chore: remove GitHub Actions workflows, use Woodpecker CI exclusively
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
jest.mock('next/link', () => {
|
||||
return ({ children, href, ...props }: any) => (
|
||||
<a href={href} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
jest.mock('next/image', () => {
|
||||
return ({ src, alt, width, height, className, ...props }: any) => (
|
||||
<img src={src} alt={alt} width={width} height={height} className={className} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
jest.mock('lucide-react', () => ({
|
||||
Mail: () => <span data-testid="mail-icon" />,
|
||||
Phone: () => <span data-testid="phone-icon" />,
|
||||
MapPin: () => <span data-testid="map-pin-icon" />,
|
||||
}));
|
||||
|
||||
jest.mock('@/lib/constants', () => ({
|
||||
COMPANY_INFO: {
|
||||
name: '四川睿新致远科技有限公司',
|
||||
description: '以智慧连接数字趋势,以伙伴身份陪您成长',
|
||||
email: 'contact@novalon.cn',
|
||||
phone: '028-88888888',
|
||||
address: '中国四川省成都市龙泉驿区幸福路12号',
|
||||
icp: '蜀ICP备XXXXXXXX号',
|
||||
police: '川公网安备XXXXXXXXXXX号',
|
||||
},
|
||||
NAVIGATION: [
|
||||
{ id: 'home', label: '首页', href: '/' },
|
||||
{ id: 'services', label: '服务', href: '/#services' },
|
||||
{ id: 'products', label: '产品', href: '/#products' },
|
||||
{ id: 'cases', label: '案例', href: '/#cases' },
|
||||
{ id: 'about', label: '关于', href: '/#about' },
|
||||
{ id: 'contact', label: '联系', href: '/contact' },
|
||||
],
|
||||
}));
|
||||
|
||||
import { Footer } from './footer';
|
||||
|
||||
describe('Footer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render footer component', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByTestId('footer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render logo', () => {
|
||||
render(<Footer />);
|
||||
const logo = screen.getByAltText('四川睿新致远科技有限公司');
|
||||
expect(logo).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render company description', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('以智慧连接数字趋势,以伙伴身份陪您成长')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render quick links section', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('快速链接')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render service items section', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('服务项目')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render contact information section', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('联系方式')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render navigation links', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('首页')).toBeInTheDocument();
|
||||
expect(screen.getByText('服务')).toBeInTheDocument();
|
||||
expect(screen.getByText('产品')).toBeInTheDocument();
|
||||
expect(screen.getByText('案例')).toBeInTheDocument();
|
||||
expect(screen.getByText('关于')).toBeInTheDocument();
|
||||
expect(screen.getByText('联系')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render service links', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('软件开发')).toBeInTheDocument();
|
||||
expect(screen.getByText('云服务')).toBeInTheDocument();
|
||||
expect(screen.getByText('数据分析')).toBeInTheDocument();
|
||||
expect(screen.getByText('信息安全')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render contact details', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('contact@novalon.cn')).toBeInTheDocument();
|
||||
expect(screen.getByText('028-88888888')).toBeInTheDocument();
|
||||
expect(screen.getByText('中国四川省成都市龙泉驿区幸福路12号')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Icons', () => {
|
||||
it('should render contact icons', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByTestId('mail-icon')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('phone-icon')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('map-pin-icon')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Legal Links', () => {
|
||||
it('should render privacy policy link', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('隐私政策')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render terms of service link', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('服务条款')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render ICP filing info', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('蜀ICP备XXXXXXXX号')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render police filing info', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('川公网安备XXXXXXXXXXX号')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Copyright', () => {
|
||||
it('should render copyright with current year', () => {
|
||||
render(<Footer />);
|
||||
const currentYear = new Date().getFullYear();
|
||||
expect(screen.getByText(new RegExp(`${currentYear}`))).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render company name in copyright', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText(/四川睿新致远科技有限公司/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper role attribute', () => {
|
||||
render(<Footer />);
|
||||
const footer = screen.getByTestId('footer');
|
||||
expect(footer).toHaveAttribute('role', 'contentinfo');
|
||||
});
|
||||
|
||||
it('should have proper link hrefs', () => {
|
||||
render(<Footer />);
|
||||
const privacyLink = screen.getByText('隐私政策').closest('a');
|
||||
const termsLink = screen.getByText('服务条款').closest('a');
|
||||
|
||||
expect(privacyLink).toHaveAttribute('href', '/privacy');
|
||||
expect(termsLink).toHaveAttribute('href', '/terms');
|
||||
});
|
||||
});
|
||||
|
||||
describe('QR Code Section', () => {
|
||||
it('should render QR code image', () => {
|
||||
render(<Footer />);
|
||||
const qrCode = screen.getByAltText('微信公众号二维码');
|
||||
expect(qrCode).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render QR code description', () => {
|
||||
render(<Footer />);
|
||||
expect(screen.getByText('扫码关注获取最新资讯')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,199 @@
|
||||
import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
const mockReplace = jest.fn();
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
usePathname: jest.fn(() => '/'),
|
||||
useSearchParams: jest.fn(() => new URLSearchParams()),
|
||||
useRouter: jest.fn(() => ({
|
||||
push: mockPush,
|
||||
replace: mockReplace,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('next/link', () => {
|
||||
return ({ children, href, onClick, ...props }: any) => (
|
||||
<a href={href} onClick={onClick} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
jest.mock('next/image', () => {
|
||||
return ({ src, alt, width, height, className, ...props }: any) => (
|
||||
<img src={src} alt={alt} width={width} height={height} className={className} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
jest.mock('framer-motion', () => ({
|
||||
motion: {
|
||||
div: ({ children, className, ...props }: any) => (
|
||||
<div className={className} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
AnimatePresence: ({ children }: any) => <>{children}</>,
|
||||
}));
|
||||
|
||||
jest.mock('lucide-react', () => ({
|
||||
Menu: () => <span data-testid="menu-icon" />,
|
||||
X: () => <span data-testid="x-icon" />,
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/button', () => ({
|
||||
Button: ({ children, className, asChild, ...props }: any) => (
|
||||
<button className={className} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/lib/constants', () => ({
|
||||
COMPANY_INFO: {
|
||||
name: '四川睿新致远科技有限公司',
|
||||
shortName: '睿新致遠',
|
||||
},
|
||||
NAVIGATION: [
|
||||
{ id: 'home', label: '首页', href: '/' },
|
||||
{ id: 'services', label: '服务', href: '/#services' },
|
||||
{ id: 'products', label: '产品', href: '/#products' },
|
||||
{ id: 'cases', label: '案例', href: '/#cases' },
|
||||
{ id: 'about', label: '关于', href: '/#about' },
|
||||
{ id: 'contact', label: '联系', href: '/contact' },
|
||||
],
|
||||
}));
|
||||
|
||||
jest.mock('@/hooks/use-focus-trap', () => ({
|
||||
useFocusTrap: () => ({ current: null }),
|
||||
}));
|
||||
|
||||
import { Header } from './header';
|
||||
|
||||
describe('Header', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render header component', () => {
|
||||
render(<Header />);
|
||||
expect(screen.getByRole('banner')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render logo', () => {
|
||||
render(<Header />);
|
||||
const logo = screen.getByAltText('四川睿新致远科技有限公司');
|
||||
expect(logo).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render desktop navigation', () => {
|
||||
render(<Header />);
|
||||
expect(screen.getByTestId('desktop-navigation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render navigation items', () => {
|
||||
render(<Header />);
|
||||
expect(screen.getByText('首页')).toBeInTheDocument();
|
||||
expect(screen.getByText('服务')).toBeInTheDocument();
|
||||
expect(screen.getByText('产品')).toBeInTheDocument();
|
||||
expect(screen.getByText('案例')).toBeInTheDocument();
|
||||
expect(screen.getByText('关于')).toBeInTheDocument();
|
||||
expect(screen.getByText('联系')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render consult button', () => {
|
||||
render(<Header />);
|
||||
expect(screen.getByTestId('consult-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render mobile menu button', () => {
|
||||
render(<Header />);
|
||||
expect(screen.getByTestId('mobile-menu-button')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mobile Menu', () => {
|
||||
it('should toggle mobile menu on button click', async () => {
|
||||
render(<Header />);
|
||||
const menuButton = screen.getByTestId('mobile-menu-button');
|
||||
|
||||
expect(screen.queryByTestId('mobile-navigation')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(menuButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('mobile-navigation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(menuButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('mobile-navigation')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show X icon when menu is open', async () => {
|
||||
render(<Header />);
|
||||
const menuButton = screen.getByTestId('mobile-menu-button');
|
||||
|
||||
fireEvent.click(menuButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('x-icon')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Menu icon when menu is closed', () => {
|
||||
render(<Header />);
|
||||
expect(screen.getByTestId('menu-icon')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper ARIA attributes on menu button', () => {
|
||||
render(<Header />);
|
||||
const menuButton = screen.getByTestId('mobile-menu-button');
|
||||
|
||||
expect(menuButton).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(menuButton).toHaveAttribute('aria-controls', 'mobile-menu');
|
||||
expect(menuButton).toHaveAttribute('aria-label', '打开菜单');
|
||||
});
|
||||
|
||||
it('should update aria-expanded when menu is toggled', async () => {
|
||||
render(<Header />);
|
||||
const menuButton = screen.getByTestId('mobile-menu-button');
|
||||
|
||||
fireEvent.click(menuButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(menuButton).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(menuButton).toHaveAttribute('aria-label', '关闭菜单');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have proper navigation role', () => {
|
||||
render(<Header />);
|
||||
const nav = screen.getByTestId('desktop-navigation');
|
||||
expect(nav).toHaveAttribute('role', 'navigation');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation', () => {
|
||||
it('should have correct href for navigation items', () => {
|
||||
render(<Header />);
|
||||
const homeLink = screen.getByText('首页').closest('a');
|
||||
const contactLink = screen.getByText('联系').closest('a');
|
||||
|
||||
expect(homeLink).toHaveAttribute('href', '/');
|
||||
expect(contactLink).toHaveAttribute('href', '/contact');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,158 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { MobileMenu } from './mobile-menu';
|
||||
|
||||
jest.mock('@/hooks/use-focus-trap', () => ({
|
||||
useFocusTrap: () => ({ current: null }),
|
||||
}));
|
||||
|
||||
describe('MobileMenu', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render menu button', () => {
|
||||
render(<MobileMenu />);
|
||||
expect(screen.getByRole('button', { name: '打开菜单' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render menu icon when closed', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button');
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render menu panel when closed', () => {
|
||||
render(<MobileMenu />);
|
||||
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Opening Menu', () => {
|
||||
it('should open menu when button clicked', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByRole('navigation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should change button label when open', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByRole('button', { name: '关闭菜单' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render navigation items', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('首页')).toBeInTheDocument();
|
||||
expect(screen.getByText('核心业务')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Closing Menu', () => {
|
||||
it('should close menu when button clicked again', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.click(button);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: '关闭菜单' });
|
||||
fireEvent.click(closeButton);
|
||||
|
||||
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should close menu when overlay clicked', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.click(button);
|
||||
|
||||
const overlay = document.querySelector('.fixed.inset-0');
|
||||
if (overlay) {
|
||||
fireEvent.click(overlay);
|
||||
}
|
||||
|
||||
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Keyboard Navigation', () => {
|
||||
it('should open menu with Enter key', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.keyDown(button, { key: 'Enter' });
|
||||
|
||||
expect(screen.getByRole('navigation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open menu with Space key', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.keyDown(button, { key: ' ' });
|
||||
|
||||
expect(screen.getByRole('navigation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should close menu with Escape key', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.click(button);
|
||||
|
||||
fireEvent.keyDown(button, { key: 'Escape' });
|
||||
|
||||
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have aria-expanded attribute', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button');
|
||||
expect(button).toHaveAttribute('aria-expanded', 'false');
|
||||
});
|
||||
|
||||
it('should update aria-expanded when open', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(button).toHaveAttribute('aria-expanded', 'true');
|
||||
});
|
||||
|
||||
it('should have aria-controls attribute', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button');
|
||||
expect(button).toHaveAttribute('aria-controls', 'mobile-menu-panel');
|
||||
});
|
||||
|
||||
it('should have navigation role', () => {
|
||||
render(<MobileMenu />);
|
||||
const button = screen.getByRole('button', { name: '打开菜单' });
|
||||
fireEvent.click(button);
|
||||
|
||||
const nav = screen.getByRole('navigation');
|
||||
expect(nav).toHaveAttribute('aria-label', '移动端导航');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should have responsive visibility', () => {
|
||||
const { container } = render(<MobileMenu />);
|
||||
const wrapper = container.firstChild as HTMLElement;
|
||||
expect(wrapper).toHaveClass('lg:hidden');
|
||||
});
|
||||
|
||||
it('should apply custom className', () => {
|
||||
const { container } = render(<MobileMenu className="custom-class" />);
|
||||
const wrapper = container.firstChild as HTMLElement;
|
||||
expect(wrapper).toHaveClass('custom-class');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { MobileTabBar } from './mobile-tab-bar';
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
usePathname: () => '/',
|
||||
}));
|
||||
|
||||
jest.mock('framer-motion', () => ({
|
||||
motion: {
|
||||
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('next/link', () => {
|
||||
return ({ children, href }: any) => <a href={href}>{children}</a>;
|
||||
});
|
||||
|
||||
describe('MobileTabBar', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render tab bar', () => {
|
||||
render(<MobileTabBar />);
|
||||
const nav = screen.getByRole('navigation');
|
||||
expect(nav).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render all tabs', () => {
|
||||
render(<MobileTabBar />);
|
||||
expect(screen.getByText('首页')).toBeInTheDocument();
|
||||
expect(screen.getByText('服务')).toBeInTheDocument();
|
||||
expect(screen.getByText('产品')).toBeInTheDocument();
|
||||
expect(screen.getByText('新闻')).toBeInTheDocument();
|
||||
expect(screen.getByText('联系')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render tab icons', () => {
|
||||
render(<MobileTabBar />);
|
||||
const icons = document.querySelectorAll('svg');
|
||||
expect(icons.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Active State', () => {
|
||||
it('should highlight active tab', () => {
|
||||
render(<MobileTabBar />);
|
||||
const homeTab = screen.getByText('首页').closest('a');
|
||||
expect(homeTab).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show active indicator', () => {
|
||||
render(<MobileTabBar />);
|
||||
const activeIndicator = document.querySelector('.bg-\\[\\#C41E3A\\]');
|
||||
expect(activeIndicator).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation Links', () => {
|
||||
it('should have correct href for home', () => {
|
||||
render(<MobileTabBar />);
|
||||
const homeLink = screen.getByText('首页').closest('a');
|
||||
expect(homeLink).toHaveAttribute('href', '/');
|
||||
});
|
||||
|
||||
it('should have correct href for services', () => {
|
||||
render(<MobileTabBar />);
|
||||
const servicesLink = screen.getByText('服务').closest('a');
|
||||
expect(servicesLink).toHaveAttribute('href', '/#services');
|
||||
});
|
||||
|
||||
it('should have correct href for products', () => {
|
||||
render(<MobileTabBar />);
|
||||
const productsLink = screen.getByText('产品').closest('a');
|
||||
expect(productsLink).toHaveAttribute('href', '/#products');
|
||||
});
|
||||
|
||||
it('should have correct href for news', () => {
|
||||
render(<MobileTabBar />);
|
||||
const newsLink = screen.getByText('新闻').closest('a');
|
||||
expect(newsLink).toHaveAttribute('href', '/#news');
|
||||
});
|
||||
|
||||
it('should have correct href for contact', () => {
|
||||
render(<MobileTabBar />);
|
||||
const contactLink = screen.getByText('联系').closest('a');
|
||||
expect(contactLink).toHaveAttribute('href', '/#contact');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have navigation role', () => {
|
||||
render(<MobileTabBar />);
|
||||
const nav = screen.getByRole('navigation');
|
||||
expect(nav).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have accessible tab labels', () => {
|
||||
render(<MobileTabBar />);
|
||||
const tabs = screen.getAllByRole('link');
|
||||
expect(tabs.length).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should have fixed positioning', () => {
|
||||
render(<MobileTabBar />);
|
||||
const nav = screen.getByRole('navigation');
|
||||
expect(nav).toHaveClass('fixed');
|
||||
expect(nav).toHaveClass('bottom-0');
|
||||
});
|
||||
|
||||
it('should have responsive visibility', () => {
|
||||
render(<MobileTabBar />);
|
||||
const nav = screen.getByRole('navigation');
|
||||
expect(nav).toHaveClass('md:hidden');
|
||||
});
|
||||
|
||||
it('should have backdrop blur', () => {
|
||||
render(<MobileTabBar />);
|
||||
const nav = screen.getByRole('navigation');
|
||||
expect(nav).toHaveClass('backdrop-blur-xl');
|
||||
});
|
||||
|
||||
it('should have proper height', () => {
|
||||
render(<MobileTabBar />);
|
||||
const nav = screen.getByRole('navigation');
|
||||
const heightDiv = nav.querySelector('.h-16');
|
||||
expect(heightDiv).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user