Rail sections (Outline/Blocks/Nodes) collapse via their headers like Experiments already did, so long pages stay navigable. Rail open state and section toggles persist in localStorage across reloads (the QA preview flow reloads constantly). The assistant panel shifts left while the dev panel is open instead of stacking exactly underneath it, and the rail/dev/assistant widths cap at the viewport on small screens. Tests: rail-sections harness (collapse, reload persistence, shifted assistant); harness boots with a fresh profile unless keepStorage is passed; in-memory localStorage shim for the origin-less jsdom. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
62 lines
2.6 KiB
TypeScript
62 lines
2.6 KiB
TypeScript
// Test harness: boots the real editor against the mock CMS inside jsdom.
|
|
import { act } from 'react'
|
|
import { Api } from '../src/api'
|
|
import { AuthManager } from '../src/auth'
|
|
import { boot } from '../src/boot'
|
|
import type { EditorHandle } from '../src/boot'
|
|
import { createMockCms } from '../mock/server'
|
|
import { renderPage } from '../mock/fixtures'
|
|
|
|
export interface Harness {
|
|
cms: ReturnType<typeof createMockCms>
|
|
auth: AuthManager
|
|
editor: EditorHandle
|
|
/** Wait for async work (fetches, patch application, React). */
|
|
settle(): Promise<void>
|
|
shadow: ShadowRoot
|
|
/** Click something inside the editor UI by accessible label. */
|
|
press(label: string): Promise<void>
|
|
cleanup(): void
|
|
}
|
|
|
|
/** `stale`: the DOM is the *published* page, and the store has moved on since (the base-page problem). */
|
|
export async function setup(opts: { token?: string; auth?: boolean; stale?: boolean; noPreviewApi?: boolean; keepStorage?: boolean } = {}): Promise<Harness> {
|
|
// Fresh profile by default: remembered panel state must not leak between tests.
|
|
// Pass keepStorage to simulate a reload with the same profile.
|
|
if (!opts.keepStorage) localStorage.clear()
|
|
const cms = createMockCms({ token: opts.token, auth: opts.auth })
|
|
document.head.innerHTML = ''
|
|
window.open = () => null
|
|
document.body.innerHTML = renderPage(cms.graph).match(/<body>([\s\S]*)<\/body>/)![1]
|
|
if (opts.stale) cms.graph.edit([{ op: 'set', id: 'h1', data: { text: 'Latest from the store' } }])
|
|
if (opts.noPreviewApi) cms.disablePreviewApi()
|
|
const auth = new AuthManager({ fetch: cms.fetch, persistent: null, session: null, sleep: (ms) => new Promise((r) => setTimeout(r, Math.min(ms, 5))) })
|
|
const api = new Api({ fetch: cms.fetch, auth })
|
|
let editor!: EditorHandle
|
|
await act(async () => {
|
|
editor = boot({ api })
|
|
})
|
|
const settle = () => act(async () => void (await new Promise((r) => setTimeout(r, 25))))
|
|
const shadow = document.getElementById('cms-onpage-editor')!.shadowRoot!
|
|
return {
|
|
cms,
|
|
auth,
|
|
editor,
|
|
settle,
|
|
shadow,
|
|
async press(label) {
|
|
const el = shadow.querySelector<HTMLElement>(`[aria-label="${label}"], [title="${label}"]`)
|
|
if (!el) throw new Error(`no control "${label}" in editor UI`)
|
|
await act(async () => el.click())
|
|
await settle()
|
|
},
|
|
cleanup() {
|
|
editor.destroy()
|
|
document.body.innerHTML = ''
|
|
},
|
|
}
|
|
}
|
|
|
|
export const click = (el: Element) => el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, composed: true }))
|
|
export const byId = (id: string) => document.querySelector<HTMLElement>(`[data-cms="${id}"]`)!
|