|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +/** |
| 4 | + * [#25573] The submitter exclusion note: one English source, every shipped |
| 5 | + * language from the same catalog, and a strict read of the stored shape. The |
| 6 | + * end-to-end half (the note an opened request writes, and the door a zh-CN |
| 7 | + * reader reads it through) is `exclude-submitter.integration.test.ts`. |
| 8 | + */ |
| 9 | + |
| 10 | +import { describe, it, expect } from 'vitest'; |
| 11 | +import { submitterExclusionNoteOf, submitterExclusionNoteText, type SubmitterExclusionRoute } from './submitter-exclusion-note.js'; |
| 12 | +import { adminRescueNoteOf, adminRescueReason, type ApprovalSentenceRender } from './admin-rescue-note.js'; |
| 13 | +import { approvalMessages } from './translations/messages.js'; |
| 14 | + |
| 15 | +const renderIn = (locale: keyof typeof approvalMessages): ApprovalSentenceRender => |
| 16 | + (key) => (approvalMessages[locale] as Record<string, string>)[key.replace(/^messages\./, '')]; |
| 17 | + |
| 18 | +const ROUTES: SubmitterExclusionRoute[] = ['remaining_approvers', 'fallback', 'administrators', 'no_one']; |
| 19 | + |
| 20 | +describe('[#25573] the submitter exclusion note', () => { |
| 21 | + it('each route has its own English sentence, and every shipped language renders each one', () => { |
| 22 | + const english = ROUTES.map((route) => submitterExclusionNoteText({ route })); |
| 23 | + expect(new Set(english).size).toBe(ROUTES.length); |
| 24 | + for (const text of english) expect(text).toMatch(/^The request's submitter was removed from its approvers/); |
| 25 | + for (const locale of Object.keys(approvalMessages) as Array<keyof typeof approvalMessages>) { |
| 26 | + for (const route of ROUTES) { |
| 27 | + expect(submitterExclusionNoteText({ route }, renderIn(locale)), `${locale} ${route}`).toBeTruthy(); |
| 28 | + } |
| 29 | + } |
| 30 | + expect(submitterExclusionNoteText({ route: 'fallback' }, () => undefined)).toBeUndefined(); |
| 31 | + }); |
| 32 | + |
| 33 | + it('reads only the shape it writes', () => { |
| 34 | + const good = { action_id: 'aact_1', route: 'administrators' }; |
| 35 | + expect(submitterExclusionNoteOf({ __submitterExclusionNote: good })).toEqual(good); |
| 36 | + for (const bad of [ |
| 37 | + undefined, |
| 38 | + {}, |
| 39 | + { __submitterExclusionNote: 'The request…' }, |
| 40 | + { __submitterExclusionNote: { route: 'fallback' } }, |
| 41 | + { __submitterExclusionNote: { action_id: 'aact_1', route: 'auto_approve' } }, |
| 42 | + ]) { |
| 43 | + expect(submitterExclusionNoteOf(bad), JSON.stringify(bad)).toBeUndefined(); |
| 44 | + } |
| 45 | + }); |
| 46 | + |
| 47 | + it("the administrators' note names the exclusion instead of saying no approver resolved to a person", () => { |
| 48 | + expect(adminRescueReason([], undefined, true)) |
| 49 | + .toBe("the only approver it resolved to was the request's submitter, whom excludeSubmitter removes"); |
| 50 | + expect(adminRescueReason([], undefined)).toBe('no approver on the node resolved to a person'); |
| 51 | + expect(adminRescueNoteOf({ __adminRescueNote: { action_id: 'a', abandoned: [], submitter_excluded: true } })) |
| 52 | + .toEqual({ action_id: 'a', abandoned: [], submitter_excluded: true }); |
| 53 | + expect(adminRescueNoteOf({ __adminRescueNote: { action_id: 'a', abandoned: [], submitter_excluded: 'yes' } })) |
| 54 | + .toBeUndefined(); |
| 55 | + }); |
| 56 | +}); |
0 commit comments