From 53699e8875871193660d5c87b3444fc1cf390299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Rahir=20=28rar=29?= Date: Wed, 22 Mar 2023 21:59:50 +0100 Subject: [PATCH] [FIX] tests: fix component tests and their organization 1. UI tests would sometimes do queries on document but it is more correct to rely on `fixture` as ATM? we cannot ensure that a fixture is alone in the document. Note that this commit only fixes the potentially problematic occurences. Related to point 2. 2. Some tests were nested in describe section that would mount useless components. --- .../__snapshots__/context_menu.test.ts.snap | 2 +- tests/components/bottom_bar.test.ts | 35 +- tests/components/charts.test.ts | 2 +- tests/components/context_menu.test.ts | 1229 ++++++++--------- .../custom_currency_side_panel.test.ts | 4 +- tests/components/figure.test.ts | 11 +- tests/components/grid.test.ts | 3 - tests/components/selection_input.test.ts | 8 +- tests/components/spreadsheet.test.ts | 56 +- tests/components/top_bar.test.ts | 38 +- tests/menu_item_insert_chart.test.ts | 2 +- tests/menu_items_registry.test.ts | 2 +- tests/test_helpers/helpers.ts | 12 +- 13 files changed, 691 insertions(+), 713 deletions(-) diff --git a/tests/components/__snapshots__/context_menu.test.ts.snap b/tests/components/__snapshots__/context_menu.test.ts.snap index cc3e71940b..b1d8d40e68 100644 --- a/tests/components/__snapshots__/context_menu.test.ts.snap +++ b/tests/components/__snapshots__/context_menu.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Standalone context menu tests Context Menu context menu simple rendering 1`] = ` +exports[`Context Menu integration tests context menu simple rendering 1`] = `
diff --git a/tests/components/bottom_bar.test.ts b/tests/components/bottom_bar.test.ts index af354b5658..91f75ca302 100644 --- a/tests/components/bottom_bar.test.ts +++ b/tests/components/bottom_bar.test.ts @@ -58,19 +58,17 @@ async function mountBottomBar( ): Promise<{ parent: Parent; model: Model }> { let parent: Component; ({ fixture, parent } = await mountComponent(Parent, { model, env, props: { model } })); - return { parent: parent as Parent, model: parent.props.model }; + return { parent: parent as Parent, model }; } describe("BottomBar component", () => { test("simple rendering", async () => { await mountBottomBar(); - expect(fixture.querySelector(".o-spreadsheet-bottom-bar")).toMatchSnapshot(); }); test("Can create a new sheet", async () => { const { model } = await mountBottomBar(); - const dispatch = jest.spyOn(model, "dispatch"); mockUuidV4To(model, 42); const activeSheetId = model.getters.getActiveSheetId(); @@ -112,7 +110,6 @@ describe("BottomBar component", () => { test("Can open context menu of a sheet", async () => { await mountBottomBar(); - expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); @@ -121,7 +118,6 @@ describe("BottomBar component", () => { test("Can open context menu of a sheet with the arrow", async () => { await mountBottomBar(); - expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); await click(fixture, ".o-sheet-icon"); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); @@ -129,7 +125,6 @@ describe("BottomBar component", () => { test("Click on the arrow when the context menu is open should close it", async () => { await mountBottomBar(); - expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); await click(fixture, ".o-sheet-icon"); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); @@ -165,10 +160,11 @@ describe("BottomBar component", () => { }); test("Can move right a sheet", async () => { - const { model } = await mountBottomBar(); - const dispatch = jest.spyOn(model, "dispatch"); + const model = new Model(); createSheet(model, { sheetId: "42" }); - await nextTick(); + await mountBottomBar(model); + const dispatch = jest.spyOn(model, "dispatch"); + triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); const sheetId = model.getters.getActiveSheetId(); @@ -180,12 +176,13 @@ describe("BottomBar component", () => { }); test("Can move left a sheet", async () => { - const { model } = await mountBottomBar(); + const model = new Model(); + createSheet(model, { sheetId: "42", activate: true }); + await mountBottomBar(model); const dispatch = jest.spyOn(model, "dispatch"); - createSheet(model, { sheetId: "42" }); - activateSheet(model, "42"); - await nextTick(); - triggerMouseEvent(".o-sheet[data-id='42']", "contextmenu"); + + const target = fixture.querySelectorAll(".o-sheet")[1]!; + triggerMouseEvent(target, "contextmenu"); await nextTick(); const sheetId = model.getters.getActiveSheetId(); await click(fixture, ".o-menu-item[data-name='move_left'"); @@ -196,10 +193,10 @@ describe("BottomBar component", () => { }); test("Can hide a sheet", async () => { - const { model } = await mountBottomBar(); - const dispatch = jest.spyOn(model, "dispatch"); + const model = new Model(); createSheet(model, { sheetId: "42" }); - activateSheet(model, "42"); + await mountBottomBar(model); + const dispatch = jest.spyOn(model, "dispatch"); triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); @@ -211,9 +208,10 @@ describe("BottomBar component", () => { }); test("Hide sheet menu is not visible if there's only one visible sheet", async () => { - const { model } = await mountBottomBar(); + const model = new Model(); createSheet(model, { sheetId: "42" }); hideSheet(model, "42"); + await mountBottomBar(model); triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); @@ -366,6 +364,7 @@ describe("BottomBar component", () => { const { model } = await mountBottomBar(); const sheet = model.getters.getActiveSheetId(); createSheet(model, { sheetId: "42" }); + expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); await click(fixture, ".o-list-sheets"); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); diff --git a/tests/components/charts.test.ts b/tests/components/charts.test.ts index bcc1a44ee7..4dd53d2b97 100644 --- a/tests/components/charts.test.ts +++ b/tests/components/charts.test.ts @@ -61,7 +61,7 @@ let sheetId: string; let parent: Spreadsheet; -describe("figures", () => { +describe("charts", () => { beforeEach(async () => { mockChartData = mockChart(); chartId = "someuuid"; diff --git a/tests/components/context_menu.test.ts b/tests/components/context_menu.test.ts index e96f59569c..f0344c472f 100644 --- a/tests/components/context_menu.test.ts +++ b/tests/components/context_menu.test.ts @@ -1,22 +1,17 @@ -import { Component, useSubEnv, xml } from "@odoo/owl"; +import { Component, xml } from "@odoo/owl"; import { Menu } from "../../src/components/menu/menu"; import { MENU_ITEM_HEIGHT, MENU_VERTICAL_PADDING, MENU_WIDTH } from "../../src/constants"; import { toXC } from "../../src/helpers"; import { Model } from "../../src/model"; import { cellMenuRegistry } from "../../src/registries/menus/cell_menu_registry"; import { createMenu, MenuItem } from "../../src/registries/menu_items_registry"; -import { MockClipboard } from "../test_helpers/clipboard"; import { setCellContent } from "../test_helpers/commands_helpers"; -import { - click, - keyDown, - rightClickCell, - simulateClick, - triggerMouseEvent, -} from "../test_helpers/dom_helper"; +import { rightClickCell, simulateClick, triggerMouseEvent } from "../test_helpers/dom_helper"; import { getCell, getCellContent, getEvaluatedCell } from "../test_helpers/getters_helpers"; + import { getStylePropertyInPx, + makeTestFixture, mountComponent, mountSpreadsheet, nextTick, @@ -95,69 +90,64 @@ function getSubMenuSize(depth = 1) { return getSize(menuItems.length); } -describe("Standalone context menu tests", () => { - beforeEach(async () => { - const clipboard = new MockClipboard(); - Object.defineProperty(navigator, "clipboard", { - get() { - return clipboard; - }, - configurable: true, - }); - - ({ model, fixture } = await mountSpreadsheet()); - }); +interface ContextMenuTestConfig { + onClose?: () => void; + menuItems?: MenuItem[]; +} - function getSelectionAnchorCellXc(model: Model): string { - const { col, row } = model.getters.getSelection().anchor.cell; - return toXC(col, row); - } +async function renderContextMenu( + x: number, + y: number, + testConfig: ContextMenuTestConfig = {}, + width = 1000, + height = 1000 +): Promise<[number, number]> { + // x, y are relative to the upper left grid corner, but the menu + // props must take the top bar into account. + fixture = makeTestFixture(); + ({ fixture, model } = await mountComponent(ContextMenuParent, { + props: { + x, + y, + width, + height, + model: new Model(), + config: testConfig, + }, + fixture, + })); - interface ContextMenuTestConfig { - onClose?: () => void; - menuItems?: MenuItem[]; - } + return [x, y]; +} - async function renderContextMenu( - x: number, - y: number, - testConfig: ContextMenuTestConfig = {}, - width = 1000, - height = 1000 - ): Promise<[number, number]> { - // x, y are relative to the upper left grid corner, but the menu - // props must take the top bar into account. - - const model = new Model(); - const props = { x, y, width, height, model, config: testConfig }; - ({ fixture } = await mountComponent(ContextMenuParent, { props, fixture, model })); - await nextTick(); - return [x, y]; - } +function getSelectionAnchorCellXc(model: Model): string { + const { col, row } = model.getters.getSelection().anchor.cell; + return toXC(col, row); +} - const subMenu: MenuItem[] = createMenu([ - { - id: "root", - name: "root", - children: [ - () => [ - { - id: "subMenu1", - name: "subMenu1", - action() {}, - }, - { - id: "subMenu2", - name: "subMenu2", - action() {}, - }, - ], +const subMenu: MenuItem[] = createMenu([ + { + id: "root", + name: "root", + children: [ + () => [ + { + id: "subMenu1", + name: "subMenu1", + action() {}, + }, + { + id: "subMenu2", + name: "subMenu2", + action() {}, + }, ], - }, - ]); + ], + }, +]); - class ContextMenuParent extends Component { - static template = xml/* xml */ ` +class ContextMenuParent extends Component { + static template = xml/* xml */ `
{ />
`; - static components = { Menu }; - menus!: MenuItem[]; - position!: { x: number; y: number; width: number; height: number }; - onClose!: () => void; - - setup() { - useSubEnv({ - model: this.props.model, - isDashboard: () => this.props.model.getters.isDashboard(), - }); - } - - constructor(props, env, node) { - super(props, env, node); - this.onClose = this.props.config.onClose || (() => {}); - this.position = { - x: this.props.x, - y: this.props.y, - width: this.props.width, - height: this.props.height, - }; - this.menus = - this.props.config.menuItems || - createMenu([ - { - id: "Action", - name: "Action", - action() {}, - }, - ]); - this.props.model.dispatch("RESIZE_SHEETVIEW", { - height: this.props.height, - width: this.props.width, - gridOffsetX: 0, - gridOffsetY: 0, - }); - } + static components = { Menu }; + menus!: MenuItem[]; + position!: { x: number; y: number; width: number; height: number }; + onClose!: () => void; + + constructor(props, env, node) { + super(props, env, node); + this.onClose = this.props.config.onClose || (() => {}); + this.position = { + x: this.props.x, + y: this.props.y, + width: this.props.width, + height: this.props.height, + }; + this.menus = + this.props.config.menuItems || + createMenu([ + { + id: "Action", + name: "Action", + action() {}, + }, + ]); + this.props.model.dispatch("RESIZE_SHEETVIEW", { + height: this.props.height, + width: this.props.width, + gridOffsetX: 0, + gridOffsetY: 0, + }); } +} - describe("Context Menu", () => { - test("context menu simple rendering", async () => { - await rightClickCell(model, "C8"); - expect(fixture.querySelector(".o-menu")).toMatchSnapshot(); - }); +describe("Context Menu integration tests", () => { + beforeEach(async () => { + ({ fixture, model } = await mountSpreadsheet()); + }); + test("context menu simple rendering", async () => { + await rightClickCell(model, "C8"); + expect(fixture.querySelector(".o-menu")).toMatchSnapshot(); + }); - test("right click on a cell opens a context menu", async () => { - expect(getSelectionAnchorCellXc(model)).toBe("A1"); - expect(fixture.querySelector(".o-menu")).toBeFalsy(); - await rightClickCell(model, "C8"); - expect(getSelectionAnchorCellXc(model)).toBe("C8"); - expect(fixture.querySelector(".o-menu")).toBeTruthy(); - }); + test("right click on a cell opens a context menu", async () => { + expect(getSelectionAnchorCellXc(model)).toBe("A1"); + expect(fixture.querySelector(".o-menu")).toBeFalsy(); + await rightClickCell(model, "C8"); + expect(getSelectionAnchorCellXc(model)).toBe("C8"); + expect(fixture.querySelector(".o-menu")).toBeTruthy(); + }); - test("right click on a cell, then left click elsewhere closes a context menu", async () => { - await rightClickCell(model, "C8"); - expect(getSelectionAnchorCellXc(model)).toBe("C8"); - await nextTick(); - expect(fixture.querySelector(".o-menu")).toBeTruthy(); + test("right click on a cell, then left click elsewhere closes a context menu", async () => { + await rightClickCell(model, "C8"); + expect(getSelectionAnchorCellXc(model)).toBe("C8"); + await nextTick(); + expect(fixture.querySelector(".o-menu")).toBeTruthy(); - await simulateClick(".o-grid-overlay", 50, 50); - expect(fixture.querySelector(".o-menu")).toBeFalsy(); - }); + await simulateClick(".o-grid-overlay", 50, 50); + expect(fixture.querySelector(".o-menu")).toBeFalsy(); + }); - test("right click on a cell, then hitting esc key closes a context menu", async () => { - await rightClickCell(model, "C8"); - expect(fixture.querySelector(".o-menu")).toBeTruthy(); + test("can copy/paste with context menu", async () => { + setCellContent(model, "B1", "b1"); - await keyDown("Escape"); - expect(fixture.querySelector(".o-menu")).toBeFalsy(); - }); + await rightClickCell(model, "B1"); + expect(getSelectionAnchorCellXc(model)).toBe("B1"); - test("can copy/paste with context menu", async () => { - setCellContent(model, "B1", "b1"); + // click on 'copy' menu item + await simulateClick(".o-menu div[data-name='copy']"); - await rightClickCell(model, "B1"); - expect(getSelectionAnchorCellXc(model)).toBe("B1"); + await rightClickCell(model, "B2"); - // click on 'copy' menu item - await simulateClick(".o-menu div[data-name='copy']"); + // click on 'paste' menu item + await simulateClick(".o-menu div[data-name='paste']"); + expect(getCellContent(model, "B1")).toBe("b1"); + expect(getCellContent(model, "B2")).toBe("b1"); + }); - await rightClickCell(model, "B2"); + test("can cut/paste with context menu", async () => { + setCellContent(model, "B1", "b1"); - // click on 'paste' menu item - await simulateClick(".o-menu div[data-name='paste']"); - expect(getCellContent(model, "B1")).toBe("b1"); - expect(getCellContent(model, "B2")).toBe("b1"); - }); + await rightClickCell(model, "B1"); - test("can cut/paste with context menu", async () => { - setCellContent(model, "B1", "b1"); + // click on 'cut' menu item + await simulateClick(".o-menu div[data-name='cut']"); - await rightClickCell(model, "B1"); + // right click on B2 + await rightClickCell(model, "B2"); + await nextTick(); + expect(getSelectionAnchorCellXc(model)).toBe("B2"); - // click on 'cut' menu item - await simulateClick(".o-menu div[data-name='cut']"); + // click on 'paste' menu item + await simulateClick(".o-menu div[data-name='paste']"); - // right click on B2 - await rightClickCell(model, "B2"); - await nextTick(); - expect(getSelectionAnchorCellXc(model)).toBe("B2"); + expect(getCell(model, "B1")).toBeUndefined(); + expect(getCellContent(model, "B2")).toBe("b1"); + }); - // click on 'paste' menu item - await simulateClick(".o-menu div[data-name='paste']"); + test("menu does not close when right click elsewhere", async () => { + await rightClickCell(model, "B1"); + expect(fixture.querySelector(".o-menu")).toBeTruthy(); + await rightClickCell(model, "D5"); + expect(fixture.querySelector(".o-menu")).toBeTruthy(); + }); - expect(getCell(model, "B1")).toBeUndefined(); - expect(getCellContent(model, "B2")).toBe("b1"); - }); + test("close contextmenu when clicking on menubar", async () => { + await rightClickCell(model, "B1"); + expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeTruthy(); + triggerMouseEvent(".o-topbar-topleft", "click"); + await nextTick(); + expect(fixture.querySelector(".o-menu")).toBeFalsy(); + }); - test("menu does not close when right click elsewhere", async () => { - await rightClickCell(model, "B1"); - expect(fixture.querySelector(".o-menu")).toBeTruthy(); - await rightClickCell(model, "D5"); - expect(fixture.querySelector(".o-menu")).toBeTruthy(); - }); + test("close contextmenu when clicking on menubar item", async () => { + await rightClickCell(model, "B1"); + expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeTruthy(); + triggerMouseEvent(".o-topbar-menu[data-id='insert']", "click"); + await nextTick(); + expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeFalsy(); + }); - test("close contextmenu when clicking on menubar", async () => { - await rightClickCell(model, "B1"); - expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeTruthy(); - await click(fixture, ".o-topbar-topleft"); - expect(fixture.querySelector(".o-menu")).toBeFalsy(); - }); - test("close contextmenu when clicking on menubar item", async () => { - await rightClickCell(model, "B1"); - expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeTruthy(); - await click(fixture, ".o-topbar-menu[data-id='insert']"); - expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeFalsy(); - }); - test("close contextmenu when clicking on tools bar", async () => { - await rightClickCell(model, "B1"); - expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeTruthy(); - const fontSizeTool = fixture.querySelector('.o-tool[title="Bold (Ctrl+B)"]')!; - triggerMouseEvent(fontSizeTool, "click"); - await nextTick(); - expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeFalsy(); - }); + test("close contextmenu when clicking on tools bar", async () => { + await rightClickCell(model, "B1"); + expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeTruthy(); + const boldTool = fixture.querySelector('.o-tool[title="Bold (Ctrl+B)"]')!; + triggerMouseEvent(boldTool, "click"); + await nextTick(); + expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeFalsy(); + }); - test("close contextmenu when clicking on menubar item", async () => { - await rightClickCell(model, "B1"); - expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeTruthy(); - triggerMouseEvent(".o-topbar-menu[data-id='insert']", "click"); - await nextTick(); - expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeFalsy(); - }); + test("menu can be hidden/displayed based on the env", async () => { + const menuDefinitions = Object.assign({}, cellMenuRegistry.content); + cellMenuRegistry + .add("visible_action", { + name: "visible_action", + sequence: 1, + isVisible: (env) => getEvaluatedCell(model, "B1").value === "b1", + action() {}, + }) + .add("hidden_action", { + name: "hidden_action", + sequence: 2, + isVisible: (env) => getEvaluatedCell(model, "B1").value !== "b1", + action() {}, + }); + setCellContent(model, "B1", "b1"); + await rightClickCell(model, "B1"); + expect(fixture.querySelector(".o-menu div[data-name='visible_action']")).toBeTruthy(); + expect(fixture.querySelector(".o-menu div[data-name='hidden_action']")).toBeFalsy(); + cellMenuRegistry.content = menuDefinitions; + }); - test("menu can be hidden/displayed based on the env", async () => { - const menuDefinitions = Object.assign({}, cellMenuRegistry.content); - cellMenuRegistry - .add("visible_action", { - name: "visible_action", - isVisible: (env) => getEvaluatedCell(model, "B1").value === "b1", - action() {}, - }) - .add("hidden_action", { - name: "hidden_action", - isVisible: (env) => getEvaluatedCell(model, "B1").value !== "b1", - action() {}, - }); - setCellContent(model, "B1", "b1"); - await rightClickCell(model, "B1"); - expect(fixture.querySelector(".o-menu div[data-name='visible_action']")).toBeTruthy(); - expect(fixture.querySelector(".o-menu div[data-name='hidden_action']")).toBeFalsy(); - cellMenuRegistry.content = menuDefinitions; - }); + test("scroll through the menu with the wheel / scrollbar prevents the grid from scrolling", async () => { + const verticalScrollBar = fixture.querySelector(".o-scrollbar.vertical") as HTMLElement; + const horizontalScrollBar = fixture.querySelector(".o-scrollbar.horizontal") as HTMLElement; + expect(verticalScrollBar.scrollTop).toBe(0); + expect(horizontalScrollBar.scrollLeft).toBe(0); - test("submenu opens and close when (un)overed", async () => { - const menuItems = createMenu([ - { - id: "action", - name: "action", - action() {}, - }, - { - id: "root", - name: "root", - children: [ - () => [ - { - id: "subMenu", - name: "subMenu", - action() {}, - }, - ], - ], - }, - ]); - await renderContextMenu(300, 300, { menuItems }); - triggerMouseEvent(".o-menu div[data-name='root']", "mouseover"); - await nextTick(); - expect(fixture.querySelector(".o-menu div[data-name='subMenu']")).toBeTruthy(); - triggerMouseEvent(".o-menu div[data-name='action']", "mouseover"); - await nextTick(); - expect(fixture.querySelector(".o-menu div[data-name='subMenu']")).toBeFalsy(); - }); + await rightClickCell(model, "C8"); - test("Submenu parent is highlighted", async () => { - await renderContextMenu(300, 300, { menuItems: cellMenuRegistry.getMenuItems() }); - const menuItem = fixture.querySelector(".o-menu div[data-name='paste_special']"); - expect(menuItem?.classList).not.toContain("o-menu-item-active"); - triggerMouseEvent(menuItem, "mouseover"); - await nextTick(); - expect(menuItem?.classList).toContain("o-menu-item-active"); - triggerMouseEvent(".o-menu div[data-name='paste_value_only']", "mouseover"); - await nextTick(); - expect(menuItem?.classList).toContain("o-menu-item-active"); - }); + const menu = fixture.querySelector(".o-menu")!; + // scroll + menu.dispatchEvent( + new WheelEvent("wheel", { deltaY: 300, deltaX: 300, deltaMode: 0, bubbles: true }) + ); + menu.dispatchEvent(new Event("scroll", { bubbles: true })); + await nextTick(); - test("submenu does not open when disabled", async () => { - const menuItems: MenuItem[] = createMenu([ - { - id: "root", - name: "root", - isEnabled: () => false, - children: [ + // grid always at (0, 0) scroll position + expect(verticalScrollBar.scrollTop).toBe(0); + expect(horizontalScrollBar.scrollLeft).toBe(0); + }); + + test("scroll through the menu with the touch device prevents the grid from scrolling", async () => { + const verticalScrollBar = fixture.querySelector(".o-scrollbar.vertical") as HTMLElement; + const horizontalScrollBar = fixture.querySelector(".o-scrollbar.horizontal") as HTMLElement; + + expect(verticalScrollBar.scrollTop).toBe(0); + expect(horizontalScrollBar.scrollLeft).toBe(0); + + await rightClickCell(model, "C8"); + + const menu = fixture.querySelector(".o-menu")!; + + // start move at (310, 210) touch position + menu.dispatchEvent( + new TouchEvent("touchstart", { + bubbles: true, + cancelable: true, + touches: [ + new Touch({ + clientX: 310, + clientY: 210, + identifier: 1, + target: menu, + }), + ], + }) + ); + // move down; + menu.dispatchEvent( + new TouchEvent("touchmove", { + bubbles: true, + cancelable: true, + touches: [ + new Touch({ + clientX: 310, + clientY: 180, + identifier: 2, + target: menu, + }), + ], + }) + ); + + await nextTick(); + // grid always at (0, 0) scroll position + expect(verticalScrollBar.scrollTop).toBe(0); + expect(horizontalScrollBar.scrollLeft).toBe(0); + }); +}); + +describe("Context Menu internal tests", () => { + test("submenu opens and close when (un)hovered", async () => { + const menuItems = createMenu([ + { + id: "action", + name: "action", + action() {}, + }, + { + id: "root", + name: "root", + children: [ + () => [ { - name: "subMenu", id: "subMenu", + name: "subMenu", action() {}, }, ], - }, - ]); - await renderContextMenu(300, 300, { menuItems }); - expect(fixture.querySelector(".o-menu div[data-name='root']")!.classList).toContain( - "disabled" - ); - await simulateClick(".o-menu div[data-name='root']"); - expect(fixture.querySelector(".o-menu div[data-name='subMenu']")).toBeFalsy(); - }); + ], + }, + ]); + await renderContextMenu(300, 300, { menuItems }); + triggerMouseEvent(".o-menu div[data-name='root']", "mouseover"); + await nextTick(); + expect(fixture.querySelector(".o-menu div[data-name='subMenu']")).toBeTruthy(); + triggerMouseEvent(".o-menu div[data-name='action']", "mouseover"); + await nextTick(); + expect(fixture.querySelector(".o-menu div[data-name='subMenu']")).toBeFalsy(); + }); - test("submenu does not close when sub item overed", async () => { - await renderContextMenu(300, 300, { menuItems: subMenu }); - triggerMouseEvent(".o-menu div[data-name='root']", "mouseover"); - await nextTick(); - expect(fixture.querySelector(".o-menu div[data-name='subMenu1']")).toBeTruthy(); - triggerMouseEvent(".o-menu div[data-name='subMenu1']", "mouseover"); - await nextTick(); - expect(fixture.querySelector(".o-menu div[data-name='subMenu1']")).toBeTruthy(); - }); + test("Submenu parent is highlighted", async () => { + await renderContextMenu(300, 300, { menuItems: cellMenuRegistry.getMenuItems() }); + const menuItem = fixture.querySelector(".o-menu div[data-name='paste_special']"); + expect(menuItem?.classList).not.toContain("o-menu-item-active"); + triggerMouseEvent(menuItem, "mouseover"); + await nextTick(); + expect(menuItem?.classList).toContain("o-menu-item-active"); + triggerMouseEvent(".o-menu div[data-name='paste_value_only']", "mouseover"); + await nextTick(); + expect(menuItem?.classList).toContain("o-menu-item-active"); + }); - test("menu does not close when root menu is clicked", async () => { - await renderContextMenu(300, 300, { menuItems: subMenu }); - await simulateClick(".o-menu div[data-name='root']"); - expect(fixture.querySelector(".o-menu div[data-name='subMenu1']")).toBeTruthy(); - expect(fixture.querySelector(".o-menu div[data-name='root']")).toBeTruthy(); - }); + test("submenu does not open when disabled", async () => { + const menuItems: MenuItem[] = createMenu([ + { + id: "root", + name: "root", + isEnabled: () => false, + children: [ + { + name: "subMenu", + id: "subMenu", + action() {}, + }, + ], + }, + ]); + await renderContextMenu(300, 300, { menuItems }); + expect(fixture.querySelector(".o-menu div[data-name='root']")!.classList).toContain("disabled"); + await simulateClick(".o-menu div[data-name='root']"); + expect(fixture.querySelector(".o-menu div[data-name='subMenu']")).toBeFalsy(); + }); - test("menu closed when sub menu item is clicked", async () => { - const mockCallback = jest.fn(() => {}); - await renderContextMenu(300, 300, { - onClose: mockCallback, - menuItems: subMenu, - }); - await simulateClick(".o-menu div[data-name='root']"); - await simulateClick(".o-menu div[data-name='subMenu1']"); - expect(fixture.querySelector(".o-menu div[data-name='subMenu1']")).toBeFalsy(); - expect(mockCallback).toHaveBeenCalled(); + test("submenu does not close when sub item hovered", async () => { + await renderContextMenu(300, 300, { menuItems: subMenu }); + triggerMouseEvent(".o-menu div[data-name='root']", "mouseover"); + await nextTick(); + expect(fixture.querySelector(".o-menu div[data-name='subMenu1']")).toBeTruthy(); + triggerMouseEvent(".o-menu div[data-name='subMenu1']", "mouseover"); + await nextTick(); + expect(fixture.querySelector(".o-menu div[data-name='subMenu1']")).toBeTruthy(); + }); + + test("menu does not close when root menu is clicked", async () => { + await renderContextMenu(300, 300, { menuItems: subMenu }); + await simulateClick(".o-menu div[data-name='root']"); + expect(fixture.querySelector(".o-menu div[data-name='subMenu1']")).toBeTruthy(); + expect(fixture.querySelector(".o-menu div[data-name='root']")).toBeTruthy(); + }); + + test("menu closed when sub menu item is clicked", async () => { + const mockCallback = jest.fn(() => {}); + await renderContextMenu(300, 300, { + onClose: mockCallback, + menuItems: subMenu, }); + await simulateClick(".o-menu div[data-name='root']"); + await simulateClick(".o-menu div[data-name='subMenu1']"); + expect(fixture.querySelector(".o-menu div[data-name='subMenu1']")).toBeFalsy(); + expect(mockCallback).toHaveBeenCalled(); + }); - test("it renders subsubmenus", async () => { - const menuItems = createMenu([ - { - id: "root1", - name: "root1", - children: [ - () => [ - { - id: "root2", - name: "root2", - children: [ - () => [ - { - id: "subMenu", - name: "subMenu", - action() {}, - }, - ], + test("it renders subsubmenus", async () => { + const menuItems = createMenu([ + { + id: "root1", + name: "root1", + children: [ + () => [ + { + id: "root2", + name: "root2", + children: [ + () => [ + { + id: "subMenu", + name: "subMenu", + action() {}, + }, ], - }, - ], + ], + }, ], - }, - ]); - await renderContextMenu(300, 990, { menuItems }); - await simulateClick("div[data-name='root1']"); - await simulateClick("div[data-name='root2']"); - expect(fixture.querySelector(".o-menu div[data-name='subMenu']")).toBeTruthy(); - }); + ], + }, + ]); + await renderContextMenu(300, 990, { menuItems }); + await simulateClick("div[data-name='root1']"); + await simulateClick("div[data-name='root2']"); + expect(fixture.querySelector(".o-menu div[data-name='subMenu']")).toBeTruthy(); + }); - test("Menu with icon is correctly displayed", async () => { - const menuItems: MenuItem[] = createMenu([ - { - id: "root1", - name: "root1", - icon: "not-displayed-class", - children: [ - () => [ - { - id: "root2", - name: "root2", - action() {}, - icon: "my-class", - }, - ], + test("Menu with icon is correctly displayed", async () => { + const menuItems: MenuItem[] = createMenu([ + { + id: "root1", + name: "root1", + icon: "not-displayed-class", + children: [ + () => [ + { + id: "root2", + name: "root2", + action() {}, + icon: "my-class", + }, ], - }, - ]); - await renderContextMenu(300, 990, { menuItems }); - expect(fixture.querySelector("div[data-name='root1'] > i")).toBeNull(); - await simulateClick("div[data-name='root1']"); - expect(fixture.querySelector("div[data-name='root2'] > i")?.classList).toContain("my-class"); - }); + ], + }, + ]); + await renderContextMenu(300, 990, { menuItems }); + expect(fixture.querySelector("div[data-name='root1'] > i")).toBeNull(); + await simulateClick("div[data-name='root1']"); + expect(fixture.querySelector("div[data-name='root2'] > i")?.classList).toContain("my-class"); + }); - test("Can color menu items", async () => { - const menuItems: MenuItem[] = createMenu([ - { - id: "black", - name: "black", - action() {}, - }, - { - id: "orange", - name: "orange", - action() {}, - textColor: "orange", - }, - ]); - await renderContextMenu(0, 0, { menuItems }); - expect((fixture.querySelector("div[data-name='black']") as HTMLElement).style.color).toEqual( - "" - ); - expect((fixture.querySelector("div[data-name='orange']") as HTMLElement).style.color).toEqual( - "orange" - ); - }); + test("Can color menu items", async () => { + const menuItems: MenuItem[] = createMenu([ + { + id: "black", + name: "black", + action() {}, + }, + { + id: "orange", + name: "orange", + action() {}, + textColor: "orange", + }, + ]); + await renderContextMenu(0, 0, { menuItems }); + expect((fixture.querySelector("div[data-name='black']") as HTMLElement).style.color).toEqual( + "" + ); + expect((fixture.querySelector("div[data-name='orange']") as HTMLElement).style.color).toEqual( + "orange" + ); + }); - test("Only submenus of the current parent are visible", async () => { - const menuItems = createMenu([ - { - id: "root_1", - name: "root_1", - children: [ - () => [ - { - id: "root_1_1", - name: "root_1_1", - children: [ - () => [ - { - id: "subMenu_1", - name: "subMenu_1", - action() {}, - }, - ], - ], - }, - ], - ], - }, - { - id: "root_2", - name: "root_2", - children: [ - () => [ - { - id: "root_2_1", - name: "root_2_1", - children: [ - () => [ - { - id: "subMenu_2", - name: "subMenu_2", - action() {}, - }, - ], + test("Only submenus of the current parent are visible", async () => { + const menuItems = createMenu([ + { + id: "root_1", + name: "root_1", + children: [ + () => [ + { + id: "root_1_1", + name: "root_1_1", + children: [ + () => [ + { + id: "subMenu_1", + name: "subMenu_1", + action() {}, + }, ], - }, - ], + ], + }, ], - }, - ]); - await renderContextMenu(300, 300, { menuItems }); - - triggerMouseEvent(".o-menu div[data-name='root_1']", "mouseover"); - await nextTick(); - triggerMouseEvent(".o-menu div[data-name='root_1_1']", "mouseover"); - await nextTick(); - expect(fixture.querySelector(".o-menu div[data-name='subMenu_1']")).toBeTruthy(); - triggerMouseEvent(".o-menu div[data-name='root_2']", "mouseover"); - await nextTick(); - expect(fixture.querySelector(".o-menu div[data-name='subMenu_1']")).toBeFalsy(); - expect(fixture.querySelector(".o-menu div[data-name='root_2_1']")).toBeTruthy(); - }); - - test("Submenu visibility is taken into account", async () => { - const menuItems = createMenu([ - { - id: "root", - name: "root_1", - children: [ - () => [ - { - id: "menu_1", - name: "root_1_1", - children: [ - () => [ - { - id: "visible_submenu_1", - name: "visible_submenu_1", - action() {}, - isVisible: () => true, - }, - { - id: "invisible_submenu_1", - name: "invisible_submenu_1", - action() {}, - isVisible: () => false, - }, - ], + ], + }, + { + id: "root_2", + name: "root_2", + children: [ + () => [ + { + id: "root_2_1", + name: "root_2_1", + children: [ + () => [ + { + id: "subMenu_2", + name: "subMenu_2", + action() {}, + }, ], - }, - ], + ], + }, ], - }, - ]); - await renderContextMenu(300, 300, { menuItems }); - triggerMouseEvent(".o-menu div[data-name='root']", "mouseover"); - await nextTick(); - expect(fixture.querySelector(".o-menu div[data-name='menu_1']")).toBeTruthy(); - triggerMouseEvent(".o-menu div[data-name='menu_1']", "mouseover"); - await nextTick(); - expect(fixture.querySelector(".o-menu div[data-name='visible_submenu_1']")).toBeTruthy(); - expect(fixture.querySelector(".o-menu div[data-name='invisible_submenu_1']")).toBeFalsy(); - }); + ], + }, + ]); + await renderContextMenu(300, 300, { menuItems }); - test("scroll through the menu with the wheel / scrollbar prevents the grid from scrolling", async () => { - const verticalScrollBar = fixture.querySelector(".o-scrollbar.vertical") as HTMLElement; - const horizontalScrollBar = fixture.querySelector(".o-scrollbar.horizontal") as HTMLElement; - expect(verticalScrollBar.scrollTop).toBe(0); - expect(horizontalScrollBar.scrollLeft).toBe(0); - - await rightClickCell(model, "C8"); - - const menu = fixture.querySelector(".o-menu")!; - // scroll - menu.dispatchEvent( - new WheelEvent("wheel", { deltaY: 300, deltaX: 300, deltaMode: 0, bubbles: true }) - ); - menu.dispatchEvent(new Event("scroll", { bubbles: true })); - await nextTick(); - - // grid always at (0, 0) scroll position - expect(verticalScrollBar.scrollTop).toBe(0); - expect(horizontalScrollBar.scrollLeft).toBe(0); - }); + triggerMouseEvent(".o-menu div[data-name='root_1']", "mouseover"); + await nextTick(); + triggerMouseEvent(".o-menu div[data-name='root_1_1']", "mouseover"); + await nextTick(); + expect(fixture.querySelector(".o-menu div[data-name='subMenu_1']")).toBeTruthy(); + triggerMouseEvent(".o-menu div[data-name='root_2']", "mouseover"); + await nextTick(); + expect(fixture.querySelector(".o-menu div[data-name='subMenu_1']")).toBeFalsy(); + expect(fixture.querySelector(".o-menu div[data-name='root_2_1']")).toBeTruthy(); + }); - test("scroll through the menu with the touch device prevents the grid from scrolling", async () => { - const verticalScrollBar = fixture.querySelector(".o-scrollbar.vertical") as HTMLElement; - const horizontalScrollBar = fixture.querySelector(".o-scrollbar.horizontal") as HTMLElement; - - expect(verticalScrollBar.scrollTop).toBe(0); - expect(horizontalScrollBar.scrollLeft).toBe(0); - - await rightClickCell(model, "C8"); - - const menu = fixture.querySelector(".o-menu")!; - - // start move at (310, 210) touch position - menu.dispatchEvent( - new TouchEvent("touchstart", { - bubbles: true, - cancelable: true, - touches: [ - new Touch({ - clientX: 310, - clientY: 210, - identifier: 1, - target: menu, - }), - ], - }) - ); - // move down; - menu.dispatchEvent( - new TouchEvent("touchmove", { - bubbles: true, - cancelable: true, - touches: [ - new Touch({ - clientX: 310, - clientY: 180, - identifier: 2, - target: menu, - }), + test("Submenu visibility is taken into account", async () => { + const menuItems = createMenu([ + { + id: "root", + name: "root_1", + children: [ + () => [ + { + id: "menu_1", + name: "root_1_1", + children: [ + () => [ + { + id: "visible_submenu_1", + name: "visible_submenu_1", + action() {}, + isVisible: () => true, + }, + { + id: "invisible_submenu_1", + name: "invisible_submenu_1", + action() {}, + isVisible: () => false, + }, + ], + ], + }, ], - }) - ); - - await nextTick(); - // grid always at (0, 0) scroll position - expect(verticalScrollBar.scrollTop).toBe(0); - expect(horizontalScrollBar.scrollLeft).toBe(0); - }); + ], + }, + ]); + await renderContextMenu(300, 300, { menuItems }); + triggerMouseEvent(".o-menu div[data-name='root']", "mouseover"); + await nextTick(); + expect(fixture.querySelector(".o-menu div[data-name='menu_1']")).toBeTruthy(); + triggerMouseEvent(".o-menu div[data-name='menu_1']", "mouseover"); + await nextTick(); + expect(fixture.querySelector(".o-menu div[data-name='visible_submenu_1']")).toBeTruthy(); + expect(fixture.querySelector(".o-menu div[data-name='invisible_submenu_1']")).toBeFalsy(); + }); +}); +jest.setTimeout(300000); +describe("Context Menu position on large screen 1000px/1000px", () => { + test("it renders menu on the bottom right if enough space", async () => { + const [clickX, clickY] = await renderContextMenu(300, 300); + const { left, top } = getMenuPosition(); + expect(left).toBe(clickX); + expect(top).toBe(clickY); }); - describe("Context Menu position on large screen 1000px/1000px", () => { - test("it renders menu on the bottom right if enough space", async () => { - const [clickX, clickY] = await renderContextMenu(300, 300); - const { left, top } = getMenuPosition(); - expect(left).toBe(clickX); - expect(top).toBe(clickY); - }); - - test("it renders menu on the top right if not enough space", async () => { - const [clickX, clickY] = await renderContextMenu(300, 990); - const { left, top } = getMenuPosition(); - const { height } = getMenuSize(); - expect(left).toBe(clickX); - expect(top).toBe(clickY - height); - }); + test("it renders menu on the top right if not enough space", async () => { + const [clickX, clickY] = await renderContextMenu(300, 990); + const { left, top } = getMenuPosition(); + const { height } = getMenuSize(); + expect(left).toBe(clickX); + expect(top).toBe(clickY - height); + }); - test("it renders menu on the bottom left if not enough space", async () => { - const [clickX, clickY] = await renderContextMenu(990, 300); - const { left, top } = getMenuPosition(); - const { width } = getMenuSize(); - expect(left).toBe(clickX - width); - expect(top).toBe(clickY); - }); + test("it renders menu on the bottom left if not enough space", async () => { + const [clickX, clickY] = await renderContextMenu(990, 300); + const { left, top } = getMenuPosition(); + const { width } = getMenuSize(); + expect(left).toBe(clickX - width); + expect(top).toBe(clickY); + }); - test("it renders menu on the top left if not enough space", async () => { - const [clickX, clickY] = await renderContextMenu(990, 990); - const { left, top } = getMenuPosition(); - const { width, height } = getMenuSize(); - expect(left).toBe(clickX - width); - expect(top).toBe(clickY - height); - }); + test("it renders menu on the top left if not enough space", async () => { + const [clickX, clickY] = await renderContextMenu(990, 990); + const { left, top } = getMenuPosition(); + const { width, height } = getMenuSize(); + expect(left).toBe(clickX - width); + expect(top).toBe(clickY - height); + }); - test("it renders submenu on the bottom right if enough space", async () => { - const [clickX, clickY] = await renderContextMenu(300, 300, { menuItems: subMenu }); - await simulateClick("div[data-name='root']"); - const { left, top } = getSubMenuPosition(); - const { width } = getMenuSize(); - expect(left).toBe(clickX + width); - expect(top).toBe(clickY); - }); + test("it renders submenu on the bottom right if enough space", async () => { + const [clickX, clickY] = await renderContextMenu(300, 300, { menuItems: subMenu }); + await simulateClick("div[data-name='root']"); + const { left, top } = getSubMenuPosition(); + const { width } = getMenuSize(); + expect(left).toBe(clickX + width); + expect(top).toBe(clickY); + }); - test("it renders submenu on the bottom left if not enough space", async () => { - const [clickX, clickY] = await renderContextMenu(1000 - MENU_WIDTH - 10, 300, { - menuItems: subMenu, - }); - await simulateClick("div[data-name='root']"); - const { left, top } = getSubMenuPosition(); - const { width } = getMenuSize(); - const { left: rootLeft } = getMenuPosition(); - expect(rootLeft).toBe(clickX); - expect(left).toBe(clickX - width); - expect(top).toBe(clickY); - }); + test("it renders submenu on the bottom left if not enough space", async () => { + const [clickX, clickY] = await renderContextMenu(1000 - MENU_WIDTH - 10, 300, { + menuItems: subMenu, + }); + await simulateClick("div[data-name='root']"); + const { left, top } = getSubMenuPosition(); + const { width } = getMenuSize(); + const { left: rootLeft } = getMenuPosition(); + expect(rootLeft).toBe(clickX); + expect(left).toBe(clickX - width); + expect(top).toBe(clickY); + }); - test("it renders all menus on the bottom left if not enough space", async () => { - const [clickX, clickY] = await renderContextMenu(990, 300, { menuItems: subMenu }); - await simulateClick("div[data-name='root']"); - const { left, top } = getSubMenuPosition(); - const { width } = getMenuSize(); - const { left: rootLeft } = getMenuPosition(); - expect(rootLeft).toBe(clickX - width); - expect(left).toBe(clickX - 2 * width); - expect(top).toBe(clickY); - }); + test("it renders all menus on the bottom left if not enough space", async () => { + const [clickX, clickY] = await renderContextMenu(990, 300, { menuItems: subMenu }); + await simulateClick("div[data-name='root']"); + const { left, top } = getSubMenuPosition(); + const { width } = getMenuSize(); + const { left: rootLeft } = getMenuPosition(); + expect(rootLeft).toBe(clickX - width); + expect(left).toBe(clickX - 2 * width); + expect(top).toBe(clickY); + }); - test("it renders submenu on the top right if not enough space", async () => { - const [clickX, clickY] = await renderContextMenu(300, 960, { menuItems: subMenu }); - await simulateClick("div[data-name='root']"); - const { left, top } = getSubMenuPosition(); - const { height } = getSubMenuSize(); - const { width } = getMenuSize(); - expect(top).toBe(clickY - height + getItemSize()); - expect(left).toBe(clickX + width); - }); + test("it renders submenu on the top right if not enough space", async () => { + const [clickX, clickY] = await renderContextMenu(300, 960, { menuItems: subMenu }); + await simulateClick("div[data-name='root']"); + const { left, top } = getSubMenuPosition(); + const { height } = getSubMenuSize(); + const { width } = getMenuSize(); + expect(top).toBe(clickY - height + getItemSize()); + expect(left).toBe(clickX + width); + }); - test("it renders all menus on the top right if not enough space", async () => { - const [clickX, clickY] = await renderContextMenu(300, 990, { menuItems: subMenu }); - await simulateClick("div[data-name='root']"); - const { left, top } = getSubMenuPosition(); - const { top: rootTop } = getMenuPosition(); - const { height, width } = getSubMenuSize(); - const { height: rootHeight } = getMenuSize(); - expect(rootTop).toBe(clickY - rootHeight); - expect(top).toBe(clickY - height); - expect(left).toBe(clickX + width); - }); + test("it renders all menus on the top right if not enough space", async () => { + const [clickX, clickY] = await renderContextMenu(300, 990, { menuItems: subMenu }); + await simulateClick("div[data-name='root']"); + const { left, top } = getSubMenuPosition(); + const { top: rootTop } = getMenuPosition(); + const { height, width } = getSubMenuSize(); + const { height: rootHeight } = getMenuSize(); + expect(rootTop).toBe(clickY - rootHeight); + expect(top).toBe(clickY - height); + expect(left).toBe(clickX + width); + }); - test("multi depth menu is properly placed on the screen", async () => { - const subMenus: MenuItem[] = createMenu([ - { - id: "root", - name: "root", - children: [ - { - id: "subMenu", - name: "subMenu", - children: [ - { - id: "subSubMenu", - name: "subSubMenu", - action() {}, - }, - ], - }, - ], - }, - ]); - const [clickX] = await renderContextMenu(100, 100, { menuItems: subMenus }); - await simulateClick("div[data-name='root']"); - await simulateClick("div[data-name='subMenu']"); - const { left: secondSubLeft } = getSubMenuPosition(2); - const { width: subMenuWidth } = getSubMenuSize(); - const { width: rootWidth } = getMenuSize(); - expect(secondSubLeft).toBe(clickX + rootWidth + subMenuWidth); - }); + test("multi depth menu is properly placed on the screen", async () => { + const subMenus: MenuItem[] = createMenu([ + { + id: "root", + name: "root", + children: [ + { + id: "subMenu", + name: "subMenu", + children: [ + { + id: "subSubMenu", + name: "subSubMenu", + action() {}, + }, + ], + }, + ], + }, + ]); + const [clickX] = await renderContextMenu(100, 100, { menuItems: subMenus }); + await simulateClick("div[data-name='root']"); + await simulateClick("div[data-name='subMenu']"); + const { left: secondSubLeft } = getSubMenuPosition(2); + const { width: subMenuWidth } = getSubMenuSize(); + const { width: rootWidth } = getMenuSize(); + expect(secondSubLeft).toBe(clickX + rootWidth + subMenuWidth); }); }); diff --git a/tests/components/custom_currency_side_panel.test.ts b/tests/components/custom_currency_side_panel.test.ts index 7c67ea4c0c..cbdc9c4f3c 100644 --- a/tests/components/custom_currency_side_panel.test.ts +++ b/tests/components/custom_currency_side_panel.test.ts @@ -42,11 +42,11 @@ const loadCurrencies = async () => { return currenciesData; }; -let fixture: HTMLElement; let parent: Spreadsheet; -let dispatch; +let dispatch: jest.SpyInstance; let currenciesContent: { [key: string]: Currency }; let model: Model; +let fixture: HTMLElement; beforeEach(async () => { currenciesContent = Object.assign({}, currenciesRegistry.content); diff --git a/tests/components/figure.test.ts b/tests/components/figure.test.ts index ca85e8d934..6a2e084e11 100644 --- a/tests/components/figure.test.ts +++ b/tests/components/figure.test.ts @@ -42,9 +42,10 @@ import { import { mockGetBoundingClientRect } from "../test_helpers/mock_helpers"; import { TEST_CHART_DATA } from "./../test_helpers/constants"; +let env: SpreadsheetChildEnv; +let parent: Spreadsheet; let fixture: HTMLElement; let model: Model; -let parent: Spreadsheet; function createFigure( model: Model, @@ -60,7 +61,7 @@ function createFigure( tag: "text", }; - model.dispatch("CREATE_FIGURE", { + return model.dispatch("CREATE_FIGURE", { sheetId, figure: { ...defaultParameters, ...figureParameters }, }); @@ -116,7 +117,7 @@ afterAll(() => { describe("figures", () => { beforeEach(async () => { - ({ model, parent, fixture } = await mountSpreadsheet()); + ({ model, parent, fixture, env } = await mountSpreadsheet()); mockSpreadsheetRect = { top: 100, left: 200, height: 1000, width: 1000 }; mockFigureMenuItemRect = { top: 500, left: 500 }; }); @@ -560,7 +561,7 @@ describe("figures", () => { await simulateClick(".o-figure"); await simulateClick(".o-figure-menu-item"); await simulateClick(".o-menu div[data-name='copy']"); - const envClipBoardContent = await parent.env.clipboard.readText(); + const envClipBoardContent = await env.clipboard.readText(); if (envClipBoardContent.status === "ok") { expect(envClipBoardContent.content).toEqual( model.getters.getClipboardContent()["text/plain"] @@ -578,7 +579,7 @@ describe("figures", () => { await simulateClick(".o-figure"); await simulateClick(".o-figure-menu-item"); await simulateClick(".o-menu div[data-name='cut']"); - const envClipBoardContent = await parent.env.clipboard.readText(); + const envClipBoardContent = await env.clipboard.readText(); if (envClipBoardContent.status === "ok") { expect(envClipBoardContent.content).toEqual( model.getters.getClipboardContent()["text/plain"] diff --git a/tests/components/grid.test.ts b/tests/components/grid.test.ts index e85e95ed9e..5656a9695f 100644 --- a/tests/components/grid.test.ts +++ b/tests/components/grid.test.ts @@ -1295,7 +1295,6 @@ describe("Events on Grid update viewport correctly", () => { describe("Edge-Scrolling on mouseMove in selection", () => { beforeEach(async () => { jest.useFakeTimers(); - ({ parent, model, fixture } = await mountSpreadsheet()); }); @@ -1382,14 +1381,12 @@ describe("Copy paste keyboard shortcut", () => { ({ parent, model, fixture } = await mountSpreadsheet()); sheetId = model.getters.getActiveSheetId(); }); - test("Can paste from OS", async () => { selectCell(model, "A1"); clipboardData.setText("Excalibur"); document.body.dispatchEvent(getClipboardEvent("paste", clipboardData)); expect(getCellContent(model, "A1")).toEqual("Excalibur"); }); - test("Can copy/paste cells", async () => { setCellContent(model, "A1", "things"); selectCell(model, "A1"); diff --git a/tests/components/selection_input.test.ts b/tests/components/selection_input.test.ts index 9f48a2ced8..8473095e59 100644 --- a/tests/components/selection_input.test.ts +++ b/tests/components/selection_input.test.ts @@ -163,8 +163,8 @@ describe("Selection Input", () => { }); test("ctrl + select cell --> add new input", async () => { - const { parent, model, fixture } = await mountSpreadsheet(); - OPEN_CF_SIDEPANEL_ACTION(parent.env); + const { env, model, fixture } = await mountSpreadsheet(); + OPEN_CF_SIDEPANEL_ACTION(env); await nextTick(); await simulateClick(".o-cf-add"); await nextTick(); @@ -382,8 +382,8 @@ describe("Selection Input", () => { }); test("pressing and releasing control has no effect on future clicks", async () => { - const { parent, model, fixture } = await mountSpreadsheet(); - OPEN_CF_SIDEPANEL_ACTION(parent.env); + const { env, model, fixture } = await mountSpreadsheet(); + OPEN_CF_SIDEPANEL_ACTION(env); await nextTick(); await simulateClick(".o-cf-add"); await nextTick(); diff --git a/tests/components/spreadsheet.test.ts b/tests/components/spreadsheet.test.ts index f4aef89c3e..7df2a2fca5 100644 --- a/tests/components/spreadsheet.test.ts +++ b/tests/components/spreadsheet.test.ts @@ -3,6 +3,7 @@ import { Spreadsheet } from "../../src/components"; import { functionRegistry } from "../../src/functions"; import { toZone } from "../../src/helpers"; import { OPEN_CF_SIDEPANEL_ACTION } from "../../src/registries"; +import { SpreadsheetChildEnv } from "../../src/types"; import { addRows, createChart, @@ -36,20 +37,21 @@ jest.mock("../../src/components/composer/content_editable_helper", () => let fixture: HTMLElement; let parent: Spreadsheet; let model: Model; +let env: SpreadsheetChildEnv; describe("Simple Spreadsheet Component", () => { - // default model and env - beforeEach(async () => { + test("simple rendering snapshot", async () => { ({ model, parent, fixture } = await mountSpreadsheet({ model: new Model({ sheets: [{ id: "sh1" }] }), })); - }); - - test("simple rendering snapshot", async () => { expect(fixture.querySelector(".o-spreadsheet")).toMatchSnapshot(); }); test("focus is properly set, initially and after switching sheet", async () => { + ({ model, fixture } = await mountSpreadsheet({ + model: new Model({ sheets: [{ id: "sh1" }] }), + })); + // TODO check expect(document.activeElement!.tagName).toEqual("INPUT"); await click(fixture, ".o-add-sheet"); expect(document.querySelectorAll(".o-sheet").length).toBe(2); @@ -59,7 +61,6 @@ describe("Simple Spreadsheet Component", () => { }); describe("Use of env in a function", () => { - let env; beforeAll(() => { functionRegistry.add("GETACTIVESHEET", { description: "Get the name of the current sheet", @@ -108,11 +109,15 @@ describe("Simple Spreadsheet Component", () => { }); }); - test("Clipboard is in spreadsheet env", () => { - expect(parent.env.clipboard["clipboard"]).toBe(navigator.clipboard); + test("Clipboard is in spreadsheet env", async () => { + ({ env } = await mountSpreadsheet({ + model: new Model({ sheets: [{ id: "sh1" }] }), + })); + expect(env.clipboard["clipboard"]).toBe(navigator.clipboard); }); test("typing opens composer after toolbar clicked", async () => { + ({ model, parent, fixture } = await mountSpreadsheet()); await simulateClick(`div[title="Bold (Ctrl+B)"]`); expect(document.activeElement).not.toBeNull(); document.activeElement?.dispatchEvent(new InputEvent("input", { data: "d", bubbles: true })); @@ -122,6 +127,7 @@ describe("Simple Spreadsheet Component", () => { }); test("can open/close search with ctrl+h", async () => { + ({ model, parent, fixture } = await mountSpreadsheet()); await nextTick(); document.activeElement!.dispatchEvent( new KeyboardEvent("keydown", { key: "H", ctrlKey: true, bubbles: true }) @@ -136,6 +142,7 @@ describe("Simple Spreadsheet Component", () => { }); test("can open/close search with ctrl+f", async () => { + ({ model, parent, fixture } = await mountSpreadsheet()); document.activeElement!.dispatchEvent( new KeyboardEvent("keydown", { key: "F", ctrlKey: true, bubbles: true }) ); @@ -150,6 +157,7 @@ describe("Simple Spreadsheet Component", () => { }); test("Z-indexes of the various spreadsheet components", async () => { + ({ model, fixture } = await mountSpreadsheet()); const getZIndex = (selector: string) => Number(getElComputedStyle(selector, "zIndex")) || 0; mockChart(); const gridZIndex = getZIndex(".o-grid"); @@ -186,27 +194,30 @@ describe("Simple Spreadsheet Component", () => { }); test("Keydown is ineffective in dashboard mode", async () => { + ({ model, parent, fixture } = await mountSpreadsheet()); const spreadsheetKeyDown = jest.spyOn(parent, "onKeydown"); const spreadsheetDiv = fixture.querySelector(".o-spreadsheet")!; spreadsheetDiv.dispatchEvent(new KeyboardEvent("keydown", { key: "H", ctrlKey: true })); expect(spreadsheetKeyDown).toHaveBeenCalled(); jest.clearAllMocks(); - parent.model.updateMode("dashboard"); + model.updateMode("dashboard"); await nextTick(); spreadsheetDiv.dispatchEvent(new KeyboardEvent("keydown", { key: "H", ctrlKey: true })); expect(spreadsheetKeyDown).not.toHaveBeenCalled(); }); test("The spreadsheet does not render after onbeforeunload", async () => { + ({ model, parent, fixture } = await mountSpreadsheet()); window.dispatchEvent(new Event("beforeunload", { bubbles: true })); await nextTick(); createSheet(model, {}); await nextTick(); - const sheets = document.querySelectorAll(".o-all-sheets .o-sheet"); + const sheets = fixture.querySelectorAll(".o-all-sheets .o-sheet"); expect(sheets).toHaveLength(model.getters.getSheetIds().length - 1); }); test("The composer helper should be closed on toggle topbar context menu", async () => { + ({ parent, fixture } = await mountSpreadsheet()); await typeInComposerGrid("=sum("); expect(parent.model.getters.getEditionMode()).not.toBe("inactive"); expect(fixture.querySelectorAll(".o-composer-assistant")).toHaveLength(1); @@ -218,10 +229,7 @@ describe("Simple Spreadsheet Component", () => { test("Can instantiate a spreadsheet with a given client id-name", async () => { const client = { id: "alice", name: "Alice" }; - - ({ parent, model, fixture } = await mountSpreadsheet({ - model: new Model({}, { client }), - })); + ({ model } = await mountSpreadsheet({ model: new Model({}, { client }) })); expect(model.getters.getClient()).toEqual(client); }); @@ -279,7 +287,7 @@ describe("Composer / selectionInput interactions", () => { ], }; beforeEach(async () => { - ({ model, parent, fixture } = await mountSpreadsheet({ + ({ model, parent, fixture, env } = await mountSpreadsheet({ model: new Model(modelDataCf), })); }); @@ -287,50 +295,50 @@ describe("Composer / selectionInput interactions", () => { test("Switching from selection input to composer should update the highlihts", async () => { //open cf sidepanel selectCell(model, "B2"); - OPEN_CF_SIDEPANEL_ACTION(parent.env); + OPEN_CF_SIDEPANEL_ACTION(env); await nextTick(); await simulateClick(".o-selection-input input"); expect(model.getters.getHighlights().map((h) => h.zone)).toEqual([toZone("B2:C4")]); - expect(document.querySelectorAll(".o-spreadsheet .o-highlight")).toHaveLength(0); + expect(fixture.querySelectorAll(".o-spreadsheet .o-highlight")).toHaveLength(0); // select Composer await simulateClick(".o-spreadsheet-topbar .o-composer"); expect(model.getters.getHighlights().map((h) => h.zone)).toEqual([toZone("A1")]); - expect(document.querySelectorAll(".o-spreadsheet .o-highlight")).toHaveLength(1); + expect(fixture.querySelectorAll(".o-spreadsheet .o-highlight")).toHaveLength(1); }); test.each(["A", "="])( "Switching from grid composer to selection input should update the highlights and hide the highlight components", async (composerContent) => { selectCell(model, "B2"); - OPEN_CF_SIDEPANEL_ACTION(parent.env); + OPEN_CF_SIDEPANEL_ACTION(env); await nextTick(); await startGridComposition(composerContent); - expect(document.querySelectorAll(".o-grid-composer")).toHaveLength(1); + expect(fixture.querySelectorAll(".o-grid-composer")).toHaveLength(1); // focus selection input await simulateClick(".o-selection-input input"); - expect(document.querySelectorAll(".o-grid-composer")).toHaveLength(0); + expect(fixture.querySelectorAll(".o-grid-composer")).toHaveLength(0); } ); test("Switching from composer to selection input should update the highlights and hide the highlight components", async () => { selectCell(model, "B2"); - OPEN_CF_SIDEPANEL_ACTION(parent.env); + OPEN_CF_SIDEPANEL_ACTION(env); await nextTick(); await simulateClick(".o-spreadsheet-topbar .o-composer"); expect(model.getters.getHighlights().map((h) => h.zone)).toEqual([toZone("A1")]); - expect(document.querySelectorAll(".o-spreadsheet .o-highlight")).toHaveLength(1); + expect(fixture.querySelectorAll(".o-spreadsheet .o-highlight")).toHaveLength(1); //open cf sidepanel await simulateClick(".o-selection-input input"); expect(model.getters.getHighlights().map((h) => h.zone)).toEqual([toZone("B2:C4")]); - expect(document.querySelectorAll(".o-spreadsheet .o-highlight")).toHaveLength(0); + expect(fixture.querySelectorAll(".o-spreadsheet .o-highlight")).toHaveLength(0); }); test("Switching from composer to focusing a figure should resubscribe grid_selection", async () => { diff --git a/tests/components/top_bar.test.ts b/tests/components/top_bar.test.ts index c9eca81da2..a5ebbda0f7 100644 --- a/tests/components/top_bar.test.ts +++ b/tests/components/top_bar.test.ts @@ -88,14 +88,14 @@ class Parent extends Component { async function mountParent( model: Model = new Model(), focusComposer: ComposerFocusType = "inactive" -): Promise<{ parent: Parent; model: Model }> { +): Promise<{ parent: Parent; model: Model; fixture: HTMLElement }> { const env = makeTestEnv({ model, isDashboard: () => model.getters.isDashboard(), }); let parent: Component; ({ parent, fixture } = await mountComponent(Parent, { props: { focusComposer }, env })); - return { parent: parent as Parent, model }; + return { parent: parent as Parent, model, fixture }; } describe("TopBar component", () => { @@ -471,7 +471,7 @@ describe("TopBar component", () => { number++; }, }); - await mountParent(); + const { fixture } = await mountParent(); await click(fixture, ".o-topbar-menu[data-id='test']"); await click(fixture, ".o-menu-item"); expect(fixture.querySelectorAll(".o-menu-dropdown-content")).toHaveLength(0); @@ -547,7 +547,8 @@ describe("TopBar component", () => { test("Cannot edit cell in a readonly spreadsheet", async () => { const model = new Model({}, { mode: "readonly" }); - const { parent } = await mountParent(model); + let parent: Parent; + ({ parent, fixture } = await mountParent(model)); let composerEl = fixture.querySelector(".o-spreadsheet-topbar div.o-composer")!; @@ -573,7 +574,7 @@ describe("TopBar component", () => { ])( "Clicking a static element inside a dropdown '%s' don't close the dropdown", async (toolName: string, dropdownContentSelector: string) => { - await mountParent(); + ({ fixture } = await mountParent()); await simulateClick(`[title="${toolName}"]`); await nextTick(); @@ -601,8 +602,7 @@ describe("TopBar component", () => { test("can insert an image", async () => { const fileStore = new FileStore(); const model = new Model({}, { external: { fileStore } }); - await mountSpreadsheet({ model }); - await nextTick(); + await mountParent(model); const sheetId = model.getters.getActiveSheetId(); await simulateClick(".o-topbar-menu[data-id='insert']"); await simulateClick(".o-menu-item[data-name='insert_image']"); @@ -801,11 +801,11 @@ describe("TopBar - CF", () => { }); describe("Topbar - View", () => { test("Setting show formula from topbar should retain its state even it's changed via f&r side panel upon closing", async () => { - const { parent, model, fixture } = await mountSpreadsheet(); + const { model, fixture, env } = await mountSpreadsheet(); await click(fixture, ".o-topbar-menu[data-id='view']"); await click(fixture, ".o-menu-item[data-name='view_formulas']"); expect(model.getters.shouldShowFormulas()).toBe(true); - parent.env.openSidePanel("FindAndReplace"); + env.openSidePanel("FindAndReplace"); await nextTick(); expect(model.getters.shouldShowFormulas()).toBe(true); await nextTick(); @@ -820,11 +820,9 @@ describe("Topbar - View", () => { }); describe("Topbar - menu item resizing with viewport", () => { - test("font size dropdown in top bar is resized with screen size change", async () => { - const model = new Model(); - await mountParent(model); - triggerMouseEvent('[title="Font Size"]', "click"); - await nextTick(); + test("Number format dropdown in top bar is resized with screen size change", async () => { + const { model, fixture } = await mountParent(); + await click(fixture, '.o-tool[title="More formats"]'); let height = getElComputedStyle(".o-dropdown-content.o-text-options", "maxHeight"); expect(parseInt(height)).toBe( model.getters.getVisibleRect(model.getters.getActiveMainViewport()).height @@ -838,10 +836,8 @@ describe("Topbar - menu item resizing with viewport", () => { }); test("color picker of fill color in top bar is resized with screen size change", async () => { - const model = new Model(); - await mountParent(model); - triggerMouseEvent('.o-tool[title="Fill Color"]', "click"); - await nextTick(); + const { model, fixture } = await mountParent(); + await click(fixture, '.o-tool[title="Fill Color"]'); let height = getElComputedStyle(".o-popover", "maxHeight"); expect(parseInt(height)).toBe( model.getters.getVisibleRect(model.getters.getActiveMainViewport()).height @@ -855,10 +851,8 @@ describe("Topbar - menu item resizing with viewport", () => { }); test("color picker of text color in top bar is resized with screen size change", async () => { - const model = new Model(); - await mountParent(model); - triggerMouseEvent('.o-tool[title="Text Color"]', "click"); - await nextTick(); + const { model, fixture } = await mountParent(); + await click(fixture, '.o-tool[title="Text Color"]'); let height = getElComputedStyle(".o-popover", "maxHeight"); expect(parseInt(height)).toBe( model.getters.getVisibleRect(model.getters.getActiveMainViewport()).height diff --git a/tests/menu_item_insert_chart.test.ts b/tests/menu_item_insert_chart.test.ts index 9be94921ed..f2c7ecca09 100644 --- a/tests/menu_item_insert_chart.test.ts +++ b/tests/menu_item_insert_chart.test.ts @@ -90,7 +90,7 @@ describe("Insert chart menu item", () => { } async function mountTestSpreadsheet() { - ({ model, env, model } = await mountSpreadsheet({ model: new Model(data) })); + ({ model, env } = await mountSpreadsheet({ model: new Model(data) })); dispatchSpy = spyModelDispatch(model); } diff --git a/tests/menu_items_registry.test.ts b/tests/menu_items_registry.test.ts index 31e1dc86e3..c6df2afd14 100644 --- a/tests/menu_items_registry.test.ts +++ b/tests/menu_items_registry.test.ts @@ -96,7 +96,7 @@ describe("Menu Item actions", () => { let model: Model; let parent: Spreadsheet; let env: SpreadsheetChildEnv; - let dispatch; + let dispatch: jest.SpyInstance; beforeEach(async () => { ({ parent, model, env } = await mountSpreadsheet()); diff --git a/tests/test_helpers/helpers.ts b/tests/test_helpers/helpers.ts index 2ca58d300e..0023464b44 100644 --- a/tests/test_helpers/helpers.ts +++ b/tests/test_helpers/helpers.ts @@ -3,6 +3,7 @@ import { ChartConfiguration } from "chart.js"; import format from "xml-formatter"; import { Spreadsheet, SpreadsheetProps } from "../../src/components/spreadsheet/spreadsheet"; import { functionRegistry } from "../../src/functions/index"; +import { ImageProvider } from "../../src/helpers/figures/images/image_provider"; import { toCartesian, toUnboundedZone, toXC, toZone } from "../../src/helpers/index"; import { Model } from "../../src/model"; import { MergePlugin } from "../../src/plugins/core/merge"; @@ -14,6 +15,7 @@ import { ColorScaleThreshold, CommandTypes, ConditionalFormat, + Currency, RangeData, SpreadsheetChildEnv, Style, @@ -23,10 +25,8 @@ import { import { Image } from "../../src/types/image"; import { XLSXExport } from "../../src/types/xlsx"; import { isXLSXExportXMLFile } from "../../src/xlsx/helpers/xlsx_helper"; -import { ImageProvider } from "../components/__mocks__/mock_image_provider"; import { OWL_TEMPLATES, registerCleanup } from "../setup/jest.setup"; import { FileStore } from "../__mocks__/mock_file_store"; -import { Currency } from "./../../src/types/currency"; import { MockClipboard } from "./clipboard"; import { redo, setCellContent, undo } from "./commands_helpers"; import { getCellContent, getEvaluatedCell } from "./getters_helpers"; @@ -96,6 +96,8 @@ export function makeTestEnv(mockEnv: Partial = {}): Spreads openSidePanel: mockEnv.openSidePanel || (() => {}), toggleSidePanel: mockEnv.toggleSidePanel || (() => {}), clipboard: mockEnv.clipboard || new MockClipboard(), + //FIXME : image provider is not built on top of the file store of the model if provided + // and imageProvider is defined even when there is no file store on the model imageProvider: new ImageProvider(new FileStore()), _t: mockEnv._t || ((str: string, ...values: any) => str), notifyUser: mockEnv.notifyUser || (() => {}), @@ -144,8 +146,10 @@ export async function mountComponent( const fixture = optionalArgs?.fixture || makeTestFixture(); const parent = await app.mount(fixture); - registerCleanup(() => app.destroy()); - registerCleanup(() => fixture.remove()); + registerCleanup(() => { + app.destroy(); + fixture.remove(); + }); return { app, parent, model, fixture, env: parent.env }; }