From a17319cf67eec885a1227a1534f2354e9c79faac Mon Sep 17 00:00:00 2001 From: "Adrien Minne (adrm)" Date: Fri, 3 Feb 2023 14:12:40 +0000 Subject: [PATCH] [FIX] tests: mount owl App in test mode and mount components in helper This commit is a backport of original merged in PR #2045, it was introduced as it diminishes drastically the sources of errors when mounting OWL components. Change the parameters of the owl `App` in the test. The `App` will now be mounted in test mode. The main purpose is to enable props validation, which ensure that the test is close to the reality, and avoid tests failing for unclear reasons because a props was missing (d4663158). The Owl test mode is equivalent to dev mode, but without warning that the app is unsuited for production. Documentation here : https://github.com/odoo/owl/blob/master/doc/reference/app.md#dev-mode Also added an helper `mountComponent` and modified the existing helper `mountSpreadsheet`. The helpers now create and return the fixture in which the component is mounted. They also register cleanup functions to remove the fixture and destroy the app, so we don't have to do it manually. closes odoo/o-spreadsheet#2045 Signed-off-by: Pierre Rousseau (pro) Part-of: odoo/o-spreadsheet#2258 --- .../__snapshots__/context_menu.test.ts.snap | 2 +- .../components/autocomplete_dropdown.test.ts | 11 +- tests/components/autofill.test.ts | 13 +- tests/components/bottom_bar.test.ts | 139 +- tests/components/charts.test.ts | 28 +- tests/components/color_picker.test.ts | 38 +- tests/components/composer.test.ts | 13 +- .../components/conditional_formatting.test.ts | 11 +- tests/components/context_menu.test.ts | 1253 ++++++++--------- .../custom_currency_side_panel.test.ts | 17 +- tests/components/dashboard_grid.test.ts | 13 +- tests/components/drag_and_drop.test.ts | 14 +- tests/components/figure.test.ts | 20 +- tests/components/filter_menu.test.ts | 18 +- .../find_replace_side_panel.test.ts | 12 +- tests/components/formula_assistant.test.ts | 11 +- tests/components/grid.test.ts | 58 +- tests/components/grid_manipulation.test.ts | 12 +- tests/components/highlight.test.ts | 68 +- tests/components/link/link_display.test.ts | 12 +- tests/components/link/link_editor.test.ts | 12 +- tests/components/overlay.test.ts | 28 +- tests/components/scorecard_chart.test.ts | 13 +- tests/components/selection_input.test.ts | 116 +- tests/components/side_panel.test.ts | 10 +- tests/components/spreadsheet.test.ts | 128 +- tests/components/top_bar.test.ts | 187 +-- tests/menu_item_insert_chart.test.ts | 15 +- tests/menu_items_registry.test.ts | 14 +- tests/plugins/merges.test.ts | 47 +- tests/setup/jest.setup.ts | 14 + tests/test_helpers/helpers.ts | 66 +- 32 files changed, 986 insertions(+), 1427 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/autocomplete_dropdown.test.ts b/tests/components/autocomplete_dropdown.test.ts index e7f4d6d96b..f50c64f439 100644 --- a/tests/components/autocomplete_dropdown.test.ts +++ b/tests/components/autocomplete_dropdown.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { args, functionRegistry } from "../../src/functions/index"; import { Model } from "../../src/model"; import { selectCell } from "../test_helpers/commands_helpers"; @@ -6,7 +5,6 @@ import { clickCell } from "../test_helpers/dom_helper"; import { getCellText } from "../test_helpers/getters_helpers"; import { clearFunctions, - makeTestFixture, mountSpreadsheet, nextTick, restoreDefaultFunctions, @@ -20,7 +18,6 @@ jest.mock("../../src/components/composer/content_editable_helper", () => let model: Model; let composerEl: Element; let fixture: HTMLElement; -let app: App; let cehMock: ContentEditableHelper; async function typeInComposerGrid(text: string, fromScratch: boolean = true) { @@ -32,8 +29,7 @@ async function typeInComposerGrid(text: string, fromScratch: boolean = true) { } beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); + ({ model, fixture } = await mountSpreadsheet()); // start composition fixture.querySelector(".o-grid")!.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); @@ -41,11 +37,6 @@ beforeEach(async () => { composerEl = fixture.querySelector(".o-grid div.o-composer")!; }); -afterEach(() => { - fixture.remove(); - app.destroy(); -}); - describe("Functions autocomplete", () => { beforeEach(() => { clearFunctions(); diff --git a/tests/components/autofill.test.ts b/tests/components/autofill.test.ts index db97e54d8e..32e505adb8 100644 --- a/tests/components/autofill.test.ts +++ b/tests/components/autofill.test.ts @@ -1,4 +1,4 @@ -import { App, Component, xml } from "@odoo/owl"; +import { Component, xml } from "@odoo/owl"; import { Spreadsheet } from "../../src"; import { DEFAULT_CELL_HEIGHT, @@ -9,21 +9,14 @@ import { import { Model } from "../../src/model"; import { setCellContent, setSelection, setViewportOffset } from "../test_helpers/commands_helpers"; import { clickCell, triggerMouseEvent } from "../test_helpers/dom_helper"; -import { makeTestFixture, mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; let fixture: HTMLElement; let model: Model; let parent: Spreadsheet; -let app: App; beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); -}); - -afterEach(() => { - fixture.remove(); - app.destroy(); + ({ parent, model, fixture } = await mountSpreadsheet()); }); describe("Autofill component", () => { diff --git a/tests/components/bottom_bar.test.ts b/tests/components/bottom_bar.test.ts index 9036b3ffc4..2241a8ff10 100644 --- a/tests/components/bottom_bar.test.ts +++ b/tests/components/bottom_bar.test.ts @@ -1,17 +1,15 @@ -import { App, Component, onMounted, onWillUnmount, xml } from "@odoo/owl"; +import { Component, onMounted, onWillUnmount, xml } from "@odoo/owl"; import { BottomBar } from "../../src/components/bottom_bar/bottom_bar"; import { Model } from "../../src/model"; import { SpreadsheetChildEnv } from "../../src/types"; -import { OWL_TEMPLATES } from "../setup/jest.setup"; import { - activateSheet, createSheet, hideSheet, selectCell, setCellContent, } from "../test_helpers/commands_helpers"; import { triggerMouseEvent } from "../test_helpers/dom_helper"; -import { makeTestEnv, makeTestFixture, mockUuidV4To, nextTick } from "../test_helpers/helpers"; +import { mockUuidV4To, mountComponent, nextTick } from "../test_helpers/helpers"; jest.mock("../../src/helpers/uuid", () => require("../__mocks__/uuid")); let fixture: HTMLElement; @@ -36,33 +34,20 @@ class Parent extends Component { async function mountBottomBar( model: Model = new Model(), env: Partial = {} -): Promise<{ parent: Parent; app: App; model: Model }> { - const mockEnv = makeTestEnv({ ...env, model }); - const app = new App(Parent, { props: { model }, env: mockEnv }); - app.addTemplates(OWL_TEMPLATES); - const parent = await app.mount(fixture); - return { app, parent, model: parent.props.model }; +): Promise<{ parent: Parent; model: Model }> { + let parent: Component; + ({ fixture, parent } = await mountComponent(Parent, { model, env, props: { model } })); + return { parent: parent as Parent, model }; } -beforeEach(() => { - fixture = makeTestFixture(); -}); - -afterEach(() => { - fixture.remove(); -}); - describe("BottomBar component", () => { test("simple rendering", async () => { - const { app } = await mountBottomBar(); - + await mountBottomBar(); expect(fixture.querySelector(".o-spreadsheet-bottom-bar")).toMatchSnapshot(); - app.destroy(); }); test("Can create a new sheet", async () => { - const { app, model } = await mountBottomBar(); - + const { model } = await mountBottomBar(); const dispatch = jest.spyOn(model, "dispatch"); mockUuidV4To(model, 42); const activeSheetId = model.getters.getActiveSheetId(); @@ -76,12 +61,11 @@ describe("BottomBar component", () => { sheetIdTo: "42", sheetIdFrom: activeSheetId, }); - app.destroy(); }); test("create a second sheet while the first one is called Sheet2", async () => { const model = new Model({ sheets: [{ name: "Sheet2" }] }); - const { app } = await mountBottomBar(model); + await mountBottomBar(model); const dispatch = jest.spyOn(model, "dispatch"); expect(model.getters.getSheetIds().map(model.getters.getSheetName)).toEqual(["Sheet2"]); triggerMouseEvent(".o-add-sheet", "click"); @@ -90,45 +74,38 @@ describe("BottomBar component", () => { name: "Sheet1", position: 1, }); - app.destroy(); }); test("Can activate a sheet", async () => { - const { app, parent } = await mountBottomBar(); - const dispatch = jest.spyOn(parent.props.model, "dispatch"); + const { model } = await mountBottomBar(); + const dispatch = jest.spyOn(model, "dispatch"); triggerMouseEvent(".o-sheet", "click"); - const sheetIdFrom = parent.props.model.getters.getActiveSheetId(); + const sheetIdFrom = model.getters.getActiveSheetId(); const sheetIdTo = sheetIdFrom; expect(dispatch).toHaveBeenCalledWith("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo, }); - app.destroy(); }); test("Can open context menu of a sheet", async () => { - const { app } = await mountBottomBar(); - + await mountBottomBar(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); - app.destroy(); }); test("Can open context menu of a sheet with the arrow", async () => { - const { app } = await mountBottomBar(); - + await mountBottomBar(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); triggerMouseEvent(".o-sheet-icon", "click"); await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); - app.destroy(); }); test("Click on the arrow when the context menu is open should close it", async () => { - const { app } = await mountBottomBar(); - + await mountBottomBar(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); triggerMouseEvent(".o-sheet-icon", "click"); await nextTick(); @@ -136,14 +113,11 @@ describe("BottomBar component", () => { triggerMouseEvent(".o-sheet-icon", "click"); await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); - app.destroy(); }); test("Can open context menu of a sheet with the arrow if another menu is already open", async () => { - const { app } = await mountBottomBar(); - + await mountBottomBar(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); - triggerMouseEvent(".o-sheet-item.o-list-sheets", "click"); await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); @@ -153,13 +127,11 @@ describe("BottomBar component", () => { await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); expect(fixture.querySelector(".o-menu-item")!.textContent).toEqual("Duplicate"); - app.destroy(); }); test("Can open list of sheet menu if another menu is already open", async () => { - const { app } = await mountBottomBar(); + await mountBottomBar(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); - triggerMouseEvent(".o-sheet-icon", "click"); await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); @@ -169,14 +141,14 @@ describe("BottomBar component", () => { await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); expect(fixture.querySelector(".o-menu-item")!.textContent).toEqual("Sheet1"); - app.destroy(); }); test("Can move right a sheet", async () => { - const { app, 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(); @@ -185,16 +157,16 @@ describe("BottomBar component", () => { sheetId, direction: "right", }); - app.destroy(); }); test("Can move left a sheet", async () => { - const { app, 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(); triggerMouseEvent(".o-menu-item[data-name='move_left'", "click"); @@ -202,70 +174,67 @@ describe("BottomBar component", () => { sheetId, direction: "left", }); - app.destroy(); }); test("Can hide a sheet", async () => { - const { app, 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(); const sheetId = model.getters.getActiveSheetId(); - triggerMouseEvent(".o-menu-item[data-name='hide_sheet']", "click"); + triggerMouseEvent(".o-menu-item[data-name='hide_sheet'", "click"); expect(dispatch).toHaveBeenCalledWith("HIDE_SHEET", { sheetId, }); - app.destroy(); }); test("Hide sheet menu is not visible if there's only one visible sheet", async () => { - const { app, model } = await mountBottomBar(); + const model = new Model(); createSheet(model, { sheetId: "42" }); hideSheet(model, "42"); + await mountBottomBar(model); triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); expect(fixture.querySelector(".o-menu")).not.toBeNull(); expect(fixture.querySelector(".o-menu-item[data-name='hide_sheet']")).toBeNull(); - app.destroy(); }); test("Move right and left are not visible when it's not possible to move", async () => { - const { app } = await mountBottomBar(); + await mountBottomBar(); triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); expect(fixture.querySelector(".o-menu-item[data-name='move_left'")).toBeNull(); expect(fixture.querySelector(".o-menu-item[data-name='move_right'")).toBeNull(); - app.destroy(); }); test("Can rename a sheet", async () => { - const { app, model } = await mountBottomBar(new Model(), { + const { model } = await mountBottomBar(new Model(), { editText: jest.fn((title, callback, options) => callback("new_name")), }); + triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); triggerMouseEvent(".o-menu-item[data-name='rename'", "click"); expect(model.getters.getActiveSheet().name).toEqual("new_name"); - app.destroy(); }); test("Can rename a sheet with dblclick", async () => { - const { app, model } = await mountBottomBar(new Model(), { + const { model } = await mountBottomBar(new Model(), { editText: jest.fn((title, callback, options) => callback("new_name")), }); + triggerMouseEvent(".o-sheet-name", "dblclick"); await nextTick(); expect(model.getters.getActiveSheet().name).toEqual("new_name"); - app.destroy(); }); test("Can duplicate a sheet", async () => { - const { app, model } = await mountBottomBar(); + const { model } = await mountBottomBar(); const dispatch = jest.spyOn(model, "dispatch"); mockUuidV4To(model, 123); @@ -277,11 +246,10 @@ describe("BottomBar component", () => { sheetId: sheet, sheetIdTo: "123", }); - app.destroy(); }); test("Can delete a sheet", async () => { - const { app, model } = await mountBottomBar(new Model(), { + const { model } = await mountBottomBar(new Model(), { askConfirmation: jest.fn((title, callback) => callback()), }); const dispatch = jest.spyOn(model, "dispatch"); @@ -292,32 +260,30 @@ describe("BottomBar component", () => { const sheetId = model.getters.getActiveSheetId(); triggerMouseEvent(".o-menu-item[data-name='delete'", "click"); expect(dispatch).toHaveBeenCalledWith("DELETE_SHEET", { sheetId }); - app.destroy(); }); test("Delete sheet is not visible when there is only one sheet", async () => { - const { app } = await mountBottomBar(); + await mountBottomBar(); triggerMouseEvent(".o-sheet", "contextmenu"); await nextTick(); expect(fixture.querySelector(".o-menu-item[data-name='delete'")).toBeNull(); - app.destroy(); }); test("Can open the list of sheets", async () => { - const { app } = await mountBottomBar(); + await mountBottomBar(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); triggerMouseEvent(".o-list-sheets", "click"); await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); - app.destroy(); }); test("Can open the list of sheets", async () => { - const { app, model } = await mountBottomBar(); + const { model } = await mountBottomBar(); const sheet = model.getters.getActiveSheetId(); createSheet(model, { sheetId: "42" }); + expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); triggerMouseEvent(".o-list-sheets", "click"); await nextTick(); @@ -326,15 +292,13 @@ describe("BottomBar component", () => { expect(sheets.length).toBe(2); expect((sheets[0] as HTMLElement).dataset.name).toBe(sheet); expect((sheets[1] as HTMLElement).dataset.name).toBe("42"); - app.destroy(); }); test("Can activate a sheet from the list of sheets", async () => { - const { app, model } = await mountBottomBar(); + const { model } = await mountBottomBar(); const dispatch = jest.spyOn(model, "dispatch"); const sheet = model.getters.getActiveSheetId(); createSheet(model, { sheetId: "42" }); - triggerMouseEvent(".o-list-sheets", "click"); await nextTick(); triggerMouseEvent(".o-menu-item[data-name='42'", "click"); @@ -342,11 +306,10 @@ describe("BottomBar component", () => { sheetIdFrom: sheet, sheetIdTo: "42", }); - app.destroy(); }); test("Display the statistic button only if no-empty cells are selected", async () => { - const { app, model } = await mountBottomBar(); + const { model } = await mountBottomBar(); setCellContent(model, "A2", "24"); setCellContent(model, "A3", "=A1"); @@ -361,7 +324,6 @@ describe("BottomBar component", () => { selectCell(model, "A3"); await nextTick(); expect(fixture.querySelector(".o-selection-statistic")?.textContent).toBe("Sum: 0"); - app.destroy(); }); test("Display empty information if the statistic function doesn't handle the types of the selected cells", async () => { @@ -374,7 +336,7 @@ describe("BottomBar component", () => { }); test("Can open the list of statistics", async () => { - const { app, model } = await mountBottomBar(); + const { model } = await mountBottomBar(); setCellContent(model, "A2", "24"); selectCell(model, "A2"); await nextTick(); @@ -382,13 +344,12 @@ describe("BottomBar component", () => { triggerMouseEvent(".o-selection-statistic", "click"); await nextTick(); expect(fixture.querySelector(".o-menu")).toMatchSnapshot(); - app.destroy(); }); test("Can open the list of statistics if another menu is already open", async () => { const model = new Model(); const nonMockedDispatch = model.dispatch; - const { app } = await mountBottomBar(model); + await mountBottomBar(model); model.dispatch = nonMockedDispatch; setCellContent(model, "A2", "24"); selectCell(model, "A2"); @@ -401,11 +362,10 @@ describe("BottomBar component", () => { triggerMouseEvent(".o-selection-statistic", "click"); await nextTick(); expect(fixture.querySelector(".o-menu-item")!.textContent).toEqual("Sum: 24"); - app.destroy(); }); test("Can activate a statistic from the list of statistics", async () => { - const { app, model } = await mountBottomBar(); + const { model } = await mountBottomBar(); setCellContent(model, "A2", "24"); selectCell(model, "A2"); await nextTick(); @@ -417,6 +377,5 @@ describe("BottomBar component", () => { triggerMouseEvent(".o-menu-item[data-name='Count Numbers'", "click"); await nextTick(); expect(fixture.querySelector(".o-selection-statistic")?.textContent).toBe("Count Numbers: 1"); - app.destroy(); }); }); diff --git a/tests/components/charts.test.ts b/tests/components/charts.test.ts index 2c6f842d61..3851410e44 100644 --- a/tests/components/charts.test.ts +++ b/tests/components/charts.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { CommandResult, Model, Spreadsheet } from "../../src"; import { ChartTerms } from "../../src/components/translations_terms"; import { BACKGROUND_CHART_COLOR } from "../../src/constants"; @@ -21,7 +20,6 @@ import { triggerMouseEvent, } from "../test_helpers/dom_helper"; import { - makeTestFixture, mockChart, mountSpreadsheet, nextTick, @@ -61,10 +59,9 @@ let chartId: string; let sheetId: string; let parent: Spreadsheet; -let app: App; + describe("figures", () => { beforeEach(async () => { - fixture = makeTestFixture(); mockChartData = mockChart(); chartId = "someuuid"; sheetId = "Sheet1"; @@ -93,15 +90,12 @@ describe("figures", () => { }, ], }; - ({ app, parent, model } = await mountSpreadsheet(fixture, { model: new Model(data) })); + ({ parent, model, fixture } = await mountSpreadsheet({ model: new Model(data) })); await nextTick(); await nextTick(); await nextTick(); }); - afterEach(() => { - app.destroy(); - fixture.remove(); - }); + test.each(["basicChart", "scorecard", "gauge"])("can export a chart %s", (chartType: string) => { createTestChart(chartType); const data = model.exportData(); @@ -977,7 +971,6 @@ describe("figures", () => { describe("charts with multiple sheets", () => { beforeEach(async () => { - fixture = makeTestFixture(); mockChartData = mockChart(); const data = { sheets: [ @@ -1034,13 +1027,10 @@ describe("charts with multiple sheets", () => { }, ], }; - ({ app, parent, model } = await mountSpreadsheet(fixture, { model: new Model(data) })); + ({ parent, model, fixture } = await mountSpreadsheet({ model: new Model(data) })); await nextTick(); }); - afterEach(() => { - fixture.remove(); - app.destroy(); - }); + test("delete sheet containing chart data does not crash", async () => { expect(model.getters.getSheetName(model.getters.getActiveSheetId())).toBe("Sheet1"); model.dispatch("DELETE_SHEET", { sheetId: model.getters.getActiveSheetId() }); @@ -1053,14 +1043,10 @@ describe("charts with multiple sheets", () => { describe("Default background on runtime tests", () => { beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent } = await mountSpreadsheet(fixture, { model: new Model() })); - model = parent.model; + ({ parent, fixture, model } = await mountSpreadsheet({ model: new Model() })); await nextTick(); }); - afterEach(() => { - app.destroy(); - }); + test("Creating a 'basicChart' without background should have default background on runtime", async () => { createChart(model, { dataSets: ["A1"] }, "1", sheetId); expect(model.getters.getChartDefinition("1")?.background).toBeUndefined(); diff --git a/tests/components/color_picker.test.ts b/tests/components/color_picker.test.ts index f21b831d0d..10bc214cf4 100644 --- a/tests/components/color_picker.test.ts +++ b/tests/components/color_picker.test.ts @@ -1,44 +1,24 @@ -import { App } from "@odoo/owl"; import { Model } from "../../src"; import { ColorPicker, ColorPickerProps } from "../../src/components/color_picker/color_picker"; import { toHex } from "../../src/helpers"; -import { SpreadsheetChildEnv } from "../../src/types"; -import { OWL_TEMPLATES } from "../setup/jest.setup"; import { setStyle } from "../test_helpers/commands_helpers"; import { getElComputedStyle, setInputValueAndTrigger, simulateClick, } from "../test_helpers/dom_helper"; -import { makeTestFixture, nextTick } from "../test_helpers/helpers"; +import { mountComponent, nextTick } from "../test_helpers/helpers"; let fixture: HTMLElement; -beforeEach(async () => { - fixture = makeTestFixture(); -}); - -afterEach(() => { - fixture.remove(); -}); - -async function mountColorPicker( - props: Partial = {}, - model = new Model() -): Promise { - const app = new App(ColorPicker, { - props: { - dropdownDirection: props.dropdownDirection, - onColorPicked: props.onColorPicked || (() => {}), - currentColor: props.currentColor || "#000000", - maxHeight: props.maxHeight !== undefined ? props.maxHeight : 1000, - }, - env: { - model, - } as SpreadsheetChildEnv, - }); - app.addTemplates(OWL_TEMPLATES); - return await app.mount(fixture); +async function mountColorPicker(partialProps: Partial = {}, model = new Model()) { + const props = { + dropdownDirection: partialProps.dropdownDirection, + onColorPicked: partialProps.onColorPicked || (() => {}), + currentColor: partialProps.currentColor || "#000000", + maxHeight: partialProps.maxHeight !== undefined ? partialProps.maxHeight : 1000, + }; + ({ fixture } = await mountComponent(ColorPicker, { model, props })); } describe("Color Picker position tests", () => { diff --git a/tests/components/composer.test.ts b/tests/components/composer.test.ts index 5899303dd9..a70736a59d 100644 --- a/tests/components/composer.test.ts +++ b/tests/components/composer.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { MatchingParenColor, NumberColor, @@ -37,7 +36,6 @@ import { } from "../test_helpers/getters_helpers"; import { createEqualCF, - makeTestFixture, mountSpreadsheet, nextTick, startGridComposition, @@ -53,7 +51,6 @@ let model: Model; let composerEl: Element; let gridInputEl: Element; let fixture: HTMLElement; -let app: App; let cehMock: ContentEditableHelper; function getHighlights(model: Model): Highlight[] { @@ -76,14 +73,8 @@ async function typeInComposerGrid(text: string, fromScratch: boolean = true) { beforeEach(async () => { jest.useFakeTimers(); - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); - gridInputEl = document.querySelector(".o-grid>input")!; -}); - -afterEach(() => { - app.destroy(); - fixture.remove(); + ({ model, fixture } = await mountSpreadsheet()); + gridInputEl = fixture.querySelector(".o-grid>input")!; }); describe("ranges and highlights", () => { diff --git a/tests/components/conditional_formatting.test.ts b/tests/components/conditional_formatting.test.ts index 1849134b23..ffa32ba10b 100644 --- a/tests/components/conditional_formatting.test.ts +++ b/tests/components/conditional_formatting.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { Model, Spreadsheet } from "../../src"; import { toZone } from "../../src/helpers/zones"; import { ConditionalFormatPlugin } from "../../src/plugins/core/conditional_format"; @@ -15,7 +14,6 @@ import { createColorScale, createEqualCF, getPlugin, - makeTestFixture, mockUuidV4To, mountSpreadsheet, nextTick, @@ -30,20 +28,13 @@ let model: Model; describe("UI of conditional formats", () => { let fixture: HTMLElement; let parent: Spreadsheet; - let app: App; beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); + ({ parent, model, fixture } = await mountSpreadsheet()); parent.env.openSidePanel("ConditionalFormatting"); await nextTick(); }); - afterEach(() => { - fixture.remove(); - app.destroy(); - }); - function errorMessages(): string[] { return textContentAll(selectors.error); } diff --git a/tests/components/context_menu.test.ts b/tests/components/context_menu.test.ts index edfa863b13..13449dde48 100644 --- a/tests/components/context_menu.test.ts +++ b/tests/components/context_menu.test.ts @@ -1,23 +1,17 @@ -import { App, 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 { createFullMenuItem, FullMenuItem } from "../../src/registries/menu_items_registry"; -import { OWL_TEMPLATES } from "../setup/jest.setup"; -import { MockClipboard } from "../test_helpers/clipboard"; import { setCellContent } from "../test_helpers/commands_helpers"; -import { - 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, Touch, @@ -25,7 +19,6 @@ import { import { mockGetBoundingClientRect } from "../test_helpers/mock_helpers"; let fixture: HTMLElement; -let app: App; let model: Model; mockGetBoundingClientRect({ @@ -96,83 +89,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, - }); - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); - }); - - function getSelectionAnchorCellXc(model: Model): string { - const { col, row } = model.getters.getSelection().anchor.cell; - return toXC(col, row); - } +interface ContextMenuTestConfig { + onClose?: () => void; + menuItems?: FullMenuItem[]; +} - interface ContextMenuTestConfig { - onClose?: () => void; - menuItems?: FullMenuItem[]; - } +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, + })); + + 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. - - app = new App(ContextMenuParent, { - props: { - x, - y, - width, - height, - model: new Model(), - config: testConfig, - }, - }); - app.addTemplates(OWL_TEMPLATES); - parent = await app.mount(fixture); - await nextTick(); - return [x, y]; - } +function getSelectionAnchorCellXc(model: Model): string { + const { col, row } = model.getters.getSelection().anchor.cell; + return toXC(col, row); +} - const subMenu: FullMenuItem[] = [ - createFullMenuItem("root", { - name: "root", - sequence: 1, - children: [ - () => [ - createFullMenuItem("subMenu1", { - name: "subMenu1", - sequence: 1, - action() {}, - }), - createFullMenuItem("subMenu2", { - name: "subMenu2", - sequence: 1, - action() {}, - }), - ], +const subMenu: FullMenuItem[] = [ + createFullMenuItem("root", { + name: "root", + sequence: 1, + children: [ + () => [ + createFullMenuItem("subMenu1", { + name: "subMenu1", + sequence: 1, + action() {}, + }), + createFullMenuItem("subMenu2", { + name: "subMenu2", + sequence: 1, + action() {}, + }), ], - }), - ]; + ], + }), +]; - class ContextMenuParent extends Component { - static template = xml/* xml */ ` +class ContextMenuParent extends Component { + static template = xml/* xml */ `
{ />
`; - static components = { Menu }; - menus!: FullMenuItem[]; - 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 || [ - createFullMenuItem("Action", { - name: "Action", - sequence: 1, - action() {}, - }), - ]; - this.props.model.dispatch("RESIZE_SHEETVIEW", { - height: this.props.height, - width: this.props.width, - gridOffsetX: 0, - gridOffsetY: 0, - }); - } + static components = { Menu }; + menus!: FullMenuItem[]; + 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 || [ + createFullMenuItem("Action", { + name: "Action", + sequence: 1, + 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 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="Font Size"]')!; + triggerMouseEvent(fontSizeTool, "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(); - triggerMouseEvent(".o-topbar-topleft", "click"); - await nextTick(); - expect(fixture.querySelector(".o-menu")).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("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 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="Font Size"]')!; - triggerMouseEvent(fontSizeTool, "click"); - await nextTick(); - expect(fixture.querySelector(".o-menu .o-menu-item[data-name='cut']")).toBeFalsy(); - }); + 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("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; - }); + await rightClickCell(model, "C8"); - test("submenu opens and close when (un)overed", async () => { - const menuItems: FullMenuItem[] = [ - createFullMenuItem("action", { - name: "action", - sequence: 1, - action() {}, - }), - createFullMenuItem("root", { - name: "root", - sequence: 2, - children: [ - () => [ - createFullMenuItem("subMenu", { - name: "subMenu", - sequence: 1, - 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(); - }); + 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 parent is highlighted", async () => { - await renderContextMenu(300, 300, { menuItems: cellMenuRegistry.getAll() }); - 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"); - }); + // grid always at (0, 0) scroll position + expect(verticalScrollBar.scrollTop).toBe(0); + expect(horizontalScrollBar.scrollLeft).toBe(0); + }); - test("submenu does not open when disabled", async () => { - const menuItems: FullMenuItem[] = [ - createFullMenuItem("root", { - name: "root", - sequence: 1, - isEnabled: () => false, - children: [ - () => [ - createFullMenuItem("subMenu", { - name: "subMenu", - sequence: 1, - 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("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; - 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(); - }); + expect(verticalScrollBar.scrollTop).toBe(0); + expect(horizontalScrollBar.scrollLeft).toBe(0); - 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(); - }); + await rightClickCell(model, "C8"); - 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(); - }); + const menu = fixture.querySelector(".o-menu")!; - test("it renders subsubmenus", async () => { - const menuItems: FullMenuItem[] = [ - createFullMenuItem("root1", { - name: "root1", - sequence: 1, - children: [ - () => [ - createFullMenuItem("root2", { - name: "root2", - sequence: 1, - children: [ - () => [ - createFullMenuItem("subMenu", { - name: "subMenu", - sequence: 1, - 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(); - }); + // 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("Menu with icon is correctly displayed", async () => { - const menuItems: FullMenuItem[] = [ - createFullMenuItem("root1", { - name: "root1", - sequence: 1, - icon: "not-displayed-class", - children: [ - () => [ - createFullMenuItem("root2", { - name: "root2", - sequence: 1, - action() {}, - icon: "my-class", - }), - ], + 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: FullMenuItem[] = [ + createFullMenuItem("action", { + name: "action", + sequence: 1, + action() {}, + }), + createFullMenuItem("root", { + name: "root", + sequence: 2, + children: [ + () => [ + createFullMenuItem("subMenu", { + name: "subMenu", + sequence: 1, + action() {}, + }), ], - }), - ]; - 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, 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("Can color menu items", async () => { - const menuItems: FullMenuItem[] = [ - createFullMenuItem("black", { - name: "black", - sequence: 1, - action() {}, - }), - createFullMenuItem("orange", { - name: "orange", - sequence: 2, - 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("Submenu parent is highlighted", async () => { + await renderContextMenu(300, 300, { menuItems: cellMenuRegistry.getAll() }); + 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("Only submenus of the current parent are visible", async () => { - const menuItems: FullMenuItem[] = [ - createFullMenuItem("root_1", { - name: "root_1", - sequence: 1, - children: [ - () => [ - createFullMenuItem("root_1_1", { - name: "root_1_1", - sequence: 1, - children: [ - () => [ - createFullMenuItem("subMenu_1", { - name: "subMenu_1", - sequence: 1, - action() {}, - }), - ], - ], - }), - ], - ], - }), - createFullMenuItem("root_2", { - name: "root_2", - sequence: 2, - children: [ - () => [ - createFullMenuItem("root_2_1", { - name: "root_2_1", - sequence: 1, - children: [ - () => [ - createFullMenuItem("subMenu_2", { - name: "subMenu_2", - sequence: 1, - action() {}, - }), - ], - ], - }), - ], + test("submenu does not open when disabled", async () => { + const menuItems: FullMenuItem[] = [ + createFullMenuItem("root", { + name: "root", + sequence: 1, + isEnabled: () => false, + children: [ + () => [ + createFullMenuItem("subMenu", { + name: "subMenu", + sequence: 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(); - }); + ], + }), + ]; + 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("Submenu visibility is taken into account", async () => { - const menuItems: FullMenuItem[] = [ - createFullMenuItem("root", { - name: "root_1", - sequence: 1, - children: [ - () => [ - createFullMenuItem("menu_1", { - name: "root_1_1", - sequence: 1, - children: [ - () => [ - createFullMenuItem("visible_submenu_1", { - name: "visible_submenu_1", - sequence: 1, - action() {}, - isVisible: () => true, - }), - createFullMenuItem("invisible_submenu_1", { - name: "invisible_submenu_1", - sequence: 1, - action() {}, - isVisible: () => false, - }), - ], - ], - }), - ], - ], - }), - ]; - 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(); - }); + 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("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); - }); + 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("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; + 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(); + }); - expect(verticalScrollBar.scrollTop).toBe(0); - expect(horizontalScrollBar.scrollLeft).toBe(0); + test("it renders subsubmenus", async () => { + const menuItems: FullMenuItem[] = [ + createFullMenuItem("root1", { + name: "root1", + sequence: 1, + children: [ + () => [ + createFullMenuItem("root2", { + name: "root2", + sequence: 1, + children: [ + () => [ + createFullMenuItem("subMenu", { + name: "subMenu", + sequence: 1, + 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 rightClickCell(model, "C8"); + test("Menu with icon is correctly displayed", async () => { + const menuItems: FullMenuItem[] = [ + createFullMenuItem("root1", { + name: "root1", + sequence: 1, + icon: "not-displayed-class", + children: [ + () => [ + createFullMenuItem("root2", { + name: "root2", + sequence: 1, + 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"); + }); - const menu = fixture.querySelector(".o-menu")!; + test("Can color menu items", async () => { + const menuItems: FullMenuItem[] = [ + createFullMenuItem("black", { + name: "black", + sequence: 1, + action() {}, + }), + createFullMenuItem("orange", { + name: "orange", + sequence: 2, + 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" + ); + }); - // 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, + test("Only submenus of the current parent are visible", async () => { + const menuItems: FullMenuItem[] = [ + createFullMenuItem("root_1", { + name: "root_1", + sequence: 1, + children: [ + () => [ + createFullMenuItem("root_1_1", { + name: "root_1_1", + sequence: 1, + children: [ + () => [ + createFullMenuItem("subMenu_1", { + name: "subMenu_1", + sequence: 1, + action() {}, + }), + ], + ], }), ], - }) - ); - // move down; - menu.dispatchEvent( - new TouchEvent("touchmove", { - bubbles: true, - cancelable: true, - touches: [ - new Touch({ - clientX: 310, - clientY: 180, - identifier: 2, - target: menu, + ], + }), + createFullMenuItem("root_2", { + name: "root_2", + sequence: 2, + children: [ + () => [ + createFullMenuItem("root_2_1", { + name: "root_2_1", + sequence: 1, + children: [ + () => [ + createFullMenuItem("subMenu_2", { + name: "subMenu_2", + sequence: 1, + action() {}, + }), + ], + ], }), ], - }) - ); + ], + }), + ]; + await renderContextMenu(300, 300, { menuItems }); - 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(); }); - 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("Submenu visibility is taken into account", async () => { + const menuItems: FullMenuItem[] = [ + createFullMenuItem("root", { + name: "root_1", + sequence: 1, + children: [ + () => [ + createFullMenuItem("menu_1", { + name: "root_1_1", + sequence: 1, + children: [ + () => [ + createFullMenuItem("visible_submenu_1", { + name: "visible_submenu_1", + sequence: 1, + action() {}, + isVisible: () => true, + }), + createFullMenuItem("invisible_submenu_1", { + name: "invisible_submenu_1", + sequence: 1, + action() {}, + isVisible: () => false, + }), + ], + ], + }), + ], + ], + }), + ]; + 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); + }); - 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: FullMenuItem[] = [ - createFullMenuItem("root", { - name: "root", - sequence: 1, - children: [ - () => [ - createFullMenuItem("subMenu", { - name: "subMenu", - sequence: 1, - children: [ - createFullMenuItem("subSubMenu", { - name: "subSubMenu", - sequence: 1, - action() {}, - }), - ], - }), - ], + test("multi depth menu is properly placed on the screen", async () => { + const subMenus: FullMenuItem[] = [ + createFullMenuItem("root", { + name: "root", + sequence: 1, + children: [ + () => [ + createFullMenuItem("subMenu", { + name: "subMenu", + sequence: 1, + children: [ + createFullMenuItem("subSubMenu", { + name: "subSubMenu", + sequence: 1, + 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); - }); + ], + }), + ]; + 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); }); }); describe("Context menu react to grid size changes", () => { beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); - }); - - afterEach(() => { - fixture.remove(); - app.destroy(); + ({ model, fixture } = await mountSpreadsheet()); }); test("Submenu is closed when grid size change make the parent menu hidden", async () => { diff --git a/tests/components/custom_currency_side_panel.test.ts b/tests/components/custom_currency_side_panel.test.ts index d22de63a19..5a22b391e3 100644 --- a/tests/components/custom_currency_side_panel.test.ts +++ b/tests/components/custom_currency_side_panel.test.ts @@ -1,10 +1,9 @@ -import { App } from "@odoo/owl"; import { Model, Spreadsheet } from "../../src"; import { currenciesRegistry } from "../../src/registries/currencies_registry"; import { Currency } from "../../src/types/currency"; import { setSelection } from "../test_helpers/commands_helpers"; import { setInputValueAndTrigger, triggerMouseEvent } from "../test_helpers/dom_helper"; -import { makeTestFixture, mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; jest.mock("../../src/helpers/uuid", () => require("../__mocks__/uuid")); jest.useFakeTimers(); @@ -43,18 +42,15 @@ const loadCurrencies = async () => { return currenciesData; }; -let fixture: HTMLElement; let parent: Spreadsheet; -let app: App; -let dispatch; +let dispatch: jest.SpyInstance; let currenciesContent: { [key: string]: Currency }; let model: Model; beforeEach(async () => { - fixture = makeTestFixture(); currenciesContent = Object.assign({}, currenciesRegistry.content); - ({ app, parent, model } = await mountSpreadsheet(fixture, { + ({ parent, model } = await mountSpreadsheet({ model: new Model({}, { external: { loadCurrencies } }), })); dispatch = spyDispatch(parent); @@ -64,8 +60,6 @@ beforeEach(async () => { }); afterEach(() => { - app.destroy(); - fixture.remove(); currenciesRegistry.content = currenciesContent; }); @@ -92,13 +86,10 @@ describe("custom currency sidePanel component", () => { test("if currencies aren't provided in spreadsheet --> remove 'available currencies' section", async () => { // create spreadsheet without loadCurrencies in env currenciesRegistry.content = {}; - const fixture = makeTestFixture(); - const { app, parent } = await mountSpreadsheet(fixture); + const { parent, fixture } = await mountSpreadsheet(); parent.env.openSidePanel("CustomCurrency"); await nextTick(); expect(fixture.querySelector(selectors.availableCurrencies)).toBe(null); - fixture.remove(); - app.destroy(); }); // ------------------------------------------------------------------------- diff --git a/tests/components/dashboard_grid.test.ts b/tests/components/dashboard_grid.test.ts index 9c65d972b9..9ecf257023 100644 --- a/tests/components/dashboard_grid.test.ts +++ b/tests/components/dashboard_grid.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { Spreadsheet } from "../../src"; import { DEFAULT_CELL_HEIGHT, @@ -10,12 +9,11 @@ import { Model } from "../../src/model"; import { createFilter, selectCell, setCellContent } from "../test_helpers/commands_helpers"; import { simulateClick } from "../test_helpers/dom_helper"; import { getSelectionAnchorCellXc } from "../test_helpers/getters_helpers"; -import { makeTestFixture, mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; let fixture: HTMLElement; let parent: Spreadsheet; let model: Model; -let app: App; function getEmptyClipboardEvent(type: "copy" | "paste" | "cut") { const event = new Event(type, { bubbles: true }); @@ -30,17 +28,10 @@ function getEmptyClipboardEvent(type: "copy" | "paste" | "cut") { describe("Grid component in dashboard mode", () => { beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent } = await mountSpreadsheet(fixture)); - model = parent.model; + ({ parent, fixture, model } = await mountSpreadsheet()); await nextTick(); }); - afterEach(() => { - app.destroy(); - fixture.remove(); - }); - test("simple dashboard rendering snapshot", async () => { model.updateMode("dashboard"); await nextTick(); diff --git a/tests/components/drag_and_drop.test.ts b/tests/components/drag_and_drop.test.ts index 49ce5ff625..f1a42dc663 100644 --- a/tests/components/drag_and_drop.test.ts +++ b/tests/components/drag_and_drop.test.ts @@ -1,4 +1,4 @@ -import { App, Component, useSubEnv, xml } from "@odoo/owl"; +import { Component, useSubEnv, xml } from "@odoo/owl"; import { Model } from "../../src"; import { dragAndDropBeyondTheViewport } from "../../src/components/helpers/drag_and_drop"; import { DEFAULT_CELL_HEIGHT, DEFAULT_CELL_WIDTH } from "../../src/constants"; @@ -14,14 +14,12 @@ import { setViewportOffset, } from "../test_helpers/commands_helpers"; import { edgeScrollDelay, triggerMouseEvent } from "../test_helpers/dom_helper"; -import { makeTestFixture, nextTick } from "../test_helpers/helpers"; +import { mountComponent, nextTick } from "../test_helpers/helpers"; // As we test an isolated component, grid and gridOverlay won't exist jest.mock("../../src/components/helpers/dom_helpers", () => require("./__mocks__/dom_helpers")); -let fixture: HTMLElement; let model: Model; -let app: App; let sheetId: UID; //Test Component required @@ -68,18 +66,12 @@ afterAll(() => { beforeEach(async () => { model = new Model(); - app = new App(FakeGridComponent, { props: { model } }); + await mountComponent(FakeGridComponent, { model, props: { model } }); selectedCol = selectedRow = undefined; - fixture = makeTestFixture(); - await app.mount(fixture); sheetId = model.getters.getActiveSheetId(); await nextTick(); }); -afterEach(() => { - app.destroy(); -}); - describe("Drag And Drop horizontal tests", () => { test("Start Drag&Drop in XRight then moving to XLeft edge-scroll XRight to the left", async () => { freezeColumns(model, 4, sheetId); diff --git a/tests/components/figure.test.ts b/tests/components/figure.test.ts index 546ab5e054..e9e2f09f14 100644 --- a/tests/components/figure.test.ts +++ b/tests/components/figure.test.ts @@ -1,4 +1,4 @@ -import { App, Component, xml } from "@odoo/owl"; +import { Component, xml } from "@odoo/owl"; import { Model, Spreadsheet } from "../../src"; import { ChartJsComponent } from "../../src/components/figures/chart/chartJs/chartjs"; import { ScorecardChart } from "../../src/components/figures/chart/scorecard/chart_scorecard"; @@ -36,7 +36,6 @@ import { getCellContent, getCellText } from "../test_helpers/getters_helpers"; import { getFigureDefinition, getFigureIds, - makeTestFixture, mockChart, mountSpreadsheet, nextTick, @@ -44,10 +43,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 app: App; -let parent: Spreadsheet; function createFigure( model: Model, @@ -63,7 +62,7 @@ function createFigure( tag: "text", }; - model.dispatch("CREATE_FIGURE", { + return model.dispatch("CREATE_FIGURE", { sheetId, figure: { ...defaultParameters, ...figureParameters }, }); @@ -122,16 +121,11 @@ afterAll(() => { describe("figures", () => { beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model, parent } = await mountSpreadsheet(fixture)); + ({ model, env, parent, fixture } = await mountSpreadsheet()); mockSpreadsheetRect = { top: 100, left: 200, height: 1000, width: 1000 }; mockFigureMenuItemRect = { top: 500, left: 500 }; }); - afterEach(() => { - app.destroy(); - }); - test("can create a figure with some data", () => { createFigure(model); expect(model.getters.getFigures(model.getters.getActiveSheetId())).toEqual([ @@ -564,7 +558,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"] @@ -582,7 +576,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/filter_menu.test.ts b/tests/components/filter_menu.test.ts index d35b054c64..2920f59012 100644 --- a/tests/components/filter_menu.test.ts +++ b/tests/components/filter_menu.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { Model } from "../../src"; import { UID } from "../../src/types"; import { @@ -9,13 +8,7 @@ import { updateFilter, } from "../test_helpers/commands_helpers"; import { keyDown, simulateClick } from "../test_helpers/dom_helper"; -import { - getCellsObject, - makeTestFixture, - mountSpreadsheet, - nextTick, - target, -} from "../test_helpers/helpers"; +import { getCellsObject, mountSpreadsheet, nextTick, target } from "../test_helpers/helpers"; async function openFilterMenu() { await simulateClick(".o-filter-icon"); @@ -25,7 +18,6 @@ describe("Filter menu component", () => { let fixture: HTMLElement; let model: Model; let sheetId: UID; - let app: App; function getFilterMenuValues() { const values: { value: string; isChecked: boolean }[] = []; @@ -40,16 +32,10 @@ describe("Filter menu component", () => { } beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); + ({ model, fixture } = await mountSpreadsheet()); sheetId = model.getters.getActiveSheetId(); }); - afterEach(() => { - fixture.remove(); - app.destroy(); - }); - describe("Filter Tests", () => { beforeEach(async () => { createFilter(model, "A1:A5"); diff --git a/tests/components/find_replace_side_panel.test.ts b/tests/components/find_replace_side_panel.test.ts index 2a01d79615..12dbe5634d 100644 --- a/tests/components/find_replace_side_panel.test.ts +++ b/tests/components/find_replace_side_panel.test.ts @@ -1,8 +1,7 @@ -import { App } from "@odoo/owl"; import { Model, Spreadsheet } from "../../src"; import { setCellContent } from "../test_helpers/commands_helpers"; import { setInputValueAndTrigger, triggerMouseEvent } from "../test_helpers/dom_helper"; -import { makeTestFixture, mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; jest.mock("../../src/helpers/uuid", () => require("../__mocks__/uuid")); let model: Model; @@ -33,18 +32,13 @@ const selectors = { describe("find and replace sidePanel component", () => { let fixture: HTMLElement; let parent: Spreadsheet; - let app: App; + beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); + ({ parent, model, fixture } = await mountSpreadsheet()); parent.env.openSidePanel("FindAndReplace"); await nextTick(); }); - afterEach(() => { - fixture.remove(); - app.destroy(); - }); describe("Sidepanel", () => { test("Can close the find and replace side panel", async () => { expect(document.querySelectorAll(".o-sidePanel").length).toBe(1); diff --git a/tests/components/formula_assistant.test.ts b/tests/components/formula_assistant.test.ts index 620cf1a1a6..0c9b84d902 100644 --- a/tests/components/formula_assistant.test.ts +++ b/tests/components/formula_assistant.test.ts @@ -1,9 +1,7 @@ -import { App } from "@odoo/owl"; import { args, functionRegistry } from "../../src/functions/index"; import { Model } from "../../src/model"; import { clearFunctions, - makeTestFixture, mountSpreadsheet, nextTick, restoreDefaultFunctions, @@ -16,11 +14,9 @@ jest.mock("../../src/components/composer/content_editable_helper", () => let model: Model; let composerEl: Element; let fixture: HTMLElement; -let app: App; beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); + ({ model, fixture } = await mountSpreadsheet()); // start composition document.querySelector(".o-grid")!.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); @@ -28,11 +24,6 @@ beforeEach(async () => { composerEl = fixture.querySelector(".o-grid div.o-composer")!; }); -afterEach(() => { - app.destroy(); - fixture.remove(); -}); - describe("formula assistant", () => { beforeAll(() => { clearFunctions(); diff --git a/tests/components/grid.test.ts b/tests/components/grid.test.ts index 3938c24674..84c7d1f3d0 100644 --- a/tests/components/grid.test.ts +++ b/tests/components/grid.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { Spreadsheet, TransportService } from "../../src"; import { BACKGROUND_GRAY_COLOR, @@ -50,7 +49,7 @@ import { getSelectionAnchorCellXc, getStyle, } from "../test_helpers/getters_helpers"; -import { makeTestFixture, mountSpreadsheet, nextTick, Touch } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick, Touch } from "../test_helpers/helpers"; import { MockTransportService } from "../__mocks__/transport_service"; import { mockChart } from "./__mocks__/chart"; jest.mock("../../src/components/composer/content_editable_helper", () => @@ -70,27 +69,12 @@ function getHorizontalScroll(): number { let fixture: HTMLElement; let model: Model; let parent: Spreadsheet; -let app: App; jest.useFakeTimers(); -beforeEach(async () => { - fixture = makeTestFixture(); -}); - -afterEach(() => { - fixture.remove(); -}); - describe("Grid component", () => { beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ parent, model, fixture } = await mountSpreadsheet()); }); test("simple rendering snapshot", async () => { @@ -782,14 +766,9 @@ describe("Multi User selection", () => { let transportService: TransportService; beforeEach(async () => { transportService = new MockTransportService(); - fixture = makeTestFixture(); - model = new Model({}, { transportService }); - ({ app, parent } = await mountSpreadsheet(fixture, { model })); - }); - afterEach(() => { - app.destroy(); - fixture.remove(); + model = new Model({}, { transportService }); + ({ parent, fixture } = await mountSpreadsheet({ model })); }); test("Do not render multi user selection with invalid sheet", async () => { @@ -836,12 +815,11 @@ describe("Multi User selection", () => { describe("error tooltip", () => { beforeEach(async () => { jest.useFakeTimers(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); + ({ parent, model, fixture } = await mountSpreadsheet()); }); afterEach(() => { jest.useRealTimers(); - app.destroy(); }); test("can display error on A1", async () => { @@ -913,13 +891,9 @@ describe("error tooltip", () => { describe("Events on Grid update viewport correctly", () => { beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); - }); - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ parent, model, fixture } = await mountSpreadsheet()); }); + test("Vertical scroll", async () => { fixture.querySelector(".o-grid")!.dispatchEvent(new WheelEvent("wheel", { deltaY: 1200 })); await nextTick(); @@ -1321,13 +1295,7 @@ describe("Events on Grid update viewport correctly", () => { describe("Edge-Scrolling on mouseMove in selection", () => { beforeEach(async () => { jest.useFakeTimers(); - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ parent, model, fixture } = await mountSpreadsheet()); }); test("Can edge-scroll horizontally", async () => { @@ -1409,24 +1377,16 @@ describe("Copy paste keyboard shortcut", () => { let sheetId: string; beforeEach(async () => { - fixture = makeTestFixture(); clipboardData = new MockClipboardData(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); + ({ parent, model, fixture } = await mountSpreadsheet()); sheetId = model.getters.getActiveSheetId(); }); - - afterEach(() => { - app.destroy(); - fixture.remove(); - }); - 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/grid_manipulation.test.ts b/tests/components/grid_manipulation.test.ts index d64e11280e..3cc7903d11 100644 --- a/tests/components/grid_manipulation.test.ts +++ b/tests/components/grid_manipulation.test.ts @@ -1,11 +1,10 @@ -import { App } from "@odoo/owl"; import { Spreadsheet } from "../../src"; import { DEFAULT_CELL_HEIGHT, DEFAULT_CELL_WIDTH } from "../../src/constants"; import { Model } from "../../src/model"; import { selectColumn, selectRow } from "../test_helpers/commands_helpers"; import { simulateClick, triggerMouseEvent } from "../test_helpers/dom_helper"; import { getSelectionAnchorCellXc } from "../test_helpers/getters_helpers"; -import { makeTestFixture, mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick, spyDispatch } from "../test_helpers/helpers"; const COLUMN_D = { x: 340, y: 10 }; const ROW_5 = { x: 30, y: 100 }; @@ -14,16 +13,9 @@ const OUTSIDE_CM = { x: 50, y: 50 }; let fixture: HTMLElement; let model: Model; let parent: Spreadsheet; -let app: App; beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); -}); - -afterEach(() => { - app.destroy(); - fixture.remove(); + ({ parent, model, fixture } = await mountSpreadsheet()); }); function simulateContextMenu(selector: string, coord: { x: number; y: number }) { diff --git a/tests/components/highlight.test.ts b/tests/components/highlight.test.ts index a044c162cd..a789308abb 100644 --- a/tests/components/highlight.test.ts +++ b/tests/components/highlight.test.ts @@ -1,4 +1,4 @@ -import { App, Component, useSubEnv, xml } from "@odoo/owl"; +import { Component, useSubEnv, xml } from "@odoo/owl"; import { Highlight } from "../../src/components/highlight/highlight/highlight"; import { DEFAULT_CELL_HEIGHT, @@ -8,11 +8,10 @@ import { import { toZone } from "../../src/helpers"; import { Model } from "../../src/model"; import { DispatchResult } from "../../src/types/commands"; -import { OWL_TEMPLATES } from "../setup/jest.setup"; import { merge } from "../test_helpers/commands_helpers"; import { edgeScrollDelay, triggerMouseEvent } from "../test_helpers/dom_helper"; import { - makeTestFixture, + mountComponent, mountSpreadsheet, nextTick, startGridComposition, @@ -95,9 +94,7 @@ async function moveToCell(el: Element, xc: string) { } let model: Model; -let app: App; let fixture: HTMLElement; -let parent: Parent; let cornerEl: Element; let borderEl: Element; @@ -119,13 +116,14 @@ class Parent extends Component { } async function mountHighlight(zone: string, color: string): Promise { - app = new App(Parent, { props: { zone: toZone(zone), color, model } }); - app.addTemplates(OWL_TEMPLATES); - return await app.mount(fixture); + let parent: Component; + ({ fixture, parent } = await mountComponent(Parent, { + props: { zone: toZone(zone), color, model }, + })); + return parent as Parent; } const genericBeforeEach = async () => { - fixture = makeTestFixture(); model = new Model(); model.dispatch("RESIZE_SHEETVIEW", { width: DEFAULT_SHEETVIEW_SIZE, @@ -135,17 +133,11 @@ const genericBeforeEach = async () => { }); }; -const genericAfterEach = () => { - app.destroy(); - fixture.remove(); -}; - describe("Corner component", () => { beforeEach(genericBeforeEach); - afterEach(genericAfterEach); describe("can drag all corners", () => { test("start on nw corner", async () => { - parent = await mountHighlight("B2", "#666"); + await mountHighlight("B2", "#666"); cornerEl = fixture.querySelector(".o-corner-nw")!; // select B2 nw corner @@ -161,7 +153,7 @@ describe("Corner component", () => { }); test("start on ne corner", async () => { - parent = await mountHighlight("B2", "#666"); + const parent = await mountHighlight("B2", "#666"); cornerEl = fixture.querySelector(".o-corner-ne")!; // select B2 ne corner @@ -178,7 +170,7 @@ describe("Corner component", () => { }); test("start on sw corner", async () => { - parent = await mountHighlight("B2", "#666"); + const parent = await mountHighlight("B2", "#666"); cornerEl = fixture.querySelector(".o-corner-sw")!; // select B2 sw corner @@ -195,7 +187,7 @@ describe("Corner component", () => { }); test("start on se corner", async () => { - parent = await mountHighlight("B2", "#666"); + const parent = await mountHighlight("B2", "#666"); cornerEl = fixture.querySelector(".o-corner-se")!; // select B2 se corner @@ -213,7 +205,7 @@ describe("Corner component", () => { }); test("do nothing if drag outside the grid", async () => { - parent = await mountHighlight("A1", "#666"); + const parent = await mountHighlight("A1", "#666"); cornerEl = fixture.querySelector(".o-corner-nw")!; // select A1 nw corner @@ -235,7 +227,7 @@ describe("Corner component", () => { test("drag highlight corner on merged cells expands the final highlight zone", async () => { merge(model, "B1:C1"); - parent = await mountHighlight("B2", "#666"); + const parent = await mountHighlight("B2", "#666"); cornerEl = fixture.querySelector(".o-corner-nw")!; // select B2 se corner @@ -259,7 +251,7 @@ describe("Corner component", () => { elements: [0, 1], size: width / 2, }); - parent = await mountHighlight("B1", "#666"); + const parent = await mountHighlight("B1", "#666"); cornerEl = fixture.querySelector(".o-corner-nw")!; // select B1 nw corner @@ -284,7 +276,7 @@ describe("Corner component", () => { elements: [0, 1], size: height / 2, }); - parent = await mountHighlight("A2", "#666"); + const parent = await mountHighlight("A2", "#666"); cornerEl = fixture.querySelector(".o-corner-nw")!; // select A2 nw corner @@ -304,10 +296,9 @@ describe("Corner component", () => { describe("Border component", () => { beforeEach(genericBeforeEach); - afterEach(genericAfterEach); describe("can drag all borders", () => { test("start on top border", async () => { - parent = await mountHighlight("B2", "#666"); + const parent = await mountHighlight("B2", "#666"); borderEl = fixture.querySelector(".o-border-n")!; // select B2 top border @@ -324,7 +315,7 @@ describe("Border component", () => { }); test("start on left border", async () => { - parent = await mountHighlight("B2", "#666"); + const parent = await mountHighlight("B2", "#666"); borderEl = fixture.querySelector(".o-border-w")!; // select B2 left border @@ -341,7 +332,7 @@ describe("Border component", () => { }); test("start on right border", async () => { - parent = await mountHighlight("B2", "#666"); + const parent = await mountHighlight("B2", "#666"); borderEl = fixture.querySelector(".o-border-w")!; // select B2 right border @@ -358,7 +349,7 @@ describe("Border component", () => { }); test("start on bottom border", async () => { - parent = await mountHighlight("B2", "#666"); + const parent = await mountHighlight("B2", "#666"); borderEl = fixture.querySelector(".o-border-w")!; // select B2 bottom border @@ -376,7 +367,7 @@ describe("Border component", () => { }); test("drag the A1:B2 highlight, start on A1 top border, finish on C1 --> set C1:D2 highlight", async () => { - parent = await mountHighlight("A1:B2", "#666"); + const parent = await mountHighlight("A1:B2", "#666"); borderEl = fixture.querySelector(".o-border-n")!; // select A1 top border @@ -399,7 +390,7 @@ describe("Border component", () => { }); test("drag the A1:B2 highlight, start on B1 top border, finish on C1 --> set B1:C2 highlight", async () => { - parent = await mountHighlight("A1:B2", "#666"); + const parent = await mountHighlight("A1:B2", "#666"); borderEl = fixture.querySelector(".o-border-n")!; // select B1 top border @@ -416,7 +407,7 @@ describe("Border component", () => { }); test("cannot drag highlight zone if already beside limit border", async () => { - parent = await mountHighlight("A1:B2", "#666"); + const parent = await mountHighlight("A1:B2", "#666"); borderEl = fixture.querySelector(".o-border-s")!; // select B2 bottom border @@ -432,7 +423,7 @@ describe("Border component", () => { test("drag highlight order on merged cells expands the final highlight zone", async () => { merge(model, "B1:C1"); - parent = await mountHighlight("A1", "#666"); + const parent = await mountHighlight("A1", "#666"); borderEl = fixture.querySelector(".o-border-n")!; // select A1 top border @@ -450,7 +441,7 @@ describe("Border component", () => { test("drag highlight on merged cells expands the highlight zone", async () => { merge(model, "B1:C1"); - parent = await mountHighlight("A1", "#666"); + const parent = await mountHighlight("A1", "#666"); borderEl = fixture.querySelector(".o-border-n")!; // select A1 top border @@ -474,7 +465,7 @@ describe("Border component", () => { elements: [0, 1], size: width / 2, }); - parent = await mountHighlight("B1", "#666"); + const parent = await mountHighlight("B1", "#666"); borderEl = fixture.querySelector(".o-border-n")!; // select B1 top border @@ -499,7 +490,7 @@ describe("Border component", () => { elements: [0, 1], size: height / 2, }); - parent = await mountHighlight("A2", "#666"); + const parent = await mountHighlight("A2", "#666"); borderEl = fixture.querySelector(".o-border-n")!; // select A2 top border @@ -520,17 +511,12 @@ describe("Border component", () => { describe("Edge-Scrolling on mouseMove of hightlights", () => { beforeEach(async () => { jest.useFakeTimers(); - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); + ({ model, fixture } = await mountSpreadsheet()); // ensure that highlights exist await startGridComposition(); await typeInComposerGrid("=A1"); }); - afterEach(() => { - app.destroy(); - fixture.remove(); - }); test("Can edge-scroll border horizontally", async () => { const { width } = model.getters.getSheetViewDimensionWithHeaders(); const y = DEFAULT_CELL_HEIGHT; diff --git a/tests/components/link/link_display.test.ts b/tests/components/link/link_display.test.ts index c28fb7538e..337b56dbd8 100644 --- a/tests/components/link/link_display.test.ts +++ b/tests/components/link/link_display.test.ts @@ -1,26 +1,18 @@ -import { App } from "@odoo/owl"; import { Model, Spreadsheet } from "../../../src"; import { buildSheetLink } from "../../../src/helpers"; import { clearCell, createSheet, merge, setCellContent } from "../../test_helpers/commands_helpers"; import { clickCell, hoverCell, rightClickCell, simulateClick } from "../../test_helpers/dom_helper"; import { getCell, getEvaluatedCell } from "../../test_helpers/getters_helpers"; -import { makeTestFixture, mountSpreadsheet, nextTick } from "../../test_helpers/helpers"; +import { mountSpreadsheet, nextTick } from "../../test_helpers/helpers"; describe("link display component", () => { let fixture: HTMLElement; let model: Model; - let app: App; let parent: Spreadsheet; beforeEach(async () => { jest.useFakeTimers(); - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ parent, model, fixture } = await mountSpreadsheet()); }); test("simple snapshot", async () => { diff --git a/tests/components/link/link_editor.test.ts b/tests/components/link/link_editor.test.ts index 91512b5ffe..da1ce5b135 100644 --- a/tests/components/link/link_editor.test.ts +++ b/tests/components/link/link_editor.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { Model } from "../../../src"; import { buildSheetLink } from "../../../src/helpers"; import { @@ -14,7 +13,7 @@ import { simulateClick, } from "../../test_helpers/dom_helper"; import { getCell, getEvaluatedCell } from "../../test_helpers/getters_helpers"; -import { makeTestFixture, mountSpreadsheet, nextTick } from "../../test_helpers/helpers"; +import { mountSpreadsheet, nextTick } from "../../test_helpers/helpers"; import { mockGetBoundingClientRect } from "../../test_helpers/mock_helpers"; mockGetBoundingClientRect({ @@ -24,7 +23,6 @@ mockGetBoundingClientRect({ describe("link editor component", () => { let fixture: HTMLElement; let model: Model; - let app: App; async function openLinkEditor(model: Model, xc: string) { await rightClickCell(model, xc); @@ -46,13 +44,7 @@ describe("link editor component", () => { } beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ model, fixture } = await mountSpreadsheet()); }); test("open link editor from cell context menu", async () => { diff --git a/tests/components/overlay.test.ts b/tests/components/overlay.test.ts index 37baf69122..f662ff23a2 100644 --- a/tests/components/overlay.test.ts +++ b/tests/components/overlay.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { ColResizer, RowResizer } from "../../src/components/headers_overlay/headers_overlay"; import { DEFAULT_CELL_HEIGHT, @@ -25,11 +24,10 @@ import { triggerMouseEvent, } from "../test_helpers/dom_helper"; import { getEvaluatedCell, getSelectionAnchorCellXc } from "../test_helpers/getters_helpers"; -import { makeTestFixture, mountSpreadsheet, nextTick } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick } from "../test_helpers/helpers"; let fixture: HTMLElement; let model: Model; -let app: App; ColResizer.prototype._getMaxSize = () => 1000; RowResizer.prototype._getMaxSize = () => 1000; @@ -138,7 +136,6 @@ async function dblClickRow(index: number) { describe("Resizer component", () => { beforeEach(async () => { - fixture = makeTestFixture(); const data = { sheets: [ { @@ -148,12 +145,7 @@ describe("Resizer component", () => { ], }; model = new Model(data); - ({ app } = await mountSpreadsheet(fixture, { model })); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ fixture } = await mountSpreadsheet({ model })); }); test("can click on a header to select a column", async () => { @@ -751,14 +743,10 @@ describe("Resizer component", () => { describe("Edge-Scrolling on mouseMove in selection", () => { beforeEach(async () => { jest.useFakeTimers(); - fixture = makeTestFixture(); - ({ app, model } = await mountSpreadsheet(fixture)); - }); - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ model, fixture } = await mountSpreadsheet()); }); + test("Can edge-scroll horizontally", async () => { const { width } = model.getters.getSheetViewDimension(); const y = DEFAULT_CELL_HEIGHT; @@ -829,7 +817,6 @@ describe("Edge-Scrolling on mouseMove in selection", () => { describe("move selected element(s)", () => { beforeEach(async () => { - fixture = makeTestFixture(); const data = { sheets: [ { @@ -840,12 +827,7 @@ describe("move selected element(s)", () => { ], }; model = new Model(data); - ({ app } = await mountSpreadsheet(fixture, { model })); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ fixture } = await mountSpreadsheet({ model })); }); test("select the last selected cols/rows keep all selected zone active", async () => { diff --git a/tests/components/scorecard_chart.test.ts b/tests/components/scorecard_chart.test.ts index 4e5b07f149..676c9ba34b 100644 --- a/tests/components/scorecard_chart.test.ts +++ b/tests/components/scorecard_chart.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { Model } from "../../src"; import { toHex } from "../../src/helpers"; import { UID } from "../../src/types"; @@ -10,15 +9,13 @@ import { } from "../test_helpers/commands_helpers"; import { dragElement, getElComputedStyle, simulateClick } from "../test_helpers/dom_helper"; import { getCellContent } from "../test_helpers/getters_helpers"; -import { makeTestFixture, mountSpreadsheet, nextTick, target } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick, target } from "../test_helpers/helpers"; let fixture: HTMLElement; let model: Model; let chartId: string; let sheetId: string; -let app: App; - function getChartElement(): HTMLElement { return fixture.querySelector(".o-figure")!; } @@ -82,7 +79,6 @@ function getChartBaselineTextContent() { describe("Scorecard charts", () => { beforeEach(async () => { - fixture = makeTestFixture(); chartId = "someuuid"; sheetId = "Sheet1"; const data = { @@ -103,12 +99,7 @@ describe("Scorecard charts", () => { }, ], }; - ({ app, model } = await mountSpreadsheet(fixture, { model: new Model(data) })); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); + ({ model, fixture } = await mountSpreadsheet({ model: new Model(data) })); }); test("Scorecard snapshot", async () => { diff --git a/tests/components/selection_input.test.ts b/tests/components/selection_input.test.ts index ecb9b3cc3f..dbe84931d6 100644 --- a/tests/components/selection_input.test.ts +++ b/tests/components/selection_input.test.ts @@ -2,12 +2,12 @@ import { App, Component, onMounted, onWillUnmount, useSubEnv, xml } from "@odoo/ import { Model } from "../../src"; import { SelectionInput } from "../../src/components/selection_input/selection_input"; import { OPEN_CF_SIDEPANEL_ACTION } from "../../src/registries"; -import { OWL_TEMPLATES } from "../setup/jest.setup"; import { activateSheet, createSheet, selectCell, undo } from "../test_helpers/commands_helpers"; import { clickCell, keyDown, keyUp, simulateClick } from "../test_helpers/dom_helper"; import { getChildFromComponent, makeTestFixture, + mountComponent, mountSpreadsheet, nextTick, } from "../test_helpers/helpers"; @@ -38,7 +38,7 @@ interface SelectionInputTestConfig { class Parent extends Component { static template = xml/* xml */ ` `; @@ -73,10 +73,10 @@ class MultiParent extends Component { static template = xml/* xml */ `
- +
- +
`; @@ -94,46 +94,42 @@ class MultiParent extends Component { } } -async function createSelectionInput(config: SelectionInputTestConfig = {}) { +async function createSelectionInput( + config: SelectionInputTestConfig = {}, + fixtureEl?: HTMLElement +) { model = new Model(); - const app = new App(Parent, { props: { model, config } }); - app.addTemplates(OWL_TEMPLATES); - const parent = await app.mount(fixture); + let parent: Component; + let app: App; + ({ fixture, parent, app } = await mountComponent(Parent, { + model, + fixture: fixtureEl, + props: { model, config }, + })); await nextTick(); - const id = parent.id; - return { parent, model, id, app }; + const id = (parent as Parent).id; + return { parent: parent as Parent, model, id, app }; } describe("Selection Input", () => { - beforeEach(async () => { - fixture = makeTestFixture(); - }); - - afterEach(() => { - fixture.remove(); - }); - test("empty input is not colored", async () => { - const { app } = await createSelectionInput(); + await createSelectionInput(); expect(fixture.querySelectorAll("input")[0].getAttribute("style")).toBe("color: #000;"); - app.destroy(); }); test("remove button is not displayed with a single input", async () => { - const { app } = await createSelectionInput(); + await createSelectionInput(); expect(fixture.querySelectorAll(".o-remove-selection").length).toBeFalsy(); - app.destroy(); }); test("remove button is displayed with more than one input", async () => { - const { app } = await createSelectionInput(); + await createSelectionInput(); await simulateClick(".o-add-selection"); expect(fixture.querySelectorAll(".o-remove-selection").length).toBe(2); - app.destroy(); }); test("input is filled when new cells are selected", async () => { - const { app, model } = await createSelectionInput(); + const { model } = await createSelectionInput(); selectCell(model, "B4"); await nextTick(); expect(fixture.querySelector("input")!.value).toBe("B4"); @@ -147,12 +143,11 @@ describe("Selection Input", () => { expect(fixture.querySelectorAll("input")[0].getAttribute("style")).toBe(`color: ${color};`); expect(fixture.querySelectorAll("input")[1].value).toBe("B5"); expect(fixture.querySelectorAll("input")[1].getAttribute("style")).toBe(`color: ${color2};`); - app.destroy(); }); test("ctrl + select cell --> add new input", async () => { - const { app, parent, model } = await mountSpreadsheet(fixture); - 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(); @@ -168,11 +163,10 @@ describe("Selection Input", () => { expect(inputs.length).toBe(2); expect(inputs[0].value).toBe("B4"); expect(inputs[1].value).toBe("B5"); - app.destroy(); }); test("input is not filled with highlight when maximum ranges reached", async () => { - const { app, model } = await createSelectionInput({ hasSingleRange: true }); + const { model } = await createSelectionInput({ hasSingleRange: true }); expect(fixture.querySelectorAll("input")).toHaveLength(1); model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION"); selectCell(model, "B2"); @@ -180,77 +174,69 @@ describe("Selection Input", () => { expect(fixture.querySelectorAll("input")).toHaveLength(1); expect(fixture.querySelector("input")!.value).toBe("B2"); expect(fixture.querySelector(".o-add-selection")).toBeNull(); - app.destroy(); }); test("new range is added when button clicked", async () => { - const { app } = await createSelectionInput(); + await createSelectionInput(); expect(fixture.querySelectorAll("input").length).toBe(1); await simulateClick(".o-add-selection"); expect(fixture.querySelectorAll("input").length).toBe(2); - app.destroy(); }); test("can set initial ranges", async () => { - const { app } = await createSelectionInput({ initialRanges: ["C4", "A1"] }); + await createSelectionInput({ initialRanges: ["C4", "A1"] }); expect(fixture.querySelectorAll("input").length).toBe(2); expect(fixture.querySelectorAll("input")[0].value).toBe("C4"); expect(fixture.querySelectorAll("input")[1].value).toBe("A1"); - app.destroy(); }); test("can focus a range", async () => { - const { app } = await createSelectionInput(); + await createSelectionInput(); await simulateClick(".o-add-selection"); // last input is now focused expect(fixture.querySelectorAll("input")[0].classList).not.toContain("o-focused"); expect(fixture.querySelectorAll("input")[1].classList).toContain("o-focused"); await simulateClick("input"); // focus the first input expect(fixture.querySelectorAll("input")[0].classList).toContain("o-focused"); expect(fixture.querySelectorAll("input")[1].classList).not.toContain("o-focused"); - app.destroy(); }); test("unmounting deletes the state", async () => { - const { app, model, id } = await createSelectionInput(); + const { model, id, app } = await createSelectionInput(); expect(model.getters.getSelectionInput(id).length).toBe(1); app.destroy(); expect(model.getters.getSelectionInput(id).length).toBe(0); }); test("can unfocus all inputs with the OK button", async () => { - const { app } = await createSelectionInput(); + await createSelectionInput(); expect(fixture.querySelector(".o-focused")).toBeTruthy(); await simulateClick(".o-selection-ok"); expect(fixture.querySelector(".o-focused")).toBeFalsy(); - app.destroy(); }); test("manually input a single cell", async () => { - const { app, model, id } = await createSelectionInput(); + const { model, id } = await createSelectionInput(); await writeInput(0, "C2"); expect(fixture.querySelectorAll("input")[0].value).toBe("C2"); expect(model.getters.getSelectionInput(id)[0].xc).toBe("C2"); - app.destroy(); }); test("manually input multiple cells", async () => { - const { app, model, id } = await createSelectionInput(); + const { model, id } = await createSelectionInput(); await writeInput(0, "C2,A1"); expect(fixture.querySelectorAll("input")[0].value).toBe("C2"); expect(model.getters.getSelectionInput(id)[0].xc).toBe("C2"); expect(fixture.querySelectorAll("input")[1].value).toBe("A1"); expect(model.getters.getSelectionInput(id)[1].xc).toBe("A1"); - app.destroy(); }); test("manually add another cell", async () => { - const { app, model, id } = await createSelectionInput({ initialRanges: ["C2"] }); + const { model, id } = await createSelectionInput({ initialRanges: ["C2"] }); await writeInput(0, "C2,A1"); expect(fixture.querySelectorAll("input")[0].value).toBe("C2"); expect(model.getters.getSelectionInput(id)[0].xc).toBe("C2"); expect(fixture.querySelectorAll("input")[1].value).toBe("A1"); expect(model.getters.getSelectionInput(id)[1].xc).toBe("A1"); - app.destroy(); }); test("F2 alters edition mode", async () => { @@ -276,11 +262,10 @@ describe("Selection Input", () => { const onChanged = jest.fn((ranges) => { newRanges = ranges; }); - const { app } = await createSelectionInput({ onChanged }); + await createSelectionInput({ onChanged }); await writeInput(0, "C2"); expect(onChanged).toHaveBeenCalled(); expect(newRanges).toStrictEqual(["C2"]); - app.destroy(); }); test("changed event is triggered when cell is selected", async () => { @@ -288,48 +273,45 @@ describe("Selection Input", () => { const onChanged = jest.fn((ranges) => { newRanges = ranges; }); - const { app, model } = await createSelectionInput({ onChanged }); + const { model } = await createSelectionInput({ onChanged }); selectCell(model, "B4"); await nextTick(); expect(onChanged).toHaveBeenCalled(); expect(newRanges).toStrictEqual(["B4"]); - app.destroy(); }); test("focus is transferred from one input to another", async () => { model = new Model(); - const app = new App(MultiParent, { props: { model } }); - app.addTemplates(OWL_TEMPLATES); - await app.mount(fixture); + ({ fixture } = await mountComponent(MultiParent, { props: { model }, model })); await nextTick(); expect(fixture.querySelector(".input-1 .o-focused")).toBeTruthy(); expect(fixture.querySelector(".input-2 .o-focused")).toBeFalsy(); await simulateClick(".input-2 input"); expect(fixture.querySelector(".input-1 .o-focused")).toBeFalsy(); expect(fixture.querySelector(".input-2 .o-focused")).toBeTruthy(); - app.destroy(); }); test("go back to initial sheet when selection is finished", async () => { - const { app, model } = await createSelectionInput(); + const fixture = makeTestFixture(); + const { model } = await createSelectionInput({}, fixture); const sheet1Id = model.getters.getActiveSheetId(); createSheet(model, { sheetId: "42", activate: true }); - const { app: app1 } = await createSelectionInput(); + await createSelectionInput({}, fixture); activateSheet(model, "42"); selectCell(model, "B4"); await nextTick(); expect(fixture.querySelector("input")!.value).toBe("Sheet2!B4"); await simulateClick(".o-selection-ok"); expect(model.getters.getActiveSheetId()).toBe(sheet1Id); - app.destroy(); - app1.destroy(); + fixture.remove(); }); test("undo after selection won't change active sheet", async () => { - const { app, model } = await createSelectionInput(); + const fixture = makeTestFixture(); + const { model } = await createSelectionInput({}, fixture); const sheet1Id = model.getters.getActiveSheetId(); createSheet(model, { sheetId: "42" }); - const { app: app1 } = await createSelectionInput(); + await createSelectionInput({}, fixture); activateSheet(model, "42"); selectCell(model, "B4"); await nextTick(); @@ -337,11 +319,10 @@ describe("Selection Input", () => { expect(model.getters.getActiveSheetId()).toBe(sheet1Id); undo(model); expect(model.getters.getActiveSheetId()).toBe(sheet1Id); - app.destroy(); - app1.destroy(); + fixture.remove(); }); test("show red border if and only if invalid range", async () => { - const { app } = await createSelectionInput(); + await createSelectionInput(); await writeInput(0, "A1"); expect(fixture.querySelectorAll("input")[0].value).toBe("A1"); expect(fixture.querySelectorAll("input")[0].getAttribute("style")).not.toBe("color: #000;"); @@ -354,17 +335,15 @@ describe("Selection Input", () => { expect(fixture.querySelectorAll("input")[0].value).toBe("B1"); expect(fixture.querySelectorAll("input")[0].getAttribute("style")).not.toBe("color: #000;"); expect(fixture.querySelectorAll("input")[0].classList).not.toContain("o-invalid"); - app.destroy(); }); test("don't show red border initially", async () => { - const { app } = await createSelectionInput(); + await createSelectionInput(); expect(fixture.querySelectorAll("input")[0].classList).not.toContain("o-invalid"); - app.destroy(); }); test("pressing and releasing control has no effect on future clicks", async () => { - const { app, parent, model } = await mountSpreadsheet(fixture); - 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(); @@ -377,6 +356,5 @@ describe("Selection Input", () => { expect(fixture.querySelectorAll(".o-selection-input input")).toHaveLength(1); input = fixture.querySelector(".o-selection-input input") as HTMLInputElement; expect(input.value).toBe("A2"); - app.destroy(); }); }); diff --git a/tests/components/side_panel.test.ts b/tests/components/side_panel.test.ts index 51f115ca1e..164f237a99 100644 --- a/tests/components/side_panel.test.ts +++ b/tests/components/side_panel.test.ts @@ -1,13 +1,12 @@ -import { App, Component, xml } from "@odoo/owl"; +import { Component, xml } from "@odoo/owl"; import { Spreadsheet } from "../../src"; import { sidePanelRegistry } from "../../src/registries/index"; import { SidePanelContent } from "../../src/registries/side_panel_registry"; import { simulateClick } from "../test_helpers/dom_helper"; -import { makeTestFixture, mountSpreadsheet, nextTick } from "../test_helpers/helpers"; +import { mountSpreadsheet, nextTick } from "../test_helpers/helpers"; let fixture: HTMLElement; let parent: Spreadsheet; -let app: App; let sidePanelContent: { [key: string]: SidePanelContent }; class Body extends Component { @@ -27,14 +26,11 @@ class Body2 extends Component { } beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent } = await mountSpreadsheet(fixture)); + ({ parent, fixture } = await mountSpreadsheet()); sidePanelContent = Object.assign({}, sidePanelRegistry.content); }); afterEach(() => { - app.destroy(); - fixture.remove(); sidePanelRegistry.content = sidePanelContent; }); diff --git a/tests/components/spreadsheet.test.ts b/tests/components/spreadsheet.test.ts index 4874f3e70f..8fc777d135 100644 --- a/tests/components/spreadsheet.test.ts +++ b/tests/components/spreadsheet.test.ts @@ -1,10 +1,10 @@ -import { App } from "@odoo/owl"; import { Model } from "../../src"; import { Spreadsheet } from "../../src/components"; import { DEFAULT_CELL_HEIGHT } from "../../src/constants"; import { args, 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, @@ -22,7 +22,6 @@ import { } from "../test_helpers/dom_helper"; import { getActiveSheetFullScrollInfo, getCellContent } from "../test_helpers/getters_helpers"; import { - makeTestFixture, mountSpreadsheet, nextTick, restoreDefaultFunctions, @@ -40,27 +39,21 @@ jest.mock("../../src/components/composer/content_editable_helper", () => let fixture: HTMLElement; let parent: Spreadsheet; let model: Model; -let app: App; +let env: SpreadsheetChildEnv; describe("Simple Spreadsheet Component", () => { - // default model and env - beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model, parent } = await mountSpreadsheet(fixture, { + test("simple rendering snapshot", async () => { + ({ model, parent, fixture } = await mountSpreadsheet({ model: new Model({ sheets: [{ id: "sh1" }] }), })); - }); - - afterEach(() => { - app.destroy(); - fixture.remove(); - }); - - 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"); document.querySelector(".o-add-sheet")!.dispatchEvent(new Event("click")); await nextTick(); @@ -71,7 +64,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", @@ -96,7 +88,7 @@ describe("Simple Spreadsheet Component", () => { }); test("Can use an external dependency in a function at model start", async () => { - await mountSpreadsheet(fixture, { + await mountSpreadsheet({ model: new Model( { version: 2, @@ -120,11 +112,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"]`); expect(document.activeElement).not.toBeNull(); document.activeElement?.dispatchEvent(new InputEvent("input", { data: "d", bubbles: true })); @@ -134,6 +130,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 }) @@ -148,6 +145,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 }) ); @@ -162,6 +160,7 @@ describe("Simple Spreadsheet Component", () => { }); test("Z-indexes of the various spreadsheet components", async () => { + ({ model } = await mountSpreadsheet()); const getZIndex = (selector: string) => Number(getElComputedStyle(selector, "zIndex")) || 0; mockChart(); const gridZIndex = getZIndex(".o-grid"); @@ -203,12 +202,13 @@ 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(); @@ -217,29 +217,20 @@ describe("Simple Spreadsheet Component", () => { test("Can instantiate a spreadsheet with a given client id-name", async () => { const client = { id: "alice", name: "Alice" }; - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture, { - model: new Model({}, { client }), - })); + ({ model } = await mountSpreadsheet({ model: new Model({}, { client }) })); expect(model.getters.getClient()).toEqual(client); - app.destroy(); - fixture.remove(); }); test("Spreadsheet detects frozen panes that exceed the limit size at start", async () => { const notifyUser = jest.fn(); - fixture = makeTestFixture(); const model = new Model({ sheets: [{ panes: { xSplit: 12, ySplit: 50 } }] }); - ({ app, parent } = await mountSpreadsheet(fixture, { model }, { notifyUser })); + ({ parent } = await mountSpreadsheet({ model }, { notifyUser })); expect(notifyUser).toHaveBeenCalled(); - app.destroy(); - fixture.remove(); }); test("Warn user only once when the viewport is too small for its frozen panes", async () => { const notifyUser = jest.fn(); - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture, undefined, { notifyUser })); + ({ parent, model } = await mountSpreadsheet(undefined, { notifyUser })); expect(notifyUser).not.toHaveBeenCalled(); freezeRows(model, 51); await nextTick(); @@ -258,29 +249,21 @@ test("Warn user only once when the viewport is too small for its frozen panes", freezeRows(model, 51); await nextTick(); expect(notifyUser).toHaveBeenCalledTimes(2); - app.destroy(); - fixture.remove(); }); describe("Composer interactions", () => { beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model, parent } = await mountSpreadsheet(fixture, { + ({ model, fixture } = await mountSpreadsheet({ model: new Model({ sheets: [{ id: "sh1" }] }), })); }); - - afterEach(() => { - app.destroy(); - fixture.remove(); - }); test("type in grid composer adds text to topbar composer", async () => { document.activeElement!.dispatchEvent( new KeyboardEvent("keydown", { key: "Enter", bubbles: true }) ); await nextTick(); - const gridComposer = document.querySelector(".o-grid .o-composer"); - const topBarComposer = document.querySelector(".o-spreadsheet-topbar .o-composer"); + const gridComposer = fixture.querySelector(".o-grid .o-composer"); + const topBarComposer = fixture.querySelector(".o-spreadsheet-topbar .o-composer"); expect(document.activeElement).toBe(gridComposer); await typeInComposerGrid("text"); expect(topBarComposer!.textContent).toBe("text"); @@ -290,8 +273,8 @@ describe("Composer interactions", () => { test("type in topbar composer adds text to grid composer", async () => { triggerMouseEvent(".o-spreadsheet-topbar .o-composer", "click"); await nextTick(); - const topBarComposer = document.querySelector(".o-spreadsheet-topbar .o-composer"); - const gridComposer = document.querySelector(".o-grid .o-composer"); + const topBarComposer = fixture.querySelector(".o-spreadsheet-topbar .o-composer"); + const gridComposer = fixture.querySelector(".o-grid .o-composer"); expect(topBarComposer).not.toBeNull(); expect(document.activeElement).toBe(topBarComposer); expect(gridComposer).not.toBeNull(); @@ -304,8 +287,8 @@ describe("Composer interactions", () => { test("start typing in topbar composer then continue in grid composer", async () => { triggerMouseEvent(".o-spreadsheet-topbar .o-composer", "click"); await nextTick(); - const topBarComposer = document.querySelector(".o-spreadsheet-topbar .o-composer"); - const gridComposer = document.querySelector(".o-grid .o-composer"); + const topBarComposer = fixture.querySelector(".o-spreadsheet-topbar .o-composer"); + const gridComposer = fixture.querySelector(".o-grid .o-composer"); // Type in top bar composer await typeInComposerTopBar("from topbar"); @@ -324,7 +307,7 @@ describe("Composer interactions", () => { setCellContent(model, "A2", "Hello"); selectCell(model, "A2"); await nextTick(); - const topBarComposer = document.querySelector(".o-spreadsheet-topbar .o-composer"); + const topBarComposer = fixture.querySelector(".o-spreadsheet-topbar .o-composer"); expect(topBarComposer!.textContent).toBe("Hello"); }); @@ -332,7 +315,7 @@ describe("Composer interactions", () => { setCellContent(model, "A2", "10/10/2021"); selectCell(model, "A2"); await nextTick(); - const topBarComposer = document.querySelector(".o-spreadsheet-topbar .o-composer"); + const topBarComposer = fixture.querySelector(".o-spreadsheet-topbar .o-composer"); expect(topBarComposer!.textContent).toBe("10/10/2021"); // Focus top bar composer triggerMouseEvent(".o-spreadsheet-topbar .o-composer", "click"); @@ -344,7 +327,7 @@ describe("Composer interactions", () => { new KeyboardEvent("keydown", { key: "Enter", bubbles: true }) ); await nextTick(); - const topBarComposer = document.querySelector(".o-spreadsheet-topbar .o-composer")!; + const topBarComposer = fixture.querySelector(".o-spreadsheet-topbar .o-composer")!; await typeInComposerGrid("=SU"); await nextTick(); expect(fixture.querySelector(".o-grid .o-autocomplete-dropdown")).not.toBeNull(); @@ -358,8 +341,8 @@ describe("Composer interactions", () => { new KeyboardEvent("keydown", { key: "Enter", bubbles: true }) ); await nextTick(); - const topBarComposer = document.querySelector(".o-spreadsheet-topbar .o-composer")!; - const gridComposerContainer = document.querySelector(".o-grid-composer")! as HTMLElement; + const topBarComposer = fixture.querySelector(".o-spreadsheet-topbar .o-composer")!; + const gridComposerContainer = fixture.querySelector(".o-grid-composer")! as HTMLElement; const spy = jest.spyOn(gridComposerContainer.style, "width", "set"); await typeInComposerGrid("=SU"); await nextTick(); @@ -372,7 +355,7 @@ describe("Composer interactions", () => { test("selecting ranges multiple times in topbar bar does not resize grid composer", async () => { triggerMouseEvent(".o-spreadsheet-topbar .o-composer", "click"); await nextTick(); - const gridComposerContainer = document.querySelector(".o-grid-composer")! as HTMLElement; + const gridComposerContainer = fixture.querySelector(".o-grid-composer")! as HTMLElement; // Type in top bar composer await typeInComposerTopBar("="); const spy = jest.spyOn(gridComposerContainer.style, "width", "set"); @@ -389,37 +372,33 @@ describe("Composer interactions", () => { 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("Notify ui correctly with type notification correctly use notifyUser in the env", async () => { const raiseError = jest.fn(); - const fixture = makeTestFixture(); const model = new Model(); - const { app } = await mountSpreadsheet(fixture, { model }, { raiseError }); - await app.mount(fixture); + await mountSpreadsheet({ model }, { raiseError }); model["config"].notifyUI({ type: "ERROR", text: "hello" }); expect(raiseError).toHaveBeenCalledWith("hello"); - fixture.remove(); - app.destroy(); }); test("The composer helper should be closed on toggle topbar context menu", async () => { await typeInComposerGrid("=sum("); - expect(parent.model.getters.getEditionMode()).not.toBe("inactive"); + expect(model.getters.getEditionMode()).not.toBe("inactive"); expect(fixture.querySelectorAll(".o-composer-assistant")).toHaveLength(1); await simulateClick(".o-topbar-topleft .o-topbar-menu"); - expect(parent.model.getters.getEditionMode()).toBe("inactive"); + expect(model.getters.getEditionMode()).toBe("inactive"); expect(fixture.querySelectorAll(".o-composer-assistant")).toHaveLength(0); }); }); describe("Composer / selectionInput interactions", () => { beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, model, parent } = await mountSpreadsheet(fixture, { - model: new Model({ sheets: [{ id: "sh1" }] }), + model = new Model({ sheets: [{ id: "sh1" }] }); + ({ fixture, env, parent } = await mountSpreadsheet({ + model, })); const sheetId = model.getters.getActiveSheetId(); model.dispatch("ADD_CONDITIONAL_FORMAT", { @@ -439,26 +418,21 @@ describe("Composer / selectionInput interactions", () => { setCellContent(model, "B2", "=A1"); }); - afterEach(() => { - app.destroy(); - fixture.remove(); - }); - 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", @@ -468,29 +442,29 @@ describe("Composer / selectionInput interactions", () => { 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 fff077c10f..e5e4960e2f 100644 --- a/tests/components/top_bar.test.ts +++ b/tests/components/top_bar.test.ts @@ -1,4 +1,5 @@ -import { App, Component, onMounted, onWillUnmount, useState, useSubEnv, xml } from "@odoo/owl"; +import { Component, onMounted, onWillUnmount, useState, useSubEnv, xml } from "@odoo/owl"; +import { ComposerFocusType } from "../../src/components/spreadsheet/spreadsheet"; import { TopBar } from "../../src/components/top_bar/top_bar"; import { DEFAULT_FONT_SIZE } from "../../src/constants"; import { toZone } from "../../src/helpers"; @@ -7,7 +8,6 @@ import { topbarComponentRegistry } from "../../src/registries"; import { getMenuChildren } from "../../src/registries/menus/helpers"; import { topbarMenuRegistry } from "../../src/registries/menus/topbar_menu_registry"; import { ConditionalFormat, Pixel, Style } from "../../src/types"; -import { OWL_TEMPLATES } from "../setup/jest.setup"; import { addCellToSelection, createFilter, @@ -15,15 +15,15 @@ import { setAnchorCorner, setCellContent, setSelection, + setStyle, } from "../test_helpers/commands_helpers"; import { getElComputedStyle, simulateClick, triggerMouseEvent } from "../test_helpers/dom_helper"; import { getBorder, getCell, getStyle } from "../test_helpers/getters_helpers"; import { getFigureIds, - makeTestFixture, + mountComponent, mountSpreadsheet, nextTick, - target, toRangesData, typeInComposerTopBar, } from "../test_helpers/helpers"; @@ -43,13 +43,17 @@ const t = (s: string): string => s; class Parent extends Component { static template = xml/* xml */ `
- +
`; static components = { TopBar }; static _t = t; - state = useState({ focusComposer: false }); + state = useState({ focusComposer: "inactive" }); setup() { useSubEnv({ @@ -59,7 +63,7 @@ class Parent extends Component { _t: Parent._t, isDashboard: () => this.props.model.getters.isDashboard(), }); - this.state.focusComposer = this.props.focusComposer || false; + this.state.focusComposer = this.props.focusComposer || "inactive"; onMounted(() => this.props.model.on("update", this, this.render)); onWillUnmount(() => this.props.model.off("update", this)); } @@ -69,40 +73,33 @@ class Parent extends Component { return height; } - setFocusComposer(isFocused: boolean) { + setFocusComposer(isFocused: ComposerFocusType) { this.state.focusComposer = isFocused; } } async function mountParent( model: Model = new Model(), - focusComposer: boolean = false -): Promise<{ parent: Parent; app: App }> { - const app = new App(Parent, { props: { model, focusComposer } }); - app.addTemplates(OWL_TEMPLATES); - const parent = await app.mount(fixture); - return { app, parent }; + focusComposer: ComposerFocusType = "inactive" +): Promise<{ parent: Parent; model: Model; fixture: HTMLElement }> { + let parent: Component; + ({ parent, fixture } = await mountComponent(Parent, { + props: { focusComposer, model }, + model, + })); + return { parent: parent as Parent, model, fixture }; } -beforeEach(() => { - fixture = makeTestFixture(); -}); - -afterEach(() => { - fixture.remove(); -}); - describe("TopBar component", () => { test("simple rendering", async () => { - const { app } = await mountParent(); + await mountParent(); expect(fixture.querySelector(".o-spreadsheet-topbar")).toMatchSnapshot(); - app.destroy(); }); test("opening a second menu closes the first one", async () => { const model = new Model(); setCellContent(model, "B2", "b2"); - const { app } = await mountParent(model); + await mountParent(model); expect(fixture.querySelectorAll(".o-dropdown-content").length).toBe(0); fixture.querySelector('.o-tool[title="Borders"]')!.dispatchEvent(new Event("click")); @@ -115,12 +112,10 @@ describe("TopBar component", () => { await nextTick(); expect(fixture.querySelectorAll(".o-dropdown-content").length).toBe(1); expect(fixture.querySelectorAll(".o-color-line").length).toBe(0); - app.destroy(); }); test("Menu should be closed while clicking on composer", async () => { - const { app } = await mountParent(); - + await mountParent(); expect(fixture.querySelectorAll(".o-menu").length).toBe(0); fixture .querySelector(".o-topbar-menu[data-id='file']")! @@ -132,7 +127,6 @@ describe("TopBar component", () => { )!; await simulateClick(topbarComposerElement); expect(fixture.querySelectorAll(".o-menu").length).toBe(0); - app.destroy(); }); test("merging cell button state is correct", async () => { @@ -146,7 +140,7 @@ describe("TopBar component", () => { }, ], }); - const { app } = await mountParent(model); + await mountParent(model); const mergeTool = fixture.querySelector('.o-tool[title="Merge Cells"]')!; expect(mergeTool.classList.contains("active")).toBeTruthy(); @@ -155,14 +149,13 @@ describe("TopBar component", () => { setAnchorCorner(model, "A2"); await nextTick(); expect(mergeTool.classList.contains("active")).toBeFalsy(); - app.destroy(); }); test("multiple selection zones => merge tools is disabled", async () => { const model = new Model(); setCellContent(model, "B2", "b2"); - const { app } = await mountParent(model); + await mountParent(model); const mergeTool = fixture.querySelector('.o-tool[title="Merge Cells"]')!; // should be disabled, because the selection is just one cell @@ -177,13 +170,10 @@ describe("TopBar component", () => { await nextTick(); // should be disabled, because multiple zones are selected expect(mergeTool.classList.contains("o-disabled")).toBeTruthy(); - app.destroy(); }); test("undo/redo tools", async () => { - const model = new Model(); - - const { app } = await mountParent(model); + const { model } = await mountParent(); const undoTool = fixture.querySelector('.o-tool[title="Undo"]')!; const redoTool = fixture.querySelector('.o-tool[title="Redo"]')!; @@ -207,13 +197,10 @@ describe("TopBar component", () => { expect(redoTool.classList.contains("o-disabled")).toBeFalsy(); expect(getCell(model, "A1")).toBeUndefined(); - app.destroy(); }); test("paint format tools", async () => { - const model = new Model(); - - const { app } = await mountParent(model); + await mountParent(); const paintFormatTool = fixture.querySelector('.o-tool[title="Paint Format"]')!; expect(paintFormatTool.classList.contains("active")).toBeFalsy(); @@ -222,20 +209,13 @@ describe("TopBar component", () => { await nextTick(); expect(paintFormatTool.classList.contains("active")).toBeTruthy(); - app.destroy(); }); describe("Filter Tool", () => { let model: Model; - let app: App; beforeEach(async () => { - model = new Model(); - ({ app } = await mountParent(model)); - }); - - afterEach(() => { - app.destroy(); + ({ model } = await mountParent()); }); test("Filter tool is enabled with single selection", async () => { @@ -283,17 +263,15 @@ describe("TopBar component", () => { border: "all", }); expect(getBorder(model, "B1")).toBeDefined(); - const { app } = await mountParent(model); + await mountParent(model); const clearFormatTool = fixture.querySelector('.o-tool[title="Clear Format"]')!; clearFormatTool.dispatchEvent(new Event("click")); expect(getCell(model, "B1")).toBeUndefined(); - app.destroy(); }); test("can set cell format", async () => { - const model = new Model(); + const { model } = await mountParent(); expect(getCell(model, "A1")).toBeUndefined(); - const { app } = await mountParent(model); const formatTool = fixture.querySelector('.o-tool[title="More formats"]')!; formatTool.dispatchEvent(new Event("click")); await nextTick(); @@ -303,12 +281,10 @@ describe("TopBar component", () => { .dispatchEvent(new Event("click", { bubbles: true })); await nextTick(); expect(getCell(model, "A1")!.format).toEqual("0.00%"); - app.destroy(); }); test("can set font size", async () => { - const model = new Model(); - const { app } = await mountParent(model); + const { model } = await mountParent(); const fontSizeTool = fixture.querySelector('.o-tool[title="Font Size"]')!; expect(fontSizeTool.textContent!.trim()).toBe(DEFAULT_FONT_SIZE.toString()); fontSizeTool.dispatchEvent(new Event("click")); @@ -317,7 +293,6 @@ describe("TopBar component", () => { await nextTick(); expect(fontSizeTool.textContent!.trim()).toBe("8"); expect(getStyle(model, "A1").fontSize).toBe(8); - app.destroy(); }); test.each([ @@ -327,7 +302,7 @@ describe("TopBar component", () => { ])("can set horizontal alignment with the toolbar", async (iconClass, expectedStyle) => { const model = new Model(); selectCell(model, "A1"); - const { app } = await mountParent(model); + await mountParent(model); const alignTool = fixture.querySelector('.o-tool[title="Horizontal align"]')!; alignTool.dispatchEvent(new Event("click")); await nextTick(); @@ -340,8 +315,8 @@ describe("TopBar component", () => { button.dispatchEvent(new Event("click")); await nextTick(); expect(model.getters.getCurrentStyle()).toEqual(expectedStyle); - app.destroy(); }); + test.each([ ["text", {}, "align-left"], ["0", {}, "align-right"], @@ -358,17 +333,16 @@ describe("TopBar component", () => { target: [toZone("A1")], style: style as Style, }); - const { app } = await mountParent(model); + await mountParent(model); const alignTool = fixture.querySelector('.o-tool[title="Horizontal align"]')!; expect(alignTool.querySelector("svg")!.classList).toContain(expectedIconClass); - app.destroy(); } ); test("opening, then closing same menu", async () => { const model = new Model(); setCellContent(model, "B2", "b2"); - const { app } = await mountParent(model); + await mountParent(model); expect(fixture.querySelectorAll(".o-dropdown-content").length).toBe(0); fixture.querySelector('.o-tool[title="Borders"]')!.dispatchEvent(new Event("click")); @@ -377,11 +351,10 @@ describe("TopBar component", () => { fixture.querySelector('.o-tool[title="Borders"]')!.dispatchEvent(new Event("click")); await nextTick(); expect(fixture.querySelectorAll(".o-dropdown-content").length).toBe(0); - app.destroy(); }); test("Can open a Topbar menu", async () => { - const { app, parent } = await mountParent(); + const { parent } = await mountParent(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); const items = topbarMenuRegistry.getAll(); const number = items.filter((item) => item.children.length !== 0).length; @@ -397,11 +370,10 @@ describe("TopBar component", () => { triggerMouseEvent(".o-spreadsheet-topbar", "click"); await nextTick(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); - app.destroy(); }); test("Can open a Topbar menu with mousemove", async () => { - const { app, parent } = await mountParent(); + const { parent } = await mountParent(); triggerMouseEvent(".o-topbar-menu[data-id='file']", "click"); await nextTick(); const file = topbarMenuRegistry.get("file"); @@ -418,7 +390,6 @@ describe("TopBar component", () => { ).length; expect(fixture.querySelectorAll(".o-menu-item")).toHaveLength(numberChild); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(1); - app.destroy(); }); test("Can click on a menuItem do execute action and close menus", async () => { @@ -432,7 +403,7 @@ describe("TopBar component", () => { number++; }, }); - const { app } = await mountParent(); + const { fixture } = await mountParent(); triggerMouseEvent(".o-topbar-menu[data-id='test']", "click"); await nextTick(); triggerMouseEvent(".o-menu-item", "click"); @@ -440,11 +411,10 @@ describe("TopBar component", () => { expect(fixture.querySelectorAll(".o-menu-dropdown-content")).toHaveLength(0); expect(number).toBe(1); topbarMenuRegistry.content = menuDefinitions; - app.destroy(); }); test("Opened menu parent is highlighted", async () => { - const { app } = await mountParent(); + await mountParent(); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); const menuItem = fixture.querySelector(".o-topbar-menu[data-id='edit']"); expect(menuItem?.classList).not.toContain("o-topbar-menu-active"); @@ -456,7 +426,6 @@ describe("TopBar component", () => { await nextTick(); expect(menuItem?.classList).not.toContain("o-topbar-menu-active"); expect(fixture.querySelectorAll(".o-menu")).toHaveLength(0); - app.destroy(); }); test("Can add a custom component to topbar", async () => { @@ -465,10 +434,9 @@ describe("TopBar component", () => { static template = xml`
Test
`; } topbarComponentRegistry.add("1", { component: Comp }); - const { app } = await mountParent(); + await mountParent(); expect(fixture.querySelectorAll(".o-topbar-test")).toHaveLength(1); topbarComponentRegistry.content = compDefinitions; - app.destroy(); }); test("Can add multiple components to topbar with different visibilities", async () => { @@ -487,7 +455,7 @@ describe("TopBar component", () => { }, }); topbarComponentRegistry.add("second", { component: Comp2 }); - const { app, parent } = await mountParent(); + const { parent } = await mountParent(); expect(fixture.querySelectorAll(".o-topbar-test1")).toHaveLength(0); expect(fixture.querySelectorAll(".o-topbar-test2")).toHaveLength(1); @@ -499,12 +467,10 @@ describe("TopBar component", () => { // reset Top Component Registry topbarComponentRegistry.content = compDefinitions; - app.destroy(); }); test("Readonly spreadsheet has a specific top bar", async () => { - const model = new Model(); - const { app } = await mountParent(model); + const { model } = await mountParent(); expect(fixture.querySelectorAll(".o-readonly-toolbar")).toHaveLength(0); model.updateMode("readonly"); @@ -514,26 +480,25 @@ describe("TopBar component", () => { await nextTick(); const insertMenuItems = fixture.querySelectorAll(".o-menu div.o-menu-item"); expect([...insertMenuItems].every((item) => item.classList.contains("disabled"))).toBeTruthy(); - app.destroy(); }); test("Cannot edit cell in a readonly spreadsheet", async () => { const model = new Model({}, { mode: "readonly" }); - const { app, parent } = await mountParent(model); + let parent: Parent; + ({ parent, fixture } = await mountParent(model)); let composerEl = fixture.querySelector(".o-spreadsheet-topbar div.o-composer")!; expect(composerEl.classList.contains("unfocusable")).toBeTruthy(); expect(composerEl.attributes.getNamedItem("contentEditable")!.value).toBe("false"); - parent.setFocusComposer(true); + parent.setFocusComposer("contentFocus"); await nextTick(); // Won't update the current content const content = model.getters.getCurrentContent(); expect(content).toBe(""); composerEl = await typeInComposerTopBar("tabouret", false); expect(model.getters.getCurrentContent()).toBe(content); - app.destroy(); }); test.each([ @@ -547,7 +512,7 @@ describe("TopBar component", () => { "Clicking a static element inside a dropdown '%s' don't close the dropdown", async (toolName: string, dropdownContentSelector: string) => { const model = new Model(); - const { app } = await mountParent(model); + ({ fixture } = await mountParent(model)); await simulateClick(`.o-tool[title="${toolName}"]`); await nextTick(); @@ -555,22 +520,17 @@ describe("TopBar component", () => { await simulateClick(dropdownContentSelector); await nextTick(); expect(fixture.querySelector(dropdownContentSelector)).toBeTruthy(); - app.destroy(); } ); test("can insert an image", async () => { - fixture = makeTestFixture(); const fileStore = new FileStore(); const model = new Model({}, { external: { fileStore } }); - const { app } = await mountSpreadsheet(fixture, { 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']"); expect(getFigureIds(model, sheetId)).toHaveLength(1); - app.destroy(); - fixture.remove(); }); }); @@ -590,34 +550,27 @@ test("Can show/hide a TopBarComponent based on condition", async () => { component: Comp2, isVisible: (env) => false, }); - const { app } = await mountParent(); + await mountParent(); expect(fixture.querySelectorAll(".o-topbar-test1")).toHaveLength(1); expect(fixture.querySelectorAll(".o-topbar-test2")).toHaveLength(0); topbarComponentRegistry.content = compDefinitions; - app.destroy(); }); describe("TopBar - Custom currency", () => { test("can open custom currency sidepanel from tool", async () => { - const { app } = await mountSpreadsheet(fixture); + const { fixture } = await mountSpreadsheet(); triggerMouseEvent(".o-tool[title='More formats']", "click"); await nextTick(); triggerMouseEvent(".o-format-tool div[data-custom='custom_currency']", "click"); await nextTick(); expect(fixture.querySelector(".o-custom-currency")).toBeTruthy(); - app.destroy(); }); }); describe("Format", () => { test("can clear format", async () => { - const { app, model } = await mountSpreadsheet(fixture); - const sheetId = model.getters.getActiveSheetId(); - model.dispatch("SET_FORMATTING", { - sheetId, - target: target("A1, B2:B3"), - style: { fillColor: "#000000" }, - }); + const { model } = await mountSpreadsheet(); + setStyle(model, "A1, B2:B3", { fillColor: "#000000" }); selectCell(model, "A1"); addCellToSelection(model, "B2"); setAnchorCorner(model, "B3"); @@ -631,13 +584,12 @@ describe("Format", () => { expect(getCell(model, "A1")?.style).toBeUndefined(); expect(getCell(model, "B2")?.style).toBeUndefined(); expect(getCell(model, "B3")?.style).toBeUndefined(); - app.destroy(); }); }); describe("TopBar - CF", () => { test("open sidepanel with no CF in selected zone", async () => { - const { app } = await mountSpreadsheet(fixture); + const { fixture } = await mountSpreadsheet(); triggerMouseEvent(".o-topbar-menu[data-id='format']", "click"); await nextTick(); triggerMouseEvent(".o-menu-item[data-name='format_cf']", "click"); @@ -648,11 +600,10 @@ describe("TopBar - CF", () => { expect( fixture.querySelector(".o-sidePanel .o-sidePanelBody .o-cf .o-cf-ruleEditor") ).toBeFalsy(); - app.destroy(); }); test("open sidepanel with one CF in selected zone", async () => { - const { app, model } = await mountSpreadsheet(fixture); + const { model, fixture } = await mountSpreadsheet(); const cfRule: ConditionalFormat = { ranges: ["A1:C7"], @@ -682,11 +633,10 @@ describe("TopBar - CF", () => { expect( fixture.querySelector(".o-sidePanel .o-sidePanelBody .o-cf .o-cf-ruleEditor") ).toBeTruthy(); - app.destroy(); }); test("open sidepanel with with more then one CF in selected zone", async () => { - const { app, model } = await mountSpreadsheet(fixture); + const { model, fixture } = await mountSpreadsheet(); const cfRule1: ConditionalFormat = { ranges: ["A1:C7"], @@ -731,11 +681,10 @@ describe("TopBar - CF", () => { expect( fixture.querySelector(".o-sidePanel .o-sidePanelBody .o-cf .o-cf-ruleEditor") ).toBeFalsy(); - app.destroy(); }); test("will update sidepanel if we reopen it from other cell", async () => { - const { app, model } = await mountSpreadsheet(fixture); + const { model, fixture } = await mountSpreadsheet(); const cfRule1: ConditionalFormat = { ranges: ["A1:A10"], @@ -776,18 +725,17 @@ describe("TopBar - CF", () => { expect( fixture.querySelector(".o-sidePanel .o-sidePanelBody .o-cf .o-cf-ruleEditor") ).toBeFalsy(); - app.destroy(); }); }); 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 { app, parent, model } = await mountSpreadsheet(fixture); + const { model, env } = await mountSpreadsheet(); triggerMouseEvent(".o-topbar-menu[data-id='view']", "click"); await nextTick(); triggerMouseEvent(".o-menu-item[data-name='view_formulas']", "click"); await nextTick(); expect(model.getters.shouldShowFormulas()).toBe(true); - parent.env.openSidePanel("FindAndReplace"); + env.openSidePanel("FindAndReplace"); await nextTick(); expect(model.getters.shouldShowFormulas()).toBe(true); await nextTick(); @@ -805,14 +753,13 @@ describe("Topbar - View", () => { ); await nextTick(); expect(model.getters.shouldShowFormulas()).toBe(true); - app.destroy(); }); }); 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(); - const { app } = await mountParent(model); + const { model } = await mountParent(); + triggerMouseEvent('.o-tool[title="Font Size"]', "click"); await nextTick(); let height = getElComputedStyle(".o-dropdown-content.o-text-options", "maxHeight"); @@ -825,12 +772,11 @@ describe("Topbar - menu item resizing with viewport", () => { expect(parseInt(height)).toBe( model.getters.getVisibleRect(model.getters.getActiveMainViewport()).height ); - app.destroy(); }); test("color picker of fill color in top bar is resized with screen size change", async () => { - const model = new Model(); - const { app } = await mountParent(model); + const { model } = await mountParent(); + triggerMouseEvent('.o-tool[title="Fill Color"]', "click"); await nextTick(); let height = getElComputedStyle(".o-color-picker.right", "maxHeight"); @@ -843,12 +789,10 @@ describe("Topbar - menu item resizing with viewport", () => { expect(parseInt(height)).toBe( model.getters.getVisibleRect(model.getters.getActiveMainViewport()).height ); - app.destroy(); }); test("color picker of text color in top bar is resized with screen size change", async () => { - const model = new Model(); - const { app } = await mountParent(model); + const { model } = await mountParent(); triggerMouseEvent('.o-tool[title="Text Color"]', "click"); await nextTick(); let height = getElComputedStyle(".o-color-picker.right", "maxHeight"); @@ -861,16 +805,15 @@ describe("Topbar - menu item resizing with viewport", () => { expect(parseInt(height)).toBe( model.getters.getVisibleRect(model.getters.getActiveMainViewport()).height ); - app.destroy(); }); }); test("The composer helper should be closed on toggle topbar context menu", async () => { - const { parent } = await mountSpreadsheet(fixture); + const { model, fixture } = await mountSpreadsheet(); await typeInComposerTopBar("=sum("); - expect(parent.model.getters.getEditionMode()).not.toBe("inactive"); + expect(model.getters.getEditionMode()).not.toBe("inactive"); expect(fixture.querySelectorAll(".o-composer-assistant")).toHaveLength(1); await simulateClick(".o-topbar-topleft .o-topbar-menu"); - expect(parent.model.getters.getEditionMode()).toBe("inactive"); + expect(model.getters.getEditionMode()).toBe("inactive"); expect(fixture.querySelectorAll(".o-composer-assistant")).toHaveLength(0); }); diff --git a/tests/menu_item_insert_chart.test.ts b/tests/menu_item_insert_chart.test.ts index e1b781436e..f2c7ecca09 100644 --- a/tests/menu_item_insert_chart.test.ts +++ b/tests/menu_item_insert_chart.test.ts @@ -1,5 +1,4 @@ -import { App } from "@odoo/owl"; -import { Model, Spreadsheet } from "../src"; +import { Model } from "../src"; import { DEFAULT_CELL_HEIGHT, DEFAULT_CELL_WIDTH, @@ -22,7 +21,6 @@ import { import { doAction, makeTestEnv, - makeTestFixture, mockChart, mountSpreadsheet, nextTick, @@ -84,7 +82,6 @@ describe("Insert chart menu item", () => { let dispatchSpy: jest.SpyInstance; let defaultPayload: any; let model: Model; - let app: App; let env: SpreadsheetChildEnv; let openSidePanelSpy: jest.Mock; @@ -93,11 +90,7 @@ describe("Insert chart menu item", () => { } async function mountTestSpreadsheet() { - const fixture = makeTestFixture(); - let parent: Spreadsheet; - ({ app, model, parent } = await mountSpreadsheet(fixture, { model: new Model(data) })); - env = parent.env; - model = env.model; + ({ model, env } = await mountSpreadsheet({ model: new Model(data) })); dispatchSpy = spyModelDispatch(model); } @@ -130,10 +123,6 @@ describe("Insert chart menu item", () => { }; }); - afterEach(() => { - app?.destroy(); - }); - test("Chart is inserted at correct position", () => { setSelection(model, ["B2"]); insertChart(); diff --git a/tests/menu_items_registry.test.ts b/tests/menu_items_registry.test.ts index 84a68d322a..71f2c68d95 100644 --- a/tests/menu_items_registry.test.ts +++ b/tests/menu_items_registry.test.ts @@ -1,4 +1,3 @@ -import { App } from "@odoo/owl"; import { Model, Spreadsheet } from "../src"; import { fontSizes } from "../src/fonts"; import { zoneToXc } from "../src/helpers"; @@ -25,7 +24,6 @@ import { doAction, getName, getNode, - makeTestFixture, mockUuidV4To, mountSpreadsheet, nextTick, @@ -95,24 +93,16 @@ describe("Menu Item Registry", () => { }); describe("Menu Item actions", () => { - let fixture: HTMLElement; let model: Model; let parent: Spreadsheet; - let app: App; let env: SpreadsheetChildEnv; - let dispatch; + let dispatch: jest.SpyInstance; beforeEach(async () => { - fixture = makeTestFixture(); - ({ app, parent, model } = await mountSpreadsheet(fixture)); - env = parent.env; + ({ parent, model, env } = await mountSpreadsheet()); dispatch = spyDispatch(parent); }); - afterEach(() => { - app.destroy(); - }); - test("Edit -> undo", () => { doAction(["edit", "undo"], env); expect(dispatch).toHaveBeenCalledWith("REQUEST_UNDO"); diff --git a/tests/plugins/merges.test.ts b/tests/plugins/merges.test.ts index 313cdae38f..d30a768c10 100644 --- a/tests/plugins/merges.test.ts +++ b/tests/plugins/merges.test.ts @@ -1,9 +1,6 @@ -import { App, Component, useSubEnv, xml } from "@odoo/owl"; -import { Spreadsheet } from "../../src"; import { toCartesian, toXC, toZone } from "../../src/helpers/index"; import { Model } from "../../src/model"; import { CommandResult } from "../../src/types/index"; -import { OWL_TEMPLATES } from "../setup/jest.setup"; import { addColumns, deleteRows, @@ -19,7 +16,6 @@ import { undo, unMerge, } from "../test_helpers/commands_helpers"; -import { simulateClick } from "../test_helpers/dom_helper"; import { getBorder, getCell, @@ -29,14 +25,7 @@ import { getSelectionAnchorCellXc, getStyle, } from "../test_helpers/getters_helpers"; -import { - getChildFromComponent, - getMergeCellMap, - makeTestFixture, - nextTick, - target, - XCToMergeCellMap, -} from "../test_helpers/helpers"; +import { getMergeCellMap, target, XCToMergeCellMap } from "../test_helpers/helpers"; function getCellsXC(model: Model): string[] { return Object.values(model.getters.getCells(model.getters.getActiveSheetId())).map((cell) => { @@ -310,40 +299,6 @@ describe("merges", () => { expect(merge(model, "A1:C4")).toBeSuccessfullyDispatched(); }); - test("merging destructively a selection ask for confirmation", async () => { - const askConfirmation = jest.fn(); - class Parent extends Component { - static template = xml/* xml */ ``; - static components = { Spreadsheet }; - private _model!: Model; - setup() { - this._model = new Model(); - useSubEnv({ - askConfirmation, - }); - } - - get spreadsheet(): Spreadsheet { - return getChildFromComponent(this, Spreadsheet); - } - - get model(): Model { - return this._model; - } - } - const fixture = makeTestFixture(); - const app = new App(Parent); - app.addTemplates(OWL_TEMPLATES); - const parent = await app.mount(fixture); - const model = parent.model; - setCellContent(model, "B2", "b2"); - - setAnchorCorner(model, "F6"); - await nextTick(); - await simulateClick(".o-merge-tool"); - expect(askConfirmation).toHaveBeenCalled(); - }); - test("merging cells with values will do nothing if not forced", () => { const model = new Model({ sheets: [ diff --git a/tests/setup/jest.setup.ts b/tests/setup/jest.setup.ts index 89cfdfc15f..7c3688125e 100644 --- a/tests/setup/jest.setup.ts +++ b/tests/setup/jest.setup.ts @@ -35,4 +35,18 @@ beforeEach(() => { afterEach(() => { //@ts-ignore global.resizers.removeAll(); + executeCleanups(); }); + +const cleanUps: (() => void)[] = []; + +export function registerCleanup(cleanupFn: () => void) { + cleanUps.push(cleanupFn); +} + +function executeCleanups() { + let cleanupFn: (() => void) | undefined; + while ((cleanupFn = cleanUps.pop())) { + cleanupFn(); + } +} diff --git a/tests/test_helpers/helpers.ts b/tests/test_helpers/helpers.ts index 58410cf425..08222aa7fa 100644 --- a/tests/test_helpers/helpers.ts +++ b/tests/test_helpers/helpers.ts @@ -1,8 +1,9 @@ -import { App, Component, xml } from "@odoo/owl"; +import { App, Component, ComponentConstructor, xml } from "@odoo/owl"; 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, @@ -22,10 +24,8 @@ import { } from "../../src/types"; import { Image } from "../../src/types/image"; import { XLSXExport } from "../../src/types/xlsx"; -import { ImageProvider } from "../components/__mocks__/mock_image_provider"; -import { OWL_TEMPLATES } from "../setup/jest.setup"; +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"; @@ -88,13 +88,15 @@ export function makeTestFixture() { return fixture; } -export function makeTestEnv(mockEnv: Partial): SpreadsheetChildEnv { +export function makeTestEnv(mockEnv: Partial = {}): SpreadsheetChildEnv { return { model: mockEnv.model || new Model(), isDashboard: mockEnv.isDashboard || (() => false), 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 || (() => {}), @@ -119,22 +121,60 @@ export function testUndoRedo(model: Model, expect: jest.Expect, command: Command expect(model).toExport(after); } +export async function mountComponent( + component: ComponentConstructor, + optionalArgs: { + props?: Props; + env?: Partial; + model?: Model; + fixture?: HTMLElement; + } = {} +): Promise<{ + app: App; + parent: Component; + model: Model; + fixture: HTMLElement; + env: SpreadsheetChildEnv; +}> { + const model = optionalArgs.model || optionalArgs?.env?.model || new Model(); + const env = makeTestEnv({ ...optionalArgs.env, model: model }); + const props = optionalArgs.props || ({} as Props); + const app = new App(component, { props, env, test: true }); + app.addTemplates(OWL_TEMPLATES); + const fixture = optionalArgs?.fixture || makeTestFixture(); + const parent = await app.mount(fixture); + + registerCleanup(() => { + app.destroy(); + fixture.remove(); + }); + + return { app, parent, model, fixture, env: parent.env }; +} + // Requires to be called wit jest realTimers export async function mountSpreadsheet( - fixture: HTMLElement, props: SpreadsheetProps = { model: new Model() }, - env: Partial = {} -): Promise<{ app: App; parent: Spreadsheet; model: Model }> { - const mockEnv = makeTestEnv({ ...env, model: props.model }); - const app = new App(Spreadsheet, { props, env: mockEnv, test: true }); - app.addTemplates(OWL_TEMPLATES); - const parent = (await app.mount(fixture)) as Spreadsheet; + partialEnv: Partial = {} +): Promise<{ + app: App; + parent: Spreadsheet; + model: Model; + fixture: HTMLElement; + env: SpreadsheetChildEnv; +}> { + const { app, parent, model, fixture, env } = await mountComponent(Spreadsheet, { + props, + env: partialEnv, + model: props.model, + }); + /** * The following nextTick is necessary to ensure that a re-render is correctly * done after the resize of the sheet view. */ await nextTick(); - return { app, parent, model: parent.props.model }; + return { app, parent: parent as Spreadsheet, model, fixture, env }; } type GridDescr = { [xc: string]: string | undefined };