/** * React vs SSR Parity Tests * Tests that compare React-rendered content against SSR HTML output * to ensure consistent rendering between client and server * * Note: These are snapshot-based tests. Run with --update to regenerate snapshots. */ import { describe, it, expect, vi } from 'vitest'; import { renderToStaticMarkup } from 'react-dom/server'; import { HeroSection, ContentSection, ImageTextSection, FeatureGridSection, CTABannerSection, ContactFormSection, BentoCategoryGrid, ProductCarousel, ShoppableImage, MarqueeBanner, } from '../../customer-spa/src/pages/DynamicPage/sections'; // Mock Lucide icons vi.mock('lucide-react', () => ({ Star: () => null, Heart: () => null, Shield: () => null, Zap: () => null, Award: () => null, Clock: () => null, Truck: () => null, User: () => null, Settings: () => null, })); // Helper to extract text content from HTML for comparison function extractTextContent(html: string): string { return html .replace(/<[^>]*>/g, ' ') .replace(/\s+/g, ' ') .trim(); } // Helper to check if class exists in HTML function hasClass(html: string, className: string): boolean { return html.includes(`class="${className}"`) || html.includes(`class="${className} `) || html.includes(` ${className} `); } // Helper to check if style attribute exists function hasStyle(html: string, stylePart: string): boolean { return html.includes(`style="`) && html.includes(stylePart); } // Test data fixtures const FIXTURES = { hero: { id: 'hero-test', title: 'Welcome to Our Store', subtitle: 'Discover amazing products', image: 'https://example.com/hero.jpg', cta_text: 'Shop Now', cta_url: '/shop', styles: {}, elementStyles: {}, }, content: { id: 'content-test', content: '
This is rich content with formatting.
', styles: {}, elementStyles: {}, }, imageText: { id: 'image-text-test', title: 'About Our Company', text: 'We are passionate about quality.', image: 'https://example.com/about.jpg', cta_text: 'Learn More', cta_url: '/about', styles: {}, elementStyles: {}, }, featureGrid: { id: 'features-test', heading: 'Our Features', items: [ { title: 'Feature 1', description: 'Description 1', icon: 'Star' }, { title: 'Feature 2', description: 'Description 2', icon: 'Heart' }, { title: 'Feature 3', description: 'Description 3', icon: 'Shield' }, ], layout: 'grid-3', colorScheme: 'default', styles: {}, elementStyles: {}, }, ctaBanner: { id: 'cta-test', title: 'Ready to Get Started?', text: 'Join thousands of happy customers.', button_text: 'Sign Up Now', button_url: '/signup', layout: 'default', colorScheme: 'primary', styles: {}, elementStyles: {}, }, contactForm: { id: 'contact-test', title: 'Contact Us', webhook_url: 'https://example.com/webhook', redirect_url: '/thank-you', styles: {}, elementStyles: {}, }, bentoGrid: { id: 'bento-test', title: 'Shop by Category', items: [ { label: 'Electronics', url: '/category/electronics', image: 'https://example.com/elec.jpg', size: 'large' }, { label: 'Clothing', url: '/category/clothing', size: 'medium' }, ], colorScheme: 'default', styles: {}, elementStyles: {}, }, productCarousel: { id: 'carousel-test', title: 'Trending Products', subtitle: 'Popular right now', cta_text: 'View All', cta_url: '/products', products: [ { name: 'Product 1', url: '/product/1', image: 'https://example.com/p1.jpg', price: '$29.99' }, { name: 'Product 2', url: '/product/2', image: 'https://example.com/p2.jpg', price: '$39.99' }, ], colorScheme: 'default', styles: {}, elementStyles: {}, }, shoppableImage: { id: 'shoppable-test', title: 'Shop the Look', subtitle: 'Get inspired', image: 'https://example.com/look.jpg', hotspots: [ { product_name: 'Item 1', product_slug: 'item-1', product_price: '$49.99', x: '25', y: '30' }, { product_name: 'Item 2', product_slug: 'item-2', product_price: '$59.99', x: '65', y: '50' }, ], colorScheme: 'default', styles: {}, elementStyles: {}, }, marquee: { id: 'marquee-test', text: 'Free Shipping*Easy Returns*24/7 Support', separator: '*', styles: {}, elementStyles: {}, }, }; describe('React vs SSR Parity Tests', () => { describe('Hero Section', () => { it('renders title content', () => { const html = renderToStaticMarkup( HeroSection({ ...FIXTURES.hero, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Welcome to Our Store'); }); it('renders subtitle content', () => { const html = renderToStaticMarkup( HeroSection({ ...FIXTURES.hero, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Discover amazing products'); }); it('renders CTA button', () => { const html = renderToStaticMarkup( HeroSection({ ...FIXTURES.hero, colorScheme: 'default', }) ); expect(html).toContain('href="/shop"'); expect(extractTextContent(html)).toContain('Shop Now'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( HeroSection({ ...FIXTURES.hero, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-hero')).toBe(true); expect(hasClass(html, 'wn-section')).toBe(true); }); }); describe('Content Section', () => { it('renders content with HTML', () => { const html = renderToStaticMarkup( ContentSection({ ...FIXTURES.content, colorScheme: 'default', }) ); expect(html).toContain('This is rich content'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( ContentSection({ ...FIXTURES.content, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-content')).toBe(true); expect(hasClass(html, 'wn-section')).toBe(true); }); }); describe('Image + Text Section', () => { it('renders title', () => { const html = renderToStaticMarkup( ImageTextSection({ ...FIXTURES.imageText, layout: 'image-left', colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('About Our Company'); }); it('renders text content', () => { const html = renderToStaticMarkup( ImageTextSection({ ...FIXTURES.imageText, layout: 'image-left', colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('We are passionate'); }); it('renders image with correct src', () => { const html = renderToStaticMarkup( ImageTextSection({ ...FIXTURES.imageText, layout: 'image-left', colorScheme: 'default', }) ); expect(html).toContain('https://example.com/about.jpg'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( ImageTextSection({ ...FIXTURES.imageText, layout: 'image-left', colorScheme: 'default', }) ); expect(hasClass(html, 'wn-image-text')).toBe(true); }); }); describe('Feature Grid Section', () => { it('renders heading', () => { const html = renderToStaticMarkup( FeatureGridSection({ ...FIXTURES.featureGrid, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Our Features'); }); it('renders all feature items', () => { const html = renderToStaticMarkup( FeatureGridSection({ ...FIXTURES.featureGrid, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Feature 1'); expect(extractTextContent(html)).toContain('Feature 2'); expect(extractTextContent(html)).toContain('Feature 3'); }); it('renders feature descriptions', () => { const html = renderToStaticMarkup( FeatureGridSection({ ...FIXTURES.featureGrid, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Description 1'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( FeatureGridSection({ ...FIXTURES.featureGrid, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-feature-grid')).toBe(true); }); it('has grid layout class', () => { const html = renderToStaticMarkup( FeatureGridSection({ ...FIXTURES.featureGrid, layout: 'grid-3', colorScheme: 'default', }) ); expect(hasClass(html, 'wn-feature-grid--grid-3')).toBe(true); }); }); describe('CTA Banner Section', () => { it('renders title', () => { const html = renderToStaticMarkup( CTABannerSection({ ...FIXTURES.ctaBanner, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Ready to Get Started?'); }); it('renders description', () => { const html = renderToStaticMarkup( CTABannerSection({ ...FIXTURES.ctaBanner, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Join thousands'); }); it('renders button with href', () => { const html = renderToStaticMarkup( CTABannerSection({ ...FIXTURES.ctaBanner, colorScheme: 'default', }) ); expect(html).toContain('href="/signup"'); expect(extractTextContent(html)).toContain('Sign Up Now'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( CTABannerSection({ ...FIXTURES.ctaBanner, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-cta-banner')).toBe(true); }); }); describe('Contact Form Section', () => { it('renders title', () => { const html = renderToStaticMarkup( ContactFormSection({ ...FIXTURES.contactForm, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Contact Us'); }); it('renders form inputs', () => { const html = renderToStaticMarkup( ContactFormSection({ ...FIXTURES.contactForm, colorScheme: 'default', }) ); expect(html).toContain('name="name"'); expect(html).toContain('name="email"'); expect(html).toContain('name="message"'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( ContactFormSection({ ...FIXTURES.contactForm, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-contact-form')).toBe(true); }); }); describe('Bento Category Grid Section', () => { it('renders title', () => { const html = renderToStaticMarkup( BentoCategoryGrid({ ...FIXTURES.bentoGrid, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Shop by Category'); }); it('renders category labels', () => { const html = renderToStaticMarkup( BentoCategoryGrid({ ...FIXTURES.bentoGrid, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Electronics'); expect(extractTextContent(html)).toContain('Clothing'); }); it('renders links', () => { const html = renderToStaticMarkup( BentoCategoryGrid({ ...FIXTURES.bentoGrid, colorScheme: 'default', }) ); expect(html).toContain('href="/category/electronics"'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( BentoCategoryGrid({ ...FIXTURES.bentoGrid, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-bento-grid')).toBe(true); }); }); describe('Product Carousel Section', () => { it('renders title', () => { const html = renderToStaticMarkup( ProductCarousel({ ...FIXTURES.productCarousel, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Trending Products'); }); it('renders products', () => { const html = renderToStaticMarkup( ProductCarousel({ ...FIXTURES.productCarousel, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Product 1'); expect(extractTextContent(html)).toContain('Product 2'); }); it('renders product prices', () => { const html = renderToStaticMarkup( ProductCarousel({ ...FIXTURES.productCarousel, colorScheme: 'default', }) ); expect(html).toContain('$29.99'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( ProductCarousel({ ...FIXTURES.productCarousel, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-product-carousel')).toBe(true); }); }); describe('Shoppable Image Section', () => { it('renders title', () => { const html = renderToStaticMarkup( ShoppableImage({ ...FIXTURES.shoppableImage, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Shop the Look'); }); it('renders image', () => { const html = renderToStaticMarkup( ShoppableImage({ ...FIXTURES.shoppableImage, colorScheme: 'default', }) ); expect(html).toContain('https://example.com/look.jpg'); }); it('renders hotspots', () => { const html = renderToStaticMarkup( ShoppableImage({ ...FIXTURES.shoppableImage, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Item 1'); expect(extractTextContent(html)).toContain('Item 2'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( ShoppableImage({ ...FIXTURES.shoppableImage, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-shoppable-image')).toBe(true); }); }); describe('Marquee Banner Section', () => { it('renders text items', () => { const html = renderToStaticMarkup( MarqueeBanner({ ...FIXTURES.marquee, colorScheme: 'default', }) ); expect(extractTextContent(html)).toContain('Free Shipping'); expect(extractTextContent(html)).toContain('Easy Returns'); expect(extractTextContent(html)).toContain('24/7 Support'); }); it('has section class for SSR matching', () => { const html = renderToStaticMarkup( MarqueeBanner({ ...FIXTURES.marquee, colorScheme: 'default', }) ); expect(hasClass(html, 'wn-marquee')).toBe(true); }); }); }); describe('SSR Class Matching Verification', () => { /** * These tests verify that React components use the same CSS classes * as the PHP SSR renderers. This ensures visual parity. */ const SECTION_CLASSES = { hero: 'wn-hero', content: 'wn-content', 'image-text': 'wn-image-text', 'feature-grid': 'wn-feature-grid', 'cta-banner': 'wn-cta-banner', 'contact-form': 'wn-contact-form', 'bento-category-grid': 'wn-bento-grid', 'product-carousel': 'wn-product-carousel', 'shoppable-image': 'wn-shoppable-image', 'marquee-banner': 'wn-marquee', }; it('hero section uses wn-hero class', () => { const html = renderToStaticMarkup( HeroSection({ ...FIXTURES.hero, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES.hero)).toBe(true); }); it('content section uses wn-content class', () => { const html = renderToStaticMarkup( ContentSection({ ...FIXTURES.content, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES.content)).toBe(true); }); it('image-text section uses wn-image-text class', () => { const html = renderToStaticMarkup( ImageTextSection({ ...FIXTURES.imageText, layout: 'image-left', colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES['image-text'])).toBe(true); }); it('feature-grid section uses wn-feature-grid class', () => { const html = renderToStaticMarkup( FeatureGridSection({ ...FIXTURES.featureGrid, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES['feature-grid'])).toBe(true); }); it('cta-banner section uses wn-cta-banner class', () => { const html = renderToStaticMarkup( CTABannerSection({ ...FIXTURES.ctaBanner, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES['cta-banner'])).toBe(true); }); it('contact-form section uses wn-contact-form class', () => { const html = renderToStaticMarkup( ContactFormSection({ ...FIXTURES.contactForm, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES['contact-form'])).toBe(true); }); it('bento-category-grid section uses wn-bento-grid class', () => { const html = renderToStaticMarkup( BentoCategoryGrid({ ...FIXTURES.bentoGrid, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES['bento-category-grid'])).toBe(true); }); it('product-carousel section uses wn-product-carousel class', () => { const html = renderToStaticMarkup( ProductCarousel({ ...FIXTURES.productCarousel, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES['product-carousel'])).toBe(true); }); it('shoppable-image section uses wn-shoppable-image class', () => { const html = renderToStaticMarkup( ShoppableImage({ ...FIXTURES.shoppableImage, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES['shoppable-image'])).toBe(true); }); it('marquee-banner section uses wn-marquee class', () => { const html = renderToStaticMarkup( MarqueeBanner({ ...FIXTURES.marquee, colorScheme: 'default', }) ); expect(hasClass(html, SECTION_CLASSES['marquee-banner'])).toBe(true); }); }); describe('Color Scheme Parity', () => { it('hero supports colorScheme prop', () => { const html = renderToStaticMarkup( HeroSection({ ...FIXTURES.hero, colorScheme: 'primary', }) ); expect(hasClass(html, 'wn-scheme--primary')).toBe(true); }); it('feature-grid supports colorScheme prop', () => { const html = renderToStaticMarkup( FeatureGridSection({ ...FIXTURES.featureGrid, colorScheme: 'muted', }) ); expect(hasClass(html, 'wn-scheme--muted')).toBe(true); }); it('cta-banner supports colorScheme prop', () => { const html = renderToStaticMarkup( CTABannerSection({ ...FIXTURES.ctaBanner, colorScheme: 'secondary', }) ); expect(hasClass(html, 'wn-scheme--secondary')).toBe(true); }); });