deer-flow/frontend/tests/unit/core/artifacts/delimited-preview.test.ts
Ryker_Feng 9fda432ba1
feat(artifacts): preview CSV and TSV files as bounded tables (#5284)
* feat(artifacts): preview CSV and TSV files as bounded tables

* chore: keep preview screenshots out of the PR file diff

* fix(artifacts): detect record newlines outside quoted fields

* test(auth): include project permissions in me contract expectations
2026-09-08 19:11:11 +08:00

116 lines
3.9 KiB
TypeScript

import { describe, expect, it } from "@rstest/core";
import { parseDelimitedPreview } from "@/core/artifacts/delimited-preview";
const parse = (
content: string,
truncated = false,
delimiter: "," | "\t" = ",",
) => parseDelimitedPreview({ content, truncated, delimiter });
describe("delimited preview parser", () => {
it("preserves strings, BOM, duplicate/empty headings and quoted fields", () => {
expect(
parse(
'\uFEFF名字,,名字\r\n001,"a,b","say ""hi"""\r\n12345678901234567890,"多\n行", true ',
).rows,
).toEqual([
["名字", "", "名字"],
["001", "a,b", 'say "hi"'],
["12345678901234567890", "多\n行", " true "],
]);
});
it.each(["\n", "\r\n", "\r"])(
"preserves real empty records with %j",
(newline) => {
expect(parse(`a${newline}${newline}b${newline}`).rows).toEqual([
["a"],
[""],
["b"],
]);
expect(parse(newline).rows).toEqual([[""]]);
},
);
it("distinguishes an empty file from empty fields and missing fields", () => {
expect(parse("").rows).toEqual([]);
expect(parse("a,b\n,\nx")).toEqual({
rows: [["a", "b"], ["", ""], ["x"]],
columnCount: 2,
unevenRows: true,
limited: false,
});
});
it("uses the explicit TSV delimiter", () => {
expect(parse('a\tb\n"x\ty"\t1,000', false, "\t").rows).toEqual([
["a", "b"],
["x\ty", "1,000"],
]);
});
it("drops only incomplete terminal prefix records", () => {
expect(parse('a,b\n"multi\nline",tail', true).rows).toEqual([["a", "b"]]);
expect(parse('a,b\n"multi\n', true).rows).toEqual([["a", "b"]]);
expect(parse('a,b\n"multi\nline",tail\n', true).rows).toEqual([
["a", "b"],
["multi\nline", "tail"],
]);
expect(parse("abc", true).rows).toEqual([]);
expect(parse("\uFEFFa\nb\n", true).rows).toEqual([["a"], ["b"]]);
expect(parse("a\n\n", true).rows).toEqual([["a"], [""]]);
});
it("fails malformed quote syntax even in a prefix", () => {
expect(() => parse('a\n"unfinished')).toThrow();
expect(() => parse('a\n"bad"x\n', true)).toThrow();
expect(() => parse('a\n"bad"x\n')).toThrow();
});
it.each(["," as const, "\t" as const])(
"ignores embedded CRs in an incomplete quoted field for delimiter %j",
(delimiter) => {
const prefix = `ID${delimiter}Note\r\n001${delimiter}good\r\n"hello\rworld\rthird`;
const content = prefix + "x".repeat(1_048_576 - prefix.length);
expect(parse(content, true, delimiter)).toEqual({
rows: [
["ID", "Note"],
["001", "good"],
],
columnCount: 2,
limited: true,
unevenRows: false,
});
},
);
it.each(["\r\n", "\n", "\r"])(
"finds %j record boundaries after a quoted multiline first field",
(newline) => {
expect(
parse(
`"hello\rworld\nwith ""quotes""",Note${newline}001,good${newline}`,
).rows,
).toEqual([
['hello\rworld\nwith "quotes"', "Note"],
["001", "good"],
]);
},
);
it("does not treat a literal quote inside an unquoted field as an opening quote", () => {
expect(
parse('inch",Note\r\n001,good\r\n"unfinished\ra\rb', true).rows,
).toEqual([
['inch"', "Note"],
["001", "good"],
]);
});
it("bounds records and fields while reporting actual sample width", () => {
const result = parse(
Array.from({ length: 300 }, () =>
Array.from({ length: 60 }, (_, i) => `${i}`).join(","),
).join("\n"),
);
expect(result.rows).toHaveLength(202);
expect(result.rows.every((row) => row.length === 50)).toBe(true);
expect(result.columnCount).toBe(60);
expect(result.limited).toBe(true);
expect(result.unevenRows).toBe(false);
});
it("does not validate beyond the bounded record sample", () => {
expect(parse(`${"a\n".repeat(202)}"broken`).rows).toHaveLength(202);
});
});