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:
张翔
2026-04-27 16:27:35 +08:00
parent e83ecddfe5
commit 1832640e8f
20 changed files with 395 additions and 134 deletions
@@ -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', () => ({
+12 -12
View File
@@ -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
View File
@@ -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%;