diff --git a/frontend/src/test/__snapshots__/inbound-form-modal.test.tsx.snap b/frontend/src/test/__snapshots__/inbound-form-modal.test.tsx.snap index 91727e44..9e97d979 100644 --- a/frontend/src/test/__snapshots__/inbound-form-modal.test.tsx.snap +++ b/frontend/src/test/__snapshots__/inbound-form-modal.test.tsx.snap @@ -1,6 +1,132 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`InboundFormModal > add-mode field structure is stable 1`] = ` +exports[`InboundFormModal > field structure is stable for every protocol > http 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > hysteria 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > mixed 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > shadowsocks 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > trojan 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > tun 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > tunnel 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > vless 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > vmess 1`] = ` +[ + "Enabled", + "Remark", + "Protocol", + "Address", + "Port", + "Total Flow", + "Traffic Reset", + "Duration", + "Enabled", +] +`; + +exports[`InboundFormModal > field structure is stable for every protocol > wireguard 1`] = ` [ "Enabled", "Remark", diff --git a/frontend/src/test/__snapshots__/outbound-form-modal.test.tsx.snap b/frontend/src/test/__snapshots__/outbound-form-modal.test.tsx.snap index 2d6f6014..6b555f64 100644 --- a/frontend/src/test/__snapshots__/outbound-form-modal.test.tsx.snap +++ b/frontend/src/test/__snapshots__/outbound-form-modal.test.tsx.snap @@ -1,6 +1,132 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`OutboundFormModal > add-mode field structure is stable 1`] = ` +exports[`OutboundFormModal > field structure is stable for every protocol > blackhole 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > dns 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > freedom 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > hysteria 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > shadowsocks 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > socks 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > trojan 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > vless 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > vmess 1`] = ` +[ + "Protocol", + "Tag", + "Send Through", + "Address", + "Port", + "ID", + "Encryption", + "Reverse tag", + "Mux", +] +`; + +exports[`OutboundFormModal > field structure is stable for every protocol > wireguard 1`] = ` [ "Protocol", "Tag", diff --git a/frontend/src/test/inbound-form-modal.test.tsx b/frontend/src/test/inbound-form-modal.test.tsx index c1e50f6d..534aa4e6 100644 --- a/frontend/src/test/inbound-form-modal.test.tsx +++ b/frontend/src/test/inbound-form-modal.test.tsx @@ -1,7 +1,12 @@ import { describe, it, expect } from 'vitest'; import InboundFormModal from '@/pages/inbounds/form/InboundFormModal'; -import { renderWithProviders, fieldLabels } from './test-utils'; +import { + renderWithProviders, + fieldLabels, + listSelectOptions, + chooseSelectOption, +} from './test-utils'; function renderModal() { return renderWithProviders( @@ -24,8 +29,13 @@ describe('InboundFormModal', () => { expect(fieldLabels().length).toBeGreaterThan(0); }); - it('add-mode field structure is stable', () => { + it('field structure is stable for every protocol', () => { renderModal(); - expect(fieldLabels()).toMatchSnapshot(); + const protocols = listSelectOptions('protocol'); + expect(protocols.length).toBeGreaterThan(3); + for (const proto of protocols) { + chooseSelectOption('protocol', proto); + expect(fieldLabels()).toMatchSnapshot(proto); + } }); }); diff --git a/frontend/src/test/outbound-form-modal.test.tsx b/frontend/src/test/outbound-form-modal.test.tsx index 781b6921..a469003d 100644 --- a/frontend/src/test/outbound-form-modal.test.tsx +++ b/frontend/src/test/outbound-form-modal.test.tsx @@ -1,7 +1,12 @@ import { describe, it, expect } from 'vitest'; import OutboundFormModal from '@/pages/xray/outbounds/OutboundFormModal'; -import { renderWithProviders, fieldLabels } from './test-utils'; +import { + renderWithProviders, + fieldLabels, + listSelectOptions, + chooseSelectOption, +} from './test-utils'; function renderModal(outbound: Record | null = null) { return renderWithProviders( @@ -22,8 +27,13 @@ describe('OutboundFormModal', () => { expect(fieldLabels().length).toBeGreaterThan(0); }); - it('add-mode field structure is stable', () => { + it('field structure is stable for every protocol', () => { renderModal(null); - expect(fieldLabels()).toMatchSnapshot(); + const protocols = listSelectOptions('protocol'); + expect(protocols.length).toBeGreaterThan(3); + for (const proto of protocols) { + chooseSelectOption('protocol', proto); + expect(fieldLabels()).toMatchSnapshot(proto); + } }); }); diff --git a/frontend/src/test/setup.components.ts b/frontend/src/test/setup.components.ts index 0cc5dc75..45ea0ea1 100644 --- a/frontend/src/test/setup.components.ts +++ b/frontend/src/test/setup.components.ts @@ -60,4 +60,5 @@ if (!i18next.isInitialized) { afterEach(() => { cleanup(); + document.body.innerHTML = ''; }); diff --git a/frontend/src/test/test-utils.tsx b/frontend/src/test/test-utils.tsx index 3a4f2960..d0640eaa 100644 --- a/frontend/src/test/test-utils.tsx +++ b/frontend/src/test/test-utils.tsx @@ -1,5 +1,5 @@ import type { ReactElement } from 'react'; -import { render } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import { ThemeProvider } from '@/hooks/useTheme'; @@ -12,3 +12,40 @@ export function fieldLabels(): string[] { .map((el) => (el.textContent ?? '').trim()) .filter(Boolean); } + +function selectRootForField(fieldId: string): HTMLElement { + const control = document.getElementById(fieldId); + const select = control?.closest('.ant-select') as HTMLElement | null; + if (!select) throw new Error(`Select not found for field id: ${fieldId}`); + return select; +} + +function openSelect(select: HTMLElement) { + const target = (select.querySelector('.ant-select-selector') ?? select) as HTMLElement; + fireEvent.mouseDown(target); +} + +function openDropdownOptions(): string[] { + return Array.from( + document.querySelectorAll('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option'), + ) + .map((o) => (o.getAttribute('title') ?? o.textContent ?? '').trim()) + .filter(Boolean); +} + +export function listSelectOptions(fieldId: string): string[] { + const select = selectRootForField(fieldId); + openSelect(select); + const opts = openDropdownOptions(); + fireEvent.keyDown(select, { key: 'Escape' }); + return opts; +} + +export function chooseSelectOption(fieldId: string, optionText: string) { + const select = selectRootForField(fieldId); + openSelect(select); + const option = Array.from(document.querySelectorAll('.ant-select-item-option')) + .find((o) => (o.getAttribute('title') ?? o.textContent ?? '').trim() === optionText); + if (!option) throw new Error(`Option '${optionText}' not found for field '${fieldId}'`); + fireEvent.click(option); +}