326 lines
13 KiB
JavaScript
326 lines
13 KiB
JavaScript
// createJuzhen 實例所有權:revert 只得復原本實例產物。
|
|
|
|
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
|
|
import { setupDom } from "./helpers.mjs";
|
|
import { createJuzhen } from "../dist/juzhen.mjs";
|
|
|
|
const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
test("所有權:作者 data-jz 結構不可作為 revert 憑證", () =>
|
|
{
|
|
const html = '<section data-jz="spacing"><jz-hws data-jz="spacing" '
|
|
+ 'data-jz-kind="wrap">author</jz-hws></section>';
|
|
const { document } = setupDom(html);
|
|
const before = document.body.innerHTML;
|
|
createJuzhen().revert(document.body);
|
|
assert.equal(document.body.innerHTML, before);
|
|
});
|
|
|
|
test("所有權:實例 B 不得還原實例 A 之結構或 root 屬性", () =>
|
|
{
|
|
const { document } = setupDom('<p lang="zh-Hans">用Neovim寫,很好。</p>');
|
|
const before = document.body.innerHTML;
|
|
const a = createJuzhen({ jiya: { halfWidth: "margin" }, justifyAtoms: false });
|
|
const b = createJuzhen();
|
|
a.render(document.body);
|
|
const rendered = document.body.innerHTML;
|
|
assert.equal(document.body.getAttribute("data-jz-halfwidth"), "margin");
|
|
assert.equal(document.body.getAttribute("data-jz-atoms"), "inline");
|
|
|
|
b.revert(document.body);
|
|
assert.equal(document.body.innerHTML, rendered);
|
|
assert.equal(document.body.getAttribute("data-jz-halfwidth"), "margin");
|
|
assert.equal(document.body.getAttribute("data-jz-atoms"), "inline");
|
|
|
|
a.revert(document.body);
|
|
assert.equal(document.body.innerHTML, before);
|
|
});
|
|
|
|
test("所有權:兩實例同值屬性之早到者先 revert 不移除後到者聲明", () =>
|
|
{
|
|
const { document } = setupDom('<p lang="zh-Hans">純文字</p>');
|
|
const opts = {
|
|
spacing: false,
|
|
jiya: { halfWidth: "margin" },
|
|
jinze: false,
|
|
orphan: false,
|
|
longWord: false,
|
|
justifyAtoms: false,
|
|
};
|
|
const a = createJuzhen(opts);
|
|
const b = createJuzhen(opts);
|
|
a.render(document.body);
|
|
b.render(document.body);
|
|
|
|
a.revert(document.body);
|
|
assert.equal(document.body.getAttribute("data-jz-halfwidth"), "margin");
|
|
assert.equal(document.body.getAttribute("data-jz-atoms"), "inline");
|
|
assert.equal(document.querySelector("p").getAttribute("data-jz-block-level"), "paragraph");
|
|
|
|
b.revert(document.body);
|
|
assert.equal(document.body.hasAttribute("data-jz-halfwidth"), false);
|
|
assert.equal(document.body.hasAttribute("data-jz-atoms"), false);
|
|
assert.equal(document.querySelector("p").hasAttribute("data-jz-block-level"), false);
|
|
});
|
|
|
|
test("所有權:屬性寫者亂序 revert 還原最近活躍值或原值", () =>
|
|
{
|
|
const options = {
|
|
spacing: false, jiya: false, jinze: false, orphan: false, longWord: false,
|
|
};
|
|
const first = setupDom("<p>純文字</p>");
|
|
const a = createJuzhen(options);
|
|
const b = createJuzhen({ ...options, level: { text: "p" } });
|
|
a.render(first.document.body);
|
|
b.render(first.document.body);
|
|
a.revert(first.document.body);
|
|
assert.equal(first.document.querySelector("p").getAttribute("data-jz-block-level"), "text");
|
|
b.revert(first.document.body);
|
|
assert.equal(first.document.querySelector("p").hasAttribute("data-jz-block-level"), false);
|
|
|
|
const second = setupDom("<p>純文字</p>");
|
|
const c = createJuzhen(options);
|
|
const d = createJuzhen({ ...options, level: { text: "p" } });
|
|
c.render(second.document.body);
|
|
d.render(second.document.body);
|
|
d.revert(second.document.body);
|
|
assert.equal(second.document.querySelector("p").getAttribute("data-jz-block-level"), "paragraph");
|
|
c.revert(second.document.body);
|
|
assert.equal(second.document.querySelector("p").hasAttribute("data-jz-block-level"), false);
|
|
});
|
|
|
|
test("所有權:外部改寫屬性後 revert fail closed,新寫者可以當前值重建基線", () =>
|
|
{
|
|
const options = {
|
|
spacing: false, jiya: false, jinze: false, orphan: false, longWord: false,
|
|
justifyAtoms: false,
|
|
};
|
|
const { document } = setupDom("<p>純文字</p>");
|
|
const a = createJuzhen(options);
|
|
a.render(document.body);
|
|
document.body.setAttribute("data-jz-atoms", "external");
|
|
document.querySelector("p").removeAttribute("data-jz-block-level");
|
|
|
|
const b = createJuzhen(options);
|
|
b.render(document.body);
|
|
assert.equal(document.body.getAttribute("data-jz-atoms"), "inline");
|
|
assert.equal(document.querySelector("p").getAttribute("data-jz-block-level"), "paragraph");
|
|
a.revert(document.body);
|
|
assert.equal(document.body.getAttribute("data-jz-atoms"), "inline");
|
|
assert.equal(document.querySelector("p").getAttribute("data-jz-block-level"), "paragraph");
|
|
b.revert(document.body);
|
|
assert.equal(document.body.getAttribute("data-jz-atoms"), "external");
|
|
assert.equal(document.querySelector("p").hasAttribute("data-jz-block-level"), false);
|
|
});
|
|
|
|
test("所有權:子根 render 後對祖先 revert 完整清理", () =>
|
|
{
|
|
const { document } = setupDom('<div><p lang="zh-Hans">用Neovim寫,很好。</p></div>');
|
|
const before = document.body.innerHTML;
|
|
const p = document.querySelector("p");
|
|
const jz = createJuzhen({ jiya: { halfWidth: "margin" }, justifyAtoms: false });
|
|
jz.render(p);
|
|
assert.equal(p.getAttribute("data-jz-halfwidth"), "margin");
|
|
assert.ok(p.querySelector("[data-jz]"));
|
|
|
|
jz.revert(document.body);
|
|
assert.equal(document.body.innerHTML, before);
|
|
});
|
|
|
|
test("所有權:局部 revert 完整撤回祖根與獨立子根的混合產物", () =>
|
|
{
|
|
const { document } = setupDom('<main><section id="part">'
|
|
+ '<p id="a">甲文,內容。</p><p id="b">乙文,內容。</p></section></main>');
|
|
const main = document.querySelector("main");
|
|
const part = document.getElementById("part");
|
|
const a = document.getElementById("a");
|
|
const b = document.getElementById("b");
|
|
const before = part.innerHTML;
|
|
const jz = createJuzhen({ jiya: { halfWidth: "margin" }, justifyAtoms: false });
|
|
jz.render(main);
|
|
jz.render(b);
|
|
assert.ok(a.querySelector("jz-char"));
|
|
assert.ok(b.querySelector("jz-char"));
|
|
assert.equal(b.getAttribute("data-jz-halfwidth"), "margin");
|
|
|
|
jz.revert(part);
|
|
assert.equal(part.innerHTML, before, "祖根寫入的兄弟產物與子根屬性皆撤回");
|
|
assert.equal(main.getAttribute("data-jz-halfwidth"), "margin", "請求範圍外祖根屬性保留");
|
|
jz.render(part);
|
|
assert.ok(a.querySelector("jz-char"), "還原後可再次渲染");
|
|
jz.revert(main);
|
|
assert.equal(part.innerHTML, before);
|
|
});
|
|
|
|
test("所有權:混合根局部 revert 保留作者與其他實例的產物", () =>
|
|
{
|
|
const { document } = setupDom('<main><section id="part">'
|
|
+ '<p id="a" data-jz-block-level="author-level">甲文,內容。</p>'
|
|
+ '<p id="b">乙文,內容。</p><p id="foreign">他文,內容。</p>'
|
|
+ '<span data-jz-skip><jz-hws data-jz="spacing" data-jz-kind="wrap">作者</jz-hws></span>'
|
|
+ '</section></main>');
|
|
const part = document.getElementById("part");
|
|
const foreign = document.getElementById("foreign");
|
|
const peer = createJuzhen();
|
|
peer.render(foreign);
|
|
const before = part.innerHTML;
|
|
const foreignBefore = foreign.innerHTML;
|
|
const author = part.querySelector("span > jz-hws");
|
|
const local = createJuzhen({ scope: { avoid: "#foreign" } });
|
|
local.render(document.querySelector("main"));
|
|
local.render(document.getElementById("b"));
|
|
|
|
local.revert(part);
|
|
assert.equal(part.innerHTML, before, "本實例全清且作者/peer 產物完整保留");
|
|
assert.equal(foreign.innerHTML, foreignBefore);
|
|
assert.equal(part.querySelector("span > jz-hws"), author, "作者元素身份保留");
|
|
assert.equal(document.getElementById("a").getAttribute("data-jz-block-level"), "author-level");
|
|
assert.equal(foreign.getAttribute("data-jz-block-level"), "paragraph");
|
|
peer.revert(foreign);
|
|
});
|
|
|
|
test("所有權:本實例完整還原 wrapper 與 splitoff", () =>
|
|
{
|
|
const html = '<p lang="zh-Hans">前文<strong>粗体文字</strong>,后续</p>';
|
|
const { document } = setupDom(html);
|
|
const before = document.body.innerHTML;
|
|
const jz = createJuzhen({ spacing: false, longWord: false, orphan: false });
|
|
jz.render(document.body);
|
|
assert.ok(document.querySelector('[data-jz-kind="wrap"]'));
|
|
assert.ok(document.querySelector('[data-jz-kind="splitoff"]'));
|
|
|
|
jz.revert(document.body);
|
|
assert.equal(document.body.innerHTML, before);
|
|
});
|
|
|
|
test("所有權:已有實例重複 render 仍冪等", () =>
|
|
{
|
|
const { document } = setupDom('<p lang="zh-Hans">用Neovim寫,很好。</p>');
|
|
const jz = createJuzhen();
|
|
jz.render(document.body);
|
|
const once = document.body.innerHTML;
|
|
jz.render(document.body);
|
|
assert.equal(document.body.innerHTML, once);
|
|
});
|
|
|
|
test("所有權:已還原 wrapper 之舊憑證即時撤銷", () =>
|
|
{
|
|
const { document } = setupDom("<p>中A文</p>");
|
|
const jz = createJuzhen({ jiya: false, longWord: false, jinze: false, orphan: false });
|
|
jz.render(document.body);
|
|
const gap = document.querySelector("jz-hws");
|
|
assert.ok(gap);
|
|
jz.revert(document.body);
|
|
document.querySelector("p").appendChild(gap);
|
|
const stale = gap.outerHTML;
|
|
|
|
jz.revert(document.body);
|
|
assert.ok(document.body.contains(gap));
|
|
assert.equal(gap.outerHTML, stale);
|
|
});
|
|
|
|
test("所有權:已知 tag 改成無生產者之 marker kind 亦 fail closed", () =>
|
|
{
|
|
const { document } = setupDom("<p>中A文</p>");
|
|
const jz = createJuzhen({ jiya: false, longWord: false, jinze: false, orphan: false });
|
|
jz.render(document.body);
|
|
const gap = document.querySelector("jz-hws");
|
|
assert.ok(gap);
|
|
gap.setAttribute("data-jz-kind", "marker");
|
|
const malformed = gap.outerHTML;
|
|
|
|
jz.revert(document.body);
|
|
assert.ok(document.body.contains(gap));
|
|
assert.equal(gap.outerHTML, malformed);
|
|
});
|
|
|
|
test("所有權:observe 移除獨立 render 根時清理脫離子樹與佈局觀察者", async () =>
|
|
{
|
|
const previousResizeObserver = globalThis.ResizeObserver;
|
|
const observers = [];
|
|
class MockResizeObserver
|
|
{
|
|
constructor()
|
|
{
|
|
this.targets = [];
|
|
this.disconnectCount = 0;
|
|
observers.push(this);
|
|
}
|
|
|
|
observe(target)
|
|
{
|
|
this.targets.push(target);
|
|
}
|
|
|
|
disconnect()
|
|
{
|
|
this.disconnectCount += 1;
|
|
}
|
|
}
|
|
globalThis.ResizeObserver = MockResizeObserver;
|
|
try
|
|
{
|
|
const { document } = setupDom('<div id="parent"></div>');
|
|
const parent = document.getElementById("parent");
|
|
const jz = createJuzhen({ jiya: { halfWidth: "margin" }, justifyAtoms: false });
|
|
jz.observe(parent);
|
|
const block = document.createElement("p");
|
|
block.lang = "zh-Hans";
|
|
block.innerHTML = "用Neovim寫,很好。";
|
|
const original = block.innerHTML;
|
|
parent.appendChild(block);
|
|
await tick();
|
|
|
|
const blockObservers = observers.filter((ro) => ro.targets.includes(block));
|
|
assert.equal(blockObservers.length, 2, "lineEdge 與 gapTrim 各持一觀察者");
|
|
assert.ok(block.querySelector("[data-jz]"));
|
|
assert.equal(block.getAttribute("data-jz-halfwidth"), "margin");
|
|
assert.equal(block.getAttribute("data-jz-atoms"), "inline");
|
|
|
|
block.setAttribute("data-jz-atoms", "external");
|
|
block.remove();
|
|
await tick();
|
|
assert.ok(blockObservers.every((ro) => ro.disconnectCount === 1));
|
|
assert.equal(block.querySelector("[data-jz]"), null);
|
|
assert.equal(block.innerHTML, original);
|
|
assert.equal(block.hasAttribute("data-jz-halfwidth"), false);
|
|
assert.equal(block.getAttribute("data-jz-atoms"), "external", "外部改寫 fail closed");
|
|
|
|
parent.appendChild(block);
|
|
await tick();
|
|
assert.ok(block.querySelector("[data-jz]"), "脫離根清理後可再插入重新 render");
|
|
jz.revert(parent);
|
|
jz.disconnect();
|
|
assert.equal(block.getAttribute("data-jz-atoms"), "external");
|
|
}
|
|
finally
|
|
{
|
|
if (previousResizeObserver === undefined) { delete globalThis.ResizeObserver; }
|
|
else { globalThis.ResizeObserver = previousResizeObserver; }
|
|
}
|
|
});
|
|
|
|
test("所有權:observe 移除非 render 根之行內內容不過度清理", async () =>
|
|
{
|
|
const { document } = setupDom('<div id="parent"><p>前<span>中A文</span>後</p></div>');
|
|
const parent = document.getElementById("parent");
|
|
const jz = createJuzhen({ jiya: false, jinze: false, orphan: false, longWord: false });
|
|
jz.render(parent);
|
|
jz.observe(parent);
|
|
const span = document.querySelector("span");
|
|
const rendered = span.innerHTML;
|
|
assert.ok(span.querySelector("jz-hws"));
|
|
|
|
span.remove();
|
|
await tick();
|
|
assert.equal(span.innerHTML, rendered, "非獨立 render 根之脫離內容保持原渲染結構");
|
|
assert.ok(span.querySelector("jz-hws"));
|
|
|
|
document.querySelector("p").appendChild(span);
|
|
await tick();
|
|
jz.revert(parent);
|
|
jz.disconnect();
|
|
});
|