From 0c5f2df37446b1a43cda271e1f10b5119810f99f Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 15 Sep 2026 00:40:39 +0500 Subject: [PATCH 1/2] plugin/format: prevent re-processing of interpolated text nodes --- src/plugins/format/index.js | 2 +- tests/playwright/x-format.spec.js | 82 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/plugins/format/index.js b/src/plugins/format/index.js index 8d4be8a..4460e56 100644 --- a/src/plugins/format/index.js +++ b/src/plugins/format/index.js @@ -98,7 +98,7 @@ function plugin({ directive, evaluateLater, mutateDom }) { } function process_nodes(node) { - for (let child of node.childNodes) { + for (let child of [...node.childNodes]) { process(child); } } diff --git a/tests/playwright/x-format.spec.js b/tests/playwright/x-format.spec.js index 3f7a2cb..00be882 100644 --- a/tests/playwright/x-format.spec.js +++ b/tests/playwright/x-format.spec.js @@ -108,3 +108,85 @@ test("x-format: nested x-format.once is an independent boundary", async ({ page await expect(page.locator("#once")).toHaveText("initial"); await expect(page.locator("#once")).toHaveAttribute("title", "initial"); }); + +for (const modifier of ["", ".once"]) { + test(`x-format${modifier}: interpolated values remain literal`, async ({ page }) => { + await set_html(page, ` +
+

{{ value }}

+

{{ markup }}

+

{{ expression }}

+

+

+ +
`); + + await expect(page.locator("#formatted")).toHaveText("{{ 1 + 1 }}"); + await expect(page.locator("#text")).toHaveText("{{ 1 + 1 }}"); + await expect(page.locator("#markup")).toHaveText("Text"); + await expect(page.locator("#markup *")).toHaveCount(0); + await expect(page.locator("#expression")).toHaveText("{{ executed = true }}"); + await expect(page.locator("#executed")).toHaveText("false"); + + await page.locator("button").click(); + + await expect(page.locator("#text")).toHaveText("{{ 2 + 2 }}"); + await expect(page.locator("#formatted")).toHaveText(modifier ? "{{ 1 + 1 }}" : "{{ 2 + 2 }}"); + await expect(page.locator("#markup")).toHaveText(modifier ? "Text" : "Updated"); + await expect(page.locator("#markup *")).toHaveCount(0); + await expect(page.locator("#expression")).toHaveText(modifier ? "{{ executed = true }}" : "next {{ executed = true }}"); + await expect(page.locator("#executed")).toHaveText("false"); + }); +} + +test("x-format: preserves interpolations in following original nodes", async ({ page }) => { + await set_html(page, ` +
+

[{{ value }},{{ name }}]{{ name }}{{ value }}{{ name }}

+ +
`); + + await expect(page.locator("p")).toHaveText("[{{ 1 + 1 }},Foo]Foo{{ 1 + 1 }}Foo"); + await expect(page.locator("p")).toHaveAttribute("title", "{{ 1 + 1 }}/Foo"); + + await page.locator("button").click(); + + await expect(page.locator("p")).toHaveText("[{{ 2 + 2 }},Bar]Bar{{ 2 + 2 }}Bar"); + await expect(page.locator("p")).toHaveAttribute("title", "{{ 2 + 2 }}/Bar"); +}); + +test("x-format: nested boundaries preserve literal values", async ({ page }) => { + await set_html(page, ` +
+
+

{{ value }}

+
+

{{ local }}

+

{{ local }}

+

{{ local }}

+ +
+
+
`); + + for (const id of ["outer", "automatic", "explicit", "once"]) { + await expect(page.locator(`#${id}`)).toHaveText("{{ 1 + 1 }}"); + await expect(page.locator(`#${id}`)).toHaveAttribute("title", "{{ 1 + 1 }}"); + } + + await page.locator("button").click(); + + for (const id of ["automatic", "explicit"]) { + await expect(page.locator(`#${id}`)).toHaveText("{{ 2 + 2 }}"); + await expect(page.locator(`#${id}`)).toHaveAttribute("title", "{{ 2 + 2 }}"); + } + for (const id of ["outer", "once"]) { + await expect(page.locator(`#${id}`)).toHaveText("{{ 1 + 1 }}"); + await expect(page.locator(`#${id}`)).toHaveAttribute("title", "{{ 1 + 1 }}"); + } +}); From bd6b4bdf76a0bae415f3d7811230a4f923f0f6c4 Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 15 Sep 2026 02:32:22 +0500 Subject: [PATCH 2/2] plugin/format: simplify tests --- tests/playwright/x-format.spec.js | 72 +++++++++++-------------------- 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/tests/playwright/x-format.spec.js b/tests/playwright/x-format.spec.js index 00be882..5cb2ac0 100644 --- a/tests/playwright/x-format.spec.js +++ b/tests/playwright/x-format.spec.js @@ -10,6 +10,22 @@ test("x-format", async ({ page }) => { await expect(page.locator("div")).toContainText("[Foo,Bar]"); }); +test("x-format: renders HTML strings as text", async ({ page }) => { + await set_html(page, ` +
+

{{ value }}

+ +
`); + + await expect(page.locator("p")).toHaveText("Text"); + await expect(page.locator("p *")).toHaveCount(0); + + await page.locator("button").click(); + + await expect(page.locator("p")).toHaveText("Updated"); + await expect(page.locator("p *")).toHaveCount(0); +}); + test("x-format: recursively", async ({ page }) => { await set_html(page, `
@@ -110,36 +126,20 @@ test("x-format: nested x-format.once is an independent boundary", async ({ page }); for (const modifier of ["", ".once"]) { - test(`x-format${modifier}: interpolated values remain literal`, async ({ page }) => { + test(`x-format${modifier}: does not re-evaluate interpolated values`, async ({ page }) => { await set_html(page, ` -
+

{{ value }}

-

{{ markup }}

-

{{ expression }}

-

- +
`); - await expect(page.locator("#formatted")).toHaveText("{{ 1 + 1 }}"); - await expect(page.locator("#text")).toHaveText("{{ 1 + 1 }}"); - await expect(page.locator("#markup")).toHaveText("Text"); - await expect(page.locator("#markup *")).toHaveCount(0); - await expect(page.locator("#expression")).toHaveText("{{ executed = true }}"); + await expect(page.locator("#formatted")).toHaveText("{{ executed = true }}"); await expect(page.locator("#executed")).toHaveText("false"); await page.locator("button").click(); - await expect(page.locator("#text")).toHaveText("{{ 2 + 2 }}"); - await expect(page.locator("#formatted")).toHaveText(modifier ? "{{ 1 + 1 }}" : "{{ 2 + 2 }}"); - await expect(page.locator("#markup")).toHaveText(modifier ? "Text" : "Updated"); - await expect(page.locator("#markup *")).toHaveCount(0); - await expect(page.locator("#expression")).toHaveText(modifier ? "{{ executed = true }}" : "next {{ executed = true }}"); + await expect(page.locator("#formatted")).toHaveText(modifier ? "{{ executed = true }}" : "{{ executed = 12345 }}"); await expect(page.locator("#executed")).toHaveText("false"); }); } @@ -160,33 +160,13 @@ test("x-format: preserves interpolations in following original nodes", async ({ await expect(page.locator("p")).toHaveAttribute("title", "{{ 2 + 2 }}/Bar"); }); -test("x-format: nested boundaries preserve literal values", async ({ page }) => { +test("x-format: nested x-format does not re-evaluate interpolated values", async ({ page }) => { await set_html(page, ` -
-
-

{{ value }}

-
-

{{ local }}

-

{{ local }}

-

{{ local }}

- -
+
+
+

{{ value }}

`); - for (const id of ["outer", "automatic", "explicit", "once"]) { - await expect(page.locator(`#${id}`)).toHaveText("{{ 1 + 1 }}"); - await expect(page.locator(`#${id}`)).toHaveAttribute("title", "{{ 1 + 1 }}"); - } - - await page.locator("button").click(); - - for (const id of ["automatic", "explicit"]) { - await expect(page.locator(`#${id}`)).toHaveText("{{ 2 + 2 }}"); - await expect(page.locator(`#${id}`)).toHaveAttribute("title", "{{ 2 + 2 }}"); - } - for (const id of ["outer", "once"]) { - await expect(page.locator(`#${id}`)).toHaveText("{{ 1 + 1 }}"); - await expect(page.locator(`#${id}`)).toHaveAttribute("title", "{{ 1 + 1 }}"); - } + await expect(page.locator("p")).toHaveText("{{ 1 + 1 }}"); });