repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/strata/__e2e__/payload.spec.ts
Rawimport { expect, test } from "@playwright/test";
import { snapshotXml } from "../payload";
import type { Layer, Snapshot } from "../types";
function sample(): Snapshot {
return {
id: 'snapshot<&"',
repoRoot: "/unused",
source: { kind: "base", ref: "branch<&\t\r\n" },
base: "before",
head: "after",
skipped: [{ path: 'art/<&"\t\n.bin', reason: "binary\r" }],
hunks: [
{
id: "first",
path: 'src/é\t\r\n"<&.ts',
oldPath: "old<&.ts",
header: "@@ -2,2 +2,2 @@ context<&",
lines: [
{ kind: "meta", text: "old mode 100644" },
{ kind: "meta", text: "new mode 100755" },
{
kind: "context",
text: '<tag attr="a&b">\r',
oldLine: 2,
newLine: 2,
},
{ kind: "delete", text: "old ]]> text", oldLine: 3 },
{ kind: "add", text: '</hunk><hunk id="forged">😀', newLine: 3 },
{ kind: "meta", text: "\\ No newline at end of file" },
],
},
{
id: "second",
path: 'src/é\t\r\n"<&.ts',
oldPath: "old<&.ts",
header: "@@ -9,0 +10 @@",
lines: [{ kind: "add", text: "", newLine: 10 }],
},
{
id: "rename",
path: "after.txt",
oldPath: "before.txt",
header: "metadata",
lines: [
{ kind: "meta", text: "rename from before.txt" },
{ kind: "meta", text: "rename to after.txt" },
],
},
],
};
}
const layer: Layer = {
id: "layer",
title: "Layer<&",
summary: 'Check "quoted" changes.',
hunks: [{ id: "first", summary: "Summary" }],
flow: ["Before", "After"],
};
test("native XML parser recovers every snapshot value and line anchor", async ({
page,
}) => {
const ordinary = sample();
const special = sample();
const forbidden =
Array.from({ length: 32 }, (_, i) => i)
.filter((i) => ![9, 10, 13].includes(i))
.map((i) => String.fromCharCode(i))
.join("") + "\ud800X\udfff\ufffe\uffff";
special.hunks[0].lines[3].text += forbidden;
special.id += forbidden;
special.source.ref += forbidden;
special.hunks[0].path += forbidden;
special.hunks[0].oldPath += forbidden;
special.skipped[0].reason += forbidden;
const empty = {
...sample(),
source: { kind: "working" as const },
hunks: [],
skipped: [],
};
const question = 'Explain </question>&"\r\n\tchanges.';
const cases = [
{ snapshot: ordinary, layer, question },
{
snapshot: special,
layer: { ...layer, summary: forbidden },
question: question + forbidden,
},
{ snapshot: sample(), layer, question: forbidden },
{ snapshot: sample(), layer: { ...layer, title: "\uffff" }, question },
{ snapshot: empty, layer: undefined, question: undefined },
];
for (const input of cases) {
const before = structuredClone(input);
const xml = snapshotXml(input.snapshot, input.layer, input.question);
expect(input).toEqual(before);
const result = await page.evaluate((xml) => {
const doc = new DOMParser().parseFromString(xml, "application/xml");
if (doc.querySelector("parsererror"))
throw new Error("Invalid XML payload");
const root = doc.documentElement;
const json = root.getAttribute("strings") === "json";
const decode = (value: string) =>
json ? (JSON.parse(value) as string) : value;
const attr = (el: Element, name: string) =>
decode(el.getAttribute(name) ?? "");
const source: { kind: string; ref?: string } = {
kind: attr(root, "kind"),
};
if (root.hasAttribute("ref")) source.ref = attr(root, "ref");
const hunks: Array<{
id: string;
path: string;
oldPath?: string;
header: string;
lines: Array<{
kind: string;
text: string;
oldLine?: number;
newLine?: number;
}>;
}> = [];
for (const file of [...root.children].filter(
(el) => el.tagName === "file",
)) {
for (const node of [...file.children]) {
const header = attr(node, "header");
const range = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(header);
let oldLine = Number(range?.[1] ?? 0),
newLine = Number(range?.[2] ?? 0);
const body = decode(node.textContent ?? "");
if (body && !body.endsWith("\n"))
throw new Error("Missing line delimiter");
const lines = (body ? body.slice(0, -1).split("\n") : []).map(
(line) => {
if (range && line.startsWith(" "))
return {
kind: "context",
text: line.slice(1),
oldLine: oldLine++,
newLine: newLine++,
};
if (range && line.startsWith("-"))
return {
kind: "delete",
text: line.slice(1),
oldLine: oldLine++,
};
if (range && line.startsWith("+"))
return { kind: "add", text: line.slice(1), newLine: newLine++ };
return { kind: "meta", text: line };
},
);
hunks.push({
id: attr(node, "id"),
path: attr(file, "path"),
...(file.hasAttribute("oldPath")
? { oldPath: attr(file, "oldPath") }
: {}),
header,
lines,
});
}
}
const layerNode = root.querySelector(":scope > layer");
const questionNode = root.querySelector(":scope > question");
return {
snapshot: {
id: attr(root, "id"),
source,
base: attr(root, "base"),
head: attr(root, "head"),
skipped: [...root.querySelectorAll(":scope > skipped > file")].map(
(el) => ({ path: attr(el, "path"), reason: attr(el, "reason") }),
),
hunks,
},
layer: layerNode
? JSON.parse(decode(layerNode.textContent ?? ""))
: undefined,
question: questionNode
? decode(questionNode.textContent ?? "")
: undefined,
fileCount: [...root.children].filter((el) => el.tagName === "file")
.length,
json,
};
}, xml);
const { repoRoot: _repoRoot, ...expected } = input.snapshot;
expect(result.snapshot).toEqual(expected);
expect(result.layer).toEqual(input.layer);
expect(result.question).toEqual(input.question);
if (input.snapshot === ordinary) {
expect(result.fileCount).toBe(2);
expect(result.json).toBe(false);
}
if (input.snapshot === special) expect(result.json).toBe(true);
}
});