mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
test(frontend): per-protocol field-structure coverage for both form modals
- drive the protocol Select in jsdom and snapshot rendered Form.Item labels for every protocol - 10 outbound + 10 inbound protocol states captured as the regression net for protocol-core extraction - add robust select-driving helpers (test-utils) + post-test body cleanup (setup.components) - 341 tests pass; typecheck/lint green
This commit is contained in:
parent
0b130d24ac
commit
c24b7d9da3
6 changed files with 319 additions and 9 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<string, unknown> | 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,4 +60,5 @@ if (!i18next.isInitialized) {
|
|||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue