feat(site-builder): derive Layers rows from array-prop composites
FeaturesGrid, Tabs, Accordion, PricingTable, Testimonials, Gallery, ContentSlider, NumberCounter, Menu, SocialLinks, Navbar and ContactForm store their content in array props rather than Craft child nodes, so the Layers tree showed nothing underneath them. This adds the pure deriveVirtualRows() function and VIRTUAL_CHILD_PROPS registry the Layers panel will consume in a later task. Verified the registry against each component's actual item interface: corrected Tabs (label field is `label`, not `title` -- TabItem has no `title`) and Content Slider (label field is `heading`, not `title` -- Slide has no `title`). Both would have silently fallen back to "Tab N" / "Slide N" for every existing site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { deriveVirtualRows, VIRTUAL_CHILD_PROPS } from './layers-virtual-rows';
|
||||
|
||||
describe('deriveVirtualRows', () => {
|
||||
test('returns one row per item, labelled by the registered field', () => {
|
||||
const rows = deriveVirtualRows('Features Grid', {
|
||||
features: [{ title: 'Fast' }, { title: 'Secure' }],
|
||||
});
|
||||
expect(rows).toEqual([
|
||||
{ index: 0, label: 'Fast' },
|
||||
{ index: 1, label: 'Secure' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('falls back to "<Fallback> N" when the label field is missing or blank', () => {
|
||||
const rows = deriveVirtualRows('Features Grid', {
|
||||
features: [{ title: '' }, { description: 'no title key' }],
|
||||
});
|
||||
expect(rows).toEqual([
|
||||
{ index: 0, label: 'Feature 1' },
|
||||
{ index: 1, label: 'Feature 2' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('trims and truncates a long label to 40 characters with an ellipsis', () => {
|
||||
const long = 'x'.repeat(60);
|
||||
const rows = deriveVirtualRows('Features Grid', { features: [{ title: ` ${long} ` }] });
|
||||
expect(rows[0].label).toHaveLength(41);
|
||||
expect(rows[0].label.endsWith('…')).toBe(true);
|
||||
});
|
||||
|
||||
test('an unregistered component yields no rows', () => {
|
||||
expect(deriveVirtualRows('Heading', { text: 'hi' })).toEqual([]);
|
||||
});
|
||||
|
||||
test('a missing or non-array prop yields no rows instead of throwing', () => {
|
||||
expect(deriveVirtualRows('Tabs', {})).toEqual([]);
|
||||
expect(deriveVirtualRows('Tabs', { tabs: 'not an array' })).toEqual([]);
|
||||
expect(deriveVirtualRows('Tabs', { tabs: null })).toEqual([]);
|
||||
});
|
||||
|
||||
test('a non-object item still gets a fallback label', () => {
|
||||
expect(deriveVirtualRows('Menu', { links: ['raw string'] })).toEqual([
|
||||
{ index: 0, label: 'Link 1' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('every registry entry has a non-empty prop, label and fallback', () => {
|
||||
for (const [name, spec] of Object.entries(VIRTUAL_CHILD_PROPS)) {
|
||||
expect(spec.prop, `${name}.prop`).toBeTruthy();
|
||||
expect(spec.label, `${name}.label`).toBeTruthy();
|
||||
expect(spec.fallback, `${name}.fallback`).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user