Files
2026-07-13 12:25:07 +08:00

2176 lines
76 KiB
JavaScript

// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
import perspective from "./perspective_client";
import moment from "moment";
import * as arrows from "./test_arrows.js";
const data = [
{ x: 1, y: "a", z: true },
{ x: 2, y: "b", z: false },
{ x: 3, y: "c", z: true },
{ x: 4, y: "d", z: false },
];
const col_data = {
x: [1, 2, 3, 4],
y: ["a", "b", "c", "d"],
z: [true, false, true, false],
};
const meta = {
x: "integer",
y: "string",
z: "boolean",
};
const data_3 = [
{ w: 1.5, x: 1, y: "a", z: true },
{ w: 2.5, x: 2, y: "b", z: false },
{ w: 3.5, x: 3, y: "c", z: true },
{ w: 4.5, x: 4, y: "d", z: false },
];
const data_7 = {
w: [1.5, 2.5, 3.5, 4.5],
x: [1, 2, 3, 4],
y: ["a", "b", "c", "d"],
z: [true, false, true, false],
};
const int_in_string = [{ a: "1" }, { a: "2" }, { a: "12345" }];
const float_in_string = [{ a: "1.5" }, { a: "2.5" }, { a: "12345.56789" }];
const meta_3 = {
w: "float",
x: "integer",
y: "string",
z: "boolean",
};
const arrow_result = [
{
f32: 1.5,
f64: 1.5,
i64: 1,
i32: 1,
i16: 1,
i8: 1,
bool: true,
char: "a",
dict: "a",
"datetime(ms)": +new Date("2018-01-25"),
"datetime(us)": +new Date("2018-01-25"),
"datetime(ns)": +new Date("2018-01-25"),
},
{
f32: 2.5,
f64: 2.5,
i64: 2,
i32: 2,
i16: 2,
i8: 2,
bool: false,
char: "b",
dict: "b",
"datetime(ms)": +new Date("2018-01-26"),
"datetime(us)": +new Date("2018-01-26"),
"datetime(ns)": +new Date("2018-01-26"),
},
{
f32: 3.5,
f64: 3.5,
i64: 3,
i32: 3,
i16: 3,
i8: 3,
bool: true,
char: "c",
dict: "c",
"datetime(ms)": +new Date("2018-01-27"),
"datetime(us)": +new Date("2018-01-27"),
"datetime(ns)": +new Date("2018-01-27"),
},
{
f32: 4.5,
f64: 4.5,
i64: 4,
i32: 4,
i16: 4,
i8: 4,
bool: false,
char: "",
dict: "",
"datetime(ms)": +new Date("2018-01-28"),
"datetime(us)": +new Date("2018-01-28"),
"datetime(ns)": +new Date("2018-01-28"),
},
{
f32: null,
f64: null,
i64: null,
i32: null,
i16: null,
i8: null,
bool: null,
char: null,
dict: null,
"datetime(ms)": null,
"datetime(us)": null,
"datetime(ns)": null,
},
];
let arrow_lists_data = {
uint32_arr: ["[1,2,3,4]", "[5]", "[43215]", "[]"],
string_arr: [
`["#1","second","third","fourth"]`,
`["fifth"]`,
`[]`,
`["Last!"]`,
],
bool_arr: [
"[true,false,false,true,true,false,false,true,true]",
"[]",
"[true]",
"[false]",
],
float_arr: [
"[]",
"[3.14,3.141592653589793,6.283185307179586,1.4142135623730951]",
"[12.00001]",
"[100000000.0,100000000.0,100000000.0,100000000.0]",
],
};
let arrow_date_data = {
"jan-2019": [
"2019-01-01",
"2019-01-02",
"2019-01-03",
"2019-01-04",
"2019-01-05",
"2019-01-06",
"2019-01-07",
"2019-01-08",
"2019-01-09",
"2019-01-10",
"2019-01-11",
"2019-01-12",
"2019-01-13",
"2019-01-14",
"2019-01-15",
"2019-01-16",
"2019-01-17",
"2019-01-18",
"2019-01-19",
"2019-01-20",
"2019-01-21",
"2019-01-22",
"2019-01-23",
"2019-01-24",
"2019-01-25",
"2019-01-26",
"2019-01-27",
"2019-01-28",
"2019-01-29",
"2019-01-30",
"2019-01-31",
],
"feb-2020": [
"2020-02-01",
"2020-02-02",
"2020-02-03",
"2020-02-04",
"2020-02-05",
"2020-02-06",
"2020-02-07",
"2020-02-08",
"2020-02-09",
"2020-02-10",
"2020-02-11",
"2020-02-12",
"2020-02-13",
"2020-02-14",
"2020-02-15",
"2020-02-16",
"2020-02-17",
"2020-02-18",
"2020-02-19",
"2020-02-20",
"2020-02-21",
"2020-02-22",
"2020-02-23",
"2020-02-24",
"2020-02-25",
"2020-02-26",
"2020-02-27",
"2020-02-28",
"2020-02-29",
null,
null,
],
"mar-2019": [
"2019-03-01",
"2019-03-02",
"2019-03-03",
"2019-03-04",
"2019-03-05",
"2019-03-06",
"2019-03-07",
"2019-03-08",
"2019-03-09",
"2019-03-10",
"2019-03-11",
"2019-03-12",
"2019-03-13",
"2019-03-14",
"2019-03-15",
"2019-03-16",
"2019-03-17",
"2019-03-18",
"2019-03-19",
"2019-03-20",
"2019-03-21",
"2019-03-22",
"2019-03-23",
"2019-03-24",
"2019-03-25",
"2019-03-26",
"2019-03-27",
"2019-03-28",
"2019-03-29",
"2019-03-30",
"2019-03-31",
],
"apr-2020": [
"2020-04-01",
"2020-04-02",
"2020-04-03",
"2020-04-04",
"2020-04-05",
"2020-04-06",
"2020-04-07",
"2020-04-08",
"2020-04-09",
"2020-04-10",
"2020-04-11",
"2020-04-12",
"2020-04-13",
"2020-04-14",
"2020-04-15",
"2020-04-16",
"2020-04-17",
"2020-04-18",
"2020-04-19",
"2020-04-20",
"2020-04-21",
"2020-04-22",
"2020-04-23",
"2020-04-24",
"2020-04-25",
"2020-04-26",
"2020-04-27",
"2020-04-28",
"2020-04-29",
"2020-04-30",
null,
],
};
// transform arrow strings into timestamps
for (const k in arrow_date_data) {
arrow_date_data[k] = arrow_date_data[k].map((d) =>
d ? new Date(d).getTime() : null,
);
}
const dt = () => {
let dt = new Date();
dt.setHours(4);
dt.setMinutes(12);
return dt;
};
const data_4 = [{ v: dt() }];
const data_5 = [{ v: "11-09-2017" }];
const meta_4 = { v: "datetime" };
const csv = "x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false";
const data_6 = [{ x: "š" }];
const int_float_data = [
{ int: 1, float: 2.25 },
{ int: 2, float: 3.5 },
{ int: 3, float: 4.75 },
{ int: 4, float: 5.25 },
];
const int_float_string_data = [
{ int: 1, float: 2.25, string: "a" },
{ int: 2, float: 3.5, string: "b" },
{ int: 3, float: 4.75, string: "c" },
{ int: 4, float: 5.25, string: "d" },
];
// out of order to make sure we can read out of perspective in insertion
// order, not pkey order.
const all_types_data = [
{
int: 4,
float: 5.25,
string: "d",
date: new Date(2020, 3, 15),
datetime: new Date(2020, 0, 15, 23, 30),
boolean: true,
},
{
int: 3,
float: 4.75,
string: "c",
date: new Date(2020, 2, 15),
datetime: new Date(2020, 0, 15, 18, 30),
boolean: false,
},
{
int: 2,
float: 3.5,
string: "b",
date: new Date(2020, 1, 15),
datetime: new Date(2020, 0, 15, 12, 30),
boolean: true,
},
{
int: 1,
float: 2.25,
string: "a",
date: new Date(2020, 0, 15),
datetime: new Date(2020, 0, 15, 6, 30),
boolean: false,
},
// values above should be replaced with these values below, due to
// indexing
{
int: 4,
float: 5.25,
string: "d",
date: new Date(2020, 3, 15),
datetime: new Date(2020, 0, 15, 23, 30),
boolean: true,
},
{
int: 3,
float: 4.75,
string: "c",
date: new Date(2020, 2, 15),
datetime: new Date(2020, 0, 15, 18, 30),
boolean: false,
},
{
int: 2,
float: 3.5,
string: "b",
date: new Date(2020, 1, 15),
datetime: new Date(2020, 0, 15, 12, 30),
boolean: true,
},
{
int: 1,
float: 2.25,
string: "a",
date: new Date(2020, 0, 15),
datetime: new Date(2020, 0, 15, 6, 30),
boolean: false,
},
];
const datetime_data = [
{ datetime: new Date(), int: 1 },
{ datetime: new Date(), int: 1 },
{ datetime: new Date(), int: 2 },
{ datetime: new Date(), int: 2 },
];
// utility for checking typed arrays
function validate_typed_array(typed_array, column_data) {
let is_valid = true;
for (let i = 0; i < typed_array.length; i++) {
if (column_data[i] !== null) {
if (typed_array[i] !== column_data[i]) {
is_valid = false;
break;
}
}
}
return is_valid;
}
((perspective) => {
// test.describe("Execute", function () {
// test.skip("serialized functions in a worker", async function () {
// var table = await perspective.table({
// x: "integer",
// y: "string",
// z: "boolean",
// });
// table.execute((t) => {
// t.update([
// { x: 1, y: "a", z: true },
// { x: 2, y: "b", z: false },
// { x: 3, y: "c", z: true },
// { x: 4, y: "d", z: false },
// ]);
// });
// let view = await table.view({});
// let js = await view.to_json();
// expect(js).toEqual([
// { x: 1, y: "a", z: true },
// { x: 2, y: "b", z: false },
// { x: 3, y: "c", z: true },
// { x: 4, y: "d", z: false },
// ]);
// view.delete();
// table.delete();
// });
// });
test.describe("Destructors", function () {
test("calls delete() on table with no views", async function () {
let table = await perspective.table(data);
await table.delete();
expect(true).toEqual(true);
});
test("calls delete on a view, then a table", async function () {
var table = await perspective.table(data);
var view = await table.view();
await view.delete();
await table.delete();
expect(true).toEqual(true);
});
test("calls delete on multiple views, then a table", async function () {
var table = await perspective.table(data);
var view1 = await table.view();
var view2 = await table.view();
await view1.delete();
await view2.delete();
await table.delete();
expect(true).toEqual(true);
});
});
test.describe("Schema", function () {
test("0-sided view", async function () {
const table = await perspective.table(int_float_string_data);
const view = await table.view();
const schema = await view.schema();
expect(schema).toEqual({
int: "integer",
float: "float",
string: "string",
});
view.delete();
table.delete();
});
test("0-sided view with columns selection", async function () {
const table = await perspective.table(int_float_string_data);
const view = await table.view({ columns: ["float", "string"] });
const schema = await view.schema();
expect(schema).toEqual({ float: "float", string: "string" });
view.delete();
table.delete();
});
});
test.describe.skip("Typed Arrays", function () {
test("Respects start/end rows", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view();
const result = await view.col_to_js_typed_array("int", {
start_row: 1,
end_row: 2,
});
expect(result[0].byteLength).toEqual(4);
view.delete();
table.delete();
});
test("Int, 0-sided view", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view();
const result = await view.col_to_js_typed_array("int");
expect(result[0].byteLength).toEqual(16);
view.delete();
table.delete();
});
test("Float, 0-sided view", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view();
const result = await view.col_to_js_typed_array("float");
expect(result[0].byteLength).toEqual(32);
view.delete();
table.delete();
});
test("Datetime, 0-sided view", async function () {
var table = await perspective.table(datetime_data);
var view = await table.view();
var schema = await view.schema();
expect(schema).toEqual({ datetime: "datetime", int: "integer" });
const result = await view.col_to_js_typed_array("datetime");
expect(result[0].byteLength).toEqual(32);
view.delete();
table.delete();
});
test("Int, 1-sided view", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view({
group_by: ["int"],
columns: ["int", "float"],
});
const result = await view.col_to_js_typed_array("int");
// should include aggregate row
expect(result[0].byteLength).toEqual(20);
view.delete();
table.delete();
});
test("Float, 1-sided view", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view({
group_by: ["int"],
columns: ["int", "float"],
});
const result = await view.col_to_js_typed_array("float");
expect(result[0].byteLength).toEqual(40);
view.delete();
table.delete();
});
test("Datetime, 1-sided view", async function () {
var table = await perspective.table(datetime_data);
var view = await table.view({
group_by: ["int"],
columns: ["datetime"],
aggregates: { datetime: "high" },
});
const result = await view.col_to_js_typed_array("datetime");
expect(result[0].byteLength).toEqual(24);
view.delete();
table.delete();
});
test("Int, 2-sided view with group by", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view({
split_by: ["float"],
group_by: ["int"],
columns: ["int", "float"],
});
const result = await view.col_to_js_typed_array("3.5|int");
expect(result[0].byteLength).toEqual(20);
view.delete();
table.delete();
});
test("Float, 2-sided view with group by", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view({
split_by: ["float"],
group_by: ["int"],
columns: ["int", "float"],
});
const result = await view.col_to_js_typed_array("3.5|float");
expect(result[0].byteLength).toEqual(40);
view.delete();
table.delete();
});
test("Int, 2-sided view, no group by", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view({ split_by: ["float"] });
const result = await view.col_to_js_typed_array("3.5|int");
// bytelength should not include the aggregate row
expect(result[0].byteLength).toEqual(16);
view.delete();
table.delete();
});
test("Float, 2-sided view, no group by", async function () {
var table = await perspective.table(int_float_data);
var view = await table.view({ split_by: ["float"] });
const result = await view.col_to_js_typed_array("3.5|float");
expect(result[0].byteLength).toEqual(32);
view.delete();
table.delete();
});
test("Symmetric output with to_columns, 0-sided", async function () {
let table = await perspective.table(int_float_data);
let view = await table.view();
let cols = await view.to_columns();
for (let col in cols) {
let ta = await view.col_to_js_typed_array(col);
let column = cols[col];
if (ta !== undefined && column !== undefined) {
expect(ta[0].length).toEqual(cols[col].length);
expect(validate_typed_array(ta[0], cols[col])).toEqual(
true,
);
}
}
view.delete();
table.delete();
});
test("Symmetric output with to_columns, 1-sided", async function () {
let table = await perspective.table(int_float_string_data);
let view = await table.view({
group_by: ["int"],
columns: ["int", "float"],
});
let cols = await view.to_columns();
for (let col in cols) {
let ta = await view.col_to_js_typed_array(col);
let column = cols[col];
if (ta !== undefined && column !== undefined) {
expect(ta[0].length).toEqual(cols[col].length);
expect(validate_typed_array(ta[0], cols[col])).toEqual(
true,
);
}
}
view.delete();
table.delete();
});
});
test.describe("Other `View`s", function () {
test("Construct a table from another view", async function () {
const table = await perspective.table(int_float_string_data);
const view = await table.view();
const table2 = await perspective.table(view);
const view2 = await table2.view();
const data = await view2.to_json();
expect(data).toEqual(int_float_string_data);
view2.delete();
table2.delete();
view.delete();
table.delete();
});
});
test.describe("Errors", function () {
test("Table constructor should throw an exception and reject promise", async function () {
expect.assertions(1);
perspective.table([1, 2, 3]).catch((error) => {
expect(error.message).toContain(
"Abort(): Cannot determine data types without column names!\n",
);
});
});
test("View constructor should throw an exception and reject promise", async function () {
expect.assertions(1);
const table = await perspective.table(int_float_string_data);
table
.view({
group_by: ["abcd"],
})
.catch((error) => {
expect(error.message).toContain(
"Abort(): Invalid column 'abcd' found in View group_by.\n",
);
table.delete();
});
});
test("Table constructor should throw an exception on await", async function () {
expect.assertions(1);
try {
await perspective.table([1, 2, 3]);
} catch (error) {
expect(error.message).toContain(
"Abort(): Cannot determine data types without column names!\n",
);
}
});
test("View constructor should throw an exception on await", async function () {
expect.assertions(1);
const table = await perspective.table(int_float_string_data);
try {
await table.view({
group_by: ["abcd"],
});
} catch (error) {
expect(error.message).toContain(
"Abort(): Invalid column 'abcd' found in View group_by.\n",
);
table.delete();
}
});
test("Table constructor pads short trailing columns with null", async function () {
const table = await perspective.table({
overflow: [1, 2, 3, 4, 5, 6, 7, 8, 9],
short: [1],
});
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual({
overflow: [1, 2, 3, 4, 5, 6, 7, 8, 9],
short: [1, null, null, null, null, null, null, null, null],
});
await view.delete();
await table.delete();
});
test("Table constructor pads short leading columns with null", async function () {
const table = await perspective.table({
short: [1],
overflow: [1, 2, 3, 4, 5, 6, 7, 8, 9],
});
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual({
short: [1, null, null, null, null, null, null, null, null],
overflow: [1, 2, 3, 4, 5, 6, 7, 8, 9],
});
await view.delete();
await table.delete();
});
test("Table constructor handles columns of equal length", async function () {
const table = await perspective.table({
a: [1, 2, 3],
b: ["x", "y", "z"],
});
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual({
a: [1, 2, 3],
b: ["x", "y", "z"],
});
await view.delete();
await table.delete();
});
test("Table update pads short columns with null", async function () {
const table = await perspective.table({
a: "integer",
b: "integer",
});
await table.update({
a: [1, 2, 3, 4, 5],
b: [1],
});
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual({
a: [1, 2, 3, 4, 5],
b: [1, null, null, null, null],
});
// The table must remain usable for subsequent updates.
await table.update({ a: [10], b: [20] });
const result2 = await view.to_columns();
expect(result2).toEqual({
a: [1, 2, 3, 4, 5, 10],
b: [1, null, null, null, null, 20],
});
await view.delete();
await table.delete();
});
});
test.describe("Formatters", function () {
test("Serializes a simple view to CSV", async function () {
var table = await perspective.table(data);
var view = await table.view({});
var answer = `"x","y","z"\n1,"a",true\n2,"b",false\n3,"c",true\n4,"d",false\n`;
let result = await view.to_csv();
expect(result).toEqual(answer);
view.delete();
table.delete();
});
test("Serializes 1 sided view to CSV", async function () {
var table = await perspective.table(data);
var view = await table.view({
group_by: ["z"],
columns: ["x"],
});
var answer = `"z (Group by 1)","x"\n,10\nfalse,6\ntrue,4\n`;
let result = await view.to_csv();
expect(result).toEqual(answer);
view.delete();
table.delete();
});
test("Serializes a 2 sided view to CSV", async function () {
var table = await perspective.table(data);
var view = await table.view({
group_by: ["z"],
split_by: ["y"],
columns: ["x"],
});
var answer = `"z (Group by 1)","a|x","b|x","c|x","d|x"\n,1,2,3,4\nfalse,,2,,4\ntrue,1,,3,\n`;
let result = await view.to_csv();
expect(result).toEqual(answer);
view.delete();
table.delete();
});
test("Serializes a simple view to column-oriented JSON", async function () {
var table = await perspective.table(data_3);
var view = await table.view({});
let result = await view.to_columns();
expect(result).toEqual(data_7);
view.delete();
table.delete();
});
});
test.describe("CSV parsing", function () {
test("Does not lose leading 0's when a CSV column is declared as a string", async function () {
let table = await perspective.table({ x: "string", y: "integer" });
await table.update("x,y\n000123,000123");
let view = await table.view();
let result = await view.to_json();
expect(result).toEqual([{ x: "000123", y: 123 }]);
view.delete();
table.delete();
});
// Added
test("Does not lose leading 0's when a CSV column is declared as a string (async)", async function () {
let table = await perspective.table({ x: "string", y: "integer" });
await table.update("x,y\n000123,000123");
let view = await table.view();
let result = await view.to_json();
expect(result).toEqual([{ x: "000123", y: 123 }]);
view.delete();
table.delete();
});
test("Handles duplicate column names with different types", async function () {
const csv = "A,A\ntest,1";
let table = await perspective.table(csv);
let view = await table.view();
let csv2 = await view.to_json();
expect(csv2).toEqual([{ A: "test", "A*": 1 }]);
view.delete();
table.delete();
});
test("Handles duplicate column names with rename collisions", async function () {
const csv = "A,A,A*\ntest,1,2";
let table = await perspective.table(csv);
let view = await table.view();
let csv2 = await view.to_json();
expect(csv2).toEqual([{ A: "test", "A*": 1, "A**": 2 }]);
view.delete();
table.delete();
});
test("Handles strings with quotation characters and commas", async function () {
let table = await perspective.table({ x: "string", y: "integer" });
await table.update([
{ x: "Test, hello!", y: 1 },
{ x: 'Test2"', y: 2 },
{ x: 'Test3, Hello!"', y: 3 },
]);
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual(
`"x","y"\n"Test, hello!",1\n"Test2""",2\n"Test3, Hello!""",3\n`,
);
view.delete();
table.delete();
});
// Added
test("Handles strings with quotation characters and commas (async)", async function () {
let table = await perspective.table({ x: "string", y: "integer" });
await table.update([
{ x: "Test, hello!", y: 1 },
{ x: 'Test2"', y: 2 },
{ x: 'Test3, Hello!"', y: 3 },
]);
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual(
`"x","y"\n"Test, hello!",1\n"Test2""",2\n"Test3, Hello!""",3\n`,
);
view.delete();
table.delete();
});
test("Transitively loads a CSV created from `to_csv()` on a table with a datetime column", async function () {
// Assert that the CSV parser can handle POSIX timestamps.
let table = await perspective.table(arrow_date_data);
let view = await table.view();
let schema = await table.schema();
let csv = await view.to_csv();
let table2 = await perspective.table(csv);
let schema2 = await table2.schema();
let view2 = await table2.view();
let csv2 = await view2.to_csv();
expect(schema2).toEqual(schema);
expect(csv2).toEqual(csv);
view2.delete();
table2.delete();
view.delete();
table.delete();
});
});
test.describe("ndjson", function () {
test("basic constructor", async function () {
var table = await perspective.table(`{"x":1}\n{"x":2}`, {
format: "ndjson",
});
var view = await table.view();
let result = await view.to_json();
expect(result).toEqual([{ x: 1 }, { x: 2 }]);
view.delete();
table.delete();
});
test("date types", async function () {
const ndjson = [];
for (const row of data_4) {
ndjson.push(row);
}
var table = await perspective.table(
ndjson.map(JSON.stringify).join("\n"),
{
format: "ndjson",
},
);
var view = await table.view();
let result = await view.to_json();
expect(result).toEqual([{ v: +data_4[0]["v"] }]);
view.delete();
table.delete();
});
test("Handles dates when provided a schema", async function () {
var table = await perspective.table(meta_4);
await table.update(data_4.map(JSON.stringify).join("\n"), {
format: "ndjson",
});
let view = await table.view();
let result = await view.to_json();
expect(result).toEqual([{ v: +data_4[0]["v"] }]);
view.delete();
table.delete();
});
test("Handles many objects without newline separators", async function () {
const expected = [];
for (let i = 0; i < 64; i++) {
expected.push({ a: `row-${i}` });
}
const data = expected.map(JSON.stringify).join("");
const table = await perspective.table(data, { format: "ndjson" });
const view = await table.view();
expect(await table.size()).toEqual(64);
expect(await view.to_json()).toEqual(expected);
await view.delete();
await table.delete();
});
});
test.describe("Constructors", function () {
test("JSON constructor", async function () {
var table = await perspective.table(data);
var view = await table.view();
let result = await view.to_json();
expect(result).toEqual(data);
view.delete();
table.delete();
});
test("JSON column oriented constructor", async function () {
var table = await perspective.table(col_data);
var view = await table.view();
let result = await view.to_json();
expect(result).toEqual(data);
view.delete();
table.delete();
});
test("Arrow constructor", async function () {
var table = await perspective.table(arrows.test_null_arrow.slice());
var view = await table.view();
let result = await view.to_json();
expect(result).toEqual(arrow_result);
view.delete();
table.delete();
});
test.skip("Arrow (chunked format) constructor", async function () {
var table = await perspective.table(arrows.chunked_arrow.slice());
var view = await table.view();
let result = await view.to_json();
expect(result.length).toEqual(10);
view.delete();
table.delete();
});
test("Arrow date32 constructor", async function () {
const table = await perspective.table(arrows.date32_arrow.slice());
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual(arrow_date_data);
view.delete();
table.delete();
});
test("Arrow float32 constructor", async function () {
const table = await perspective.table(arrows.float32_arrow.slice());
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual({
id: [1, 2, 3],
name: ["Alice", "Bob", "Charlie"],
score: [92.5, 87.30000305175781, 95.80000305175781],
});
view.delete();
table.delete();
});
test("Arrow date64 constructor", async function () {
const table = await perspective.table(arrows.date64_arrow.slice());
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual(arrow_date_data);
view.delete();
table.delete();
});
test("Arrow Lists constructor", async function () {
const table = await perspective.table(arrows.lists_arrow.slice());
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual(arrow_lists_data);
view.delete();
table.delete();
});
test("Arrow dictionary constructor", async function () {
const table = await perspective.table(arrows.dict_arrow.slice());
const view = await table.view();
const result = await view.to_columns();
expect(result).toEqual({
a: ["abc", "def", "def", null, "abc"],
b: ["klm", "hij", null, "hij", "klm"],
});
view.delete();
table.delete();
});
test("CSV constructor with null columns", async function () {
const table = await perspective.table("x,y\n1,");
const view = await table.view();
expect(await table.schema()).toEqual({
x: "integer",
y: "string",
});
const result = await view.to_columns();
expect(result).toEqual({
x: [1],
y: [null],
});
await view.delete();
await table.delete();
});
test("CSV constructor indexed, with null columns, and update", async function () {
const table = await perspective.table("x,y\n1,", { index: "x" });
const view = await table.view();
expect(await table.schema()).toEqual({
x: "integer",
y: "string",
});
const result = await view.to_columns();
expect(result).toEqual({
x: [1],
y: [null],
});
await table.update("x,y\n1,abc\n2,123");
const result2 = await view.to_columns();
expect(result2).toEqual({
x: [1, 2],
y: ["abc", "123"],
});
await view.delete();
await table.delete();
});
test("Meta constructor", async function () {
var table = await perspective.table(meta);
var view = await table.view();
let result = await view.to_json();
expect(result).toEqual([]);
view.delete();
table.delete();
});
test("Handles floats", async function () {
var table = await perspective.table(data_3);
var view = await table.view();
let result = await view.to_json();
expect(result).toEqual(data_3);
view.delete();
table.delete();
});
test("Infers ints wrapped in strings", async function () {
var table = await perspective.table(int_in_string);
var view = await table.view();
let result = await view.schema();
expect(result).toEqual({ a: "integer" });
view.delete();
table.delete();
});
test("Infers floats wrapped in strings", async function () {
var table = await perspective.table(float_in_string);
var view = await table.view();
let result = await view.schema();
expect(result).toEqual({ a: "float" });
view.delete();
table.delete();
});
test("Infers correct type for empty string columns", async function () {
var table = await perspective.table([
{ x: "", y: 1 },
{ x: "", y: 2 },
{ x: "", y: 3 },
{ x: "", y: 4 },
]);
var view = await table.view();
let result = await view.schema();
expect(result).toEqual({ x: "string", y: "integer" });
view.delete();
table.delete();
});
test("Returns the correct number of rows for column-only views", async function () {
var table = await perspective.table(data);
var view = await table.view();
var num_rows = await view.num_rows();
var view2 = await table.view({
split_by: ["x"],
});
var num_rows_col_only = await view2.num_rows();
expect(num_rows_col_only).toEqual(num_rows);
view.delete();
view2.delete();
table.delete();
});
test.skip("Handles inconsistent rows with same width", async function () {
const int_to_float = [
{ x: 1, y: 2 },
{ y: 2, z: 3 },
];
var table = await perspective.table(int_to_float);
var view = await table.view();
var json = await view.to_json();
expect(json).toEqual([
{ x: 1, y: 2, z: null },
{ x: null, y: 2, z: 3 },
]);
view.delete();
table.delete();
});
test.skip("Handles inconsistent rows", async function () {
const int_to_float = [{ x: 1 }, { y: 2, z: 3 }];
var table = await perspective.table(int_to_float);
var schema = await table.schema();
expect(schema).toEqual({
x: "integer",
y: "integer",
z: "integer",
});
var view = await table.view();
var json = await view.to_json();
expect(json).toEqual([
{ x: 1, y: null, z: null },
{ x: null, y: 2, z: 3 },
]);
view.delete();
table.delete();
});
test("Upgrades integer columns to strings", async function () {
const int_to_float = {
a: [1, 2, 3, "x", "y"],
};
var table = await perspective.table(int_to_float);
var schema_1 = await table.schema();
expect(schema_1["a"]).toEqual("string");
var view = await table.view();
var json = await view.to_json();
expect(json).toEqual([
{ a: "1" },
{ a: "2" },
{ a: "3" },
{ a: "x" },
{ a: "y" },
]);
view.delete();
table.delete();
});
test("Upgrades integer columns with values beyond max/min_int to float", async function () {
const int_to_float = {
a: [1, 2, 3, 2147483667, 5],
};
var table = await perspective.table(int_to_float);
var view = await table.view();
var json = await view.to_columns();
expect(json).toEqual(int_to_float);
var schema_2 = await table.schema();
expect(schema_2["a"]).toEqual("float");
view.delete();
table.delete();
});
// This currently won't work, and I'm unclear we want it to - upgrading
// ths column in place is easy, but once the gnode and potentially
// contexts have been created, this becomes much more difficult.
test.skip("Upgrades integer columns with values beyond max/min_int to float old", async function () {
const schema = {
a: "integer",
};
const int_to_float = {
a: [1, 2, 3, 2147483667, 5],
};
var table = await perspective.table(schema);
var schema_1 = await table.schema();
expect(schema_1["a"]).toEqual("integer");
await table.update(int_to_float);
var schema_2 = await table.schema();
expect(schema_2["a"]).toEqual("float");
table.delete();
});
test("Does not infer float column as integers", async function () {
const int_to_float = [];
for (let x = 0; x < 200; x++) {
int_to_float.push({ a: 1 });
}
int_to_float.push({ a: 2147483667 });
var table = await perspective.table(int_to_float);
var schema_1 = await table.schema();
expect(schema_1["a"]).toEqual("float");
let view = await table.view();
var data = await view.to_json();
expect(data).toEqual(int_to_float);
view.delete();
table.delete();
});
test("has correct size", async function () {
var table = await perspective.table(data);
let result = await table.size();
expect(result).toEqual(4);
table.delete();
});
test("has a schema", async function () {
var table = await perspective.table(data);
let result = await table.schema();
expect(result).toEqual(meta);
table.delete();
});
test("has columns", async function () {
var table = await perspective.table(data);
let result = await table.columns();
expect(result).toEqual(["x", "y", "z"]);
table.delete();
});
test("Handles floats schemas", async function () {
var table = await perspective.table(data_3);
let result = await table.schema();
expect(result).toEqual(meta_3);
table.delete();
});
test("Generates correct date schemas", async function () {
var table = await perspective.table(data_4);
let result = await table.schema();
expect(result).toEqual(meta_4);
table.delete();
});
test("Handles date updates when constructed from a schema", async function () {
var table = await perspective.table(meta_4);
await table.update(data_4);
let view = await table.view();
let result = await view.to_json();
expect(result).toEqual([{ v: +data_4[0]["v"] }]);
view.delete();
table.delete();
});
test("Handles datetime values", async function () {
var table = await perspective.table(data_4);
let view = await table.view();
let result = await view.to_json();
expect(result).toEqual([{ v: +data_4[0]["v"] }]);
view.delete();
table.delete();
});
test("Handles datetime strings", async function () {
var table = await perspective.table(data_5);
let view = await table.view();
let result = await view.to_json();
expect(result).toEqual([
{ v: +moment(data_5[0]["v"], "MM-DD-YYYY") },
]);
view.delete();
table.delete();
});
test("Handles datetime strings in US locale string old", async function () {
// FIXME: 1/1/2020, 12:30:45 PM = 1/1/2020, 7:30:45 AM UTC, but
// because C++ strptime in Emscripten parses the time as 12:30:45AM,
// the output is 12/31/2019 19:30:45 PM UTC. This is clearly wrong.
const data = {
x: [
"1/1/2020, 12:30:45 PM",
"1/1/2020, 12:30:45 AM",
"03/15/2020, 11:30:45 AM",
"06/30/2020, 01:30:45 AM",
"06/30/2020, 01:30:45 PM",
"12/31/2020, 11:59:59 PM",
],
};
const table = await perspective.table(data);
const schema = await table.schema();
expect(schema).toEqual({
x: "datetime",
});
const view = await table.view();
const result = await view.to_columns();
const expected = {
x: [
"1/1/2020, 12:30:45 PM",
"1/1/2020, 12:30:45 AM",
"03/15/2020, 11:30:45 AM",
"06/30/2020, 01:30:45 AM",
"06/30/2020, 01:30:45 PM",
"12/31/2020, 11:59:59 PM",
].map((v) => new Date(v).getTime()),
};
expect(result).toEqual(expected);
view.delete();
table.delete();
});
test("Handles datetime strings in US locale string", async function () {
const data = {
x: [
"1/1/2020, 05:30:45 AM",
"03/15/2020, 11:30:45 AM",
"06/30/2020, 01:30:45 AM",
"12/31/2020, 11:59:59 PM",
],
};
const table = await perspective.table(data);
const schema = await table.schema();
expect(schema).toEqual({
x: "datetime",
});
const view = await table.view();
const result = await view.to_columns();
const expected = {
x: [
"1/1/2020, 05:30:45 AM",
"03/15/2020, 11:30:45 AM",
"06/30/2020, 01:30:45 AM",
"12/31/2020, 11:59:59 PM",
].map((v) => new Date(v).getTime()),
};
expect(result).toEqual(expected);
view.delete();
table.delete();
});
test("Handles datetime values with mixed formats", async function () {
var table = await perspective.table({ datetime: "datetime" });
await table.update([
{ datetime: new Date(1549257586108) },
{ datetime: "2019-01-30" },
{ datetime: 11 },
]);
let view = await table.view();
let result = await view.to_json();
expect(result).toEqual([
{ datetime: 1549257586108 },
{ datetime: 1548806400000 },
{ datetime: 11 },
]);
view.delete();
table.delete();
});
test("Handles date values", async function () {
var table = await perspective.table({ v: "date" });
await table.update(data_4);
let view = await table.view();
let result = await view.to_json();
let d = new Date(data_4[0]["v"]);
d.setHours(0);
d.setMinutes(0);
d.setSeconds(0);
d.setMilliseconds(0);
expect(result).toEqual([{ v: +d }]);
view.delete();
table.delete();
});
test("CSV Parses an ISO-8601 formatted string with milliseconds and timezone", async function () {
let table = await perspective.table(
"d\n2008-09-15T10:00:00.123+05:45",
);
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 04:15:00.123\n');
await view.delete();
await table.delete();
});
test("JSON Parses an ISO-8601 formatted string with milliseconds and timezone", async function () {
let table = await perspective.table({
d: ["2008-09-15T10:00:00.123+05:45"],
});
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 04:15:00.123\n');
await view.delete();
await table.delete();
});
test("CSV Updates an ISO-8601 formatted string with milliseconds and timezone", async function () {
let table = await perspective.table({ d: "datetime" });
await table.update("d\n2008-09-15T10:00:00.123+05:45");
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 04:15:00.123\n');
await view.delete();
await table.delete();
});
test("JSON Updates an ISO-8601 formatted string with milliseconds and timezone", async function () {
let table = await perspective.table({ d: "datetime" });
await table.update({
d: ["2008-09-15T10:00:00.123+05:45"],
});
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 04:15:00.123\n');
await view.delete();
await table.delete();
});
test("CSV Parses an ISO-8601 formatted string with microseconds and timezone", async function () {
let table = await perspective.table(
"d\n2008-09-15T15:30:00.123456+05:30",
);
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 10:00:00.123\n');
await view.delete();
await table.delete();
});
test("JSON Parses an ISO-8601 formatted string with microseconds and timezone", async function () {
let table = await perspective.table({
d: ["2008-09-15T15:30:00.123456+05:30"],
});
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 10:00:00.123\n');
await view.delete();
await table.delete();
});
test("CSV Updates an ISO-8601 formatted string with microseconds and timezone", async function () {
let table = await perspective.table({ d: "datetime" });
await table.update("d\n2008-09-15T15:30:00.123456+05:30");
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 10:00:00.123\n');
await view.delete();
await table.delete();
});
test("JSON Updates an ISO-8601 formatted string with microseconds and timezone", async function () {
let table = await perspective.table({ d: "datetime" });
await table.update({
d: ["2008-09-15T15:30:00.123456+05:30"],
});
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 10:00:00.123\n');
await view.delete();
await table.delete();
});
test("CSV Parses an ISO-8601 formatted string with nanoseconds and timezone", async function () {
let table = await perspective.table(
"d\n2008-09-15T15:15:00.123456789-05:15",
);
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 20:30:00.123\n');
await view.delete();
await table.delete();
});
test("JSON Parses an ISO-8601 formatted string with nanoseconds and timezone", async function () {
let table = await perspective.table({
d: ["2008-09-15T15:15:00.123456789-05:15"],
});
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 20:30:00.123\n');
await view.delete();
await table.delete();
});
test("CSV Updates an ISO-8601 formatted string with nanoseconds and timezone", async function () {
let table = await perspective.table({ d: "datetime" });
await table.update("d\n2008-09-15T15:15:00.123456789-05:15");
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 20:30:00.123\n');
await view.delete();
await table.delete();
});
test("JSON Updates an ISO-8601 formatted string with nanoseconds and timezone", async function () {
let table = await perspective.table({ d: "datetime" });
await table.update({
d: ["2008-09-15T15:15:00.123456789-05:15"],
});
let view = await table.view();
let result = await view.to_csv();
expect(result).toEqual('"d"\n2008-09-15 20:30:00.123\n');
await view.delete();
await table.delete();
});
test("CSV Reader parses milliseconds", async function () {
const a = '"start"\n2024-08-14T14:06:07.826Z';
const table = await perspective.table(a);
const view = await table.view();
const csv1 = await view.to_csv();
expect(csv1).toEqual('"start"\n2024-08-14 14:06:07.826\n');
view.delete();
table.delete();
});
test("CSV Reader update parses milliseconds", async function () {
const a = '"d"\n2024-08-14T14:06:07.826Z';
const table = await perspective.table({
d: "datetime",
});
await table.update(a);
const view = await table.view();
const csv1 = await view.to_csv();
expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
view.delete();
table.delete();
});
test("CSV Reader update updates microseconds", async function () {
const a = '"d"\n2024-08-14T14:06:07.826789Z';
const table = await perspective.table({
d: "datetime",
});
await table.update(a);
const view = await table.view();
const csv1 = await view.to_csv();
expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
view.delete();
table.delete();
});
test("CSV Reader update updates nanoseconds", async function () {
const a = '"d"\n2024-08-14T14:06:07.826789789Z';
const table = await perspective.table({
d: "datetime",
});
await table.update(a);
const view = await table.view();
const csv1 = await view.to_csv();
expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
view.delete();
table.delete();
});
test("CSV Reader update parses microseconds", async function () {
const a = '"d"\n2024-08-14T14:06:07.826789Z';
const table = await perspective.table(a);
const view = await table.view();
const csv1 = await view.to_csv();
expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
view.delete();
table.delete();
});
test("CSV Reader update parses nanoseconds", async function () {
const a = '"d"\n2024-08-14T14:06:07.826789789Z';
const table = await perspective.table(a);
const view = await table.view();
const csv1 = await view.to_csv();
expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
view.delete();
table.delete();
});
test("JSON Reader parses milliseconds", async function () {
const a = "2024-08-14T14:06:07.826Z";
const table = await perspective.table({
d: "datetime",
});
await table.update({ d: [a] });
const view = await table.view();
const csv1 = await view.to_csv();
expect(csv1).toEqual('"d"\n2024-08-14 14:06:07.826\n');
view.delete();
table.delete();
});
test("Handles utf16 column names", async function () {
var table = await perspective.table({ š: [1, 2, 3] });
let view = await table.view({});
let result = await view.schema();
expect(result).toEqual({ š: "integer" });
view.delete();
table.delete();
});
test("Handles utf16", async function () {
var table = await perspective.table(data_6);
let view = await table.view({});
let result = await view.to_json();
expect(result).toEqual(data_6);
view.delete();
table.delete();
});
test.describe("Datetime constructors", function () {
test("Correctly parses an ISO-8601 formatted string", async function () {
let table = await perspective.table({
d: ["2011-10-05T14:48:00"],
});
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("datetime");
await view.delete();
await table.delete();
});
test("Correctly parses an ISO-8601 formatted string 2", async function () {
let table = await perspective.table({
d: ["2011-10-05T14:48:00.000"],
});
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("datetime");
await view.delete();
await table.delete();
});
test("Correctly parses an ISO-8601 formatted string 3", async function () {
let table = await perspective.table({
d: ["2011-10-05T14:48:00Z"],
});
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("datetime");
await view.delete();
await table.delete();
});
test("Correctly parses an ISO-8601 formatted string 4", async function () {
let table = await perspective.table({
d: ["2011-10-05T14:48:00.000Z"],
});
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("datetime");
await view.delete();
await table.delete();
});
test("Correctly parses an ISO-8601 formatted string with timezone", async function () {
let table = await perspective.table({
d: ["2008-09-15T15:53:00+05:00"],
});
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("datetime");
await view.delete();
await table.delete();
});
test.skip("Correctly parses an RFC 2822 formatted string", async function () {
let table = await perspective.table({
d: ["Wed, 05 Oct 2011 22:26:12 -0400"],
});
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("datetime");
await view.delete();
await table.delete();
});
// Not all formats covered by JS parser, test intended for C++ parser
test.skip("Correctly parses all m-d-y formatted strings", async function () {
let datestrings = [
"08-15-2009",
"08/15/2009",
"08-15-2009",
"02 28 2009",
"08/15/10",
"31 08 2009",
];
for (let str of datestrings) {
let table = await perspective.table({ d: [str] });
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("datetime");
await view.delete();
await table.delete();
}
});
// Only implemented in the C++ date parser - skip
test.skip("Correctly parses a 'dd mm yyyy' formatted string", async function () {
let table = await perspective.table({ d: ["15 08 08"] });
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("datetime");
await view.delete();
await table.delete();
});
test("Does parse a date string in non-US formatting", async function () {
let table = await perspective.table({ d: ["2018/07/30"] });
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("date");
await view.delete();
await table.delete();
});
test("Does not mistakenly parse a date-like string", async function () {
let table = await perspective.table({ d: ["Jan 14, 14"] });
let view = await table.view({});
let result = await view.schema();
expect(result["d"]).toEqual("string");
await view.delete();
await table.delete();
});
});
test("allocates a large tables", async function () {
function makeid() {
var text = "";
var possible = Array.from(Array(26).keys()).map((x) =>
String.fromCharCode(x + 65),
);
for (var i = 0; i < 15; i++)
text +=
possible[Math.floor(Math.random() * possible.length)];
return text;
}
let data = [];
for (let i = 0; i < 35000; i++) {
data.push({
a: makeid(),
b: makeid(),
c: makeid(),
d: makeid(),
w: i + 0.5,
x: i,
y: makeid(),
});
}
let table = await perspective.table(data);
let view = await table.view();
let result = await view.to_json();
expect(result.length).toEqual(35000);
view.delete();
table.delete();
}, 3000);
test.describe("get_index/get_limit", function () {
test("get_index() on unindexed table", async function () {
const table = await perspective.table(data);
expect(await table.get_index()).toBeUndefined();
await table.delete();
});
test("get_index() on an indexed table", async function () {
const table = await perspective.table(data, { index: "x" });
expect(await table.get_index()).toEqual("x");
await table.delete();
});
test("get_limit() on table without limit", async function () {
const table = await perspective.table(data);
expect(await table.get_limit()).toBeUndefined();
await table.delete();
});
test("get_limit() on table with limit", async function () {
const table = await perspective.table(data, { limit: 2 });
expect(await table.get_limit()).toEqual(2);
await table.delete();
});
});
test.describe("Limit table constructors", function () {
// This is a better version of the OG but still questionable.
// IMO we should not allow this without an explicit `__INDEX__`.
test("Should not apply partial updates on limit tables when index wraps", async function () {
const table = await perspective.table(
{ a: "float", b: "float" },
{
limit: 3,
},
);
await table.update([
{ a: 10, b: 1 },
{ a: 20, b: 2 },
]);
await table.update([{ a: 30 }, { a: 40 }]);
const view = await table.view();
const records = await view.to_json();
await view.delete();
await table.delete();
expect(records).toEqual([
{ a: 20, b: 2 },
{ a: 30, b: null },
{ a: 40, b: null },
]);
});
// We don' tsupport mixed-partial updates anymore!
test.skip("OG - Should not apply partial updates on limit tables when index wraps", async function () {
const table = await perspective.table(
{ a: "float", b: "float" },
{
limit: 3,
},
);
await table.update([
{ a: 10 },
{ b: 1 },
{ a: 20 },
{ a: null, b: 2 },
]);
const view = await table.view();
const records = await view.to_json();
await view.delete();
await table.delete();
const table2 = await perspective.table(
{ a: "float", b: "float" },
{
limit: 3,
},
);
table2.update([{ a: 10 }, { b: 1 }, { a: 20 }, { b: 2 }]);
const view2 = await table2.view();
const records2 = await view2.to_json();
await view2.delete();
await table2.delete();
expect(records).toEqual(records2);
});
});
test.describe("Indexed table constructors", function () {
test("Should index on an integer column", async function () {
const table = await perspective.table(all_types_data, {
index: "int",
});
const view = await table.view();
expect(await table.size()).toEqual(4);
expect(await view.to_json()).toEqual([
{
int: 1,
float: 2.25,
string: "a",
date: 1579046400000,
datetime: 1579069800000,
boolean: false,
},
{
int: 2,
float: 3.5,
string: "b",
date: 1581724800000,
datetime: 1579091400000,
boolean: true,
},
{
int: 3,
float: 4.75,
string: "c",
date: 1584230400000,
datetime: 1579113000000,
boolean: false,
},
{
int: 4,
float: 5.25,
string: "d",
date: 1586908800000,
datetime: 1579131000000,
boolean: true,
},
]);
await view.delete();
await table.delete();
});
test("Should index on a float column", async function () {
const table = await perspective.table(all_types_data, {
index: "float",
});
const view = await table.view();
expect(await table.size()).toEqual(4);
expect(await view.to_json()).toEqual([
{
int: 1,
float: 2.25,
string: "a",
date: 1579046400000,
datetime: 1579069800000,
boolean: false,
},
{
int: 2,
float: 3.5,
string: "b",
date: 1581724800000,
datetime: 1579091400000,
boolean: true,
},
{
int: 3,
float: 4.75,
string: "c",
date: 1584230400000,
datetime: 1579113000000,
boolean: false,
},
{
int: 4,
float: 5.25,
string: "d",
date: 1586908800000,
datetime: 1579131000000,
boolean: true,
},
]);
await view.delete();
await table.delete();
});
test("Should index on a string column", async function () {
const table = await perspective.table(all_types_data, {
index: "string",
});
const view = await table.view();
expect(await table.size()).toEqual(4);
expect(await view.to_json()).toEqual([
{
int: 1,
float: 2.25,
string: "a",
date: 1579046400000,
datetime: 1579069800000,
boolean: false,
},
{
int: 2,
float: 3.5,
string: "b",
date: 1581724800000,
datetime: 1579091400000,
boolean: true,
},
{
int: 3,
float: 4.75,
string: "c",
date: 1584230400000,
datetime: 1579113000000,
boolean: false,
},
{
int: 4,
float: 5.25,
string: "d",
date: 1586908800000,
datetime: 1579131000000,
boolean: true,
},
]);
await view.delete();
await table.delete();
});
test("Should index on a date column", async function () {
const table = await perspective.table(all_types_data, {
index: "date",
});
const view = await table.view();
expect(await table.size()).toEqual(4);
expect(await view.to_json()).toEqual([
{
int: 1,
float: 2.25,
string: "a",
date: 1579046400000,
datetime: 1579069800000,
boolean: false,
},
{
int: 2,
float: 3.5,
string: "b",
date: 1581724800000,
datetime: 1579091400000,
boolean: true,
},
{
int: 3,
float: 4.75,
string: "c",
date: 1584230400000,
datetime: 1579113000000,
boolean: false,
},
{
int: 4,
float: 5.25,
string: "d",
date: 1586908800000,
datetime: 1579131000000,
boolean: true,
},
]);
await view.delete();
await table.delete();
});
test("Should index on a datetime column", async function () {
const table = await perspective.table(all_types_data, {
index: "datetime",
});
const view = await table.view();
expect(await table.size()).toEqual(4);
expect(await view.to_json()).toEqual([
{
int: 1,
float: 2.25,
string: "a",
date: 1579046400000,
datetime: 1579069800000,
boolean: false,
},
{
int: 2,
float: 3.5,
string: "b",
date: 1581724800000,
datetime: 1579091400000,
boolean: true,
},
{
int: 3,
float: 4.75,
string: "c",
date: 1584230400000,
datetime: 1579113000000,
boolean: false,
},
{
int: 4,
float: 5.25,
string: "d",
date: 1586908800000,
datetime: 1579131000000,
boolean: true,
},
]);
await view.delete();
await table.delete();
});
});
});
})(perspective);