diff --git a/src/component/directive.ts b/src/component/directive.ts index 4b8ca11f..5dd02718 100644 --- a/src/component/directive.ts +++ b/src/component/directive.ts @@ -456,11 +456,11 @@ QWeb.addDirective({ // need to update component let patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue"; if (keepAlive) { - // if we have t-keepalive="1", the component could be unmounted, but then - // we __updateProps is called. This is ok, but we do not want to call - // the willPatch/patched hooks of the component in this case, so we - // disable the patch queue - patchQueueCode = `w${componentID}.__owl__.isMounted ? ${patchQueueCode} : []`; + // if we have t-keepalive="1", the component could be unmounted, but then + // we __updateProps is called. This is ok, but we do not want to call + // the willPatch/patched hooks of the component in this case, so we + // disable the patch queue + patchQueueCode = `w${componentID}.__owl__.isMounted ? ${patchQueueCode} : []`; } if (QWeb.dev) { ctx.addLine(`utils.validateProps(w${componentID}.constructor, props${componentID})`); diff --git a/src/core/observer.ts b/src/core/observer.ts index d18005cd..f0273069 100644 --- a/src/core/observer.ts +++ b/src/core/observer.ts @@ -25,7 +25,6 @@ export class Observer { notifyCB() {} async notifyChange() { - this.dirty = true; await Promise.resolve(); if (this.dirty) { diff --git a/src/router/Router.ts b/src/router/Router.ts index c3b0425c..e774341a 100644 --- a/src/router/Router.ts +++ b/src/router/Router.ts @@ -85,9 +85,6 @@ export class Router { this.routeIds.push(partialRoute.name); } - (this as any)._listener = () => this.matchAndApplyRules(this.currentPath()); - window.addEventListener("popstate", (this as any)._listener); - // setup link and directive env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE); QWeb.addDirective(makeDirective(env)); @@ -98,6 +95,11 @@ export class Router { //-------------------------------------------------------------------------- async start() { + (this as any)._listener = () => this._navigate(this.currentPath()); + window.addEventListener("popstate", (this as any)._listener); + if (this.mode === "hash") { + window.addEventListener("hashchange", (this as any)._listener); + } const result = await this.matchAndApplyRules(this.currentPath()); if (result.type === "match") { this.currentRoute = result.route; @@ -111,6 +113,9 @@ export class Router { async navigate(to: Destination): Promise { const path = this.destToPath(to); + return this._navigate(path); + } + async _navigate(path: string): Promise { const initialName = this.currentRouteName; const initialParams = this.currentParams; const result = await this.matchAndApplyRules(path); diff --git a/src/store/store.ts b/src/store/store.ts index 10e85817..aee91784 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -42,7 +42,7 @@ export class Store extends EventBus { debug: boolean; env: any; observer: Observer; - getters: { [name: string]: (payload?) => any }; + getters: { [name: string]: (payload?) => any }; constructor(config: StoreConfig, options: StoreOption = {}) { super(); @@ -54,13 +54,13 @@ export class Store extends EventBus { this.state = this.observer.observe(config.state || {}); this.getters = {}; if (config.getters) { - const firstArg = { - state: this.state, - getters: this.getters, - }; - for (let g in config.getters) { - this.getters[g] = config.getters[g].bind(this, firstArg); - } + const firstArg = { + state: this.state, + getters: this.getters + }; + for (let g in config.getters) { + this.getters[g] = config.getters[g].bind(this, firstArg); + } } } diff --git a/src/vdom/vdom.ts b/src/vdom/vdom.ts index 2d36e4f6..1e766ada 100644 --- a/src/vdom/vdom.ts +++ b/src/vdom/vdom.ts @@ -526,7 +526,7 @@ const htmlDomApi = { parentNode, nextSibling, tagName, - setTextContent, + setTextContent } as DOMAPI; //------------------------------------------------------------------------------ diff --git a/tests/component/props_validation.test.ts b/tests/component/props_validation.test.ts index 2c30bf37..a50801ea 100644 --- a/tests/component/props_validation.test.ts +++ b/tests/component/props_validation.test.ts @@ -280,7 +280,7 @@ describe("props validation", () => { test("props: extra props cause an error, part 2", async () => { class TestWidget extends Widget { - static props = {message: true}; + static props = { message: true }; } expect(() => { @@ -294,23 +294,22 @@ describe("props validation", () => { } expect(() => { - QWeb.utils.validateProps(TestWidget, { message: 1}); + QWeb.utils.validateProps(TestWidget, { message: 1 }); }).not.toThrow(); }); test("optional prop do not cause an error if value is undefined", async () => { class TestWidget extends Widget { - static props = {message: {type: String, optional: true}}; + static props = { message: { type: String, optional: true } }; } expect(() => { - QWeb.utils.validateProps(TestWidget, { message: undefined}); + QWeb.utils.validateProps(TestWidget, { message: undefined }); }).not.toThrow(); expect(() => { - QWeb.utils.validateProps(TestWidget, { message: null}); + QWeb.utils.validateProps(TestWidget, { message: null }); }).toThrow(); }); - }); describe("default props", () => { diff --git a/tests/core/observer.test.ts b/tests/core/observer.test.ts index 7e936845..3e7557b9 100644 --- a/tests/core/observer.test.ts +++ b/tests/core/observer.test.ts @@ -65,7 +65,7 @@ describe("observer", () => { expect(observer.revNumber(obj)).toBe(1); expect(observer.deepRevNumber(obj)).toBe(1); expect(observer.rev).toBe(1); - expect(typeof obj.date.getFullYear()).toBe('number'); + expect(typeof obj.date.getFullYear()).toBe("number"); expect(obj.date).toBe(date); obj.date = new Date(); diff --git a/tests/router/router.test.ts b/tests/router/router.test.ts index fac78e06..add153bc 100644 --- a/tests/router/router.test.ts +++ b/tests/router/router.test.ts @@ -1,5 +1,5 @@ import { Destination, RouterEnv, Route } from "../../src/router/Router"; -import { makeTestEnv } from "../helpers"; +import { makeTestEnv, nextTick } from "../helpers"; import { TestRouter } from "./TestRouter"; let env: RouterEnv; @@ -37,6 +37,21 @@ describe("router miscellaneous", () => { expect(window.location.pathname).toBe("/users/5"); expect(env.qweb.forceUpdate).toHaveBeenCalledTimes(2); }); + + test("changing url to same route but with different params should trigger update (hash mode)", async () => { + env.qweb.forceUpdate = jest.fn(); + router = new TestRouter(env, [{ name: "users", path: "/users/{{id}}" }], { mode: "hash" }); + await router.start(); + await router.navigate({ to: "users", params: { id: 3 } }); + expect(window.location.hash).toBe("#/users/3"); + expect(env.qweb.forceUpdate).toHaveBeenCalledTimes(1); + + window.location.hash = "/users/5"; + window.dispatchEvent(new Event("hashchange")); + await nextTick(); + expect(window.location.hash).toBe("#/users/5"); + expect(env.qweb.forceUpdate).toHaveBeenCalledTimes(2); + }); }); describe("routeToPath", () => { diff --git a/tests/store/connected_component.test.ts b/tests/store/connected_component.test.ts index 39c41f24..0297fc5a 100644 --- a/tests/store/connected_component.test.ts +++ b/tests/store/connected_component.test.ts @@ -616,8 +616,8 @@ describe("connecting a component to store", () => { return { flag: s.flag, someId: s.someId }; } async render(force) { - await def; - return super.render(force); + await def; + return super.render(force); } } @@ -628,7 +628,7 @@ describe("connecting a component to store", () => { } } - const state = { someId: 1, flag: true, messages: {1: "abc"}}; + const state = { someId: 1, flag: true, messages: { 1: "abc" } }; const actions = { setFlagToFalse({ state }) { state.flag = false; @@ -1040,7 +1040,7 @@ describe("connected components and default values", () => { super.off(eventType, owner); } } - const store = new TestStore({ state: {val: 1} }); + const store = new TestStore({ state: { val: 1 } }); (env).store = store; const parent = new Parent(env); @@ -1088,7 +1088,7 @@ describe("connected components and default values", () => { await app.mount(fixture); expect(fixture.innerHTML).toBe("
0
"); - const res = app.dispatch('inc'); + const res = app.dispatch("inc"); expect(res).toBe(1); await nextTick(); expect(fixture.innerHTML).toBe("
1
"); diff --git a/tests/store/store.test.ts b/tests/store/store.test.ts index 4e8ffc93..6372c100 100644 --- a/tests/store/store.test.ts +++ b/tests/store/store.test.ts @@ -249,32 +249,32 @@ describe("advanced state properties", () => { expect(store.state.a).toEqual([1, 2, 3, 53]); }); - test("can use object assign in store", async () => { - const actions = { - dosomething({ state }) { - Object.assign(state.westmalle, { a: 3, b: 4 }); - } - }; - const store = new Store({ - state: { westmalle: { a: 1, b: 2 } }, - actions - }); - store.dispatch("dosomething"); - expect(store.state.westmalle).toEqual({ a: 3, b: 4 }); + test("can use object assign in store", async () => { + const actions = { + dosomething({ state }) { + Object.assign(state.westmalle, { a: 3, b: 4 }); + } + }; + const store = new Store({ + state: { westmalle: { a: 1, b: 2 } }, + actions }); + store.dispatch("dosomething"); + expect(store.state.westmalle).toEqual({ a: 3, b: 4 }); + }); - test("aku reactive store state 1", async () => { - const actions = { - inc({ state }) { - state.counter++; - } - }; - const state = { counter: 0 }; - const store = new Store({ state, actions }); - expect(store.state.counter).toBe(0); - store.dispatch("inc", {}); - expect(store.state.counter).toBe(1); - }); + test("aku reactive store state 1", async () => { + const actions = { + inc({ state }) { + state.counter++; + } + }; + const state = { counter: 0 }; + const store = new Store({ state, actions }); + expect(store.state.counter).toBe(0); + store.dispatch("inc", {}); + expect(store.state.counter).toBe(1); + }); }); describe("updates triggered by the store", () => {