chore: remove GitHub Actions workflows, use Woodpecker CI exclusively
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
import { describe, it, expect, jest, beforeAll, afterEach } from '@jest/globals';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('framer-motion', () => ({
|
||||
motion: {
|
||||
div: ({ children, className, ...props }: any) => (
|
||||
<div className={className} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
section: ({ children, className, ...props }: any) => (
|
||||
<section className={className} {...props}>
|
||||
{children}
|
||||
</section>
|
||||
),
|
||||
},
|
||||
AnimatePresence: ({ children }: any) => <>{children}</>,
|
||||
}));
|
||||
|
||||
jest.mock('lucide-react', () => ({
|
||||
Mail: () => <span data-testid="mail-icon" />,
|
||||
Phone: () => <span data-testid="phone-icon" />,
|
||||
MapPin: () => <span data-testid="map-pin-icon" />,
|
||||
Send: () => <span data-testid="send-icon" />,
|
||||
Loader2: () => <span data-testid="loader-icon" />,
|
||||
Clock: () => <span data-testid="clock-icon" />,
|
||||
HeadphonesIcon: () => <span data-testid="headphones-icon" />,
|
||||
CheckCircle2: () => <span data-testid="check-circle-icon" />,
|
||||
}));
|
||||
|
||||
jest.mock('@/lib/sanitize', () => ({
|
||||
sanitizeInput: (value: string) => value,
|
||||
}));
|
||||
|
||||
jest.mock('@/lib/csrf', () => ({
|
||||
generateCSRFToken: jest.fn(() => 'test-csrf-token'),
|
||||
setCSRFTokenToStorage: jest.fn(),
|
||||
getCSRFTokenFromStorage: jest.fn(() => 'test-csrf-token'),
|
||||
}));
|
||||
|
||||
jest.mock('@/lib/constants', () => ({
|
||||
COMPANY_INFO: {
|
||||
name: '四川睿新致远科技有限公司',
|
||||
email: 'contact@novalon.cn',
|
||||
phone: '028-88888888',
|
||||
address: '中国四川省成都市龙泉驿区幸福路12号',
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/button', () => ({
|
||||
Button: ({ children, className, disabled, ...props }: any) => (
|
||||
<button className={className} disabled={disabled} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/input', () => ({
|
||||
Input: ({ label, id, placeholder, required, value, onChange, onBlur, error, ...props }: any) => (
|
||||
<div>
|
||||
<label htmlFor={id}>{label}{required && '*'}</label>
|
||||
<input
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
data-testid={`${id}-input`}
|
||||
{...props}
|
||||
/>
|
||||
{error && <span data-testid={`${id}-error`}>{error}</span>}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/textarea', () => ({
|
||||
Textarea: ({ label, id, placeholder, rows, required, value, onChange, onBlur, error, ...props }: any) => (
|
||||
<div>
|
||||
<label htmlFor={id}>{label}{required && '*'}</label>
|
||||
<textarea
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
rows={rows}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
data-testid={`${id}-input`}
|
||||
{...props}
|
||||
/>
|
||||
{error && <span data-testid={`${id}-error`}>{error}</span>}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/toast', () => ({
|
||||
Toast: ({ message, type, onClose, ...props }: any) => (
|
||||
<div data-testid="toast-notification" data-type={type} {...props}>
|
||||
{message}
|
||||
<button onClick={onClose}>关闭</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
import { ContactSection } from './contact-section';
|
||||
|
||||
describe('ContactSection', () => {
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render contact section', () => {
|
||||
render(<ContactSection />);
|
||||
const section = document.querySelector('section#contact');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render contact form', () => {
|
||||
render(<ContactSection />);
|
||||
expect(screen.getByTestId('name-input')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('phone-input')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('email-input')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('message-input')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render submit button', () => {
|
||||
render(<ContactSection />);
|
||||
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render company contact information', () => {
|
||||
render(<ContactSection />);
|
||||
expect(screen.getByText('contact@novalon.cn')).toBeInTheDocument();
|
||||
expect(screen.getByText('028-88888888')).toBeInTheDocument();
|
||||
expect(screen.getByText('中国四川省成都市龙泉驿区幸福路12号')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render work hours card', () => {
|
||||
render(<ContactSection />);
|
||||
expect(screen.getByTestId('work-hours-card')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Form Validation', () => {
|
||||
it('should show error for invalid name', async () => {
|
||||
render(<ContactSection />);
|
||||
const nameInput = screen.getByTestId('name-input');
|
||||
|
||||
await userEvent.type(nameInput, '张');
|
||||
fireEvent.blur(nameInput);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('name-error')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show error for invalid phone', async () => {
|
||||
render(<ContactSection />);
|
||||
const phoneInput = screen.getByTestId('phone-input');
|
||||
|
||||
await userEvent.type(phoneInput, '1234567890');
|
||||
fireEvent.blur(phoneInput);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('phone-error')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show error for invalid email', async () => {
|
||||
render(<ContactSection />);
|
||||
const emailInput = screen.getByTestId('email-input');
|
||||
|
||||
await userEvent.type(emailInput, 'invalid-email');
|
||||
fireEvent.blur(emailInput);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('email-error')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show error for short message', async () => {
|
||||
render(<ContactSection />);
|
||||
const messageInput = screen.getByTestId('message-input');
|
||||
|
||||
await userEvent.type(messageInput, '短留言');
|
||||
fireEvent.blur(messageInput);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('message-error')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper form labels', () => {
|
||||
render(<ContactSection />);
|
||||
|
||||
expect(screen.getByLabelText(/姓名/)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/电话/)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/邮箱/)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/留言/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have proper ARIA attributes', () => {
|
||||
render(<ContactSection />);
|
||||
const section = document.querySelector('section#contact');
|
||||
expect(section).toHaveAttribute('role', 'region');
|
||||
expect(section).toHaveAttribute('aria-labelledby', 'contact-heading');
|
||||
});
|
||||
});
|
||||
|
||||
describe('CSRF Protection', () => {
|
||||
it('should generate CSRF token on mount', () => {
|
||||
const { generateCSRFToken, setCSRFTokenToStorage } = require('@/lib/csrf');
|
||||
render(<ContactSection />);
|
||||
|
||||
expect(generateCSRFToken).toHaveBeenCalled();
|
||||
expect(setCSRFTokenToStorage).toHaveBeenCalledWith('test-csrf-token');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user