forked from Vero-protocol/vero-core-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcsv.test.ts
More file actions
107 lines (93 loc) · 3.09 KB
/
Copy pathcsv.test.ts
File metadata and controls
107 lines (93 loc) · 3.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { describe, it, expect } from "vitest";
import {
escapeCsvField,
toCsv,
timestampForFilename,
} from "./csv";
describe("escapeCsvField", () => {
it("returns empty string for null/undefined", () => {
expect(escapeCsvField(null)).toBe("");
expect(escapeCsvField(undefined)).toBe("");
});
it("passes through plain strings untouched", () => {
expect(escapeCsvField("hello")).toBe("hello");
expect(escapeCsvField("hello world")).toBe("hello world");
});
it("quotes fields containing commas", () => {
expect(escapeCsvField("a,b")).toBe('"a,b"');
});
it("quotes and doubles inner quotes", () => {
expect(escapeCsvField('she said "hi"')).toBe('"she said ""hi"""');
});
it("quotes fields containing newlines", () => {
expect(escapeCsvField("line1\nline2")).toBe('"line1\nline2"');
expect(escapeCsvField("line1\r\nline2")).toBe('"line1\r\nline2"');
});
it("coerces non-strings without quoting unless needed", () => {
expect(escapeCsvField(42)).toBe("42");
expect(escapeCsvField(true)).toBe("true");
expect(escapeCsvField(3.14)).toBe("3.14");
});
});
describe("toCsv", () => {
interface Row {
name: string;
count: number;
note: string;
}
const cols: import("./csv").CsvColumn<Row>[] = [
{ key: "name", header: "Name" },
{ key: "count", header: "Count" },
{
key: "note",
header: "Note",
// `v` is typed as the union of the row's string-keyed fields. Wrap
// in `String()` so the callable is compatible regardless of whether
// the underlying value is a number or a string.
format: (v) => String(v).toUpperCase(),
},
];
it("emits a header line followed by CRLF rows", () => {
const csv = toCsv<Row>([{ name: "alpha", count: 1, note: "ok" }], cols);
expect(csv).toBe("Name,Count,Note\r\nalpha,1,OK\r\n");
});
it("quotes special characters correctly", () => {
const csv = toCsv<Row>(
[{ name: 'he said "hi"', count: 0, note: "a,b\nc" }],
cols
);
// The Note column applies `v.toUpperCase()`, so the value is escaped
// and uppercased before being wrapped in quotes.
expect(csv).toBe(
'Name,Count,Note\r\n"he said ""hi""",0,"A,B\nC"\r\n'
);
});
it("handles an empty row list with just the header", () => {
expect(toCsv<Row>([], cols)).toBe("Name,Count,Note\r\n");
});
it("coerces missing values to empty strings", () => {
interface Partial {
a: string | null;
b: number | undefined;
}
const csv = toCsv<Partial>(
[{ a: null, b: undefined }],
[
{ key: "a", header: "A" },
{ key: "b", header: "B" },
]
);
expect(csv).toBe("A,B\r\n,\r\n");
});
});
describe("timestampForFilename", () => {
it("formats epoch ms as UTC YYYY-MM-DD_HH-mm-ss", () => {
// 2024-01-02 03:04:05 UTC
const ts = Date.UTC(2024, 0, 2, 3, 4, 5);
expect(timestampForFilename(ts)).toBe("2024-01-02_03-04-05");
});
it("zero-pads single-digit values", () => {
const ts = Date.UTC(2024, 8, 9, 1, 2, 3); // Sept 9
expect(timestampForFilename(ts)).toBe("2024-09-09_01-02-03");
});
});