import test from 'node:test'; import assert from 'node:assert/strict'; import { changeVisualField, confirmSuggestion, createSuggestionState, provenanceCopy } from '../src/suggestion.js'; test('keeps an AI suggestion provisional until the user acts on it', () => { const state = createSuggestionState({ status: 'suggestion', bristolType: 4, color: 'brown', confidence: 0.83 }); assert.equal(state.status, 'pending'); assert.equal(state.affirmed, false); assert.deepEqual(state.suggestion, { bristolType: 4, color: 'brown', confidence: 0.83 }); }); test('treats a missing or unusable suggestion as no suggestion at all', () => { assert.deepEqual(createSuggestionState(null).status, 'none'); assert.deepEqual(createSuggestionState(undefined).suggestion, null); assert.deepEqual(createSuggestionState({ status: 'needs_user_input', reason: 'The image is too uncertain to prefill safely.' }).status, 'none'); }); test('confirms a suggestion only when the user explicitly accepts it', () => { const pending = createSuggestionState({ status: 'suggestion', bristolType: 4, color: 'brown', confidence: 0.83 }); const confirmed = confirmSuggestion(pending); assert.equal(confirmed.status, 'confirmed'); assert.equal(confirmed.affirmed, true); assert.deepEqual(confirmed.suggestion, pending.suggestion); const nothing = confirmSuggestion(createSuggestionState(null)); assert.equal(nothing.status, 'none'); assert.equal(nothing.affirmed, false); assert.equal(confirmSuggestion(confirmed).status, 'confirmed'); }); test('changing a suggested field clears AI-confirmed state but keeps the context', () => { const confirmed = confirmSuggestion(createSuggestionState({ status: 'suggestion', bristolType: 4, color: 'brown', confidence: 0.83 })); const corrected = changeVisualField(confirmed, 'bristolType', 2); assert.equal(corrected.status, 'corrected'); assert.equal(corrected.affirmed, false); assert.deepEqual(corrected.suggestion, { bristolType: 4, color: 'brown', confidence: 0.83 }); assert.deepEqual(corrected.correctedFields, ['bristolType']); const recorrected = changeVisualField(corrected, 'color', 'black'); assert.deepEqual(recorrected.correctedFields, ['bristolType', 'color']); assert.deepEqual(changeVisualField(recorrected, 'note', 'ignored').correctedFields, ['bristolType', 'color']); }); test('does not invent an AI correction when the user never had a suggestion', () => { const manual = changeVisualField(createSuggestionState(null), 'bristolType', 6); assert.equal(manual.status, 'manual'); assert.equal(manual.suggestion, null); assert.deepEqual(manual.correctedFields, []); }); test('re-picking the suggested value is not treated as a correction', () => { const confirmed = confirmSuggestion(createSuggestionState({ status: 'suggestion', bristolType: 4, color: 'brown', confidence: 0.83 })); assert.equal(changeVisualField(confirmed, 'bristolType', 4).status, 'confirmed'); assert.deepEqual(changeVisualField(confirmed, 'bristolType', 4).correctedFields, []); }); test('labels suggested, confirmed, corrected, and manual states distinctly', () => { const pending = createSuggestionState({ status: 'suggestion', bristolType: 4, color: 'brown', confidence: 0.83 }); const confirmed = confirmSuggestion(pending); const corrected = changeVisualField(confirmed, 'bristolType', 2); const manual = changeVisualField(createSuggestionState(null), 'bristolType', 6); const labels = [pending, confirmed, corrected, manual].map(state => provenanceCopy(state, { bristolType: 2, color: 'brown' }).label); assert.deepEqual(labels, ['AI SUGGESTION', 'AI PREFILLED', 'CORRECTED BY YOU', 'YOUR OWN ENTRY']); assert.equal(provenanceCopy(createSuggestionState(null), {}).label, ''); }); test('screen-reader copy carries the AI confidence and who is in control', () => { const pending = createSuggestionState({ status: 'suggestion', bristolType: 4, color: 'brown', confidence: 0.83 }); const confirmed = confirmSuggestion(pending); const corrected = changeVisualField(confirmed, 'bristolType', 2); const manual = changeVisualField(createSuggestionState(null), 'bristolType', 6); assert.match(provenanceCopy(pending, { bristolType: 4, color: 'brown' }).aria, /83 percent confidence/i); assert.match(provenanceCopy(pending, { bristolType: 4, color: 'brown' }).aria, /not saved until you confirm/i); assert.match(provenanceCopy(confirmed, { bristolType: 4, color: 'brown' }).aria, /83 percent confidence/i); assert.match(provenanceCopy(confirmed, { bristolType: 4, color: 'brown' }).aria, /You confirmed/i); const correctedAria = provenanceCopy(corrected, { bristolType: 2, color: 'brown' }).aria; assert.match(correctedAria, /You changed/i); assert.match(correctedAria, /Type 4/); assert.match(correctedAria, /Type 2/); assert.equal(corrected.affirmed, false); assert.match(provenanceCopy(manual, { bristolType: 6, color: 'brown' }).aria, /No AI suggestion/i); });