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, /