import test from 'node:test' import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { filterAndSortFleet, emptyFleetReason } from '../public/state/fleet-view-model.js' import { instanceDraftPayload, requestDraft } from '../public/state/operation-draft.js' import { resolveView } from '../public/router/view-router.js' const root = new URL('../', import.meta.url) const text = path => readFile(new URL(path, root), 'utf8') test('fleet view model filters, searches and sorts without mutating source', () => { const source = [{ id: 'z', name: 'Zulu', url: 'http://z', tags: ['备用'] }, { id: 'a', name: 'Alpha', url: 'http://a', tags: [] }] const statuses = new Map([['z', { reachable: false, latencyMs: 80 }], ['a', { reachable: true, authenticated: true, latencyMs: 12 }]]) assert.deepEqual(filterAndSortFleet(source, statuses, { filter: 'online', sort: 'latency' }).map(x => x.id), ['a']) assert.deepEqual(filterAndSortFleet(source, statuses, { query: '备用', sort: 'name' }).map(x => x.id), ['z']) assert.deepEqual(source.map(x => x.id), ['z', 'a']) assert.equal(emptyFleetReason({ total: 0 }), 'config') assert.equal(emptyFleetReason({ total: 2, query: 'x', visible: 0 }), 'search') assert.equal(emptyFleetReason({ total: 2, filter: 'offline', visible: 0 }), 'filter') }) test('operation drafts enforce explicit password and request semantics', () => { assert.deepEqual(instanceDraftPayload({ id: 'one', name: 'One', url: 'http://one', passwordAction: 'preserve' }, true).passwordAction, 'preserve') assert.throws(() => instanceDraftPayload({ id: 'one', name: 'One', url: 'http://one', passwordAction: 'set', password: '' }, true), /密码/) assert.throws(() => instanceDraftPayload({ id: 'one', name: 'One', url: 'http://one', passwordAction: 'clear' }, true), /确认/) assert.deepEqual(requestDraft('GET', '/api/device', { ignored: true }), { method: 'GET', path: '/api/device' }) assert.equal(requestDraft('PATCH', '/api/data', { enabled: true }).body.enabled, true) }) test('protected views fall back home without an active device', () => { for (const view of ['detail', 'rawpage', 'api']) assert.equal(resolveView(view, false), 'home') assert.equal(resolveView('rawpage', true), 'rawpage') }) test('phase two shell, CRUD, API console and accessibility contracts exist', async () => { const [html, app, css, responsive, api] = await Promise.all([ text('public/index.html'), text('public/app.js'), text('public/styles/components.css'), text('public/styles/responsive.css'), text('public/infrastructure/api-client.js'), ]) assert.doesNotMatch(html, /fonts\.googleapis|fonts\.gstatic/) assert.match(html, /class="desktop-sidebar"/) assert.match(html, /class="mobile-nav"/) assert.match(html, /aria-live="polite"[^>]*role="status"/) assert.match(html, /id="fatalState"[^>]*role="alert"/) assert.match(html, /id="persistentError"[^>]*role="alert"/) assert.match(html, /id="deleteDialog"[\s\S]*aria-labelledby="deleteDialogTitle"/) assert.match(html, /id="apiDeleteDialog"[\s\S]*aria-labelledby="apiDeleteTitle"/) assert.match(html, /id="passwordPreserve"[\s\S]*id="passwordSet"[\s\S]*id="passwordClear"/) assert.match(html, /id="endpointSearch"/) assert.match(html, /id="outputPretty"[\s\S]*id="outputRaw"[\s\S]*id="copyOutput"/) assert.match(html, /