diff --git a/web/static/src/ts/env.ts b/web/static/src/ts/env.ts index bd81297c..a4012bed 100644 --- a/web/static/src/ts/env.ts +++ b/web/static/src/ts/env.ts @@ -1,7 +1,7 @@ import { WEnv } from "./core/component"; import { QWeb } from "./core/qweb_vdom"; import { idGenerator } from "./core/utils"; -import { Store, Services, RPC } from "./store"; +import { INotification, RPC, Services, Store } from "./store"; //------------------------------------------------------------------------------ // Types @@ -12,6 +12,8 @@ export interface Env extends WEnv { // helpers dispatch(action: string, param?: any): void; + addNotification(notif: Partial): number; + closeNotification(id: number); rpc: RPC; // configuration @@ -33,6 +35,8 @@ export function makeEnv(store: Store, templates: string): Env { getID: idGenerator(), services: store.services, dispatch: store.dispatch.bind(store), + addNotification: store.addNotification.bind(store), + closeNotification: store.closeNotification.bind(store), rpc: store.rpc.bind(store), debug: false, isMobile: window.innerWidth <= 768 diff --git a/web/static/src/ts/loaders.ts b/web/static/src/ts/loaders.ts index 4e3e7f82..696808a2 100644 --- a/web/static/src/ts/loaders.ts +++ b/web/static/src/ts/loaders.ts @@ -1,5 +1,5 @@ import { findInTree } from "./core/utils"; -import { MenuItem, MenuInfo } from "./store"; +import { MenuInfo, MenuItem } from "./store"; //------------------------------------------------------------------------------ // Templates diff --git a/web/static/src/ts/store.ts b/web/static/src/ts/store.ts index 784d15df..e555ede6 100644 --- a/web/static/src/ts/store.ts +++ b/web/static/src/ts/store.ts @@ -152,10 +152,10 @@ export class Store extends EventBus { this.updateState({ inHome: !this.state.inHome }); break; case "add_notification": - this.add(params); + this.addNotification(params); break; case "close_notification": - this.close(params); + this.closeNotification(params); break; } } @@ -167,7 +167,7 @@ export class Store extends EventBus { nextID = 1; - add(notif: Partial): number { + addNotification(notif: Partial): number { const id = this.nextID++; const defaultVals = { title: "", @@ -178,11 +178,11 @@ export class Store extends EventBus { const notification = Object.assign(defaultVals, notif, { id }); this.trigger("notification_added", notification); if (!notification.sticky) { - setTimeout(() => this.close(id), 2500); + setTimeout(() => this.closeNotification(id), 2500); } return id; } - close(id: number) { + closeNotification(id: number) { this.trigger("notification_closed", id); } diff --git a/web/static/src/ts/widgets/Discuss.ts b/web/static/src/ts/widgets/Discuss.ts index 60408c41..7ef07bfa 100644 --- a/web/static/src/ts/widgets/Discuss.ts +++ b/web/static/src/ts/widgets/Discuss.ts @@ -46,7 +46,7 @@ export class Discuss extends Widget<{}, State> { addNotif(sticky: boolean) { const text = (this.refs.textinput).value; const message = `It is now ${new Date().toLocaleTimeString()}.
Msg: ${text}`; - this.env.dispatch("add_notification", { + this.env.addNotification({ title: "hey", message: message, sticky diff --git a/web/static/src/ts/widgets/notification.ts b/web/static/src/ts/widgets/notification.ts index b9bc8560..cd3a11af 100644 --- a/web/static/src/ts/widgets/notification.ts +++ b/web/static/src/ts/widgets/notification.ts @@ -7,6 +7,6 @@ export class Notification extends Widget { close(ev: MouseEvent) { // we do not want the url to change ev.preventDefault(); - this.env.dispatch("close_notification", this.props.id); + this.env.closeNotification(this.props.id); } } diff --git a/web/static/tests/core/component.test.ts b/web/static/tests/core/component.test.ts index 19128de1..0b2a510a 100644 --- a/web/static/tests/core/component.test.ts +++ b/web/static/tests/core/component.test.ts @@ -802,7 +802,6 @@ describe("random stuff/miscellaneous", () => { } const widget = new Test(env); await widget.mount(fixture); - // console.log(children(widget)[0].__widget__) expect(fixture.innerHTML).toBe("
txttxt
"); }); diff --git a/web/static/tests/core/notifications.test.ts b/web/static/tests/core/notifications.test.ts deleted file mode 100644 index 233878bd..00000000 --- a/web/static/tests/core/notifications.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NotificationManager } from "../../src/ts/store/notifications"; - -test("can subscribe and add notification", () => { - let n = 0; - const notifications = new NotificationManager(); - notifications.on("notification_added", null, () => n++); - notifications.on("notification_closed", null, () => n--); - expect(n).toBe(0); - const id = notifications.add({ title: "test", message: "message" }); - expect(n).toBe(1); - expect(id).toBeDefined(); -}); - -test("can close a notification", () => { - let n = 0; - const notifications = new NotificationManager(); - notifications.on("notification_added", null, () => n++); - notifications.on("notification_closed", null, () => n--); - - const id = notifications.add({ title: "test", message: "message" }); - expect(n).toBe(1); - - notifications.close(id); - expect(n).toBe(0); -}); - -test("notifications closes themselves after a while", () => { - jest.useFakeTimers(); - let n = 0; - const notifications = new NotificationManager(); - notifications.on("notification_added", null, () => n++); - notifications.on("notification_closed", null, () => n--); - - notifications.add({ title: "test", message: "message" }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(n).toBe(1); - jest.runAllTimers(); - expect(n).toBe(0); -}); - -test("sticky notifications do not close themselves after a while", () => { - jest.useFakeTimers(); - let n = 0; - const notifications = new NotificationManager(); - notifications.on("notification_added", null, () => n++); - notifications.on("notification_closed", null, () => n--); - - notifications.add({ title: "test", message: "message", sticky: true }); - - expect(setTimeout).toHaveBeenCalledTimes(0); - expect(n).toBe(1); - jest.runAllTimers(); - expect(n).toBe(1); -}); diff --git a/web/static/tests/helpers.ts b/web/static/tests/helpers.ts index ee3c96b7..6be9228d 100644 --- a/web/static/tests/helpers.ts +++ b/web/static/tests/helpers.ts @@ -1,15 +1,12 @@ import { readFile } from "fs"; import { WEnv } from "../src/ts/core/component"; import { Callback } from "../src/ts/core/event_bus"; -import { NotificationManager } from "../src/ts/store/notifications"; import { QWeb } from "../src/ts/core/qweb_vdom"; -import { IRouter, Query, RouterEvent } from "../src/ts/store/router"; import { idGenerator } from "../src/ts/core/utils"; -import { getMenuInfo, MenuInfo } from "../src/ts/loaders/menus"; +import { getMenuInfo } from "../src/ts/loaders"; import { actionRegistry } from "../src/ts/registries"; -import { ActionManager } from "../src/ts/store/action_manager"; -import { Ajax } from "../src/ts/store/ajax"; -import { Env } from "../src/ts/env"; +import { IRouter, Query, RouterEvent } from "../src/ts/services/router"; +import { MenuInfo, Services, Store } from "../src/ts/store"; export function makeTestFixture() { let fixture = document.createElement("div"); @@ -24,37 +21,29 @@ export function makeTestWEnv(): WEnv { }; } -export interface MockEnv extends Env { - router: MockRouter; -} - -export function makeTestEnv(): MockEnv { - const ajax = new MockAjax(mockFetch); - const actionManager = new ActionManager(actionRegistry, ajax); - const router = new MockRouter(); - const notifications = new NotificationManager(); - let { qweb, getID } = makeTestWEnv(); - return { - qweb, - getID, - actionRegistry, - ajax, - actionManager, - notifications, - router, - rpc: ajax.rpc, - debug: false, - isMobile: false - }; +export function makeTestStore(services: Partial = {}): Store { + const fullservices: Services = Object.assign( + { + rpc: mockFetch, + router: new MockRouter() + }, + services + ); + const menuInfo = makeDemoMenuInfo(); + const store = new Store(fullservices, menuInfo, actionRegistry); + return store; } function mockFetch(route: string, params: any): Promise { return Promise.resolve(true); } -class MockAjax extends Ajax {} -class MockRouter implements IRouter { - currentQuery: Query = {}; +export class MockRouter implements IRouter { + currentQuery: Query; + + constructor(query: Query = {}) { + this.currentQuery = query; + } navigate(query: Query) { this.currentQuery = query; @@ -67,10 +56,6 @@ class MockRouter implements IRouter { formatURL(path: string, query: Query): string { return ""; } - - setQuery(query: Query) { - this.currentQuery = query; - } } export function normalize(str: string): string { diff --git a/web/static/tests/store.test.ts b/web/static/tests/store.test.ts new file mode 100644 index 00000000..eefa7b1d --- /dev/null +++ b/web/static/tests/store.test.ts @@ -0,0 +1,93 @@ +import { makeTestStore, nextMicroTick } from "./helpers"; + +//------------------------------------------------------------------------------ +// Setup and helpers +//------------------------------------------------------------------------------ + +function mockFetch(route: string, params: any): Promise { + return Promise.resolve(`${route}`); +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("rpc", () => { + test("properly translate query in route", async () => { + const store = makeTestStore({ rpc: mockFetch }); + const result = await store.rpc({ model: "test", method: "hey" }); + expect(result).toBe("/web/dataset/call_kw/test/hey"); + }); + + test("trigger proper events", async () => { + const store = makeTestStore({ rpc: mockFetch }); + const events: string[] = []; + store.on("rpc_status", null, s => { + events.push(s); + }); + expect(events).toEqual([]); + store.rpc({ model: "test", method: "hey" }); + expect(events).toEqual(["loading"]); + await nextMicroTick(); + expect(events).toEqual(["loading", "notloading"]); + }); +}); + +describe("notifications", () => { + test("can subscribe and add notification", () => { + let n = 0; + const store = makeTestStore(); + store.on("notification_added", null, () => n++); + store.on("notification_closed", null, () => n--); + expect(n).toBe(0); + const id = store.addNotification({ + title: "test", + message: "message" + }); + expect(n).toBe(1); + expect(id).toBeDefined(); + }); +}); + +test("can close a notification", () => { + let n = 0; + const store = makeTestStore(); + store.on("notification_added", null, () => n++); + store.on("notification_closed", null, () => n--); + + const id = store.addNotification({ title: "test", message: "message" }); + expect(n).toBe(1); + + store.closeNotification(id); + expect(n).toBe(0); +}); + +test("notifications closes themselves after a while", () => { + jest.useFakeTimers(); + let n = 0; + const store = makeTestStore(); + store.on("notification_added", null, () => n++); + store.on("notification_closed", null, () => n--); + + store.addNotification({ title: "test", message: "message" }); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(n).toBe(1); + jest.runAllTimers(); + expect(n).toBe(0); +}); + +test("sticky notifications do not close themselves after a while", () => { + jest.useFakeTimers(); + let n = 0; + const store = makeTestStore(); + store.on("notification_added", null, () => n++); + store.on("notification_closed", null, () => n--); + + store.addNotification({ title: "test", message: "message", sticky: true }); + + expect(setTimeout).toHaveBeenCalledTimes(0); + expect(n).toBe(1); + jest.runAllTimers(); + expect(n).toBe(1); +}); diff --git a/web/static/tests/store/ajax.test.ts b/web/static/tests/store/ajax.test.ts deleted file mode 100644 index bbfb9b84..00000000 --- a/web/static/tests/store/ajax.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Ajax } from "../../src/ts/store/ajax"; -import { nextMicroTick } from "../helpers"; - -//------------------------------------------------------------------------------ -// Setup and helpers -//------------------------------------------------------------------------------ - -function mockFetch(route: string, params: any): Promise { - return Promise.resolve(`${route}`); -} - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("parameters conversion", () => { - test("properly translate query in route", async () => { - const ajax = new Ajax(mockFetch); - const result = await ajax.rpc({ model: "test", method: "hey" }); - expect(result).toBe("/web/dataset/call_kw/test/hey"); - }); -}); - -describe("event and status", () => { - test("trigger proper events", async () => { - const ajax = new Ajax(mockFetch); - const events: string[] = []; - ajax.on("rpc_status", null, s => { - events.push(s); - }); - expect(events).toEqual([]); - ajax.rpc({ model: "test", method: "hey" }); - expect(events).toEqual(["loading"]); - await nextMicroTick(); - expect(events).toEqual(["loading", "notloading"]); - }); -}); diff --git a/web/static/tests/widgets/__snapshots__/root.test.ts.snap b/web/static/tests/widgets/__snapshots__/root.test.ts.snap index a62ce9f9..4fcb1719 100644 --- a/web/static/tests/widgets/__snapshots__/root.test.ts.snap +++ b/web/static/tests/widgets/__snapshots__/root.test.ts.snap @@ -100,10 +100,12 @@ exports[`if url has action_id, will render action and navigate to proper menu_id -
+
+ CRM!!!! +
-
Loading
+
Loading
" `; diff --git a/web/static/tests/widgets/action_container.test.ts b/web/static/tests/widgets/action_container.test.ts index f98c9170..e3f22c80 100644 --- a/web/static/tests/widgets/action_container.test.ts +++ b/web/static/tests/widgets/action_container.test.ts @@ -1,4 +1,5 @@ -import { ActionStack } from "../../src/ts/store/action_manager"; +import { Env, makeEnv } from "../../src/ts/env"; +import { ActionStack, Store } from "../../src/ts/store"; import { ActionContainer, Props } from "../../src/ts/widgets/action_container"; import { Widget } from "../../src/ts/widgets/widget"; import * as helpers from "../helpers"; @@ -8,7 +9,8 @@ import * as helpers from "../helpers"; //------------------------------------------------------------------------------ let fixture: HTMLElement; -let env: ReturnType; +let store: Store; +let env: Env; let props: Props; let templates: string; @@ -18,8 +20,8 @@ beforeAll(async () => { beforeEach(() => { fixture = helpers.makeTestFixture(); - env = helpers.makeTestEnv(); - env.qweb.loadTemplates(templates); + store = helpers.makeTestStore(); + env = makeEnv(store, templates); props = { stack: [] }; }); diff --git a/web/static/tests/widgets/home_menu.test.ts b/web/static/tests/widgets/home_menu.test.ts index c320fb7f..420b3177 100644 --- a/web/static/tests/widgets/home_menu.test.ts +++ b/web/static/tests/widgets/home_menu.test.ts @@ -1,3 +1,5 @@ +import { Env, makeEnv } from "../../src/ts/env"; +import { Store } from "../../src/ts/store"; import { HomeMenu, Props } from "../../src/ts/widgets/home_menu"; import * as helpers from "../helpers"; @@ -6,7 +8,8 @@ import * as helpers from "../helpers"; //------------------------------------------------------------------------------ let fixture: HTMLElement; -let env: ReturnType; +let store: Store; +let env: Env; let props: Props; let templates: string; @@ -16,8 +19,8 @@ beforeAll(async () => { beforeEach(() => { fixture = helpers.makeTestFixture(); - env = helpers.makeTestEnv(); - env.qweb.loadTemplates(templates); + store = helpers.makeTestStore(); + env = makeEnv(store, templates); props = { menuInfo: helpers.makeDemoMenuInfo() }; }); diff --git a/web/static/tests/widgets/navbar.test.ts b/web/static/tests/widgets/navbar.test.ts index 8e742805..8f499aa2 100644 --- a/web/static/tests/widgets/navbar.test.ts +++ b/web/static/tests/widgets/navbar.test.ts @@ -1,13 +1,15 @@ +import { Env, makeEnv } from "../../src/ts/env"; +import { MenuInfo, Store } from "../../src/ts/store"; import { Navbar, Props } from "../../src/ts/widgets/navbar"; import * as helpers from "../helpers"; -import { MenuInfo } from "../../src/ts/loaders/menus"; //------------------------------------------------------------------------------ // Setup and helpers //------------------------------------------------------------------------------ let fixture: HTMLElement; -let env: ReturnType; +let store: Store; +let env: Env; let props: Props; let menuInfo: MenuInfo; let templates: string; @@ -18,8 +20,8 @@ beforeAll(async () => { beforeEach(() => { fixture = helpers.makeTestFixture(); - env = helpers.makeTestEnv(); - env.qweb.loadTemplates(templates); + store = helpers.makeTestStore(); + env = makeEnv(store, templates); props = { inHome: false, app: null }; menuInfo = helpers.makeDemoMenuInfo(); }); diff --git a/web/static/tests/widgets/notification.test.ts b/web/static/tests/widgets/notification.test.ts index c57dc259..c3639a35 100644 --- a/web/static/tests/widgets/notification.test.ts +++ b/web/static/tests/widgets/notification.test.ts @@ -1,23 +1,26 @@ -import { INotification } from "../../src/ts/store/notifications"; +import { Env, makeEnv } from "../../src/ts/env"; +import { INotification, Store } from "../../src/ts/store"; import { Notification } from "../../src/ts/widgets/notification"; -import { makeTestEnv, makeTestFixture, loadTemplates } from "../helpers"; +import * as helpers from "../helpers"; //------------------------------------------------------------------------------ // Setup and helpers //------------------------------------------------------------------------------ let fixture: HTMLElement; -let env: ReturnType; +let store: Store; +let env: Env; let templates: string; beforeAll(async () => { - templates = await loadTemplates(); + templates = await helpers.loadTemplates(); }); beforeEach(() => { - fixture = makeTestFixture(); - env = makeTestEnv(); - env.qweb.loadTemplates(templates); + fixture = helpers.makeTestFixture(); + fixture = helpers.makeTestFixture(); + store = helpers.makeTestStore(); + env = makeEnv(store, templates); }); afterEach(() => { @@ -49,13 +52,13 @@ test("can be rendered", async () => { test("can be closed by clicking on it (if sticky)", async () => { let n = 0; let notif; - env.notifications.on("notification_added", null, _notif => { + store.on("notification_added", null, _notif => { n++; notif = _notif; }); - env.notifications.on("notification_closed", null, () => n--); + store.on("notification_closed", null, () => n--); - env.notifications.add({ + env.addNotification({ title: "title", message: "message", sticky: true diff --git a/web/static/tests/widgets/root.test.ts b/web/static/tests/widgets/root.test.ts index df83833f..e4d3e172 100644 --- a/web/static/tests/widgets/root.test.ts +++ b/web/static/tests/widgets/root.test.ts @@ -1,4 +1,6 @@ -import { Root, Props } from "../../src/ts/widgets/root"; +import { Env, makeEnv } from "../../src/ts/env"; +import { Store } from "../../src/ts/store"; +import { Root } from "../../src/ts/widgets/root"; import * as helpers from "../helpers"; //------------------------------------------------------------------------------ @@ -6,8 +8,8 @@ import * as helpers from "../helpers"; //------------------------------------------------------------------------------ let fixture: HTMLElement; -let env: ReturnType; -let props: Props; +let store: Store; +let env: Env; let templates: string; beforeAll(async () => { @@ -16,9 +18,9 @@ beforeAll(async () => { beforeEach(() => { fixture = helpers.makeTestFixture(); - env = helpers.makeTestEnv(); - env.qweb.loadTemplates(templates); - props = { menuInfo: helpers.makeDemoMenuInfo() }; + store = helpers.makeTestStore(); + env = makeEnv(store, templates); + // props = { menuInfo: helpers.makeDemoMenuInfo() }; }); afterEach(() => { @@ -30,29 +32,38 @@ afterEach(() => { //------------------------------------------------------------------------------ test("can be rendered (in home menu)", async () => { - const root = new Root(env, props); + const root = new Root(env, store); await root.mount(fixture); expect(fixture.innerHTML).toMatchSnapshot(); }); test("if url has action_id, will render action and navigate to proper menu_id", async () => { - env.router.setQuery({ action_id: "595" }); - const root = new Root(env, props); + const router = new helpers.MockRouter({ action_id: "595" }); + store = helpers.makeTestStore({ router }); + env = makeEnv(store, templates); + + const root = new Root(env, store); await root.mount(fixture); + expect(env.services.router.getQuery()).toEqual({ + action_id: "595", + menu_id: "409" + }); expect(fixture.innerHTML).toMatchSnapshot(); // we check here that the url was changed to set app id as menu_id - expect(env.router.currentQuery).toEqual({ action_id: "595", menu_id: "409" }); }); test("start with no action => clicks on client action => discuss is rendered", async () => { - const root = new Root(env, props); + const root = new Root(env, store); await root.mount(fixture); - expect(env.router.currentQuery).toEqual({}); + expect(env.services.router.getQuery()).toEqual({}); // discuss menu item await (document.querySelector('[data-menu="96"]')).click(); await helpers.nextTick(); expect(fixture.innerHTML).toMatchSnapshot(); - expect(env.router.currentQuery).toEqual({ action_id: "131", menu_id: "96" }); + expect(env.services.router.getQuery()).toEqual({ + action_id: "131", + menu_id: "96" + }); });