// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ // ┃ 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, PageView, compareContentsToSnapshot, } from "../helpers.ts"; async function openSidebarAndScrollToBottom() { const elem = document.querySelector("perspective-viewer"); await elem.getTable(); await elem.toggleConfig(true); elem.shadowRoot.querySelector("#active-columns").scrollTop = 500; } async function checkSaveDisabled(page, expr) { let view = new PageView(page); let settingsPanel = await view.openSettingsPanel(); await settingsPanel.createNewExpression("", expr, false); } 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("Expressions", () => { test("editor > opens on add-column button click", async ({ page }) => { await page.evaluate(openSidebarAndScrollToBottom); await page.waitForFunction( () => !!document .querySelector("perspective-viewer") .shadowRoot.querySelector("#add-expression"), ); await page.locator("perspective-viewer #add-expression").click(); await page.waitForFunction(() => { const root = document .querySelector("perspective-viewer") .shadowRoot.querySelector("#editor-container"); return !!root; }); const editor = await page.waitForFunction(async () => { const root = document .querySelector("perspective-viewer") .shadowRoot.querySelector("#editor-container"); return root.querySelector("#content"); }); const contents = await editor.evaluate((x) => x.outerHTML); await page.evaluate(() => document.activeElement.blur()); await compareContentsToSnapshot(contents); }); test("editor > closes on close button click", async ({ page }) => { await page.evaluate(openSidebarAndScrollToBottom); await page.waitForFunction( () => !!document .querySelector("perspective-viewer") .shadowRoot.querySelector("#add-expression"), ); await page.locator("perspective-viewer #add-expression").click(); await page.waitForSelector("#editor-container"); await page.evaluate(async () => { let root = document.querySelector("perspective-viewer").shadowRoot; await root.querySelector("#column_settings_close_button").click(); }); await page.waitForSelector("#editor-container", { state: "hidden", }); const contents = await page.evaluate(async () => { let root = document.querySelector("perspective-viewer").shadowRoot; return ( root.querySelector("#editor-container")?.innerHTML || "MISSING" ); }); await compareContentsToSnapshot(contents); }); test("validation > disables save for unknown symbols", async ({ page }) => { await checkSaveDisabled(page, "abc"); }); test("validation > disables save for type-invalid expression", async ({ page, }) => { await checkSaveDisabled(page, '"Sales" + "Category";'); }); test("validation > disables save for invalid column references", async ({ page, }) => { await checkSaveDisabled(page, '"aaaa" + "Sales";'); }); test("columns > shows both aliased and non-aliased expressions", async ({ page, }) => { const contents = await page.evaluate(async () => { document.activeElement.blur(); const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ expressions: { "1 + 2": "1 + 2", abc: "3 + 4" }, }); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); // No longer relevant as we cannot save a duplicate identifier test.skip("columns > overwrites a duplicate expression alias", async ({ page, }) => { let view = new PageView(page); view.restore({ expressions: { "4 + 5": "3 + 4" } }); await view.settingsPanel.createNewExpression("", "4 + 5", true); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); // No longer relevant as we cannot save a duplicate identifier test.skip("columns > overwrites a duplicate expression", async ({ page, }) => { let view = new PageView(page); view.restore({ expressions: { "3 + 4": "3 + 4" } }); await view.settingsPanel.createNewExpression("", "3 + 4", true); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); test("reset > deletes all expressions on full reset", async ({ page }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" }, }); await elem.reset(true); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return ( elem.shadowRoot.querySelector("#sub-columns")?.innerHTML || "MISSING" ); }); await compareContentsToSnapshot(contents); }); test("reset > preserves expressions on partial reset", async ({ page }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" }, }); await elem.reset(false); }); const content = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(content); }); test("reset > deletes expressions used in columns on full reset", async ({ page, }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ columns: ["1 + 2"], expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" }, }); await elem.reset(true); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return ( elem.shadowRoot.querySelector("#sub-columns")?.innerHTML || "MISSING" ); }); await compareContentsToSnapshot(contents); }); test("reset > preserves expressions used in columns on partial reset", async ({ page, }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ columns: ["1 + 2"], expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" }, }); await elem.reset(false); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); test("reset > deletes expressions used in group_by/sort/filter on full reset", async ({ page, }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ columns: ["1 + 2"], group_by: ["3 + 4"], sort: [["1 + 2", "asc"]], filter: [["1 + 2", "==", 3]], expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" }, }); await elem.reset(true); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return ( elem.shadowRoot.querySelector("#sub-columns")?.innerHTML || "MISSING" ); }); await compareContentsToSnapshot(contents); }); test("persistence > survives views that don't reference them", async ({ page, }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" }, }); await elem.restore({ columns: ["State"], }); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); test("add button > marks on hover", async ({ page }) => { await page.evaluate(openSidebarAndScrollToBottom); let addExprButton = await page.waitForSelector("#add-expression"); let notHovered = await addExprButton.getAttribute("class"); expect(notHovered).toBeNull(); await addExprButton.hover(); let hovered = await addExprButton.getAttribute("class"); expect(hovered).toBe("dragdrop-hover"); }); // Currently does not work in Firefox! test("add button > marks on click", async ({ page }) => { await page.evaluate(openSidebarAndScrollToBottom); let addExprButton = await page.waitForSelector("#add-expression"); let unclicked = await addExprButton.getAttribute("class"); expect(unclicked).toBeNull(); await addExprButton.click(); await page.evaluate(openSidebarAndScrollToBottom); let clicked = await addExprButton.getAttribute("class"); expect(clicked).toBe("dragdrop-hover"); }); test("persistence > survives views that reference them", async ({ page, }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" }, }); await elem.restore({ columns: ["3 + 4"], }); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); test("aggregates > applies aggregate to expression column", async ({ page, }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ expressions: { '"Sales" + 100': '"Sales" + 100' }, aggregates: { '"Sales" + 100': "avg" }, group_by: ["State"], columns: ['"Sales" + 100'], }); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); test("sort > sorts by a hidden expression column", async ({ page }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ expressions: { '"Sales" + 100': '"Sales" + 100' }, sort: [['"Sales" + 100', "asc"]], columns: ["Row ID"], }); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); test("filter > filters by an expression column", async ({ page }) => { await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); await elem.restore({ expressions: { '"Sales" + 100': '"Sales" + 100' }, filter: [['"Sales" + 100', ">", 150]], columns: ["Row ID", '"Sales" + 100'], }); }); const contents = await page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); return elem.shadowRoot.querySelector("#sub-columns").innerHTML; }); await compareContentsToSnapshot(contents); }); });