diff --git a/packages/dom/src/Render/DOMRenderer.ts b/packages/dom/src/Render/DOMRenderer.ts index 88a7ce4..b642f34 100644 --- a/packages/dom/src/Render/DOMRenderer.ts +++ b/packages/dom/src/Render/DOMRenderer.ts @@ -25,8 +25,8 @@ export const DOMRenderer: Renderer = { Effect.sync(() => document.createTextNode(text)), appendChild: (parent: Node, child: Node) => - Effect.sync(() => { - warnIfInvalidNesting(tagNameOf(parent), tagNameOf(child)); + Effect.gen(function* () { + yield* warnIfInvalidNesting(tagNameOf(parent), tagNameOf(child)); parent.appendChild(child); }), diff --git a/packages/dom/src/Render/server/StringRenderer.ts b/packages/dom/src/Render/server/StringRenderer.ts index 3d473d6..adabc9b 100644 --- a/packages/dom/src/Render/server/StringRenderer.ts +++ b/packages/dom/src/Render/server/StringRenderer.ts @@ -24,10 +24,10 @@ export const StringRenderer: Renderer = { createTextNode: (text: string) => Effect.sync(() => vText(text)), appendChild: (parent: VNode, child: VNode) => - Effect.sync(() => { + Effect.gen(function* () { if (parent._tag === "VElement") { if (child._tag === "VElement") { - warnIfInvalidNesting(parent.type, child.type); + yield* warnIfInvalidNesting(parent.type, child.type); } parent.children.push(child); // Track parent reference for slot markers diff --git a/packages/dom/src/Render/validateNesting.test.ts b/packages/dom/src/Render/validateNesting.test.ts index f6626e0..ce7c4a3 100644 --- a/packages/dom/src/Render/validateNesting.test.ts +++ b/packages/dom/src/Render/validateNesting.test.ts @@ -1,7 +1,13 @@ +import { Effect } from "effect"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { checkNesting, warnIfInvalidNesting } from "./validateNesting.js"; +/** Helper: warnIfInvalidNesting returns Effect; run it synchronously. */ +const warn = (parent: string | undefined, child: string | undefined): void => { + Effect.runSync(warnIfInvalidNesting(parent, child)); +}; + describe("checkNesting", () => { it("flags a paragraph nested inside a paragraph", () => { expect(checkNesting("p", "p")).toMatch(/

inside

/); @@ -56,36 +62,36 @@ describe("warnIfInvalidNesting", () => { }); it("emits a console.warn on invalid nesting", () => { - warnIfInvalidNesting("p", "div"); + warn("p", "div"); expect(warnSpy).toHaveBeenCalledTimes(1); expect(warnSpy.mock.calls[0][0]).toMatch(/\[@effex\/dom\]/); expect(warnSpy.mock.calls[0][0]).toMatch(/

inside

/); }); it("is silent on valid nesting", () => { - warnIfInvalidNesting("div", "p"); - warnIfInvalidNesting("ul", "li"); + warn("div", "p"); + warn("ul", "li"); expect(warnSpy).not.toHaveBeenCalled(); }); it("is silent when either arg is missing (text nodes, comments, etc.)", () => { - warnIfInvalidNesting(undefined, "div"); - warnIfInvalidNesting("p", undefined); - warnIfInvalidNesting(undefined, undefined); + warn(undefined, "div"); + warn("p", undefined); + warn(undefined, undefined); expect(warnSpy).not.toHaveBeenCalled(); }); it("only warns once per parent-child pair (across calls)", () => { // The module-level Set caches across tests, so use a distinct pair that // no other test in this file exercises for warning-count assertions. - warnIfInvalidNesting("form", "form"); - warnIfInvalidNesting("form", "form"); - warnIfInvalidNesting("form", "form"); + warn("form", "form"); + warn("form", "form"); + warn("form", "form"); expect(warnSpy).toHaveBeenCalledTimes(1); }); it("normalizes tag case", () => { - warnIfInvalidNesting("BUTTON", "INPUT"); + warn("BUTTON", "INPUT"); expect(warnSpy).toHaveBeenCalledTimes(1); expect(warnSpy.mock.calls[0][0]).toMatch(/ inside