import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; import { readStyleSource } from './style-source.js'; import { autoServer, filterServers, groupServers, parseServerPingResults, SERVER_RESULT_WINDOW, } from '../../.test-dist/src/web/features/servers/serverPickerModel.js'; const root = path.resolve(import.meta.dirname, '../..'); const picker = fs.readFileSync(path.join(root, 'src/web/features/servers/ServerPicker.tsx'), 'utf8'); const boundary = fs.readFileSync(path.join(root, 'src/web/features/servers/index.ts'), 'utf8'); const overview = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8'); const styles = readStyleSource(root); test('server picker has one public feature owner without legacy shims', () => { assert.equal(boundary.trim(), "export { ServerPicker } from './ServerPicker.js';"); assert.equal(fs.existsSync(path.join(root, 'src/web/components/ServerPicker.jsx')), false); assert.equal(fs.existsSync(path.join(root, 'src/web/utils/serverPicker.js')), false); assert.match(overview, /import \{ ServerPicker \} from '\.\.\/features\/servers\/index\.js'/); assert.equal((overview.match(/ Array.from({ length: count }, (_, index) => ({ id: `srv-${String(count - index).padStart(3, '0')}`, label: index < 2 ? 'Duplicate' : `Server ${index}`, host: `node-${index}.example`, country: index % 2 ? 'NL' : 'DE', provider: index % 3 ? 'Harbor' : 'Other', protocol: index % 2 ? 'vless' : 'trojan', })); test('server picker handles 1, 30 and 300 stable-ID servers with duplicate labels', () => { for (const count of [1, 30, 300]) { const servers = fixtures(count); assert.equal(filterServers(servers, '').length, count); assert.equal(new Set(servers.map(({ id }) => id)).size, count); } assert.equal(filterServers(fixtures(30), 'node-12.example')[0].id, 'srv-018'); assert.equal(filterServers(fixtures(30), 'trojan').length, 15); assert.equal(groupServers(fixtures(30)).length, 2); assert.equal(autoServer(fixtures(30)).id, 'srv-001'); assert.equal(SERVER_RESULT_WINDOW, 60); }); test('server picker validates unknown ping payloads before publishing results', () => { const result = { id: 'srv-1', ok: true, latency: 12, checkedAt: '2026-08-08T12:00:00.000Z', extra: 'kept' }; const failed = { id: 'srv-2', ok: false, latency: null, error: 'timeout', checkedAt: '2026-08-08T12:00:01.000Z', extra: 'kept' }; assert.deepEqual(parseServerPingResults({}), []); assert.equal(parseServerPingResults({ results: [result] })[0], result); assert.equal(parseServerPingResults({ results: [failed] })[0], failed); for (const payload of [ null, [], { results: null }, { results: [{}] }, { results: [{ id: '', ok: true }] }, { results: [{ id: 'srv-1', ok: 'yes' }] }, { results: [{ id: 'srv-1', latency: -1 }] }, { results: [{ id: 'srv-1', checkedAt: 123 }] }, ]) { assert.throws(() => parseServerPingResults(payload), TypeError); } assert.match(picker, /parseServerPingResults\(await pingServers\(profileId, ids\)\)/); }); test('server picker checks health only on manual refresh and bounds the result window', () => { assert.doesNotMatch(overview, /pingAll|servers\.ping/); assert.doesNotMatch(picker, /checkVisible\(\);/); assert.match(picker, /onClick={checkVisible}/); assert.match(picker, /\{ \.\.\.current\[id\], checking: true \}/); assert.match(picker, /Math\.max\(900, Math\.ceil\(elapsed \/ 900\) \* 900\)/); assert.match(picker, /\{ \.\.\.current\[id\], checking: false \}/); assert.match(picker, /\.slice\(page \* SERVER_RESULT_WINDOW, \(page \+ 1\) \* SERVER_RESULT_WINDOW\)/); assert.match(picker, /\.slice\(0, 30\)/); assert.match(picker, /Math\.min\(index, 7\)/); assert.match(picker, /type="search"/); assert.match(picker, /harbor-server-favorites/); assert.match(picker, /harbor-server-recent/); assert.match(picker, /aria-expanded={!isCollapsed}/); }); test('manual ping uses plain language and keeps results beside server names', () => { assert.match(picker, /function ServerHealth/); assert.match(picker, /Проверяем…<\/span>/); assert.match(picker, /function ServerCheckButton/); assert.match(picker, /client-server-check\$\{checking \? ' is-checking' : ''\}/); assert.match(picker, //); assert.doesNotMatch(picker, /client-tooltip/); assert.doesNotMatch(picker, /TCP|Не проверен/); assert.match(picker, /\{server\.label\}<\/strong>[\s\S]*?<\/button>[\s\S]*?client-server-meta/); assert.match(picker, /\[server\.city, server\.country\]\.filter\(Boolean\)\.join\(' · '\)/); assert.match(picker, /is-scalable\$\{servers\.length <= SIMPLE_SERVER_LIMIT \? ' is-short' : ''\}/); assert.match(styles, /\.client-server-row \{[\s\S]*?grid-template-columns: minmax\(0, 1fr\) 120px minmax\(0, 1fr\);/); assert.match(styles, /\.client-server-row \.client-server \{[\s\S]*?width: 120px;/); assert.match(styles, /\.client-server-health \{[\s\S]*?font-size: 9px;[\s\S]*?font-variant-numeric: tabular-nums;/); assert.match(styles, /\.client-server-meta \.client-server-health \{[\s\S]*?place-items: end start;[\s\S]*?padding-bottom: 7px;/); assert.match(styles, /\.client-server-health\.is-checking \.client-server-health-checking \{[\s\S]*?animation: client-spin 900ms linear infinite;/); assert.match(styles, /\.client-server-check\.is-checking svg \{[\s\S]*?animation: client-spin 900ms linear infinite;/); assert.match(styles, /\.client-server-check:hover:not\(:disabled\) svg[\s\S]*?transform: rotate\(90deg\);/); assert.match(styles, /\.client-server-meta \{[\s\S]*?width: 42px;[\s\S]*?margin-left: 4px;/); assert.match(styles, /\.client-server-favorite \{[\s\S]*?position: absolute;[\s\S]*?width: 44px;/); assert.match(picker, /server={servers\[0\]}[\s\S]*?ping={pings\[servers\[0\]\.id\]}/); assert.match(picker, /simpleServers\.map[\s\S]*?ping={pings\[server\.id\]}/); }); test('server picker starts simple and reveals advanced controls on demand', () => { assert.match(picker, /const \[advanced, setAdvanced\] = useState\(false\)/); assert.match(picker, /aria-expanded={advanced}/); assert.match(picker, /setAdvanced\(\(current\) => !current\)/); assert.match(picker, /Поиск и фильтры<\/span>/); assert.match(picker, /client-server-toolbar-title">Список серверов/); assert.doesNotMatch(picker, /client-server-toolbar-actions/); assert.match(styles, /\.client-servers\.is-scalable \{[\s\S]*?width: min\(100%, 300px\);/); assert.match(styles, /\.client-server-mode-toggle \{[\s\S]*?grid-row: 2;/); assert.match(styles, /\.client-server-check \{[\s\S]*?grid-column: 3;[\s\S]*?grid-row: 1;/); assert.match(styles, /\.client-server-auto \{[\s\S]*?width: 220px;[\s\S]*?margin-inline: auto;/); assert.match(picker, /client-server-mode-panel is-simple/); assert.match(picker, /client-server-mode-panel is-advanced/); assert.match(picker, /inert={advanced \? true : undefined}/); assert.match(picker, /inert={!advanced \? true : undefined}/); assert.match(picker, /const SIMPLE_SERVER_LIMIT = 5/); assert.match(picker, /\.slice\(0, SIMPLE_SERVER_LIMIT\)/); assert.match(picker, /\{selected &&