码桶
发现社区成员的开源项目
DnsRecordsTab.test.jsx5.9 KB
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import DnsRecordsTab from '../../src/components/DnsRecordsTab.jsx';
// localStorage mock
beforeEach(() => {
vi.spyOn(Storage.prototype, 'getItem').mockReturnValue('none');
vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
});
const t = (key) => key;
const mockZone = { id: 'zone-1', name: 'example.com' };
const mockRecords = [
{ id: 'r1', type: 'A', name: 'example.com', content: '1.2.3.4', ttl: 1, proxied: true, tags: [] },
{ id: 'r2', type: 'CNAME', name: 'www.example.com', content: 'example.com', ttl: 3600, proxied: false, tags: [] },
{ id: 'r3', type: 'MX', name: 'example.com', content: 'mail.example.com', ttl: 1, proxied: false, priority: 10, tags: [] },
];
const defaultProps = {
zone: mockZone,
records: mockRecords,
setRecords: vi.fn(),
filteredRecords: mockRecords,
loading: false,
searchTerm: '',
setSearchTerm: vi.fn(),
selectedRecords: new Set(),
setSelectedRecords: vi.fn(),
fetchDNS: vi.fn(),
onOpenAddRecord: vi.fn(),
onOpenEditRecord: vi.fn(),
onOpenBulkImport: vi.fn(),
onShowHistory: vi.fn(),
onUpdatePriority: vi.fn(),
getHeaders: vi.fn(() => ({})),
t,
showToast: vi.fn(),
openConfirm: vi.fn(),
};
describe('DnsRecordsTab component', () => {
it('renders without crashing', () => {
render(<DnsRecordsTab {...defaultProps} />);
});
it('renders records table with correct headers', () => {
render(<DnsRecordsTab {...defaultProps} />);
// Table headers are rendered via t() keys
expect(screen.getAllByText('type').length).toBeGreaterThan(0);
expect(screen.getAllByText('content').length).toBeGreaterThan(0);
expect(screen.getAllByText('ttl').length).toBeGreaterThan(0);
expect(screen.getAllByText('proxied').length).toBeGreaterThan(0);
expect(screen.getAllByText('actions').length).toBeGreaterThan(0);
});
it('renders record type badges', () => {
render(<DnsRecordsTab {...defaultProps} />);
expect(screen.getAllByText('A').length).toBeGreaterThan(0);
expect(screen.getAllByText('CNAME').length).toBeGreaterThan(0);
expect(screen.getAllByText('MX').length).toBeGreaterThan(0);
});
it('renders record names', () => {
render(<DnsRecordsTab {...defaultProps} />);
expect(screen.getAllByText('example.com').length).toBeGreaterThan(0);
expect(screen.getAllByText('www.example.com').length).toBeGreaterThan(0);
});
it('renders record content', () => {
render(<DnsRecordsTab {...defaultProps} />);
expect(screen.getAllByText('1.2.3.4').length).toBeGreaterThan(0);
expect(screen.getAllByText('mail.example.com').length).toBeGreaterThan(0);
});
it('shows empty state when filteredRecords is empty', () => {
render(
<DnsRecordsTab
{...defaultProps}
records={[]}
filteredRecords={[]}
/>
);
// Table body should have no record rows (no type badges rendered)
expect(screen.queryByText('1.2.3.4')).not.toBeInTheDocument();
});
it('renders group by toggle buttons', () => {
render(<DnsRecordsTab {...defaultProps} />);
// The three group-by options should be present
expect(screen.getByText('groupByNone')).toBeInTheDocument();
expect(screen.getByText('groupByType')).toBeInTheDocument();
expect(screen.getByText('groupBySubdomain')).toBeInTheDocument();
});
it('group by type works when button is clicked', () => {
render(<DnsRecordsTab {...defaultProps} />);
const typeButton = screen.getByText('groupByType');
fireEvent.click(typeButton);
// After clicking, localStorage should be updated
expect(Storage.prototype.setItem).toHaveBeenCalledWith('dns_group_by', 'type');
});
it('shows TTL auto text for ttl=1', () => {
render(<DnsRecordsTab {...defaultProps} />);
// Records with ttl === 1 show t('ttlAuto')
expect(screen.getAllByText('ttlAuto').length).toBeGreaterThan(0);
});
it('shows numeric TTL for non-auto values', () => {
render(<DnsRecordsTab {...defaultProps} />);
expect(screen.getAllByText('3600').length).toBeGreaterThan(0);
});
it('inline edit triggers on double-click of content cell', () => {
render(<DnsRecordsTab {...defaultProps} />);
// Find a content cell (by the record content text, then its parent cell)
const contentCells = document.querySelectorAll('.inline-edit-cell');
expect(contentCells.length).toBeGreaterThan(0);
// Double-click the first content cell
fireEvent.doubleClick(contentCells[0]);
// After double-click, an input should appear in the inline-edit-wrapper
const editInput = document.querySelector('.inline-edit-wrapper input');
expect(editInput).not.toBeNull();
});
it('renders checkboxes for record selection', () => {
render(<DnsRecordsTab {...defaultProps} />);
// Each record row has a checkbox, plus the header checkbox
const checkboxes = screen.getAllByRole('checkbox');
// At minimum: 1 header + number of records (desktop table)
expect(checkboxes.length).toBeGreaterThanOrEqual(mockRecords.length + 1);
});
it('renders edit and delete buttons for each record', () => {
render(<DnsRecordsTab {...defaultProps} />);
// Each record has an edit and delete button with aria-labels
for (const record of mockRecords) {
const editButtons = screen.getAllByLabelText(`Edit ${record.name}`);
expect(editButtons.length).toBeGreaterThan(0);
const deleteButtons = screen.getAllByLabelText(`Delete ${record.name}`);
expect(deleteButtons.length).toBeGreaterThan(0);
}
});
});