mirror of
https://github.com/denoland/deno.git
synced 2025-09-28 13:14:48 +00:00
feat(std/encoding/csv): Add stringify functionality (#8408)
This commit is contained in:
parent
d40b0711a7
commit
ed11eb6687
4 changed files with 737 additions and 6 deletions
373
std/encoding/csv_stringify_test.ts
Normal file
373
std/encoding/csv_stringify_test.ts
Normal file
|
@ -0,0 +1,373 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { assertEquals, assertThrowsAsync } from "../testing/asserts.ts";
|
||||
|
||||
import {
|
||||
Column,
|
||||
DataItem,
|
||||
NEWLINE,
|
||||
stringify,
|
||||
StringifyError,
|
||||
StringifyOptions,
|
||||
} from "./csv_stringify.ts";
|
||||
|
||||
type StringifyTestCaseBase = {
|
||||
columns: Column[];
|
||||
data: DataItem[];
|
||||
name: string;
|
||||
options?: StringifyOptions;
|
||||
};
|
||||
|
||||
type StringifyTestCaseError = StringifyTestCaseBase & {
|
||||
errorMessage?: string;
|
||||
// deno-lint-ignore no-explicit-any
|
||||
throwsError: new (...args: any[]) => Error;
|
||||
};
|
||||
|
||||
type StringifyTestCase = StringifyTestCaseBase & { expected: string };
|
||||
|
||||
const stringifyTestCases: (StringifyTestCase | StringifyTestCaseError)[] = [
|
||||
{
|
||||
columns: ["a"],
|
||||
data: [["foo"], ["bar"]],
|
||||
errorMessage: 'Property accessor is not of type "number"',
|
||||
name: "[CSV_stringify] Access array index using string",
|
||||
throwsError: StringifyError,
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [["foo"], ["bar"]],
|
||||
errorMessage: [
|
||||
"Separator cannot include the following strings:",
|
||||
' - U+0022: Quotation mark (")',
|
||||
" - U+000D U+000A: Carriage Return + Line Feed (\\r\\n)",
|
||||
].join("\n"),
|
||||
name: "[CSV_stringify] Double quote in separator",
|
||||
options: { separator: '"' },
|
||||
throwsError: StringifyError,
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [["foo"], ["bar"]],
|
||||
errorMessage: [
|
||||
"Separator cannot include the following strings:",
|
||||
' - U+0022: Quotation mark (")',
|
||||
" - U+000D U+000A: Carriage Return + Line Feed (\\r\\n)",
|
||||
].join("\n"),
|
||||
name: "[CSV_stringify] CRLF in separator",
|
||||
options: { separator: "\r\n" },
|
||||
throwsError: StringifyError,
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fn: (obj) => obj.toUpperCase(),
|
||||
prop: "msg",
|
||||
},
|
||||
],
|
||||
data: [{ msg: { value: "foo" } }, { msg: { value: "bar" } }],
|
||||
name: "[CSV_stringify] Transform function",
|
||||
throwsError: TypeError,
|
||||
},
|
||||
{
|
||||
columns: [],
|
||||
data: [],
|
||||
expected: NEWLINE,
|
||||
name: "[CSV_stringify] No data, no columns",
|
||||
},
|
||||
{
|
||||
columns: [],
|
||||
data: [],
|
||||
expected: ``,
|
||||
name: "[CSV_stringify] No data, no columns, no headers",
|
||||
options: { headers: false },
|
||||
},
|
||||
{
|
||||
columns: ["a"],
|
||||
data: [],
|
||||
expected: `a${NEWLINE}`,
|
||||
name: "[CSV_stringify] No data, columns",
|
||||
},
|
||||
{
|
||||
columns: ["a"],
|
||||
data: [],
|
||||
expected: ``,
|
||||
name: "[CSV_stringify] No data, columns, no headers",
|
||||
options: { headers: false },
|
||||
},
|
||||
{
|
||||
columns: [],
|
||||
data: [{ a: 1 }, { a: 2 }],
|
||||
expected: `${NEWLINE}${NEWLINE}${NEWLINE}`,
|
||||
name: "[CSV_stringify] Data, no columns",
|
||||
},
|
||||
{
|
||||
columns: [0, 1],
|
||||
data: [["foo", "bar"], ["baz", "qux"]],
|
||||
expected: `0\r1${NEWLINE}foo\rbar${NEWLINE}baz\rqux${NEWLINE}`,
|
||||
name: "[CSV_stringify] Separator: CR",
|
||||
options: { separator: "\r" },
|
||||
},
|
||||
{
|
||||
columns: [0, 1],
|
||||
data: [["foo", "bar"], ["baz", "qux"]],
|
||||
expected: `0\n1${NEWLINE}foo\nbar${NEWLINE}baz\nqux${NEWLINE}`,
|
||||
name: "[CSV_stringify] Separator: LF",
|
||||
options: { separator: "\n" },
|
||||
},
|
||||
{
|
||||
columns: [1],
|
||||
data: [{ 1: 1 }, { 1: 2 }],
|
||||
expected: `1${NEWLINE}1${NEWLINE}2${NEWLINE}`,
|
||||
name: "[CSV_stringify] Column: number accessor, Data: object",
|
||||
},
|
||||
{
|
||||
columns: [{ header: "Value", prop: "value" }],
|
||||
data: [{ value: "foo" }, { value: "bar" }],
|
||||
expected: `foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Explicit header value, no headers",
|
||||
options: { headers: false },
|
||||
},
|
||||
{
|
||||
columns: [1],
|
||||
data: [["key", "foo"], ["key", "bar"]],
|
||||
expected: `1${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Column: number accessor, Data: array",
|
||||
},
|
||||
{
|
||||
columns: [[1]],
|
||||
data: [{ 1: 1 }, { 1: 2 }],
|
||||
expected: `1${NEWLINE}1${NEWLINE}2${NEWLINE}`,
|
||||
name: "[CSV_stringify] Column: array number accessor, Data: object",
|
||||
},
|
||||
{
|
||||
columns: [[1]],
|
||||
data: [["key", "foo"], ["key", "bar"]],
|
||||
expected: `1${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Column: array number accessor, Data: array",
|
||||
},
|
||||
{
|
||||
columns: [[1, 1]],
|
||||
data: [["key", ["key", "foo"]], ["key", ["key", "bar"]]],
|
||||
expected: `1${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Column: array number accessor, Data: array",
|
||||
},
|
||||
{
|
||||
columns: ["value"],
|
||||
data: [{ value: "foo" }, { value: "bar" }],
|
||||
expected: `value${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Column: string accessor, Data: object",
|
||||
},
|
||||
{
|
||||
columns: [["value"]],
|
||||
data: [{ value: "foo" }, { value: "bar" }],
|
||||
expected: `value${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Column: array string accessor, Data: object",
|
||||
},
|
||||
{
|
||||
columns: [["msg", "value"]],
|
||||
data: [{ msg: { value: "foo" } }, { msg: { value: "bar" } }],
|
||||
expected: `value${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Column: array string accessor, Data: object",
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
header: "Value",
|
||||
prop: ["msg", "value"],
|
||||
},
|
||||
],
|
||||
data: [{ msg: { value: "foo" } }, { msg: { value: "bar" } }],
|
||||
expected: `Value${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Explicit header",
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fn: (str: string) => str.toUpperCase(),
|
||||
prop: ["msg", "value"],
|
||||
},
|
||||
],
|
||||
data: [{ msg: { value: "foo" } }, { msg: { value: "bar" } }],
|
||||
expected: `value${NEWLINE}FOO${NEWLINE}BAR${NEWLINE}`,
|
||||
name: "[CSV_stringify] Transform function 1",
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fn: (str: string) => Promise.resolve(str.toUpperCase()),
|
||||
prop: ["msg", "value"],
|
||||
},
|
||||
],
|
||||
data: [{ msg: { value: "foo" } }, { msg: { value: "bar" } }],
|
||||
expected: `value${NEWLINE}FOO${NEWLINE}BAR${NEWLINE}`,
|
||||
name: "[CSV_stringify] Transform function 1 async",
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fn: (obj: { value: string }) => obj.value,
|
||||
prop: "msg",
|
||||
},
|
||||
],
|
||||
data: [{ msg: { value: "foo" } }, { msg: { value: "bar" } }],
|
||||
expected: `msg${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Transform function 2",
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fn: (obj: { value: string }) => obj.value,
|
||||
header: "Value",
|
||||
prop: "msg",
|
||||
},
|
||||
],
|
||||
data: [{ msg: { value: "foo" } }, { msg: { value: "bar" } }],
|
||||
expected: `Value${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Transform function 2, explicit header",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[{ value: "foo" }], [{ value: "bar" }]],
|
||||
expected:
|
||||
`0${NEWLINE}"{""value"":""foo""}"${NEWLINE}"{""value"":""bar""}"${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: object",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [
|
||||
[[{ value: "foo" }, { value: "bar" }]],
|
||||
[[{ value: "baz" }, { value: "qux" }]],
|
||||
],
|
||||
expected:
|
||||
`0${NEWLINE}"[{""value"":""foo""},{""value"":""bar""}]"${NEWLINE}"[{""value"":""baz""},{""value"":""qux""}]"${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: arary of objects",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[["foo", "bar"]], [["baz", "qux"]]],
|
||||
expected:
|
||||
`0${NEWLINE}"[""foo"",""bar""]"${NEWLINE}"[""baz"",""qux""]"${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: array",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[["foo", "bar"]], [["baz", "qux"]]],
|
||||
expected:
|
||||
`0${NEWLINE}"[""foo"",""bar""]"${NEWLINE}"[""baz"",""qux""]"${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: array, separator: tab",
|
||||
options: { separator: "\t" },
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[], []],
|
||||
expected: `0${NEWLINE}${NEWLINE}${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: undefined",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[null], [null]],
|
||||
expected: `0${NEWLINE}${NEWLINE}${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: null",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[0xa], [0xb]],
|
||||
expected: `0${NEWLINE}10${NEWLINE}11${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: hex number",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[BigInt("1")], [BigInt("2")]],
|
||||
expected: `0${NEWLINE}1${NEWLINE}2${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: BigInt",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[true], [false]],
|
||||
expected: `0${NEWLINE}true${NEWLINE}false${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: boolean",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [["foo"], ["bar"]],
|
||||
expected: `0${NEWLINE}foo${NEWLINE}bar${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: string",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[Symbol("foo")], [Symbol("bar")]],
|
||||
expected: `0${NEWLINE}Symbol(foo)${NEWLINE}Symbol(bar)${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: symbol",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [[(n: number) => n]],
|
||||
expected: `0${NEWLINE}(n) => n${NEWLINE}`,
|
||||
name: "[CSV_stringify] Targeted value: function",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [['foo"']],
|
||||
expected: `0${NEWLINE}"foo"""${NEWLINE}`,
|
||||
name: "[CSV_stringify] Value with double quote",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [["foo\r\n"]],
|
||||
expected: `0${NEWLINE}"foo\r\n"${NEWLINE}`,
|
||||
name: "[CSV_stringify] Value with CRLF",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [["foo\r"]],
|
||||
expected: `0${NEWLINE}foo\r${NEWLINE}`,
|
||||
name: "[CSV_stringify] Value with CR",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [["foo\n"]],
|
||||
expected: `0${NEWLINE}foo\n${NEWLINE}`,
|
||||
name: "[CSV_stringify] Value with LF",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [["foo,"]],
|
||||
expected: `0${NEWLINE}"foo,"${NEWLINE}`,
|
||||
name: "[CSV_stringify] Value with comma",
|
||||
},
|
||||
{
|
||||
columns: [0],
|
||||
data: [["foo,"]],
|
||||
expected: `0${NEWLINE}foo,${NEWLINE}`,
|
||||
name: "[CSV_stringify] Value with comma, tab separator",
|
||||
options: { separator: "\t" },
|
||||
},
|
||||
];
|
||||
|
||||
for (const tc of stringifyTestCases) {
|
||||
if ((tc as StringifyTestCaseError).throwsError) {
|
||||
const t = tc as StringifyTestCaseError;
|
||||
Deno.test({
|
||||
async fn() {
|
||||
await assertThrowsAsync(
|
||||
async () => {
|
||||
await stringify(t.data, t.columns, t.options);
|
||||
},
|
||||
t.throwsError,
|
||||
t.errorMessage,
|
||||
);
|
||||
},
|
||||
name: t.name,
|
||||
});
|
||||
} else {
|
||||
const t = tc as StringifyTestCase;
|
||||
Deno.test({
|
||||
async fn() {
|
||||
const actual = await stringify(t.data, t.columns, t.options);
|
||||
assertEquals(actual, t.expected);
|
||||
},
|
||||
name: t.name,
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue