/** * Feature Grid Regression Tests * Tests to prevent regression of items/features naming and default values */ import { describe, it, expect } from 'vitest'; import { SECTION_SCHEMAS, cloneDefaultProps, normalizeFeatureGridProps, } from '../../admin-spa/src/routes/Appearance/Pages/schema/sectionSchema'; describe('Feature Grid Regression Tests', () => { describe('Items vs Features Naming', () => { it('schema uses items not features', () => { const schema = SECTION_SCHEMAS['feature-grid']; expect(schema.defaultProps).toHaveProperty('items'); expect(schema.defaultProps).not.toHaveProperty('features'); }); it('items default value is empty array', () => { const props = cloneDefaultProps('feature-grid'); expect(Array.isArray(props.items.value)).toBe(true); expect(props.items.value).toHaveLength(0); }); it('normalizeFeatureGridProps uses items not features', () => { const legacyProps = { features: [{ title: 'Legacy Feature' }], }; const normalized = normalizeFeatureGridProps(legacyProps); expect(normalized).toHaveProperty('items'); expect(normalized.items).toHaveLength(1); expect(normalized.items[0].title).toBe('Legacy Feature'); }); it('normalizeFeatureGridProps keeps items when present', () => { const currentProps = { items: [{ title: 'Current Feature' }], }; const normalized = normalizeFeatureGridProps(currentProps); expect(normalized.items).toHaveLength(1); expect(normalized.items[0].title).toBe('Current Feature'); }); }); describe('Default Value Type', () => { it('items is always an array, not empty string', () => { const props = cloneDefaultProps('feature-grid'); // Should be array, not empty string expect(props.items.value).toEqual([]); expect(typeof props.items.value).toBe('object'); }); it('normalizeFeatureGridProps handles empty features string', () => { const legacyProps = { features: '', // Legacy empty string }; const normalized = normalizeFeatureGridProps(legacyProps); expect(Array.isArray(normalized.items)).toBe(true); expect(normalized.items).toHaveLength(0); }); it('normalizeFeatureGridProps handles missing features', () => { const noFeaturesProps = {}; const normalized = normalizeFeatureGridProps(noFeaturesProps); expect(normalized.items).toBeUndefined(); }); }); describe('Feature Grid Component Props Contract', () => { // These tests verify the contract between schema and component it('schema items prop structure matches component expectation', () => { const props = cloneDefaultProps('feature-grid'); // Component expects: items = array of FeatureItem expect(props.items.type).toBe('static'); expect(Array.isArray(props.items.value)).toBe(true); }); it('feature item structure supports title, description, icon', () => { const schema = SECTION_SCHEMAS['feature-grid']; // The schema doesn't restrict the item structure directly // but the component expects these fields expect(schema.defaultProps.items.value).toEqual([]); }); it('heading is static by default', () => { const props = cloneDefaultProps('feature-grid'); expect(props.heading.type).toBe('static'); }); }); describe('Dynamic Source Handling', () => { it('feature-grid items can be dynamic', () => { // Verify the schema structure supports dynamic items // The actual component handles both static arrays and dynamic sources const dynamicItems = { items: { type: 'dynamic', source: 'related_posts' }, }; expect(dynamicItems.items.type).toBe('dynamic'); expect(dynamicItems.items.source).toBe('related_posts'); }); it('normalizeFeatureGridProps preserves dynamic items', () => { const dynamicProps = { items: { type: 'dynamic', source: 'related_posts' }, }; const normalized = normalizeFeatureGridProps(dynamicProps); expect(normalized.items.type).toBe('dynamic'); expect(normalized.items.source).toBe('related_posts'); }); }); }); describe('Style Keys Regression Tests', () => { describe('Section Styles Normalization', () => { it('contentWidth is the canonical key', () => { const styles = { contentWidth: 'full', }; // Should NOT have container_width expect(styles).not.toHaveProperty('container_width'); expect(styles.contentWidth).toBe('full'); }); it('backgroundType is required', () => { const styles = { backgroundType: 'gradient', gradientFrom: '#9333ea', gradientTo: '#3b82f6', }; expect(styles.backgroundType).toBe('gradient'); }); it('heightPreset replaces old height key', () => { const styles = { heightPreset: 'fullscreen', }; // Should NOT have legacy 'height' key expect(styles).not.toHaveProperty('height'); expect(styles.heightPreset).toBe('fullscreen'); }); }); describe('Element Styles Keys', () => { it('cta_text is the canonical button key', () => { const elementStyles = { cta_text: { color: '#fff' }, }; // Should NOT use 'cta' or 'button' aliases expect(elementStyles).not.toHaveProperty('cta'); expect(elementStyles).toHaveProperty('cta_text'); }); it('heading element key is consistent', () => { const elementStyles = { heading: { fontSize: '2rem' }, }; expect(elementStyles.heading).toBeDefined(); }); it('feature_item is the canonical card key', () => { const elementStyles = { feature_item: { backgroundColor: '#fff' }, }; expect(elementStyles).toHaveProperty('feature_item'); }); }); }); describe('Dynamic Repeater Arrays Regression', () => { describe('Related Posts Array Structure', () => { it('related_posts resolves to array of post objects', () => { const resolvedRelatedPosts = [ { id: 1, title: 'Post 1', url: '/post-1', featured_image: '/img1.jpg', excerpt: 'Excerpt 1' }, { id: 2, title: 'Post 2', url: '/post-2', featured_image: '/img2.jpg', excerpt: 'Excerpt 2' }, ]; // Component should render these as post cards expect(Array.isArray(resolvedRelatedPosts)).toBe(true); expect(resolvedRelatedPosts[0]).toHaveProperty('url'); expect(resolvedRelatedPosts[0]).toHaveProperty('title'); }); it('empty related_posts shows placeholder', () => { const emptyRelatedPosts: any[] = []; // Component should show empty state message expect(emptyRelatedPosts).toHaveLength(0); }); }); describe('Dynamic Features Array', () => { it('features can be dynamic source', () => { const dynamicFeatures = { features: { type: 'dynamic', source: 'product_categories' }, }; expect(dynamicFeatures.features.type).toBe('dynamic'); }); }); });