import { describe, test, expect } from 'vitest'; import { Gallery } from './Gallery'; const toHtml = (Gallery as any).toHtml; describe('Gallery.toHtml columns + lightbox controls (previously unexposed props)', () => { test('columns drives the grid-template-columns repeat count', () => { const { html } = toHtml({ images: [{ src: '/a.jpg', alt: 'a' }], columns: 5 }, ''); expect(html).toContain('grid-template-columns:repeat(5,1fr)'); }); test('lightbox=true adds the delegated-listener overlay markup (columns unaffected)', () => { const { html } = toHtml({ images: [{ src: '/a.jpg', alt: 'a' }], columns: 4, lightbox: true }, ''); expect(html).toContain('grid-template-columns:repeat(4,1fr)'); expect(html).toContain('role="dialog"'); }); test('a non-numeric columns value falls back safely (Number() coercion, not NaN in the template)', () => { const { html } = toHtml({ images: [{ src: '/a.jpg' }], columns: 'not-a-number' as any }, ''); expect(html).toContain('grid-template-columns:repeat(3,1fr)'); }); }); describe('Gallery.toHtml box-model styles (margin/padding/border/shadow/opacity)', () => { test('margin/padding/border/box-shadow/opacity all flow into the section style attribute', () => { const { html } = toHtml( { images: [{ src: '/a.jpg', alt: 'a' }], style: { marginTop: '20px', paddingLeft: '12px', border: '1px solid #333333', boxShadow: '0 1px 2px rgba(0,0,0,0.08)', opacity: '0.9', }, }, '' ); expect(html).toContain('margin-top:20px'); expect(html).toContain('padding-left:12px'); expect(html).toContain('border:1px solid #333333'); expect(html).toContain('box-shadow:0 1px 2px rgba(0,0,0,0.08)'); expect(html).toContain('opacity:0.9'); }); }); describe('Gallery.craft.props exposes columns/lightbox + the animation/visibility rollout', () => { test('columns and lightbox have their existing defaults', () => { const props = (Gallery as any).craft.props; expect(props.columns).toBe(3); expect(props.lightbox).toBe(false); }); test('animation, animationDelay, hideOnDesktop/Tablet/Mobile are present with blank/false defaults', () => { const props = (Gallery as any).craft.props; expect(props.animation).toBe(''); expect(props.animationDelay).toBe('0'); expect(props.hideOnDesktop).toBe(false); expect(props.hideOnTablet).toBe(false); expect(props.hideOnMobile).toBe(false); }); test('style carries blank/default box-model keys', () => { const style = (Gallery as any).craft.props.style; expect(style).toHaveProperty('marginTop'); expect(style).toHaveProperty('paddingTop'); expect(style.border).toBe('none'); expect(style.boxShadow).toBe('none'); expect(style.opacity).toBe('1'); }); }); describe('Gallery.toHtml lightbox uses a delegated listener, not per-item onclick (A4.3)', () => { test('no per-item inline onclick with interpolated src', () => { const { html } = toHtml({ images: [{ src: '/a.jpg', alt: 'a' }], lightbox: true }, ''); expect(html).not.toMatch(/onclick="[^"]*_open\(/); expect(html).toContain('data-lb-src="/a.jpg"'); }); test('a single-quote in src cannot break the handler (no per-item onclick at all)', () => { const { html } = toHtml({ images: [{ src: "/a'.jpg", alt: 'a' }], lightbox: true }, ''); // no per-item onclick handler exists at all (delegated listener only) expect(html).not.toMatch(/onclick="[^"]*_open\(/); // the quote in src is entity-escaped in the data attribute, not raw expect(html).toContain('data-lb-src="/a'.jpg"'); expect(html).not.toContain(`data-lb-src="/a'.jpg"`); }); test('emits exactly one delegated click listener', () => { const { html } = toHtml({ images: [{ src: '/a.jpg' }, { src: '/b.jpg' }], lightbox: true }, ''); const matches = html.match(/addEventListener\(['"]click['"]/g) || []; expect(matches.length).toBe(1); }); test('lightbox=false: no data-lb-src, no script', () => { const { html } = toHtml({ images: [{ src: '/a.jpg' }], lightbox: false }, ''); expect(html).not.toContain('data-lb-src'); expect(html).not.toContain('