From be8c3bbf6f14ce40d6f7a496d2297bee2ec4b79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 6 Dec 2019 12:02:06 +0100 Subject: [PATCH] [IMP] tooling: add warning if component not mounted, but rendered closes #551 --- doc/tooling.md | 2 +- tests/tooling/debug_script_3.test.ts | 46 ++++++++++++++++++++++++++++ tools/debug.js | 26 ++++++++++------ 3 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 tests/tooling/debug_script_3.test.ts diff --git a/doc/tooling.md b/doc/tooling.md index b449103f..96b96d97 100644 --- a/doc/tooling.md +++ b/doc/tooling.md @@ -91,7 +91,7 @@ logging useful information is extremely valuable. There is a [javascript file](. Once it is executed, it will log a lot of information on each component main hooks. The following code is a minified version to make it easier to copy/paste: ``` -function debugOwl(o,t){let e,n="[OWL_DEBUG]";function l(o){let t=JSON.stringify(o||{});return t.length>200&&(t=t.slice(0,200)+"..."),t}if(Object.defineProperty(o.Component,"current",{get:()=>e,set(s){e=s;const c=s.constructor.name;if(t.componentBlackList&&t.componentBlackList.test(c))return;if(t.componentWhiteList&&!t.componentWhiteList.test(c))return;let i;Object.defineProperty(e,"__owl__",{get:()=>i,set(e){!function(e,s,c){let i=`${s}`,r=o=>(!t.methodBlackList||!t.methodBlackList.includes(o))&&!(t.methodWhiteList&&!t.methodWhiteList.includes(o));r("constructor")&&console.log(`${n} ${i} constructor, props=${l(e.props)}`);r("willStart")&&o.hooks.onWillStart(()=>{console.log(`${n} ${i} willStart`)});r("mounted")&&o.hooks.onMounted(()=>{console.log(`${n} ${i} mounted`)});r("willUpdateProps")&&o.hooks.onWillUpdateProps(o=>{console.log(`${n} ${i} willUpdateProps, nextprops=${l(o)}`)});r("willPatch")&&o.hooks.onWillPatch(()=>{console.log(`${n} ${i} willPatch`)});r("patched")&&o.hooks.onPatched(()=>{console.log(`${n} ${i} patched`)});r("willUnmount")&&o.hooks.onWillUnmount(()=>{console.log(`${n} ${i} willUnmount`)});const u=e.__render.bind(e);e.__render=function(...o){console.log(`${n} ${i} rendering template`),u(...o)};const d=e.render.bind(e);e.render=function(...o){return console.log(`${n} ${i} render`),d(...o)};const h=e.mount.bind(e);e.mount=function(...o){return console.log(`${n} ${i} mount`),h(...o)}}(s,c,(i=e).id)}})}}),t.logScheduler){let t=o.Component.scheduler.start,e=o.Component.scheduler.stop;o.Component.scheduler.start=function(){this.isRunning||console.log(`${n} scheduler: start running tasks queue`),t.call(this)},o.Component.scheduler.stop=function(){this.isRunning&&console.log(`${n} scheduler: stop running tasks queue`),e.call(this)}}if(t.logStore){let t=o.Store.prototype.dispatch;o.Store.prototype.dispatch=function(o,...e){return console.log(`${n} store: action '${o}' dispatched. Payload: '${l(e)}'`),t.call(this,o,...e)}}} +function debugOwl(t,n){let e,o="[OWL_DEBUG]";function s(t){let n=JSON.stringify(t||{});return n.length>200&&(n=n.slice(0,200)+"..."),n}if(Object.defineProperty(t.Component,"current",{get:()=>e,set(i){e=i;const r=i.constructor.name;if(n.componentBlackList&&n.componentBlackList.test(r))return;if(n.componentWhiteList&&!n.componentWhiteList.test(r))return;let l;Object.defineProperty(e,"__owl__",{get:()=>l,set(e){!function(e,i,r){let l=`${i}`,c=t=>console.log(`${o} ${l} ${t}`),u=t=>(!n.methodBlackList||!n.methodBlackList.includes(t))&&!(n.methodWhiteList&&!n.methodWhiteList.includes(t));u("constructor")&&c(`constructor, props=${s(e.props)}`);u("willStart")&&t.hooks.onWillStart(()=>{c("willStart")});u("mounted")&&t.hooks.onMounted(()=>{c("mounted")});u("willUpdateProps")&&t.hooks.onWillUpdateProps(t=>{c(`willUpdateProps, nextprops=${s(t)}`)});u("willPatch")&&t.hooks.onWillPatch(()=>{c("willPatch")});u("patched")&&t.hooks.onPatched(()=>{c("patched")});u("willUnmount")&&t.hooks.onWillUnmount(()=>{c("willUnmount")});const d=e.__render.bind(e);e.__render=function(...t){c("rendering template"),d(...t)};const h=e.render.bind(e);e.render=function(...t){const n=e.__owl__;let o="render";return n.isMounted||n.currentFiber||(o+=" (warning: component is not mounted, this render has no effect)"),c(o),h(...t)};const p=e.mount.bind(e);e.mount=function(...t){return c("mount"),p(...t)}}(i,r,(l=e).id)}})}}),n.logScheduler){let n=t.Component.scheduler.start,e=t.Component.scheduler.stop;t.Component.scheduler.start=function(){this.isRunning||console.log(`${o} scheduler: start running tasks queue`),n.call(this)},t.Component.scheduler.stop=function(){this.isRunning&&console.log(`${o} scheduler: stop running tasks queue`),e.call(this)}}if(n.logStore){let n=t.Store.prototype.dispatch;t.Store.prototype.dispatch=function(t,...e){return console.log(`${o} store: action '${t}' dispatched. Payload: '${s(e)}'`),n.call(this,t,...e)}}} debugOwl(owl, { // componentBlackList: /App/, // regexp // componentWhiteList: /SomeComponent/, // regexp diff --git a/tests/tooling/debug_script_3.test.ts b/tests/tooling/debug_script_3.test.ts new file mode 100644 index 00000000..0822bde8 --- /dev/null +++ b/tests/tooling/debug_script_3.test.ts @@ -0,0 +1,46 @@ +/** + * We can only make one test per file, since the debug tool modify in place + * the owl object in a way that is difficult to undo. + */ + +import { debugOwl } from "../../tools/debug"; +import * as owl from "../../src/index"; + +import { Component, Env } from "../../src/component/component"; +import { xml } from "../../src/tags"; +import { makeTestFixture, makeTestEnv } from "../helpers"; + +let fixture: HTMLElement = makeTestFixture(); +let env: Env = makeTestEnv(); +Component.env = env; + +debugOwl(owl, { logScheduler: true }); + +test("log a specific message for render method calls if component is not mounted", async () => { + const steps: string[] = []; + const log = console.log; + console.log = arg => steps.push(arg); + + class Parent extends Component { + static template = xml`
`; + state = owl.hooks.useState({value: 1}); + } + + const parent = new Parent(null, {}); + await parent.mount(fixture); + parent.unmount(); + parent.state.value = 2; + + expect(steps).toEqual([ + "[OWL_DEBUG] Parent constructor, props={}", + "[OWL_DEBUG] Parent mount", + "[OWL_DEBUG] Parent willStart", + "[OWL_DEBUG] scheduler: start running tasks queue", + "[OWL_DEBUG] Parent rendering template", + "[OWL_DEBUG] Parent mounted", + "[OWL_DEBUG] scheduler: stop running tasks queue", + "[OWL_DEBUG] Parent willUnmount", + "[OWL_DEBUG] Parent render (warning: component is not mounted, this render has no effect)" + ]); + console.log = log; +}); diff --git a/tools/debug.js b/tools/debug.js index 71c78cf0..a7ab230e 100644 --- a/tools/debug.js +++ b/tools/debug.js @@ -44,6 +44,7 @@ function debugComponent(component, name, id) { let fullName = `${name}`; + let log = str => console.log(`${prefix} ${fullName} ${str}`); let shouldDebug = method => { if (options.methodBlackList && options.methodBlackList.includes(method)) { return false; @@ -54,51 +55,56 @@ return true; }; if (shouldDebug("constructor")) { - console.log(`${prefix} ${fullName} constructor, props=${toStr(component.props)}`); + log(`constructor, props=${toStr(component.props)}`); } if (shouldDebug("willStart")) { owl.hooks.onWillStart(() => { - console.log(`${prefix} ${fullName} willStart`); + log(`willStart`); }); } if (shouldDebug("mounted")) { owl.hooks.onMounted(() => { - console.log(`${prefix} ${fullName} mounted`); + log(`mounted`); }); } if (shouldDebug("willUpdateProps")) { owl.hooks.onWillUpdateProps(nextProps => { - console.log(`${prefix} ${fullName} willUpdateProps, nextprops=${toStr(nextProps)}`); + log(`willUpdateProps, nextprops=${toStr(nextProps)}`); }); } if (shouldDebug("willPatch")) { owl.hooks.onWillPatch(() => { - console.log(`${prefix} ${fullName} willPatch`); + log(`willPatch`); }); } if (shouldDebug("patched")) { owl.hooks.onPatched(() => { - console.log(`${prefix} ${fullName} patched`); + log(`patched`); }); } if (shouldDebug("willUnmount")) { owl.hooks.onWillUnmount(() => { - console.log(`${prefix} ${fullName} willUnmount`); + log(`willUnmount`); }); } const __render = component.__render.bind(component); component.__render = function(...args) { - console.log(`${prefix} ${fullName} rendering template`); + log(`rendering template`); __render(...args); }; const render = component.render.bind(component); component.render = function(...args) { - console.log(`${prefix} ${fullName} render`); + const __owl__ = component.__owl__; + let msg = `render`; + if (!__owl__.isMounted && !__owl__.currentFiber) { + msg += ` (warning: component is not mounted, this render has no effect)`; + } + log(msg); return render(...args); }; const mount = component.mount.bind(component); component.mount = function(...args) { - console.log(`${prefix} ${fullName} mount`); + log(`mount`); return mount(...args); }; }