|
33 | 33 | * stay green if the containment check were removed, mis-keyed, or fed a manifest |
34 | 34 | * the tag is missing from — the three ways this fact dies without anyone |
35 | 35 | * noticing. The reachability controls below exist for the same reason. |
| 36 | + * |
| 37 | + * RE-POINTED BY objectui#9910 (2026-09-24). What `validateTree` reads is no |
| 38 | + * longer the flag but the declared `children` slot input (`acceptsChildren`); |
| 39 | + * the four declare both. The flag keeps its OTHER consumer — the react-page |
| 40 | + * JSX scope skips containers — which is what the second describe still pins, |
| 41 | + * and the childless control moved from `badge` (which now declares the slot it |
| 42 | + * renders as a label fallback) to `tabs` (renders `items[].content`, never |
| 43 | + * `schema.children`). |
36 | 44 | */ |
37 | 45 |
|
38 | 46 | import { describe, it, expect } from 'vitest'; |
39 | 47 | import { render, waitFor } from '@testing-library/react'; |
40 | 48 | import { ComponentRegistry } from '@object-ui/core'; |
41 | 49 | import { SchemaRenderer, AdapterCtx } from '@object-ui/react'; |
42 | | -import { manifestFromConfigs, validateTree } from '@object-ui/sdui-parser'; |
| 50 | +import { CHILD_LIST_KEY, manifestFromConfigs, validateTree } from '@object-ui/sdui-parser'; |
43 | 51 | import type { Diagnostic, SchemaElement } from '@object-ui/sdui-parser'; |
44 | 52 |
|
45 | 53 | // Module scope, not a hook: this import IS the registration (AGENTS.md |
@@ -91,27 +99,39 @@ describe('the ui layout primitives accept children without warning (objectui#674 |
91 | 99 | expect(diagnostics.filter((d) => d.code === CONTAINMENT)).toEqual([]); |
92 | 100 | }); |
93 | 101 |
|
| 102 | + it.each(LAYOUT_CONTAINERS)('`%s` declares the `children` slot the tier reads (objectui#9910)', (type) => { |
| 103 | + // The declaration behind the silence above, asserted by name so the |
| 104 | + // absence of a diagnostic cannot pass on a fallback: `validateTree` reads |
| 105 | + // this input and nothing else. |
| 106 | + const inputs = ComponentRegistry.getMeta(type)?.inputs ?? []; |
| 107 | + expect(inputs.find((i) => i.name === CHILD_LIST_KEY)?.type).toBe('slot'); |
| 108 | + }); |
| 109 | + |
94 | 110 | it('still reports `not-a-container` for a component that genuinely takes none', () => { |
95 | 111 | // The control that keeps every assertion above meaningful. Without it the |
96 | 112 | // suite would stay green if the containment check were deleted outright, or |
97 | | - // if `isContainer` were defaulted on — either of which turns this file into |
98 | | - // a measurement of nothing. `badge` is a leaf: it renders its own label and |
99 | | - // never reads `schema.children`, so children under it ARE an authoring |
100 | | - // mistake the author must still hear about. |
| 113 | + // if every registration declared the slot — either of which turns this file |
| 114 | + // into a measurement of nothing. `tabs` renders `items[].content` and never |
| 115 | + // reads `schema.children`, so children under it ARE an authoring mistake the |
| 116 | + // author must still hear about. (`badge` held this role until objectui#9910, |
| 117 | + // when it declared the slot it renders as its label fallback.) |
101 | 118 | // |
102 | | - // If `badge` ever legitimately becomes a container, this goes red — move the |
| 119 | + // If `tabs` ever legitimately reads `children`, this goes red — move the |
103 | 120 | // control to another childless registration rather than deleting it. |
104 | | - const codes = diagnose(withChildren('badge')).map((d) => d.code); |
| 121 | + const codes = diagnose(withChildren('tabs')).map((d) => d.code); |
105 | 122 | expect(codes).toContain(CONTAINMENT); |
| 123 | + expect((ComponentRegistry.getMeta('tabs')?.inputs ?? []).some((i) => i.name === CHILD_LIST_KEY)).toBe(false); |
106 | 124 | }); |
107 | 125 | }); |
108 | 126 |
|
109 | 127 | describe('the declaration reaches the consumers that read it (objectui#6740)', () => { |
110 | 128 | it.each(LAYOUT_CONTAINERS)('`%s` reports as a container on the public tier', (type) => { |
111 | | - // The second consumer, and the reason it is pinned rather than left |
112 | | - // implicit: `renderers/layout/react-page.tsx:77` builds the JSX scope of |
| 129 | + // The flag's consumer, and the reason it is pinned rather than left |
| 130 | + // implicit: `renderers/layout/react-page.tsx` builds the JSX scope of |
113 | 131 | // every `kind:'react'` page with `if (!tag || cfg.isContainer) continue;`, |
114 | 132 | // reading THIS predicate off `getPublicConfigs()` — not off `getMeta()`. |
| 133 | + // Since objectui#9910 this is the flag's ONE meaning — layout containment — |
| 134 | + // so this block is about the react-page scope, not about `validateTree`. |
115 | 135 | // While `flex` omitted the flag it was the one layout primitive of the five |
116 | 136 | // still injected there, contradicting |
117 | 137 | // `content/docs/guide/react-pages.md` ("Layout containers are deliberately |
|
0 commit comments