88 lines
4.3 KiB
TypeScript
88 lines
4.3 KiB
TypeScript
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<string, unknown>, nodes: string[] = [], extra: Record<string, unknown> = {}) => ({
|
||
|
|
type: { resolvedName: type }, isCanvas: type === 'Container', props, displayName: type,
|
||
|
|
custom: {}, hidden: false, nodes, linkedNodes: {}, ...extra,
|
||
|
|
});
|
||
|
|
const colsState = (props: Record<string, unknown>) =>
|
||
|
|
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) => <DeviceProvider initialDevice={device}><div className="canvas-device-frame" data-device={device}>{tree}</div></DeviceProvider>,
|
||
|
|
});
|
||
|
|
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);
|
||
|
|
});
|
||
|
|
});
|