import { deepStrictEqual } from 'node:assert/strict';
import { unlink } from 'node:fs/promises';
-import { csvStreamEqual, csvStreamEqualWritable, createCSVMockStream, createTempFile } from './utils.js';
+import { csvStreamEqualWritable, createCSVMockStream, createTempFile } from './utils.js';
import { arrayToCSVString, createCSVReadableStream, createCSVTransformStream, createCSVWritableStream } from '../src/core.js';
suite('arrayToCSVString', { concurrency: true }, () => {
});
suite('createCSVReadableStream', { concurrency: true }, () => {
- test('parses simple CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/simple.csv');
- await csvStreamEqual(stream, [
- ['column1', 'column2', 'column3'],
- ['abc', 'def', 'ghi'],
- ['123', '456', '789'],
- ['aaa', 'bbb', 'ccc']
- ]);
- });
- test('parses escaping CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/escaping.csv');
- await csvStreamEqual(stream, [
- ['name', 'value'],
- ['Three spaces', ' '],
- ['Three commas', ',,,'],
- ['Three newlines', '\r\n\r\n\r\n'],
- ['Three unescaped double quotes', 'a"""'],
- ['Three escaped double quotes', '"""'],
- ['Unescaped double quotes around delimiter 1 "', 'a'],
- ['Unescaped double quotes around delimiter 2 ""', 'a'],
- ['Unescaped double quotes around delimiter 3 """', 'a'],
- ['Unescaped double quotes around delimiter 4', ' "'],
- ['Unescaped double quotes around delimiter 5', ' ""'],
- ['Unescaped double quotes around delimiter 6', ' """'],
- ['Spaces before and after a value', ' abc '],
- ['Spaces before and after three unescaped double quotes', ' """ '],
- ['Spaces trailing escaped double quotes', 'abc '],
- ['Characters trailing escaped double quotes', 'abc def'],
- ['Unescaped double quotes trailing escaped double quotes', 'abc " def 123 """ 456'],
- ['Unescaped "double quotes" trailing escaped double quotes', 'abc """ def 123 """ 456'],
- ['Unicode test 1', 'ไฝ ๅฅฝ'],
- ['Unicode test 2', '๐๐๐'],
- ['Unicode test 3', '๐ดโโ ๏ธ๐จโ๐ฉโ๐งโ๐ฆ'],
- ['Mixed', ',\r\n",\r\n",\r\n"']
- ]);
- });
- test('parses escaping edges CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/escaping-edges.csv');
- await csvStreamEqual(stream, [
- [',,,', '"""'],
- ['"""', ',,,'],
- ['a"b""', 'a"b""'],
- ['a"b""', 'a"b""']
- ]);
- });
- test('parses sparse CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/sparse.csv');
- await csvStreamEqual(stream, [
- ['column1', 'column2', 'column3'],
- [''],
- ['', ''],
- ['', '', ''],
- ['', '', '', ''],
- ['', '', '', '', ''],
- ['aaa', 'bbb', 'ccc'],
- ['111', '222', '333', '444', '555', '666', '777', '888'],
- ['', 'hhh', 'iii'],
- ['ggg', '', 'iii'],
- ['ggg', 'hhh', ''],
- ['', 'hhh', ''],
- ['ggg', '', ''],
- ['', '', 'iii'],
- ['', '', ''],
- ['', '', ''],
- ['', '', ''],
- [''],
- ['']
- ]);
- });
- test('parses line feed end-of-line CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/lf-eol.csv');
- await csvStreamEqual(stream, [
- ['column1', 'column2'],
- ['a', 'b'],
- ['c', 'd'],
- ['1', '2'],
- ['3', '4']
- ]);
- });
- test('parses byte order mark CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/bom.csv');
- await csvStreamEqual(stream, [
- ['column1', 'column2'],
- ['ab', 'cd'],
- ['12', '34']
- ]);
- });
- test('parses single value (no commas or newlines) CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/single-value.csv');
- await csvStreamEqual(stream, [
- ['abc']
- ]);
- });
- test('parses single column (no commas) CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/single-column.csv');
- await csvStreamEqual(stream, [
- ['abc'],
- ['def'],
- ['ghi']
- ]);
- });
- test('parses single row (no newlines) CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/single-row.csv');
- await csvStreamEqual(stream, [
- ['abc', 'def', 'ghi']
- ]);
- });
- test('parses blank CSV', { concurrency: true }, async () => {
- const stream = createCSVReadableStream('./test/data/blank.csv');
- await csvStreamEqual(stream, [
- ['']
- ]);
- });
+ const vectors = [
+ {
+ name: 'parses simple CSV',
+ input: './test/data/simple.csv',
+ output: [
+ ['column1', 'column2', 'column3'],
+ ['abc', 'def', 'ghi'],
+ ['123', '456', '789'],
+ ['aaa', 'bbb', 'ccc']
+ ]
+ },
+ {
+ name: 'parses escaping CSV',
+ input: './test/data/escaping.csv',
+ output: [
+ ['name', 'value'],
+ ['Three spaces', ' '],
+ ['Three commas', ',,,'],
+ ['Three newlines', '\r\n\r\n\r\n'],
+ ['Three unescaped double quotes', 'a"""'],
+ ['Three escaped double quotes', '"""'],
+ ['Unescaped double quotes around delimiter 1 "', 'a'],
+ ['Unescaped double quotes around delimiter 2 ""', 'a'],
+ ['Unescaped double quotes around delimiter 3 """', 'a'],
+ ['Unescaped double quotes around delimiter 4', ' "'],
+ ['Unescaped double quotes around delimiter 5', ' ""'],
+ ['Unescaped double quotes around delimiter 6', ' """'],
+ ['Spaces before and after a value', ' abc '],
+ ['Spaces before and after three unescaped double quotes', ' """ '],
+ ['Spaces trailing escaped double quotes', 'abc '],
+ ['Characters trailing escaped double quotes', 'abc def'],
+ ['Unescaped double quotes trailing escaped double quotes', 'abc " def 123 """ 456'],
+ ['Unescaped "double quotes" trailing escaped double quotes', 'abc """ def 123 """ 456'],
+ ['Unicode test 1', 'ไฝ ๅฅฝ'],
+ ['Unicode test 2', '๐๐๐'],
+ ['Unicode test 3', '๐ดโโ ๏ธ๐จโ๐ฉโ๐งโ๐ฆ'],
+ ['Mixed', ',\r\n",\r\n",\r\n"']
+ ]
+ },
+ {
+ name: 'parses escaping edges CSV',
+ input: './test/data/escaping-edges.csv',
+ output: [
+ [',,,', '"""'],
+ ['"""', ',,,'],
+ ['a"b""', 'a"b""'],
+ ['a"b""', 'a"b""']
+ ]
+ },
+ {
+ name: 'parses sparse CSV',
+ input: './test/data/sparse.csv',
+ output: [
+ ['column1', 'column2', 'column3'],
+ [''],
+ ['', ''],
+ ['', '', ''],
+ ['', '', '', ''],
+ ['', '', '', '', ''],
+ ['aaa', 'bbb', 'ccc'],
+ ['111', '222', '333', '444', '555', '666', '777', '888'],
+ ['', 'hhh', 'iii'],
+ ['ggg', '', 'iii'],
+ ['ggg', 'hhh', ''],
+ ['', 'hhh', ''],
+ ['ggg', '', ''],
+ ['', '', 'iii'],
+ ['', '', ''],
+ ['', '', ''],
+ ['', '', ''],
+ [''],
+ ['']
+ ]
+ },
+ {
+ name: 'parses line feed end-of-line CSV',
+ input: './test/data/lf-eol.csv',
+ output: [
+ ['column1', 'column2'],
+ ['a', 'b'],
+ ['c', 'd'],
+ ['1', '2'],
+ ['3', '4']
+ ]
+ },
+ {
+ name: 'parses byte order mark CSV',
+ input: './test/data/bom.csv',
+ output: [
+ ['column1', 'column2'],
+ ['ab', 'cd'],
+ ['12', '34']
+ ]
+ },
+ {
+ name: 'parses single value (no commas or newlines) CSV',
+ input: './test/data/single-value.csv',
+ output: [
+ ['abc']
+ ]
+ },
+ {
+ name: 'parses single column (no commas) CSV',
+ input: './test/data/single-column.csv',
+ output: [
+ ['abc'],
+ ['def'],
+ ['ghi']
+ ]
+ },
+ {
+ name: 'parses single row (no newlines) CSV',
+ input: './test/data/single-row.csv',
+ output: [
+ ['abc', 'def', 'ghi']
+ ]
+ },
+ {
+ name: 'parses blank CSV',
+ input: './test/data/blank.csv',
+ output: [
+ ['']
+ ]
+ }
+ ];
+ for (const { name, input, output } of vectors) {
+ test(name, { concurrency: true }, async () => {
+ await createCSVReadableStream(input).pipeTo(csvStreamEqualWritable(output));
+ });
+ }
});
suite('createCSVTransformStream', { concurrency: true }, () => {
function timesTwo(row) {
return [Number(row[0]) * 2, Number(row[1]) * 2];
}
- const stream = createCSVMockStream([
+ await createCSVMockStream([
['columnA', 'columnB'],
['1', '1'],
['100', '100'],
['223423', '455947'],
['348553', '692708'],
['536368', '676147']
- ]).pipeThrough(createCSVTransformStream(timesTwo));
- await csvStreamEqual(stream, [
- ['columnA', 'columnB'],
- [2, 2],
- [200, 200],
- [446846, 911894],
- [697106, 1385416],
- [1072736, 1352294]
- ]);
+ ])
+ .pipeThrough(createCSVTransformStream(timesTwo))
+ .pipeTo(csvStreamEqualWritable([
+ ['columnA', 'columnB'],
+ [2, 2],
+ [200, 200],
+ [446846, 911894],
+ [697106, 1385416],
+ [1072736, 1352294]
+ ]));
});
test('can add new column', { concurrency: true }, async () => {
let firstRow = true;
}
return [...row, Number(row[0]) + Number(row[1])];
}
- const stream = createCSVMockStream([
+ await createCSVMockStream([
['columnA', 'columnB'],
['1', '1'],
['100', '100'],
['223423', '455947'],
['348553', '692708'],
['536368', '676147']
- ]).pipeThrough(createCSVTransformStream(sum, { includeHeaders: true }));
- await csvStreamEqual(stream, [
- ['columnA', 'columnB', 'sum'],
- ['1', '1', 2],
- ['100', '100', 200],
- ['223423', '455947', 679370],
- ['348553', '692708', 1041261],
- ['536368', '676147', 1212515]
- ]);
+ ])
+ .pipeThrough(createCSVTransformStream(sum, { includeHeaders: true }))
+ .pipeTo(csvStreamEqualWritable([
+ ['columnA', 'columnB', 'sum'],
+ ['1', '1', 2],
+ ['100', '100', 200],
+ ['223423', '455947', 679370],
+ ['348553', '692708', 1041261],
+ ['536368', '676147', 1212515]
+ ]));
});
});
suite('createCSVWritableStream', { concurrency: true }, () => {
- test('writes simple CSV', { concurrency: true }, async (t) => {
- const temp = await createTempFile();
- t.after(async () => await unlink(temp));
- const csv = [
- ['column1', 'column2', 'column3'],
- ['abc', 'def', 'ghi'],
- ['123', '456', '789'],
- ['aaa', 'bbb', 'ccc']
- ];
- await createCSVMockStream(csv).pipeTo(createCSVWritableStream(temp));
- await createCSVReadableStream(temp).pipeTo(csvStreamEqualWritable(csv));
- });
- test('writes escaping CSV', { concurrency: true }, async (t) => {
- const temp = await createTempFile();
- t.after(async () => await unlink(temp));
- const csv = [
- ['name', 'value'],
- ['Three spaces', ' '],
- ['Three commas', ',,,'],
- ['Three newlines', '\r\n\r\n\r\n'],
- ['Three unescaped double quotes', 'a"""'],
- ['Three escaped double quotes', '"""'],
- ['Unescaped double quotes around delimiter 1 "', 'a'],
- ['Unescaped double quotes around delimiter 2 ""', 'a'],
- ['Unescaped double quotes around delimiter 3 """', 'a'],
- ['Unescaped double quotes around delimiter 4', ' "'],
- ['Unescaped double quotes around delimiter 5', ' ""'],
- ['Unescaped double quotes around delimiter 6', ' """'],
- ['Spaces before and after a value', ' abc '],
- ['Spaces before and after three unescaped double quotes', ' """ '],
- ['Spaces trailing escaped double quotes', 'abc '],
- ['Characters trailing escaped double quotes', 'abc def'],
- ['Unescaped double quotes trailing escaped double quotes', 'abc " def 123 """ 456'],
- ['Unescaped "double quotes" trailing escaped double quotes', 'abc """ def 123 """ 456'],
- ['Unicode test 1', 'ไฝ ๅฅฝ'],
- ['Unicode test 2', '๐๐๐'],
- ['Unicode test 3', '๐ดโโ ๏ธ๐จโ๐ฉโ๐งโ๐ฆ'],
- ['Mixed', ',\r\n",\r\n",\r\n"']
- ];
- await createCSVMockStream(csv).pipeTo(createCSVWritableStream(temp));
- await createCSVReadableStream(temp).pipeTo(csvStreamEqualWritable(csv));
- });
- test('writes escaping edges CSV', { concurrency: true }, async (t) => {
- const temp = await createTempFile();
- t.after(async () => await unlink(temp));
- const csv = [
- [',,,', '"""'],
- ['"""', ',,,'],
- ['a"b""', 'a"b""'],
- ['a"b""', 'a"b""']
- ];
- await createCSVMockStream(csv).pipeTo(createCSVWritableStream(temp));
- await createCSVReadableStream(temp).pipeTo(csvStreamEqualWritable(csv));
- });
- test('writes sparse CSV', { concurrency: true }, async (t) => {
- const temp = await createTempFile();
- t.after(async () => await unlink(temp));
- const csv = [
- ['column1', 'column2', 'column3'],
- [''],
- ['', ''],
- ['', '', ''],
- ['', '', '', ''],
- ['', '', '', '', ''],
- ['aaa', 'bbb', 'ccc'],
- ['111', '222', '333', '444', '555', '666', '777', '888'],
- ['', 'hhh', 'iii'],
- ['ggg', '', 'iii'],
- ['ggg', 'hhh', ''],
- ['', 'hhh', ''],
- ['ggg', '', ''],
- ['', '', 'iii'],
- ['', '', ''],
- ['', '', ''],
- ['', '', ''],
- [''],
- ['']
- ];
- await createCSVMockStream(csv).pipeTo(createCSVWritableStream(temp));
- await createCSVReadableStream(temp).pipeTo(csvStreamEqualWritable(csv));
- });
- test('writes single value (no commas or newlines)', { concurrency: true }, async (t) => {
- const temp = await createTempFile();
- t.after(async () => await unlink(temp));
- const csv = [
- ['abc']
- ];
- await createCSVMockStream(csv).pipeTo(createCSVWritableStream(temp));
- await createCSVReadableStream(temp).pipeTo(csvStreamEqualWritable(csv));
- });
- test('writes single row (no newlines) CSV', { concurrency: true }, async (t) => {
- const temp = await createTempFile();
- t.after(async () => await unlink(temp));
- const csv = [
- ['abc', 'def', 'ghi']
- ];
- await createCSVMockStream(csv).pipeTo(createCSVWritableStream(temp));
- await createCSVReadableStream(temp).pipeTo(csvStreamEqualWritable(csv));
- });
- test('writes blank CSV', { concurrency: true }, async (t) => {
- const temp = await createTempFile();
- t.after(async () => await unlink(temp));
- const csv = [
- ['']
- ];
- await createCSVMockStream(csv).pipeTo(createCSVWritableStream(temp));
- await createCSVReadableStream(temp).pipeTo(csvStreamEqualWritable(csv));
- });
+ const vectors = [
+ {
+ name: 'writes simple CSV',
+ csv: [
+ ['column1', 'column2', 'column3'],
+ ['abc', 'def', 'ghi'],
+ ['123', '456', '789'],
+ ['aaa', 'bbb', 'ccc']
+ ]
+ },
+ {
+ name: 'writes escaping CSV',
+ csv: [
+ ['name', 'value'],
+ ['Three spaces', ' '],
+ ['Three commas', ',,,'],
+ ['Three newlines', '\r\n\r\n\r\n'],
+ ['Three unescaped double quotes', 'a"""'],
+ ['Three escaped double quotes', '"""'],
+ ['Unescaped double quotes around delimiter 1 "', 'a'],
+ ['Unescaped double quotes around delimiter 2 ""', 'a'],
+ ['Unescaped double quotes around delimiter 3 """', 'a'],
+ ['Unescaped double quotes around delimiter 4', ' "'],
+ ['Unescaped double quotes around delimiter 5', ' ""'],
+ ['Unescaped double quotes around delimiter 6', ' """'],
+ ['Spaces before and after a value', ' abc '],
+ ['Spaces before and after three unescaped double quotes', ' """ '],
+ ['Spaces trailing escaped double quotes', 'abc '],
+ ['Characters trailing escaped double quotes', 'abc def'],
+ ['Unescaped double quotes trailing escaped double quotes', 'abc " def 123 """ 456'],
+ ['Unescaped "double quotes" trailing escaped double quotes', 'abc """ def 123 """ 456'],
+ ['Unicode test 1', 'ไฝ ๅฅฝ'],
+ ['Unicode test 2', '๐๐๐'],
+ ['Unicode test 3', '๐ดโโ ๏ธ๐จโ๐ฉโ๐งโ๐ฆ'],
+ ['Mixed', ',\r\n",\r\n",\r\n"']
+ ]
+ },
+ {
+ name: 'writes escaping edges CSV',
+ csv: [
+ [',,,', '"""'],
+ ['"""', ',,,'],
+ ['a"b""', 'a"b""'],
+ ['a"b""', 'a"b""']
+ ]
+ },
+ {
+ name: 'writes sparse CSV',
+ csv: [
+ ['column1', 'column2', 'column3'],
+ [''],
+ ['', ''],
+ ['', '', ''],
+ ['', '', '', ''],
+ ['', '', '', '', ''],
+ ['aaa', 'bbb', 'ccc'],
+ ['111', '222', '333', '444', '555', '666', '777', '888'],
+ ['', 'hhh', 'iii'],
+ ['ggg', '', 'iii'],
+ ['ggg', 'hhh', ''],
+ ['', 'hhh', ''],
+ ['ggg', '', ''],
+ ['', '', 'iii'],
+ ['', '', ''],
+ ['', '', ''],
+ ['', '', ''],
+ [''],
+ ['']
+ ]
+ },
+ {
+ name: 'writes single value (no commas or newlines) CSV',
+ csv: [
+ ['abc']
+ ]
+ },
+ {
+ name: 'writes single column (no commas) CSV',
+ csv: [
+ ['abc'],
+ ['def'],
+ ['ghi']
+ ]
+ },
+ {
+ name: 'writes single row (no newlines) CSV',
+ csv: [
+ ['abc', 'def', 'ghi']
+ ]
+ },
+ {
+ name: 'writes blank CSV',
+ csv: [
+ ['']
+ ]
+ }
+ ];
+ for (const { name, csv } of vectors) {
+ test(name, { concurrency: true }, async (t) => {
+ const temp = await createTempFile();
+ t.after(async () => await unlink(temp));
+ await createCSVMockStream(csv).pipeTo(createCSVWritableStream(temp));
+ await createCSVReadableStream(temp).pipeTo(csvStreamEqualWritable(csv));
+ });
+ }
});
\ No newline at end of file