diff --git a/package.json b/package.json index c9a1d3eca3..e9f70e416f 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,8 @@ "python/perspective" ], "devDependencies": { - "@babel/cli": "^7.8.4", - "@babel/core": "^7.8.4", - "@babel/preset-env": "^7.8.4", "@types/ws": "^7.2.2", "arraybuffer-loader": "^1.0.2", - "babel-jest": "^25.1.0", "chalk": "^2.4.2", "cpy-cli": "^3.1.1", "css-loader": "^0.28.7", @@ -56,7 +52,7 @@ "html-webpack-plugin": "^5.1.0", "husky": "^7.0.4", "inquirer": "^7.0.0", - "jest": "^25.1.0", + "jest": "^29.3.1", "jest-junit": "^10.0.0", "jsdoc": "3.5.5", "jsdoc-babel": "^0.5.0", diff --git a/packages/perspective-jupyterlab/package.json b/packages/perspective-jupyterlab/package.json index 178800a2a1..bdc964d625 100644 --- a/packages/perspective-jupyterlab/package.json +++ b/packages/perspective-jupyterlab/package.json @@ -21,9 +21,9 @@ "bench:run": "echo \"No Benchmarks\"", "clean:screenshots": "rimraf \"screenshots/**/*.@(failed|diff).png\"", "test:build": "cpy \"test/html/*\" dist/umd && cpy \"test/csv/*\" dist/umd && cpy \"test/css/*\" dist/umd && node build.js --test", - "test:run": "jest --rootDir=. --config=test/config/jest.config.js --color --verbose", + "test:run": "jest --rootDir=. --config=test/config/jest.config.js --color", "test:jupyter:build": "cpy \"test/html/*\" dist/umd && cpy \"test/arrow/*\" dist/umd && cpy \"test/css/*\" dist/umd", - "test:jupyter:run": "__JUPYTERLAB_PORT__=6538 jest --rootDir=. --config=test/config/jupyter/jest.config.js --color --verbose", + "test:jupyter:run": "__JUPYTERLAB_PORT__=6538 jest --rootDir=. --config=test/config/jupyter/jest.config.js --color", "test": "npm-run-all test:build test:run", "test:jupyter": "npm-run-all test:jupyter:build test:jupyter:run", "build": "node build.js", diff --git a/packages/perspective-viewer-d3fc/package.json b/packages/perspective-viewer-d3fc/package.json index 10651c0aa8..c2a4671cf9 100644 --- a/packages/perspective-viewer-d3fc/package.json +++ b/packages/perspective-viewer-d3fc/package.json @@ -36,8 +36,7 @@ "bench:run": "echo \"No Benchmarks\"", "prebuild": "mkdirp dist/esm", "build": "node ./build.js", - "test:build": "cpy --cwd test \"html/*\" ../dist/umd", - "watch": ":", + "test:build": "cpy \"test/html/*\" dist/umd", "test:run": "jest --rootDir=. --config=../../tools/perspective-test/jest.config.js --color", "test:unit": "jest --rootDir=. --roots=test/js/unit --config=../../tools/perspective-test/jest.config.js --color --runInBand", "test": "npm-run-all test:build test:run", @@ -57,12 +56,15 @@ "@finos/perspective": "^1.9.0", "@finos/perspective-viewer": "^1.9.0", "chroma-js": "^1.3.4", - "d3": "^7.1.1", + "d3fc": "^15.2.4", + "d3-selection": "^3.0.0", + "d3-array": "^3.2.1", "d3-svg-legend": "^2.25.6", - "d3fc": "15.2.4", + "d3": "^7.8.0", "gradient-parser": "1.0.2" }, "devDependencies": { + "@finos/perspective-esbuild-plugin": "^1.9.0", "@finos/perspective-test": "^1.9.0" } } diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/findBest.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/data/findBest.spec.js deleted file mode 100644 index 35d99a913a..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/findBest.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -const { findBestFromData } = require("../../../../src/js/data/findBest"); - -describe("findBestFromData should", () => { - const compareFn = Math.max; - - test("find the right value using the compareFn", () => { - const array = [1, 2, 3, 4, 5]; - const result = findBestFromData(array, (d) => d, compareFn); - expect(result).toEqual(5); - }); - - test("work for stacked arrays", () => { - const array = [[1, 2, 3], 4, 5, [6, [7, 8]]]; - const result = findBestFromData(array, (d) => d, compareFn); - expect(result).toEqual(8); - }); -}); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js deleted file mode 100644 index efeb340567..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js +++ /dev/null @@ -1,60 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -// TODO `d3fc` now has `customElements` reference, which fails in jest 25.x > -// jsdom 5.x - even importing this dependency fails. Upgrading causes havoc, -// so re-enable these post upgrade. - -// const {groupAndStackData} = require("../../../../src/js/data/groupData"); - -describe.skip("groupAndStackData should", () => { - test("include globals", () => { - expect(typeof HTMLElement).toBe("function"); - expect(typeof customElements).toBe("object"); - expect(typeof customElements.define).toBe("function"); - }); - - test("use settings data if no specific data is supplied", () => { - const settings = { - crossValues: [{ name: "cross1", type: "string" }], - data: [ - { value1: 10, __ROW_PATH__: ["CROSS1.1"] }, - { value1: 20, __ROW_PATH__: ["CROSS1.2"] }, - { value1: 30, __ROW_PATH__: ["CROSS1.1"] }, - ], - mainValues: [{ name: "value1", type: "integer" }], - splitValues: [], - }; - - const groupedResult = groupAndStackData(settings); - expect(groupedResult[0].length).toEqual(3); - }); - - test("use specific data if supplied", () => { - const suppliedData = [ - { value1: 10, __ROW_PATH__: ["CROSS1.1"] }, - { value1: 20, __ROW_PATH__: ["CROSS1.2"] }, - { value1: 30, __ROW_PATH__: ["CROSS1.1"] }, - ]; - const settings = { - crossValues: [{ name: "cross1", type: "string" }], - data: suppliedData, - mainValues: [{ name: "value1", type: "integer" }], - splitValues: [], - }; - - const extraData = suppliedData.concat([ - { value1: 40, __ROW_PATH__: ["CROSS1.3"] }, - { value1: 50, __ROW_PATH__: ["CROSS1.3"] }, - ]); - const groupedResult = groupAndStackData(settings, extraData); - - expect(groupedResult[0].length).toEqual(5); - }); -}); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/testTreeData.js b/packages/perspective-viewer-d3fc/test/js/unit/data/testTreeData.js deleted file mode 100644 index 60c2559438..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/testTreeData.js +++ /dev/null @@ -1,129 +0,0 @@ -export const data = [ - { - __ROW_PATH__: ["Central", "Furniture"], - Sales: 163797.16380000004, - Quantity: 1827, - }, - { - __ROW_PATH__: ["Central", "Technology"], - Sales: 170416.31200000003, - Quantity: 1544, - }, - { - __ROW_PATH__: ["East", "Furniture"], - Sales: 208291.20400000017, - Quantity: 2214, - }, - { - __ROW_PATH__: ["East", "Technology"], - Sales: 264973.9810000004, - Quantity: 1942, - }, -]; - -const item = { - __ROW_PATH__: ["East"], - Quantity: 4156, -}; - -export const agg_paths = [ - [item, item, item], - [item, item, item], - [item, item, item], - [item, item, item], - [item, item, item], - [item, item, item], -]; - -export const splitData = [ - { - __ROW_PATH__: ["Central", "Furniture"], - "First Class|Sales": 21037.825399999994, - "First Class|Quantity": 263, - "Same Day|Sales": 8842.286, - "Same Day|Quantity": 103, - "Second Class|Sales": 31187.1234, - "Second Class|Quantity": 343, - "Standard Class|Sales": 102729.92900000002, - "Standard Class|Quantity": 1118, - }, - { - __ROW_PATH__: ["Central", "Office Supplies"], - "First Class|Sales": 19390.004000000004, - "First Class|Quantity": 665, - "Same Day|Sales": 5818.718, - "Same Day|Quantity": 235, - "Second Class|Sales": 34307.253, - "Second Class|Quantity": 1063, - "Standard Class|Sales": 107510.43999999997, - "Standard Class|Quantity": 3446, - }, - { - __ROW_PATH__: ["Central", "Technology"], - "First Class|Sales": 18319.086000000003, - "First Class|Quantity": 228, - "Same Day|Sales": 5754.406, - "Same Day|Quantity": 54, - "Second Class|Sales": 38055.629, - "Second Class|Quantity": 389, - "Standard Class|Sales": 108287.191, - "Standard Class|Quantity": 873, - }, - { - __ROW_PATH__: ["East", "Furniture"], - "First Class|Sales": 29410.64399999999, - "First Class|Quantity": 383, - "Same Day|Sales": 12852.570999999994, - "Same Day|Quantity": 130, - "Second Class|Sales": 44035.93700000001, - "Second Class|Quantity": 433, - "Standard Class|Sales": 121992.05199999994, - "Standard Class|Quantity": 1268, - }, - { - __ROW_PATH__: ["East", "Office Supplies"], - "First Class|Sales": 36483.09600000002, - "First Class|Quantity": 1105, - "Same Day|Sales": 9124.796000000004, - "Same Day|Quantity": 332, - "Second Class|Sales": 43205.097, - "Second Class|Quantity": 1249, - "Standard Class|Sales": 116703.0659999999, - "Standard Class|Quantity": 3776, - }, - { - __ROW_PATH__: ["East", "Technology"], - "First Class|Sales": 47693.31300000001, - "First Class|Quantity": 317, - "Same Day|Sales": 21349.464999999997, - "Same Day|Quantity": 111, - "Second Class|Sales": 29304.48999999999, - "Second Class|Quantity": 344, - "Standard Class|Sales": 166626.71300000005, - "Standard Class|Quantity": 1170, - }, -]; - -export const mainValues = [ - { - name: "Sales", - type: "float", - }, - { - name: "Quantity", - type: "integer", - }, -]; - -export const realValues = ["Sales", "Quantity"]; - -export const crossValues = [ - { - name: "Region", - type: "string", - }, - { - name: "Category", - type: "string", - }, -]; diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/treeData.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/data/treeData.spec.js deleted file mode 100644 index 16d4950a5f..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/treeData.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -const { treeData } = require("../../../../src/js/data/treeData"); -const { - data, - splitData, - mainValues, - crossValues, - realValues, - agg_paths, -} = require("./testTreeData"); - -describe("treeData should", () => { - test("create a structure with the right number of levels", () => { - const { data: result } = treeData({ - data, - agg_paths, - mainValues, - crossValues, - realValues, - })[0]; - expect(result.height).toEqual(2); - }); - - test("calculate the correct color extents", () => { - const { extents } = treeData({ - data, - agg_paths, - mainValues, - crossValues, - realValues, - })[0]; - expect(extents).toEqual([1544, 4156]); - }); - - test("produce tree data for each split", () => { - const result = treeData({ - data: splitData, - agg_paths, - mainValues, - crossValues, - realValues, - }); - expect(result.length).toEqual(4); - }); -}); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js deleted file mode 100644 index 129cae6433..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js +++ /dev/null @@ -1,91 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ -const { select } = require("d3"); -const { initialiseStyles } = require("../../../../src/js/series/colorStyles"); -const sinon = require("sinon"); - -const styleVariables = { - "--d3fc-local-series": "rgba(31, 119, 180, 0.5)", - "--d3fc-local-series-1": "#0366d6", - "--d3fc-local-series-2": "#ff7f0e", - "--d3fc-local-series-3": "#2ca02c", - "--d3fc-local-series-4": "#d62728", - "--d3fc-local-series-5": "#9467bd", - "--d3fc-local-series-6": "#8c564b", - "--d3fc-local-series-7": "#e377c2", - "--d3fc-local-series-8": "#7f7f7f", - "--d3fc-local-series-9": "#bcbd22", - "--d3fc-local-series-10": "#17becf", - "--d3fc-local-full--gradient": `linear-gradient( - #4d342f 0%, - #f0f0f0 50%, - #1a237e 100% - )`, - "--d3fc-local-positive--gradient": `linear-gradient( - #dcedc8 0%, - #1a237e 100% - )`, - "--d3fc-local-negative--gradient": `linear-gradient( - #feeb65 100%, - #4d342f 0% - )`, -}; - -describe("colorStyles should", () => { - let container = null; - let settings = null; - - beforeEach(() => { - container = select("body").append("div"); - - settings = {}; - - window.getComputedStyle = () => ({ - getPropertyValue(x) { - return styleVariables[x]; - }, - }); - }); - - afterEach(() => { - container.remove(); - }); - - test("initialise colors from CSS variables", () => { - initialiseStyles(container.node(), settings); - const result = settings.colorStyles; - - expect(result.opacity).toEqual(0.5); - expect(result.series).toEqual(styleVariables["--d3fc-local-series"]); - for (let n = 1; n <= 10; n++) { - expect(result[`series-${n}`]).toEqual( - styleVariables[`--d3fc-local-series-${n}`] - ); - } - }); - - test("initialise gradients with opacity", () => { - initialiseStyles(container.node(), settings); - const result = settings.colorStyles; - - expect(result.gradient.full).toEqual([ - [0, "rgba(77, 52, 47, 0.5)"], - [0.5, "rgba(240, 240, 240, 0.5)"], - [1, "rgba(26, 35, 126, 0.5)"], - ]); - expect(result.gradient.positive).toEqual([ - [0, "rgba(220, 237, 200, 0.5)"], - [1, "rgba(26, 35, 126, 0.5)"], - ]); - expect(result.gradient.negative).toEqual([ - [0, "rgba(77, 52, 47, 0.5)"], - [1, "rgba(254, 235, 101, 0.5)"], - ]); - }); -}); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js deleted file mode 100644 index c3f2f89ab9..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js +++ /dev/null @@ -1,157 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -// const * as linearAxis = require("../../../../src/js/axis/linearAxis"); -// const * as seriesRange = require("../../../../src/js/series/seriesRange"); -// const * as sinon = require("sinon"); - -const settings = { - colorStyles: { - gradient: { - positive: [ - [0, "rgb(0, 0, 0)"], - [1, "rgb(100, 0, 0)"], - ], - negative: [ - [0, "rgb(0, 0, 0)"], - [1, "rgb(0, 100, 0)"], - ], - full: [ - [0, "rgb(100, 0, 0)"], - [0.5, "rgb(0, 0, 0)"], - [1, "rgb(0, 0, 100)"], - ], - }, - }, -}; - -describe.skip("seriesRange", () => { - let sandbox; - let domainStub; - - beforeEach(() => { - domainStub = sinon.stub().returns([100, 1100]); - domainStub.valueName = sinon.stub().returns(domainStub); - domainStub.pad = sinon.stub().returns(domainStub); - - sandbox = sinon.createSandbox(); - sandbox.stub(linearAxis, "domain").returns(domainStub); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe("seriesLinearRange should", () => { - test("get extent from domain", () => { - const data = ["a", "b", "c"]; - seriesRange.seriesLinearRange(settings, data, "test-value"); - - sinon.assert.calledWith(domainStub.valueName, "test-value"); - sinon.assert.calledWith(domainStub.pad, [0, 0]); - sinon.assert.calledWith(domainStub, data); - }); - - test("create linear range from data extent", () => { - const result = seriesRange.seriesLinearRange( - settings, - [], - "test-value" - ); - - expect(result.domain()).toEqual([100, 1100]); - result.range([0, 100]); - - expect(result(100)).toEqual(0); - expect(result(500)).toEqual(40); - expect(result(700)).toEqual(60); - expect(result(1100)).toEqual(100); - }); - - test("create linear range from custom extent", () => { - const result = seriesRange.seriesLinearRange( - settings, - [], - "test-value", - [200, 300] - ); - - sinon.assert.notCalled(domainStub); - expect(result.domain()).toEqual([200, 300]); - }); - }); - - describe("seriesColorRange should", () => { - test("get extent from domain", () => { - const data = ["a", "b", "c"]; - seriesRange.seriesColorRange(settings, data, "test-value"); - - sinon.assert.calledWith(domainStub.valueName, "test-value"); - sinon.assert.calledWith(domainStub.pad, [0, 0]); - sinon.assert.calledWith(domainStub, data); - }); - - test("return color range from data extent", () => { - const data = []; - const result = seriesRange.seriesColorRange( - settings, - data, - "test-value" - ); - - expect(result.domain()).toEqual([100, 1100]); - - expect(result(100)).toEqual("rgb(0, 0, 0)"); - expect(result(500)).toEqual("rgb(40, 0, 0)"); - expect(result(700)).toEqual("rgb(60, 0, 0)"); - expect(result(1100)).toEqual("rgb(100, 0, 0)"); - }); - - test("create linear range from custom extent", () => { - const result = seriesRange.seriesColorRange( - settings, - [], - "test-value", - [200, 300] - ); - - sinon.assert.notCalled(domainStub); - expect(result.domain()).toEqual([200, 300]); - }); - - test("return negative color range from custom extent", () => { - const result = seriesRange.seriesColorRange( - settings, - [], - "test-value", - [-200, -100] - ); - - expect(result(-200)).toEqual("rgb(0, 0, 0)"); - expect(result(-160)).toEqual("rgb(0, 40, 0)"); - expect(result(-140)).toEqual("rgb(0, 60, 0)"); - expect(result(-100)).toEqual("rgb(0, 100, 0)"); - }); - - test("return full color range from custom extent", () => { - const result = seriesRange.seriesColorRange( - settings, - [], - "test-value", - [-100, 100] - ); - - expect(result(-100)).toEqual("rgb(100, 0, 0)"); - expect(result(-40)).toEqual("rgb(40, 0, 0)"); - expect(result(0)).toEqual("rgb(0, 0, 0)"); - expect(result(40)).toEqual("rgb(0, 0, 40)"); - expect(result(100)).toEqual("rgb(0, 0, 100)"); - }); - }); -}); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/generateHTML.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/tooltip/generateHTML.spec.js deleted file mode 100644 index 25eaa1d23e..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/generateHTML.spec.js +++ /dev/null @@ -1,166 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ -const { select } = require("d3"); -const { generateHtml } = require("../../../../src/js/tooltip/generateHTML"); -const { - get_type_config, -} = require("@finos/perspective/dist/cjs/perspective.node.js"); - -describe("tooltip generateHTML should", () => { - let tooltip = null; - let settings = null; - - beforeEach(() => { - tooltip = select("body").append("div").classed("tooltip-test", true); - tooltip.append("div").attr("id", "tooltip-values"); - - settings = { - crossValues: [], - splitValues: [], - mainValues: [{ name: "main-1", type: "integer" }], - realValues: ["main-1"], - }; - }); - afterEach(() => { - tooltip.remove(); - }); - - const getContent = () => { - const content = []; - tooltip.selectAll("li").each((d, i, nodes) => { - content.push(select(nodes[i]).text()); - }); - return content; - }; - - test("show single mainValue", () => { - const data = { - mainValue: 101, - }; - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual(["main-1: 101"]); - }); - - test("show multiple mainValues", () => { - settings.mainValues.push({ name: "main-2", type: "float" }); - settings.realValues.push("main-2"); - const data = { - mainValues: [101, 202.22], - }; - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual(["main-1: 101", "main-2: 202.22"]); - }); - - test("format mainValue as date", () => { - settings.mainValues[0].type = "datetime"; - const testDate = new Date("2019-04-03T15:15Z"); - const data = { - mainValue: testDate.getTime(), - }; - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual([ - `main-1: ${testDate.toLocaleString( - [], - get_type_config("datetime").format - )}`, - ]); - }); - - test("format mainValue as integer", () => { - settings.mainValues[0].type = "integer"; - const data = { - mainValue: 12345.6789, - }; - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual(["main-1: 12,345"]); - }); - - test("format mainValue as decimal", () => { - settings.mainValues[0].type = "float"; - const data = { - mainValue: 12345.6789, - }; - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual(["main-1: 12,345.679"]); - }); - - test("show with single crossValue", () => { - settings.crossValues.push({ name: "cross-1", type: "string" }); - const data = { - crossValue: "tc-1", - mainValue: 101, - }; - - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual(["cross-1: tc-1", "main-1: 101"]); - }); - - test("show with multiple crossValues", () => { - settings.crossValues.push({ name: "cross-1", type: "string" }); - settings.crossValues.push({ name: "cross-2", type: "integer" }); - const data = { - crossValue: "tc-1|1001", - mainValue: 101, - }; - - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual([ - "cross-1: tc-1", - "cross-2: 1,001", - "main-1: 101", - ]); - }); - - test("show with single splitValue", () => { - settings.splitValues.push({ name: "split-1", type: "string" }); - const data = { - key: "ts-1", - mainValue: 101, - }; - - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual(["split-1: ts-1", "main-1: 101"]); - }); - - test("show with multiple splitValues", () => { - settings.splitValues.push({ name: "split-1", type: "string" }); - settings.splitValues.push({ name: "split-2", type: "integer" }); - const data = { - key: "ts-1|1001", - mainValue: 101, - }; - - generateHtml(tooltip, data, settings); - expect(getContent()).toEqual([ - "split-1: ts-1", - "split-2: 1,001", - "main-1: 101", - ]); - }); - - test("Provide default number formatting for null and undefined types", () => { - settings.splitValues.push({ name: "split-1", type: "string" }); - settings.splitValues.push({ name: "split-2", type: null }); - settings.splitValues.push({ name: "split-3", type: undefined }); - - const data = { - key: "ts-1", - mainValue: 101, - }; - - generateHtml(tooltip, data, settings); - - expect(getContent()).toEqual([ - "split-1: ts-1", - "split-2: -", - "split-3: -", - "main-1: 101", - ]); - }); -}); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/selectionData.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/tooltip/selectionData.spec.js deleted file mode 100644 index dbe11fc01f..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/selectionData.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -const { getSplitValues } = require("../../../../src/js/tooltip/selectionData"); - -describe("tooltip selectionData should", () => { - let settings = null; - - beforeEach(() => { - settings = { - crossValues: [], - splitValues: [], - mainValues: [{ name: "main-1", type: "integer" }], - realValues: ["main-1"], - }; - }); - - describe("getSplitValues", () => { - test("handle empty splitValues array", () => { - expect(getSplitValues(null, settings)).toEqual([]); - }); - - test("handle data that contains `|` in key property", () => { - settings.splitValues.push({ name: "split-1", type: "integer" }); - const data = { - key: "1|2", - }; - - expect(getSplitValues(data, settings)).toEqual([ - { name: "split-1", value: 1 }, - ]); - }); - - test("handle data that does not contain `|` in key property", () => { - settings.splitValues.push({ name: "split-1", type: "integer" }); - const data = { - key: "1", - }; - - expect(getSplitValues(data, settings)).toEqual([ - { name: "split-1", value: 1 }, - ]); - }); - - test("handle data that contains a `|` in mainValue property", () => { - settings.splitValues.push({ name: "split-1", type: "integer" }); - const data = { - mainValue: "1|2", - }; - - expect(getSplitValues(data, settings)).toEqual([ - { name: "split-1", value: 1 }, - ]); - }); - - test("handle data that does not contain a `|` in mainValue property", () => { - settings.splitValues.push({ name: "split-1", type: "integer" }); - const data = { - mainValue: "1", - }; - - expect(getSplitValues(data, settings)).toEqual([ - { name: "split-1", value: 1 }, - ]); - }); - - test("handle data with no key or mainValue property", () => { - settings.splitValues.push({ name: "split-1", type: "integer" }); - const data = { - mainValue: "1", - }; - - expect(getSplitValues(data, settings)).toEqual([ - { name: "split-1", value: 1 }, - ]); - }); - }); -}); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/tooltip.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/tooltip/tooltip.spec.js deleted file mode 100644 index c65442f92f..0000000000 --- a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/tooltip.spec.js +++ /dev/null @@ -1,104 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ -const { select } = require("d3"); -const { tooltip } = require("../../../../src/js/tooltip/tooltip"); - -describe("tooltip with", () => { - let container = null; - let testElement = null; - let settings = null; - const data = [{ mainValue: 101 }]; - - const awaitTransition = (selection) => { - return new Promise((resolve) => { - const transition = selection.transition(); - let n = transition.size(); - if (n === 0) { - resolve(); - } - transition.on("end", () => { - if (!--n) { - resolve(); - } - }); - }); - }; - - beforeEach(() => { - container = select("body") - .append("div") - .attr("id", "container") - .classed("chart", true); - - testElement = container - .selectAll("div.element") - .data(data) - .enter() - .append("div") - .classed("element", true) - .style("position", "absolute") - .style("top", "150px") - .style("left", "300px") - .style("width", "25px") - .style("height", "40px"); - - settings = { - crossValues: [], - splitValues: [], - mainValues: [{ name: "main-1", type: "integer" }], - }; - }); - afterEach(() => { - container.remove(); - }); - - describe("on-hover should", () => { - let tooltipDiv; - beforeEach(async () => { - tooltip().settings(settings)(testElement); - tooltipDiv = container.select("div.tooltip"); - await awaitTransition(tooltipDiv); - }); - - test("not show a tooltip initially", () => { - expect(tooltipDiv.style("opacity")).toEqual("0"); - }); - - test("show a tooltip on mouse over", async () => { - testElement.node().dispatchEvent(new MouseEvent("mouseover")); - await awaitTransition(tooltipDiv); - - expect(tooltipDiv.style("opacity")).not.toEqual("0"); - }); - }); - - describe("always-show should", () => { - let tooltipDiv; - let tooltipComponent; - beforeEach(async () => { - tooltipComponent = tooltip().settings(settings).alwaysShow(true); - - tooltipComponent(testElement); - tooltipDiv = container.select("div.tooltip"); - await awaitTransition(tooltipDiv); - }); - - test("show a tooltip initially", () => { - expect(tooltipDiv.style("opacity")).not.toEqual("0"); - }); - - test("hide a tooltip if no element", async () => { - tooltipComponent(container.select("div.notexists")); - await awaitTransition(tooltipDiv); - expect(Math.floor(parseFloat(tooltipDiv.style("opacity")))).toEqual( - 0 - ); - }); - }); -}); diff --git a/packages/perspective/package.json b/packages/perspective/package.json index 4c19dfc694..2e42853ec5 100644 --- a/packages/perspective/package.json +++ b/packages/perspective/package.json @@ -38,23 +38,10 @@ "docs": "npm-run-all docs:jsdoc docs:deploy", "docs:jsdoc": "jsdoc2md src/js/perspective.js -p list --separators --no-gfm > README.md", "docs:deploy": "(echo \"---\nid: perspective\ntitle: perspective API\n---\n\n\"; cat README.md) > ../../docs/docs/obj/perspective.md", - "test:run": "jest --color --ci --noStackTrace --testPathIgnorePatterns='timezone'", - "test_timezone:run": "jest --color --ci --config=test/config/timezone/jest.config.js --rootDir=. timezone.spec.js", + "test:run": "jest --color --ci --rootDir=. --config=../../tools/perspective-test/jest.config.js", "test": "npm-run-all test:run", "clean": "rimraf dist" }, - "jest": { - "roots": [ - "test/js" - ], - "transform": {}, - "transformIgnorePatterns": [ - "/build/" - ], - "verbose": true, - "testURL": "http://localhost/", - "automock": false - }, "dependencies": { "fflate": "^0.7.2", "ws": "^6.1.2" diff --git a/packages/perspective/test/config/timezone/jest.config.js b/packages/perspective/test/config/timezone/jest.config.js deleted file mode 100644 index d0267791b8..0000000000 --- a/packages/perspective/test/config/timezone/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -module.exports = { - verbose: true, - globalSetup: "/test/config/timezone/set_timezone.js", -}; diff --git a/packages/perspective/test/config/timezone/set_timezone.js b/packages/perspective/test/config/timezone/set_timezone.js deleted file mode 100644 index 915f9343c4..0000000000 --- a/packages/perspective/test/config/timezone/set_timezone.js +++ /dev/null @@ -1,12 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -module.exports = async () => { - process.env.TZ = "America/New_York"; -}; diff --git a/packages/perspective/test/js/clear.js b/packages/perspective/test/js/clear.js index cb71c47ccf..463e9a32c6 100644 --- a/packages/perspective/test/js/clear.js +++ b/packages/perspective/test/js/clear.js @@ -7,6 +7,12 @@ * */ +function it_old_behavior(name, capture) { + it(name, function (done) { + capture(done); + }); +} + module.exports = (perspective) => { describe("Clear", function () { it("removes the rows from the table", async function () { @@ -57,74 +63,80 @@ module.exports = (perspective) => { table.delete(); }); - it("replaces the rows in the table with the input data and fires an on_update", async function (done) { - const table = await perspective.table([ - { x: 1, y: 2 }, - { x: 3, y: 4 }, - ]); - - const view = await table.view(); - - const callback = async function (updated) { - expect(updated.port_id).toEqual(0); - const json = await view.to_json(); - expect(json).toHaveLength(1); - expect(json).toEqual([{ x: 5, y: 6 }]); - view.delete(); - table.delete(); - done(); - }; - - view.on_update(callback); - - let json = await view.to_json(); - expect(json).toHaveLength(2); - expect(json).toEqual([ - { x: 1, y: 2 }, - { x: 3, y: 4 }, - ]); - - table.replace([{ x: 5, y: 6 }]); - }); - - it("replaces the rows in the table with the input data and fires an on_update with the correct delta", async function (done) { - const table = await perspective.table([ - { x: 1, y: 2 }, - { x: 3, y: 4 }, - ]); - - const view = await table.view(); - - const callback = async function (updated) { - expect(updated.port_id).toEqual(0); - const table2 = await perspective.table(updated.delta); - const view2 = await table2.view(); - - const json = await view.to_json(); - expect(json).toHaveLength(1); - expect(json).toEqual([{ x: 5, y: 6 }]); - - const json2 = await view2.to_json(); - expect(json2).toEqual(json); - - view2.delete(); - table2.delete(); - view.delete(); - table.delete(); - done(); - }; - - view.on_update(callback, { mode: "row" }); - - let json = await view.to_json(); - expect(json).toHaveLength(2); - expect(json).toEqual([ - { x: 1, y: 2 }, - { x: 3, y: 4 }, - ]); - - table.replace([{ x: 5, y: 6 }]); - }); + it_old_behavior( + "replaces the rows in the table with the input data and fires an on_update", + async function (done) { + const table = await perspective.table([ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + ]); + + const view = await table.view(); + + const callback = async function (updated) { + expect(updated.port_id).toEqual(0); + const json = await view.to_json(); + expect(json).toHaveLength(1); + expect(json).toEqual([{ x: 5, y: 6 }]); + view.delete(); + table.delete(); + done(); + }; + + view.on_update(callback); + + let json = await view.to_json(); + expect(json).toHaveLength(2); + expect(json).toEqual([ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + ]); + + table.replace([{ x: 5, y: 6 }]); + } + ); + + it_old_behavior( + "replaces the rows in the table with the input data and fires an on_update with the correct delta", + async function (done) { + const table = await perspective.table([ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + ]); + + const view = await table.view(); + + const callback = async function (updated) { + expect(updated.port_id).toEqual(0); + const table2 = await perspective.table(updated.delta); + const view2 = await table2.view(); + + const json = await view.to_json(); + expect(json).toHaveLength(1); + expect(json).toEqual([{ x: 5, y: 6 }]); + + const json2 = await view2.to_json(); + expect(json2).toEqual(json); + + view2.delete(); + table2.delete(); + view.delete(); + table.delete(); + done(); + }; + + view.on_update(callback, { mode: "row" }); + + let json = await view.to_json(); + expect(json).toHaveLength(2); + expect(json).toEqual([ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + ]); + + table.replace([{ x: 5, y: 6 }]); + } + ); it("replace the rows in the table atomically", async function () { const table = await perspective.table([ diff --git a/packages/perspective/test/js/delete.js b/packages/perspective/test/js/delete.js index 1a64014eb7..75129a5596 100644 --- a/packages/perspective/test/js/delete.js +++ b/packages/perspective/test/js/delete.js @@ -7,6 +7,12 @@ * */ +function it_old_behavior(name, capture) { + it(name, function (done) { + capture(done); + }); +} + module.exports = (perspective) => { describe("Delete", function () { it("calls all delete callbacks registered on table", async function () { @@ -77,30 +83,36 @@ module.exports = (perspective) => { await table.delete(); }); - it("properly removes a failed delete callback on a table", async function (done) { - const table = await perspective.table([{ x: 1 }]); - - // when a callback throws, it should delete that callback - table.on_delete(() => { - throw new Error("something went wrong!"); - }); - - table.delete(); - done(); - }); - - it("properly removes a failed delete callback on a view", async function (done) { - const table = await perspective.table([{ x: 1 }]); - const view = await table.view(); - - // when a callback throws, it should delete that callback - view.on_delete(() => { - throw new Error("something went wrong!"); - }); - - view.delete(); - table.delete(); - done(); - }); + it_old_behavior( + "properly removes a failed delete callback on a table", + async function (done) { + const table = await perspective.table([{ x: 1 }]); + + // when a callback throws, it should delete that callback + table.on_delete(() => { + throw new Error("something went wrong!"); + }); + + table.delete(); + done(); + } + ); + + it_old_behavior( + "properly removes a failed delete callback on a view", + async function (done) { + const table = await perspective.table([{ x: 1 }]); + const view = await table.view(); + + // when a callback throws, it should delete that callback + view.on_delete(() => { + throw new Error("something went wrong!"); + }); + + view.delete(); + table.delete(); + done(); + } + ); }); }; diff --git a/packages/perspective/test/js/delta.js b/packages/perspective/test/js/delta.js index 3e0077ec80..852ccedbc0 100644 --- a/packages/perspective/test/js/delta.js +++ b/packages/perspective/test/js/delta.js @@ -47,10 +47,16 @@ async function match_delta( await table.delete(); } +function it_old_behavior(name, capture) { + it(name, function (done) { + capture(done); + }); +} + module.exports = (perspective) => { describe("Row delta", function () { describe("0-sided row delta", function () { - it("returns changed rows", async function (done) { + 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( @@ -69,59 +75,73 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("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" } - ); - table.update(partial_change_y); - }); - - it("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" } - ); - 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("returns added rows", async function (done) { + 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" } + ); + 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" } + ); + 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( @@ -140,26 +160,29 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("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" } - ); - table.update(data); - }); - - it("returns deleted columns", async function (done) { + 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" } + ); + 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( @@ -181,105 +204,129 @@ module.exports = (perspective) => { ]); }); - it("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" } - ); - table.update(partial_change_y); - }); - - it("returns changed rows in sorted context from schema", async function (done) { - let table = await perspective.table( - { + 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" } + ); + 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" } + ); + 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", - }, - { 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" } - ); - 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("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" } - ); - table.update(data); - }); - - it("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" } - ); - table.update(partial_change_nonseq); - }); + }); + 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" } + ); + 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" } + ); + table.update(partial_change_nonseq); + } + ); }); describe("0-sided row delta, randomized column order", function () { - it("returns changed rows", async function (done) { + 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({ @@ -301,42 +348,49 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("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" } - ); - 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("returns added rows", async function (done) { + 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" } + ); + 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({ @@ -358,48 +412,58 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("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" } - ); - table.update(partial_change_y); - }); - - it("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" } - ); - table.update(data); - }); - - it("returns deleted columns", async function (done) { + 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" } + ); + 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" } + ); + 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({ @@ -424,28 +488,35 @@ module.exports = (perspective) => { ]); }); - it("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" } - ); - table.update(partial_change_nonseq); - }); + 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" } + ); + table.update(partial_change_nonseq); + } + ); }); describe("0-sided row delta, column order subset", function () { - it("returns changed rows", async function (done) { + it_old_behavior("returns changed rows", async function (done) { let table = await perspective.table(data, { index: "x" }); let view = await table.view({ columns: ["y"], @@ -463,41 +534,48 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("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" } - ); - 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("returns added rows", async function (done) { + 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" } + ); + 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"], @@ -515,33 +593,36 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("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" } - ); - table.update(data); - }); - - it("returns deleted rows", async function (done) { + 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" } + ); + 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"], @@ -562,29 +643,32 @@ module.exports = (perspective) => { ]); }); - it("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" } - ); - table.update(partial_change_nonseq); - }); + 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" } + ); + table.update(partial_change_nonseq); + } + ); }); describe("1-sided row delta", function () { - it("returns changed rows", async function (done) { + 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"], @@ -653,46 +737,62 @@ module.exports = (perspective) => { }); }); - it("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" } - ); - table.update(partial_change_y); - }); - - it("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" } - ); - table.update(partial_change_z); - }); - - it("returns added rows", async function (done) { - let table = await perspective.table(data); + 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" } + ); + 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" } + ); + 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" }, @@ -714,27 +814,37 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("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" } - ); - table.update(partial_change_y); - }); - - it("returns deleted columns", async function (done) { + 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" } + ); + 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"], @@ -757,130 +867,164 @@ module.exports = (perspective) => { ]); }); - it("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" } - ); - table.update(partial_change_nonseq); - }); - - it("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" } - ); - - table.update(partial_change_y); - }); + 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" } + ); + 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" } + ); + + table.update(partial_change_y); + } + ); }); describe("2-sided row delta", function () { - it("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" } - ); - table.update(partial_change_y); - }); - - it("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" } - ); - table.update(partial_change_y); - }); - - it("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" } - ); - table.update(partial_change_y); - }); + 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" } + ); + 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" } + ); + 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" } + ); + table.update(partial_change_y); + } + ); it.skip("returns changed rows when updated data in group by multi, hidden sort", async function (done) { let table = await perspective.table(data, { index: "y" }); @@ -917,120 +1061,154 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("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" } - ); - table.update(partial_change_z); - }); - - it("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" } - ); - table.update(partial_change_z); - }); - - it("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" } - ); - table.update(partial_change_y_z); - }); - - it("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" } - ); - table.update(partial_change_y_z); - }); - - it("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" } - ); - table.update(partial_change_z); - }); - - it("returns added rows", async function (done) { + 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" } + ); + 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" } + ); + 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" } + ); + 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" } + ); + 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" } + ); + 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"], @@ -1053,7 +1231,7 @@ module.exports = (perspective) => { table.update(partial_change_y); }); - it("returns deleted columns", async function (done) { + 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"], @@ -1083,361 +1261,427 @@ module.exports = (perspective) => { ]); }); - it("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" } - ); - table.update([ - { x: 1, y: null }, - { x: 2, y: null }, - { x: 4, y: null }, - ]); - }); - - it("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" } - ); - table.update(partial_change_nonseq); - }); - - it("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" } - ); - table.update(partial_change_nonseq); - }); - - it("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", - } - ); - table.update(partial_change_y); - }); - - it("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" } - ); - - table.update(partial_change_y); - }); - - it("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", - } - ); - table.update(partial_change_y); - }); - - it("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", - } - ); - table.update(partial_change_y); - }); - - it("returns changed rows, col only sorted change not in pivot", async function (done) { - let table = await perspective.table( - { x: [1], y: [100] }, - { + 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" } + ); + 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" } + ); + 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" } + ); + 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"], - 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", - } - ); - table.update([{ x: 3, y: 100 }]); - }); - - it("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" } - ); - table.update(partial_change_y); - }); + }); + 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", + } + ); + 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" } + ); + + 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", + } + ); + 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", + } + ); + 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", + } + ); + 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" } + ); + table.update(partial_change_y); + } + ); }); }); }; diff --git a/packages/perspective/test/js/expressions/deltas.js b/packages/perspective/test/js/expressions/deltas.js index fbf900786f..f502118fff 100644 --- a/packages/perspective/test/js/expressions/deltas.js +++ b/packages/perspective/test/js/expressions/deltas.js @@ -16,6 +16,12 @@ const match_delta = async function (perspective, delta, expected) { await table.delete(); }; +function it_old_behavior(name, capture) { + it(name, function (done) { + capture(done); + }); +} + /** * Tests the correctness of updates on Tables with expression columns created * through `View` and deltas created through `on_update`. @@ -23,466 +29,547 @@ const match_delta = async function (perspective, delta, expected) { module.exports = (perspective) => { describe("Expression column update deltas", function () { describe("0-sided expression column deltas", function () { - it("Returns appended rows for normal and expression columns", async function (done) { - const table = await perspective.table({ - x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"], - }); - const view = await table.view({ - expressions: ['lower("y")', '-"x"'], - }); - - view.on_update( - async function (updated) { - const expected = [ - { - x: 1, - y: "HELLO", - 'lower("y")': "hello", - '-"x"': -1, - }, - { - x: 3, - y: "WORLD", - 'lower("y")': "world", - '-"x"': -3, - }, - ]; - await match_delta(perspective, updated.delta, expected); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); - }); - - it("Returns appended rows for normal and expression columns from schema", async function (done) { - const table = await perspective.table({ - x: "integer", - y: "string", - }); - const view = await table.view({ - expressions: ['upper("y")'], - }); - - view.on_update( - async function (updated) { - const expected = [ - { x: 1, y: "a", 'upper("y")': "A" }, - { x: 2, y: "b", 'upper("y")': "B" }, - { x: 3, y: "c", 'upper("y")': "C" }, - { x: 4, y: "d", 'upper("y")': "D" }, - ]; - await match_delta(perspective, updated.delta, expected); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ - x: [1, 2, 3, 4], - y: ["a", "b", "c", "d"], - }); - }); - - it("Returns partially updated rows for normal and expression columns", async function (done) { - const table = await perspective.table( - { + it_old_behavior( + "Returns appended rows for normal and expression columns", + async function (done) { + const table = await perspective.table({ x: [1, 2, 3, 4], y: ["A", "B", "C", "D"], - }, - { index: "x" } - ); - const view = await table.view({ - expressions: ['lower("y")'], - }); - - view.on_update( - async function (updated) { - const full = await view.to_columns(); - const expected = [ - { x: 1, y: "HELLO", 'lower("y")': "hello" }, - { x: 3, y: "WORLD", 'lower("y")': "world" }, - ]; - expect(full).toEqual({ + }); + const view = await table.view({ + expressions: ['lower("y")', '-"x"'], + }); + + view.on_update( + async function (updated) { + const expected = [ + { + x: 1, + y: "HELLO", + 'lower("y")': "hello", + '-"x"': -1, + }, + { + x: 3, + y: "WORLD", + 'lower("y")': "world", + '-"x"': -3, + }, + ]; + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); + } + ); + + it_old_behavior( + "Returns appended rows for normal and expression columns from schema", + async function (done) { + const table = await perspective.table({ + x: "integer", + y: "string", + }); + const view = await table.view({ + expressions: ['upper("y")'], + }); + + view.on_update( + async function (updated) { + const expected = [ + { x: 1, y: "a", 'upper("y")': "A" }, + { x: 2, y: "b", 'upper("y")': "B" }, + { x: 3, y: "c", 'upper("y")': "C" }, + { x: 4, y: "d", 'upper("y")': "D" }, + ]; + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ + x: [1, 2, 3, 4], + y: ["a", "b", "c", "d"], + }); + } + ); + + it_old_behavior( + "Returns partially updated rows for normal and expression columns", + async function (done) { + const table = await perspective.table( + { x: [1, 2, 3, 4], - y: ["HELLO", "B", "WORLD", "D"], - 'lower("y")': ["hello", "b", "world", "d"], - }); - await match_delta(perspective, updated.delta, expected); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); - }); - - it("Returns appended rows with missing columns for normal and expression columns", async function (done) { - const self = this; - const table = await perspective.table({ - x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"], - }); - self.view = await table.view({ - expressions: ['lower("y")'], - }); - - self.view.on_update( - async function (updated) { - const full = await self.view.to_columns(); - const expected = [ - { x: 1, y: null, 'lower("y")': null }, - { x: 3, y: null, 'lower("y")': null }, - ]; - expect(full).toEqual({ - x: [1, 2, 3, 4, 1, 3], - y: ["A", "B", "C", "D", null, null], - 'lower("y")': ["a", "b", "c", "d", null, null], - }); - await match_delta(perspective, updated.delta, expected); - await self.view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3] }); - }); + y: ["A", "B", "C", "D"], + }, + { index: "x" } + ); + const view = await table.view({ + expressions: ['lower("y")'], + }); + + view.on_update( + async function (updated) { + const full = await view.to_columns(); + const expected = [ + { x: 1, y: "HELLO", 'lower("y")': "hello" }, + { x: 3, y: "WORLD", 'lower("y")': "world" }, + ]; + expect(full).toEqual({ + x: [1, 2, 3, 4], + y: ["HELLO", "B", "WORLD", "D"], + 'lower("y")': ["hello", "b", "world", "d"], + }); + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); + } + ); + + it_old_behavior( + "Returns appended rows with missing columns for normal and expression columns", + async function (done) { + const self = this; + const table = await perspective.table({ + x: [1, 2, 3, 4], + y: ["A", "B", "C", "D"], + }); + self.view = await table.view({ + expressions: ['lower("y")'], + }); + + self.view.on_update( + async function (updated) { + const full = await self.view.to_columns(); + const expected = [ + { x: 1, y: null, 'lower("y")': null }, + { x: 3, y: null, 'lower("y")': null }, + ]; + expect(full).toEqual({ + x: [1, 2, 3, 4, 1, 3], + y: ["A", "B", "C", "D", null, null], + 'lower("y")': ["a", "b", "c", "d", null, null], + }); + await match_delta( + perspective, + updated.delta, + expected + ); + await self.view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3] }); + } + ); }); describe("1-sided expression column deltas", function () { - it("Returns appended rows for normal and expression columns, 1-sided", 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: ['lower("y")'], - expressions: ['lower("y")'], - }); - - view.on_update( - async function (updated) { - const expected = [ - { x: 14, y: 6, 'lower("y")': 6 }, - { x: 1, y: 1, 'lower("y")': 1 }, - { x: 3, y: 1, 'lower("y")': 1 }, - ]; - await match_delta(perspective, updated.delta, expected); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); - }); - - it("Returns appended rows for normal and expression columns, 1-sided hidden sorted", 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: ['lower("y")'], - expressions: ['lower("y")'], - sort: [['lower("y")', "desc"]], - columns: ["x"], - }); - - view.on_update( - async function (updated) { - const expected = [{ x: 14 }, { x: 3 }, { x: 1 }]; - await match_delta(perspective, updated.delta, expected); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); - }); + it_old_behavior( + "Returns appended rows for normal and expression columns, 1-sided", + 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: ['lower("y")'], + expressions: ['lower("y")'], + }); + + view.on_update( + async function (updated) { + const expected = [ + { x: 14, y: 6, 'lower("y")': 6 }, + { x: 1, y: 1, 'lower("y")': 1 }, + { x: 3, y: 1, 'lower("y")': 1 }, + ]; + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); + } + ); + + it_old_behavior( + "Returns appended rows for normal and expression columns, 1-sided hidden sorted", + 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: ['lower("y")'], + expressions: ['lower("y")'], + sort: [['lower("y")', "desc"]], + columns: ["x"], + }); + + view.on_update( + async function (updated) { + const expected = [{ x: 14 }, { x: 3 }, { x: 1 }]; + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); + } + ); }); describe("2-sided expression column deltas", function () { - it("Returns appended rows for normal and expression columns, 2-sided", async function (done) { - const table = await perspective.table({ - x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"], - }); - const view = await table.view({ - aggregates: { - 'lower("y")': "last", - }, - group_by: ['lower("y")'], - split_by: ["y"], - expressions: ['lower("y")'], - }); - - view.on_update( - async function (updated) { - const expected = [ - { - 'A|lower("y")': "a", - "A|x": 1, - "A|y": 1, - 'B|lower("y")': "b", - "B|x": 2, - "B|y": 1, - 'C|lower("y")': "c", - "C|x": 3, - "C|y": 1, - 'D|lower("y")': "d", - "D|x": 4, - "D|y": 1, - 'HELLO|lower("y")': "hello", - "HELLO|x": 1, - "HELLO|y": 1, - 'WORLD|lower("y")': "world", - "WORLD|x": 3, - "WORLD|y": 1, - }, - { - 'A|lower("y")': null, - "A|x": null, - "A|y": null, - 'B|lower("y")': null, - "B|x": null, - "B|y": null, - 'C|lower("y")': null, - "C|x": null, - "C|y": null, - 'D|lower("y")': null, - "D|x": null, - "D|y": null, - 'HELLO|lower("y")': "hello", - "HELLO|x": 1, - "HELLO|y": 1, - 'WORLD|lower("y")': null, - "WORLD|x": null, - "WORLD|y": null, - }, - { - 'A|lower("y")': null, - "A|x": null, - "A|y": null, - 'B|lower("y")': null, - "B|x": null, - "B|y": null, - 'C|lower("y")': null, - "C|x": null, - "C|y": null, - 'D|lower("y")': null, - "D|x": null, - "D|y": null, - 'HELLO|lower("y")': null, - "HELLO|x": null, - "HELLO|y": null, - 'WORLD|lower("y")': "world", - "WORLD|x": 3, - "WORLD|y": 1, - }, - ]; - await match_delta(perspective, updated.delta, expected); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); - }); + it_old_behavior( + "Returns appended rows for normal and expression columns, 2-sided", + async function (done) { + const table = await perspective.table({ + x: [1, 2, 3, 4], + y: ["A", "B", "C", "D"], + }); + const view = await table.view({ + aggregates: { + 'lower("y")': "last", + }, + group_by: ['lower("y")'], + split_by: ["y"], + expressions: ['lower("y")'], + }); + + view.on_update( + async function (updated) { + const expected = [ + { + 'A|lower("y")': "a", + "A|x": 1, + "A|y": 1, + 'B|lower("y")': "b", + "B|x": 2, + "B|y": 1, + 'C|lower("y")': "c", + "C|x": 3, + "C|y": 1, + 'D|lower("y")': "d", + "D|x": 4, + "D|y": 1, + 'HELLO|lower("y")': "hello", + "HELLO|x": 1, + "HELLO|y": 1, + 'WORLD|lower("y")': "world", + "WORLD|x": 3, + "WORLD|y": 1, + }, + { + 'A|lower("y")': null, + "A|x": null, + "A|y": null, + 'B|lower("y")': null, + "B|x": null, + "B|y": null, + 'C|lower("y")': null, + "C|x": null, + "C|y": null, + 'D|lower("y")': null, + "D|x": null, + "D|y": null, + 'HELLO|lower("y")': "hello", + "HELLO|x": 1, + "HELLO|y": 1, + 'WORLD|lower("y")': null, + "WORLD|x": null, + "WORLD|y": null, + }, + { + 'A|lower("y")': null, + "A|x": null, + "A|y": null, + 'B|lower("y")': null, + "B|x": null, + "B|y": null, + 'C|lower("y")': null, + "C|x": null, + "C|y": null, + 'D|lower("y")': null, + "D|x": null, + "D|y": null, + 'HELLO|lower("y")': null, + "HELLO|x": null, + "HELLO|y": null, + 'WORLD|lower("y")': "world", + "WORLD|x": 3, + "WORLD|y": 1, + }, + ]; + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); + } + ); }); describe("0-sided expression column deltas with multiple views", function () { - it("`on_update` on a view with expression column should contain expression delta when columns are appended", async function (done) { - const table = await perspective.table({ - x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"], - }); - const view = await table.view({ - expressions: ['lower("y")'], - }); - - const pre_update = await view.to_columns(); - expect(pre_update).toEqual({ - x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"], - 'lower("y")': ["a", "b", "c", "d"], - }); - - view.on_update( - async function (updated) { - const expected = [ - { x: 1, y: null, 'lower("y")': null }, - { x: 3, y: null, 'lower("y")': null }, - ]; - const full = await view.to_columns(); - expect(full).toEqual({ - x: [1, 2, 3, 4, 1, 3], - y: ["A", "B", "C", "D", null, null], - 'lower("y")': ["a", "b", "c", "d", null, null], - }); - await match_delta(perspective, updated.delta, expected); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3] }); - }); - - it("`on_update` on a view with expression column should contain expression delta when columns are updated", async function (done) { - const table = await perspective.table( - { + it_old_behavior( + "`on_update` on a view with expression column should contain expression delta when columns are appended", + async function (done) { + const table = await perspective.table({ x: [1, 2, 3, 4], y: ["A", "B", "C", "D"], - }, - { index: "x" } - ); - const view = await table.view({ - expressions: ['lower("y")'], - }); - - const pre_update = await view.to_columns(); - - expect(pre_update).toEqual({ - x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"], - 'lower("y")': ["a", "b", "c", "d"], - }); - - view.on_update( - async function (updated) { - const expected = [ - { x: 1, y: "ABCD", 'lower("y")': "abcd" }, - { x: 3, y: null, 'lower("y")': null }, - ]; - const full = await view.to_columns(); - expect(full).toEqual({ - x: [1, 2, 3, 4], - y: ["ABCD", "B", null, "D"], - 'lower("y")': ["abcd", "b", null, "d"], - }); - await match_delta(perspective, updated.delta, expected); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3], y: ["ABCD", null] }); - }); - - it("`on_update` on different views with different expression columns should only be notified of their columns", async function (done) { - const table = await perspective.table( - { + }); + const view = await table.view({ + expressions: ['lower("y")'], + }); + + const pre_update = await view.to_columns(); + expect(pre_update).toEqual({ x: [1, 2, 3, 4], y: ["A", "B", "C", "D"], - }, - { - index: "x", - } - ); - - const view = await table.view({ - expressions: ['lower("y")'], - }); - - const view2 = await table.view({ - expressions: ['-"x"'], - }); - - view.on_update( - async function (updated) { - const expected = [ - { x: 1, y: "HELLO", 'lower("y")': "hello" }, - { x: 3, y: "WORLD", 'lower("y")': "world" }, - ]; - const full = await view.to_columns(); - expect(full).toEqual({ + 'lower("y")': ["a", "b", "c", "d"], + }); + + view.on_update( + async function (updated) { + const expected = [ + { x: 1, y: null, 'lower("y")': null }, + { x: 3, y: null, 'lower("y")': null }, + ]; + const full = await view.to_columns(); + expect(full).toEqual({ + x: [1, 2, 3, 4, 1, 3], + y: ["A", "B", "C", "D", null, null], + 'lower("y")': ["a", "b", "c", "d", null, null], + }); + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3] }); + } + ); + + it_old_behavior( + "`on_update` on a view with expression column should contain expression delta when columns are updated", + async function (done) { + const table = await perspective.table( + { x: [1, 2, 3, 4], - y: ["HELLO", "B", "WORLD", "D"], - 'lower("y")': ["hello", "b", "world", "d"], - }); - await match_delta(perspective, updated.delta, expected); - await view.delete(); - }, - { mode: "row" } - ); - - view2.on_update( - async function (updated) { - const expected = [ - { x: 1, y: "HELLO", '-"x"': -1 }, - { x: 3, y: "WORLD", '-"x"': -3 }, - ]; - const full = await view2.to_columns(); - expect(full).toEqual({ + y: ["A", "B", "C", "D"], + }, + { index: "x" } + ); + const view = await table.view({ + expressions: ['lower("y")'], + }); + + const pre_update = await view.to_columns(); + + expect(pre_update).toEqual({ + x: [1, 2, 3, 4], + y: ["A", "B", "C", "D"], + 'lower("y")': ["a", "b", "c", "d"], + }); + + view.on_update( + async function (updated) { + const expected = [ + { x: 1, y: "ABCD", 'lower("y")': "abcd" }, + { x: 3, y: null, 'lower("y")': null }, + ]; + const full = await view.to_columns(); + expect(full).toEqual({ + x: [1, 2, 3, 4], + y: ["ABCD", "B", null, "D"], + 'lower("y")': ["abcd", "b", null, "d"], + }); + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3], y: ["ABCD", null] }); + } + ); + + it_old_behavior( + "`on_update` on different views with different expression columns should only be notified of their columns", + async function (done) { + const table = await perspective.table( + { x: [1, 2, 3, 4], - y: ["HELLO", "B", "WORLD", "D"], - '-"x"': [-1, -2, -3, -4], - }); - await match_delta(perspective, updated.delta, expected); - await view2.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); - }); - - it("`on_update` on view without expression column should not be notified of expression column", async function (done) { - const table = await perspective.table({ - x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"], - }); - const view = await table.view(); - - const view2 = await table.view({ - expressions: ['lower("y")'], - }); - - expect(await view2.to_columns()).toEqual({ - x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"], - 'lower("y")': ["a", "b", "c", "d"], - }); - - view.on_update( - async function (updated) { - const expected = [ - { x: 1, y: "abc" }, - { x: 3, y: "def" }, - ]; - await match_delta(perspective, updated.delta, expected); - await view2.delete(); - await view.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ x: [1, 3], y: ["abc", "def"] }); - }); + y: ["A", "B", "C", "D"], + }, + { + index: "x", + } + ); + + const view = await table.view({ + expressions: ['lower("y")'], + }); + + const view2 = await table.view({ + expressions: ['-"x"'], + }); + + view.on_update( + async function (updated) { + const expected = [ + { x: 1, y: "HELLO", 'lower("y")': "hello" }, + { x: 3, y: "WORLD", 'lower("y")': "world" }, + ]; + const full = await view.to_columns(); + expect(full).toEqual({ + x: [1, 2, 3, 4], + y: ["HELLO", "B", "WORLD", "D"], + 'lower("y")': ["hello", "b", "world", "d"], + }); + await match_delta( + perspective, + updated.delta, + expected + ); + await view.delete(); + }, + { mode: "row" } + ); + + view2.on_update( + async function (updated) { + const expected = [ + { x: 1, y: "HELLO", '-"x"': -1 }, + { x: 3, y: "WORLD", '-"x"': -3 }, + ]; + const full = await view2.to_columns(); + expect(full).toEqual({ + x: [1, 2, 3, 4], + y: ["HELLO", "B", "WORLD", "D"], + '-"x"': [-1, -2, -3, -4], + }); + await match_delta( + perspective, + updated.delta, + expected + ); + await view2.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3], y: ["HELLO", "WORLD"] }); + } + ); + + it_old_behavior( + "`on_update` on view without expression column should not be notified of expression column", + async function (done) { + const table = await perspective.table({ + x: [1, 2, 3, 4], + y: ["A", "B", "C", "D"], + }); + const view = await table.view(); + + const view2 = await table.view({ + expressions: ['lower("y")'], + }); + + expect(await view2.to_columns()).toEqual({ + x: [1, 2, 3, 4], + y: ["A", "B", "C", "D"], + 'lower("y")': ["a", "b", "c", "d"], + }); + + view.on_update( + async function (updated) { + const expected = [ + { x: 1, y: "abc" }, + { x: 3, y: "def" }, + ]; + await match_delta( + perspective, + updated.delta, + expected + ); + await view2.delete(); + await view.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update({ x: [1, 3], y: ["abc", "def"] }); + } + ); }); }); }; diff --git a/packages/perspective/test/js/expressions/functionality.js b/packages/perspective/test/js/expressions/functionality.js index c7c4d7db6d..f333f37531 100644 --- a/packages/perspective/test/js/expressions/functionality.js +++ b/packages/perspective/test/js/expressions/functionality.js @@ -9,6 +9,12 @@ const expressions_common = require("./common.js"); +function it_old_behavior(name, capture) { + it(name, function (done) { + capture(done); + }); +} + /** * Tests the functionality of `View`-based expressions, specifically that * existing column/view semantics (pivots, aggregates, columns, sorts, filters) @@ -1595,23 +1601,26 @@ module.exports = (perspective) => { await table.delete(); }); - it("Should not be able to overwrite table column with an expression", async function (done) { - expect.assertions(1); - const table = await perspective.table( - expressions_common.int_float_data - ); - table - .view({ - expressions: ["// w\nupper('abc')"], - }) - .catch((e) => { - expect(e.message).toMatch( - `Abort(): View creation failed: cannot create expression column 'w' that overwrites a column that already exists.\n` - ); - table.delete(); - done(); - }); - }); + it_old_behavior( + "Should not be able to overwrite table column with an expression", + async function (done) { + expect.assertions(1); + const table = await perspective.table( + expressions_common.int_float_data + ); + table + .view({ + expressions: ["// w\nupper('abc')"], + }) + .catch((e) => { + expect(e.message).toMatch( + `Abort(): View creation failed: cannot create expression column 'w' that overwrites a column that already exists.\n` + ); + table.delete(); + done(); + }); + } + ); it("Should be able to overwrite expression column with one that returns a different type", async function () { const table = await perspective.table( @@ -1636,29 +1645,32 @@ module.exports = (perspective) => { table.delete(); }); - it("A new view should not reference expression columns it did not create.", async function (done) { - expect.assertions(2); - const table = await perspective.table( - expressions_common.int_float_data - ); - const view = await table.view({ - expressions: ['"w" + "x"'], - }); - const result = await view.to_columns(); - expect(result['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); - table - .view({ - columns: ['"w" + "x"', "x"], - }) - .catch((e) => { - expect(e.message).toMatch( - `Abort(): Invalid column '"w" + "x"' found in View columns.\n` - ); - view.delete(); - table.delete(); - done(); - }); - }); + it_old_behavior( + "A new view should not reference expression columns it did not create.", + async function (done) { + expect.assertions(2); + const table = await perspective.table( + expressions_common.int_float_data + ); + const view = await table.view({ + expressions: ['"w" + "x"'], + }); + const result = await view.to_columns(); + expect(result['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); + table + .view({ + columns: ['"w" + "x"', "x"], + }) + .catch((e) => { + expect(e.message).toMatch( + `Abort(): Invalid column '"w" + "x"' found in View columns.\n` + ); + view.delete(); + table.delete(); + done(); + }); + } + ); it("A view should be able to shadow real columns with an expression column", async function () { const table = await perspective.table( diff --git a/packages/perspective/test/js/expressions/multiple_views.js b/packages/perspective/test/js/expressions/multiple_views.js index 53e279ca2d..3d2e9bcf77 100644 --- a/packages/perspective/test/js/expressions/multiple_views.js +++ b/packages/perspective/test/js/expressions/multiple_views.js @@ -745,218 +745,232 @@ module.exports = (perspective) => { }); describe("Deltas", () => { - it("Appends delta", async (done) => { - expect.assertions(6); + it("Appends delta", (done) => { + (async () => { + expect.assertions(6); - const table = await perspective.table(expressions_common.data); - - const v1 = await table.view({ - expressions: [`// column \n"x" + 10`], - }); - - const v2 = await table.view({ - expressions: [`// column \n upper("y")`], - }); - - const result = await v1.to_columns(); - const result2 = await v2.to_columns(); + const table = await perspective.table( + expressions_common.data + ); - expect(result["column"]).toEqual([11, 12, 13, 14]); - expect(result2["column"]).toEqual(["A", "B", "C", "D"]); - - v1.on_update( - async (updated) => { - await validate_delta( - "column", - updated.delta, - [11, 12, 13, 14] - ); - const result = await v1.to_columns(); - expect(result["column"]).toEqual([ - 11, 12, 13, 14, 11, 12, 13, 14, - ]); - }, - { mode: "row" } - ); + const v1 = await table.view({ + expressions: [`// column \n"x" + 10`], + }); - v2.on_update( - async (updated) => { - await validate_delta("column", updated.delta, [ - "A", - "B", - "C", - "D", - ]); - const result2 = await v2.to_columns(); - expect(result2["column"]).toEqual([ - "A", - "B", - "C", - "D", - "A", - "B", - "C", - "D", - ]); - await v2.delete(); - await v1.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); + const v2 = await table.view({ + expressions: [`// column \n upper("y")`], + }); - table.update(expressions_common.data); + const result = await v1.to_columns(); + const result2 = await v2.to_columns(); + + expect(result["column"]).toEqual([11, 12, 13, 14]); + expect(result2["column"]).toEqual(["A", "B", "C", "D"]); + + v1.on_update( + async (updated) => { + await validate_delta( + "column", + updated.delta, + [11, 12, 13, 14] + ); + const result = await v1.to_columns(); + expect(result["column"]).toEqual([ + 11, 12, 13, 14, 11, 12, 13, 14, + ]); + }, + { mode: "row" } + ); + + v2.on_update( + async (updated) => { + await validate_delta("column", updated.delta, [ + "A", + "B", + "C", + "D", + ]); + const result2 = await v2.to_columns(); + expect(result2["column"]).toEqual([ + "A", + "B", + "C", + "D", + "A", + "B", + "C", + "D", + ]); + await v2.delete(); + await v1.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); + + table.update(expressions_common.data); + })(); }); - it("Partial update delta", async (done) => { - expect.assertions(6); - - const table = await perspective.table(expressions_common.data, { - index: "x", - }); + it("Partial update delta", (done) => { + (async () => { + expect.assertions(6); - const v1 = await table.view({ - expressions: [`// column \n"x" * 2`], - }); + const table = await perspective.table( + expressions_common.data, + { + index: "x", + } + ); - const v2 = await table.view({ - expressions: [`// column \n upper("y")`], - }); - - const result = await v1.to_columns(); - const result2 = await v2.to_columns(); + const v1 = await table.view({ + expressions: [`// column \n"x" * 2`], + }); - expect(result["column"]).toEqual([2, 4, 6, 8]); - expect(result2["column"]).toEqual(["A", "B", "C", "D"]); + const v2 = await table.view({ + expressions: [`// column \n upper("y")`], + }); - v1.on_update( - async (updated) => { - await validate_delta("column", updated.delta, [ - null, - 4, - 6, - 8, - 20, - ]); - const result = await v1.to_columns(); - expect(result["column"]).toEqual([ - null, - 2, - 4, - 6, - 8, - 20, - ]); - }, - { mode: "row" } - ); + const result = await v1.to_columns(); + const result2 = await v2.to_columns(); + + expect(result["column"]).toEqual([2, 4, 6, 8]); + expect(result2["column"]).toEqual(["A", "B", "C", "D"]); + + v1.on_update( + async (updated) => { + await validate_delta("column", updated.delta, [ + null, + 4, + 6, + 8, + 20, + ]); + const result = await v1.to_columns(); + expect(result["column"]).toEqual([ + null, + 2, + 4, + 6, + 8, + 20, + ]); + }, + { mode: "row" } + ); + + v2.on_update( + async (updated) => { + await validate_delta("column", updated.delta, [ + "DEF", + "X", + "Z", + "Y", + "ABC", + ]); + const result = await v2.to_columns(); + expect(result["column"]).toEqual([ + "DEF", + "A", + "X", + "Z", + "Y", + "ABC", + ]); + await v2.delete(); + await v1.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); - v2.on_update( - async (updated) => { - await validate_delta("column", updated.delta, [ - "DEF", - "X", - "Z", - "Y", - "ABC", - ]); - const result = await v2.to_columns(); - expect(result["column"]).toEqual([ - "DEF", - "A", - "X", - "Z", - "Y", - "ABC", - ]); - await v2.delete(); - await v1.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ - x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"], - }); + table.update({ + x: [2, 4, 3, 10, null], + y: ["X", "Y", "Z", "ABC", "DEF"], + }); + })(); }); - it("Partial update, sorted delta", async (done) => { - expect.assertions(6); - const table = await perspective.table(expressions_common.data, { - index: "x", - }); - - const v1 = await table.view({ - expressions: [`// column \n"x" * 2`], - sort: [["column", "desc"]], - }); - const v2 = await table.view({ - expressions: [`// column \n upper("y")`], - sort: [["column", "desc"]], - }); - - const result = await v1.to_columns(); - const result2 = await v2.to_columns(); - - expect(result["column"]).toEqual([8, 6, 4, 2]); - expect(result2["column"]).toEqual(["D", "C", "B", "A"]); - - v1.on_update( - async (updated) => { - await validate_delta("column", updated.delta, [ - 20, - 8, - 6, - 4, - null, - ]); - const result = await v1.to_columns(); - expect(result["column"]).toEqual([ - 20, - 8, - 6, - 4, - 2, - null, - ]); - }, - { mode: "row" } - ); + it("Partial update, sorted delta", (done) => { + (async () => { + expect.assertions(6); + const table = await perspective.table( + expressions_common.data, + { + index: "x", + } + ); + + const v1 = await table.view({ + expressions: [`// column \n"x" * 2`], + sort: [["column", "desc"]], + }); + const v2 = await table.view({ + expressions: [`// column \n upper("y")`], + sort: [["column", "desc"]], + }); - v2.on_update( - async (updated) => { - await validate_delta("column", updated.delta, [ - "Z", - "Y", - "X", - "DEF", - "ABC", - ]); - const result2 = await v2.to_columns(); - expect(result2["column"]).toEqual([ - "Z", - "Y", - "X", - "DEF", - "ABC", - "A", - ]); - await v2.delete(); - await v1.delete(); - await table.delete(); - done(); - }, - { mode: "row" } - ); + const result = await v1.to_columns(); + const result2 = await v2.to_columns(); + + expect(result["column"]).toEqual([8, 6, 4, 2]); + expect(result2["column"]).toEqual(["D", "C", "B", "A"]); + + v1.on_update( + async (updated) => { + await validate_delta("column", updated.delta, [ + 20, + 8, + 6, + 4, + null, + ]); + const result = await v1.to_columns(); + expect(result["column"]).toEqual([ + 20, + 8, + 6, + 4, + 2, + null, + ]); + }, + { mode: "row" } + ); + + v2.on_update( + async (updated) => { + await validate_delta("column", updated.delta, [ + "Z", + "Y", + "X", + "DEF", + "ABC", + ]); + const result2 = await v2.to_columns(); + expect(result2["column"]).toEqual([ + "Z", + "Y", + "X", + "DEF", + "ABC", + "A", + ]); + await v2.delete(); + await v1.delete(); + await table.delete(); + done(); + }, + { mode: "row" } + ); - table.update({ - x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"], - }); + table.update({ + x: [2, 4, 3, 10, null], + y: ["X", "Y", "Z", "ABC", "DEF"], + }); + })(); }); }); diff --git a/packages/perspective/test/js/leaks.spec.js b/packages/perspective/test/js/leaks.spec.js index 8bc60a27d3..0093ba44e0 100644 --- a/packages/perspective/test/js/leaks.spec.js +++ b/packages/perspective/test/js/leaks.spec.js @@ -12,10 +12,7 @@ const fs = require("fs"); const path = require("path"); const arr = fs.readFileSync( - path.join( - __dirname, - "../../../../node_modules/superstore-arrow/superstore.arrow" - ) + require.resolve("superstore-arrow/superstore.arrow") ).buffer; /** diff --git a/packages/perspective/test/js/pivots.js b/packages/perspective/test/js/pivots.js index 00c92dff05..f42cbad573 100644 --- a/packages/perspective/test/js/pivots.js +++ b/packages/perspective/test/js/pivots.js @@ -2356,6 +2356,11 @@ module.exports = (perspective) => { columns: ["x", "y"], }); let result2 = await view.to_columns(); + result2 = Object.entries(result2).reduce((obj, [key, val]) => { + obj[key.replace(/[^,:\/|A-Z0-9 ]/gi, " ")] = val; + return obj; + }, {}); + expect(result2).toEqual({ "4/11/2019, 11:40:35 PM|x": [null, null, 3, 4], "4/11/2019, 11:40:35 PM|y": [null, null, "c", "d"], diff --git a/packages/perspective/test/js/ports.js b/packages/perspective/test/js/ports.js index 1e1e5a5da5..04afaf1f42 100644 --- a/packages/perspective/test/js/ports.js +++ b/packages/perspective/test/js/ports.js @@ -22,6 +22,12 @@ const get_random_int = function (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }; +function it_old_behavior(name, capture) { + it(name, function (done) { + capture(done); + }); +} + module.exports = (perspective) => { describe("ports", function () { it("Should create port IDs in incremental order", async function () { @@ -437,108 +443,70 @@ module.exports = (perspective) => { }); describe("on_update notifications from different ports", function () { - it("All calls to on_update should contain the port ID", async function (done) { - const table = await perspective.table(data); - const port_ids = []; - - for (let i = 0; i < 10; i++) { - port_ids.push(await table.make_port()); - } - - expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - - const view = await table.view(); - - // because no updates were called in port 0, start at 1 - let last_port_id = 1; - view.on_update(function (updated) { - expect(updated.port_id).toEqual(last_port_id); - if (last_port_id == 10) { - view.delete(); - table.delete(); - done(); - } else { - last_port_id++; + it_old_behavior( + "All calls to on_update should contain the port ID", + async function (done) { + const table = await perspective.table(data); + const port_ids = []; + + for (let i = 0; i < 10; i++) { + port_ids.push(await table.make_port()); } - }); - for (const port_id of port_ids) { - table.update( - { - w: [1.5], - x: [1], - y: ["a"], - z: [true], - }, - { port_id } - ); - } - }); + expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - it("Only the port that was updated should be notified in on_update", async function (done) { - const table = await perspective.table(data); - const port_ids = []; + const view = await table.view(); - for (let i = 0; i < 10; i++) { - port_ids.push(await table.make_port()); + // because no updates were called in port 0, start at 1 + let last_port_id = 1; + view.on_update(function (updated) { + expect(updated.port_id).toEqual(last_port_id); + if (last_port_id == 10) { + view.delete(); + table.delete(); + done(); + } else { + last_port_id++; + } + }); + + for (const port_id of port_ids) { + table.update( + { + w: [1.5], + x: [1], + y: ["a"], + z: [true], + }, + { port_id } + ); + } } + ); - expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - - const view = await table.view(); - - const port_id = get_random_int(1, 9); - - view.on_update(function (updated) { - expect(updated.port_id).toEqual(port_id); - view.delete(); - table.delete(); - done(); - }); - - table.update( - { - w: [1.5], - x: [1], - y: ["a"], - z: [true], - }, - { port_id } - ); - }); - - it("All ports should be notified in creation order, regardless of what order the update is called.", async function (done) { - const table = await perspective.table(data); - const port_ids = []; + it_old_behavior( + "Only the port that was updated should be notified in on_update", + async function (done) { + const table = await perspective.table(data); + const port_ids = []; - for (let i = 0; i < 10; i++) { - port_ids.push(await table.make_port()); - } + for (let i = 0; i < 10; i++) { + port_ids.push(await table.make_port()); + } - expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - const view = await table.view(); + const view = await table.view(); - let last_port_id = 0; - let num_updates = 0; + const port_id = get_random_int(1, 9); - view.on_update(function (updated) { - expect(updated.port_id).toEqual(last_port_id); - if (last_port_id == 10 && num_updates === 10) { + view.on_update(function (updated) { + expect(updated.port_id).toEqual(port_id); view.delete(); table.delete(); done(); - } else { - num_updates++; - last_port_id++; - } - }); + }); - const update_order = _.shuffle([ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - ]); - - for (const port_id of update_order) { table.update( { w: [1.5], @@ -549,46 +517,27 @@ module.exports = (perspective) => { { port_id } ); } - }); + ); - it("On update callbacks should be able to ignore updates from certain ports.", async function (done) { - const table = await perspective.table(data); - const update_table = await perspective.table( - await table.schema() - ); - const port_ids = []; + it("All ports should be notified in creation order, regardless of what order the update is called.", function (done) { + (async function () { + const table = await perspective.table(data); + const port_ids = []; - for (let i = 0; i < 10; i++) { - port_ids.push(await table.make_port()); - } + for (let i = 0; i < 10; i++) { + port_ids.push(await table.make_port()); + } - expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - const view = await table.view(); + const view = await table.view(); - let last_port_id = 0; - let num_updates = 0; + let last_port_id = 0; + let num_updates = 0; - view.on_update( - async function (updated) { + view.on_update(function (updated) { expect(updated.port_id).toEqual(last_port_id); - - if (![0, 5, 7, 8, 9].includes(updated.port_id)) { - update_table.update(updated.delta); - } - if (last_port_id == 10 && num_updates === 10) { - expect(await update_table.size()).toEqual(6); - const update_view = await update_table.view(); - const result = await update_view.to_columns(); - expect(result).toEqual({ - w: [1.5, 1.5, 1.5, 1.5, 1.5, 1.5], - x: [1, 2, 3, 4, 6, 10], - y: ["a", "a", "a", "a", "a", "a"], - z: [true, true, true, true, true, true], - }); - update_view.delete(); - update_table.delete(); view.delete(); table.delete(); done(); @@ -596,338 +545,422 @@ module.exports = (perspective) => { num_updates++; last_port_id++; } - }, - { mode: "row" } - ); + }); + + const update_order = _.shuffle([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + ]); + + for (const port_id of update_order) { + table.update( + { + w: [1.5], + x: [1], + y: ["a"], + z: [true], + }, + { port_id } + ); + } + })(); + }); - const update_order = _.shuffle([ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - ]); + it_old_behavior( + "On update callbacks should be able to ignore updates from certain ports.", + async function (done) { + const table = await perspective.table(data); + const update_table = await perspective.table( + await table.schema() + ); + const port_ids = []; - for (const port_id of update_order) { - table.update( - { - w: [1.5], - x: [port_id], - y: ["a"], - z: [true], + for (let i = 0; i < 10; i++) { + port_ids.push(await table.make_port()); + } + + expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + const view = await table.view(); + + let last_port_id = 0; + let num_updates = 0; + + view.on_update( + async function (updated) { + expect(updated.port_id).toEqual(last_port_id); + + if (![0, 5, 7, 8, 9].includes(updated.port_id)) { + update_table.update(updated.delta); + } + + if (last_port_id == 10 && num_updates === 10) { + expect(await update_table.size()).toEqual(6); + const update_view = await update_table.view(); + const result = await update_view.to_columns(); + expect(result).toEqual({ + w: [1.5, 1.5, 1.5, 1.5, 1.5, 1.5], + x: [1, 2, 3, 4, 6, 10], + y: ["a", "a", "a", "a", "a", "a"], + z: [true, true, true, true, true, true], + }); + update_view.delete(); + update_table.delete(); + view.delete(); + table.delete(); + done(); + } else { + num_updates++; + last_port_id++; + } }, - { port_id } + { mode: "row" } ); + + const update_order = _.shuffle([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + ]); + + for (const port_id of update_order) { + table.update( + { + w: [1.5], + x: [port_id], + y: ["a"], + z: [true], + }, + { port_id } + ); + } } - }); + ); }); describe("deltas", function () { - it("Deltas should be unique to each port", async function (done) { - const table = await perspective.table(data); - const port_ids = []; + it_old_behavior( + "Deltas should be unique to each port", + async function (done) { + const table = await perspective.table(data); + const port_ids = []; + + for (let i = 0; i < 10; i++) { + port_ids.push(await table.make_port()); + } - for (let i = 0; i < 10; i++) { - port_ids.push(await table.make_port()); - } + expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + const view = await table.view(); + + let update_count = 0; + + view.on_update( + async function (updated) { + const _t = await perspective.table(updated.delta); + const _v = await _t.view(); + const result = await _v.to_columns(); + + if (updated.port_id == first_port) { + expect(result).toEqual({ + w: [100], + x: [first_port], + y: ["first port"], + z: [false], + }); + } else if (updated.port_id == second_port) { + expect(result).toEqual({ + w: [200], + x: [second_port], + y: ["second port"], + z: [true], + }); + } + + update_count++; + + if (update_count === 2) { + _v.delete(); + _t.delete(); + view.delete(); + table.delete(); + done(); + } + }, + { mode: "row" } + ); - expect(port_ids).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const first_port = get_random_int(0, 10); + let second_port = get_random_int(0, 10); - const view = await table.view(); + if (second_port === first_port) { + second_port = get_random_int(0, 10); + } - let update_count = 0; + table.update( + { + w: [100], + x: [first_port], + y: ["first port"], + z: [false], + }, + { port_id: first_port } + ); - view.on_update( - async function (updated) { - const _t = await perspective.table(updated.delta); - const _v = await _t.view(); - const result = await _v.to_columns(); + table.update( + { + w: [200], + x: [second_port], + y: ["second port"], + z: [true], + }, + { port_id: second_port } + ); + } + ); + }); - if (updated.port_id == first_port) { - expect(result).toEqual({ - w: [100], - x: [first_port], - y: ["first port"], - z: [false], - }); - } else if (updated.port_id == second_port) { - expect(result).toEqual({ - w: [200], - x: [second_port], - y: ["second port"], - z: [true], - }); - } + describe("Cross-table port operations", function () { + it_old_behavior( + "Should allow a client-server round trip without loops", + async function (done) { + const client_table = await perspective.table(data); + const server_table = await perspective.table(data); + const client_port = await client_table.make_port(); + + // "get rid" of a random number of ports by creating them + for (let i = 0; i < get_random_int(5, 20); i++) { + await server_table.make_port(); + } - update_count++; + const server_port = await server_table.make_port(); - if (update_count === 2) { - _v.delete(); - _t.delete(); - view.delete(); - table.delete(); - done(); - } - }, - { mode: "row" } - ); + expect(client_port).toBeLessThan(server_port); - const first_port = get_random_int(0, 10); - let second_port = get_random_int(0, 10); + const client_view = await client_table.view(); - if (second_port === first_port) { - second_port = get_random_int(0, 10); - } + let CLIENT_TO_SERVER_UPDATES = 0; - table.update( - { - w: [100], - x: [first_port], - y: ["first port"], - z: [false], - }, - { port_id: first_port } - ); + client_view.on_update( + async (updated) => { + if (updated.port_id === client_port) { + server_table.update(updated.delta, { + port_id: server_port, + }); + CLIENT_TO_SERVER_UPDATES++; + } else { + // Should not pass forward that update, and break + // the chain. + expect(CLIENT_TO_SERVER_UPDATES).toEqual(1); + } + }, + { mode: "row" } + ); - table.update( - { - w: [200], - x: [second_port], - y: ["second port"], - z: [true], - }, - { port_id: second_port } - ); - }); - }); + const server_view = await server_table.view(); + + server_view.on_update( + async (updated) => { + if (updated.port_id !== server_port) { + client_table.update(updated.delta); + } else { + // update is trapped in this state - assert correct + // state and exit the test. + expect(await client_table.size()).toEqual(8); + expect(await server_table.size()).toEqual(8); + client_view.delete(); + server_view.delete(); + client_table.delete(); + server_table.delete(); + done(); + } + }, + { mode: "row" } + ); - describe("Cross-table port operations", function () { - it("Should allow a client-server round trip without loops", async function (done) { - const client_table = await perspective.table(data); - const server_table = await perspective.table(data); - const client_port = await client_table.make_port(); - - // "get rid" of a random number of ports by creating them - for (let i = 0; i < get_random_int(5, 20); i++) { - await server_table.make_port(); + // this should go to the server and round trip back to the + // client, but stop when it gets to the client again. + client_table.update(data, { port_id: client_port }); } + ); + + it_old_behavior( + "Should allow a client-server round trip without loops and server updates", + async function (done) { + const client_table = await perspective.table(data); + const server_table = await perspective.table(data); + const client_port = await client_table.make_port(); + + // "get rid" of a random number of ports by creating them + for (let i = 0; i < get_random_int(5, 20); i++) { + await server_table.make_port(); + } - const server_port = await server_table.make_port(); + const server_port = await server_table.make_port(); - expect(client_port).toBeLessThan(server_port); + expect(client_port).toBeLessThan(server_port); - const client_view = await client_table.view(); + const client_view = await client_table.view(); - let CLIENT_TO_SERVER_UPDATES = 0; + let CLIENT_TO_SERVER_UPDATES = 0; - client_view.on_update( - async (updated) => { - if (updated.port_id === client_port) { - server_table.update(updated.delta, { - port_id: server_port, - }); - CLIENT_TO_SERVER_UPDATES++; - } else { - // Should not pass forward that update, and break - // the chain. - expect(CLIENT_TO_SERVER_UPDATES).toEqual(1); - } - }, - { mode: "row" } - ); - - const server_view = await server_table.view(); + client_view.on_update( + async (updated) => { + if (updated.port_id === client_port) { + server_table.update(updated.delta, { + port_id: server_port, + }); + CLIENT_TO_SERVER_UPDATES++; + } else { + // Should not pass forward that update, and break + // the chain. + expect( + CLIENT_TO_SERVER_UPDATES + ).toBeLessThanOrEqual(2); + } + }, + { mode: "row" } + ); - server_view.on_update( - async (updated) => { - if (updated.port_id !== server_port) { - client_table.update(updated.delta); - } else { - // update is trapped in this state - assert correct - // state and exit the test. - expect(await client_table.size()).toEqual(8); - expect(await server_table.size()).toEqual(8); - client_view.delete(); - server_view.delete(); - client_table.delete(); - server_table.delete(); - done(); - } - }, - { mode: "row" } - ); + const server_view = await server_table.view(); + + server_view.on_update( + async (updated) => { + if (updated.port_id !== server_port) { + client_table.update(updated.delta); + } else { + // update is trapped in this state - assert correct + // state and exit the test. + expect(await client_table.size()).toEqual(16); + expect(await server_table.size()).toEqual(16); + client_view.delete(); + server_view.delete(); + client_table.delete(); + server_table.delete(); + done(); + } + }, + { mode: "row" } + ); - // this should go to the server and round trip back to the - // client, but stop when it gets to the client again. - client_table.update(data, { port_id: client_port }); - }); + // this should go to the server and round trip back to the + // client, but stop when it gets to the client again. + client_table.update(data, { port_id: client_port }); - it("Should allow a client-server round trip without loops and server updates", async function (done) { - const client_table = await perspective.table(data); - const server_table = await perspective.table(data); - const client_port = await client_table.make_port(); + // this should go to the client + server_table.update(data); - // "get rid" of a random number of ports by creating them - for (let i = 0; i < get_random_int(5, 20); i++) { - await server_table.make_port(); + // this should go to the server + client_table.update(data, { port_id: client_port }); } + ); + + it_old_behavior( + "Should allow multi client-server round trips without loops", + async function (done) { + const client_table = await perspective.table(data, { + index: "w", + }); + const client_table2 = await perspective.table(data, { + index: "w", + }); + const server_table = await perspective.table(data, { + index: "w", + }); + const client_port = await client_table.make_port(); + const client_port2 = await client_table2.make_port(); + + // "get rid" of a random number of ports by creating them + for (let i = 0; i < get_random_int(5, 20); i++) { + await server_table.make_port(); + } - const server_port = await server_table.make_port(); + const server_port = await server_table.make_port(); + const server_port2 = await server_table.make_port(); - expect(client_port).toBeLessThan(server_port); + expect(client_port).toBeLessThan(server_port); + expect(client_port2).toBeLessThan(server_port); + expect(client_port).toBeLessThan(server_port2); + expect(client_port2).toBeLessThan(server_port2); - const client_view = await client_table.view(); + // port numbers can be equal between clients + expect(client_port2).toEqual(client_port); - let CLIENT_TO_SERVER_UPDATES = 0; + const client_view = await client_table.view(); + const client_view2 = await client_table2.view(); - client_view.on_update( - async (updated) => { - if (updated.port_id === client_port) { - server_table.update(updated.delta, { - port_id: server_port, - }); - CLIENT_TO_SERVER_UPDATES++; - } else { - // Should not pass forward that update, and break - // the chain. - expect( - CLIENT_TO_SERVER_UPDATES - ).toBeLessThanOrEqual(2); - } - }, - { mode: "row" } - ); - - const server_view = await server_table.view(); + client_view.on_update( + async (updated) => { + // client1 and server handlers are as minimal as can be + if (updated.port_id === client_port) { + server_table.update(updated.delta, { + port_id: server_port, + }); + } + }, + { mode: "row" } + ); - server_view.on_update( - async (updated) => { - if (updated.port_id !== server_port) { - client_table.update(updated.delta); - } else { - // update is trapped in this state - assert correct - // state and exit the test. - expect(await client_table.size()).toEqual(16); - expect(await server_table.size()).toEqual(16); + client_view2.on_update( + async (updated) => { + // client2 only reads updates from other clients + const results = await client_view2.to_columns(); + expect(results).toEqual({ + w: [1.5, 2.5, 3.5, 4.5], + x: [1, 2, 300, 4], + y: ["a", "b", "ccc", "d"], + z: [true, false, true, false], + }); + expect(await client_view.to_columns()).toEqual( + results + ); + expect(await server_view.to_columns()).toEqual( + results + ); client_view.delete(); + client_view2.delete(); server_view.delete(); client_table.delete(); + client_table2.delete(); server_table.delete(); done(); - } - }, - { mode: "row" } - ); - - // this should go to the server and round trip back to the - // client, but stop when it gets to the client again. - client_table.update(data, { port_id: client_port }); + }, + { mode: "row" } + ); - // this should go to the client - server_table.update(data); + const server_view = await server_table.view(); - // this should go to the server - client_table.update(data, { port_id: client_port }); - }); + // Simulate multiple connections to the server + server_view.on_update( + async (updated) => { + if (updated.port_id !== server_port) { + client_table.update(updated.delta); + } + }, + { mode: "row" } + ); - it("Should allow multi client-server round trips without loops", async function (done) { - const client_table = await perspective.table(data, { - index: "w", - }); - const client_table2 = await perspective.table(data, { - index: "w", - }); - const server_table = await perspective.table(data, { - index: "w", - }); - const client_port = await client_table.make_port(); - const client_port2 = await client_table2.make_port(); + server_view.on_update( + async (updated) => { + if (updated.port_id !== server_port2) { + client_table2.update(updated.delta); + } + }, + { mode: "row" } + ); - // "get rid" of a random number of ports by creating them - for (let i = 0; i < get_random_int(5, 20); i++) { - await server_table.make_port(); + // this should go to the server and round trip back to the + // client, but stop when it gets to the client again. It should + // reflect on the client2 table though. + client_table.update( + [ + { + w: 3.5, + x: 300, + y: "ccc", + }, + ], + { port_id: client_port } + ); } - - const server_port = await server_table.make_port(); - const server_port2 = await server_table.make_port(); - - expect(client_port).toBeLessThan(server_port); - expect(client_port2).toBeLessThan(server_port); - expect(client_port).toBeLessThan(server_port2); - expect(client_port2).toBeLessThan(server_port2); - - // port numbers can be equal between clients - expect(client_port2).toEqual(client_port); - - const client_view = await client_table.view(); - const client_view2 = await client_table2.view(); - - client_view.on_update( - async (updated) => { - // client1 and server handlers are as minimal as can be - if (updated.port_id === client_port) { - server_table.update(updated.delta, { - port_id: server_port, - }); - } - }, - { mode: "row" } - ); - - client_view2.on_update( - async (updated) => { - // client2 only reads updates from other clients - const results = await client_view2.to_columns(); - expect(results).toEqual({ - w: [1.5, 2.5, 3.5, 4.5], - x: [1, 2, 300, 4], - y: ["a", "b", "ccc", "d"], - z: [true, false, true, false], - }); - expect(await client_view.to_columns()).toEqual(results); - expect(await server_view.to_columns()).toEqual(results); - client_view.delete(); - client_view2.delete(); - server_view.delete(); - client_table.delete(); - client_table2.delete(); - server_table.delete(); - done(); - }, - { mode: "row" } - ); - - const server_view = await server_table.view(); - - // Simulate multiple connections to the server - server_view.on_update( - async (updated) => { - if (updated.port_id !== server_port) { - client_table.update(updated.delta); - } - }, - { mode: "row" } - ); - - server_view.on_update( - async (updated) => { - if (updated.port_id !== server_port2) { - client_table2.update(updated.delta); - } - }, - { mode: "row" } - ); - - // this should go to the server and round trip back to the - // client, but stop when it gets to the client again. It should - // reflect on the client2 table though. - client_table.update( - [ - { - w: 3.5, - x: 300, - y: "ccc", - }, - ], - { port_id: client_port } - ); - }); + ); }); }); }; diff --git a/packages/perspective/test/js/remote.spec.js b/packages/perspective/test/js/remote.spec.js index 926810a9ab..e8defda7f5 100644 --- a/packages/perspective/test/js/remote.spec.js +++ b/packages/perspective/test/js/remote.spec.js @@ -120,29 +120,31 @@ describe("WebSocketManager", function () { server.eject_table("test"); }); - it("sends updates to client on subscribe", async (done) => { - const data = [{ x: 1 }]; - const table = await perspective.table(data); - server.host_table("test", table); - - const client = perspective.websocket(`ws://localhost:${port}`); - const client_table = await client.open_table("test"); - - const client_view = await client_table.view(); - const on_update = () => { - client_view.to_json().then(async (updated_data) => { - server.eject_table("test"); - expect(updated_data).toEqual([{ x: 1 }, { x: 2 }]); - await client.terminate(); - setTimeout(done); + it("sends updates to client on subscribe", (done) => { + (async () => { + const data = [{ x: 1 }]; + const table = await perspective.table(data); + server.host_table("test", table); + + const client = perspective.websocket(`ws://localhost:${port}`); + const client_table = await client.open_table("test"); + + const client_view = await client_table.view(); + const on_update = () => { + client_view.to_json().then(async (updated_data) => { + server.eject_table("test"); + expect(updated_data).toEqual([{ x: 1 }, { x: 2 }]); + await client.terminate(); + setTimeout(done); + }); + }; + + client_view.on_update(on_update); + client_view.to_json().then((client_data) => { + expect(client_data).toEqual(data); + table.update([{ x: 2 }]); }); - }; - - client_view.on_update(on_update); - client_view.to_json().then((client_data) => { - expect(client_data).toEqual(data); - table.update([{ x: 2 }]); - }); + })(); }); it("Calls `update` and sends arraybuffers using `binary_length`", async () => { @@ -189,37 +191,39 @@ describe("WebSocketManager", function () { server.eject_table("test"); }); - it("Calls `update` and sends arraybuffers using `on_update`", async (done) => { - const data = [{ x: 1 }]; - const table = await perspective.table(data); - const view = await table.view(); - const arrow = await view.to_arrow(); + it("Calls `update` and sends arraybuffers using `on_update`", (done) => { + (async () => { + const data = [{ x: 1 }]; + const table = await perspective.table(data); + const view = await table.view(); + const arrow = await view.to_arrow(); - let update_port; + let update_port; - const updater = async (updated) => { - expect(updated.port_id).toEqual(update_port); - expect(updated.delta instanceof ArrayBuffer).toEqual(true); - expect(updated.delta.byteLength).toBeGreaterThan(0); - await client.terminate(); - server.eject_table("test"); - done(); - }; + const updater = async (updated) => { + expect(updated.port_id).toEqual(update_port); + expect(updated.delta instanceof ArrayBuffer).toEqual(true); + expect(updated.delta.byteLength).toBeGreaterThan(0); + await client.terminate(); + server.eject_table("test"); + done(); + }; - view.on_update(updater, { mode: "row" }); + view.on_update(updater, { mode: "row" }); - server.host_table("test", table); + server.host_table("test", table); - const client = perspective.websocket(`ws://localhost:${port}`); - const client_table = await client.open_table("test"); + const client = perspective.websocket(`ws://localhost:${port}`); + const client_table = await client.open_table("test"); - for (let i = 0; i < 5; i++) { - // take up some ports on the remote table - await client_table.make_port(); - } + for (let i = 0; i < 5; i++) { + // take up some ports on the remote table + await client_table.make_port(); + } - update_port = await client_table.make_port(); + update_port = await client_table.make_port(); - client_table.update(arrow, { port_id: update_port }); + client_table.update(arrow, { port_id: update_port }); + })(); }); }); diff --git a/packages/perspective/test/js/timezone/timezone.spec.js b/packages/perspective/test/js/timezone.spec.js similarity index 99% rename from packages/perspective/test/js/timezone/timezone.spec.js rename to packages/perspective/test/js/timezone.spec.js index 389cfc6a19..87d7222205 100644 --- a/packages/perspective/test/js/timezone/timezone.spec.js +++ b/packages/perspective/test/js/timezone.spec.js @@ -7,7 +7,7 @@ * */ -const perspective = require("../../../dist/cjs/perspective.node.js"); +const perspective = require("../../dist/cjs/perspective.node.js"); const date_data = [ { x: new Date(2020, 0, 1) }, // 2020/01/01 diff --git a/packages/perspective/test/js/to_format.js b/packages/perspective/test/js/to_format.js index a4d319488a..77b53392d5 100644 --- a/packages/perspective/test/js/to_format.js +++ b/packages/perspective/test/js/to_format.js @@ -439,6 +439,11 @@ module.exports = (perspective) => { ]); let view = await table.view(); let json = await view.to_json({ formatted: true }); + json = json.map((obj) => { + obj.datetime = obj.datetime.replace(/[^:,\/|A-Z0-9 ]/gi, " "); + return obj; + }, {}); + expect(json).toEqual([ { datetime: "1/1/16, 12:30:00 AM" }, { datetime: "6/15/16, 7:20:00 PM" }, diff --git a/packages/perspective/test/js/updates.js b/packages/perspective/test/js/updates.js index acd0c83249..60abb28a28 100644 --- a/packages/perspective/test/js/updates.js +++ b/packages/perspective/test/js/updates.js @@ -10,6 +10,12 @@ const _ = require("lodash"); const arrows = require("./test_arrows.js"); +function it_old_behavior(name, capture) { + it(name, function (done) { + capture(done); + }); +} + var data = [ { x: 1, y: "a", z: true }, { x: 2, y: "b", z: false }, @@ -1210,7 +1216,7 @@ module.exports = (perspective) => { }); describe("Notifications", function () { - it("`on_update()`", async function (done) { + it_old_behavior("`on_update()`", async function (done) { var table = await perspective.table(meta); var view = await table.view(); view.on_update( @@ -1225,27 +1231,30 @@ module.exports = (perspective) => { table.update(data); }); - it("`on_update` before and after `update()`", async function (done) { - var table = await perspective.table(meta); - var view = await table.view(); - table.update(data); - var ran = false; - view.on_update( - async function (updated) { - if (!ran) { - await match_delta(perspective, updated.delta, data); - ran = true; - view.delete(); - table.delete(); - done(); - } - }, - { mode: "row" } - ); - table.update(data); - }); + it_old_behavior( + "`on_update` before and after `update()`", + async function (done) { + var table = await perspective.table(meta); + var view = await table.view(); + table.update(data); + var ran = false; + view.on_update( + async function (updated) { + if (!ran) { + await match_delta(perspective, updated.delta, data); + ran = true; + view.delete(); + table.delete(); + done(); + } + }, + { mode: "row" } + ); + table.update(data); + } + ); - it("`on_update(table.update) !`", async function (done) { + it_old_behavior("`on_update(table.update) !`", async function (done) { var table1 = await perspective.table(meta); var table2 = await perspective.table(meta); var view1 = await table1.view(); @@ -1266,135 +1275,150 @@ module.exports = (perspective) => { table1.update(data); }); - it("`on_update(table.update)` before and after `update()`", async function (done) { - var table1 = await perspective.table(meta); - var table2 = await perspective.table(meta); - var view1 = await table1.view(); - var view2 = await table2.view(); - - table1.update(data); - table2.update(data); - view1.on_update( - async function (updated) { - table2.update(updated.delta); - let result = await view2.to_json(); - expect(result).toEqual(data.concat(data)); - view1.delete(); - view2.delete(); - table1.delete(); - table2.delete(); - done(); - }, - { mode: "row" } - ); - table1.update(data); - }); - - it("properly removes a failed update callback on a table", async function (done) { - const table = await perspective.table({ x: "integer" }); - const view = await table.view(); - let size = await table.size(); - let counter = 0; + it_old_behavior( + "`on_update(table.update)` before and after `update()`", + async function (done) { + var table1 = await perspective.table(meta); + var table2 = await perspective.table(meta); + var view1 = await table1.view(); + var view2 = await table2.view(); + + table1.update(data); + table2.update(data); + view1.on_update( + async function (updated) { + table2.update(updated.delta); + let result = await view2.to_json(); + expect(result).toEqual(data.concat(data)); + view1.delete(); + view2.delete(); + table1.delete(); + table2.delete(); + done(); + }, + { mode: "row" } + ); + table1.update(data); + } + ); - // when a callback throws, it should delete that callback - view.on_update(() => { - counter++; - throw new Error("something went wrong!"); - }); + it_old_behavior( + "properly removes a failed update callback on a table", + async function (done) { + const table = await perspective.table({ x: "integer" }); + const view = await table.view(); + let size = await table.size(); + let counter = 0; - view.on_update(async () => { - // failed callback gets removed; non-failing callback gets called - let sz = await table.size(); - expect(counter).toEqual(1); - expect(sz).toEqual(size++); - }); + // when a callback throws, it should delete that callback + view.on_update(() => { + counter++; + throw new Error("something went wrong!"); + }); - table.update([{ x: 1 }]); - table.update([{ x: 2 }]); - table.update([{ x: 3 }]); + view.on_update(async () => { + // failed callback gets removed; non-failing callback gets called + let sz = await table.size(); + expect(counter).toEqual(1); + expect(sz).toEqual(size++); + }); - const view2 = await table.view(); // create a new view to make sure we process every update transacation. - const final_size = await table.size(); - expect(final_size).toEqual(3); + table.update([{ x: 1 }]); + table.update([{ x: 2 }]); + table.update([{ x: 3 }]); - view2.delete(); - view.delete(); - table.delete(); - done(); - }); + const view2 = await table.view(); // create a new view to make sure we process every update transacation. + const final_size = await table.size(); + expect(final_size).toEqual(3); - it("`on_update()` that calls operations on the table should not recurse", async function (done) { - var table = await perspective.table(meta); - var view = await table.view(); - view.on_update(async function (updated) { - expect(updated.port_id).toEqual(0); - const json = await view.to_json(); - // Not checking for correctness, literally just to assert - // that the `process()` call triggered by `to_json` will not - // infinitely recurse. - expect(json).toEqual(await view.to_json()); + view2.delete(); view.delete(); table.delete(); done(); - }); - table.update(data); - }); - - it("`on_update()` should be triggered in sequence", async function (done) { - var table = await perspective.table(meta); - var view = await table.view(); - - let order = []; - - const finish = function () { - if (order.length === 3) { - expect(order).toEqual([0, 1, 2]); + } + ); + + it_old_behavior( + "`on_update()` that calls operations on the table should not recurse", + async function (done) { + var table = await perspective.table(meta); + var view = await table.view(); + view.on_update(async function (updated) { + expect(updated.port_id).toEqual(0); + const json = await view.to_json(); + // Not checking for correctness, literally just to assert + // that the `process()` call triggered by `to_json` will not + // infinitely recurse. + expect(json).toEqual(await view.to_json()); view.delete(); table.delete(); done(); - } - }; - - for (let i = 0; i < 3; i++) { - view.on_update(() => { - order.push(i); - finish(); }); + table.update(data); } + ); - table.update(data); - }); - - it("`on_update()` should be triggered in sequence across multiple views", async function (done) { - var table = await perspective.table(meta); - const views = [ - await table.view(), - await table.view(), - await table.view(), - ]; + it_old_behavior( + "`on_update()` should be triggered in sequence", + async function (done) { + var table = await perspective.table(meta); + var view = await table.view(); - let order = []; + let order = []; - const finish = function () { - if (order.length === 3) { - expect(order).toEqual([0, 1, 2]); - for (const view of views) { + const finish = function () { + if (order.length === 3) { + expect(order).toEqual([0, 1, 2]); view.delete(); + table.delete(); + done(); } - table.delete(); - done(); + }; + + for (let i = 0; i < 3; i++) { + view.on_update(() => { + order.push(i); + finish(); + }); } - }; - for (let i = 0; i < views.length; i++) { - views[i].on_update(() => { - order.push(i); - finish(); - }); + table.update(data); } + ); + + it_old_behavior( + "`on_update()` should be triggered in sequence across multiple views", + async function (done) { + var table = await perspective.table(meta); + const views = [ + await table.view(), + await table.view(), + await table.view(), + ]; + + let order = []; + + const finish = function () { + if (order.length === 3) { + expect(order).toEqual([0, 1, 2]); + for (const view of views) { + view.delete(); + } + table.delete(); + done(); + } + }; - table.update(data); - }); + for (let i = 0; i < views.length; i++) { + views[i].on_update(() => { + order.push(i); + finish(); + }); + } + + table.update(data); + } + ); }); describe("Limit", function () { @@ -1700,7 +1724,7 @@ module.exports = (perspective) => { table.delete(); }); - it("update and index (int)", async function (done) { + it_old_behavior("update and index (int)", async function (done) { var table = await perspective.table(meta, { index: "x" }); var view = await table.view(); table.update(data); @@ -1718,7 +1742,7 @@ module.exports = (perspective) => { table.update(data_2); }); - it("update and index (string)", async function (done) { + it_old_behavior("update and index (string)", async function (done) { var table = await perspective.table(meta, { index: "y" }); var view = await table.view(); table.update(data); @@ -1760,7 +1784,7 @@ module.exports = (perspective) => { table.delete(); }); - it("partial update", async function (done) { + it_old_behavior("partial update", async function (done) { var partial = [ { x: 5, y: "a" }, { y: "b", z: true }, @@ -1792,160 +1816,172 @@ module.exports = (perspective) => { table.update(partial); }); - it("partial column oriented update", async function (done) { - var partial = { - x: [5, undefined], - y: ["a", "b"], - z: [undefined, true], - }; - - var expected = [ - { x: 5, y: "a", z: true }, - { x: 2, y: "b", z: true }, - { x: 3, y: "c", z: true }, - { x: 4, y: "d", z: false }, - ]; - var table = await perspective.table(meta, { index: "y" }); - var view = await table.view(); - table.update(col_data); - view.on_update( - async function (updated) { - await match_delta( - perspective, - updated.delta, - expected.slice(0, 2) - ); - let json = await view.to_json(); - expect(json).toEqual(expected); - view.delete(); - table.delete(); - done(); - }, - { mode: "row" } - ); - table.update(partial); - }); - - it("Partial column oriented update with new pkey", async function (done) { - const data = { - x: [0, 1, null, 2, 3], - y: ["a", "b", "c", "d", "e"], - }; - const table = await perspective.table(data, { index: "x" }); - const view = await table.view(); - const result = await view.to_columns(); - - expect(result).toEqual({ - x: [null, 0, 1, 2, 3], // null before 0 - y: ["c", "a", "b", "d", "e"], - }); - - const expected = { - x: [null, 0, 1, 2, 3, 4], - y: ["c", "a", "b", "d", "e", "f"], - }; - - view.on_update( - async function (updated) { - await match_delta(perspective, updated.delta, [ - { - x: 4, - y: "f", - }, - ]); - const result2 = await view.to_columns(); - expect(result2).toEqual(expected); - view.delete(); - table.delete(); - done(); - }, - { mode: "row" } - ); - - table.update({ - x: [4], - y: ["f"], - }); - }); - - it("Partial column oriented update with new pkey, missing columns", async function (done) { - const data = { - x: [0, 1, null, 2, 3], - y: ["a", "b", "c", "d", "e"], - z: [true, false, true, false, true], - }; - const table = await perspective.table(data, { index: "x" }); - const view = await table.view(); - const result = await view.to_columns(); - - expect(result).toEqual({ - x: [null, 0, 1, 2, 3], // null before 0 - y: ["c", "a", "b", "d", "e"], - z: [true, true, false, false, true], - }); - - const expected = { - x: [null, 0, 1, 2, 3, 4], - y: ["c", "a", "b", "d", "e", "f"], - z: [true, true, false, false, true, null], - }; + it_old_behavior( + "partial column oriented update", + async function (done) { + var partial = { + x: [5, undefined], + y: ["a", "b"], + z: [undefined, true], + }; + + var expected = [ + { x: 5, y: "a", z: true }, + { x: 2, y: "b", z: true }, + { x: 3, y: "c", z: true }, + { x: 4, y: "d", z: false }, + ]; + var table = await perspective.table(meta, { index: "y" }); + var view = await table.view(); + table.update(col_data); + view.on_update( + async function (updated) { + await match_delta( + perspective, + updated.delta, + expected.slice(0, 2) + ); + let json = await view.to_json(); + expect(json).toEqual(expected); + view.delete(); + table.delete(); + done(); + }, + { mode: "row" } + ); + table.update(partial); + } + ); + + it_old_behavior( + "Partial column oriented update with new pkey", + async function (done) { + const data = { + x: [0, 1, null, 2, 3], + y: ["a", "b", "c", "d", "e"], + }; + const table = await perspective.table(data, { index: "x" }); + const view = await table.view(); + const result = await view.to_columns(); + + expect(result).toEqual({ + x: [null, 0, 1, 2, 3], // null before 0 + y: ["c", "a", "b", "d", "e"], + }); - view.on_update( - async function (updated) { - await match_delta(perspective, updated.delta, [ - { - x: 4, - y: "f", - z: null, - }, - ]); - const result2 = await view.to_columns(); - expect(result2).toEqual(expected); - view.delete(); - table.delete(); - done(); - }, - { mode: "row" } - ); + const expected = { + x: [null, 0, 1, 2, 3, 4], + y: ["c", "a", "b", "d", "e", "f"], + }; + + view.on_update( + async function (updated) { + await match_delta(perspective, updated.delta, [ + { + x: 4, + y: "f", + }, + ]); + const result2 = await view.to_columns(); + expect(result2).toEqual(expected); + view.delete(); + table.delete(); + done(); + }, + { mode: "row" } + ); - table.update({ - x: [4], - y: ["f"], - }); - }); + table.update({ + x: [4], + y: ["f"], + }); + } + ); + + it_old_behavior( + "Partial column oriented update with new pkey, missing columns", + async function (done) { + const data = { + x: [0, 1, null, 2, 3], + y: ["a", "b", "c", "d", "e"], + z: [true, false, true, false, true], + }; + const table = await perspective.table(data, { index: "x" }); + const view = await table.view(); + const result = await view.to_columns(); + + expect(result).toEqual({ + x: [null, 0, 1, 2, 3], // null before 0 + y: ["c", "a", "b", "d", "e"], + z: [true, true, false, false, true], + }); - it("partial column oriented update with entire columns missing", async function (done) { - var partial = { - y: ["a", "b"], - z: [false, true], - }; + const expected = { + x: [null, 0, 1, 2, 3, 4], + y: ["c", "a", "b", "d", "e", "f"], + z: [true, true, false, false, true, null], + }; + + view.on_update( + async function (updated) { + await match_delta(perspective, updated.delta, [ + { + x: 4, + y: "f", + z: null, + }, + ]); + const result2 = await view.to_columns(); + expect(result2).toEqual(expected); + view.delete(); + table.delete(); + done(); + }, + { mode: "row" } + ); - var expected = [ - { x: 1, y: "a", z: false }, - { x: 2, y: "b", z: true }, - { x: 3, y: "c", z: true }, - { x: 4, y: "d", z: false }, - ]; - var table = await perspective.table(meta, { index: "y" }); - var view = await table.view(); - table.update(col_data); - view.on_update( - async function (updated) { - await match_delta( - perspective, - updated.delta, - expected.slice(0, 2) - ); - let json = await view.to_json(); - expect(json).toEqual(expected); - view.delete(); - table.delete(); - done(); - }, - { mode: "row" } - ); - table.update(partial); - }); + table.update({ + x: [4], + y: ["f"], + }); + } + ); + + it_old_behavior( + "partial column oriented update with entire columns missing", + async function (done) { + var partial = { + y: ["a", "b"], + z: [false, true], + }; + + var expected = [ + { x: 1, y: "a", z: false }, + { x: 2, y: "b", z: true }, + { x: 3, y: "c", z: true }, + { x: 4, y: "d", z: false }, + ]; + var table = await perspective.table(meta, { index: "y" }); + var view = await table.view(); + table.update(col_data); + view.on_update( + async function (updated) { + await match_delta( + perspective, + updated.delta, + expected.slice(0, 2) + ); + let json = await view.to_json(); + expect(json).toEqual(expected); + view.delete(); + table.delete(); + done(); + }, + { mode: "row" } + ); + table.update(partial); + } + ); }); describe("null handling", function () { @@ -2557,7 +2593,7 @@ module.exports = (perspective) => { }); describe("Remove update", function () { - it("Should remove a single update", async function (done) { + it_old_behavior("Should remove a single update", async function (done) { const cb1 = jest.fn(); const cb2 = () => { expect(cb1).toBeCalledTimes(0); @@ -2575,29 +2611,32 @@ module.exports = (perspective) => { table.update(data); }); - it("Should remove multiple updates", async function (done) { - const cb1 = jest.fn(); - const cb2 = jest.fn(); - const cb3 = function () { - // cb2 should have been called - expect(cb1).toBeCalledTimes(0); - expect(cb2).toBeCalledTimes(0); - setTimeout(() => { - view.delete(); - table.delete(); - done(); - }, 0); - }; - - const table = await perspective.table(meta); - const view = await table.view(); - view.on_update(cb1); - view.on_update(cb2); - view.on_update(cb3); - view.remove_update(cb1); - view.remove_update(cb2); - table.update(data); - }); + it_old_behavior( + "Should remove multiple updates", + async function (done) { + const cb1 = jest.fn(); + const cb2 = jest.fn(); + const cb3 = function () { + // cb2 should have been called + expect(cb1).toBeCalledTimes(0); + expect(cb2).toBeCalledTimes(0); + setTimeout(() => { + view.delete(); + table.delete(); + done(); + }, 0); + }; + + const table = await perspective.table(meta); + const view = await table.view(); + view.on_update(cb1); + view.on_update(cb2); + view.on_update(cb3); + view.remove_update(cb1); + view.remove_update(cb2); + table.update(data); + } + ); }); describe("Filtered update and remove", () => { diff --git a/rust/perspective-viewer/package.json b/rust/perspective-viewer/package.json index 70f38eec63..db08607d3e 100644 --- a/rust/perspective-viewer/package.json +++ b/rust/perspective-viewer/package.json @@ -17,6 +17,7 @@ }, "./dist/*": "./dist/*", "./src/*": "./src/*", + "./test/*": "./test/*", "./package.json": "./package.json", "./tsconfig.json": "./tsconfig.json" }, @@ -50,11 +51,14 @@ }, "dependencies": { "@finos/perspective": "^1.9.0", - "fflate": "^0.7.2", + "fflate": "^0.7.4", "mobile-drag-drop-shadow-dom": "3.0.0" }, "devDependencies": { + "react": "^16.8.6", "@finos/perspective-esbuild-plugin": "^1.9.0", - "@finos/perspective-test": "^1.9.0" + "@finos/perspective-test": "^1.9.0", + "@finos/perspective-viewer": "file:./", + "cpy": "^9.0.1" } } diff --git a/rust/perspective-viewer/src/rust/lib.rs b/rust/perspective-viewer/src/rust/lib.rs index 5973efff6b..789db0d33d 100644 --- a/rust/perspective-viewer/src/rust/lib.rs +++ b/rust/perspective-viewer/src/rust/lib.rs @@ -70,9 +70,7 @@ pub fn get_exprtk_commands() -> ApiResult> { #[wasm_bindgen(js_name = "defineWebComponents")] pub fn js_define_web_components() { tracing_wasm::set_as_global_default(); - define_web_components!( - "export * as psp from '@finos/perspective-viewer/dist/pkg/perspective.js'" - ); + define_web_components!("export * as psp from '../../perspective.js'"); } /// Register Web Components with the global registry, given a Perspective diff --git a/scripts/test_js.js b/scripts/test_js.js index b60a132806..abd626980d 100644 --- a/scripts/test_js.js +++ b/scripts/test_js.js @@ -60,9 +60,6 @@ function jest_all() { --rootDir=. --config=tools/perspective-test/jest.all.config.js --color - --verbose - --maxWorkers=50% - --testPathIgnorePatterns='timezone' ${getarg("--bail") && "--bail"} ${getarg("--debug") || "--silent 2>&1 --noStackTrace"} --testNamePattern="${get_regex()}"`; @@ -90,18 +87,6 @@ function jest_single(cmd) { return x; } -/** - * Run timezone tests in a new Node process. - */ -function jest_timezone() { - console.log("-- Running Perspective.js timezone test suite"); - return bash_with_scope` - test_timezone:run - -- - ${DEBUG_FLAG} - --testNamePattern="${get_regex()}"`; -} - function get_regex() { const regex = getarg`-t`; if (regex) { @@ -110,50 +95,48 @@ function get_regex() { } } -try { - // must be executed from top-level (without scope) in order to respect the - // `--screenshots` flag. - execute`${PACKAGE_MANAGER} run clean -- --screenshots`; +async function run() { + try { + // must be executed from top-level (without scope) in order to respect the + // `--screenshots` flag. + execute`${PACKAGE_MANAGER} run clean -- --screenshots`; - if (!IS_JUPYTER) { - // test:build irrelevant for jupyter tests - run_with_scope`test:build`; - } - - // if (!PACKAGE || minimatch("perspective-viewer", PACKAGE)) { - // console.log("-- Running Rust tests"); - // execute`yarn lerna --scope=@finos/perspective-viewer exec yarn test:run:rust`; - // } - - if (getarg("--quiet")) { - // Run all tests with suppressed output. - console.log("-- Running jest in quiet mode"); - execute(silent(jest_timezone())); - execute(silent(jest_all())); - } else if (process.env.PACKAGE) { - // Run tests for a single package. - - if (IS_JUPYTER) { - // Jupyterlab is guaranteed to have started at this point, so - // copy the test files over and run the tests. - run_with_scope`test:jupyter:build`; - execute_throw(jest_single("test:jupyter:run")); - process.exit(0); + if (!IS_JUPYTER) { + // test:build irrelevant for jupyter tests + await run_with_scope`test:build`; } - if (minimatch("perspective", PACKAGE)) { - execute(jest_timezone()); + // if (!PACKAGE || minimatch("perspective-viewer", PACKAGE)) { + // console.log("-- Running Rust tests"); + // execute`yarn lerna --scope=@finos/perspective-viewer exec yarn test:run:rust`; + // } + + if (getarg("--quiet")) { + // Run all tests with suppressed output. + console.log("-- Running jest in quiet mode"); + execute(silent(jest_all())); + } else if (process.env.PACKAGE) { + // Run tests for a single package. + + if (IS_JUPYTER) { + // Jupyterlab is guaranteed to have started at this point, so + // copy the test files over and run the tests. + await run_with_scope`test:jupyter:build`; + execute_throw(jest_single("test:jupyter:run")); + process.exit(0); + } + + execute(jest_single()); + } else { + // Run all tests with full output. + console.log("-- Running jest in fast mode"); + execute(jest_all()); } - - execute(jest_single()); - } else { - // Run all tests with full output. - console.log("-- Running jest in fast mode"); - execute(jest_timezone()); - execute(jest_all()); + // } + } catch (e) { + console.log(e.message); + process.exit(1); } - // } -} catch (e) { - console.log(e.message); - process.exit(1); } + +run(); diff --git a/tools/perspective-test/babel.config.js b/tools/perspective-test/babel.config.js deleted file mode 100644 index 01308d7ca1..0000000000 --- a/tools/perspective-test/babel.config.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - presets: [ - [ - "@babel/preset-env", - { - targets: { - chrome: "74", - node: "15", - ios: "13", - }, - modules: false, - }, - ], - ], - sourceType: "unambiguous", -}; diff --git a/tools/perspective-test/jest.all.config.js b/tools/perspective-test/jest.all.config.js index 9c8e0cd2e6..17f8d9a7b1 100644 --- a/tools/perspective-test/jest.all.config.js +++ b/tools/perspective-test/jest.all.config.js @@ -7,16 +7,19 @@ module.exports = { "packages/perspective-workspace/test/js", "packages/perspective-jupyterlab/test/js", ], - verbose: true, - testURL: "http://localhost/", + testEnvironment: "@finos/perspective-test/src/js/set_timezone.js", + testEnvironmentOptions: { + url: "http://localhost/", + }, transform: { - ".js$": "@finos/perspective-test/src/js/transform.js", ".html$": "html-loader-jest", }, - collectCoverage: true, - collectCoverageFrom: ["packages/perspective/dist/cjs/**"], - coverageProvider: "v8", - coverageReporters: ["cobertura", "text"], + // collectCoverage: true, + // collectCoverageFrom: ["packages/perspective/dist/cjs/**"], + // coverageProvider: "v8", + // coverageReporters: ["cobertura", "text"], + setupFilesAfterEnv: ["@finos/perspective-test/src/js/set_timezone.js"], + // perspective-jupyterlab tests mock `@jupyter-widgets`, which is in // Typescript. transformIgnorePatterns: [ @@ -24,11 +27,7 @@ module.exports = { ], automock: false, setupFiles: ["@finos/perspective-test/src/js/beforeEachSpec.js"], - reporters: [ - "default", - "@finos/perspective-test/src/js/reporter.js", - "jest-junit", - ], + reporters: ["default", "jest-junit"], globalSetup: "@finos/perspective-test/src/js/globalSetup.js", globalTeardown: "@finos/perspective-test/src/js/globalTeardown.js", }; diff --git a/tools/perspective-test/jest.config.js b/tools/perspective-test/jest.config.js index 6cf1d4e35f..e0eb81a257 100644 --- a/tools/perspective-test/jest.config.js +++ b/tools/perspective-test/jest.config.js @@ -7,12 +7,11 @@ * */ module.exports = { - // rootDir: "../", roots: ["test/js/"], - verbose: true, - testURL: "http://localhost/", + testEnvironmentOptions: { + url: "http://localhost/", + }, transform: { - ".js$": "@finos/perspective-test/src/js/transform.js", ".html$": "html-loader-jest", }, transformIgnorePatterns: [ @@ -20,7 +19,8 @@ module.exports = { ], automock: false, setupFiles: ["@finos/perspective-test/src/js/beforeEachSpec.js"], - reporters: ["default", "@finos/perspective-test/src/js/reporter.js"], + testEnvironment: "@finos/perspective-test/src/js/set_timezone.js", + reporters: ["default"], globalSetup: "@finos/perspective-test/src/js/globalSetup.js", globalTeardown: "@finos/perspective-test/src/js/globalTeardown.js", }; diff --git a/tools/perspective-test/src/js/html_compare.js b/tools/perspective-test/src/js/html_compare.js index 6a60fb0f0a..c697715152 100644 --- a/tools/perspective-test/src/js/html_compare.js +++ b/tools/perspective-test/src/js/html_compare.js @@ -15,7 +15,7 @@ const crypto = require("crypto"); * @param {string} input denormalized xml * @returns */ -export function normalize_xml(xml) { +exports.normalize_xml = function normalize_xml(xml) { let clean_xml; try { clean_xml = format_and_clean_xml(xml); @@ -24,8 +24,8 @@ export function normalize_xml(xml) { } const hash = crypto.createHash("md5").update(clean_xml).digest("hex"); - return {hash, xml: clean_xml}; -} + return { hash, xml: clean_xml }; +}; function format_and_clean_xml(result) { return format(result, { diff --git a/tools/perspective-test/src/js/index.js b/tools/perspective-test/src/js/index.js index 0b14e71035..e8754f986c 100644 --- a/tools/perspective-test/src/js/index.js +++ b/tools/perspective-test/src/js/index.js @@ -11,12 +11,12 @@ const fs = require("fs"); const puppeteer = require("puppeteer"); const path = require("path"); const execSync = require("child_process").execSync; -const {track_mouse} = require("./mouse_helper.js"); +const { track_mouse } = require("./mouse_helper.js"); const readline = require("readline"); const cons = require("console"); const private_console = new cons.Console(process.stdout, process.stderr); const cp = require("child_process"); -const {normalize_xml} = require("./html_compare.js"); +const { normalize_xml } = require("./html_compare.js"); // Jest does not resolve `exports` field so we must link directly to the file. const { @@ -31,7 +31,7 @@ const { let __PORT__; -exports.with_server = function with_server({paths}, body) { +exports.with_server = function with_server({ paths }, body) { let server; beforeAll(() => { if (test_root === "") { @@ -189,7 +189,9 @@ async function get_new_page() { // CSS Animations break our screenshot tests, so set the // animation playback rate to something extreme. - await page._client.send("Animation.setPlaybackRate", {playbackRate: 100.0}); + await page._client.send("Animation.setPlaybackRate", { + playbackRate: 100.0, + }); page.on("console", async (msg) => { if (msg.type() === "error" || msg.type() === "warn") { const args = await msg.args(); @@ -197,10 +199,10 @@ async function get_new_page() { const val = await arg.jsonValue(); // value is serializable if (JSON.stringify(val) !== JSON.stringify({})) - console.log(val); + console.error(val); // value is unserializable (or an empty oject) else { - const {type, subtype, description} = arg._remoteObject; + const { type, subtype, description } = arg._remoteObject; private_console.log(`${subtype}: ${description}`); if (msg.type() === "error") errors.push(`${type}/${subtype}: ${description}`); @@ -224,7 +226,7 @@ function get_results(filename) { } } -beforeAll(async (done) => { +beforeAll(async () => { try { browser = await puppeteer.connect({ browserWSEndpoint: process.env.PSP_BROWSER_ENDPOINT, @@ -249,8 +251,6 @@ beforeAll(async (done) => { } } catch (e) { console.error(e); - } finally { - done(); } }, 30000); @@ -270,14 +270,14 @@ function write_results(updated, filename) { fs.writeFileSync(dir_name, JSON.stringify(results2, null, 4)); } -afterAll(() => { +afterAll(async () => { try { if (process.env.WRITE_TESTS) { write_results(new_results, RESULTS_FILENAME); write_results(new_debug_results, RESULTS_DEBUG_FILENAME); } if (page) { - page.close(); + await page.close(); } } catch (e) { private_console.error(e); @@ -300,7 +300,7 @@ function mkdirSyncRec(targetDir) { describe.page = ( url, body, - {reload_page = false, check_results = true, name, root} = {} + { reload_page = false, check_results = true, name, root } = {} ) => { let _url = url ? url : page_url; test_root = root ? root : test_root; @@ -358,7 +358,7 @@ expect.extend({ test.run = function run( name, body, - {url = page_url, timeout = 60000, viewport = null} + { url = page_url, timeout = 60000, viewport = null } ) { test( name, @@ -408,8 +408,9 @@ test.capture = function capture( const iterations = process.env.PSP_SATURATE ? 10 : 1; const test_name = `${name.replace(/[ \.']/g, "_")}`; - const path_name = `${spec.result.fullName - .replace(".html", "") + const path_name = `${expect + .getState() + .currentTestName.replace(".html", "") .replace(/[ \.']/g, "_")}`; let dir_name = path.join( test_root, @@ -438,7 +439,7 @@ test.capture = function capture( `http://127.0.0.1:${__PORT__}/${url}#test=${encodeURIComponent( name )}`, - {waitUntil: "domcontentloaded"} + { waitUntil: "domcontentloaded" } ); } else { await page.evaluate(async (x) => { @@ -498,7 +499,7 @@ test.capture = function capture( throw e; } - let {xml: result, hash} = normalize_xml(raw_xml); + let { xml: result, hash } = normalize_xml(raw_xml); if (hash === results[path_name]) { if ( diff --git a/tools/perspective-test/src/js/reporter.js b/tools/perspective-test/src/js/reporter.js deleted file mode 100644 index 09b9da9e6c..0000000000 --- a/tools/perspective-test/src/js/reporter.js +++ /dev/null @@ -1,77 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -const paths = require("./paths.js"); -const fs = require("fs"); -const termimg = require("term-img"); - -module.exports = class ImageViewerReporter { - constructor(globalConfig, options) { - this._globalConfig = globalConfig; - this._options = options; - } - - write_img_fallback(filename) { - process.stdout.write(" <" + filename + ">\n"); - } - - write_img(title, ancestors, filename) { - if (fs.existsSync(filename)) { - process.stdout.write( - `\n ${ancestors.join(" > ")} > ${title}\n\n ` - ); - termimg(filename, { - width: "640px", - height: "480px", - fallback: () => this.write_img_fallback(filename), - }); - process.stdout.write("\n"); - } - } - - /** - * Runs `tiv` to dump diff images to console in blurry format. - */ - onTestResult(testRunConfig, testResults) { - for (const test of testResults.testResults) { - if (test.status === "failed") { - const name = test.title.replace(/[ \.']/g, "_"); - let desc = test.fullName - .replace(".html", "") - .replace(/ /g, "_"); - desc = desc.slice(0, desc.length - name.length - 1); - const candidates = [ - `${testRunConfig.path.split("/test")[0]}/test/screenshots/${ - paths.RESULTS_TAGNAME - }/${desc}/${name}.diff.png`, - `test/screenshots/${desc}/${name}.diff.png`, - `${testRunConfig.path.split("/test")[0]}/test/screenshots/${ - paths.RESULTS_TAGNAME - }/${desc}/${name}.failed.png`, - `test/screenshots/${desc}/${name}.failed.png`, - `${testRunConfig.path.split("/test")[0]}/test/screenshots/${ - paths.RESULTS_TAGNAME - }/${desc}/${name}.png`, - `test/screenshots/${desc}/${name}.png`, - ]; - - for (const filename of candidates) { - if (fs.existsSync(filename)) { - this.write_img( - test.title, - test.ancestorTitles, - filename - ); - break; - } - } - } - } - } -}; diff --git a/tools/perspective-test/src/js/set_timezone.js b/tools/perspective-test/src/js/set_timezone.js new file mode 100644 index 0000000000..236b0f1360 --- /dev/null +++ b/tools/perspective-test/src/js/set_timezone.js @@ -0,0 +1,13 @@ +const JSDOMEnvironment = require("jest-environment-jsdom"); + +module.exports = class TimezoneAwareJSDOMEnvironment extends JSDOMEnvironment { + constructor(config, context) { + if (context.testPath.endsWith("timezone.spec.js")) { + process.env.TZ = "America/New_York"; + } else { + process.env.TZ = "UTC"; + } + + super(config); + } +}; diff --git a/tools/perspective-test/src/js/transform.js b/tools/perspective-test/src/js/transform.js deleted file mode 100644 index 77fd6baca7..0000000000 --- a/tools/perspective-test/src/js/transform.js +++ /dev/null @@ -1,13 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2017, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -const config = require("@finos/perspective-test/babel.config"); -config.presets[0][1].modules = "auto"; - -module.exports = require("babel-jest").createTransformer(config);