Files
cjk-autospace/test/spacing-detail.test.mjs
admin 2d42c7ecf2 feat: 聚珍切片 1 — 中西間隙/標點擠壓/禁則 + v1 相容層
從零重寫之第一垂直切片(依 ARCHITECTURE.md 契約),涵蓋並超越 v1 全部功能。

核心基座(src/core):unicode 字元表+標點子類+字形側位;locale 語言與
全角/開明政策(全域 lang.style +逐 lang policy);dom(jz-* 工廠+可還原
標記);finder 遍歷引擎(avoid/scope.include/邏輯 run/pill 鄰字/分塊閘);
pass 聚合管線。

功能 pass(src/typeset):
- spacing:中西間隙採 margin 模型——包裹邊界左側字、以 margin-right 留隙,
  不注入空白字元(textContent 完全不變、複製緊湊、justify 不暴增、不撐框、
  行首乾淨)。含跨樣式邊界、pill、原檔空格移除。
- gapTrim(lineedge):layout pass,行末去隙,修正 justify 右緣內縮。
- longword:長英文詞 <span lang> + hyphens。
- jiya:標點 charify + 全角/開明/半角(全域 + 逐 lang);半形以字型 OpenType
  halt 渲染(按字形正確定位、不重疊,居中字形 !? 與繁體 locl 句逗點號皆正確);
  行內連續擠壓。
- jinze:行首行末避頭尾(閉類/點號綁前字、開類綁後字,以詞元為邊界、長引文
  中段可斷);亦為 justify 綁定載體。
- lineedge:行端擠壓 layout pass(括號引號;ResizeObserver 重算,瀏覽器專用)。

§8 justify:jz-char inline-block 原子,句末標點單份間距。
v1 相容層(compat/v1):createCjkAutospace().apply 映射,IIFE 全域別名。
構建:esbuild → ESM/IIFE/CSS;tsc → d.ts。測試:39 個 jsdom 案例。
demo:簡繁並列+justify+強制繁體開明+標點寬度量測,載 Noto webfont 作參考
(擠壓依賴字型 halt)。間隔號 · 不觸發中西間隙;200% 等詞元不被禁則切斷。
2026-06-01 21:31:07 +08:00

81 lines
3.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 針對性驗證:v1 之「行內塊(pill)邊界空格」與「原檔多餘空格收斂」是否
// 在新引擎落地(回應使用者提問)。
import assert from "node:assert/strict";
import { test } from "node:test";
import { setupDom, countTag } from "./helpers.mjs";
import { createJuzhen } from "../dist/juzhen.mjs";
const SPACING_ONLY = { jiya: false, longWord: false, jinze: false };
// ----- 原檔多餘空格收斂(STRIP_CJK_SPACE_ANS / STRIP_ANS_SPACE_CJK-----
test("stripCJK↔Latin 間之作者空格被移除(間隙改由 margin,複製緊湊)", () =>
{
const { document } = setupDom("<p>用 Neovim 寫</p>");
createJuzhen(SPACING_ONLY).render(document.body);
const p = document.querySelector("p");
assert.equal(p.textContent, "用Neovim寫", "作者空格移除,間隙改由 jz-hws margincopy 緊湊)");
assert.equal(countTag(p, "jz-hws"), 2);
});
test("strip:間隙 copy-clean(無注入空白,textContent 不含中西間空白)", () =>
{
const { document } = setupDom("<p>裝 fzf 工具</p>");
createJuzhen(SPACING_ONLY).render(document.body);
const p = document.querySelector("p");
assert.equal(p.textContent, "裝fzf工具", "中西間隙不入 textContent(純 copy-clean");
assert.equal(countTag(p, "jz-hws"), 2);
});
test("strip:全流程(含 charify)下作者空格仍收斂、不雙空格", () =>
{
const { document } = setupDom('<p lang="zh-Hans">用 TypeScript 寫,很好</p>');
createJuzhen({}).render(document.body);
const p = document.querySelector("p");
assert.ok(!/ {2,}/.test(p.textContent), "無雙空格:" + JSON.stringify(p.textContent));
});
// ----- 行內塊(pill)邊界空格(Pass B-----
test("pillcode 與 CJK 緊鄰兩側各插一 jz-hws", () =>
{
const { document } = setupDom("<p>用<code>vim</code>編輯</p>");
createJuzhen(SPACING_ONLY).render(document.body);
assert.equal(countTag(document.querySelector("p"), "jz-hws"), 2);
});
test("pillcode 與 Latin 緊鄰亦插間隙(PILL_NEIGHBOR 含 A-Za-z0-9", () =>
{
const { document } = setupDom("<p>看<code>x</code>嗎</p>");
createJuzhen(SPACING_ONLY).render(document.body);
assert.equal(countTag(document.querySelector("p"), "jz-hws"), 2);
});
test("pill:已有作者空格時收斂為單一 jz-hws(不雙空格)", () =>
{
const { document } = setupDom("<p>用 <code>vim</code> 編輯</p>");
createJuzhen(SPACING_ONLY).render(document.body);
const p = document.querySelector("p");
assert.ok(!/ {2,}/.test(p.textContent), "無雙空格:" + JSON.stringify(p.textContent));
assert.equal(countTag(p, "jz-hws"), 2);
});
test("pill:標點與 code 之間不強插間隙(鄰字非 PILL_NEIGHBOR", () =>
{
const { document } = setupDom("<p>他說:<code>vim</code>很好</p>");
createJuzhen(SPACING_ONLY).render(document.body);
// 非 PILL_NEIGHBOR → 左側不插;code|很 右側插 1。
assert.equal(countTag(document.querySelector("p"), "jz-hws"), 1);
});
// ----- 間隔號 ·(U+00B7)不觸發中西間隙 -----
test("間隔號:姓名 · 兩側不插 jz-hws(· 屬 CJK 標點,非西文)", () =>
{
const { document } = setupDom("<p>卡尔·马克思</p>");
createJuzhen(SPACING_ONLY).render(document.body);
assert.equal(countTag(document.querySelector("p"), "jz-hws"), 0, "· 兩側皆不應有間隙");
});