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

1700 lines
68 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 _ from "lodash";
let 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 },
];
let partial_change_y = [
{ x: 1, y: "string1" },
{ x: 2, y: "string2" },
];
let partial_change_z = [
{ x: 1, z: false },
{ x: 2, z: true },
];
let partial_change_y_z = [
{ x: 1, y: "string1", z: false },
{ x: 2, y: "string2", z: true },
];
let partial_change_nonseq = [
{ x: 1, y: "string1", z: false },
{ x: 4, y: "string2", z: true },
];
async function match_delta(
perspective,
delta,
expected,
formatter = "to_json",
) {
const table = await perspective.table(delta);
const view = await table.view();
const result = await view[formatter]();
expect(result).toEqual(expected);
await view.delete();
await table.delete();
}
function it_old_behavior(name, capture) {
test(name, async function () {
let done;
let result = new Promise((x) => {
done = x;
});
await capture(done);
await result;
});
}
((perspective) => {
test.describe("Row delta", function () {
test.describe("0-sided row delta", function () {
it_old_behavior("returns changed rows", async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view();
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: "string1", z: true },
{ x: 2, y: "string2", z: false },
];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior(
"returns changed rows, hidden sort",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
columns: ["x"],
sort: [["y", "desc"]],
});
console.log(await view.to_json());
view.on_update(
async function (updated) {
const expected = [{ x: 2 }, { x: 1 }];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns changed rows from schema",
async function (done) {
let table = await perspective.table(
{
x: "integer",
y: "string",
z: "boolean",
},
{ index: "x" },
);
let view = await table.view();
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: "d", z: false },
{ x: 2, y: "b", z: false },
{ x: 3, y: "c", z: true },
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: "a", z: true },
{ x: 2, y: "b", z: false },
{ x: 3, y: "c", z: true },
{ x: 1, y: "d", z: false },
]);
},
);
it_old_behavior("returns added rows", async function (done) {
let table = await perspective.table(data);
let view = await table.view();
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: "string1", z: null },
{ x: 2, y: "string2", z: null },
];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior(
"returns added rows from schema",
async function (done) {
let table = await perspective.table({
x: "integer",
y: "string",
z: "boolean",
});
let view = await table.view();
view.on_update(
async function (updated) {
await match_delta(perspective, updated.delta, data);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(data);
},
);
it_old_behavior("returns deleted columns", async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view();
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: null, z: true },
{ x: 4, y: null, z: false },
];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: null },
{ x: 4, y: null },
]);
});
it_old_behavior(
"returns changed rows in sorted context",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
sort: [["x", "desc"]],
});
view.on_update(
async function (updated) {
const expected = [
{ x: 2, y: "string2", z: false },
{ x: 1, y: "string1", z: true },
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns changed rows in sorted context from schema",
async function (done) {
let table = await perspective.table(
{
x: "integer",
y: "string",
z: "boolean",
},
{ index: "x" },
);
let view = await table.view({
sort: [["x", "desc"]],
});
view.on_update(
async function (updated) {
const expected = [
{ x: 4, y: "a", z: true },
{ x: 3, y: "c", z: true },
{ x: 2, y: "b", z: false },
{ x: 1, y: "d", z: false },
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: "a", z: true },
{ x: 2, y: "b", z: false },
{ x: 3, y: "c", z: true },
{ x: 1, y: "d", z: false },
{ x: 4, y: "a", z: true },
]);
},
);
it_old_behavior(
"returns added rows in filtered context from schema",
async function (done) {
let table = await perspective.table({
x: "integer",
y: "string",
z: "boolean",
});
let view = await table.view({
filter: [["x", ">", 3]],
});
view.on_update(
async function (updated) {
await match_delta(perspective, updated.delta, [
{ x: 4, y: "d", z: false },
]);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(data);
},
);
it_old_behavior(
"returns changed rows in non-sequential update",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view();
view.on_update(
async function (updated) {
const expected = partial_change_nonseq;
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_nonseq);
},
);
});
test.describe("0-sided row delta, randomized column order", function () {
it_old_behavior("returns changed rows", async function (done) {
let table = await perspective.table(data, { index: "x" });
let columns = _.shuffle(await table.columns());
let view = await table.view({
columns: columns,
});
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: "string1", z: true },
{ x: 2, y: "string2", z: false },
];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior(
"returns changed rows from schema",
async function (done) {
let table = await perspective.table(
{
x: "integer",
y: "string",
z: "boolean",
},
{ index: "x" },
);
let columns = _.shuffle(await table.columns());
let view = await table.view({
columns: columns,
});
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: "d", z: false },
{ x: 2, y: "b", z: false },
{ x: 3, y: "c", z: true },
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: "a", z: true },
{ x: 2, y: "b", z: false },
{ x: 3, y: "c", z: true },
{ x: 1, y: "d", z: false },
]);
},
);
it_old_behavior("returns added rows", async function (done) {
let table = await perspective.table(data);
let columns = _.shuffle(await table.columns());
let view = await table.view({
columns: columns,
});
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: "string1", z: null },
{ x: 2, y: "string2", z: null },
];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior(
"returns added rows, hidden sort",
async function (done) {
let table = await perspective.table(data);
let view = await table.view({
columns: ["x"],
sort: [["y", "desc"]],
});
view.on_update(
async function (updated) {
const expected = [{ x: 2 }, { x: 1 }];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns added rows from schema",
async function (done) {
let table = await perspective.table({
x: "integer",
y: "string",
z: "boolean",
});
let columns = _.shuffle(await table.columns());
let view = await table.view({
columns: columns,
});
view.on_update(
async function (updated) {
await match_delta(perspective, updated.delta, data);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(data);
},
);
it_old_behavior("returns deleted columns", async function (done) {
let table = await perspective.table(data, { index: "x" });
let columns = _.shuffle(await table.columns());
let view = await table.view({
columns: columns,
});
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: null, z: true },
{ x: 4, y: null, z: false },
];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: null },
{ x: 4, y: null },
]);
});
it_old_behavior(
"returns changed rows in non-sequential update",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let columns = _.shuffle(await table.columns());
let view = await table.view({
columns: columns,
});
view.on_update(
async function (updated) {
const expected = partial_change_nonseq;
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_nonseq);
},
);
});
test.describe("0-sided row delta, column order subset", function () {
it_old_behavior("returns changed rows", async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
columns: ["y"],
});
view.on_update(
async function (updated) {
const expected = [{ y: "string1" }, { y: "string2" }];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior(
"returns changed rows from schema",
async function (done) {
let table = await perspective.table(
{
x: "integer",
y: "string",
z: "boolean",
},
{ index: "x" },
);
let view = await table.view({
columns: ["z"],
});
view.on_update(
async function (updated) {
const expected = [
{ z: false },
{ z: false },
{ z: true },
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: "a", z: true },
{ x: 2, y: "b", z: false },
{ x: 3, y: "c", z: true },
{ x: 1, y: "d", z: false },
]);
},
);
it_old_behavior("returns added rows", async function (done) {
let table = await perspective.table(data);
let view = await table.view({
columns: ["y"],
});
view.on_update(
async function (updated) {
const expected = [{ y: "string1" }, { y: "string2" }];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior(
"returns added rows from schema",
async function (done) {
let table = await perspective.table({
x: "integer",
y: "string",
z: "boolean",
});
let view = await table.view({
columns: ["z"],
});
view.on_update(
async function (updated) {
await match_delta(perspective, updated.delta, [
{ z: true },
{ z: false },
{ z: true },
{ z: false },
]);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(data);
},
);
it_old_behavior("returns deleted rows", async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
columns: ["y"],
});
view.on_update(
async function (updated) {
const expected = [{ y: null }, { y: null }];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: null },
{ x: 4, y: null },
]);
});
it_old_behavior(
"returns changed rows in non-sequential update",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
columns: ["y"],
});
view.on_update(
async function (updated) {
await match_delta(perspective, updated.delta, [
{ y: "string1" },
{ y: "string2" },
]);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_nonseq);
},
);
});
test.describe("1-sided row delta", function () {
it_old_behavior("returns changed rows", async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
aggregates: { y: "distinct count", z: "distinct count" },
});
view.on_update(
async function (updated) {
const expected = [
{ x: 1, y: 1, z: 1 },
{ x: 2, y: 1, z: 1 },
];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
test.skip("returns changed rows, unique", async function () {
// FIXME: the delta doesn't seem to trigger if the
// cell is invalidated, only if the actual values are
// different. This feels off behavior-wise.
const table = await perspective.table(
{
x: [1, 2, 3, 4],
y: ["a", "a", "a", "a"],
z: [100, 200, 100, 200],
},
{ index: "x" },
);
const view = await table.view({
group_by: ["z"],
aggregates: { y: "unique" },
});
expect(await view.to_columns()).toEqual({
__ROW_PATH__: [[], [100], [200]],
x: [10, 4, 6],
y: ["a", "a", "a"],
z: [600, 200, 400],
});
view.on_update(
async function (updated) {
console.log(await view.to_columns());
const expected = [
{ x: 10, y: null, z: 600 }, // total
{ x: 6, y: null, z: 400 },
];
await match_delta(perspective, updated.delta, expected);
await view.delete();
await table.delete();
done();
},
{ mode: "row" },
);
await table.update({
x: [4],
y: ["a"],
z: [200],
});
});
it_old_behavior(
"returns changed rows, column range",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
columns: ["x"],
aggregates: {
y: "distinct count",
z: "distinct count",
},
});
view.on_update(
async function (updated) {
const expected = [{ x: 1 }, { x: 2 }];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns nothing when updated data is not in pivot",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
aggregates: {
y: "distinct count",
z: "distinct count",
},
});
view.on_update(
async function (updated) {
await match_delta(perspective, updated.delta, []);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_z);
},
);
it_old_behavior("returns added rows", async function (done) {
let table = await perspective.table(data);
let view = await table.view({
group_by: ["y"],
aggregates: { y: "distinct count", z: "distinct count" },
});
view.on_update(
async function (updated) {
const expected = [
{ x: 13, y: 6, z: 3 },
{ x: 1, y: 1, z: 1 },
{ x: 2, y: 1, z: 1 },
];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior(
"returns added rows, column range",
async function (done) {
let table = await perspective.table(data);
let view = await table.view({
group_by: ["y"],
columns: ["z"],
aggregates: {
y: "distinct count",
z: "distinct count",
},
});
view.on_update(
async function (updated) {
const expected = [{ z: 3 }, { z: 1 }, { z: 1 }];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
it_old_behavior("returns deleted columns", async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
aggregates: { y: "distinct count", z: "distinct count" },
});
view.on_update(
async function (updated) {
// underlying data changes, but only total aggregate row is affected
const expected = [{ x: 10, y: 3, z: 2 }];
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: null },
{ x: 4, y: null },
]);
});
it_old_behavior(
"returns changed rows in non-sequential update",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
aggregates: {
y: "distinct count",
z: "distinct count",
},
});
view.on_update(
async function (updated) {
// aggregates are sorted, in this case by string comparator - "string1" and "string2" are at the end
const expected = [
{ x: 1, y: 1, z: 1 },
{ x: 4, y: 1, z: 1 },
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_nonseq);
},
);
it_old_behavior(
"Returns appended rows, hidden sort",
async function (done) {
const table = await perspective.table({
x: [1, 2, 3, 4],
y: ["A", "B", "C", "D"],
});
const view = await table.view({
group_by: ["y"],
sort: [["y", "desc"]],
columns: ["x"],
});
view.on_update(
async function (updated) {
const expected = [{ x: 13 }, { x: 2 }, { x: 1 }];
await match_delta(
perspective,
updated.delta,
expected,
);
await view.delete();
await table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
});
test.describe("2-sided row delta", function () {
it_old_behavior(
"returns changed rows when updated data in group by",
async function (done) {
let table = await perspective.table(data, { index: "y" });
let view = await table.view({
group_by: ["y"],
split_by: ["x"],
});
view.on_update(
async function (updated) {
const json = await view.to_json();
json.map((d) => {
delete d["__ROW_PATH__"];
});
const expected = json.slice(0, 3);
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns changed rows when updated data in group by, column range",
async function (done) {
let table = await perspective.table(data, { index: "y" });
let view = await table.view({
group_by: ["y"],
split_by: ["x"],
columns: ["x"],
});
view.on_update(
async function (updated) {
const json = await view.to_json();
json.map((d) => {
delete d["__ROW_PATH__"];
});
const expected = json.slice(0, 3);
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns changed rows when updated data in group by, hidden sort",
async function (done) {
let table = await perspective.table(data, { index: "y" });
let view = await table.view({
group_by: ["y"],
split_by: ["x"],
sort: [["y", "desc"]],
columns: ["x"],
});
view.on_update(
async function (updated) {
const expected = await view.to_json();
await match_delta(
perspective,
updated.delta,
expected.slice(0, 3).map((x) => {
delete x.__ROW_PATH__;
return x;
}),
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
test.skip("returns changed rows when updated data in group by multi, hidden sort", async function () {
let table = await perspective.table(data, { index: "y" });
let view = await table.view({
group_by: ["y", "x", "z"],
split_by: ["x"],
sort: [["y", "desc"]],
columns: ["x"],
});
view.on_update(
async function (updated) {
const expected = {
"1|x": [14, 0, 0],
"1|y": ["-", null, null],
"2|x": [2, 3, null],
"2|y": ["-", "WORLD", null],
"3|x": [2, null, 1],
"3|y": ["b", null, "HELLO"],
"4|x": [6, null, 1],
"4|y": ["-", null, "HELLO"],
};
await match_delta(
perspective,
updated.delta,
expected,
"to_columns",
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior(
"returns changed rows when updated data in split by",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
split_by: ["z"],
});
view.on_update(
async function (updated) {
const json = await view.to_json();
json.map((d) => {
delete d["__ROW_PATH__"];
});
const expected = json.slice(0, 3);
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_z);
},
);
it_old_behavior(
"returns changed rows when updated data in split by, column range",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
split_by: ["z"],
columns: ["y"],
});
view.on_update(
async function (updated) {
const json = await view.to_json();
json.map((d) => {
delete d["__ROW_PATH__"];
});
const expected = json.slice(0, 3);
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_z);
},
);
it_old_behavior(
"returns changed rows when updated data in row and split by",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
split_by: ["z"],
});
view.on_update(
async function (updated) {
const json = await view.to_json();
json.map((d) => {
delete d["__ROW_PATH__"];
});
const expected = [json[0], json[3], json[4]];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y_z);
},
);
it_old_behavior(
"returns changed rows when updated data in row and split by, column range",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
split_by: ["z"],
columns: ["x"],
});
view.on_update(
async function (updated) {
const json = await view.to_json();
json.map((d) => {
delete d["__ROW_PATH__"];
});
const expected = [json[0], json[3], json[4]];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y_z);
},
);
it_old_behavior(
"returns nothing when updated data is not in pivot",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
split_by: ["x"],
aggregates: {
y: "distinct count",
z: "distinct count",
},
});
view.on_update(
async function (updated) {
await match_delta(perspective, updated.delta, []);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_z);
},
);
it_old_behavior("returns added rows", async function (done) {
let table = await perspective.table(data);
let view = await table.view({
group_by: ["y"],
split_by: ["x"],
});
view.on_update(
async function (updated) {
const json = await view.to_json();
json.map((d) => {
delete d["__ROW_PATH__"];
});
const expected = json.slice(0, 3);
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
});
it_old_behavior("returns deleted columns", async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
split_by: ["x"],
aggregates: { y: "unique" },
columns: ["x", "y", "z"],
});
view.on_update(
async function (updated) {
// underlying data changes, but only total aggregate row is affected
const expected = await view.to_json();
expected.splice(3, 1);
expected.map((d) => {
delete d["__ROW_PATH__"];
});
await match_delta(perspective, updated.delta, expected);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: null },
{ x: 2, y: null },
{ x: 4, y: null },
]);
});
it_old_behavior(
"returns deleted columns, column range",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
split_by: ["x"],
aggregates: { y: "unique" },
columns: ["y"],
});
view.on_update(
async function (updated) {
// underlying data changes, but only total aggregate row is affected
const expected = await view.to_json();
expected.splice(3, 1);
expected.map((d) => {
delete d["__ROW_PATH__"];
});
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update([
{ x: 1, y: null },
{ x: 2, y: null },
{ x: 4, y: null },
]);
},
);
it_old_behavior(
"returns changed rows in non-sequential update",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
group_by: ["y"],
split_by: ["x"],
aggregates: {
y: "distinct count",
z: "distinct count",
},
});
view.on_update(
async function (updated) {
// aggregates are sorted, in this case by string comparator - "string1" and "string2" are at the end
const json = await view.to_json();
json.map((d) => {
delete d["__ROW_PATH__"];
});
const expected = [json[3], json[4]];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_nonseq);
},
);
it_old_behavior(
"returns changed rows in column-only pivots",
async function (done) {
let table = await perspective.table(data, { index: "x" });
let view = await table.view({
split_by: ["x"],
});
view.on_update(
async function (updated) {
const json = await view.to_json();
const expected = [
{
"1|x": 1,
"1|y": "string1",
"1|z": false,
"2|x": 2,
"2|y": "b",
"2|z": false,
"3|x": 3,
"3|y": "c",
"3|z": true,
"4|x": 4,
"4|y": "string2",
"4|z": true,
},
json[0],
json[3],
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_nonseq);
},
);
it_old_behavior(
"returns changed rows, col only",
async function (done) {
let table = await perspective.table(data, {
index: "x",
});
let view = await table.view({
split_by: ["y"],
});
view.on_update(
async function (updated) {
const expected = [
{
"c|x": 3,
"c|y": "c",
"c|z": true,
"d|x": 4,
"d|y": "d",
"d|z": false,
"string1|x": 1,
"string1|y": "string1",
"string1|z": true,
"string2|x": 2,
"string2|y": "string2",
"string2|z": false,
},
{
"c|x": null,
"c|y": null,
"c|z": null,
"d|x": null,
"d|y": null,
"d|z": null,
"string1|x": 1,
"string1|y": "string1",
"string1|z": true,
"string2|x": null,
"string2|y": null,
"string2|z": null,
},
{
"c|x": null,
"c|y": null,
"c|z": null,
"d|x": null,
"d|y": null,
"d|z": null,
"string1|x": null,
"string1|y": null,
"string1|z": null,
"string2|x": 2,
"string2|y": "string2",
"string2|z": false,
},
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{
mode: "row",
},
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns changed rows, col only agg",
async function (done) {
const table = await perspective.table(data, {
index: "x",
});
const view = await table.view({
split_by: ["x"],
sort: [["y", "desc"]],
columns: ["y"],
aggregates: { y: "last" },
});
view.on_update(
async function (updated) {
// TODO: deltas return a total row for column only
// which they probably shouldn't.
const expected = [
{
"1|y": "string1",
"2|y": "string2",
"3|y": "c",
"4|y": "d",
},
{
"1|y": null,
"2|y": "string2",
"3|y": null,
"4|y": null,
},
{
"1|y": "string1",
"2|y": null,
"3|y": null,
"4|y": null,
},
];
await match_delta(
perspective,
updated.delta,
expected,
);
await view.delete();
await table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns changed rows, col only col range",
async function (done) {
let table = await perspective.table(data, {
index: "x",
});
let view = await table.view({
split_by: ["y"],
columns: ["x"],
});
view.on_update(
async function (updated) {
const expected = [
{
"c|x": 3,
"d|x": 4,
"string1|x": 1,
"string2|x": 2,
},
{
"c|x": null,
"d|x": null,
"string1|x": 1,
"string2|x": null,
},
{
"c|x": null,
"d|x": null,
"string1|x": null,
"string2|x": 2,
},
];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{
mode: "row",
},
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns changed rows, col only sorted",
async function (done) {
const table = await perspective.table(data, {
index: "x",
});
const view = await table.view({
split_by: ["y"],
columns: ["x"],
sort: [["x", "desc"]],
});
console.log(await view.to_json());
view.on_update(
async function (updated) {
const expected = [
{
"c|x": 3,
"d|x": 4,
"string1|x": 1,
"string2|x": 2,
},
{
"c|x": null,
"d|x": null,
"string1|x": null,
"string2|x": 2,
},
{
"c|x": null,
"d|x": null,
"string1|x": 1,
"string2|x": null,
},
];
await match_delta(
perspective,
updated.delta,
expected,
);
await view.delete();
await table.delete();
done();
},
{
mode: "row",
},
);
await table.update(partial_change_y);
},
);
it_old_behavior(
"returns changed rows, col only sorted change not in pivot",
async function (done) {
let table = await perspective.table(
{ x: [1], y: [100] },
{
index: "x",
},
);
let view = await table.view({
split_by: ["y"],
columns: ["x"],
sort: [["x", "desc"]],
});
console.log(await view.to_json());
view.on_update(
async function (updated) {
const expected = [{ "100|x": 3 }];
await match_delta(
perspective,
updated.delta,
expected,
);
view.delete();
table.delete();
done();
},
{
mode: "row",
},
);
await table.update([{ x: 3, y: 100 }]);
},
);
it_old_behavior(
"returns changed rows, col only hidden sort",
async function (done) {
const table = await perspective.table(data, { index: "x" });
const view = await table.view({
split_by: ["y"],
columns: ["x"],
sort: [["y", "desc"]],
});
view.on_update(
async function (updated) {
const expected = [
{
"c|x": 3,
"d|x": 4,
"string1|x": 1,
"string2|x": 2,
},
{
"c|x": null,
"d|x": null,
"string1|x": null,
"string2|x": 2,
},
{
"c|x": null,
"d|x": null,
"string1|x": 1,
"string2|x": null,
},
];
await match_delta(
perspective,
updated.delta,
expected,
);
await view.delete();
await table.delete();
done();
},
{ mode: "row" },
);
await table.update(partial_change_y);
},
);
});
});
})(perspective);