@gridland/testing 0.2.33 → 0.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -207,7 +207,7 @@ ${screen.text()}`
|
|
|
207
207
|
var activeInstances = [];
|
|
208
208
|
var _webModule = await import("../../web/src/index");
|
|
209
209
|
var _rendererModule = await import("../../web/src/browser-renderer");
|
|
210
|
-
var _coreModule = await import("
|
|
210
|
+
var _coreModule = await import("@gridland/utils").catch(() => {
|
|
211
211
|
throw new Error(
|
|
212
212
|
"renderTui requires @opentui/core (RootRenderable). Make sure the opentui monorepo is available and module resolution is configured."
|
|
213
213
|
);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/screen.ts","../src/keys.ts","../src/wait-for.ts","../src/render-tui.ts"],"sourcesContent":["/**\n * Minimal buffer interface — matches BrowserBuffer's public API.\n * This avoids a hard dependency on @gridland/web for the testing utilities.\n */\nexport interface ReadableBuffer {\n width: number\n height: number\n char: Uint32Array\n fg: Float32Array\n bg: Float32Array\n attributes: Uint32Array\n}\n\n/**\n * Screen provides query helpers for reading buffer content in tests.\n * Unlike ink-testing which parses ANSI, we read TypedArrays directly.\n */\nexport class Screen {\n private buffer: ReadableBuffer\n private _frames: string[] = []\n\n constructor(buffer: ReadableBuffer) {\n this.buffer = buffer\n }\n\n /** Capture a frame snapshot (call after each render) */\n captureFrame(): void {\n this._frames.push(this.text())\n }\n\n /** Get the current screen text (plain chars, trailing spaces trimmed) */\n text(): string {\n const lines: string[] = []\n for (let row = 0; row < this.buffer.height; row++) {\n let line = \"\"\n for (let col = 0; col < this.buffer.width; col++) {\n const idx = row * this.buffer.width + col\n const charCode = this.buffer.char[idx]\n line += charCode === 0 ? \" \" : String.fromCodePoint(charCode)\n }\n lines.push(line.trimEnd())\n }\n // Trim trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === \"\") {\n lines.pop()\n }\n return lines.join(\"\\n\")\n }\n\n /** Get raw text including all spaces (no trimming) */\n rawText(): string {\n const lines: string[] = []\n for (let row = 0; row < this.buffer.height; row++) {\n let line = \"\"\n for (let col = 0; col < this.buffer.width; col++) {\n const idx = row * this.buffer.width + col\n const charCode = this.buffer.char[idx]\n line += charCode === 0 ? \" \" : String.fromCodePoint(charCode)\n }\n lines.push(line)\n }\n return lines.join(\"\\n\")\n }\n\n /** Check if the screen contains the given text */\n contains(text: string): boolean {\n return this.text().includes(text)\n }\n\n /** Check if the screen matches a regex */\n matches(pattern: RegExp): boolean {\n return pattern.test(this.text())\n }\n\n /** Get a specific line (0-indexed) */\n line(n: number): string {\n const lines = this.text().split(\"\\n\")\n return lines[n] ?? \"\"\n }\n\n /** Get all non-empty lines */\n lines(): string[] {\n return this.text().split(\"\\n\").filter((l) => l.length > 0)\n }\n\n /** Get all captured frames */\n frames(): string[] {\n return [...this._frames]\n }\n\n /** Get the number of columns */\n get width(): number {\n return this.buffer.width\n }\n\n /** Get the number of rows */\n get height(): number {\n return this.buffer.height\n }\n}\n","import { EventEmitter } from \"events\"\n\n/**\n * Minimal render context interface for key input.\n */\nexport interface KeyInputContext {\n keyInput: EventEmitter\n _internalKeyInput: EventEmitter\n}\n\n/**\n * KeySender simulates keyboard input for testing.\n */\nexport class KeySender {\n private ctx: KeyInputContext\n\n constructor(ctx: KeyInputContext) {\n this.ctx = ctx\n }\n\n private sendKey(name: string, options: {\n ctrl?: boolean\n meta?: boolean\n shift?: boolean\n option?: boolean\n sequence?: string\n } = {}): void {\n const event = {\n name,\n ctrl: options.ctrl ?? false,\n meta: options.meta ?? false,\n shift: options.shift ?? false,\n option: options.option ?? false,\n sequence: options.sequence ?? name,\n number: false,\n raw: name,\n eventType: \"press\" as const,\n source: \"raw\" as const,\n _defaultPrevented: false,\n _propagationStopped: false,\n get defaultPrevented() { return this._defaultPrevented },\n get propagationStopped() { return this._propagationStopped },\n preventDefault() { this._defaultPrevented = true },\n stopPropagation() { this._propagationStopped = true },\n }\n\n this.ctx._internalKeyInput.emit(\"keypress\", event)\n this.ctx.keyInput.emit(\"keypress\", event)\n }\n\n /** Type a string of text character by character */\n type(text: string): void {\n for (const char of text) {\n this.press(char)\n }\n }\n\n /** Press a single character key */\n press(char: string): void {\n this.sendKey(char)\n }\n\n /** Send raw data (for escape sequences etc.) */\n raw(data: string): void {\n this.sendKey(data, { sequence: data })\n }\n\n // Common keys\n enter(): void { this.sendKey(\"return\") }\n escape(): void { this.sendKey(\"escape\") }\n tab(): void { this.sendKey(\"tab\") }\n backspace(): void { this.sendKey(\"backspace\") }\n delete(): void { this.sendKey(\"delete\") }\n space(): void { this.sendKey(\"space\") }\n up(): void { this.sendKey(\"up\") }\n down(): void { this.sendKey(\"down\") }\n left(): void { this.sendKey(\"left\") }\n right(): void { this.sendKey(\"right\") }\n home(): void { this.sendKey(\"home\") }\n end(): void { this.sendKey(\"end\") }\n pageUp(): void { this.sendKey(\"pageup\") }\n pageDown(): void { this.sendKey(\"pagedown\") }\n}\n","import type { Screen } from \"./screen\"\n\nexport interface WaitForOptions {\n /** Timeout in ms (default: 3000) */\n timeout?: number\n /** Polling interval in ms (default: 50) */\n interval?: number\n}\n\n/**\n * Wait for a condition to be met on the screen.\n *\n * @param screen - The screen to poll\n * @param condition - Either a string (wait for screen to contain it) or a function (wait for it to not throw)\n * @param options - Timeout and interval settings\n */\nexport async function waitFor(\n screen: Screen,\n condition: string | (() => void),\n options: WaitForOptions = {},\n): Promise<void> {\n const { timeout = 3000, interval = 50 } = options\n const start = Date.now()\n\n while (true) {\n try {\n if (typeof condition === \"string\") {\n if (screen.contains(condition)) return\n if (Date.now() - start > timeout) {\n throw new Error(\n `waitFor timed out after ${timeout}ms waiting for \"${condition}\"\\n\\nScreen content:\\n${screen.text()}`,\n )\n }\n } else {\n condition()\n return\n }\n } catch (error) {\n if (Date.now() - start > timeout) {\n if (typeof condition === \"string\") {\n throw new Error(\n `waitFor timed out after ${timeout}ms waiting for \"${condition}\"\\n\\nScreen content:\\n${screen.text()}`,\n )\n }\n throw error\n }\n }\n\n await new Promise((resolve) => setTimeout(resolve, interval))\n }\n}\n","import type { ReactNode } from \"react\"\nimport { Screen, type ReadableBuffer } from \"./screen\"\nimport { KeySender, type KeyInputContext } from \"./keys\"\nimport { waitFor, type WaitForOptions } from \"./wait-for\"\n\nexport interface TuiInstance {\n /** Screen queries — read buffer content */\n screen: Screen\n /** Key input simulation */\n keys: KeySender\n /** Wait for text or assertion */\n waitFor: (condition: string | (() => void), options?: WaitForOptions) => Promise<void>\n /** Force a synchronous render cycle */\n flush: () => void\n /** Re-render with new content */\n rerender: (node: ReactNode) => void\n /** Unmount and clean up */\n unmount: () => void\n}\n\ninterface ActiveInstance {\n cleanup: () => void\n}\n\nconst activeInstances: ActiveInstance[] = []\n\nexport interface RenderTuiOptions {\n /** Number of columns (default: 80) */\n cols?: number\n /** Number of rows (default: 24) */\n rows?: number\n}\n\n// Pre-load async modules at module scope so renderTui() can stay synchronous.\n// The gridland-web module chain contains top-level await (reconciler devtools),\n// so require() fails — we use await import() here instead.\nconst _webModule = await import(\"../../web/src/index\")\nconst _rendererModule = await import(\"../../web/src/browser-renderer\")\nconst _coreModule = await import(\"../../web/src/core-shims/index\").catch(() => {\n throw new Error(\n \"renderTui requires @opentui/core (RootRenderable). \" +\n \"Make sure the opentui monorepo is available and module resolution is configured.\",\n )\n})\n// Import reconciler to flush concurrent work synchronously in tests\nconst _reconcilerModule = await import(\"../../../opentui/packages/react/src/reconciler/reconciler\")\n\n/**\n * Render a Gridland component for testing.\n *\n * Note: This function requires @opentui/core and the gridland-web browser runtime\n * to be available. It works in test environments that have the opentui monorepo\n * accessible and proper module resolution configured.\n *\n * For simpler testing (Screen, Keys, waitFor), use those classes directly\n * with a BrowserBuffer — they have no external dependencies.\n */\nexport function renderTui(node: ReactNode, options: RenderTuiOptions = {}): TuiInstance {\n const { cols = 80, rows = 24 } = options\n\n const { BrowserRenderer, createBrowserRoot } = _webModule as any\n const { setRootRenderableClass } = _rendererModule as any\n const { RootRenderable } = _coreModule as any\n setRootRenderableClass(RootRenderable)\n\n const mockCanvas = createMockCanvas(cols, rows)\n const renderer = new BrowserRenderer(mockCanvas, cols, rows)\n const root = createBrowserRoot(renderer)\n const screen = new Screen(renderer.buffer as ReadableBuffer)\n const keys = new KeySender(renderer.renderContext as KeyInputContext)\n\n // Flush the concurrent reconciler so the React tree is committed synchronously.\n // Wrapping root.render() inside flushSync ensures updateContainer runs in sync mode.\n const _rec = (_reconcilerModule as any).reconciler\n const _flushSync = _rec.flushSyncFromReconciler ?? _rec.flushSync\n _flushSync(() => {\n root.render(node)\n })\n doRenderPass(renderer)\n screen.captureFrame()\n\n const instance: ActiveInstance = {\n cleanup() {\n renderer.stop()\n root.unmount()\n },\n }\n activeInstances.push(instance)\n\n return {\n screen,\n keys,\n waitFor: (condition, opts) => waitFor(screen, condition, opts),\n flush() {\n _flushSync(() => {})\n doRenderPass(renderer)\n screen.captureFrame()\n },\n rerender(newNode: ReactNode) {\n _flushSync(() => {\n root.render(newNode)\n })\n doRenderPass(renderer)\n screen.captureFrame()\n },\n unmount() {\n instance.cleanup()\n const idx = activeInstances.indexOf(instance)\n if (idx >= 0) activeInstances.splice(idx, 1)\n },\n }\n}\n\n/**\n * Clean up all active test instances. Call in afterEach().\n */\nexport function cleanup(): void {\n for (const instance of activeInstances) {\n instance.cleanup()\n }\n activeInstances.length = 0\n}\n\nfunction doRenderPass(renderer: any): void {\n const buffer = renderer.buffer\n const renderContext = renderer.renderContext\n\n buffer.clear()\n\n const lifecyclePasses = renderContext.getLifecyclePasses()\n for (const renderable of lifecyclePasses) {\n if (renderable.onLifecyclePass) {\n renderable.onLifecyclePass()\n }\n }\n\n renderer.root.calculateLayout()\n\n const renderList: any[] = []\n renderer.root.updateLayout(16, renderList)\n\n for (const cmd of renderList) {\n switch (cmd.action) {\n case \"pushScissorRect\":\n buffer.pushScissorRect(cmd.x, cmd.y, cmd.width, cmd.height)\n break\n case \"popScissorRect\":\n buffer.popScissorRect()\n break\n case \"pushOpacity\":\n buffer.pushOpacity(cmd.opacity)\n break\n case \"popOpacity\":\n buffer.popOpacity()\n break\n case \"render\":\n cmd.renderable.render(buffer, 16)\n break\n }\n }\n\n buffer.clearScissorRects()\n buffer.clearOpacity()\n}\n\nfunction createMockCanvas(cols: number, rows: number): any {\n const canvas = document.createElement(\"canvas\")\n\n const mockCtx = {\n font: \"\",\n fillStyle: \"\",\n strokeStyle: \"\",\n lineWidth: 1,\n measureText: () => ({ width: 8 }),\n fillRect: () => {},\n fillText: () => {},\n clearRect: () => {},\n beginPath: () => {},\n moveTo: () => {},\n lineTo: () => {},\n stroke: () => {},\n setTransform: () => {},\n scale: () => {},\n }\n\n canvas.getContext = (() => mockCtx) as any\n canvas.width = cols * 8\n canvas.height = rows * 16\n canvas.style.width = `${cols * 8}px`\n canvas.style.height = `${rows * 16}px`\n canvas.style.cursor = \"\"\n canvas.tabIndex = 0\n\n canvas.getBoundingClientRect = () => ({\n x: 0, y: 0,\n width: cols * 8, height: rows * 16,\n top: 0, left: 0, bottom: rows * 16, right: cols * 8,\n toJSON: () => {},\n })\n\n return canvas\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/screen.ts","../src/keys.ts","../src/wait-for.ts","../src/render-tui.ts"],"sourcesContent":["/**\n * Minimal buffer interface — matches BrowserBuffer's public API.\n * This avoids a hard dependency on @gridland/web for the testing utilities.\n */\nexport interface ReadableBuffer {\n width: number\n height: number\n char: Uint32Array\n fg: Float32Array\n bg: Float32Array\n attributes: Uint32Array\n}\n\n/**\n * Screen provides query helpers for reading buffer content in tests.\n * Unlike ink-testing which parses ANSI, we read TypedArrays directly.\n */\nexport class Screen {\n private buffer: ReadableBuffer\n private _frames: string[] = []\n\n constructor(buffer: ReadableBuffer) {\n this.buffer = buffer\n }\n\n /** Capture a frame snapshot (call after each render) */\n captureFrame(): void {\n this._frames.push(this.text())\n }\n\n /** Get the current screen text (plain chars, trailing spaces trimmed) */\n text(): string {\n const lines: string[] = []\n for (let row = 0; row < this.buffer.height; row++) {\n let line = \"\"\n for (let col = 0; col < this.buffer.width; col++) {\n const idx = row * this.buffer.width + col\n const charCode = this.buffer.char[idx]\n line += charCode === 0 ? \" \" : String.fromCodePoint(charCode)\n }\n lines.push(line.trimEnd())\n }\n // Trim trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === \"\") {\n lines.pop()\n }\n return lines.join(\"\\n\")\n }\n\n /** Get raw text including all spaces (no trimming) */\n rawText(): string {\n const lines: string[] = []\n for (let row = 0; row < this.buffer.height; row++) {\n let line = \"\"\n for (let col = 0; col < this.buffer.width; col++) {\n const idx = row * this.buffer.width + col\n const charCode = this.buffer.char[idx]\n line += charCode === 0 ? \" \" : String.fromCodePoint(charCode)\n }\n lines.push(line)\n }\n return lines.join(\"\\n\")\n }\n\n /** Check if the screen contains the given text */\n contains(text: string): boolean {\n return this.text().includes(text)\n }\n\n /** Check if the screen matches a regex */\n matches(pattern: RegExp): boolean {\n return pattern.test(this.text())\n }\n\n /** Get a specific line (0-indexed) */\n line(n: number): string {\n const lines = this.text().split(\"\\n\")\n return lines[n] ?? \"\"\n }\n\n /** Get all non-empty lines */\n lines(): string[] {\n return this.text().split(\"\\n\").filter((l) => l.length > 0)\n }\n\n /** Get all captured frames */\n frames(): string[] {\n return [...this._frames]\n }\n\n /** Get the number of columns */\n get width(): number {\n return this.buffer.width\n }\n\n /** Get the number of rows */\n get height(): number {\n return this.buffer.height\n }\n}\n","import { EventEmitter } from \"events\"\n\n/**\n * Minimal render context interface for key input.\n */\nexport interface KeyInputContext {\n keyInput: EventEmitter\n _internalKeyInput: EventEmitter\n}\n\n/**\n * KeySender simulates keyboard input for testing.\n */\nexport class KeySender {\n private ctx: KeyInputContext\n\n constructor(ctx: KeyInputContext) {\n this.ctx = ctx\n }\n\n private sendKey(name: string, options: {\n ctrl?: boolean\n meta?: boolean\n shift?: boolean\n option?: boolean\n sequence?: string\n } = {}): void {\n const event = {\n name,\n ctrl: options.ctrl ?? false,\n meta: options.meta ?? false,\n shift: options.shift ?? false,\n option: options.option ?? false,\n sequence: options.sequence ?? name,\n number: false,\n raw: name,\n eventType: \"press\" as const,\n source: \"raw\" as const,\n _defaultPrevented: false,\n _propagationStopped: false,\n get defaultPrevented() { return this._defaultPrevented },\n get propagationStopped() { return this._propagationStopped },\n preventDefault() { this._defaultPrevented = true },\n stopPropagation() { this._propagationStopped = true },\n }\n\n this.ctx._internalKeyInput.emit(\"keypress\", event)\n this.ctx.keyInput.emit(\"keypress\", event)\n }\n\n /** Type a string of text character by character */\n type(text: string): void {\n for (const char of text) {\n this.press(char)\n }\n }\n\n /** Press a single character key */\n press(char: string): void {\n this.sendKey(char)\n }\n\n /** Send raw data (for escape sequences etc.) */\n raw(data: string): void {\n this.sendKey(data, { sequence: data })\n }\n\n // Common keys\n enter(): void { this.sendKey(\"return\") }\n escape(): void { this.sendKey(\"escape\") }\n tab(): void { this.sendKey(\"tab\") }\n backspace(): void { this.sendKey(\"backspace\") }\n delete(): void { this.sendKey(\"delete\") }\n space(): void { this.sendKey(\"space\") }\n up(): void { this.sendKey(\"up\") }\n down(): void { this.sendKey(\"down\") }\n left(): void { this.sendKey(\"left\") }\n right(): void { this.sendKey(\"right\") }\n home(): void { this.sendKey(\"home\") }\n end(): void { this.sendKey(\"end\") }\n pageUp(): void { this.sendKey(\"pageup\") }\n pageDown(): void { this.sendKey(\"pagedown\") }\n}\n","import type { Screen } from \"./screen\"\n\nexport interface WaitForOptions {\n /** Timeout in ms (default: 3000) */\n timeout?: number\n /** Polling interval in ms (default: 50) */\n interval?: number\n}\n\n/**\n * Wait for a condition to be met on the screen.\n *\n * @param screen - The screen to poll\n * @param condition - Either a string (wait for screen to contain it) or a function (wait for it to not throw)\n * @param options - Timeout and interval settings\n */\nexport async function waitFor(\n screen: Screen,\n condition: string | (() => void),\n options: WaitForOptions = {},\n): Promise<void> {\n const { timeout = 3000, interval = 50 } = options\n const start = Date.now()\n\n while (true) {\n try {\n if (typeof condition === \"string\") {\n if (screen.contains(condition)) return\n if (Date.now() - start > timeout) {\n throw new Error(\n `waitFor timed out after ${timeout}ms waiting for \"${condition}\"\\n\\nScreen content:\\n${screen.text()}`,\n )\n }\n } else {\n condition()\n return\n }\n } catch (error) {\n if (Date.now() - start > timeout) {\n if (typeof condition === \"string\") {\n throw new Error(\n `waitFor timed out after ${timeout}ms waiting for \"${condition}\"\\n\\nScreen content:\\n${screen.text()}`,\n )\n }\n throw error\n }\n }\n\n await new Promise((resolve) => setTimeout(resolve, interval))\n }\n}\n","import type { ReactNode } from \"react\"\nimport { Screen, type ReadableBuffer } from \"./screen\"\nimport { KeySender, type KeyInputContext } from \"./keys\"\nimport { waitFor, type WaitForOptions } from \"./wait-for\"\n\nexport interface TuiInstance {\n /** Screen queries — read buffer content */\n screen: Screen\n /** Key input simulation */\n keys: KeySender\n /** Wait for text or assertion */\n waitFor: (condition: string | (() => void), options?: WaitForOptions) => Promise<void>\n /** Force a synchronous render cycle */\n flush: () => void\n /** Re-render with new content */\n rerender: (node: ReactNode) => void\n /** Unmount and clean up */\n unmount: () => void\n}\n\ninterface ActiveInstance {\n cleanup: () => void\n}\n\nconst activeInstances: ActiveInstance[] = []\n\nexport interface RenderTuiOptions {\n /** Number of columns (default: 80) */\n cols?: number\n /** Number of rows (default: 24) */\n rows?: number\n}\n\n// Pre-load async modules at module scope so renderTui() can stay synchronous.\n// The gridland-web module chain contains top-level await (reconciler devtools),\n// so require() fails — we use await import() here instead.\nconst _webModule = await import(\"../../web/src/index\")\nconst _rendererModule = await import(\"../../web/src/browser-renderer\")\nconst _coreModule = await import(\"@gridland/utils\").catch(() => {\n throw new Error(\n \"renderTui requires @opentui/core (RootRenderable). \" +\n \"Make sure the opentui monorepo is available and module resolution is configured.\",\n )\n})\n// Import reconciler to flush concurrent work synchronously in tests\nconst _reconcilerModule = await import(\"../../../opentui/packages/react/src/reconciler/reconciler\")\n\n/**\n * Render a Gridland component for testing.\n *\n * Note: This function requires @opentui/core and the gridland-web browser runtime\n * to be available. It works in test environments that have the opentui monorepo\n * accessible and proper module resolution configured.\n *\n * For simpler testing (Screen, Keys, waitFor), use those classes directly\n * with a BrowserBuffer — they have no external dependencies.\n */\nexport function renderTui(node: ReactNode, options: RenderTuiOptions = {}): TuiInstance {\n const { cols = 80, rows = 24 } = options\n\n const { BrowserRenderer, createBrowserRoot } = _webModule as any\n const { setRootRenderableClass } = _rendererModule as any\n const { RootRenderable } = _coreModule as any\n setRootRenderableClass(RootRenderable)\n\n const mockCanvas = createMockCanvas(cols, rows)\n const renderer = new BrowserRenderer(mockCanvas, cols, rows)\n const root = createBrowserRoot(renderer)\n const screen = new Screen(renderer.buffer as ReadableBuffer)\n const keys = new KeySender(renderer.renderContext as KeyInputContext)\n\n // Flush the concurrent reconciler so the React tree is committed synchronously.\n // Wrapping root.render() inside flushSync ensures updateContainer runs in sync mode.\n const _rec = (_reconcilerModule as any).reconciler\n const _flushSync = _rec.flushSyncFromReconciler ?? _rec.flushSync\n _flushSync(() => {\n root.render(node)\n })\n doRenderPass(renderer)\n screen.captureFrame()\n\n const instance: ActiveInstance = {\n cleanup() {\n renderer.stop()\n root.unmount()\n },\n }\n activeInstances.push(instance)\n\n return {\n screen,\n keys,\n waitFor: (condition, opts) => waitFor(screen, condition, opts),\n flush() {\n _flushSync(() => {})\n doRenderPass(renderer)\n screen.captureFrame()\n },\n rerender(newNode: ReactNode) {\n _flushSync(() => {\n root.render(newNode)\n })\n doRenderPass(renderer)\n screen.captureFrame()\n },\n unmount() {\n instance.cleanup()\n const idx = activeInstances.indexOf(instance)\n if (idx >= 0) activeInstances.splice(idx, 1)\n },\n }\n}\n\n/**\n * Clean up all active test instances. Call in afterEach().\n */\nexport function cleanup(): void {\n for (const instance of activeInstances) {\n instance.cleanup()\n }\n activeInstances.length = 0\n}\n\nfunction doRenderPass(renderer: any): void {\n const buffer = renderer.buffer\n const renderContext = renderer.renderContext\n\n buffer.clear()\n\n const lifecyclePasses = renderContext.getLifecyclePasses()\n for (const renderable of lifecyclePasses) {\n if (renderable.onLifecyclePass) {\n renderable.onLifecyclePass()\n }\n }\n\n renderer.root.calculateLayout()\n\n const renderList: any[] = []\n renderer.root.updateLayout(16, renderList)\n\n for (const cmd of renderList) {\n switch (cmd.action) {\n case \"pushScissorRect\":\n buffer.pushScissorRect(cmd.x, cmd.y, cmd.width, cmd.height)\n break\n case \"popScissorRect\":\n buffer.popScissorRect()\n break\n case \"pushOpacity\":\n buffer.pushOpacity(cmd.opacity)\n break\n case \"popOpacity\":\n buffer.popOpacity()\n break\n case \"render\":\n cmd.renderable.render(buffer, 16)\n break\n }\n }\n\n buffer.clearScissorRects()\n buffer.clearOpacity()\n}\n\nfunction createMockCanvas(cols: number, rows: number): any {\n const canvas = document.createElement(\"canvas\")\n\n const mockCtx = {\n font: \"\",\n fillStyle: \"\",\n strokeStyle: \"\",\n lineWidth: 1,\n measureText: () => ({ width: 8 }),\n fillRect: () => {},\n fillText: () => {},\n clearRect: () => {},\n beginPath: () => {},\n moveTo: () => {},\n lineTo: () => {},\n stroke: () => {},\n setTransform: () => {},\n scale: () => {},\n }\n\n canvas.getContext = (() => mockCtx) as any\n canvas.width = cols * 8\n canvas.height = rows * 16\n canvas.style.width = `${cols * 8}px`\n canvas.style.height = `${rows * 16}px`\n canvas.style.cursor = \"\"\n canvas.tabIndex = 0\n\n canvas.getBoundingClientRect = () => ({\n x: 0, y: 0,\n width: cols * 8, height: rows * 16,\n top: 0, left: 0, bottom: rows * 16, right: cols * 8,\n toJSON: () => {},\n })\n\n return canvas\n}\n"],"mappings":";AAiBO,IAAM,SAAN,MAAa;AAAA,EACV;AAAA,EACA,UAAoB,CAAC;AAAA,EAE7B,YAAY,QAAwB;AAClC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,eAAqB;AACnB,SAAK,QAAQ,KAAK,KAAK,KAAK,CAAC;AAAA,EAC/B;AAAA;AAAA,EAGA,OAAe;AACb,UAAM,QAAkB,CAAC;AACzB,aAAS,MAAM,GAAG,MAAM,KAAK,OAAO,QAAQ,OAAO;AACjD,UAAI,OAAO;AACX,eAAS,MAAM,GAAG,MAAM,KAAK,OAAO,OAAO,OAAO;AAChD,cAAM,MAAM,MAAM,KAAK,OAAO,QAAQ;AACtC,cAAM,WAAW,KAAK,OAAO,KAAK,GAAG;AACrC,gBAAQ,aAAa,IAAI,MAAM,OAAO,cAAc,QAAQ;AAAA,MAC9D;AACA,YAAM,KAAK,KAAK,QAAQ,CAAC;AAAA,IAC3B;AAEA,WAAO,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,CAAC,MAAM,IAAI;AACzD,YAAM,IAAI;AAAA,IACZ;AACA,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAAA;AAAA,EAGA,UAAkB;AAChB,UAAM,QAAkB,CAAC;AACzB,aAAS,MAAM,GAAG,MAAM,KAAK,OAAO,QAAQ,OAAO;AACjD,UAAI,OAAO;AACX,eAAS,MAAM,GAAG,MAAM,KAAK,OAAO,OAAO,OAAO;AAChD,cAAM,MAAM,MAAM,KAAK,OAAO,QAAQ;AACtC,cAAM,WAAW,KAAK,OAAO,KAAK,GAAG;AACrC,gBAAQ,aAAa,IAAI,MAAM,OAAO,cAAc,QAAQ;AAAA,MAC9D;AACA,YAAM,KAAK,IAAI;AAAA,IACjB;AACA,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAAA;AAAA,EAGA,SAAS,MAAuB;AAC9B,WAAO,KAAK,KAAK,EAAE,SAAS,IAAI;AAAA,EAClC;AAAA;AAAA,EAGA,QAAQ,SAA0B;AAChC,WAAO,QAAQ,KAAK,KAAK,KAAK,CAAC;AAAA,EACjC;AAAA;AAAA,EAGA,KAAK,GAAmB;AACtB,UAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,IAAI;AACpC,WAAO,MAAM,CAAC,KAAK;AAAA,EACrB;AAAA;AAAA,EAGA,QAAkB;AAChB,WAAO,KAAK,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA,EAC3D;AAAA;AAAA,EAGA,SAAmB;AACjB,WAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EACzB;AAAA;AAAA,EAGA,IAAI,QAAgB;AAClB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA,EAGA,IAAI,SAAiB;AACnB,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;;;ACtFO,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EAER,YAAY,KAAsB;AAChC,SAAK,MAAM;AAAA,EACb;AAAA,EAEQ,QAAQ,MAAc,UAM1B,CAAC,GAAS;AACZ,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA,MAAM,QAAQ,QAAQ;AAAA,MACtB,MAAM,QAAQ,QAAQ;AAAA,MACtB,OAAO,QAAQ,SAAS;AAAA,MACxB,QAAQ,QAAQ,UAAU;AAAA,MAC1B,UAAU,QAAQ,YAAY;AAAA,MAC9B,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,mBAAmB;AAAA,MACnB,qBAAqB;AAAA,MACrB,IAAI,mBAAmB;AAAE,eAAO,KAAK;AAAA,MAAkB;AAAA,MACvD,IAAI,qBAAqB;AAAE,eAAO,KAAK;AAAA,MAAoB;AAAA,MAC3D,iBAAiB;AAAE,aAAK,oBAAoB;AAAA,MAAK;AAAA,MACjD,kBAAkB;AAAE,aAAK,sBAAsB;AAAA,MAAK;AAAA,IACtD;AAEA,SAAK,IAAI,kBAAkB,KAAK,YAAY,KAAK;AACjD,SAAK,IAAI,SAAS,KAAK,YAAY,KAAK;AAAA,EAC1C;AAAA;AAAA,EAGA,KAAK,MAAoB;AACvB,eAAW,QAAQ,MAAM;AACvB,WAAK,MAAM,IAAI;AAAA,IACjB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,MAAoB;AACxB,SAAK,QAAQ,IAAI;AAAA,EACnB;AAAA;AAAA,EAGA,IAAI,MAAoB;AACtB,SAAK,QAAQ,MAAM,EAAE,UAAU,KAAK,CAAC;AAAA,EACvC;AAAA;AAAA,EAGA,QAAc;AAAE,SAAK,QAAQ,QAAQ;AAAA,EAAE;AAAA,EACvC,SAAe;AAAE,SAAK,QAAQ,QAAQ;AAAA,EAAE;AAAA,EACxC,MAAY;AAAE,SAAK,QAAQ,KAAK;AAAA,EAAE;AAAA,EAClC,YAAkB;AAAE,SAAK,QAAQ,WAAW;AAAA,EAAE;AAAA,EAC9C,SAAe;AAAE,SAAK,QAAQ,QAAQ;AAAA,EAAE;AAAA,EACxC,QAAc;AAAE,SAAK,QAAQ,OAAO;AAAA,EAAE;AAAA,EACtC,KAAW;AAAE,SAAK,QAAQ,IAAI;AAAA,EAAE;AAAA,EAChC,OAAa;AAAE,SAAK,QAAQ,MAAM;AAAA,EAAE;AAAA,EACpC,OAAa;AAAE,SAAK,QAAQ,MAAM;AAAA,EAAE;AAAA,EACpC,QAAc;AAAE,SAAK,QAAQ,OAAO;AAAA,EAAE;AAAA,EACtC,OAAa;AAAE,SAAK,QAAQ,MAAM;AAAA,EAAE;AAAA,EACpC,MAAY;AAAE,SAAK,QAAQ,KAAK;AAAA,EAAE;AAAA,EAClC,SAAe;AAAE,SAAK,QAAQ,QAAQ;AAAA,EAAE;AAAA,EACxC,WAAiB;AAAE,SAAK,QAAQ,UAAU;AAAA,EAAE;AAC9C;;;AClEA,eAAsB,QACpB,QACA,WACA,UAA0B,CAAC,GACZ;AACf,QAAM,EAAE,UAAU,KAAM,WAAW,GAAG,IAAI;AAC1C,QAAM,QAAQ,KAAK,IAAI;AAEvB,SAAO,MAAM;AACX,QAAI;AACF,UAAI,OAAO,cAAc,UAAU;AACjC,YAAI,OAAO,SAAS,SAAS,EAAG;AAChC,YAAI,KAAK,IAAI,IAAI,QAAQ,SAAS;AAChC,gBAAM,IAAI;AAAA,YACR,2BAA2B,OAAO,mBAAmB,SAAS;AAAA;AAAA;AAAA,EAAyB,OAAO,KAAK,CAAC;AAAA,UACtG;AAAA,QACF;AAAA,MACF,OAAO;AACL,kBAAU;AACV;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,UAAI,KAAK,IAAI,IAAI,QAAQ,SAAS;AAChC,YAAI,OAAO,cAAc,UAAU;AACjC,gBAAM,IAAI;AAAA,YACR,2BAA2B,OAAO,mBAAmB,SAAS;AAAA;AAAA;AAAA,EAAyB,OAAO,KAAK,CAAC;AAAA,UACtG;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAEA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,QAAQ,CAAC;AAAA,EAC9D;AACF;;;AC1BA,IAAM,kBAAoC,CAAC;AAY3C,IAAM,aAAa,MAAM,OAAO,qBAAqB;AACrD,IAAM,kBAAkB,MAAM,OAAO,gCAAgC;AACrE,IAAM,cAAc,MAAM,OAAO,iBAAiB,EAAE,MAAM,MAAM;AAC9D,QAAM,IAAI;AAAA,IACR;AAAA,EAEF;AACF,CAAC;AAED,IAAM,oBAAoB,MAAM,OAAO,2DAA2D;AAY3F,SAAS,UAAU,MAAiB,UAA4B,CAAC,GAAgB;AACtF,QAAM,EAAE,OAAO,IAAI,OAAO,GAAG,IAAI;AAEjC,QAAM,EAAE,iBAAiB,kBAAkB,IAAI;AAC/C,QAAM,EAAE,uBAAuB,IAAI;AACnC,QAAM,EAAE,eAAe,IAAI;AAC3B,yBAAuB,cAAc;AAErC,QAAM,aAAa,iBAAiB,MAAM,IAAI;AAC9C,QAAM,WAAW,IAAI,gBAAgB,YAAY,MAAM,IAAI;AAC3D,QAAM,OAAO,kBAAkB,QAAQ;AACvC,QAAM,SAAS,IAAI,OAAO,SAAS,MAAwB;AAC3D,QAAM,OAAO,IAAI,UAAU,SAAS,aAAgC;AAIpE,QAAM,OAAQ,kBAA0B;AACxC,QAAM,aAAa,KAAK,2BAA2B,KAAK;AACxD,aAAW,MAAM;AACf,SAAK,OAAO,IAAI;AAAA,EAClB,CAAC;AACD,eAAa,QAAQ;AACrB,SAAO,aAAa;AAEpB,QAAM,WAA2B;AAAA,IAC/B,UAAU;AACR,eAAS,KAAK;AACd,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AACA,kBAAgB,KAAK,QAAQ;AAE7B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,CAAC,WAAW,SAAS,QAAQ,QAAQ,WAAW,IAAI;AAAA,IAC7D,QAAQ;AACN,iBAAW,MAAM;AAAA,MAAC,CAAC;AACnB,mBAAa,QAAQ;AACrB,aAAO,aAAa;AAAA,IACtB;AAAA,IACA,SAAS,SAAoB;AAC3B,iBAAW,MAAM;AACf,aAAK,OAAO,OAAO;AAAA,MACrB,CAAC;AACD,mBAAa,QAAQ;AACrB,aAAO,aAAa;AAAA,IACtB;AAAA,IACA,UAAU;AACR,eAAS,QAAQ;AACjB,YAAM,MAAM,gBAAgB,QAAQ,QAAQ;AAC5C,UAAI,OAAO,EAAG,iBAAgB,OAAO,KAAK,CAAC;AAAA,IAC7C;AAAA,EACF;AACF;AAKO,SAAS,UAAgB;AAC9B,aAAW,YAAY,iBAAiB;AACtC,aAAS,QAAQ;AAAA,EACnB;AACA,kBAAgB,SAAS;AAC3B;AAEA,SAAS,aAAa,UAAqB;AACzC,QAAM,SAAS,SAAS;AACxB,QAAM,gBAAgB,SAAS;AAE/B,SAAO,MAAM;AAEb,QAAM,kBAAkB,cAAc,mBAAmB;AACzD,aAAW,cAAc,iBAAiB;AACxC,QAAI,WAAW,iBAAiB;AAC9B,iBAAW,gBAAgB;AAAA,IAC7B;AAAA,EACF;AAEA,WAAS,KAAK,gBAAgB;AAE9B,QAAM,aAAoB,CAAC;AAC3B,WAAS,KAAK,aAAa,IAAI,UAAU;AAEzC,aAAW,OAAO,YAAY;AAC5B,YAAQ,IAAI,QAAQ;AAAA,MAClB,KAAK;AACH,eAAO,gBAAgB,IAAI,GAAG,IAAI,GAAG,IAAI,OAAO,IAAI,MAAM;AAC1D;AAAA,MACF,KAAK;AACH,eAAO,eAAe;AACtB;AAAA,MACF,KAAK;AACH,eAAO,YAAY,IAAI,OAAO;AAC9B;AAAA,MACF,KAAK;AACH,eAAO,WAAW;AAClB;AAAA,MACF,KAAK;AACH,YAAI,WAAW,OAAO,QAAQ,EAAE;AAChC;AAAA,IACJ;AAAA,EACF;AAEA,SAAO,kBAAkB;AACzB,SAAO,aAAa;AACtB;AAEA,SAAS,iBAAiB,MAAc,MAAmB;AACzD,QAAM,SAAS,SAAS,cAAc,QAAQ;AAE9C,QAAM,UAAU;AAAA,IACd,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,IACb,WAAW;AAAA,IACX,aAAa,OAAO,EAAE,OAAO,EAAE;AAAA,IAC/B,UAAU,MAAM;AAAA,IAAC;AAAA,IACjB,UAAU,MAAM;AAAA,IAAC;AAAA,IACjB,WAAW,MAAM;AAAA,IAAC;AAAA,IAClB,WAAW,MAAM;AAAA,IAAC;AAAA,IAClB,QAAQ,MAAM;AAAA,IAAC;AAAA,IACf,QAAQ,MAAM;AAAA,IAAC;AAAA,IACf,QAAQ,MAAM;AAAA,IAAC;AAAA,IACf,cAAc,MAAM;AAAA,IAAC;AAAA,IACrB,OAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AAEA,SAAO,cAAc,MAAM;AAC3B,SAAO,QAAQ,OAAO;AACtB,SAAO,SAAS,OAAO;AACvB,SAAO,MAAM,QAAQ,GAAG,OAAO,CAAC;AAChC,SAAO,MAAM,SAAS,GAAG,OAAO,EAAE;AAClC,SAAO,MAAM,SAAS;AACtB,SAAO,WAAW;AAElB,SAAO,wBAAwB,OAAO;AAAA,IACpC,GAAG;AAAA,IAAG,GAAG;AAAA,IACT,OAAO,OAAO;AAAA,IAAG,QAAQ,OAAO;AAAA,IAChC,KAAK;AAAA,IAAG,MAAM;AAAA,IAAG,QAAQ,OAAO;AAAA,IAAI,OAAO,OAAO;AAAA,IAClD,QAAQ,MAAM;AAAA,IAAC;AAAA,EACjB;AAEA,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gridland/testing",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"test:ci": "bun test --preload ../web/test/preload.ts --randomize --rerun-each 3"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@gridland/utils": "0.2.35",
|
|
22
23
|
"events": "^3.3.0"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|