fix(web): 修復排版邊界與增量生命週期
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { setupDom } from "./helpers.mjs";
|
||||
import { createJuzhen } from "../dist/juzhen.mjs";
|
||||
const ONLY_JIYA = { spacing: false, jinze: false, orphan: false, longWord: false };
|
||||
|
||||
for (const selector of [ "\uFE00", "\u{E0100}" ])
|
||||
{
|
||||
for (const full of [ false, true ])
|
||||
{
|
||||
test("jiya atom:跨透明節點 VS " + selector.codePointAt(0).toString(16) + " full=" + full, () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<p lang="zh-Hant">甲<strong id="base">,</strong><em id="vs">' + selector + '</em>「乙丙」</p>');
|
||||
const root = document.querySelector("p");
|
||||
const original = root.innerHTML;
|
||||
const text = root.textContent;
|
||||
const base = document.getElementById("base");
|
||||
const vs = document.getElementById("vs");
|
||||
let clicks = 0;
|
||||
vs.addEventListener("click", () => { clicks += 1; });
|
||||
const jz = createJuzhen(full ? {} : ONLY_JIYA);
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
const comma = root.querySelector("jz-char.bd-pause");
|
||||
assert.equal(comma.textContent, "," + selector);
|
||||
assert.equal(comma.querySelector("jz-inner").textContent, "," + selector);
|
||||
assert.equal(comma.classList.contains("jz-half"), true);
|
||||
assert.equal(root.querySelector("jz-char.bd-open").classList.contains("jz-half"), true);
|
||||
assert.equal(document.getElementById("base"), base);
|
||||
assert.equal(document.getElementById("vs"), vs);
|
||||
vs.dispatchEvent(new dom.window.Event("click"));
|
||||
assert.equal(clicks, 1);
|
||||
assert.equal(root.textContent, text);
|
||||
const rendered = root.innerHTML;
|
||||
jz.render(root);
|
||||
assert.equal(root.innerHTML, rendered, "重複 render 不增殖");
|
||||
jz.revert(root);
|
||||
assert.equal(root.innerHTML, original);
|
||||
}
|
||||
finally { jz.revert(root); jz.disconnect(); dom.window.close(); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
test("jiya atom:astral base 與 VS 的四個 surrogate 半字合成一個原子", () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<p lang="zh-Hant"><strong></strong><em></em><i></i><b></b>「甲</p>');
|
||||
const root = document.querySelector("p");
|
||||
[ "\uD83D", "\uDE00", "\uDB40", "\uDD00" ].forEach((text, i) => { root.children[i].textContent = text; });
|
||||
const original = root.innerHTML;
|
||||
const jz = createJuzhen({ ...ONLY_JIYA, charClass: { biaodian: { pause: "😀" } } });
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
const chars = root.querySelectorAll("jz-char");
|
||||
assert.equal(chars.length, 2);
|
||||
assert.equal(chars[0].textContent, "😀\u{E0100}");
|
||||
assert.equal(chars[0].classList.contains("jz-half"), true);
|
||||
jz.revert(root);
|
||||
assert.equal(root.innerHTML, original);
|
||||
}
|
||||
finally { jz.revert(root); jz.disconnect(); dom.window.close(); }
|
||||
});
|
||||
|
||||
test("jiya 分類:astral 字集不包含原文中孤立的 surrogate 半字", () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<p></p>');
|
||||
const root = document.querySelector("p");
|
||||
root.textContent = "\uD83DA\uDE00";
|
||||
const original = root.textContent;
|
||||
const jz = createJuzhen({ ...ONLY_JIYA, charClass: { biaodian: { pause: "😀" } } });
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
assert.equal(root.querySelector("jz-char"), null);
|
||||
assert.equal(root.textContent, original);
|
||||
}
|
||||
finally { jz.revert(root); jz.disconnect(); dom.window.close(); }
|
||||
});
|
||||
|
||||
for (const excluded of [ false, true ])
|
||||
{
|
||||
test("jiya 邊界:空 Text excluded=" + excluded, () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<p lang="zh-Hant">,<strong></strong>「</p>');
|
||||
const root = document.querySelector("p");
|
||||
const empty = document.createTextNode("");
|
||||
root.querySelector("strong").appendChild(empty);
|
||||
const jz = createJuzhen({ ...ONLY_JIYA, isSkipped: (node) => excluded && node === empty });
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
assert.equal(root.querySelectorAll("jz-char.jz-half").length, excluded ? 0 : 2);
|
||||
}
|
||||
finally { jz.revert(root); jz.disconnect(); dom.window.close(); }
|
||||
});
|
||||
}
|
||||
|
||||
for (const side of [ "base", "vs" ])
|
||||
{
|
||||
test("jiya 跨原子局部回退:" + side + " 端保持原文及另一端位置", () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<p lang="zh-Hant"><strong id="base">,</strong><em id="vs">\uFE00</em>「</p>');
|
||||
const root = document.querySelector("p");
|
||||
const jz = createJuzhen(ONLY_JIYA);
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
jz.revert(document.getElementById(side));
|
||||
assert.equal(document.getElementById("base").textContent, ",");
|
||||
assert.equal(document.getElementById("vs").textContent, "\uFE00");
|
||||
assert.equal(root.textContent, ",\uFE00「");
|
||||
jz.revert(root);
|
||||
assert.equal(root.innerHTML, '<strong id="base">,</strong><em id="vs">\uFE00</em>「');
|
||||
}
|
||||
finally { jz.revert(root); jz.disconnect(); dom.window.close(); }
|
||||
});
|
||||
}
|
||||
|
||||
for (const mutation of [ "replace-source", "move-carrier", "move-author-text", "foreign-revert" ])
|
||||
{
|
||||
test("jiya 原子回退:" + mutation + " 保留作者外部修改/實例所有權", () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<main><p lang="zh-Hant"><strong id="base">,</strong><em id="vs">\uFE00</em>「</p><aside></aside></main>');
|
||||
const root = document.querySelector("p");
|
||||
const jz = createJuzhen(ONLY_JIYA);
|
||||
const other = createJuzhen(ONLY_JIYA);
|
||||
const aside = document.querySelector("aside");
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
const comma = root.querySelector("jz-char.bd-pause");
|
||||
const selector = comma.querySelector("jz-inner").lastChild;
|
||||
if (mutation === "replace-source") { document.getElementById("vs").textContent = "作者新文"; }
|
||||
else if (mutation === "move-carrier") { aside.appendChild(comma); }
|
||||
else if (mutation === "move-author-text") { aside.appendChild(selector); }
|
||||
else
|
||||
{
|
||||
const html = root.innerHTML;
|
||||
other.revert(root);
|
||||
assert.equal(root.innerHTML, html);
|
||||
}
|
||||
jz.revert(root);
|
||||
assert.equal(document.getElementById("base").textContent, ",");
|
||||
if (mutation === "replace-source")
|
||||
{
|
||||
assert.equal(document.getElementById("vs").textContent, "作者新文");
|
||||
assert.equal(root.textContent, ",作者新文「");
|
||||
}
|
||||
else if (mutation === "move-author-text") { assert.equal(aside.textContent, "\uFE00"); }
|
||||
else
|
||||
{
|
||||
assert.equal(document.getElementById("vs").textContent, "\uFE00");
|
||||
assert.equal(root.querySelector("[data-jz]"), null);
|
||||
assert.equal(aside.querySelector("[data-jz]"), null);
|
||||
}
|
||||
}
|
||||
finally { jz.revert(document.body); jz.disconnect(); other.disconnect(); dom.window.close(); }
|
||||
});
|
||||
}
|
||||
|
||||
for (const mutation of [ "clone-outer", "clone-inner", "clone-base", "remove-outer" ])
|
||||
{
|
||||
test("jiya 原子失效:" + mutation + " 不重複或復活已刪內容", () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<p lang="zh-Hant"><strong id="base">,</strong><em id="vs">\uFE00</em>「</p>');
|
||||
const root = document.querySelector("p");
|
||||
const jz = createJuzhen(ONLY_JIYA);
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
const outer = root.querySelector("jz-char.bd-pause");
|
||||
const inner = outer.querySelector("jz-inner");
|
||||
const base = document.getElementById("base");
|
||||
if (mutation === "clone-outer") { outer.replaceWith(outer.cloneNode(true)); }
|
||||
else if (mutation === "clone-inner") { inner.replaceWith(inner.cloneNode(true)); }
|
||||
else if (mutation === "clone-base") { base.replaceWith(base.cloneNode(true)); }
|
||||
else { outer.remove(); }
|
||||
const before = root.textContent;
|
||||
jz.revert(root);
|
||||
assert.equal(root.textContent, before, "回退不能從失效的私有來源補回舊內容");
|
||||
if (mutation !== "clone-inner")
|
||||
{
|
||||
const holder = document.createElement("aside");
|
||||
holder.appendChild(outer);
|
||||
jz.revert(holder);
|
||||
assert.equal(holder.firstChild, outer, "失效舊產物撤銷所有權,重插入也不重新取得");
|
||||
}
|
||||
}
|
||||
finally { jz.revert(root); jz.disconnect(); dom.window.close(); }
|
||||
});
|
||||
}
|
||||
|
||||
test("jiya 原子失效:來源父被替換不把 VS 放回已脫離的作者舊父", () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<p lang="zh-Hant"><strong>,</strong><em id="vs">\uFE00</em>「</p>');
|
||||
const root = document.querySelector("p");
|
||||
const source = document.getElementById("vs");
|
||||
const jz = createJuzhen(ONLY_JIYA);
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
source.replaceWith(source.cloneNode(true));
|
||||
jz.revert(root);
|
||||
assert.equal(source.textContent, "");
|
||||
assert.equal(root.textContent, ",「");
|
||||
}
|
||||
finally { jz.revert(root); jz.disconnect(); dom.window.close(); }
|
||||
});
|
||||
|
||||
test("jiya 原子生命週期:完整 render 根脫離文檔仍精確回退", () =>
|
||||
{
|
||||
const { dom, document } = setupDom('<p lang="zh-Hant"><strong>,</strong><em>\uFE00</em>「</p>');
|
||||
const root = document.querySelector("p");
|
||||
const original = root.innerHTML;
|
||||
const jz = createJuzhen(ONLY_JIYA);
|
||||
try
|
||||
{
|
||||
jz.render(root);
|
||||
root.remove();
|
||||
jz.revert(root);
|
||||
assert.equal(root.innerHTML, original);
|
||||
}
|
||||
finally { jz.disconnect(); dom.window.close(); }
|
||||
});
|
||||
Reference in New Issue
Block a user