chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Delete", async () => {
|
||||
test("delete > blocks until the underlying view is removed", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
|
||||
const table = await viewer.getTable();
|
||||
await viewer.delete();
|
||||
await table.delete();
|
||||
});
|
||||
});
|
||||
|
||||
// `delete()` is destructive now
|
||||
test.skip("delete > flush after delete waits for view removal", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
|
||||
const table = await viewer.getTable();
|
||||
viewer.delete();
|
||||
await viewer.flush();
|
||||
await table.delete();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,337 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect, compareContentsToSnapshot } from "../helpers.ts";
|
||||
|
||||
const RESULT = {
|
||||
aggregates: {},
|
||||
columns: [
|
||||
"Row ID",
|
||||
"Order ID",
|
||||
"Order Date",
|
||||
"Ship Date",
|
||||
"Ship Mode",
|
||||
"Customer ID",
|
||||
"Segment",
|
||||
"Country",
|
||||
"City",
|
||||
"State",
|
||||
"Postal Code",
|
||||
"Region",
|
||||
"Product ID",
|
||||
"Category",
|
||||
"Sub-Category",
|
||||
"Sales",
|
||||
"Quantity",
|
||||
"Discount",
|
||||
"Profit",
|
||||
],
|
||||
columns_config: {},
|
||||
expressions: {},
|
||||
filter: [],
|
||||
group_by: [],
|
||||
plugin: "Debug",
|
||||
plugin_config: {},
|
||||
settings: false,
|
||||
sort: [],
|
||||
split_by: [],
|
||||
table: "load-viewer-csv",
|
||||
theme: "Pro Light",
|
||||
title: null,
|
||||
group_rollup_mode: "rollup",
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/superstore.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer")!.restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("DOM API", () => {
|
||||
test.describe("restore with table name > sets default plugin and columns", () => {
|
||||
test("await order > resolves correctly when fully awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const x = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({ table: "load-viewer-csv" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
delete x.version;
|
||||
expect(x).toEqual(RESULT);
|
||||
});
|
||||
|
||||
test("await order > resolves correctly when load is not awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const x = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({ table: "load-viewer-csv" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
delete x.version;
|
||||
expect(x).toEqual(RESULT);
|
||||
});
|
||||
|
||||
test("await order > resolves correctly when restore is not awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const x = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
viewer.restore({ table: "load-viewer-csv" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
delete x.version;
|
||||
expect(x).toEqual(RESULT);
|
||||
});
|
||||
|
||||
test("await order > resolves correctly when neither load nor restore is awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const x = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
viewer.load((window as any).__TEST_WORKER__);
|
||||
viewer.restore({ table: "load-viewer-csv" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
delete x.version;
|
||||
expect(x).toEqual(RESULT);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("load and restore before DOM append", () => {
|
||||
test("append > renders correctly when fully awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const contents = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({ table: "load-viewer-csv" });
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return document.body.innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("append > renders correctly when restore is not awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const contents = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
viewer.restore({ table: "load-viewer-csv" });
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return document.body.innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Regression: `<perspective-workspace>.addViewer({ columns_config })` was
|
||||
// losing the `columns_config` because the workspace awaits `viewer.restore`
|
||||
// while the viewer is still detached from the DOM (the Lumino widget hasn't
|
||||
// attached yet, so `onAfterAttach` hasn't fired). On that code path:
|
||||
// (a) `create_view`'s `reset_clean` early-return could leave `view_schema`
|
||||
// unpopulated in some interleavings, and
|
||||
// (b) `query_column_config_schema` then returned an EMPTY schema, which the
|
||||
// strip-on-write in `update_columns_configs` treats as "no allowed
|
||||
// keys" — dropping every entry in the incoming `columns_config`.
|
||||
// These tests reproduce the workspace's call sequence (createElement →
|
||||
// load(client) → restore(config) → appendChild) with non-trivial
|
||||
// `columns_config` / `plugin_config` payloads. They use the test-only
|
||||
// "Debug Styled" plugin (declared in the viewer test module), which — like a
|
||||
// real plugin — declares a non-empty `column_config_schema` /
|
||||
// `plugin_config_schema`. An empty-schema plugin would make the regression
|
||||
// untestable: the strip would drop the keys whether or not the bug is present.
|
||||
test.describe("restore with config payload before DOM append", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/superstore-debug.html",
|
||||
);
|
||||
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("save > columns_config round-trips when fully awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({
|
||||
table: "load-viewer-csv",
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
columns_config: {
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config.columns_config).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("save > columns_config round-trips when restore is not awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
viewer.restore({
|
||||
table: "load-viewer-csv",
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
columns_config: {
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config.columns_config).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("save > plugin_config round-trips when fully awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({
|
||||
table: "load-viewer-csv",
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
plugin_config: { edit_mode: "EDIT" },
|
||||
});
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config.plugin_config).toMatchObject({ edit_mode: "EDIT" });
|
||||
});
|
||||
|
||||
test("columns_config is applied to the plugin on first render", async ({
|
||||
page,
|
||||
}) => {
|
||||
const applied = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({
|
||||
table: "load-viewer-csv",
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
columns_config: {
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
const plugin = viewer.querySelector(
|
||||
"perspective-viewer-debug-styled",
|
||||
) as any;
|
||||
return plugin?._restored_columns_config;
|
||||
});
|
||||
|
||||
expect(applied).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
compareContentsToSnapshot,
|
||||
API_VERSION,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer")!.restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Events", () => {
|
||||
test("config-update event > fires on restore", async ({ page }) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
|
||||
if (!viewer) {
|
||||
throw new Error("Viewer not found");
|
||||
}
|
||||
// @ts-ignore
|
||||
await viewer.getTable();
|
||||
let config;
|
||||
viewer.addEventListener("perspective-config-update", (event) => {
|
||||
// @ts-ignore
|
||||
config = event.detail;
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
version: API_VERSION,
|
||||
aggregates: {},
|
||||
split_by: [],
|
||||
columns: ["Profit", "Sales"],
|
||||
columns_config: {},
|
||||
expressions: {},
|
||||
filter: [],
|
||||
plugin: "Debug",
|
||||
plugin_config: {},
|
||||
group_by: ["State"],
|
||||
group_rollup_mode: "rollup",
|
||||
settings: true,
|
||||
sort: [],
|
||||
table: "load-viewer-csv",
|
||||
theme: "Pro Light",
|
||||
title: null,
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("config-update event > fires on title edit", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
window["acc"] = [];
|
||||
await viewer!.restore({
|
||||
settings: true,
|
||||
});
|
||||
|
||||
viewer!.addEventListener("perspective-config-update", (event) => {
|
||||
window["acc"].push(event.detail);
|
||||
});
|
||||
});
|
||||
|
||||
const titleInput = page.locator("perspective-viewer #status_bar input");
|
||||
await titleInput.focus();
|
||||
await titleInput.pressSequentially("New Title");
|
||||
await titleInput.blur();
|
||||
|
||||
const result = await page.evaluate(async () => {
|
||||
return window["acc"];
|
||||
});
|
||||
|
||||
expect(result.map((x) => x.title)).toEqual(["New Title"]);
|
||||
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
return await viewer?.save();
|
||||
});
|
||||
|
||||
expect(config.title).toEqual("New Title");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
async function load(page) {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
await viewer.evaluate(async (viewer) => {
|
||||
const table = (await window.WORKER).table("a,b,c\n1,2,3\n4,5,6");
|
||||
await viewer.load(table);
|
||||
await viewer.flush();
|
||||
});
|
||||
return viewer;
|
||||
}
|
||||
|
||||
test.describe("Viewer Export", () => {
|
||||
test("export > defaults to CSV", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const result = await viewer.evaluate((viewer) => viewer.export());
|
||||
expect(typeof result).toBe("string");
|
||||
expect(result).toBe('"a","b","c"\n1,2,3\n4,5,6\n');
|
||||
});
|
||||
|
||||
test("export > csv method", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const result = await viewer.evaluate((viewer) => viewer.export("csv"));
|
||||
expect(result).toBe('"a","b","c"\n1,2,3\n4,5,6\n');
|
||||
});
|
||||
|
||||
test("export > json method", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const result = await viewer.evaluate(async (viewer) =>
|
||||
JSON.stringify(await viewer.export("json")),
|
||||
);
|
||||
|
||||
const parsed = JSON.parse(result as string);
|
||||
expect(parsed).toEqual({ a: [1, 4], b: [2, 5], c: [3, 6] });
|
||||
});
|
||||
|
||||
test("export > ndjson method", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const result = await viewer.evaluate((viewer) =>
|
||||
viewer.export("ndjson"),
|
||||
);
|
||||
|
||||
const lines = (result as string)
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((l) => JSON.parse(l));
|
||||
|
||||
expect(lines).toEqual([
|
||||
{ a: 1, b: 2, c: 3 },
|
||||
{ a: 4, b: 5, c: 6 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("export > arrow method returns ArrayBuffer", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const byteLength = await viewer.evaluate(async (viewer) => {
|
||||
const result = await viewer.export("arrow");
|
||||
return (result as ArrayBuffer).byteLength;
|
||||
});
|
||||
|
||||
expect(byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Viewer Export UTF8", () => {
|
||||
test("export > json to csv round-trip", async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
await viewer.evaluate(async (viewer) => {
|
||||
const table = (await window.WORKER).table([
|
||||
{
|
||||
年月: "2023/01/01",
|
||||
轄區分局: "第四分局",
|
||||
路口名稱: "南屯區",
|
||||
路口名稱split: "五權西路與環中路口",
|
||||
A1: 0.0,
|
||||
A2: 3.0,
|
||||
A3: 130.0,
|
||||
總件數: 18.0,
|
||||
死亡人數: 0.0,
|
||||
受傷人數: null,
|
||||
主要肇因: "未注意車前狀態",
|
||||
},
|
||||
{
|
||||
年月: "2023/01/01",
|
||||
轄區分局: "第六分局",
|
||||
路口名稱: "西屯區",
|
||||
路口名稱split: "中清聯絡道與環中路口",
|
||||
A1: 0.0,
|
||||
A2: 2.0,
|
||||
A3: 100.0,
|
||||
總件數: 15.0,
|
||||
死亡人數: 0.0,
|
||||
受傷人數: null,
|
||||
主要肇因: "未注意車前狀態",
|
||||
},
|
||||
]);
|
||||
|
||||
await viewer.load(table);
|
||||
await viewer.flush();
|
||||
});
|
||||
|
||||
const result = await viewer.evaluate((viewer) => viewer.export("csv"));
|
||||
expect(result).toBe(
|
||||
`"年月","轄區分局","路口名稱","路口名稱split","A1","A2","A3","總件數","死亡人數","主要肇因","受傷人數"\n2023-01-01,"第四分局","南屯區","五權西路與環中路口",0,3,130,18,0,"未注意車前狀態",\n2023-01-01,"第六分局","西屯區","中清聯絡道與環中路口",0,2,100,15,0,"未注意車前狀態",\n`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Flush method", async () => {
|
||||
test("flush > awaits settings field update", async ({ page }) => {
|
||||
const old_config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(old_config.settings).toBeFalsy();
|
||||
const config = await page.evaluate(async (columns) => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
viewer.restore({
|
||||
settings: true,
|
||||
});
|
||||
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...old_config,
|
||||
settings: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("flush > awaits view query field updates", async ({ page }) => {
|
||||
const old_config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(old_config.settings).toBeFalsy();
|
||||
const config = await page.evaluate(async (columns) => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State", "City"],
|
||||
split_by: ["Category"],
|
||||
});
|
||||
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...old_config,
|
||||
group_by: ["State", "City"],
|
||||
split_by: ["Category"],
|
||||
settings: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("flush > awaits config-update event triggered by restore", async ({
|
||||
page,
|
||||
}) => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
let count = 0;
|
||||
viewer.addEventListener("perspective-config-update", () => {
|
||||
count++;
|
||||
});
|
||||
|
||||
viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State", "City"],
|
||||
split_by: ["Category"],
|
||||
});
|
||||
|
||||
const first = count;
|
||||
await viewer.flush();
|
||||
const second = count;
|
||||
return [first, second];
|
||||
});
|
||||
|
||||
expect(result).toEqual([0, 1]);
|
||||
});
|
||||
|
||||
test("flush > awaits config-update event on load and does not repeat on DOM connect", async ({
|
||||
page,
|
||||
}) => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
const table = await viewer.getTable();
|
||||
await viewer.delete();
|
||||
viewer.parentElement.removeChild(viewer);
|
||||
|
||||
const new_viewer = document.createElement("perspective-viewer");
|
||||
let count = 0;
|
||||
|
||||
new_viewer.addEventListener("perspective-config-update", () => {
|
||||
count++;
|
||||
});
|
||||
|
||||
new_viewer.load(table);
|
||||
const first = count;
|
||||
await new_viewer.flush();
|
||||
const second = count;
|
||||
document.body.appendChild(new_viewer);
|
||||
await new_viewer.flush();
|
||||
const third = count;
|
||||
return [first, second, third];
|
||||
});
|
||||
|
||||
expect(result).toEqual([0, 1, 1]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
test.describe("Focus", async () => {
|
||||
test.beforeEach(async function init({ page }) {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/superstore_with_input.html",
|
||||
);
|
||||
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("focus > preserves external widget focus during restore", async ({
|
||||
page,
|
||||
}) => {
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
const tagName = await viewer.evaluate(async (viewer) => {
|
||||
const input = document.querySelector("input");
|
||||
input.focus();
|
||||
await viewer.restore({ group_by: ["State"] });
|
||||
return document.activeElement.tagName;
|
||||
});
|
||||
|
||||
expect(tagName).toEqual("INPUT");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
compareContentsToSnapshot,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.describe("Viewer Load", () => {
|
||||
test("load > resolves with valid Table promise", async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
await viewer.evaluate(async (viewer) => {
|
||||
const goodTable = (await window.WORKER).table("a,b,c\n1,2,3");
|
||||
return viewer.load(goodTable);
|
||||
});
|
||||
await expect(viewer).toHaveText(/"a","b","c"/); // column titles
|
||||
});
|
||||
|
||||
test("load > rejects with failed Table promise", async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
await expect(
|
||||
viewer.evaluate((viewer) => {
|
||||
const errorTable = Promise.reject(new Error("blimpy"));
|
||||
return viewer.load(errorTable);
|
||||
}),
|
||||
).rejects.toThrow("blimpy");
|
||||
});
|
||||
|
||||
test("load > recovers after a rejected Table promise", async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
const didError = await viewer.evaluate(async (viewer) => {
|
||||
const errorTable = Promise.reject(new Error("blimpy"));
|
||||
const worker = await window.WORKER;
|
||||
let didError = false;
|
||||
try {
|
||||
await viewer.load(errorTable);
|
||||
} catch (e) {
|
||||
if (e.message.includes("blimpy")) {
|
||||
didError = true;
|
||||
}
|
||||
}
|
||||
|
||||
const goodTable = worker.table("a,b,c\n1,2,3");
|
||||
await viewer.load(goodTable);
|
||||
return didError;
|
||||
});
|
||||
expect(didError).toBe(true);
|
||||
});
|
||||
|
||||
test("load > is inert when called twice with the same Table", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({ settings: true });
|
||||
const table = await viewer.getTable();
|
||||
await viewer.load(table);
|
||||
await viewer.flush();
|
||||
return viewer.shadowRoot.innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(
|
||||
contents,
|
||||
"load-called-twice-with-the-same-table.txt",
|
||||
);
|
||||
});
|
||||
|
||||
test("load > does not throw when restore is called during a failed load", async ({
|
||||
page,
|
||||
consoleLogs,
|
||||
}) => {
|
||||
const errors = [];
|
||||
page.on("pageerror", async (msg) => {
|
||||
errors.push(`${msg.name}::${msg.message}`);
|
||||
});
|
||||
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html", {
|
||||
waitUntil: "networkidle",
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
viewer.load(
|
||||
new Promise((_, reject) => reject("Intentional Load Error")),
|
||||
);
|
||||
try {
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
plugin: "Debug",
|
||||
});
|
||||
} catch (e) {
|
||||
// We need to catch this error else the `evaluate()` fails.
|
||||
// We need to await the call because we want it to fail
|
||||
// before continuing the test.
|
||||
console.error("Caught error:", e);
|
||||
}
|
||||
|
||||
await new Promise((x) => setTimeout(x, 1000));
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
expect(errors).toEqual([
|
||||
'Error::Failed to construct table from JsValue("Intentional Load Error")',
|
||||
]);
|
||||
consoleLogs.expectedLogs.push("error", /Intentional Load Error/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
// Uses test-only plugins ("Debug Styled" / "Debug Alt") declared in the
|
||||
// viewer test module so this spec does not depend on the descendant
|
||||
// `@perspective-dev/viewer-datagrid` / `-charts` packages.
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore-debug.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Reset", () => {
|
||||
test("soft reset restores columns_config after plugin swap", async ({
|
||||
page,
|
||||
}) => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
columns_config: {
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await viewer.restore({ plugin: "Debug Alt" });
|
||||
await viewer.reset();
|
||||
await viewer.flush();
|
||||
|
||||
// The active plugin is slotted into the viewer's light DOM, not
|
||||
// its shadow root.
|
||||
const plugin = viewer.querySelector(
|
||||
"perspective-viewer-debug-styled",
|
||||
) as any;
|
||||
|
||||
const config = await viewer.save();
|
||||
return {
|
||||
active_plugin: config.plugin,
|
||||
saved_columns_config: config.columns_config,
|
||||
applied_columns_config: plugin?._restored_columns_config,
|
||||
};
|
||||
});
|
||||
|
||||
// Soft reset reverts to the default plugin ...
|
||||
expect(result.active_plugin).toBe("Debug Styled");
|
||||
|
||||
// ... the preserved `columns_config` is re-applied to that plugin
|
||||
// (the plugin's own `restore()` saw it) ...
|
||||
expect(result.applied_columns_config).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// ... and it survives the round-trip back out through `save()`.
|
||||
expect(result.saved_columns_config).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,198 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
DEFAULT_CONFIG,
|
||||
compareContentsToSnapshot,
|
||||
API_VERSION,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Save/Restore", async () => {
|
||||
test("save > returns the current viewer config", async ({ page }) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("restore > roundtrips a saved config", async ({ page }) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
});
|
||||
|
||||
const config2 = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.reset();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config2).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: [
|
||||
"Row ID",
|
||||
"Order ID",
|
||||
"Order Date",
|
||||
"Ship Date",
|
||||
"Ship Mode",
|
||||
"Customer ID",
|
||||
"Segment",
|
||||
"Country",
|
||||
"City",
|
||||
"State",
|
||||
"Postal Code",
|
||||
"Region",
|
||||
"Product ID",
|
||||
"Category",
|
||||
"Sub-Category",
|
||||
"Sales",
|
||||
"Quantity",
|
||||
"Discount",
|
||||
"Profit",
|
||||
],
|
||||
plugin: "Debug",
|
||||
settings: true,
|
||||
theme: "Pro Light",
|
||||
});
|
||||
|
||||
const config3 = await page.evaluate(async (config) => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore(config);
|
||||
return await viewer.save();
|
||||
}, config);
|
||||
|
||||
expect(config3).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
theme: "Pro Light",
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("save > roundtrips in string format", async ({ page }) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
return await viewer.save("string");
|
||||
});
|
||||
|
||||
const config3 = await page.evaluate(async (config) => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.reset();
|
||||
await viewer.restore(config);
|
||||
return await viewer.save();
|
||||
}, config);
|
||||
|
||||
expect(config3).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
theme: "Pro Light",
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("save > roundtrips in arraybuffer format", async ({ page }) => {
|
||||
const config3 = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
|
||||
const config = await viewer.save("arraybuffer");
|
||||
await viewer.reset();
|
||||
await viewer.restore(config);
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config3).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
theme: "Pro Light",
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "@perspective-dev/test";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.restore({ plugin: "Debug" });
|
||||
// Explicitly register both themes so the memoized theme cache contains
|
||||
// "Pro Dark" even though superstore.html only loads pro.css.
|
||||
await viewer.resetThemes(["Pro Light", "Pro Dark"]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Theme", () => {
|
||||
test("restore sets the theme attribute on the host element", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ theme: "Pro Dark" });
|
||||
});
|
||||
|
||||
const themeAttr = await page.evaluate(() =>
|
||||
document.querySelector("perspective-viewer")!.getAttribute("theme"),
|
||||
);
|
||||
|
||||
expect(themeAttr).toBe("Pro Dark");
|
||||
});
|
||||
|
||||
test("save returns the correct theme after restore", async ({ page }) => {
|
||||
const saved = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ theme: "Pro Dark" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(saved.theme).toBe("Pro Dark");
|
||||
});
|
||||
|
||||
test("theme is preserved across settings panel open/close toggle", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Set theme, then toggle settings open and closed.
|
||||
// This exercises the UpdateSettingsOpen path which must NOT wipe
|
||||
// available_themes from the PresentationProps snapshot.
|
||||
const saved = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ theme: "Pro Dark" });
|
||||
await viewer.restore({ settings: true });
|
||||
await viewer.restore({ settings: false });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(saved.theme).toBe("Pro Dark");
|
||||
expect(saved.settings).toBe(false);
|
||||
});
|
||||
|
||||
test("switching theme updates the theme attribute", async ({ page }) => {
|
||||
const themeAttr = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ theme: "Pro Dark" });
|
||||
await viewer.restore({ theme: "Pro Light" });
|
||||
return viewer.getAttribute("theme");
|
||||
});
|
||||
|
||||
expect(themeAttr).toBe("Pro Light");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
test.describe("View Lifecycle", () => {
|
||||
test("conflation > silences view-not-found during rapid restore", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/superstore_lazy_viewer.html",
|
||||
);
|
||||
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
let vnf = false;
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
if (msg.text().includes("View not found")) {
|
||||
vnf = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const worker = window.__TEST_WORKER__;
|
||||
let resolve;
|
||||
let is_paused = false;
|
||||
const BasePlugin = customElements.get("perspective-viewer-plugin");
|
||||
class PausePlugin extends BasePlugin {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "pause-plugin",
|
||||
select_mode: "select",
|
||||
config_column_names: [],
|
||||
};
|
||||
}
|
||||
|
||||
async draw(view) {
|
||||
if (is_paused) {
|
||||
await new Promise((x) => {
|
||||
resolve = x;
|
||||
});
|
||||
}
|
||||
|
||||
const size = await view.num_rows();
|
||||
this.textContent = `Rows: ${size}`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("pause-plugin", PausePlugin);
|
||||
const Viewer = customElements.get("perspective-viewer");
|
||||
Viewer.registerPlugin("pause-plugin");
|
||||
|
||||
// use a new viewer because only new viewers get loaded with the registered plugin
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
document.body.append(viewer);
|
||||
worker.table("a,b,c\n1,2,3", { name: "A" });
|
||||
|
||||
await viewer.load(worker);
|
||||
await viewer.restore({ table: "A", plugin: "pause-plugin" });
|
||||
is_paused = true;
|
||||
|
||||
// Change in 4.1.0 - empty restore now does not render
|
||||
const restore_task = viewer.restore({
|
||||
plugin: "pause-plugin",
|
||||
});
|
||||
|
||||
while (!resolve) {
|
||||
await new Promise((x) => setTimeout(x, 0));
|
||||
}
|
||||
|
||||
await new Promise((x) => setTimeout(x, 0));
|
||||
resolve();
|
||||
resolve = undefined;
|
||||
is_paused = false;
|
||||
await restore_task;
|
||||
});
|
||||
|
||||
expect(vnf).toBeFalsy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user