fix(buttons): 修复 RippleButton 文字显示问题并解决 ESLint 错误
修复了 RippleButton 组件因 CVA 默认样式与自定义 className 冲突导致的文字不可见问题。 同时修复了项目中的 TypeScript 类型错误和 ESLint 规范问题。 主要修改: 1. 按钮显示修复:为使用红色文字的按钮添加 variant=outline, 为使用白色背景的按钮添加 variant=secondary 2. TypeScript 类型修复:修复 subtle-dots.tsx 中的类型定义错误, 删除不必要的 jest-dom.d.ts 文件 3. ESLint 规范修复:修复 React Hooks 使用规范问题, 将 useRef+forceUpdate 反模式改为 useState, 使用 eslint-disable 注释处理合理的 setState in effect 场景 4. 测试增强:添加按钮显示验证脚本和全面的页面按钮检查脚本
This commit is contained in:
@@ -10,9 +10,11 @@ jest.mock('next/navigation', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('next/link', () => {
|
||||
return ({ children, href }: { children: React.ReactNode; href: string }) => {
|
||||
const MockLink = ({ children, href }: { children: React.ReactNode; href: string }) => {
|
||||
return <a href={href}>{children}</a>;
|
||||
};
|
||||
MockLink.displayName = 'MockLink';
|
||||
return MockLink;
|
||||
});
|
||||
|
||||
jest.mock('framer-motion', () => ({
|
||||
|
||||
@@ -41,7 +41,7 @@ jest.mock('@/lib/constants', () => ({
|
||||
|
||||
// Mock ProductDetailClient 组件
|
||||
jest.mock('./product-detail-client', () => ({
|
||||
ProductDetailClient: ({ productId }: any) => (
|
||||
ProductDetailClient: () => (
|
||||
<div data-testid="product-detail-client">
|
||||
<h1>测试产品</h1>
|
||||
<h2>产品优势</h2>
|
||||
@@ -60,7 +60,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should render product detail page', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
const container = screen.getByText('测试产品').closest('div');
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
@@ -68,7 +68,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should render product title', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
const title = screen.getByRole('heading', { level: 1 });
|
||||
expect(title).toBeInTheDocument();
|
||||
expect(title).toHaveTextContent('测试产品');
|
||||
@@ -77,7 +77,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should render product category', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
// Mock 组件中没有产品类别,跳过此测试
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
@@ -85,7 +85,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should render product description', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
// Mock 组件中没有产品描述,跳过此测试
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
@@ -93,7 +93,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should render product overview section', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
// Mock 组件中没有产品概述,跳过此测试
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
@@ -101,7 +101,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should render product features section', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
// Mock 组件中没有核心功能,跳过此测试
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
@@ -109,7 +109,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should render product benefits', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
const benefits = screen.getByText('产品优势');
|
||||
expect(benefits).toBeInTheDocument();
|
||||
});
|
||||
@@ -117,7 +117,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should render pricing section', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
const pricing = screen.getByText('价格方案');
|
||||
expect(pricing).toBeInTheDocument();
|
||||
});
|
||||
@@ -127,7 +127,7 @@ describe('ProductDetailPage', () => {
|
||||
it('should have contact link', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
const contactLink = screen.getByRole('link', { name: /联系我们/i });
|
||||
expect(contactLink).toBeInTheDocument();
|
||||
});
|
||||
@@ -137,10 +137,10 @@ describe('ProductDetailPage', () => {
|
||||
it('should have proper heading hierarchy', async () => {
|
||||
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
|
||||
render(page);
|
||||
|
||||
|
||||
const h1 = screen.getByRole('heading', { level: 1 });
|
||||
expect(h1).toBeInTheDocument();
|
||||
|
||||
|
||||
const h2s = screen.getAllByRole('heading', { level: 2 });
|
||||
expect(h2s.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -60,6 +60,7 @@ export function SolutionDetailClient({ solutionId }: SolutionDetailClientProps)
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
variant="outline"
|
||||
className="border-2 border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A] hover:text-white px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center"
|
||||
rippleColor="rgba(196, 30, 58, 0.2)"
|
||||
>
|
||||
@@ -195,6 +196,7 @@ export function SolutionDetailClient({ solutionId }: SolutionDetailClientProps)
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
variant="secondary"
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="bg-white text-[#C41E3A] px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center w-full sm:w-auto"
|
||||
>
|
||||
|
||||
+19
-18
@@ -5,6 +5,7 @@
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-chinese: var(--font-noto-sans-sc);
|
||||
--font-calligraphy: 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif;
|
||||
--font-brand: var(--font-aoyagi-reisho), 'Aoyagi Reisho', 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif;
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -202,24 +203,6 @@
|
||||
input:focus, textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 马善政行书体 - 用于红色关键词高亮 */
|
||||
.font-calligraphy {
|
||||
font-family: var(--font-ma-shan-zheng), 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
|
||||
font-weight: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/* 青柳隷書 - 仅用于品牌标题"睿新致远" */
|
||||
.font-brand {
|
||||
font-family: var(--font-aoyagi-reisho), 'Aoyagi Reisho', 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
|
||||
font-weight: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: var(--color-text-primary);
|
||||
@@ -227,6 +210,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 马善政行书体 - 用于红色关键词高亮 */
|
||||
@utility font-calligraphy {
|
||||
font-family: var(--font-ma-shan-zheng), 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
|
||||
font-weight: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/* 青柳隷書 - 仅用于品牌标题"睿新致远" */
|
||||
@utility font-brand {
|
||||
font-family: var(--font-aoyagi-reisho), 'Aoyagi Reisho', 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
|
||||
font-weight: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.container-narrow {
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user