diff --git a/.changeset/5856-plugin-grid-catalog-entries.md b/.changeset/5856-plugin-grid-catalog-entries.md
new file mode 100644
index 0000000000..e44709e0e4
--- /dev/null
+++ b/.changeset/5856-plugin-grid-catalog-entries.md
@@ -0,0 +1,26 @@
+---
+---
+
+Docs and catalog fixtures only, no shipped code touched: the two `plugin-grid` catalog
+entries are now real `object-grid` nodes instead of hand-built static card layouts.
+`content/docs/plugins/plugin-grid.mdx` mounted `product-inventory-grid` and
+`team-members-grid` under `PluginLoader plugins={['grid']}` while both authored only
+`badge button card flex stack text` — pictures of a grid, not a grid. They are replaced
+by `object-grid-columns` (a `ListColumn` set with `sort`, `searchableFields` and
+`pagination`) and `object-grid-selection-summaries` (multi-row `selection`, named
+`rowActions` / `bulkActions`, per-column footer `summary` roll-ups), both querying the
+docs gallery's demo data source the same way the `plugin-view` entries have since
+objectui#5113.
+
+The two mock-ups are legitimate static layouts filed under the wrong plugin, so they are
+re-seated into `components-layout-card` as `inventory-table-card` and `team-roster-card`
+rather than deleted — deleting a catalog entry moves three corpus-wide counters
+(`NODE_CENSUS` in `layout-dom-leak-5574.test.tsx`, and the `className`-carrying layout
+node and `stack` node floors in `layout-props-conversion.test.tsx`), and a floor that
+moves because a fixture was deleted is indistinguishable later from one that moved
+because coverage regressed. All three are unchanged, with no floor edited.
+
+objectui#5113's pin in `catalog-gallery-render.test.tsx` is extended to cover
+`plugin-grid` through an explicit two-entry category/type map, keeping both of its halves
+— every entry authors the type its own package registers, and the rendered tile shows a
+record that exists only in the gallery's data source.
diff --git a/content/docs/plugins/plugin-grid.mdx b/content/docs/plugins/plugin-grid.mdx
index 87ec12d642..72fa9d6402 100644
--- a/content/docs/plugins/plugin-grid.mdx
+++ b/content/docs/plugins/plugin-grid.mdx
@@ -17,9 +17,23 @@ npm install @object-ui/plugin-grid
## Interactive Examples
-
+Each preview below **is** an `object-grid` node drawn by this plugin — the JSON
+in the Code tab is the whole example, and the rows on screen came out of a
+`find()` call, not out of that JSON. The records are served by the docs site's
+demo data source, because `dataSource` is not a schema key: it is the prop the
+registered renderer pulls off `SchemaRendererProvider` context (see
+[ObjectQL Integration](#objectql-integration) below), so in your own app these
+same nodes read whatever object your data source serves. That fixture answers
+search and sort but not filters, which is the only reason no `filter` is
+authored here.
-
+### Columns, sort and search
+
+
+
+### Selection, row actions and column summaries
+
+
## Features
diff --git a/examples/schema-catalog/src/catalog-meta.json b/examples/schema-catalog/src/catalog-meta.json
index 26d74e0a78..83157eedd9 100644
--- a/examples/schema-catalog/src/catalog-meta.json
+++ b/examples/schema-catalog/src/catalog-meta.json
@@ -36,6 +36,16 @@
"verification"
]
},
+ "components-layout-card/inventory-table-card": {
+ "title": "Inventory Table Card",
+ "description": "A card that draws a product inventory table by hand — toolbar buttons, header row, stock-status badges. For a table bound to an object, see the `plugin-grid` examples.",
+ "tags": [
+ "card",
+ "table",
+ "badge",
+ "layout"
+ ]
+ },
"components-layout-card/profile-detail-card": {
"title": "Profile Detail Card",
"description": "A card that draws one record by hand — avatar header, label/value rows, edit and delete footer. For a record surface bound to an object, see the `plugin-view` examples.",
@@ -46,6 +56,16 @@
"avatar"
]
},
+ "components-layout-card/team-roster-card": {
+ "title": "Team Roster Card",
+ "description": "A card that draws a team roster table by hand — invite action, header row, presence badges. For a table bound to an object, see the `plugin-grid` examples.",
+ "tags": [
+ "card",
+ "table",
+ "badge",
+ "roster"
+ ]
+ },
"components-layout-card/user-list-card": {
"title": "User List Card",
"description": "A card that draws a directory table by hand — header row, striped rows, status badges. For a table bound to an object, see the `plugin-view` / `plugin-grid` examples.",
@@ -89,6 +109,26 @@
"title": "Filtered Dashboard — Target Widgets Allow-list",
"description": "Legacy targetWidgets allow-list: only listed widgets get the default binding; an explicit filterBindings entry still wins"
},
+ "plugin-grid/object-grid-columns": {
+ "title": "Object Grid — Column Definitions",
+ "description": "object-grid rendering the users object: `ListColumn` objects carrying label, width, align, cell `type` and `link`, with `sort`, `searchableFields` and `pagination` shaping the query the host's data source answers.",
+ "tags": [
+ "object-grid",
+ "columns",
+ "sort",
+ "objectql"
+ ]
+ },
+ "plugin-grid/object-grid-selection-summaries": {
+ "title": "Object Grid — Selection, Row Actions and Summaries",
+ "description": "The same object with multi-row `selection`, named `rowActions` / `bulkActions`, and per-column footer `summary` roll-ups computed over the rows the data source returns.",
+ "tags": [
+ "object-grid",
+ "selection",
+ "rowActions",
+ "summary"
+ ]
+ },
"plugin-view/object-view-list": {
"title": "Object View — List Surface",
"description": "object-view rendering the users object: the columns declared in `table`, with search and sort served by the host's data source.",
diff --git a/examples/schema-catalog/src/index.ts b/examples/schema-catalog/src/index.ts
index 22e4f23856..11fd014e35 100644
--- a/examples/schema-catalog/src/index.ts
+++ b/examples/schema-catalog/src/index.ts
@@ -253,7 +253,9 @@ import components_layout_aspect_ratio_square from './schemas/components-layout-a
import components_layout_aspect_ratio_ultrawide from './schemas/components-layout-aspect-ratio/ultrawide.json' with { type: 'json' };
import components_layout_aspect_ratio_video_aspect_ratio from './schemas/components-layout-aspect-ratio/video-aspect-ratio.json' with { type: 'json' };
import components_layout_card_basic_card from './schemas/components-layout-card/basic-card.json' with { type: 'json' };
+import components_layout_card_inventory_table_card from './schemas/components-layout-card/inventory-table-card.json' with { type: 'json' };
import components_layout_card_profile_detail_card from './schemas/components-layout-card/profile-detail-card.json' with { type: 'json' };
+import components_layout_card_team_roster_card from './schemas/components-layout-card/team-roster-card.json' with { type: 'json' };
import components_layout_card_user_list_card from './schemas/components-layout-card/user-list-card.json' with { type: 'json' };
import components_layout_card_with_footer from './schemas/components-layout-card/with-footer.json' with { type: 'json' };
import components_layout_container_basic_container from './schemas/components-layout-container/basic-container.json' with { type: 'json' };
@@ -417,8 +419,8 @@ import plugin_form_contact_form from './schemas/plugin-form/contact-form.json' w
import plugin_gantt_construction_project_phases from './schemas/plugin-gantt/construction-project-phases.json' with { type: 'json' };
import plugin_gantt_project_timeline_with_dependencies from './schemas/plugin-gantt/project-timeline-with-dependencies.json' with { type: 'json' };
import plugin_gantt_sprint_development_timeline from './schemas/plugin-gantt/sprint-development-timeline.json' with { type: 'json' };
-import plugin_grid_product_inventory_grid from './schemas/plugin-grid/product-inventory-grid.json' with { type: 'json' };
-import plugin_grid_team_members_grid from './schemas/plugin-grid/team-members-grid.json' with { type: 'json' };
+import plugin_grid_object_grid_columns from './schemas/plugin-grid/object-grid-columns.json' with { type: 'json' };
+import plugin_grid_object_grid_selection_summaries from './schemas/plugin-grid/object-grid-selection-summaries.json' with { type: 'json' };
import plugin_kanban_advanced_kanban_with_badges_and_limits from './schemas/plugin-kanban/advanced-kanban-with-badges-and-limits.json' with { type: 'json' };
import plugin_kanban_basic_kanban_board from './schemas/plugin-kanban/basic-kanban-board.json' with { type: 'json' };
import plugin_map_event_venue_finder from './schemas/plugin-map/event-venue-finder.json' with { type: 'json' };
@@ -2623,6 +2625,16 @@ const REGISTRY: Record = {
},
schema: components_layout_card_basic_card,
},
+ 'components-layout-card/inventory-table-card': {
+ id: 'components-layout-card/inventory-table-card',
+ meta: {
+ title: "Inventory Table Card",
+ description: "A card that draws a product inventory table by hand — toolbar buttons, header row, stock-status badges. For a table bound to an object, see the `plugin-grid` examples.",
+ category: 'components-layout-card',
+ tags: ["card", "table", "badge", "layout"],
+ },
+ schema: components_layout_card_inventory_table_card,
+ },
'components-layout-card/profile-detail-card': {
id: 'components-layout-card/profile-detail-card',
meta: {
@@ -2633,6 +2645,16 @@ const REGISTRY: Record = {
},
schema: components_layout_card_profile_detail_card,
},
+ 'components-layout-card/team-roster-card': {
+ id: 'components-layout-card/team-roster-card',
+ meta: {
+ title: "Team Roster Card",
+ description: "A card that draws a team roster table by hand — invite action, header row, presence badges. For a table bound to an object, see the `plugin-grid` examples.",
+ category: 'components-layout-card',
+ tags: ["card", "table", "badge", "roster"],
+ },
+ schema: components_layout_card_team_roster_card,
+ },
'components-layout-card/user-list-card': {
id: 'components-layout-card/user-list-card',
meta: {
@@ -4102,23 +4124,25 @@ const REGISTRY: Record = {
},
schema: plugin_gantt_sprint_development_timeline,
},
- 'plugin-grid/product-inventory-grid': {
- id: 'plugin-grid/product-inventory-grid',
+ 'plugin-grid/object-grid-columns': {
+ id: 'plugin-grid/object-grid-columns',
meta: {
- title: "Product Inventory Grid",
- description: "",
+ title: "Object Grid — Column Definitions",
+ description: "object-grid rendering the users object: `ListColumn` objects carrying label, width, align, cell `type` and `link`, with `sort`, `searchableFields` and `pagination` shaping the query the host's data source answers.",
category: 'plugin-grid',
+ tags: ["object-grid", "columns", "sort", "objectql"],
},
- schema: plugin_grid_product_inventory_grid,
+ schema: plugin_grid_object_grid_columns,
},
- 'plugin-grid/team-members-grid': {
- id: 'plugin-grid/team-members-grid',
+ 'plugin-grid/object-grid-selection-summaries': {
+ id: 'plugin-grid/object-grid-selection-summaries',
meta: {
- title: "Team Members Grid",
- description: "",
+ title: "Object Grid — Selection, Row Actions and Summaries",
+ description: "The same object with multi-row `selection`, named `rowActions` / `bulkActions`, and per-column footer `summary` roll-ups computed over the rows the data source returns.",
category: 'plugin-grid',
+ tags: ["object-grid", "selection", "rowActions", "summary"],
},
- schema: plugin_grid_team_members_grid,
+ schema: plugin_grid_object_grid_selection_summaries,
},
'plugin-kanban/advanced-kanban-with-badges-and-limits': {
id: 'plugin-kanban/advanced-kanban-with-badges-and-limits',
diff --git a/examples/schema-catalog/src/schemas/plugin-grid/product-inventory-grid.json b/examples/schema-catalog/src/schemas/components-layout-card/inventory-table-card.json
similarity index 100%
rename from examples/schema-catalog/src/schemas/plugin-grid/product-inventory-grid.json
rename to examples/schema-catalog/src/schemas/components-layout-card/inventory-table-card.json
diff --git a/examples/schema-catalog/src/schemas/plugin-grid/team-members-grid.json b/examples/schema-catalog/src/schemas/components-layout-card/team-roster-card.json
similarity index 100%
rename from examples/schema-catalog/src/schemas/plugin-grid/team-members-grid.json
rename to examples/schema-catalog/src/schemas/components-layout-card/team-roster-card.json
diff --git a/examples/schema-catalog/src/schemas/plugin-grid/object-grid-columns.json b/examples/schema-catalog/src/schemas/plugin-grid/object-grid-columns.json
new file mode 100644
index 0000000000..4f90efa6b7
--- /dev/null
+++ b/examples/schema-catalog/src/schemas/plugin-grid/object-grid-columns.json
@@ -0,0 +1,15 @@
+{
+ "type": "object-grid",
+ "objectName": "users",
+ "label": "User Directory",
+ "columns": [
+ { "field": "name", "label": "Full Name", "width": 200, "link": true },
+ { "field": "email", "label": "Email" },
+ { "field": "role", "type": "select" },
+ { "field": "department", "label": "Department" },
+ { "field": "status", "type": "select", "align": "right" }
+ ],
+ "sort": [{ "field": "name", "order": "asc" }],
+ "searchableFields": ["name", "email", "department"],
+ "pagination": { "pageSize": 5 }
+}
diff --git a/examples/schema-catalog/src/schemas/plugin-grid/object-grid-selection-summaries.json b/examples/schema-catalog/src/schemas/plugin-grid/object-grid-selection-summaries.json
new file mode 100644
index 0000000000..51f58bd0a6
--- /dev/null
+++ b/examples/schema-catalog/src/schemas/plugin-grid/object-grid-selection-summaries.json
@@ -0,0 +1,16 @@
+{
+ "type": "object-grid",
+ "objectName": "users",
+ "label": "User Directory",
+ "columns": [
+ { "field": "name", "label": "Full Name", "summary": "count_filled" },
+ { "field": "email", "label": "Email" },
+ { "field": "department", "label": "Department", "summary": { "type": "count_unique" } },
+ { "field": "role", "type": "select" },
+ { "field": "status", "type": "select" }
+ ],
+ "selection": { "type": "multiple" },
+ "rowActions": ["view", "edit"],
+ "bulkActions": ["export"],
+ "pagination": { "pageSize": 5 }
+}
diff --git a/examples/schema-catalog/test/catalog-gallery-render.test.tsx b/examples/schema-catalog/test/catalog-gallery-render.test.tsx
index f042097e2b..eb6b543874 100644
--- a/examples/schema-catalog/test/catalog-gallery-render.test.tsx
+++ b/examples/schema-catalog/test/catalog-gallery-render.test.tsx
@@ -274,12 +274,13 @@ const AUTHORED_TEXT_EXEMPT: Record = {};
*
* objectui#5113 added the object surface (`getObjectSchema` / `find` / the
* writes) to the host fixture, because `object-view` reaches its data through
- * exactly this context value — `dataSource` is not a schema key. The three
- * `plugin-view` entries render through it, which is what
- * `THE PLUGIN-VIEW ENTRIES` below asserts. What the mirror reproduces is the
- * host's SURFACE and its rows; the query semantics ($search / $orderby /
- * windowing) are the host's, and the parity case pins the method names rather
- * than re-deriving them here.
+ * exactly this context value — `dataSource` is not a schema key. The same is
+ * true of `object-grid`, which is how objectui#5856 could give `plugin-grid`
+ * real entries without touching the fixture at all. Both categories' entries
+ * render through it, which is what `CATEGORY_OWN_TYPE` below asserts. What the
+ * mirror reproduces is the host's SURFACE and its rows; the query semantics
+ * ($search / $orderby / windowing) are the host's, and the parity case pins the
+ * method names rather than re-deriving them here.
*/
const USERS_ROWS = [
{ id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'admin', department: 'Engineering', status: 'active', created_at: '2024-01-14' },
@@ -606,100 +607,152 @@ describe('objectui#4616 — every catalog entry renders in the docs gallery', ()
});
/**
- * THE PLUGIN-VIEW ENTRIES ACTUALLY USE THE PLUGIN (objectui#5113).
+ * THESE PLUGIN CATEGORIES' ENTRIES ACTUALLY USE THEIR PLUGIN
+ * (objectui#5113 for `plugin-view`, objectui#5856 for `plugin-grid`).
*
* The sweep above answers "does every tile draw". It cannot answer the
* question objectui#5113 was filed on: whether an example mounted under a
- * PLUGIN's docs page exercises that plugin. The three `plugin-view` entries
- * used to be hand-built static card layouts — `card` / `flex` / `text` /
- * `badge`, no `object-view` node anywhere — sitting on
- * `content/docs/plugins/plugin-view.mdx` under an "Interactive Examples"
+ * PLUGIN's docs page exercises that plugin. Both categories below failed it in
+ * exactly the same way, one card apart.
+ *
+ * `plugin-view`'s three entries used to be hand-built static card layouts —
+ * `card` / `flex` / `text` / `badge`, no `object-view` node anywhere — sitting
+ * on `content/docs/plugins/plugin-view.mdx` under an "Interactive Examples"
* heading and inside a `PluginLoader plugins={['view']}` wrapper none of them
- * used. Every check in the repo was green on them: the types they named ARE
- * registered, and the tiles DID draw.
+ * used. `plugin-grid`'s two were the same defect on
+ * `content/docs/plugins/plugin-grid.mdx` under `PluginLoader plugins={['grid']}`
+ * — measured on this card's own merge-base, both authored exactly
+ * `badge button card flex stack text` and no `object-grid`. Every check in the
+ * repo was green on all five: the types they named ARE registered
+ * (`check-doc-component-types` asks only that), and the tiles DID draw (the
+ * sweep above asks only that). Neither gate can see this defect, which is why
+ * this one exists.
*
- * Two facts are pinned here, and the second is the one that cannot be
- * satisfied by a picture of a view:
+ * Two facts are pinned per category, and the second is the one that cannot be
+ * satisfied by a picture of the component:
*
- * 1. STRUCTURE — every entry in the category authors an `object-view` node.
+ * 1. STRUCTURE — every entry in the category authors a node of the type its
+ * own package registers.
* 2. RENDER — the tile shows a record that exists only in the gallery's data
- * source, so the rows on screen came through `ObjectViewRenderer` →
- * `ObjectGrid` → `dataSource.find`, not out of the entry's own JSON. An
- * entry that went back to drawing its own table would keep (1) satisfiable
- * by a stray node and would fail (2) outright.
+ * source, so the rows on screen came through the registered renderer →
+ * `dataSource.find`, not out of the entry's own JSON. An entry that went
+ * back to drawing its own table would keep (1) satisfiable by a stray node
+ * and would fail (2) outright.
+ *
+ * (2) is the half that does the work, and it is discriminating rather than
+ * incidental: an `object-grid` node whose rows come from an inline
+ * `data: { provider: 'value', items: [...] }` satisfies (1), renders a
+ * perfectly good table — measured, 51 elements — and contains no gallery
+ * record at all, so it fails (2). A grid wired to nothing cannot pass here.
+ *
+ * ## Why an explicit two-entry map and not a rule over `plugin-*`
*
- * Deliberately scoped to `plugin-view` rather than generalized to every
- * `plugin-*` category: the general rule needs a per-plugin map of which types
- * each package registers, and other categories (`plugin-grid`'s two entries,
- * for one) are hand-built mock-ups of exactly this kind today. That is a
- * separate card, not a silent exemption list here.
+ * `CATEGORY_OWN_TYPE` is enumerated, not derived. objectui#5113 scoped its pin
+ * to one category because the general rule needs a per-plugin map of which
+ * types each package registers, and it named `plugin-grid`'s two entries as
+ * the specific reason the rule could not simply be turned on for everything.
+ * objectui#5856 removed that obstacle — `plugin-grid` was the last `plugin-*`
+ * category whose entries were pictures of the component rather than the
+ * component — so generalizing is now possible and is deliberately left to its
+ * own card. Adding a category here is a two-line change; what a reader must
+ * NOT do is convert this into a loop over every `plugin-*` category with an
+ * exemption list, which would turn each remaining gap into a silent entry in a
+ * table rather than a card someone owns.
+ *
+ * The types are asserted to be REGISTERED first, separately from the render,
+ * because the gallery's registration of `object-grid` is transitive: the host
+ * list (`HOST_PACKAGES`) does not name `@object-ui/plugin-grid`, and the type
+ * arrives because `@object-ui/plugin-view` imports `ObjectGrid` from it, which
+ * runs that package's `register` calls. That is load-bearing and invisible, so
+ * it gets its own assertion — a `plugin-view` that stopped importing
+ * `ObjectGrid` would otherwise turn the render case red with "Unknown
+ * component type" and no explanation of why.
*/
-describe('objectui#5113 — the plugin-view entries render THROUGH object-view', () => {
- const pluginViewEntries = entries.filter((e) => e.meta.category === 'plugin-view');
-
- /** Every `type` string anywhere in a schema tree. */
- function nodeTypes(node: unknown, acc: Set = new Set()): Set {
- if (Array.isArray(node)) {
- for (const n of node) nodeTypes(n, acc);
- return acc;
- }
- if (node && typeof node === 'object') {
- for (const [k, v] of Object.entries(node as Record)) {
- if (k === 'type' && typeof v === 'string') acc.add(v);
- nodeTypes(v, acc);
- }
- }
- return acc;
- }
-
- it('the category is populated (guard is not vacuous)', () => {
- expect(pluginViewEntries.length).toBeGreaterThanOrEqual(3);
- });
+const CATEGORY_OWN_TYPE: ReadonlyArray<
+ readonly [category: string, ownType: string, minEntries: number]
+> = [
+ ['plugin-view', 'object-view', 3],
+ ['plugin-grid', 'object-grid', 2],
+];
- it.each(pluginViewEntries.map((e) => [e.id, e.schema] as const))(
- '%s authors an object-view node',
- (_id, schema) => {
- expect([...nodeTypes(schema)]).toContain('object-view');
- },
- );
+describe.each(CATEGORY_OWN_TYPE)(
+ 'objectui#5113/#5856 — the %s entries render THROUGH %s',
+ (category, ownType, minEntries) => {
+ const categoryEntries = entries.filter((e) => e.meta.category === category);
- it.each(pluginViewEntries.map((e) => [e.id, e.schema] as const))(
- '%s puts data from the gallery data source on screen',
- async (_id, schema) => {
- const r = await renderEntry(schema);
- try {
- // Not authored anywhere in the catalog — it exists only in the fixture.
- expect(r.text).toContain('Alice Johnson');
- } finally {
- teardown(r);
+ /** Every `type` string anywhere in a schema tree. */
+ function nodeTypes(node: unknown, acc: Set = new Set()): Set {
+ if (Array.isArray(node)) {
+ for (const n of node) nodeTypes(n, acc);
+ return acc;
}
- },
- );
+ if (node && typeof node === 'object') {
+ for (const [k, v] of Object.entries(node as Record)) {
+ if (k === 'type' && typeof v === 'string') acc.add(v);
+ nodeTypes(v, acc);
+ }
+ }
+ return acc;
+ }
- /**
- * HOST PARITY for the fixture, same technique and same reason as the
- * registration-set parity above: the mirror at the top of this file is what
- * the assertions run against, so a host fixture that lost `find` would leave
- * this file green while the docs page went back to an empty view.
- */
- describe('the docs-site hosts supply the same fixture', () => {
- const siteDir = path.join(process.cwd(), 'apps/site/app/components');
- const read = (f: string) => fs.readFileSync(path.join(siteDir, f), 'utf8');
+ it('the category is populated (guard is not vacuous)', () => {
+ expect(categoryEntries.length).toBeGreaterThanOrEqual(minEntries);
+ });
- it('the host fixture exposes every method this mirror implements', () => {
- const source = read('galleryDataSource.ts');
- const missing = GALLERY_DATA_SOURCE_METHODS.filter(
- (method) => !new RegExp(`\\basync ${method}\\s*\\(`).test(source),
- );
- expect(missing).toEqual([]);
+ it(`${ownType} is registered in the gallery's registration set`, () => {
+ expect(
+ ComponentRegistry.get(ownType),
+ `${ownType} resolves to no renderer, so the render case below would fail ` +
+ 'with the OBJUI-001 panel rather than with anything about the entries. ' +
+ 'For `object-grid` this is transitive — see the header.',
+ ).toBeTruthy();
});
- it.each(['SchemaThumbnail.tsx', 'InteractiveDemo.tsx'])(
- '%s hands it to the renderer',
- (host) => {
- expect(read(host)).toMatch(/^import \{ galleryDataSource \} from '\.\/galleryDataSource';$/m);
- expect(read(host)).toContain('dataSource: galleryDataSource');
+ it.each(categoryEntries.map((e) => [e.id, e.schema] as const))(
+ `%s authors a ${ownType} node`,
+ (_id, schema) => {
+ expect([...nodeTypes(schema)]).toContain(ownType);
+ },
+ );
+
+ it.each(categoryEntries.map((e) => [e.id, e.schema] as const))(
+ '%s puts data from the gallery data source on screen',
+ async (_id, schema) => {
+ const r = await renderEntry(schema);
+ try {
+ // Not authored anywhere in the catalog — it exists only in the fixture.
+ expect(r.text).toContain('Alice Johnson');
+ } finally {
+ teardown(r);
+ }
},
);
+ },
+);
+
+/**
+ * HOST PARITY for the fixture, same technique and same reason as the
+ * registration-set parity above: the mirror at the top of this file is what
+ * the assertions run against, so a host fixture that lost `find` would leave
+ * this file green while the docs page went back to an empty view.
+ */
+describe('objectui#5113 — the docs-site hosts supply the same fixture', () => {
+ const siteDir = path.join(process.cwd(), 'apps/site/app/components');
+ const read = (f: string) => fs.readFileSync(path.join(siteDir, f), 'utf8');
+
+ it('the host fixture exposes every method this mirror implements', () => {
+ const source = read('galleryDataSource.ts');
+ const missing = GALLERY_DATA_SOURCE_METHODS.filter(
+ (method) => !new RegExp(`\\basync ${method}\\s*\\(`).test(source),
+ );
+ expect(missing).toEqual([]);
});
+
+ it.each(['SchemaThumbnail.tsx', 'InteractiveDemo.tsx'])(
+ '%s hands it to the renderer',
+ (host) => {
+ expect(read(host)).toMatch(/^import \{ galleryDataSource \} from '\.\/galleryDataSource';$/m);
+ expect(read(host)).toContain('dataSource: galleryDataSource');
+ },
+ );
});