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 };