diff --git a/web/static/src/ts/core/component.ts b/web/static/src/ts/core/component.ts index 00ed275f..bf408f9e 100644 --- a/web/static/src/ts/core/component.ts +++ b/web/static/src/ts/core/component.ts @@ -286,9 +286,8 @@ export class PureComponent extends Component< async updateState(nextState: Partial) { for (let k in nextState) { if (nextState[k] !== this.state[k]) { - return; + return super.updateState(nextState); } } - return super.updateState(nextState); } } diff --git a/web/static/src/ts/services/action_manager.ts b/web/static/src/ts/services/action_manager.ts index ee64e96a..77445a61 100644 --- a/web/static/src/ts/services/action_manager.ts +++ b/web/static/src/ts/services/action_manager.ts @@ -59,7 +59,6 @@ export class ActionManager extends EventBus implements IActionManager { } doAction(request: ActionRequest) { - console.log("doaction", request); if (typeof request === "number") { // this is an action ID let name = request === 131 ? "discuss" : "crm"; diff --git a/web/static/src/ts/widgets/action_container.ts b/web/static/src/ts/widgets/action_container.ts index a898e9de..9ddba23b 100644 --- a/web/static/src/ts/widgets/action_container.ts +++ b/web/static/src/ts/widgets/action_container.ts @@ -17,6 +17,17 @@ export class ActionContainer extends Widget { template = "web.action_container"; currentWidget: any; + willStart() { + return this.setContentWidget(); + } + + mounted() { + if (this.currentWidget && this.currentWidget.el) { + this.el!.appendChild(this.currentWidget.el); + this.currentWidget.__mount(); + } + } + shouldUpdate(nextProps: Props) { if (nextProps.stack !== this.props.stack) { this.props = nextProps; @@ -30,7 +41,7 @@ export class ActionContainer extends Widget { if (info && info.type === "client") { const Widget = info.Widget; let widget = new Widget(this, {}); - await widget.mount(this.el!); + await widget.mount(this.el || document.createElement("div")); if (this.currentWidget) { this.currentWidget.destroy(); } diff --git a/web/static/tests/core/component.test.ts b/web/static/tests/core/component.test.ts index f7338da5..a9b06a45 100644 --- a/web/static/tests/core/component.test.ts +++ b/web/static/tests/core/component.test.ts @@ -1,5 +1,10 @@ import { Component, WEnv } from "../../src/ts/core/component"; -import { makeTestFixture, makeTestWEnv, normalize } from "../helpers"; +import { + makeTestFixture, + makeTestWEnv, + nextMicroTick, + normalize +} from "../helpers"; //------------------------------------------------------------------------------ // Setup and helpers @@ -29,10 +34,6 @@ afterEach(() => { fixture.remove(); }); -function nextTick(): Promise { - return Promise.resolve(); -} - class Widget extends Component {} function children(w: Widget): Widget[] { @@ -85,7 +86,7 @@ describe("basic widget properties", () => { expect(target.innerHTML).toBe("
0
"); const button = (counter.el).getElementsByTagName("button")[0]; await button.click(); - await nextTick(); + await nextMicroTick(); expect(target.innerHTML).toBe("
1
"); }); @@ -470,9 +471,9 @@ describe("destroy method", () => { resolve(); // Note: should we abandon await and not have to do this? // TODO: talk to vsc - await nextTick(); - await nextTick(); - await nextTick(); + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); expect(widget.__widget__.isStarted).toBe(false); expect(widget.__widget__.isMounted).toBe(false); @@ -513,7 +514,7 @@ describe("composition", () => { ); const button = fixture.getElementsByTagName("button")[0]; await button.click(); - await nextTick(); + await nextMicroTick(); expect(fixture.innerHTML).toBe( "
1
" ); @@ -548,7 +549,7 @@ describe("composition", () => { await widget.mount(fixture); const button = fixture.getElementsByTagName("button")[0]; await button.click(); - await nextTick(); + await nextMicroTick(); expect(fixture.innerHTML).toBe( "
1
" ); @@ -568,7 +569,7 @@ describe("composition", () => { await widget.mount(fixture); const button = fixture.getElementsByTagName("button")[0]; await button.click(); - await nextTick(); + await nextMicroTick(); expect(fixture.innerHTML).toBe( "
1
" ); @@ -590,7 +591,7 @@ describe("composition", () => { await widget.mount(fixture); const button = fixture.getElementsByTagName("button")[0]; await button.click(); - await nextTick(); + await nextMicroTick(); expect(fixture.innerHTML).toBe( "
1
" ); diff --git a/web/static/tests/helpers.ts b/web/static/tests/helpers.ts index eba7da34..cab0158e 100644 --- a/web/static/tests/helpers.ts +++ b/web/static/tests/helpers.ts @@ -4,11 +4,11 @@ import { WEnv } from "../src/ts/core/component"; import { Callback } from "../src/ts/core/event_bus"; import { NotificationManager } from "../src/ts/core/notifications"; import { QWeb } from "../src/ts/core/qweb_vdom"; -import { Registry } from "../src/ts/core/registry"; +import { actionRegistry } from "../src/ts/registries"; import { IRouter, Query, RouterEvent } from "../src/ts/core/router"; import { idGenerator } from "../src/ts/core/utils"; import { getMenuInfo } from "../src/ts/init"; -import { ActionEvent, IActionManager } from "../src/ts/services/action_manager"; +import { ActionManager } from "../src/ts/services/action_manager"; import { MenuInfo } from "../src/ts/widgets/root"; import { Env } from "../src/ts/widgets/widget"; @@ -31,14 +31,14 @@ export interface MockEnv extends Env { export function makeTestEnv(): MockEnv { const ajax = new MockAjax(); - const actionManager = new MockActionManager(); + const actionManager = new ActionManager(actionRegistry); const router = new MockRouter(); const notifications = new NotificationManager(); let { qweb, getID } = makeTestWEnv(); return { qweb, getID, - actionRegistry: new Registry(), + actionRegistry, ajax, actionManager, notifications, @@ -55,15 +55,6 @@ class MockAjax implements IAjax { } } -class MockActionManager implements IActionManager { - doAction(actionID: number) {} - on(event: ActionEvent, owner: any, callback: Callback) {} - activate() {} - getStack() { - return []; - } -} - class MockRouter implements IRouter { currentQuery: Query = {}; @@ -201,3 +192,11 @@ export function makeDemoMenuInfo(): MenuInfo { } ]); } + +export function nextMicroTick(): Promise { + return Promise.resolve(); +} + +export function nextTick(): Promise { + return new Promise(resolve => setTimeout(resolve)); +} diff --git a/web/static/tests/widgets/__snapshots__/action_container.test.ts.snap b/web/static/tests/widgets/__snapshots__/action_container.test.ts.snap new file mode 100644 index 00000000..134e526b --- /dev/null +++ b/web/static/tests/widgets/__snapshots__/action_container.test.ts.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`can be rendered with a non empty stack 1`] = `"
some client action
"`; + +exports[`can be rendered with an empty stack 1`] = `"
"`; + +exports[`content is updated properly when new props are given 1`] = `"
some client action
"`; diff --git a/web/static/tests/widgets/__snapshots__/root.test.ts.snap b/web/static/tests/widgets/__snapshots__/root.test.ts.snap index 714f1d8d..f65ab276 100644 --- a/web/static/tests/widgets/__snapshots__/root.test.ts.snap +++ b/web/static/tests/widgets/__snapshots__/root.test.ts.snap @@ -108,3 +108,69 @@ exports[`if url has action_id, will render action and navigate to proper menu_id " `; + +exports[`start with no action => clicks on client action => discuss is rendered 1`] = ` +"
+ + +
+ DISCUSS!! + + + + + + + +
+ + Value: 4 + +
+
+ + Value: 400 + +
+ +
+ Current Color: + red +
+ + +
+ +
+ +
+
" +`; diff --git a/web/static/tests/widgets/action_container.test.ts b/web/static/tests/widgets/action_container.test.ts new file mode 100644 index 00000000..9aa02db7 --- /dev/null +++ b/web/static/tests/widgets/action_container.test.ts @@ -0,0 +1,69 @@ +import { ActionStack } from "../../src/ts/services/action_manager"; +import { ActionContainer, Props } from "../../src/ts/widgets/action_container"; +import { Widget } from "../../src/ts/widgets/widget"; +import * as helpers from "../helpers"; + +//------------------------------------------------------------------------------ +// Setup and helpers +//------------------------------------------------------------------------------ + +let fixture: HTMLElement; +let env: ReturnType; +let props: Props; +let templates: string; + +beforeAll(async () => { + templates = await helpers.loadTemplates(); +}); + +beforeEach(() => { + fixture = helpers.makeTestFixture(); + env = helpers.makeTestEnv(); + env.qweb.loadTemplates(templates); + props = { stack: [] }; +}); + +afterEach(() => { + fixture.remove(); +}); + +class ClientAction extends Widget<{}, {}> { + inlineTemplate = "
some client action
"; +} + +const demoStack: ActionStack = [ + { + id: 33, + context: {}, + title: "some title", + target: "new", + type: "client", + name: "hey", + Widget: ClientAction + } +]; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +test("can be rendered with an empty stack", async () => { + const container = new ActionContainer(env, props); + await container.mount(fixture); + expect(fixture.innerHTML).toMatchSnapshot(); +}); + +test("can be rendered with a non empty stack", async () => { + props.stack = demoStack; + const container = new ActionContainer(env, props); + await container.mount(fixture); + expect(fixture.innerHTML).toMatchSnapshot(); +}); + +test("content is updated properly when new props are given", async () => { + const container = new ActionContainer(env, props); + await container.mount(fixture); + await container.updateProps({ stack: demoStack }); + await helpers.nextTick(); + expect(fixture.innerHTML).toMatchSnapshot(); +}); diff --git a/web/static/tests/widgets/root.test.ts b/web/static/tests/widgets/root.test.ts index d5d0d894..df83833f 100644 --- a/web/static/tests/widgets/root.test.ts +++ b/web/static/tests/widgets/root.test.ts @@ -30,16 +30,29 @@ afterEach(() => { //------------------------------------------------------------------------------ test("can be rendered (in home menu)", async () => { - const navbar = new Root(env, props); - await navbar.mount(fixture); + const root = new Root(env, props); + 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 navbar = new Root(env, props); - await navbar.mount(fixture); + const root = new Root(env, props); + await root.mount(fixture); 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); + await root.mount(fixture); + + expect(env.router.currentQuery).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" }); +});