Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/defaults-maps-mirror-discovery-7884.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
---

Test-only change (objectui#7884): the `defaults-maps-mirror-en-pack` gate's rule "every
row names a key the en pack actually defines" now runs over the `createSafeTranslation`
defaults tables discovered from source, instead of a hand-written list of three imported
maps that judged 400 of 1056 rows. The AST walk objectui#3512 already had moved into
`@object-ui/test-support` (private, never published) so both gates share one definition of
the population rather than growing a second traversal. No published behaviour changes.
132 changes: 130 additions & 2 deletions packages/app-shell/src/__tests__/defaults-maps-mirror-en-pack.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,48 @@
*/

/**
* The three ungated `createSafeTranslation` defaults maps mirror the `en` pack —
* objectui#4401, generalizing objectui#3440's collaboration-only precedent.
* The `createSafeTranslation` defaults maps mirror the `en` pack —
* objectui#4401, generalizing objectui#3440's collaboration-only precedent,
* widened from three hand-listed maps to the discovered population by
* objectui#7884.
*
* ## What objectui#7884 changed, and why it was not a new rule
*
* The case "every row names a key the en pack actually defines" below is the
* ONE rule in this repo that rejects a defaults row whose key no pack defines.
* It did not fail to catch objectui#7874's five dead `timeline.relative.*` rows
* because it was wrong — it never saw them. Its `MAPS` was a hand-written list
* of three IMPORTED maps, and `TIMELINE_DEFAULT_TRANSLATIONS` was not on it: a
* correct rule held outside the door by a hand-written list, the same family as
* objectui#7448 / #7528 / #7548 / #7825 / #7853.
*
* Measured before the widening: the three-map list judged **400 of 1056 rows,
* 37.9%** — the rule was held outside 62% of its own population. And the list
* was not merely incomplete, it was **structurally incompletable by its own
* mechanism**: 11 of today's 32 factory tables are anonymous inline object
* literals passed straight to `createSafeTranslation(…)` and exported under no
* name at all, so no amount of diligence in maintaining a list of IMPORTS could
* ever reach them. With objectui#7874's five rows retired this class is empty
* repo-wide: 0 offending rows across 34 unique tables and 1056 rows, and a
* blind spot of 0 — nothing was unreadable, so the zero is a real zero.
*
* So the rule now runs over the population `@object-ui/test-support`'s
* `scanDefaultsTables` discovers from source — the same walk objectui#3512's
* `fallback-placeholder-spelling-3512.test.ts` already used, MOVED rather than
* copied, because two traversals are two definitions of the population that
* drift apart.
*
* ## The two reading paths are kept, deliberately
*
* The three hand-listed maps stay, and are still read as RUNTIME OBJECTS
* through their package imports, while the discovered population is read as
* SOURCE TEXT through the AST. That is not duplication: on the three maps they
* overlap, each is a check on the other, and a case below pins that every row
* of every imported map is present in the discovered set. Only the
* key-existence rule is widened — the byte-identity comparison still needs the
* runtime object, and judging factory-table VALUES against the pack is
* objectui#7567's `factory-default-drift`, a different question with its own
* deliberate abstention.
*
* ## The defect this pins
*
Expand Down Expand Up @@ -75,6 +115,9 @@
*/

import { describe, it, expect } from 'vitest';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { scanDefaultsTables } from '@object-ui/test-support/defaults-table-scan';
import { en } from '@object-ui/i18n';
import { DETAIL_DEFAULT_TRANSLATIONS } from '@object-ui/plugin-detail';
import { LIST_DEFAULT_TRANSLATIONS } from '@object-ui/plugin-list';
Expand All @@ -89,6 +132,33 @@ const packValueAt = (key: string): unknown =>
/** Codepoints, so a failure distinguishes `…` from `...` and NBSP from a space. */
const codepoints = (s: string) => [...s].map((c) => c.codePointAt(0)).join(',');

const here = path.dirname(fileURLToPath(import.meta.url));
// packages/app-shell/src/__tests__ -> repo root
const REPO_ROOT = path.resolve(here, '../../../..');

/**
* The discovered population — every `createSafeTranslation(…)` first argument
* resolved from source, plus the three hand-rolled sibling tables. One walk,
* shared with objectui#3512's gate; see `@object-ui/test-support`'s
* `defaults-table-scan.ts` for why it is not a second traversal.
*/
const DISCOVERED = scanDefaultsTables(REPO_ROOT);

/**
* Rows de-duplicated on `file:line` + key.
*
* `TIMELINE_DEFAULT_TRANSLATIONS` is discovered TWICE on purpose — once as the
* factory call in `useTimelineTranslation.ts` and once through the hand-rolled
* registry, which objectui#3512 keeps so the registry mirrors its needle-file
* set. Its rows are therefore scanned twice, and a gate that reported them raw
* would inflate every count it prints (objectui#7874's five rows read as ten).
* The identity is the ROW's own location plus its key, not the table label,
* because the two discoveries of that table carry different labels.
*/
const DISCOVERED_ROWS = [
...new Map(DISCOVERED.rows.map((row) => [`${row.where}::${row.key}`, row])).values(),
];

interface MapUnderGate {
/** Name used in test titles and failure messages. */
readonly name: string;
Expand Down Expand Up @@ -237,3 +307,61 @@ describe('the plugin defaults maps mirror the en pack (objectui#4401)', () => {
}
});
});

describe('the discovered defaults tables name keys the en pack defines (objectui#7884)', () => {
it('discovers the population, and states its blind-spot size', () => {
// Non-vacuity, #4118 family standard: the rule below is "no row offends",
// which an empty or broken walk satisfies trivially. Floors, not pins — a
// new table raises them for free, only a table DISAPPEARING has to be
// explained.
expect(DISCOVERED.tables.length).toBeGreaterThanOrEqual(34);
expect(DISCOVERED_ROWS.length).toBeGreaterThanOrEqual(1_000);
expect(DISCOVERED.sourceFiles.length).toBeGreaterThan(1_000);

// THE blind spot, asserted rather than counted in silence. A table that
// does not resolve, a computed key, a non-static value: each is a row this
// instrument cannot judge, and an instrument that hides how much it cannot
// see reads as 100% coverage forever. Measured 0 when this landed —
// objectui#7567's census surfaced objectui#7874 precisely because it
// printed its abstention count.
expect(DISCOVERED.unreadable).toEqual([]);
});

it('every row of every hand-listed map is present in the discovered set', () => {
// Ties the two reading paths together. The maps above are IMPORTED runtime
// objects; the population here is read from SOURCE by the AST. If the walk
// ever stops reaching one of these three tables, this fails loudly instead
// of quietly shrinking the population the rule below judges.
const discoveredByFile = new Map<string, Set<string>>();
for (const row of DISCOVERED_ROWS) {
const file = row.where.slice(0, row.where.lastIndexOf(':'));
let keys = discoveredByFile.get(file);
if (!keys) discoveredByFile.set(file, (keys = new Set<string>()));
keys.add(row.key);
}

const missed = MAPS.flatMap(({ map, source }) =>
Object.keys(map)
.filter((key) => !(discoveredByFile.get(source)?.has(key) ?? false))
.map((key) => `${source} row ${key} is not in the discovered population`),
);
expect(missed).toEqual([]);
});

it('every row names a key the en pack actually defines', () => {
// THE widened rule. Same verdict objectui#4401 wrote for three maps, now
// asked of every discovered table: a row whose key the pack lacks means the
// provider path cannot serve this string at all (i18next answers with the
// call site's `defaultValue`, or the raw key), so the two paths disagree by
// construction. If this goes red, FILE the row — never add the key to an
// allow-list, and never re-narrow the population to make it green.
const missing = DISCOVERED_ROWS.filter(
({ key }) => typeof packValueAt(key) !== 'string',
).map(
({ where, key, table }) =>
`${where} ${key} — row of ${table}, absent from the en pack — FINDING, file it`,
);

expect(missing).toEqual([]);
});
});
1 change: 1 addition & 0 deletions packages/i18n/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"react": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"react": "19.2.8",
"typescript": "^6.0.3",
Expand Down
Loading
Loading