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
29 changes: 14 additions & 15 deletions ui/packages/app-shell/src/__tests__/spec-symbol-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,12 @@ describe('DecisionOutputDef is the spec type, with no local divergence left', ()
type _IsExactlyTheSpecType = Assert<Equal<DecisionOutputDef, SpecDecisionOutputDef>>;

// Deriving NARROWED `type` from the bare `string` this file used to declare
// to the spec's closed enum — that narrowing is the point, so pin it.
type _TypeIsClosed = Assert<
Equal<DecisionOutputDef['type'], 'user' | 'department' | 'position' | 'team' | 'text' | undefined>
// to the spec's closed enum — that narrowing is the point, so pin it: not
// `string`, and the five kinds still in it. Nothing in ui switches over
// every kind, so a kind the spec adds is the spec's to add, not a red here.
type _TypeIsClosed = Assert<Equal<string extends DecisionOutputDef['type'] ? true : false, false>>;
type _TypeKeepsTheFiveKinds = Assert<
Equal<Exclude<'user' | 'department' | 'position' | 'team' | 'text', DecisionOutputDef['type']>, never>
>;

expect(true).toBe(true);
Expand Down Expand Up @@ -447,8 +450,10 @@ describe('flow node geometry IS the spec `FlowNode.position` (#3172)', () => {
type _InputEqualsParsed = Assert<
Equal<NonNullable<SpecFlowNode['position']>, NonNullable<SpecFlowNodeParsed['position']>>
>;
// Both coordinates required: a half-position is not representable.
type _BothRequired = Assert<Equal<FlowNodePosition, { x: number; y: number }>>;
// Both coordinates required: a half-position is not representable. Pinned
// on the two coordinates the canvas reads, so a key the spec adds to
// `position` is not a red here.
type _BothRequired = Assert<Equal<Pick<FlowNodePosition, 'x' | 'y'>, { x: number; y: number }>>;
type _HalfIsNotAPosition = Assert<Equal<Extends<{ x: number }, FlowNodePosition>, false>>;

// …and the designer's node carries exactly that key, optional exactly as the
Expand Down Expand Up @@ -488,22 +493,16 @@ describe('ObjectFieldGroup derives from the spec schema INPUT side', () => {
// unrepresentable. If this flips, someone swapped z.input for z.infer.
type _CollapseOptional = Assert<Extends<{ key: string; label: string }, ObjectFieldGroup>>;

// Still the real spec vocabulary, not a hand copy that merely agrees.
// Still the real spec vocabulary, not a hand copy that merely agrees. The
// type is `z.input` of the spec schema itself, so the keys it carries are
// the spec's by construction: this names the ones the designer reads, and a
// key the spec adds is not a red here.
type _HasSpecKeys = Assert<
Extends<
'key' | 'label' | 'icon' | 'description' | 'visibleWhen' | 'collapse' | 'collapsible' | 'collapsed' | 'defaultExpanded',
keyof ObjectFieldGroup
>
>;
type _NoInventedKeys = Assert<
Equal<
Exclude<
keyof ObjectFieldGroup,
'key' | 'label' | 'icon' | 'description' | 'visibleWhen' | 'collapse' | 'collapsible' | 'collapsed' | 'defaultExpanded'
>,
never
>
>;

expect(true).toBe(true);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ export const drawableUnderGrownOnceExplained: Record<
* for — under the resolved spec AND under `main`'s. This is what makes the
* change a re-spelling rather than a re-definition: the published type does not
* move, only the way it is derived.
*
* Exact on purpose: ui draws this union exhaustively. A view kind the spec adds
* lands in `ListViewVisualization` unless `UndrawableViewKind` names it, and
* then `LIST_VIEW_KINDS` in `normalize-list-view.ts` (a total `Record` over the
* union) fails the build there first. That record and `ListView`'s `case` list
* are where the new kind is drawn; this list follows them.
*/
export type _VisualizationUnionIsTheNineDrawnKinds = Expect<
Equal<
Expand Down
10 changes: 8 additions & 2 deletions ui/packages/data-objectstack/src/queryDataset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,12 +449,18 @@ describe('queryDataset(selection) is @objectstack/spec DatasetSelection itself',
// month at all. A restatement does not only drift on the fields it has — it
// also stops at whatever the contract looked like the day it was written.
type _HasDateGranularity = Assert<HasKey<SelectionParam, 'dateGranularity'>>;
// The same lesson holds for this pin, so it names the five buckets rather
// than restating the whole enum: a bucket the spec adds is not a red here,
// and a widening to `string` still is.
type _DateGranularityEnum = Assert<
Equal<
SelectionParam['dateGranularity'],
'day' | 'week' | 'month' | 'quarter' | 'year' | undefined
Exclude<'day' | 'week' | 'month' | 'quarter' | 'year', SelectionParam['dateGranularity']>,
never
>
>;
type _DateGranularityIsClosed = Assert<
Equal<string extends SelectionParam['dateGranularity'] ? true : false, false>
>;

expect(true).toBe(true);
});
Expand Down
16 changes: 14 additions & 2 deletions ui/packages/data-objectstack/src/spec-symbol-batch6.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ describe('DroppedFieldsEvent IS the spec type, not a mirror of it', () => {
// and `main` agree again and this is the exact five-member union, with no
// `Exclude`: any new arm is a remainder `Equal` refuses, and
// `_ReasonIsNotString` below still refuses a widening to `string`.
//
// This pin stays exact on purpose: ui renders the union exhaustively.
// `STRIPPED_LINE` in `@object-ui/app-shell`'s `writeWarningToast.ts` is a
// `Record` over `DroppedFieldsEvent['reason']`, so a new arm also fails the
// app-shell build there. That map is where the new arm needs its wording
// (and both locale tables their row); this list follows it.
type _ReasonIsTheEnum = Assert<
Equal<
DroppedFieldsEvent['reason'],
Expand All @@ -281,7 +287,11 @@ describe('MetadataCacheStats is not the spec ICacheService stats', () => {
// spec's is a server KV store sized in keys and bytes; this one is a bounded
// browser LRU that evicts, coalesces in-flight fetches and reports a rate.
type _Shared = Assert<Equal<Extract<keyof MetadataCacheStats, keyof SpecCacheStats>, 'hits' | 'misses'>>;
type _SpecOnly = Assert<Equal<Exclude<keyof SpecCacheStats, keyof MetadataCacheStats>, 'keyCount' | 'memoryUsage'>>;
// The spec side is named by the two keys that make it a KV store, not
// closed: a key the spec adds to its own stats is not a red here.
type _SpecOnly = Assert<
Equal<Exclude<'keyCount' | 'memoryUsage', Exclude<keyof SpecCacheStats, keyof MetadataCacheStats>>, never>
>;
type _LocalOnly = Assert<
Equal<
Exclude<keyof MetadataCacheStats, keyof SpecCacheStats>,
Expand Down Expand Up @@ -319,7 +329,9 @@ describe('DataApiValidationError is an Error, the spec ValidationError is a reco

// The spec's is one entry in a validation report. `@object-ui/types`
// re-exports it under that name, so the two were importable side by side.
type _SpecShape = Assert<Equal<Exclude<keyof SpecValidationError, 'field' | 'message' | 'code'>, never>>;
// Named by the three members a report entry carries; a key the spec adds
// to it is not a red here.
type _SpecShape = Assert<Equal<Exclude<'field' | 'message' | 'code', keyof SpecValidationError>, never>>;

// This one is a thrown runtime object: it has a `stack`, which no data
// record does, and it carries the whole list the spec type is one item of.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,12 @@ describe('the pending-action row and status ARE the spec contract', () => {
type _NoCreatedAt = Assert<Equal<'created_at' extends keyof PendingActionRow ? true : false, false>>;
type _NoUpdatedAt = Assert<Equal<'updated_at' extends keyof PendingActionRow ? true : false, false>>;

// The vocabulary the inbox's tabs and badges are built on.
// The vocabulary the inbox's tabs and badges are built on: each status they
// name is still the spec's. No inbox structure is total over the union, so
// a status the spec adds is not a red here; `_StatusIsNotString` above
// still refuses a widening.
type _Vocabulary = Assert<
Equal<PendingActionStatus, 'pending' | 'approved' | 'executed' | 'failed' | 'rejected'>
Equal<Exclude<'pending' | 'approved' | 'executed' | 'failed' | 'rejected', PendingActionStatus>, never>
>;

expect(true).toBe(true);
Expand Down Expand Up @@ -291,8 +294,10 @@ describe('the decision outcomes ARE the spec wire responses', () => {
type _RejectStatusNotString = Assert<
Equal<string extends RejectOutcome['status'] ? true : false, false>
>;
type _ApproveVocabulary = Assert<Equal<ApproveOutcome['status'], 'executed' | 'failed'>>;
type _RejectVocabulary = Assert<Equal<RejectOutcome['status'], 'rejected'>>;
// The statuses the inbox compares against are still on each response; a
// status the spec adds to either is not a red here.
type _ApproveVocabulary = Assert<Equal<Exclude<'executed' | 'failed', ApproveOutcome['status']>, never>>;
type _RejectVocabulary = Assert<Equal<Exclude<'rejected', RejectOutcome['status']>, never>>;

// Drift 3 — `[k: string]: unknown` on the approve copy. objectstack#4075:
// with it, this whole describe block would have been green on the copies.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,23 +520,28 @@ const derivedFromSpec: Equal<MasterDetailDetailConfig, Omit<SpecDetailEntry, 'so
const columnsAreTheGridColumn: Equal<NonNullable<MasterDetailDetailConfig['columns']>[number], GridColumn> = true;

/**
* Every member a detail can be authored with, spelled out: the spec entry's
* keys minus `sortField` (objectui#11070 round 9). A key the spec adds is a
* red row here, never a silent widening of what this block is said to read.
* Every member a detail is authored with today, spelled out: the spec entry's
* keys minus `sortField` (objectui#11070 round 9). Each must stay on the
* config. The list is not closed: `derivedFromSpec` above already makes the
* config the spec's entry, so a closed list would only restate the spec, and a
* key the spec adds would be a red row here with no ui change behind it.
*/
const memberList: Equal<
keyof MasterDetailDetailConfig,
| 'childObject'
| 'relationshipField'
| 'columns'
| 'formFields'
| 'inlineMode'
| 'amountField'
| 'totalField'
| 'title'
| 'minRows'
| 'maxRows'
| 'addLabel'
Exclude<
| 'childObject'
| 'relationshipField'
| 'columns'
| 'formFields'
| 'inlineMode'
| 'amountField'
| 'totalField'
| 'title'
| 'minRows'
| 'maxRows'
| 'addLabel',
keyof MasterDetailDetailConfig
>,
never
> = true;

/** The lit control for the directive below: the same literal without the retired member compiles. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ type InputOf<T> = T extends z.ZodType ? z.input<T> : never;
* `deleteBehavior` carries the spec's vocabulary on both faces, and `onDelete`
* reads as `undefined` on both — a `?: never` tombstone, and an alias refusal
* whose `z.input` is `undefined`.
*
* The TypeScript face IS the spec's member (first row), so the second row names
* the three actions rather than restating the spec's enum: an action the spec
* adds is not a red here. The zod face's enum is this package's own list, so
* its row stays exact, and `zod-mirror-parity.test.ts` holds it to the spec's.
*/
export type assertionRespelled = [
Expect<Equal<DataModelRelationship['deleteBehavior'], SpecField['deleteBehavior']>>,
Expect<Equal<DataModelRelationship['deleteBehavior'], 'set_null' | 'cascade' | 'restrict' | undefined>>,
Expect<Equal<Exclude<'set_null' | 'cascade' | 'restrict', DataModelRelationship['deleteBehavior']>, never>>,
Expect<Equal<InputOf<ShapeOf<typeof DataModelRelationshipSchema>['deleteBehavior']>, 'set_null' | 'cascade' | 'restrict' | undefined>>,
Expect<Equal<DataModelRelationship['onDelete'], undefined>>,
Expect<Equal<InputOf<ShapeOf<typeof DataModelRelationshipSchema>['onDelete']>, undefined>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,15 @@ type _FaceCarriesStreaming = Expect<
Equal< Exclude< ListViewExportFace, unknown[] >['streaming'], boolean | undefined >
>;

/** The array arm's vocabulary is the spec's three formats — `'pdf'` is not among them. */
type _ArrayArmIsCsvXlsxJson = Expect<
Equal< Extract< ListViewExportFace, unknown[] >[number], 'csv' | 'xlsx' | 'json' >
>;
/**
* The array arm's vocabulary carries the spec's three formats — `'pdf'` is not
* among them, and it is not `string`. Membership, not a closed list: nothing in
* ui is total over the formats, so a format the spec adds is not a red here.
*/
type ArrayArmFormat = Extract< ListViewExportFace, unknown[] >[number];
type _ArrayArmIsCsvXlsxJson = Expect< Equal< Exclude< 'csv' | 'xlsx' | 'json', ArrayArmFormat >, never > >;
type _ArrayArmHasNoPdf = Expect< Equal< 'pdf' extends ArrayArmFormat ? true : false, false > >;
type _ArrayArmIsClosed = Expect< Equal< string extends ArrayArmFormat ? true : false, false > >;

/** A minimal, valid `list-view` node — the envelope every parse below rides on. */
const NODE = { type: 'list-view', objectName: 'accounts' } as const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,18 @@ type Expect< T extends true > = T;
/* ── 1. The declared member, in the spec's shape ─────────────────────────── */

export type _DependsOnIsDeclaredInSpecShape = Expect< Equal< NonNullable< BaseFieldMetadata['dependsOn'] >, FieldDependsOn > >;
// The derived shape is exactly what the installed spec declares at field level
// (`z.array(z.union([z.string(), z.strictObject({ field, param? })]))`) — if a
// spec release widens or narrows it, this line moves with it and says so.
export type _SpecShapeIsArrayOfNameOrEntry = Expect< Equal< FieldDependsOn, Array< string | { field: string; param?: string | undefined } > > >;
// The derived shape is what the installed spec declares at field level
// (`z.array(z.union([z.string(), z.strictObject({ field, param? })]))`): an
// array of a bare name or an entry with `field` and an optional `param`. Pinned
// on those members, so a narrowing is red here and a key the spec adds to the
// entry is not.
type DependsOnItem = FieldDependsOn extends Array< infer I > ? I : never;
export type _SpecShapeIsArray = Expect< Equal< FieldDependsOn, Array< DependsOnItem > > >;
export type _SpecShapeAdmitsAName = Expect< Equal< Extract< DependsOnItem, string >, string > >;
export type _SpecShapeIsArrayOfNameOrEntry = Expect< Equal<
Pick< Exclude< DependsOnItem, string >, 'field' | 'param' >,
{ field: string; param?: string | undefined }
> >;
// Inherited, not restated: the select and lookup faces carry the SAME member.
export type _SelectInherits = Expect< Equal< SelectFieldMetadata['dependsOn'], BaseFieldMetadata['dependsOn'] > >;
export type _LookupInherits = Expect< Equal< LookupFieldMetadata['dependsOn'], BaseFieldMetadata['dependsOn'] > >;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,14 @@ type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B

describe('objectui#6152 round 12 — the TypeScript face is closed the same way', () => {
it('the bag type has the eight kinds and no index signature', () => {
// `closed` is the no-index-signature half. The eight kinds are members, not
// a closed list: the bag's keys are the spec's, so a kind the spec adds is
// not a red here.
const closed: string extends keyof Bag ? 'open' : 'closed' = 'closed';
const kinds: Equal<keyof Bag, 'kanban' | 'calendar' | 'gallery' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree'> = true;
const kinds: Equal<
Exclude<'kanban' | 'calendar' | 'gallery' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree', keyof Bag>,
never
> = true;
expect([closed, kinds]).toEqual(['closed', true]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,9 @@ const _blockDescriptionRefusesTheLegacyObjectArm = true satisfies (
: false
);
// 5. UNCHANGED: `name` and `type` mean what the hand-written interface said.
// `type` is held to the three block types and its optionality, not closed:
// a block type the spec adds is not a red here (nothing in ui is total over
// this member; `resolveReportPresentation` falls back to `tabular`).
const _blockNameIsUnchanged = true satisfies (
[BlockMember<PublishedJoinedReportBlock, 'name'>] extends [never]
? false
Expand All @@ -931,7 +934,11 @@ const _blockNameIsUnchanged = true satisfies (
const _blockTypeIsUnchanged = true satisfies (
[BlockMember<PublishedJoinedReportBlock, 'type'>] extends [never]
? false
: SameType<BlockMember<PublishedJoinedReportBlock, 'type'>, 'tabular' | 'summary' | 'matrix' | undefined>
: ['tabular' | 'summary' | 'matrix' | undefined] extends [BlockMember<PublishedJoinedReportBlock, 'type'>]
? [string] extends [BlockMember<PublishedJoinedReportBlock, 'type'>]
? false
: true
: false
);
void _specJoinedReportBlockTyping;
void _specJoinedReportBlockIsNotEvenAny;
Expand Down
Loading
Loading