- update only touches the recorded project root; project installs do not follow symlinks or junctions; repeated --force rotates .orig backups; uninstall never deletes outside the recorded root - run npm/npx without a shell on Windows and pin optional tool versions - skip the update check in CI and non-interactive output - lint: cross-document section pointers, preflight row ids, gate rule numbers, per-document size budgets, gate copy equality - eval: scenarios S19-S21, route and read-cost rejudging, interpretation card judging, temp-dir previews and table-form cards no longer fail
85 lines
3.4 KiB
TypeScript
85 lines
3.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { extractBlock, hasBlock, removeBlock, upsertBlock } from "../src/marker.ts";
|
|
|
|
const M = "designpaca";
|
|
|
|
test("빈 문서에 블록을 넣는다", () => {
|
|
const out = upsertBlock("", M, "본문");
|
|
assert.ok(hasBlock(out, M));
|
|
assert.equal(extractBlock(out, M), "본문");
|
|
});
|
|
|
|
test("기존 문서 뒤에 붙이고 원문을 보존한다", () => {
|
|
const doc = "# 내 지침\n\n건드리지 마라.\n";
|
|
const out = upsertBlock(doc, M, "우리 블록");
|
|
assert.ok(out.startsWith("# 내 지침"));
|
|
assert.ok(out.includes("건드리지 마라."));
|
|
assert.equal(extractBlock(out, M), "우리 블록");
|
|
});
|
|
|
|
test("두 번째 호출은 블록 내용만 교체한다", () => {
|
|
const first = upsertBlock("# 문서\n\n앞부분\n", M, "v1");
|
|
const second = upsertBlock(first, M, "v2");
|
|
assert.equal(extractBlock(second, M), "v2");
|
|
assert.ok(!second.includes("v1"));
|
|
assert.ok(second.includes("앞부분"));
|
|
// 블록이 두 개로 늘어나면 안 된다
|
|
assert.equal(second.split("designpaca:start").length - 1, 1);
|
|
});
|
|
|
|
test("블록 뒤에 사용자가 쓴 내용도 보존한다", () => {
|
|
const doc = upsertBlock("앞\n", M, "블록") + "\n뒤에 쓴 내용\n";
|
|
const out = upsertBlock(doc, M, "새 블록");
|
|
assert.ok(out.includes("앞"));
|
|
assert.ok(out.includes("뒤에 쓴 내용"));
|
|
assert.equal(extractBlock(out, M), "새 블록");
|
|
});
|
|
|
|
test("제거하면 우리 블록만 사라진다", () => {
|
|
const doc = upsertBlock("# 문서\n\n내용\n", M, "블록");
|
|
const out = removeBlock(doc, M);
|
|
assert.ok(!hasBlock(out, M));
|
|
assert.ok(out.includes("# 문서"));
|
|
assert.ok(out.includes("내용"));
|
|
});
|
|
|
|
test("블록이 없는 문서를 제거해도 그대로다", () => {
|
|
const doc = "# 문서\n";
|
|
assert.equal(removeBlock(doc, M), doc);
|
|
});
|
|
|
|
test("본문이 마커 문자열을 언급해도 줄 전체가 마커인 블록만 인식한다", () => {
|
|
const doc =
|
|
"# 규칙\n\ndesignpaca 는 `<!-- designpaca:start -->` 블록을 쓴다.\n\n## 내 규칙\n\n지우지 마라.\n";
|
|
const once = upsertBlock(doc, M, "v1");
|
|
assert.ok(once.includes("## 내 규칙"));
|
|
const twice = upsertBlock(once, M, "v2");
|
|
assert.ok(twice.includes("## 내 규칙") && twice.includes("지우지 마라."));
|
|
assert.equal(extractBlock(twice, M), "v2");
|
|
const removed = removeBlock(twice, M);
|
|
assert.equal(removed, doc);
|
|
});
|
|
|
|
test("start/end 줄이 여러 개거나 짝이 맞지 않으면 쓰지 않고 오류로 멈춘다", () => {
|
|
const twoBlocks = upsertBlock("앞\n", M, "a") + "\n" + upsertBlock("", M, "b");
|
|
assert.throws(() => upsertBlock(twoBlocks, M, "c"), /start 2개/);
|
|
assert.throws(() => removeBlock(twoBlocks, M));
|
|
assert.equal(extractBlock(twoBlocks, M), null);
|
|
|
|
const endOnly = "<!-- designpaca:end -->\n본문\n";
|
|
assert.throws(() => upsertBlock(endOnly, M, "x"));
|
|
const reversed = "<!-- designpaca:end -->\n<!-- designpaca:start -->\n";
|
|
assert.throws(() => upsertBlock(reversed, M, "x"));
|
|
});
|
|
|
|
test("제거는 블록 밖의 연속 빈 줄을 정규화하지 않는다", () => {
|
|
const doc = "```\na\n\n\n\nb\n```\n";
|
|
const out = removeBlock(upsertBlock(doc, M, "블록"), M);
|
|
assert.equal(out, doc);
|
|
});
|
|
|
|
test("문서 중간 블록을 빼면 블록 앞뒤 구분 빈 줄 하나만 걷힌다", () => {
|
|
const doc = "A\n\n<!-- designpaca:start -->\nx\n<!-- designpaca:end -->\n\nB\n";
|
|
assert.equal(removeBlock(doc, M), "A\n\nB\n");
|
|
});
|