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
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,10 @@ describe('objectui#4970 — element:text.content / element:button.label declare
// these arms honest rather than copied from the issue — #4970's own table
// was measured at the 17.0.0-rc.6 pin, and the answer had to be re-taken
// before the declarations moved.
expect(specAcceptedArms(ElementTextPropsSchema, 'content')).toEqual(['string', 'object']);
expect(specAcceptedArms(ElementButtonPropsSchema, 'label')).toEqual(['string', 'object']);
// Membership (#26238): the arms the declarations below must carry. An arm
// the spec adds is caught by the derived comparisons, not by a literal.
expect(specAcceptedArms(ElementTextPropsSchema, 'content')).toEqual(expect.arrayContaining(['string', 'object']));
expect(specAcceptedArms(ElementButtonPropsSchema, 'label')).toEqual(expect.arrayContaining(['string', 'object']));
});

it('`element:text.content` accepts the inline translation map', () => {
Expand Down Expand Up @@ -417,26 +419,20 @@ describe('objectui#5717 — element:text_input label / placeholder / description
// Measured on the `@objectstack/spec` 17.1.0 pin — the same discipline the
// #4970 block adopted, and the reason these arms are honest rather than
// copied from the card that reported them.
expect(specAcceptedArms(ElementTextInputPropsSchema, 'label')).toEqual(['string', 'object']);
expect(specAcceptedArms(ElementTextInputPropsSchema, 'placeholder')).toEqual([
'string',
'object',
]);
expect(specAcceptedArms(ElementTextInputPropsSchema, 'description')).toEqual([
'string',
'object',
]);
// Membership (#26238), as in the #4970 block above.
for (const key of ['label', 'placeholder', 'description']) {
expect(specAcceptedArms(ElementTextInputPropsSchema, key), key).toEqual(expect.arrayContaining(['string', 'object']));
}
});

it('the sibling key whose contract has NO object arm is measured the same way', () => {
// The separation asserted from the CONTRACT rather than from the
// declaration, so "why is defaultValue different" is answered by evidence
// in the same vocabulary. Without this line the per-key control below reads
// as a convention; with it, it reads as the contract.
expect(specAcceptedArms(ElementTextInputPropsSchema, 'defaultValue')).toEqual([
'string',
'number',
]);
const defaultValueArms = specAcceptedArms(ElementTextInputPropsSchema, 'defaultValue');
expect(defaultValueArms).toEqual(expect.arrayContaining(['string', 'number']));
expect(defaultValueArms).not.toContain('object');
});

it('`element:text_input.label` accepts the inline translation map', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('objectui#3846 — the trust-tier label map answers to the spec enum',
it('covers exactly the tiers PluginRuntimeSchema declares', () => {
// The producer's own list, read from the schema rather than retyped.
const declared = [...PluginRuntimeSchema.options].sort();
expect(declared).toEqual(['node', 'sandbox', 'worker']);
for (const tier of ['node', 'sandbox', 'worker']) expect(declared).toContain(tier);
expect(Object.keys(RUNTIME_FALLBACK).sort()).toEqual(declared);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ const DECORATED_SERVED = {
// ---------------------------------------------------------------------------

describe('the instrument', () => {
it('is the spec that names the decorations, and it names exactly these two', () => {
it('is the spec that names the decorations, and it names these two', () => {
// Stated before any claim about the fix. If this list grows, the pins below
// follow it without an edit — that is why they read it rather than spell it.
expect([...METADATA_READ_DECORATIONS]).toEqual(['_diagnostics', '_draft']);
// follow it without an edit — that is why they read it rather than spell it,
// and why this asserts membership rather than the whole list (#26238).
expect([...METADATA_READ_DECORATIONS]).toEqual(expect.arrayContaining(['_diagnostics', '_draft']));
});

it('refuses each decoration BY NAME — the defect, on the schema', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,26 @@ const unrecognizedKeys = (result: { success: boolean; error?: unknown }): string
.filter((i) => i.code === 'unrecognized_keys')
.flatMap((i) => i.keys ?? []));

/**
* Levenshtein distance, case-insensitive. The near-spelling claims below are
* stated over the accept set with this, rather than through a key count that
* every unrelated spec adoption moves (#26238).
*/
function editDistance(a: string, b: string): number {
const [x, y] = [a.toLowerCase(), b.toLowerCase()];
const row = Array.from({ length: y.length + 1 }, (_, j) => j);
for (let i = 1; i <= x.length; i++) {
let diagonal = row[0];
row[0] = i;
for (let j = 1; j <= y.length; j++) {
const above = row[j];
row[j] = Math.min(above + 1, row[j - 1] + 1, diagonal + (x[i - 1] === y[j - 1] ? 0 : 1));
diagonal = above;
}
}
return row[y.length];
}

/** A base document `ObjectSchema` accepts, for probing one key at a time. */
const BASE = { name: 'account', label: 'Account', fields: { n: { type: 'text', label: 'N' } } };

Expand Down Expand Up @@ -137,37 +157,22 @@ describe('objectui#6238 · the schema oracle — both tombstone keys are refused
// key-name gate could never have stated it. `enable` passes on the name and
// fails on the value, which is why this is objectui#4687's resolution
// (delete the declaration) rather than objectui#6041's (rename it).
// 43, not the 42 this pin carried against 17.2.0: `@objectstack/spec` 17.3.0
// ADOPTED `editMode` (measured — gained set exactly `['editMode']`, lost set
// empty). That adoption is unrelated to this file's subject and is pinned in
// `packages/types/src/__tests__/object-schema-metadata-spec-derivation.test.ts`;
// the count rides here only as the corpus guard for the membership claims
// below, so it is re-pointed, not weakened.
// 44 at `@objectstack/spec` 17.6.0, which adopted `imageField` (measured against
// a 17.5.0 control: gained set exactly `['imageField']`, lost set empty;
// objectui#11438). Re-pointed as the 17.3.0 adoption was, not weakened.
// 45 at `@objectstack/spec` 18.0.0-next.0, which adopted `attachedOnRead`
// (measured against a 17.7.0 control: gained set exactly `['attachedOnRead']`,
// lost set empty; objectui#12030). Re-pointed the same way.
// 46 on the objectstack `main` pin after 18.0.0-next.3, which adopted
// `defaultRowScope` (objectstack#24398; measured against the next.3 pin: the
// one top-level key `ObjectSchema` gains between the two commits, none lost;
// objectui#12971). Re-pointed the same way.
// 47 at `@objectstack/spec` 18.1.0, which adopted `membershipKeys`
// (objectstack#25513; measured against the 915e9404 pin: the spec's own
// `authorable-surface/data.json` gains exactly `data/Object:membershipKeys`
// between the two commits, none lost; objectui#13440). Re-pointed the same way.
// 48 on the objectstack `main` pin after objectstack#26005 (5e113b39), which adopted
// `sharing` (measured against its parent fbb75998: gained set exactly `['sharing']`,
// lost set empty; unchanged at 43b47156). Re-pointed the same way.
// The membership claims are asserted by name over the accept set; an exact
// key count used to stand in for them and was re-pointed six times by
// unrelated spec adoptions (42 → 48), so it is gone (#26238).
const accept = new Set(Object.keys(ObjectSchema.shape as Record<string, unknown>));
expect(accept.size).toBe(48);
expect(accept.has('enable')).toBe(true);
expect(accept.has('enabled')).toBe(false);
expect(accept.has('_deleted')).toBe(false);
for (const key of ['disabled', 'active', 'isActive', 'deleted', 'isDeleted', 'softDeleted']) {
expect(accept.has(key), `ObjectSchema unexpectedly accepts \`${key}\``).toBe(false);
}
// No near-spelling of either tombstone key is accepted, except `enable`,
// whose VALUE is what refuses the rename (asserted next).
const nearSpellings = [...accept].filter(
(key) => key !== 'enable' && ['enabled', '_deleted'].some((retired) => editDistance(key, retired) <= 2),
);
expect(nearSpellings, 'ObjectSchema accepts a near-spelling of a tombstone key').toEqual([]);

const asBoolean = ObjectSchema.safeParse({ ...BASE, enable: false });
expect(asBoolean.success).toBe(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
* shapes: `toObjectPayload` builds it and `saveObject` PUTs it whole to
* `PUT /api/v1/meta/object/:name`.
*
* Measured against the installed `@objectstack/spec` (ESM build), whose
* `ObjectSchema` accept set is 43 keys at 17.3.0 (42 at 17.2.0, when this file
* was written; the one gained key is `editMode`, unrelated to the three below):
* Measured against the installed `@objectstack/spec` (ESM build), at 17.2.0
* when this file was written:
*
* ObjectSchema.safeParse({ ...base, group: 'Sales' }) => unrecognized_keys ["group"]
* ObjectSchema.safeParse({ ...base, sortOrder: 3 }) => unrecognized_keys ["sortOrder"]
Expand Down Expand Up @@ -57,6 +56,26 @@ import { ObjectStackAdapter } from '@object-ui/data-objectstack';
import type { ObjectDefinition } from '@object-ui/types';
import { MetadataService } from './MetadataService';

/**
* Levenshtein distance, case-insensitive. The no-near-spelling claim below is
* stated over the accept set with this, rather than through a key count that
* every unrelated spec adoption moves (#26238).
*/
function editDistance(a: string, b: string): number {
const [x, y] = [a.toLowerCase(), b.toLowerCase()];
const row = Array.from({ length: y.length + 1 }, (_, j) => j);
for (let i = 1; i <= x.length; i++) {
let diagonal = row[0];
row[0] = i;
for (let j = 1; j <= y.length; j++) {
const above = row[j];
row[j] = Math.min(above + 1, row[j - 1] + 1, diagonal + (x[i - 1] === y[j - 1] ? 0 : 1));
diagonal = above;
}
}
return row[y.length];
}

/** The bodies of every PUT the SDK issued, exactly as they went over the wire. */
function makeCapturingAdapter() {
const puts: Array<Record<string, unknown>> = [];
Expand Down Expand Up @@ -153,36 +172,23 @@ describe('the instrument', () => {
});

it('has no near-spelling for any of them — unlike objectui#6041, nothing here is a rename', () => {
// 43 at `@objectstack/spec` 17.3.0, which adopted `editMode` (measured —
// gained set exactly `['editMode']`, lost set empty). The count is this
// file's corpus guard, not its subject; every claim it guards is below and
// unchanged.
// 44 at `@objectstack/spec` 17.6.0, which adopted `imageField` (measured against
// a 17.5.0 control: gained set exactly `['imageField']`, lost set empty;
// objectui#11438). Re-pointed as the 17.3.0 adoption was, not weakened.
// 45 at `@objectstack/spec` 18.0.0-next.0, which adopted `attachedOnRead`
// (measured against a 17.7.0 control: gained set exactly `['attachedOnRead']`,
// lost set empty; objectui#12030). Re-pointed the same way.
// 46 on the objectstack `main` pin after 18.0.0-next.3, which adopted
// `defaultRowScope` (objectstack#24398; measured against the next.3 pin: the
// one top-level key `ObjectSchema` gains between the two commits, none lost;
// objectui#12971). Re-pointed the same way.
// 47 at `@objectstack/spec` 18.1.0, which adopted `membershipKeys`
// (objectstack#25513; measured against the 915e9404 pin: the spec's own
// `authorable-surface/data.json` gains exactly `data/Object:membershipKeys`
// between the two commits, none lost; objectui#13440). Re-pointed the same way.
// 48 on the objectstack `main` pin after objectstack#26005 (5e113b39), which adopted
// `sharing` (measured against its parent fbb75998: gained set exactly `['sharing']`,
// lost set empty; unchanged at 43b47156). Re-pointed the same way.
// The claims are asserted by name over the accept set; an exact key count
// used to stand in for them and was re-pointed six times by unrelated spec
// adoptions (42 → 48), so it is gone (#26238).
const accept = new Set(Object.keys(ObjectSchema.shape as Record<string, unknown>));
expect(accept.size).toBe(48);
// `fieldGroups` is the only grouping key on the object, and it groups the
// FIELDS INSIDE one object — it is not a category for objects themselves,
// so `group` has no mapping target here.
expect(accept.has('fieldGroups')).toBe(true);
for (const key of ['group', 'sortOrder', 'relationships', 'order', 'category', 'sortField']) {
expect(accept.has(key), `ObjectSchema unexpectedly accepts \`${key}\``).toBe(false);
}
// Nothing in the accept set is within two edits of a resolved key, so none
// of the three was a misspelling of a key the schema does declare.
const nearSpellings = [...accept].filter((key) =>
['group', 'sortOrder', 'relationships'].some((resolved) => editDistance(key, resolved) <= 2),
);
expect(nearSpellings, 'ObjectSchema accepts a near-spelling of a resolved key').toEqual([]);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ describe('resolveFormViewLayout carries the authored modalSize (objectui#12803)'
});
});

it('the cases above are the whole spec enum', () => {
expect(FormViewSchema.shape.modalSize.unwrap().options).toEqual(['sm', 'default', 'lg', 'xl', 'full']);
it('every case above is a size the spec enum allows', () => {
// Membership, not the whole enum: a size the spec adds later is copied by
// the same pass-through and needs no edit here (#26238).
const allowed: readonly string[] = FormViewSchema.shape.modalSize.unwrap().options;
for (const size of ['sm', 'default', 'lg', 'xl', 'full']) expect(allowed).toContain(size);
});

it('copies modalSize beside sections, and from the legacy formViews.default', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,19 +216,15 @@ describe('the emitted lane key is the one `@objectstack/spec` declares (objectui
expect(unrecognized(complete({ groupBy: 'stage' }))).toContain('groupBy');
});

it('declares exactly the four keys this file reasons about', () => {
it('declares the lane key and neither retired spelling — the premise of the arms above', () => {
// A tripwire on the premise itself: if a future spec bump declares
// `groupBy` (or drops `groupByField`), the arms above stop meaning what
// they say and this reddens first. It fired at the 17.5.0 bump
// (objectui#11073) for a key that is NOT a lane spelling: 17.5.0 declares
// `titleField` (the card-title binding, falling back to the record display
// name). Re-read: every arm above is about the lane key and still holds, so
// the pin moves from three keys to four and nothing else does.
expect(Object.keys((KanbanConfigSchema as any).shape).sort()).toEqual([
'columns',
'groupByField',
'summarizeField',
'titleField',
]);
// `groupBy` or `groupField` (or drops `groupByField`), the arms above stop
// meaning what they say and this reddens first. Asserted by name: the
// exact key list it used to pin fired at 17.5.0 for `titleField`, a key
// that is not a lane spelling at all (#26238).
const keys = Object.keys((KanbanConfigSchema as any).shape);
expect(keys).toContain('groupByField');
expect(keys).not.toContain('groupField');
expect(keys).not.toContain('groupBy');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ describe('KIND_TO_RECORD_LOOKUP is derived from APPROVER_VALUE_SOURCES (objectui
// the boundary to be re-drawn deliberately.
for (const kind of EXPECTED_KINDS) {
const source = APPROVER_VALUE_SOURCES[kind as keyof typeof APPROVER_VALUE_SOURCES] as Record<string, unknown>;
expect(Object.keys(source).sort()).toEqual(['object', 'source', 'valueField']);
const specKeys = Object.keys(source);
for (const key of ['object', 'source', 'valueField']) expect(specKeys).toContain(key);
// By name, not as the spec's whole key list (#26238): none of the keys
// this package owns for presentation is published by the spec.
const presentation = Object.keys(KIND_TO_RECORD_LOOKUP[kind]!).filter((k) => k !== 'object' && k !== 'valueField');
expect(presentation).toContain('displayField');
for (const key of presentation) expect(specKeys, `the spec now publishes \`${key}\``).not.toContain(key);
expect(KIND_TO_RECORD_LOOKUP[kind]!.displayField).toBeTruthy();
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ describe('PACKAGE_SOURCES is the closed set the spec declares (objectui#12790)',

it('equals the `source` options on both stage branches of the served row', () => {
const branches = InstalledPackageAtEitherStageSchema.options;
expect(branches).toHaveLength(2);
// Non-vacuity floor for the loop; the branch count is the spec's (#26238).
expect(branches.length).toBeGreaterThan(0);
for (const branch of branches) {
expect(sorted(branch.shape.source.options)).toEqual(sorted(PACKAGE_SOURCES));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ describe('ActionPreview placement frames', () => {
// re-added the member would leave the pins above passing while the designer
// silently stopped previewing a location the product does declare.
expect(ACTION_LOCATIONS as readonly string[]).not.toContain('global_nav');
expect(ACTION_LOCATIONS.length).toBe(6);
// Control: the vocabulary is a reading, not an empty list (#26238).
expect(ACTION_LOCATIONS.length).toBeGreaterThan(0);
});
});
7 changes: 4 additions & 3 deletions ui/packages/auth/src/__tests__/auth-spec-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,10 @@ describe('the AuthProvider exemption rests on the spec symbol being an id enum',
'ALLOW entry for @object-ui/auth:AuthProvider assumes it is an enum of provider ' +
'ids — re-read what it means now before keeping the exemption.',
).toBeTruthy();
expect(Object.keys((specAuthProvider as { def: { entries: Record<string, string> } }).def.entries).sort()).toEqual(
['github', 'google', 'ldap', 'local', 'microsoft', 'saml'],
);
// An enum of provider ids, asserted by member rather than as the spec's
// whole provider list (#26238): a provider the spec adds keeps it an id enum.
const providerIds = Object.keys((specAuthProvider as { def: { entries: Record<string, string> } }).def.entries);
for (const id of ['github', 'google', 'local']) expect(providerIds).toContain(id);
});

it('our AuthProvider is a React component, not a schema', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ function resolve(filter: unknown, scope: Record<string, unknown>) {
}

describe('{record_id} resolves against the record in scope (objectui#7297)', () => {
it('is the one record-context token the installed spec declares', () => {
it('is a record-context token the installed spec declares', () => {
// The resolver's `satisfies Record<RecordContextToken, …>` literal reds the
// compile on a second one; this states the same ratchet at runtime.
expect([...RECORD_CONTEXT_TOKENS]).toEqual(['record_id']);
// compile on a second one, so that ratchet lives in the type check; at
// runtime only the token this file resolves is asserted (#26238).
expect([...RECORD_CONTEXT_TOKENS]).toContain('record_id');
});

it('resolves in every filter shape, in both spellings', () => {
Expand Down
Loading
Loading