import test from 'node:test'; import assert from 'node:assert/strict'; import { harness, props, text, leadId } from './helpers/crm-task-creation.mjs'; const form = 'new-crm-task'; function fill(h, title = 'SYNTHETIC first') { h.change(form, 'Tarefa', title); h.change(form, 'Prazo (hora local)', '2027-11-04T10:30'); } function saved(h, request, patch = {}) { return { ...h.props, lead: { ...h.props.lead, tasks: [ { id: '20000000-0000-4000-8000-000000000001', title: request.data.title, due_at: request.data.due_at, assignee_user_id: Number(request.data.assignee_user_id), version_no: 1, status: 'open', }, ], }, taskCreation: { lead_id: leadId, actor_id: 1, commands: [ { key: request.data.client_key, task_id: '20000000-0000-4000-8000-000000000001', version: 1, }, ], }, ...patch, }; } test('confirmation preserves title and deadline edited after the request was sent', () => { const h = harness(); fill(h); const request = h.submit(form); h.change(form, 'Tarefa', 'SYNTHETIC later'); h.change(form, 'Prazo (hora local)', '2027-11-05T11:00'); h.success(saved(h, request), request); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC later'); assert.equal( h.field(form, 'Prazo (hora local)').props.value, '2027-11-05T11:00', ); }); test('a lost response retries exactly the frozen request, preserving later edits', () => { const h = harness(); fill(h); const request = h.submit(form); h.change(form, 'Tarefa', 'SYNTHETIC next task'); h.change(form, 'Responsável pela tarefa', '2'); h.fail(request); const retry = h.submit(form); assert.deepEqual(retry.data, request.data); h.success(saved(h, retry), retry); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC next task'); assert.equal(h.field(form, 'Responsável pela tarefa').props.value, '2'); const next = h.submit(form); assert.notEqual(next.data.client_key, request.data.client_key); assert.equal(next.data.title, 'SYNTHETIC next task'); }); test('a fresh read acknowledges the committed creation without posting again', () => { const h = harness(); fill(h); const request = h.submit(form); h.fail(request, 500); h.click('Actualizar tarefas'); assert.deepEqual(h.reloads.at(-1).only, [ 'lead', 'owners', 'taskCreation', 'nextAction', ]); h.refreshed(saved(h, request)); assert.equal(h.sent.length, 1); assert.equal(h.field(form, 'Tarefa').props.value, ''); assert.match(text(h.tree), /Tarefa registada/); }); test('unconfirmed reads keep the original command and later edits', () => { const h = harness(); fill(h); const request = h.submit(form); h.fail(request); h.change(form, 'Tarefa', 'SYNTHETIC later'); h.click('Actualizar tarefas'); h.refreshed(h.props); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC later'); assert.deepEqual(h.submit(form).data, request.data); }); test('another account or lead cannot acknowledge or clear a pending command', () => { for (const context of [ { lead_id: leadId, actor_id: 2 }, { lead_id: 'other', actor_id: 1 }, ]) { const h = harness(); fill(h); const request = h.submit(form); const next = saved(h, request); next.taskCreation = { ...next.taskCreation, ...context }; request.options.onSuccess({ props: next }); request.form.processing = false; h.render(); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC first'); assert.doesNotMatch(text(h.tree), /Tarefa registada/); } }); test('a same-version task mismatch does not count as confirmation', () => { const h = harness(); fill(h); const request = h.submit(form); const next = saved(h, request); next.lead.tasks[0].title = 'SYNTHETIC different'; h.success(next, request); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC first'); assert.match(text(h.tree), /não permitiu confirmar/); }); test('later task changes and a removed task are acknowledged without restoration', () => { for (const removed of [false, true]) { const h = harness(); fill(h); const request = h.submit(form); const next = saved(h, request); if (removed) next.lead.tasks = []; else Object.assign(next.lead.tasks[0], { version_no: 2, status: 'done', title: 'SYNTHETIC updated', }); request.options.onSuccess({ props: next }); request.form.processing = false; h.render(); assert.equal(h.field(form, 'Tarefa').props.value, ''); assert.equal(h.sent.length, 1); if (removed) assert.match(text(h.tree), /já não está disponível/); } }); test('validation retains the draft and allows a corrected fresh command', () => { const h = harness(); fill(h); const request = h.submit(form); h.validation({ due_at: 'SYNTHETIC validation' }, request); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC first'); h.change(form, 'Prazo (hora local)', '2027-11-06T12:00'); const next = h.submit(form); assert.notEqual(next.data.client_key, request.data.client_key); assert.equal(next.data.due_at, new Date('2027-11-06T12:00').toISOString()); }); test('an untouched assignee follows a refreshed owner while an explicit choice is retained', () => { const h = harness(); h.render({ ...h.props, lead: { ...h.props.lead, owner_user_id: 2 } }); assert.equal(h.field(form, 'Responsável pela tarefa').props.value, '2'); h.change(form, 'Responsável pela tarefa', '1'); h.render({ ...h.props, lead: { ...h.props.lead, owner_user_id: null }, owners: [], }); assert.equal(h.field(form, 'Responsável pela tarefa').props.value, '1'); assert.match(text(h.form(form)), /Responsável anterior indisponível/); }); test('draft and unknown results have one navigation guard; confirmed unchanged values clear it', () => { const h = harness(); assert.equal(h.leave(), false); fill(h); assert.equal(h.leave(), true); assert.equal(h.confirmCalls, 1); assert.equal(h.unload(), true); const request = h.submit(form); h.fail(request); assert.equal(h.leave(true), false); h.success(saved(h, request), request); assert.equal(h.leave(), false); assert.equal(h.unload(), false); }); test('an obsolete callback cannot erase the draft of a different lead or actor', () => { const h = harness(); fill(h); const request = h.submit(form); const other = props({ lead: { ...h.props.lead, id: 'other' }, taskCreation: { lead_id: 'other', actor_id: 2, commands: [] }, }); h.render(other); h.change(form, 'Tarefa', 'SYNTHETIC other draft'); request.options.onSuccess({ props: saved(h, request) }); request.form.processing = false; h.render(); assert.equal(h.field(form, 'Tarefa').props.value, 'SYNTHETIC other draft'); }); test('access and conflict responses keep the command blocked until a scoped refresh', () => { const h = harness(); fill(h); const request = h.submit(form); h.fail(request, 403); assert.equal(h.button('Recuperar a mesma criação').props.disabled, true); h.click('Actualizar tarefas'); h.refreshed(h.props); assert.equal(h.button('Recuperar a mesma criação').props.disabled, false); assert.deepEqual(h.submit(form).data, request.data); });