import assert from 'node:assert/strict'; import http from 'node:http'; import test from 'node:test'; import { createLatestRequest, createOperationRegistry, OPERATION_CONFLICTS, operationBlocked, } from '../../src/web/state/operations.js'; const deferred = () => { let resolve; const promise = new Promise((done) => { resolve = done; }); return { promise, resolve }; }; test('operation conflicts block domain controls but leave copy and navigation alone', () => { for (const [key, conflicts] of Object.entries(OPERATION_CONFLICTS)) { for (const conflict of conflicts) { assert.ok(OPERATION_CONFLICTS[conflict].includes(key), `${key} -> ${conflict} is not symmetric`); } } const refreshing = { subscriptionRefresh: { status: 'running' } }; assert.equal(operationBlocked(refreshing, 'connection'), true); assert.equal(operationBlocked(refreshing, 'serverApply'), true); assert.equal(operationBlocked(refreshing, 'subscriptionDelete'), true); assert.equal(operationBlocked(refreshing, 'copy'), false); assert.equal(operationBlocked(refreshing, 'navigation'), false); const applying = { serverApply: { status: 'running' } }; assert.equal(operationBlocked(applying, 'connection'), true); assert.equal(operationBlocked(applying, 'subscriptionRefresh'), true); assert.equal(operationBlocked(applying, 'copy'), false); }); test('double click shares one in-flight request end to end', async (t) => { let requests = 0; const server = http.createServer((request, response) => { requests += 1; setTimeout(() => { response.writeHead(200, { 'content-type': 'application/json' }); response.end('{"success":true}'); }, 20); }); await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); t.after(() => new Promise((resolve) => server.close(resolve))); const registry = createOperationRegistry(); const action = () => fetch(`http://127.0.0.1:${server.address().port}/apply`).then((response) => response.json()); const first = registry.run('serverApply', action); const second = registry.run('serverApply', action); assert.equal(second, first); assert.equal(registry.getSnapshot().serverApply.status, 'running'); assert.deepEqual(await first, { success: true }); assert.equal(requests, 1); assert.deepEqual(registry.getSnapshot(), {}); }); test('a conflicting operation is rejected before its action starts', async () => { const connection = deferred(); const registry = createOperationRegistry(); const running = registry.run('connection', () => connection.promise); let applyCalls = 0; const result = registry.run('serverApply', () => { applyCalls += 1; }); assert.equal(await result, false); assert.equal(applyCalls, 0); connection.resolve(true); await running; }); test('latest request cancels and ignores a stale validation result', async () => { const latest = createLatestRequest(); const oldResult = deferred(); const newResult = deferred(); let oldSignal; const oldRequest = latest.run((signal) => { oldSignal = signal; return oldResult.promise; }); const newRequest = latest.run(() => newResult.promise); oldResult.resolve('old'); newResult.resolve('new'); assert.equal(await oldRequest, undefined); assert.equal(await newRequest, 'new'); assert.equal(oldSignal.aborted, true); });