feat(scenario): run logs, lint/format tooling, CONTRIBUTING
- add ScenarioRunLogEntity to persist step script output to DB - stepLogger dual-writes to NestJS logger and DB (fire-and-forget) - add GET /scenarios/:id/run/:runId returning run, stepRuns and logs - add POST /scenarios/:id/run/:runId/wait (polls until terminal state) - 9 new integration tests for the two endpoints (136 total) - add eslint with typescript-eslint and eslint-config-prettier - add npm scripts: format, lint, lint:fix - resolve all lint errors across src and test (no any types) - add CONTRIBUTING.md covering dev workflow
This commit is contained in:
+219
-140
@@ -8,7 +8,7 @@
|
||||
* fake globals injected as named parameters.
|
||||
*/
|
||||
|
||||
import { dumpDom, DomNode } from '../src/code-executor/dom-helpers';
|
||||
import { dumpDom, DomNode } from "../src/code-executor/dom-helpers";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fake DOM builder
|
||||
@@ -39,10 +39,10 @@ interface FakeEl {
|
||||
|
||||
type ElAttrs = Partial<{
|
||||
role: string;
|
||||
'data-testid': string;
|
||||
'data-qa': string;
|
||||
'data-action': string;
|
||||
'data-element-id': string;
|
||||
"data-testid": string;
|
||||
"data-qa": string;
|
||||
"data-action": string;
|
||||
"data-element-id": string;
|
||||
id: string;
|
||||
type: string;
|
||||
name: string;
|
||||
@@ -52,19 +52,31 @@ type ElAttrs = Partial<{
|
||||
style: string;
|
||||
}>;
|
||||
|
||||
function el(tag: string, attrs: ElAttrs = {}, ...children: (FakeEl | string)[]): FakeEl {
|
||||
function el(
|
||||
tag: string,
|
||||
attrs: ElAttrs = {},
|
||||
...children: (FakeEl | string)[]
|
||||
): FakeEl {
|
||||
const ownTextNodes: FakeText[] = children
|
||||
.filter((c): c is string => typeof c === 'string')
|
||||
.map(t => ({ nodeType: 3, textContent: t }));
|
||||
const childEls = children.filter((c): c is FakeEl => typeof c !== 'string');
|
||||
const deepText = children.map(c => (typeof c === 'string' ? c : c.innerText)).join('');
|
||||
.filter((c): c is string => typeof c === "string")
|
||||
.map((t) => ({ nodeType: 3, textContent: t }));
|
||||
const childEls = children.filter((c): c is FakeEl => typeof c !== "string");
|
||||
const deepText = children
|
||||
.map((c) => (typeof c === "string" ? c : c.innerText))
|
||||
.join("");
|
||||
|
||||
const style = attrs.style ?? '';
|
||||
const display = /display\s*:\s*none/.test(style) ? 'none' : '';
|
||||
const visibility = /visibility\s*:\s*hidden/.test(style) ? 'hidden' : '';
|
||||
const style = attrs.style ?? "";
|
||||
const display = /display\s*:\s*none/.test(style) ? "none" : "";
|
||||
const visibility = /visibility\s*:\s*hidden/.test(style) ? "hidden" : "";
|
||||
|
||||
const attrMap: Record<string, string | null> = {};
|
||||
for (const key of ['role', 'data-testid', 'data-qa', 'data-action', 'data-element-id'] as const) {
|
||||
for (const key of [
|
||||
"role",
|
||||
"data-testid",
|
||||
"data-qa",
|
||||
"data-action",
|
||||
"data-element-id",
|
||||
] as const) {
|
||||
if (attrs[key] != null) attrMap[key] = attrs[key] as string;
|
||||
}
|
||||
|
||||
@@ -73,26 +85,28 @@ function el(tag: string, attrs: ElAttrs = {}, ...children: (FakeEl | string)[]):
|
||||
children: childEls,
|
||||
childNodes: ownTextNodes,
|
||||
offsetParent: display || visibility ? null : {},
|
||||
id: attrs.id ?? '',
|
||||
type: attrs.type ?? '',
|
||||
name: attrs.name ?? '',
|
||||
id: attrs.id ?? "",
|
||||
type: attrs.type ?? "",
|
||||
name: attrs.name ?? "",
|
||||
href: attrs.href
|
||||
? attrs.href.startsWith('http')
|
||||
? attrs.href.startsWith("http")
|
||||
? attrs.href
|
||||
: `https://example.com${attrs.href}`
|
||||
: '',
|
||||
: "",
|
||||
checked: !!attrs.checked,
|
||||
disabled: !!attrs.disabled,
|
||||
innerText: deepText,
|
||||
textContent: deepText,
|
||||
getAttribute(name: string) { return attrMap[name] ?? null; },
|
||||
getAttribute(name: string) {
|
||||
return attrMap[name] ?? null;
|
||||
},
|
||||
_display: display,
|
||||
_visibility: visibility,
|
||||
};
|
||||
}
|
||||
|
||||
function body(...children: (FakeEl | string)[]): FakeEl {
|
||||
return el('body', {}, ...children);
|
||||
return el("body", {}, ...children);
|
||||
}
|
||||
|
||||
// ── Fake page ──────────────────────────────────────────────────────────────
|
||||
@@ -109,26 +123,34 @@ function findByTag(root: FakeEl, tag: string): FakeEl | null {
|
||||
function makePage(rootEl: FakeEl) {
|
||||
const fakeDocument = {
|
||||
querySelector(sel: string): FakeEl | null {
|
||||
if (sel.startsWith('#') || sel.startsWith('[') || sel.startsWith('.')) return null;
|
||||
if (sel.startsWith("#") || sel.startsWith("[") || sel.startsWith("."))
|
||||
return null;
|
||||
return findByTag(rootEl, sel);
|
||||
},
|
||||
};
|
||||
const fakeWindow = {
|
||||
getComputedStyle: (e: FakeEl) => ({ display: e._display, visibility: e._visibility }),
|
||||
location: { origin: 'https://example.com' },
|
||||
getComputedStyle: (e: FakeEl) => ({
|
||||
display: e._display,
|
||||
visibility: e._visibility,
|
||||
}),
|
||||
location: { origin: "https://example.com" },
|
||||
};
|
||||
const fakeNode = { TEXT_NODE: 3 };
|
||||
|
||||
const evaluate = jest.fn().mockImplementation((fn: Function, args: unknown) => {
|
||||
// eslint-disable-next-line no-new-func
|
||||
const exec = new Function(
|
||||
'document', 'window', 'Node', '__args__',
|
||||
`return (${fn.toString()})(__args__)`,
|
||||
);
|
||||
return Promise.resolve(exec(fakeDocument, fakeWindow, fakeNode, args));
|
||||
});
|
||||
const evaluate = jest
|
||||
.fn()
|
||||
.mockImplementation((fn: (...args: unknown[]) => unknown, args: unknown) => {
|
||||
const exec = new Function(
|
||||
"document",
|
||||
"window",
|
||||
"Node",
|
||||
"__args__",
|
||||
`return (${fn.toString()})(__args__)`,
|
||||
);
|
||||
return Promise.resolve(exec(fakeDocument, fakeWindow, fakeNode, args));
|
||||
});
|
||||
|
||||
return { evaluate } as unknown as import('playwright').Page;
|
||||
return { evaluate } as unknown as import("playwright").Page;
|
||||
}
|
||||
|
||||
const dump = (rootEl: FakeEl, sel?: string): Promise<DomNode> =>
|
||||
@@ -138,205 +160,262 @@ const dump = (rootEl: FakeEl, sel?: string): Promise<DomNode> =>
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('dumpDom', () => {
|
||||
describe("dumpDom", () => {
|
||||
// ── Error handling ────────────────────────────────────────────────────────
|
||||
|
||||
it('returns an ERROR node when the root selector is not found', async () => {
|
||||
const result = await dump(body(el('p', {}, 'hello')), '#does-not-exist');
|
||||
expect(result.tag).toBe('ERROR');
|
||||
expect(result.text).toContain('#does-not-exist');
|
||||
it("returns an ERROR node when the root selector is not found", async () => {
|
||||
const result = await dump(body(el("p", {}, "hello")), "#does-not-exist");
|
||||
expect(result.tag).toBe("ERROR");
|
||||
expect(result.text).toContain("#does-not-exist");
|
||||
});
|
||||
|
||||
// ── Scope & defaults ──────────────────────────────────────────────────────
|
||||
|
||||
it('defaults to body scope', async () => {
|
||||
const result = await dump(body(el('button', {}, 'Go')));
|
||||
expect(result.tag).toBe('body');
|
||||
it("defaults to body scope", async () => {
|
||||
const result = await dump(body(el("button", {}, "Go")));
|
||||
expect(result.tag).toBe("body");
|
||||
});
|
||||
|
||||
it('scopes to an arbitrary sub-selector', async () => {
|
||||
it("scopes to an arbitrary sub-selector", async () => {
|
||||
const root = body(
|
||||
el('header', {}, el('a', { href: '/nav' }, 'Nav')),
|
||||
el('main', {}, el('button', {}, 'Action')),
|
||||
el("header", {}, el("a", { href: "/nav" }, "Nav")),
|
||||
el("main", {}, el("button", {}, "Action")),
|
||||
);
|
||||
const result = await dump(root, 'main');
|
||||
expect(result.tag).toBe('main');
|
||||
expect(result.children.find(c => c.tag === 'header')).toBeUndefined();
|
||||
const result = await dump(root, "main");
|
||||
expect(result.tag).toBe("main");
|
||||
expect(result.children.find((c) => c.tag === "header")).toBeUndefined();
|
||||
});
|
||||
|
||||
// ── Visibility filtering ──────────────────────────────────────────────────
|
||||
|
||||
it('skips elements with display:none', async () => {
|
||||
it("skips elements with display:none", async () => {
|
||||
const root = body(
|
||||
el('button', { style: 'display:none' }, 'Hidden'),
|
||||
el('button', {}, 'Visible'),
|
||||
el("button", { style: "display:none" }, "Hidden"),
|
||||
el("button", {}, "Visible"),
|
||||
);
|
||||
const result = await dump(root);
|
||||
const btns = result.children.filter(c => c.tag === 'button');
|
||||
const btns = result.children.filter((c) => c.tag === "button");
|
||||
expect(btns).toHaveLength(1);
|
||||
expect(btns[0].text).toBe('Visible');
|
||||
expect(btns[0].text).toBe("Visible");
|
||||
});
|
||||
|
||||
it('skips elements with visibility:hidden', async () => {
|
||||
it("skips elements with visibility:hidden", async () => {
|
||||
const root = body(
|
||||
el('button', { style: 'visibility:hidden' }, 'Hidden'),
|
||||
el('button', {}, 'Visible'),
|
||||
el("button", { style: "visibility:hidden" }, "Hidden"),
|
||||
el("button", {}, "Visible"),
|
||||
);
|
||||
const result = await dump(root);
|
||||
const btns = result.children.filter(c => c.tag === 'button');
|
||||
const btns = result.children.filter((c) => c.tag === "button");
|
||||
expect(btns).toHaveLength(1);
|
||||
expect(btns[0].text).toBe('Visible');
|
||||
expect(btns[0].text).toBe("Visible");
|
||||
});
|
||||
|
||||
// ── Ignored tag types ─────────────────────────────────────────────────────
|
||||
|
||||
it('skips SVG elements', async () => {
|
||||
const svgEl = el('svg', {}, el('path', {}));
|
||||
const result = await dump(body(el('button', {}, svgEl, 'Click')));
|
||||
const btn = result.children.find(c => c.tag === 'button');
|
||||
it("skips SVG elements", async () => {
|
||||
const svgEl = el("svg", {}, el("path", {}));
|
||||
const result = await dump(body(el("button", {}, svgEl, "Click")));
|
||||
const btn = result.children.find((c) => c.tag === "button");
|
||||
expect(btn).toBeDefined();
|
||||
expect(btn!.children.some(c => c.tag === 'svg')).toBe(false);
|
||||
expect(btn!.children.some((c) => c.tag === "svg")).toBe(false);
|
||||
});
|
||||
|
||||
it('skips SCRIPT elements', async () => {
|
||||
const result = await dump(body(el('script', {}, 'alert(1)'), el('button', {}, 'OK')));
|
||||
expect(result.children.some(c => c.tag === 'script')).toBe(false);
|
||||
it("skips SCRIPT elements", async () => {
|
||||
const result = await dump(
|
||||
body(el("script", {}, "alert(1)"), el("button", {}, "OK")),
|
||||
);
|
||||
expect(result.children.some((c) => c.tag === "script")).toBe(false);
|
||||
});
|
||||
|
||||
// ── Semantic attributes ───────────────────────────────────────────────────
|
||||
|
||||
it('captures data-testid', async () => {
|
||||
const result = await dump(body(el('button', { 'data-testid': 'save-btn' }, 'Save')));
|
||||
expect(result.children.find(c => c.tag === 'button')?.testid).toBe('save-btn');
|
||||
it("captures data-testid", async () => {
|
||||
const result = await dump(
|
||||
body(el("button", { "data-testid": "save-btn" }, "Save")),
|
||||
);
|
||||
expect(result.children.find((c) => c.tag === "button")?.testid).toBe(
|
||||
"save-btn",
|
||||
);
|
||||
});
|
||||
|
||||
it('captures data-qa', async () => {
|
||||
const result = await dump(body(el('div', { 'data-qa': 'process-name' }, el('input', { type: 'text' }))));
|
||||
expect(result.children.find(c => c.qa === 'process-name')).toBeDefined();
|
||||
it("captures data-qa", async () => {
|
||||
const result = await dump(
|
||||
body(
|
||||
el("div", { "data-qa": "process-name" }, el("input", { type: "text" })),
|
||||
),
|
||||
);
|
||||
expect(result.children.find((c) => c.qa === "process-name")).toBeDefined();
|
||||
});
|
||||
|
||||
it('captures data-action', async () => {
|
||||
const result = await dump(body(el('div', { 'data-action': 'append.append-task' })));
|
||||
expect(result.children.find(c => c.action === 'append.append-task')).toBeDefined();
|
||||
it("captures data-action", async () => {
|
||||
const result = await dump(
|
||||
body(el("div", { "data-action": "append.append-task" })),
|
||||
);
|
||||
expect(
|
||||
result.children.find((c) => c.action === "append.append-task"),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('captures data-element-id', async () => {
|
||||
const result = await dump(body(el('div', { 'data-element-id': 'Activity_1abc' })));
|
||||
expect(result.children.find(c => c.elementId === 'Activity_1abc')).toBeDefined();
|
||||
it("captures data-element-id", async () => {
|
||||
const result = await dump(
|
||||
body(el("div", { "data-element-id": "Activity_1abc" })),
|
||||
);
|
||||
expect(
|
||||
result.children.find((c) => c.elementId === "Activity_1abc"),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('captures role attribute', async () => {
|
||||
const result = await dump(body(el('div', { role: 'dialog' }, el('button', {}, 'OK'))));
|
||||
const dialog = result.children.find(c => c.role === 'dialog');
|
||||
it("captures role attribute", async () => {
|
||||
const result = await dump(
|
||||
body(el("div", { role: "dialog" }, el("button", {}, "OK"))),
|
||||
);
|
||||
const dialog = result.children.find((c) => c.role === "dialog");
|
||||
expect(dialog).toBeDefined();
|
||||
expect(dialog!.tag).toBe('div');
|
||||
expect(dialog!.tag).toBe("div");
|
||||
});
|
||||
|
||||
// ── Interactive element attributes ────────────────────────────────────────
|
||||
|
||||
it('captures input id, type, and name', async () => {
|
||||
const result = await dump(body(el('input', { id: 'email', type: 'email', name: 'userEmail' })));
|
||||
const input = result.children.find(c => c.tag === 'input');
|
||||
expect(input?.id).toBe('email');
|
||||
expect(input?.type).toBe('email');
|
||||
expect(input?.name).toBe('userEmail');
|
||||
it("captures input id, type, and name", async () => {
|
||||
const result = await dump(
|
||||
body(el("input", { id: "email", type: "email", name: "userEmail" })),
|
||||
);
|
||||
const input = result.children.find((c) => c.tag === "input");
|
||||
expect(input?.id).toBe("email");
|
||||
expect(input?.type).toBe("email");
|
||||
expect(input?.name).toBe("userEmail");
|
||||
});
|
||||
|
||||
it('captures checked:true on a checked checkbox', async () => {
|
||||
const result = await dump(body(el('input', { type: 'checkbox', checked: true })));
|
||||
expect(result.children.find(c => c.tag === 'input')?.checked).toBe(true);
|
||||
it("captures checked:true on a checked checkbox", async () => {
|
||||
const result = await dump(
|
||||
body(el("input", { type: "checkbox", checked: true })),
|
||||
);
|
||||
expect(result.children.find((c) => c.tag === "input")?.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('captures checked:false on an unchecked checkbox', async () => {
|
||||
const result = await dump(body(el('input', { type: 'checkbox' })));
|
||||
expect(result.children.find(c => c.tag === 'input')?.checked).toBe(false);
|
||||
it("captures checked:false on an unchecked checkbox", async () => {
|
||||
const result = await dump(body(el("input", { type: "checkbox" })));
|
||||
expect(result.children.find((c) => c.tag === "input")?.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('captures disabled:true on a disabled button', async () => {
|
||||
const result = await dump(body(el('button', { disabled: true }, 'Nope')));
|
||||
expect(result.children.find(c => c.tag === 'button')?.disabled).toBe(true);
|
||||
it("captures disabled:true on a disabled button", async () => {
|
||||
const result = await dump(body(el("button", { disabled: true }, "Nope")));
|
||||
expect(result.children.find((c) => c.tag === "button")?.disabled).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not set disabled for a non-disabled button', async () => {
|
||||
const result = await dump(body(el('button', {}, 'OK')));
|
||||
expect(result.children.find(c => c.tag === 'button')?.disabled).toBeUndefined();
|
||||
it("does not set disabled for a non-disabled button", async () => {
|
||||
const result = await dump(body(el("button", {}, "OK")));
|
||||
expect(
|
||||
result.children.find((c) => c.tag === "button")?.disabled,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not capture type for button elements', async () => {
|
||||
const result = await dump(body(el('button', { type: 'submit' }, 'Go')));
|
||||
expect(result.children.find(c => c.tag === 'button')?.type).toBeUndefined();
|
||||
it("does not capture type for button elements", async () => {
|
||||
const result = await dump(body(el("button", { type: "submit" }, "Go")));
|
||||
expect(
|
||||
result.children.find((c) => c.tag === "button")?.type,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('relativizes same-origin anchor href', async () => {
|
||||
const result = await dump(body(el('a', { href: '/workflow/123' }, 'Link')));
|
||||
expect(result.children.find(c => c.tag === 'a')?.href).toBe('/workflow/123');
|
||||
it("relativizes same-origin anchor href", async () => {
|
||||
const result = await dump(body(el("a", { href: "/workflow/123" }, "Link")));
|
||||
expect(result.children.find((c) => c.tag === "a")?.href).toBe(
|
||||
"/workflow/123",
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps full href for cross-origin anchors', async () => {
|
||||
const result = await dump(body(el('a', { href: 'https://other.com/page' }, 'Ext')));
|
||||
expect(result.children.find(c => c.tag === 'a')?.href).toContain('other.com');
|
||||
it("keeps full href for cross-origin anchors", async () => {
|
||||
const result = await dump(
|
||||
body(el("a", { href: "https://other.com/page" }, "Ext")),
|
||||
);
|
||||
expect(result.children.find((c) => c.tag === "a")?.href).toContain(
|
||||
"other.com",
|
||||
);
|
||||
});
|
||||
|
||||
// ── Text content ──────────────────────────────────────────────────────────
|
||||
|
||||
it('captures own text content of a button', async () => {
|
||||
const result = await dump(body(el('button', {}, 'Save')));
|
||||
expect(result.children.find(c => c.tag === 'button')?.text).toBe('Save');
|
||||
it("captures own text content of a button", async () => {
|
||||
const result = await dump(body(el("button", {}, "Save")));
|
||||
expect(result.children.find((c) => c.tag === "button")?.text).toBe("Save");
|
||||
});
|
||||
|
||||
it('truncates text to 80 characters', async () => {
|
||||
const long = 'x'.repeat(100);
|
||||
const result = await dump(body(el('button', {}, long)));
|
||||
expect(result.children.find(c => c.tag === 'button')?.text?.length).toBe(80);
|
||||
it("truncates text to 80 characters", async () => {
|
||||
const long = "x".repeat(100);
|
||||
const result = await dump(body(el("button", {}, long)));
|
||||
expect(result.children.find((c) => c.tag === "button")?.text?.length).toBe(
|
||||
80,
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to innerText when element has no direct text nodes', async () => {
|
||||
it("falls back to innerText when element has no direct text nodes", async () => {
|
||||
// button wraps a span — no direct text node on button, innerText = span text
|
||||
const result = await dump(body(el('button', {}, el('span', {}, 'Nested'))));
|
||||
expect(result.children.find(c => c.tag === 'button')?.text).toBe('Nested');
|
||||
const result = await dump(body(el("button", {}, el("span", {}, "Nested"))));
|
||||
expect(result.children.find((c) => c.tag === "button")?.text).toBe(
|
||||
"Nested",
|
||||
);
|
||||
});
|
||||
|
||||
// ── Tree pruning / unwrapping ─────────────────────────────────────────────
|
||||
|
||||
it('unwraps a non-significant div that has exactly one significant child', async () => {
|
||||
const result = await dump(body(el('div', {}, el('button', {}, 'Click'))));
|
||||
const btn = result.children.find(c => c.tag === 'button');
|
||||
it("unwraps a non-significant div that has exactly one significant child", async () => {
|
||||
const result = await dump(body(el("div", {}, el("button", {}, "Click"))));
|
||||
const btn = result.children.find((c) => c.tag === "button");
|
||||
expect(btn).toBeDefined();
|
||||
expect(result.children.some(c => c.tag === 'div' && !c.role && !c.testid && !c.qa)).toBe(false);
|
||||
expect(
|
||||
result.children.some(
|
||||
(c) => c.tag === "div" && !c.role && !c.testid && !c.qa,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps a non-significant div that has more than one significant child', async () => {
|
||||
const result = await dump(body(el('div', {}, el('button', {}, 'A'), el('button', {}, 'B'))));
|
||||
const wrapper = result.children.find(c => c.tag === 'div');
|
||||
it("keeps a non-significant div that has more than one significant child", async () => {
|
||||
const result = await dump(
|
||||
body(el("div", {}, el("button", {}, "A"), el("button", {}, "B"))),
|
||||
);
|
||||
const wrapper = result.children.find((c) => c.tag === "div");
|
||||
expect(wrapper).toBeDefined();
|
||||
expect(wrapper!.children).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('discards non-significant childless elements', async () => {
|
||||
const result = await dump(body(el('div', {}), el('button', {}, 'Keep')));
|
||||
expect(result.children.find(c => c.tag === 'div' && c.children.length === 0)).toBeUndefined();
|
||||
expect(result.children.some(c => c.tag === 'button')).toBe(true);
|
||||
it("discards non-significant childless elements", async () => {
|
||||
const result = await dump(body(el("div", {}), el("button", {}, "Keep")));
|
||||
expect(
|
||||
result.children.find((c) => c.tag === "div" && c.children.length === 0),
|
||||
).toBeUndefined();
|
||||
expect(result.children.some((c) => c.tag === "button")).toBe(true);
|
||||
});
|
||||
|
||||
// ── Structural tags ───────────────────────────────────────────────────────
|
||||
|
||||
it('preserves nested structure inside a form', async () => {
|
||||
it("preserves nested structure inside a form", async () => {
|
||||
const result = await dump(
|
||||
body(el('form', {}, el('input', { id: 'n', type: 'text', name: 'name' }), el('button', {}, 'Send'))),
|
||||
body(
|
||||
el(
|
||||
"form",
|
||||
{},
|
||||
el("input", { id: "n", type: "text", name: "name" }),
|
||||
el("button", {}, "Send"),
|
||||
),
|
||||
),
|
||||
);
|
||||
const form = result.children.find(c => c.tag === 'form');
|
||||
const form = result.children.find((c) => c.tag === "form");
|
||||
expect(form).toBeDefined();
|
||||
expect(form!.children.find(c => c.tag === 'input')).toBeDefined();
|
||||
expect(form!.children.find(c => c.tag === 'button')).toBeDefined();
|
||||
expect(form!.children.find((c) => c.tag === "input")).toBeDefined();
|
||||
expect(form!.children.find((c) => c.tag === "button")).toBeDefined();
|
||||
});
|
||||
|
||||
it('preserves dialog element', async () => {
|
||||
const result = await dump(body(el('dialog', { role: 'dialog' }, el('button', {}, 'Close'))));
|
||||
expect(result.children.find(c => c.tag === 'dialog')).toBeDefined();
|
||||
it("preserves dialog element", async () => {
|
||||
const result = await dump(
|
||||
body(el("dialog", { role: "dialog" }, el("button", {}, "Close"))),
|
||||
);
|
||||
expect(result.children.find((c) => c.tag === "dialog")).toBeDefined();
|
||||
});
|
||||
|
||||
it('returns empty node when root has no visible significant content', async () => {
|
||||
const result = await dumpDom(makePage(el('div', {})), 'div');
|
||||
expect(result.tag).toBe('empty');
|
||||
it("returns empty node when root has no visible significant content", async () => {
|
||||
const result = await dumpDom(makePage(el("div", {})), "div");
|
||||
expect(result.tag).toBe("empty");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user