|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +/** |
| 4 | + * The Data Lifecycle settings page is administrator copy. |
| 5 | + * |
| 6 | + * Setup renders the manifest's `description` under the page title and each |
| 7 | + * specifier's `label` / `description` beside its control. An ADR number, a |
| 8 | + * section reference or the name of an internal component tells that reader |
| 9 | + * nothing they can act on: the reference belongs in the docblock next to the |
| 10 | + * manifest, never in the string. |
| 11 | + */ |
| 12 | + |
| 13 | +import { describe, expect, it } from 'vitest'; |
| 14 | +import { lifecycleSettingsManifest } from './lifecycle-settings.js'; |
| 15 | + |
| 16 | +const DESIGN_RECORD_VOCABULARY: ReadonlyArray<readonly [string, RegExp]> = [ |
| 17 | + ['an ADR number', /ADR-\d/], |
| 18 | + ['a section reference', /§\s?\d/], |
| 19 | + ['an internal component', /\bLifecycleService\b|\bReaper\b|\bRotator\b|\bArchiver\b/], |
| 20 | +]; |
| 21 | + |
| 22 | +function vocabularyIn(text: string): string[] { |
| 23 | + return DESIGN_RECORD_VOCABULARY.filter(([, re]) => re.test(text)).map(([name]) => name); |
| 24 | +} |
| 25 | + |
| 26 | +type Copy = { label?: string; description?: string; key?: string }; |
| 27 | + |
| 28 | +describe('lifecycle settings: admin-facing copy carries no design-record vocabulary', () => { |
| 29 | + it('the matcher recognises each kind of vocabulary it exists to keep out', () => { |
| 30 | + expect(vocabularyIn('Governance for ADR-0057 data lifecycle enforcement.')).toEqual(['an ADR number']); |
| 31 | + expect(vocabularyIn('A regulated tenant sets years (§3.2).')).toEqual(['a section reference']); |
| 32 | + expect(vocabularyIn('Growth alerts for the LifecycleService (Reaper / Rotator / Archiver).')).toEqual(['an internal component']); |
| 33 | + expect(vocabularyIn('How long system data is kept.')).toEqual([]); |
| 34 | + }); |
| 35 | + |
| 36 | + it('the page description and every specifier label and description', () => { |
| 37 | + const manifest = lifecycleSettingsManifest as unknown as Copy & { specifiers: Copy[] }; |
| 38 | + const strings: Array<[string, string]> = []; |
| 39 | + for (const [at, item] of [['lifecycle', manifest] as const, ...manifest.specifiers.map((s) => [`lifecycle.${s.key}`, s] as const)]) { |
| 40 | + if (typeof item.label === 'string') strings.push([`${at}.label`, item.label]); |
| 41 | + if (typeof item.description === 'string') strings.push([`${at}.description`, item.description]); |
| 42 | + } |
| 43 | + // Positive control: the walk reaches the page sentence and the retention override help. |
| 44 | + expect(strings.map(([at]) => at)).toEqual(expect.arrayContaining(['lifecycle.description', 'lifecycle.retention_overrides.description'])); |
| 45 | + const offenders = strings.filter(([, text]) => vocabularyIn(text).length > 0).map(([at, text]) => `${at}: ${text}`); |
| 46 | + expect(offenders).toEqual([]); |
| 47 | + }); |
| 48 | +}); |
0 commit comments