274 lines
10 KiB
JavaScript
274 lines
10 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("所有權:本實例完整還原 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();
|
|
});
|