421 lines
15 KiB
JavaScript
421 lines
15 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { File } from 'node:buffer';
|
|
import {
|
|
harness,
|
|
props,
|
|
original,
|
|
nodes,
|
|
text,
|
|
} from './helpers/partnership-room.mjs';
|
|
const newerRoom = (h, patch = {}) => ({
|
|
...h.props,
|
|
room: { ...h.props.room, version: h.props.room.version + 1, ...patch },
|
|
});
|
|
const mutableMilestone = (h, patch = {}) => ({
|
|
...h.props,
|
|
milestones: [{ ...h.props.milestones[0], ...patch }],
|
|
});
|
|
function row(scope, label) {
|
|
return nodes(scope).find(
|
|
(node) => node.type === 'tr' && text(node).startsWith(label),
|
|
);
|
|
}
|
|
test('untouched controls follow current data without requiring comparison of an empty new milestone', () => {
|
|
const h = harness(props({ milestones: [original] }));
|
|
h.change('message', 'Mensagem privada', 'SYNTHETIC separate draft');
|
|
h.render({
|
|
...newerRoom(h),
|
|
milestones: [
|
|
{
|
|
...original,
|
|
version: 4,
|
|
title: 'SYNTHETIC updated remote deliverable',
|
|
},
|
|
],
|
|
});
|
|
assert.equal(
|
|
h.field('milestone:7', 'Entregável').props.value,
|
|
'SYNTHETIC updated remote deliverable',
|
|
);
|
|
assert.equal(
|
|
h.button('Guardar marco', h.form('milestone:7')).props.disabled,
|
|
false,
|
|
);
|
|
assert.equal(
|
|
h.field('message', 'Mensagem privada').props.value,
|
|
'SYNTHETIC separate draft',
|
|
);
|
|
assert.equal(
|
|
h.button('Usar este contexto e conservar as alterações escolhidas'),
|
|
undefined,
|
|
);
|
|
h.change('milestone:new', 'Responsável pelo marco', '1');
|
|
assert.equal(
|
|
h.button('Guardar marco', h.form('milestone:new')).props.disabled,
|
|
false,
|
|
);
|
|
});
|
|
test('room version changes retain every independent form, including a selected File', () => {
|
|
const h = harness(props({ milestones: [original] }));
|
|
const file = new File(['SYNTHETIC bytes'], 'synthetic.pdf', {
|
|
type: 'application/pdf',
|
|
});
|
|
h.change('message', 'Mensagem privada', 'SYNTHETIC unsent question');
|
|
h.change('upload', 'Anexar PDF ou imagem', file);
|
|
h.change('agreement', 'Condições e plano de valor', 'SYNTHETIC own terms');
|
|
h.change(
|
|
'milestone:7',
|
|
'Evidência ou referência do resultado',
|
|
'SYNTHETIC own evidence',
|
|
);
|
|
h.render(newerRoom(h));
|
|
assert.equal(
|
|
h.field('message', 'Mensagem privada').props.value,
|
|
'SYNTHETIC unsent question',
|
|
);
|
|
assert.match(text(h.form('upload')), /synthetic.pdf/);
|
|
assert.equal(
|
|
h.field('agreement', 'Condições e plano de valor').props.value,
|
|
'SYNTHETIC own terms',
|
|
);
|
|
assert.equal(
|
|
h.field('milestone:7', 'Evidência ou referência do resultado').props
|
|
.value,
|
|
'SYNTHETIC own evidence',
|
|
);
|
|
assert.equal(
|
|
h.button('Guardar marco', h.form('milestone:7')).props.disabled,
|
|
true,
|
|
);
|
|
});
|
|
test('milestone conflicts are compared explicitly and return-to-original remains a deliberate edit', () => {
|
|
const h = harness(props({ milestones: [original] }));
|
|
h.change('milestone:7', 'Entregável', 'SYNTHETIC temporarily changed');
|
|
h.change('milestone:7', 'Entregável', original.title);
|
|
h.render(
|
|
mutableMilestone(h, {
|
|
title: 'SYNTHETIC remote title',
|
|
version: 4,
|
|
status: 'progress',
|
|
}),
|
|
);
|
|
const scope = h.form('milestone:7');
|
|
assert.match(text(scope), /Escolha o valor a conservar/);
|
|
assert.match(text(row(scope, 'Estado')), /Pendente/);
|
|
assert.doesNotMatch(text(row(scope, 'Estado')), /Submetida/);
|
|
h.click('Manter minha alteração', row(scope, 'Entregável'));
|
|
h.click(
|
|
'Usar este contexto e conservar as alterações escolhidas',
|
|
h.form('milestone:7'),
|
|
);
|
|
const request = h.submit('milestone:7');
|
|
assert.equal(request.data.version, 4);
|
|
assert.equal(request.data.title, original.title);
|
|
});
|
|
test('independent agreement conflicts keep previous choices while another field is edited', () => {
|
|
const h = harness();
|
|
h.change('agreement', 'Título do instrumento', 'SYNTHETIC local title');
|
|
h.change(
|
|
'agreement',
|
|
'Condições e plano de valor',
|
|
'SYNTHETIC local terms',
|
|
);
|
|
h.render(
|
|
newerRoom(h, {
|
|
agreement: {
|
|
...h.props.room.agreement,
|
|
title: 'SYNTHETIC remote title',
|
|
terms: 'SYNTHETIC remote terms',
|
|
},
|
|
}),
|
|
);
|
|
h.click('Manter minha alteração', row(h.tree, 'Título do instrumento'));
|
|
h.click(
|
|
'Manter minha alteração',
|
|
row(h.tree, 'Condições e plano de valor'),
|
|
);
|
|
h.change('agreement', 'Motivo da ação', 'SYNTHETIC compare decision');
|
|
const adopt = h.button(
|
|
'Usar este contexto e conservar as alterações escolhidas',
|
|
);
|
|
assert.equal(adopt.props.disabled, false);
|
|
h.click('Usar este contexto e conservar as alterações escolhidas');
|
|
const request = h.click('Registar versão do acordo');
|
|
assert.equal(request.data.version, 3);
|
|
assert.equal(request.data.title, 'SYNTHETIC local title');
|
|
assert.equal(request.data.terms, 'SYNTHETIC local terms');
|
|
});
|
|
test('context-only change requires adoption and a confirmed record clears only its sent reason', () => {
|
|
const h = harness();
|
|
h.change('agreement', 'Motivo da ação', 'SYNTHETIC edit');
|
|
h.render(newerRoom(h));
|
|
assert.equal(h.button('Registar versão do acordo').props.disabled, true);
|
|
h.click('Usar este contexto e conservar as alterações escolhidas');
|
|
const request = h.click('Registar versão do acordo');
|
|
const confirmed = newerRoom(h);
|
|
confirmed.agreementCommands = [
|
|
{
|
|
key: request.data.client_key,
|
|
action: 'record',
|
|
version: request.data.version + 1,
|
|
status: confirmed.room.status,
|
|
party: null,
|
|
},
|
|
];
|
|
h.success(confirmed, request);
|
|
assert.equal(h.field('agreement', 'Motivo da ação').props.value, '');
|
|
assert.equal(h.button('Registar versão do acordo').props.disabled, true);
|
|
});
|
|
test('milestone acknowledgement uses canonical data and conserves edits made after send', () => {
|
|
const h = harness(props({ milestones: [original] }));
|
|
h.change('milestone:7', 'Entregável', ' SYNTHETIC trimmed title ');
|
|
const request = h.submit('milestone:7');
|
|
h.change(
|
|
'milestone:7',
|
|
'Evidência ou referência do resultado',
|
|
'SYNTHETIC later evidence',
|
|
);
|
|
h.success(
|
|
mutableMilestone(h, { title: 'SYNTHETIC trimmed title', version: 4 }),
|
|
request,
|
|
);
|
|
assert.equal(
|
|
h.field('milestone:7', 'Entregável').props.value,
|
|
'SYNTHETIC trimmed title',
|
|
);
|
|
assert.equal(
|
|
h.field('milestone:7', 'Evidência ou referência do resultado').props
|
|
.value,
|
|
'SYNTHETIC later evidence',
|
|
);
|
|
assert.equal(
|
|
h.button('Guardar marco', h.form('milestone:7')).props.disabled,
|
|
false,
|
|
);
|
|
});
|
|
test('uncertain milestone creation repeats original bytes and retains later changes as new preparation', () => {
|
|
const h = harness();
|
|
h.change('milestone:new', 'Entregável', 'SYNTHETIC original creation');
|
|
h.change('milestone:new', 'Responsável pelo marco', '1');
|
|
const first = h.submit('milestone:new');
|
|
h.fail(first);
|
|
h.change('milestone:new', 'Entregável', 'SYNTHETIC later draft');
|
|
const retry = h.submit('milestone:new');
|
|
assert.deepEqual(retry.data, first.data);
|
|
const saved = {
|
|
...original,
|
|
id: 11,
|
|
version: 1,
|
|
title: first.data.title,
|
|
client_key: first.data.client_key,
|
|
};
|
|
h.success({ ...h.props, milestones: [saved] }, retry);
|
|
assert.equal(
|
|
h.field('milestone:new', 'Entregável').props.value,
|
|
'SYNTHETIC later draft',
|
|
);
|
|
assert.match(text(h.form('milestone:new')), /como nova preparação/);
|
|
assert.ok(h.form('milestone:11'));
|
|
});
|
|
test('closed room keeps drafts mounted and blocks new changes until context and authority permit them', () => {
|
|
const h = harness(props({ milestones: [original] }));
|
|
h.change('milestone:7', 'Entregável', 'SYNTHETIC kept milestone');
|
|
h.change(
|
|
'agreement',
|
|
'Condições e plano de valor',
|
|
'SYNTHETIC kept agreement',
|
|
);
|
|
h.change('message', 'Mensagem privada', 'SYNTHETIC kept message');
|
|
h.render(newerRoom(h, { status: 'cancelled' }));
|
|
assert.ok(h.form('milestone:7'));
|
|
assert.equal(
|
|
h.field('milestone:7', 'Entregável').props.value,
|
|
'SYNTHETIC kept milestone',
|
|
);
|
|
assert.equal(
|
|
nodes(h.form('milestone:7')).find((node) => node.type === 'fieldset')
|
|
.props.disabled,
|
|
true,
|
|
);
|
|
assert.equal(
|
|
h.field('agreement', 'Condições e plano de valor').props.value,
|
|
'SYNTHETIC kept agreement',
|
|
);
|
|
assert.equal(h.button('Enviar mensagem').props.disabled, true);
|
|
assert.match(text(h.tree), /preparação.*conservada/);
|
|
});
|
|
test('uncertain messages retain their original key while later text is prepared separately', () => {
|
|
const h = harness();
|
|
h.change('message', 'Mensagem privada', 'SYNTHETIC first message');
|
|
const first = h.submit('message');
|
|
h.fail(first);
|
|
h.change('message', 'Mensagem privada', 'SYNTHETIC next message');
|
|
const retry = h.submit('message');
|
|
assert.deepEqual(retry.data, first.data);
|
|
h.success(
|
|
{
|
|
...h.props,
|
|
messages: [
|
|
{
|
|
id: 1,
|
|
author_id: 1,
|
|
client_key: first.data.client_key,
|
|
body: first.data.body,
|
|
name: 'SYNTHETIC owner',
|
|
created_at: 'synthetic',
|
|
},
|
|
],
|
|
},
|
|
retry,
|
|
);
|
|
assert.equal(
|
|
h.field('message', 'Mensagem privada').props.value,
|
|
'SYNTHETIC next message',
|
|
);
|
|
const next = h.submit('message');
|
|
assert.notEqual(next.data.client_key, first.data.client_key);
|
|
assert.equal(next.data.body, 'SYNTHETIC next message');
|
|
});
|
|
test('uncertain upload retains the immutable File and original sharing consent through retry', () => {
|
|
const h = harness();
|
|
const firstFile = new File(['SYNTHETIC first'], 'first.pdf'),
|
|
laterFile = new File(['SYNTHETIC later'], 'later.pdf');
|
|
h.change('upload', 'Anexar PDF ou imagem', firstFile);
|
|
const first = h.submit('upload');
|
|
h.fail(first);
|
|
h.change('upload', 'Anexar PDF ou imagem', laterFile);
|
|
const retry = h.submit('upload');
|
|
assert.equal(retry.data.file, firstFile);
|
|
assert.equal(retry.data.client_key, first.data.client_key);
|
|
assert.equal(retry.data.shared, false);
|
|
h.success(
|
|
{
|
|
...h.props,
|
|
documents: [
|
|
{
|
|
id: 1,
|
|
document_id: 'synthetic-document',
|
|
owner_id: 1,
|
|
file_name: 'first.pdf',
|
|
shared: false,
|
|
status: 'quarantined',
|
|
malware_scan_status: 'pending',
|
|
},
|
|
],
|
|
flash: { uploaded_document_id: 'synthetic-document' },
|
|
},
|
|
retry,
|
|
);
|
|
assert.match(text(h.form('upload')), /later.pdf/);
|
|
const next = h.submit('upload');
|
|
assert.equal(next.data.file, laterFile);
|
|
assert.notEqual(next.data.client_key, first.data.client_key);
|
|
});
|
|
test('unrelated or malformed acknowledgement never erases message or upload preparation', () => {
|
|
const h = harness();
|
|
h.change('message', 'Mensagem privada', 'SYNTHETIC retained');
|
|
const request = h.submit('message');
|
|
h.success({ ...h.props, room: { ...h.props.room, id: 999 } }, request);
|
|
assert.equal(
|
|
h.field('message', 'Mensagem privada').props.value,
|
|
'SYNTHETIC retained',
|
|
);
|
|
assert.match(text(h.form('message')), /não foi confirmado/);
|
|
});
|
|
test('one leave guard covers all drafts; comparison refresh does not trigger a leave prompt', () => {
|
|
const h = harness(props({ milestones: [original] }));
|
|
h.change('message', 'Mensagem privada', 'SYNTHETIC message');
|
|
h.change('milestone:7', 'Entregável', 'SYNTHETIC draft');
|
|
assert.equal(h.leave(), true);
|
|
assert.equal(h.confirmCalls, 1);
|
|
assert.equal(h.unload(), true);
|
|
assert.equal(h.leave(true), false);
|
|
assert.equal(h.confirmCalls, 1);
|
|
});
|
|
test('losing write access conserves preparation and another account gets a fresh room editor', () => {
|
|
const h = harness();
|
|
h.change('message', 'Mensagem privada', 'SYNTHETIC private local draft');
|
|
h.render({ ...h.props, canWrite: false, participants: [] });
|
|
assert.equal(h.button('Enviar mensagem').props.disabled, true);
|
|
assert.equal(
|
|
h.field('message', 'Mensagem privada').props.value,
|
|
'SYNTHETIC private local draft',
|
|
);
|
|
h.render({ ...h.props, canWrite: true, actorId: 2 });
|
|
assert.equal(h.field('message', 'Mensagem privada').props.value, '');
|
|
});
|
|
test('HTTP409 preserves agreement text and a successful refresh of the unchanged base permits a retry', () => {
|
|
const h = harness();
|
|
h.change('agreement', 'Motivo da ação', 'SYNTHETIC retry');
|
|
const request = h.click('Registar versão do acordo');
|
|
h.fail(request, 409);
|
|
assert.equal(h.button('Registar versão do acordo').props.disabled, true);
|
|
h.click('Comparar com os dados actuais da sala');
|
|
h.refreshed(h.props);
|
|
assert.equal(h.button('Registar versão do acordo').props.disabled, false);
|
|
assert.equal(
|
|
h.field('agreement', 'Motivo da ação').props.value,
|
|
'SYNTHETIC retry',
|
|
);
|
|
});
|
|
test('confirmation uses a fresh reviewed room version and preserves unsent instrument changes', () => {
|
|
const h = harness();
|
|
h.change(
|
|
'agreement',
|
|
'Título do instrumento',
|
|
'SYNTHETIC local unrecorded title',
|
|
);
|
|
h.change(
|
|
'agreement',
|
|
'Motivo da ação',
|
|
'SYNTHETIC confirm published terms',
|
|
);
|
|
const request = h.click('Confirmar pela minha parte');
|
|
assert.equal(request.data.title, undefined);
|
|
assert.equal(request.data.version, 2);
|
|
const confirmed = newerRoom(h);
|
|
confirmed.room.agreement = {
|
|
...confirmed.room.agreement,
|
|
confirmations: {
|
|
proponent: {
|
|
actor_id: 1,
|
|
date: 'SYNTHETIC',
|
|
reason: request.data.reason,
|
|
},
|
|
},
|
|
};
|
|
confirmed.agreementCommands = [
|
|
{
|
|
key: request.data.client_key,
|
|
action: 'confirm',
|
|
version: request.data.version + 1,
|
|
status: confirmed.room.status,
|
|
party: 'proponent',
|
|
},
|
|
];
|
|
h.success(confirmed, request);
|
|
assert.equal(
|
|
h.field('agreement', 'Título do instrumento').props.value,
|
|
'SYNTHETIC local unrecorded title',
|
|
);
|
|
assert.equal(h.field('agreement', 'Motivo da ação').props.value, '');
|
|
});
|
|
test('a newer unexpected acknowledgement is compared instead of being accepted as the sent edit', () => {
|
|
const h = harness(props({ milestones: [original] }));
|
|
h.change('milestone:7', 'Entregável', 'SYNTHETIC own title');
|
|
const request = h.submit('milestone:7');
|
|
h.success(
|
|
mutableMilestone(h, { title: 'SYNTHETIC another edit', version: 5 }),
|
|
request,
|
|
);
|
|
assert.equal(
|
|
h.field('milestone:7', 'Entregável').props.value,
|
|
'SYNTHETIC own title',
|
|
);
|
|
assert.equal(
|
|
h.button('Guardar marco', h.form('milestone:7')).props.disabled,
|
|
true,
|
|
);
|
|
});
|