import test from 'node:test'; import assert from 'node:assert/strict'; import { harness, props, text } from './helpers/crm-task-preparation.mjs'; const form = 'new-crm-task'; const taskId = '20000000-0000-4000-8000-000000000001'; function fill(h, title = 'SYNTHETIC prepared task') { h.change(form, 'Tarefa', title); h.change(form, 'Prazo (hora local)', '2027-11-04T10:30'); } function savedContext(h, call = h.draftRequests.at(-1)) { return { ...h.props.taskCreation, preparation: { installed: true, version: call.data.version + 1, operation_key: call.data.operation_key, snapshot: call.data.snapshot, unavailable: false, updated_at: '2026-11-03T12:00:00Z', }, }; } function pageWith(context, patch = {}) { return props({ taskCreation: context, ...patch }); } test('automatic preparation survives a new page instance and adoption is explicit', async () => { const first = harness(); fill(first); await first.tick(); assert.equal(first.sent.length, 0); assert.equal(first.draftRequests.length, 1); const context = savedContext(first); await first.respond(context); const next = harness(pageWith(context)); assert.equal(next.field(form, 'Tarefa').props.value, ''); assert.ok(next.button('Comparar preparação guardada')); next.click('Comparar preparação guardada'); next.click('Aplicar escolhas e retomar'); assert.equal( next.field(form, 'Tarefa').props.value, 'SYNTHETIC prepared task', ); assert.equal( new Date( next.field(form, 'Prazo (hora local)').props.value, ).toISOString(), context.preparation.snapshot.values.due_at, ); assert.equal(next.sent.length, 0); }); test('creation waits until its frozen command has been checkpointed', async () => { const h = harness(); fill(h); h.submit(form); assert.equal(h.sent.length, 0); assert.equal(h.draftRequests.length, 1); const context = savedContext(h); await h.respond(context); assert.equal(h.sent.length, 1); assert.deepEqual( h.sent[0].data, context.preparation.snapshot.pending.values, ); }); test('an uncertain preparation write retries exactly its key and preserves later edits', async () => { const h = harness(); fill(h); h.submit(form); const first = h.draftRequests[0]; h.change(form, 'Tarefa', 'SYNTHETIC later'); await h.rejectDraft(first); h.click('Voltar a guardar preparação'); const retry = h.draftRequests.at(-1); assert.deepEqual(retry.data, first.data); await h.respond(savedContext(h, retry), retry); assert.equal(h.sent.length, 0); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC later'); h.submit(form); await h.flush(); assert.equal(h.sent.length, 1); assert.equal(h.sent[0].data.title, 'SYNTHETIC prepared task'); }); test('a new page recovers an uncertain business command with its original key', async () => { const h = harness(); fill(h); h.submit(form); const context = savedContext(h); await h.respond(context); const request = h.sent[0]; h.fail(request, 500); const next = harness(pageWith(context)); next.click('Comparar preparação guardada'); next.click('Aplicar escolhas e retomar'); assert.equal(next.sent.length, 0); next.submit(form); assert.deepEqual(next.sent[0].data, request.data); }); test('a committed creation is acknowledged after reopening without another business request', async () => { const h = harness(); fill(h); h.submit(form); const context = savedContext(h); await h.respond(context); const request = h.sent[0]; const stored = structuredClone(context); stored.preparation.snapshot.values.title = 'SYNTHETIC later'; stored.preparation.snapshot.generation.title++; stored.commands = [ { key: request.data.client_key, task_id: taskId, version: 1 }, ]; const next = harness( pageWith(stored, { lead: { ...h.props.lead, tasks: [ { id: taskId, title: request.data.title, due_at: request.data.due_at, assignee_user_id: 1, version_no: 1, status: 'open', }, ], }, }), ); next.click('Comparar preparação guardada'); next.click('Aplicar escolhas e retomar'); assert.equal(next.sent.length, 0); assert.equal(next.field(form, 'Tarefa').props.value, 'SYNTHETIC later'); assert.equal(next.field(form, 'Prazo (hora local)').props.value, ''); assert.match(text(next.tree), /Tarefa registada/); }); test('changes made during the business request are checkpointed with its immutable command', async () => { const h = harness(); fill(h); h.submit(form); await h.respond(savedContext(h)); const request = h.sent[0]; h.change(form, 'Tarefa', 'SYNTHETIC later'); await h.tick(); const latest = h.draftRequests.at(-1).data; assert.equal(latest.snapshot.values.title, 'SYNTHETIC later'); assert.deepEqual(latest.snapshot.pending.values, request.data); }); test('comparison preserves local edits and requires a new choice after further changes', async () => { const h = harness(); fill(h); await h.tick(); const context = savedContext(h); const next = harness(pageWith(context)); next.change(form, 'Tarefa', 'SYNTHETIC local'); next.click('Comparar preparação guardada'); next.change(form, 'Valor a retomar: Tarefa', 'saved'); next.change(form, 'Tarefa', 'SYNTHETIC after choice'); assert.equal( next.button('Aplicar escolhas e retomar').props.disabled, true, ); next.change(form, 'Valor a retomar: Tarefa', 'page'); next.click('Aplicar escolhas e retomar'); assert.equal( next.field(form, 'Tarefa').props.value, 'SYNTHETIC after choice', ); }); test('a response for a different account cannot authorise a business send or clear the draft', async () => { const h = harness(); fill(h); h.submit(form); await h.respond({ ...savedContext(h), actor_id: 2 }); assert.equal(h.sent.length, 0); assert.equal( h.field(form, 'Tarefa').props.value, 'SYNTHETIC prepared task', ); assert.match(text(h.tree), /Não foi possível confirmar a preparação/); }); test('a callback from the previous account cannot send a task after navigation', async () => { const h = harness(); fill(h); h.submit(form); const call = h.draftRequests[0], context = savedContext(h, call); h.render(props({ taskCreation: { ...props().taskCreation, actor_id: 2 } })); h.change(form, 'Tarefa', 'SYNTHETIC next account'); await h.respond(context, call); assert.equal(h.sent.length, 0); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC next account'); }); test('an unavailable installation retains the existing manual creation path', () => { const initial = props(); initial.taskCreation.preparation.installed = false; const h = harness(initial); fill(h); const request = h.submit(form); assert.equal(h.draftRequests.length, 0); assert.equal(request.data.title, 'SYNTHETIC prepared task'); assert.match(text(h.tree), /criação manual continua disponível/); }); test('another saved preparation cannot replace a locally sent unknown command', async () => { const h = harness(); fill(h); h.submit(form); await h.respond(savedContext(h)); const request = h.sent[0]; h.fail(request); const newer = savedContext(h); newer.preparation.version = 3; newer.preparation.operation_key = '00000000-0000-4000-8000-999999999999'; newer.preparation.snapshot = structuredClone(newer.preparation.snapshot); newer.preparation.snapshot.pending.values.client_key = '00000000-0000-4000-8000-888888888888'; h.render({ ...h.props, taskCreation: newer }); h.click('Comparar preparação guardada'); h.click('Aplicar escolhas e retomar'); assert.match(text(h.tree), /outro envio sem confirmação/); const retry = h.submit(form); assert.deepEqual(retry.data, request.data); }); test('a confirmed durable preparation does not repeat the leave warning but new edits do', async () => { const h = harness(); fill(h); assert.equal(h.leave(), true); await h.tick(); await h.respond(savedContext(h)); assert.equal(h.leave(), false); assert.equal(h.unload(), false); h.change(form, 'Tarefa', 'SYNTHETIC unsaved change'); assert.equal(h.leave(), true); assert.equal(h.unload(), true); }); test('validation allows a corrected preparation with a fresh save key without discarding its fields', async () => { const h = harness(); fill(h, 'S'.repeat(201)); await h.tick(); const first = h.draftRequests[0]; await h.validationDraft( { 'snapshot.values.title': 'SYNTHETIC validation' }, first, ); h.change(form, 'Tarefa', 'SYNTHETIC corrected'); h.click('Voltar a guardar preparação'); const corrected = h.draftRequests.at(-1); assert.notEqual(corrected.data.operation_key, first.data.operation_key); assert.equal(corrected.data.snapshot.values.title, 'SYNTHETIC corrected'); await h.respond(savedContext(h, corrected), corrected); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC corrected'); }); test('unreadable saved data is explained and the explicit manual path preserves the server preparation', () => { const initial = props(); initial.taskCreation.preparation = { ...initial.taskCreation.preparation, version: 1, unavailable: true, }; const h = harness(initial); assert.match(text(h.tree), /não pôde ser lida/); h.click('Continuar só nesta página'); fill(h); h.submit(form); assert.equal(h.sent.length, 1); assert.equal(h.draftRequests.length, 0); assert.match(text(h.tree), /guardada no servidor foi preservada/); }); test('cross-time-zone recovery keeps the saved instant including repeated local time and seconds', async () => { const previous = process.env.TZ; process.env.TZ = 'America/New_York'; try { const initial = props(), instant = '2027-11-07T06:30:12.345Z'; initial.taskCreation.preparation = { installed: true, version: 1, operation_key: '00000000-0000-4000-8000-111111111111', unavailable: false, updated_at: '2026-11-03T12:00:00Z', snapshot: { values: { title: 'SYNTHETIC saved instant', due_at: instant, assignee_user_id: '1', }, generation: { title: 1, due_at: 1, assignee_user_id: 0 }, pending: null, }, }; const h = harness(initial); h.click('Comparar preparação guardada'); h.click('Aplicar escolhas e retomar'); h.submit(form); assert.equal( h.draftRequests[0].data.snapshot.pending.values.due_at, instant, ); await h.respond(savedContext(h)); assert.equal(h.sent[0].data.due_at, instant); } finally { if (previous === undefined) delete process.env.TZ; else process.env.TZ = previous; } }); test('an empty completed preparation matching the page does not ask the user to compare it again', () => { const initial = props(); initial.taskCreation.preparation = { installed: true, version: 2, operation_key: '00000000-0000-4000-8000-111111111111', unavailable: false, updated_at: '2026-11-03T12:00:00Z', snapshot: { values: { title: '', due_at: '', assignee_user_id: '1' }, generation: { title: 2, due_at: 1, assignee_user_id: 0 }, pending: null, }, }; const h = harness(initial); assert.equal(h.button('Comparar preparação guardada'), undefined); assert.equal(h.button('Criar tarefa').props.disabled, false); assert.equal(h.leave(), false); assert.equal(h.draftRequests.length, 0); });