import { describe, test, expect, afterEach } from 'vitest'; import React from 'react'; import { exportBodyHtml, RESPONSIVE_CSS_MINIFIED } from '../../utils/html-export'; import { renderEditorHarness, EditorHarness } from '../../test-utils/editorHarness'; import { DeviceProvider } from '../../state/DeviceContext'; import { RenderNode } from '../../editor/RenderNode'; import type { DeviceMode } from '../../types'; // Vitest stubs CSS imports (`?raw` reads back empty); read the real file (same as DropIndicator.test). const nodeFs = (globalThis as any).process.getBuiltinModule('node:fs'); const EDITOR_CSS: string = nodeFs.readFileSync(`${(globalThis as any).process.cwd()}/src/styles/editor.css`, 'utf8'); /** * Minor finding: un-stacking Columns on mobile (styleMobile.flexDirection 'row' * under a stacked tablet) rendered STACKED in the editor but UN-stacked when * published. Published mobile stacking is the global RESPONSIVE_CSS rule, which * matches the DESKTOP inline style; the editor's device-emulation copy of that * rule matched the device-RESOLVED inline style instead. */ /** Selector list of every editor.css rule that stacks (flex-direction:column) on the mobile frame. */ function editorMobileStackSelectors(): string { const out: string[] = []; const re = /([^{}]+)\{([^{}]*)\}/g; let m: RegExpExecArray | null; while ((m = re.exec(EDITOR_CSS))) { const sel = m[1].replace(/\/\*[\s\S]*?\*\//g, '').trim(); if (sel.includes('[data-device="mobile"]') && /flex-direction:\s*column\s*!important/.test(m[2])) out.push(sel); } return out.join(','); } /** The published global mobile stacking selector (from RESPONSIVE_CSS). */ const PUBLISHED_STACK_SELECTOR = /\{([^{}]+)\{flex-direction:column!important\}/.exec(RESPONSIVE_CSS_MINIFIED)![1]; const node = (type: string, props: Record, nodes: string[] = [], extra: Record = {}) => ({ type: { resolvedName: type }, isCanvas: type === 'Container', props, displayName: type, custom: {}, hidden: false, nodes, linkedNodes: {}, ...extra, }); const colsState = (props: Record) => JSON.stringify({ ROOT: node('Container', { tag: 'div', style: {} }, ['cl']), cl: node('ColumnLayout', { columns: 2, split: '50-50', gap: '16px', anchorId: 'cl', ...props }, [], { parent: 'ROOT', isCanvas: false, linkedNodes: { 'col-0': 'c0', 'col-1': 'c1' }, }), c0: node('Container', { tag: 'div', style: {} }, [], { parent: 'cl' }), c1: node('Container', { tag: 'div', style: {} }, [], { parent: 'cl' }), }); let h: EditorHarness | null = null; afterEach(() => { h?.unmount(); h = null; }); function editorStacksOnMobile(state: string, device: DeviceMode = 'mobile'): boolean { h = renderEditorHarness({ initialState: state, onRender: RenderNode, wrap: (tree) =>
{tree}
, }); const el = h.container.querySelector('#cl') as HTMLElement; return el.matches(editorMobileStackSelectors()); } function publishedBaseStacks(state: string): boolean { const doc = document.createElement('div'); doc.innerHTML = exportBodyHtml(state).html; return (doc.querySelector('#cl') as HTMLElement).matches(PUBLISHED_STACK_SELECTOR); } describe('Columns mobile stacking: editor matches published', () => { test('un-stack on mobile under a stacked tablet -> NOT stacked in the editor (published is not)', () => { const state = colsState({ style: {}, styleTablet: { flexDirection: 'column' }, styleMobile: { flexDirection: 'row' } }); expect(publishedBaseStacks(state)).toBe(false); expect(exportBodyHtml(state).html).toMatch(/@media \(max-width:767px\)\{\[data-wsb-r="b-cl"\]\{flex-direction:row!important/); expect(editorStacksOnMobile(state)).toBe(false); }); test('desktop row, no override -> stacked in both', () => { const state = colsState({ style: { flexDirection: 'row' } }); expect(publishedBaseStacks(state)).toBe(true); expect(editorStacksOnMobile(state)).toBe(true); }); test('desktop row + mobile row override -> stacked in both (global rule wins published)', () => { const state = colsState({ style: { flexDirection: 'row' }, styleMobile: { flexDirection: 'row' } }); expect(publishedBaseStacks(state)).toBe(true); expect(editorStacksOnMobile(state)).toBe(true); }); }); describe('Columns stack on phones BY DEFAULT (no flex-direction in the props)', () => { test('published: wrapper carries the cols_ class the global head rule targets; mobile row override is the opt-out', () => { const html = exportBodyHtml(colsState({ style: {} })).html; expect(html).toMatch(/
{ const stacksDefault = () => { h = renderEditorHarness({ initialState: colsState({ style: {} }), onRender: RenderNode, wrap: (tree) =>
{tree}
, }); const el = h.container.querySelector('#cl') as HTMLElement; return el.matches(editorMobileStackSelectors()); }; expect(stacksDefault()).toBe(true); h?.unmount(); h = null; expect(editorStacksOnMobile(colsState({ styleMobile: { flexDirection: 'row' } }))).toBe(false); }); });