diff --git a/doc/reference/misc.md b/doc/reference/misc.md index f2a9e310..71bcbf54 100644 --- a/doc/reference/misc.md +++ b/doc/reference/misc.md @@ -33,7 +33,7 @@ The component `Portal` has no particular state, rather it is meant to be a slave and ultimately just a way for the parent to teleport a piece of its own DOM elsewhere. The `Portal`'s root node is always `` and is placed where the teleported content -*would have* been. It is this element that the [teleported events](#expected-behaviors) are re-directed on. +_would have_ been. It is this element that the [teleported events](#expected-behaviors) are re-directed on. ### Example diff --git a/doc/tooling.md b/doc/tooling.md index 57bd37d8..10e21e74 100644 --- a/doc/tooling.md +++ b/doc/tooling.md @@ -91,16 +91,15 @@ 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: ``` -let debugSetup = { +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 r;Object.defineProperty(e,"__owl__",{get:()=>r,set(e){!function(e,s,c){let r=`${s}`,i=o=>(!t.methodBlackList||!t.methodBlackList.includes(o))&&!(t.methodWhiteList&&!t.methodWhiteList.includes(o));i("constructor")&&console.log(`${n} ${r} constructor, props=${l(e.props)}`);i("willStart")&&o.hooks.onWillStart(()=>{console.log(`${n} ${r} willStart`)});i("mounted")&&o.hooks.onMounted(()=>{console.log(`${n} ${r} mounted`)});i("willUpdateProps")&&o.hooks.onWillUpdateProps(o=>{console.log(`${n} ${r} willUpdateProps, nextprops=${l(o)}`)});i("willPatch")&&o.hooks.onWillPatch(()=>{console.log(`${n} ${r} willPatch`)});i("patched")&&o.hooks.onPatched(()=>{console.log(`${n} ${r} patched`)});i("willUnmount")&&o.hooks.onWillUnmount(()=>{console.log(`${n} ${r} willUnmount`)});const u=e.__render.bind(e);e.__render=function(...o){console.log(`${n} ${r} rendering template`),u(...o)};const d=e.render.bind(e);e.render=function(...o){return console.log(`${n} ${r} render`),d(...o)};const p=e.mount.bind(e);e.mount=function(...o){return console.log(`${n} ${r} mount`),p(...o)}}(s,c,(r=e).id)}})}}),t.logScheduler){let t=o.Component.scheduler.start,e=o.Component.scheduler.stop;o.Component.scheduler.start=function(){console.log(`${n} scheduler: start running tasks queue`),t.call(this)},o.Component.scheduler.stop=function(){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)}}} +debugOwl({ // componentBlackList: /App/, // regexp // componentWhiteList: /SomeComponent/, // regexp // methodBlackList: ["mounted"], // list of method names // methodWhiteList: ["willStart"], // list of method names logScheduler: false, // display/mute scheduler logs logStore: true, // display/mute store logs -}; -{let o,t="[OWL_DEBUG]";function toStr(o){let t=JSON.stringify(o||{});return t.length>200&&(t=t.slice(0,200)+"..."),t}function debugComponent(o,e,n){let l=`${e}`,r=o=>(!debugSetup.methodBlackList||!debugSetup.methodBlackList.includes(o))&&!(debugSetup.methodWhiteList&&!debugSetup.methodWhiteList.includes(o));r("constructor")&&console.log(`${t} ${l} constructor, props=${toStr(o.props)}`),r("willStart")&&owl.hooks.onWillStart(()=>{console.log(`${t} ${l} willStart`)}),r("mounted")&&owl.hooks.onMounted(()=>{console.log(`${t} ${l} mounted`)}),r("willUpdateProps")&&owl.hooks.onWillUpdateProps(o=>{console.log(`${t} ${l} willUpdateProps, nextprops=${toStr(o)}`)}),r("willPatch")&&owl.hooks.onWillPatch(()=>{console.log(`${t} ${l} willPatch`)}),r("patched")&&owl.hooks.onPatched(()=>{console.log(`${t} ${l} patched`)}),r("willUnmount")&&owl.hooks.onWillUnmount(()=>{console.log(`${t} ${l} willUnmount`)});const s=o.__render.bind(o);o.__render=function(...o){console.log(`${t} ${l} rendering template`),s(...o)};const u=o.render.bind(o);o.render=function(...o){return console.log(`${t} ${l} render`),u(...o)};const c=o.mount.bind(o);o.mount=function(...o){return console.log(`${t} ${l} mount`),c(...o)}}if(Object.defineProperty(owl.Component,"current",{get:()=>o,set(t){o=t;const e=t.constructor.name;if(debugSetup.componentBlackList&&debugSetup.componentBlackList.test(e))return;if(debugSetup.componentWhiteList&&!debugSetup.componentWhiteList.test(e))return;let n;Object.defineProperty(o,"__owl__",{get:()=>n,set(o){debugComponent(t,e,(n=o).id)}})}}),debugSetup.logScheduler){let o;Object.defineProperty(owl.Component.scheduler,"isRunning",{get:()=>o,set(e){e?console.log(`${t} scheduler: start running tasks queue`):console.log(`${t} scheduler: stop running tasks queue`),o=e}})}if(debugSetup.logStore){let o=owl.Store.prototype.dispatch;owl.Store.prototype.dispatch=function(e,...n){return console.log(`${t} store: action '${e}' dispatched. Payload: '${toStr(n)}'`),o.call(this,e,...n)}}} - +}); ``` Note that it is certainly useful to run this code at some point in an application, diff --git a/src/component/scheduler.ts b/src/component/scheduler.ts index 5013702b..258a0c4f 100644 --- a/src/component/scheduler.ts +++ b/src/component/scheduler.ts @@ -25,6 +25,15 @@ export class Scheduler { this.requestAnimationFrame = requestAnimationFrame; } + start() { + this.isRunning = true; + this.scheduleTasks(); + } + + stop() { + this.isRunning = false; + } + addFiber(fiber): Promise { // if the fiber was remapped into a larger rendering fiber, it may not be a // root fiber. But we only want to register root fibers @@ -43,7 +52,7 @@ export class Scheduler { } }); if (!this.isRunning) { - this.scheduleTasks(); + this.start(); } }); } @@ -74,16 +83,16 @@ export class Scheduler { return true; }); this.tasks = tasks.concat(this.tasks); + if (this.tasks.length === 0) { + this.stop(); + } } scheduleTasks() { - this.isRunning = true; this.requestAnimationFrame(() => { this.flush(); - if (this.tasks.length > 0) { + if (this.isRunning) { this.scheduleTasks(); - } else { - this.isRunning = false; } }); } diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts index a0d12ecb..abc14a4b 100644 --- a/tests/misc/portal.test.ts +++ b/tests/misc/portal.test.ts @@ -740,7 +740,7 @@ describe("Portal: Events handling", () => { `; } class Parent extends Component { - static components = { Portal, Child, }; + static components = { Portal, Child }; static template = xml`
diff --git a/tests/tooling/debug_script_1.test.ts b/tests/tooling/debug_script_1.test.ts new file mode 100644 index 00000000..e9845dc9 --- /dev/null +++ b/tests/tooling/debug_script_1.test.ts @@ -0,0 +1,57 @@ +/** + * 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 { useState } from "../../src/hooks"; +import { makeTestFixture, makeTestEnv, nextTick } from "../helpers"; + +let fixture: HTMLElement = makeTestFixture(); +let env: Env = makeTestEnv(); +Component.env = env; + +debugOwl(owl, {}); + +test("can log full lifecycle", async () => { + const steps: string[] = []; + const log = console.log; + console.log = arg => steps.push(arg); + + class Child extends Component { + static template = xml`
child
`; + } + + class Parent extends Component { + static template = xml`
`; + static components = { Child }; + state = useState({ flag: false }); + } + + const parent = new Parent(null, {}); + await parent.mount(fixture); + + parent.state.flag = true; + await nextTick(); + + expect(steps).toEqual([ + "[OWL_DEBUG] Parent constructor, props={}", + "[OWL_DEBUG] Parent mount", + "[OWL_DEBUG] Parent willStart", + "[OWL_DEBUG] Parent rendering template", + "[OWL_DEBUG] Parent mounted", + "[OWL_DEBUG] Parent render", + "[OWL_DEBUG] Parent rendering template", + "[OWL_DEBUG] Child constructor, props={}", + "[OWL_DEBUG] Child willStart", + "[OWL_DEBUG] Child rendering template", + "[OWL_DEBUG] Parent willPatch", + "[OWL_DEBUG] Child mounted", + "[OWL_DEBUG] Parent patched" + ]); + console.log = log; +}); diff --git a/tests/tooling/debug_script_2.test.ts b/tests/tooling/debug_script_2.test.ts new file mode 100644 index 00000000..d69d934f --- /dev/null +++ b/tests/tooling/debug_script_2.test.ts @@ -0,0 +1,50 @@ +/** + * 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("can log scheduler start and stop", async () => { + const steps: string[] = []; + const log = console.log; + console.log = arg => steps.push(arg); + + class Child extends Component { + static template = xml`
child
`; + } + + class Parent extends Component { + static template = xml`
`; + static components = { Child }; + } + + const parent = new Parent(null, {}); + await parent.mount(fixture); + + 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] Child constructor, props={}", + "[OWL_DEBUG] Child willStart", + "[OWL_DEBUG] Child rendering template", + "[OWL_DEBUG] Child mounted", + "[OWL_DEBUG] Parent mounted", + "[OWL_DEBUG] scheduler: stop running tasks queue" + ]); + console.log = log; +}); diff --git a/tests/doc_link_checker.test.ts b/tests/tooling/doc_link_checker.test.ts similarity index 100% rename from tests/doc_link_checker.test.ts rename to tests/tooling/doc_link_checker.test.ts diff --git a/tools/debug.js b/tools/debug.js index 1dea15fa..6f4dd1b0 100644 --- a/tools/debug.js +++ b/tools/debug.js @@ -5,15 +5,7 @@ * to log lot of helpful information on how Owl components behave. */ -let debugSetup = { - // componentBlackList: /App/, // regexp - // componentWhiteList: /SomeComponent/, // regexp - // methodBlackList: ["mounted"], // list of method names - // methodWhiteList: ["willStart"], // list of method names - logScheduler: true, // display/mute scheduler logs - logStore: true // display/mute store logs -}; -{ + function debugOwl(owl, options) { let prefix = "[OWL_DEBUG]"; let current; Object.defineProperty(owl.Component, "current", { @@ -23,10 +15,10 @@ let debugSetup = { set(comp) { current = comp; const name = comp.constructor.name; - if (debugSetup.componentBlackList && debugSetup.componentBlackList.test(name)) { + if (options.componentBlackList && options.componentBlackList.test(name)) { return; } - if (debugSetup.componentWhiteList && !debugSetup.componentWhiteList.test(name)) { + if (options.componentWhiteList && !options.componentWhiteList.test(name)) { return; } let __owl__; @@ -53,10 +45,10 @@ let debugSetup = { function debugComponent(component, name, id) { let fullName = `${name}`; let shouldDebug = method => { - if (debugSetup.methodBlackList && debugSetup.methodBlackList.includes(method)) { + if (options.methodBlackList && options.methodBlackList.includes(method)) { return false; } - if (debugSetup.methodWhiteList && !debugSetup.methodWhiteList.includes(method)) { + if (options.methodWhiteList && !options.methodWhiteList.includes(method)) { return false; } return true; @@ -111,23 +103,19 @@ let debugSetup = { }; } - if (debugSetup.logScheduler) { - let isRunning; - Object.defineProperty(owl.Component.scheduler, "isRunning", { - get() { - return isRunning; - }, - set(val) { - if (val) { - console.log(`${prefix} scheduler: start running tasks queue`); - } else { - console.log(`${prefix} scheduler: stop running tasks queue`); - } - isRunning = val; - } - }); + if (options.logScheduler) { + let start = owl.Component.scheduler.start; + let stop = owl.Component.scheduler.stop; + owl.Component.scheduler.start = function () { + console.log(`${prefix} scheduler: start running tasks queue`); + start.call(this); + }; + owl.Component.scheduler.stop = function () { + console.log(`${prefix} scheduler: stop running tasks queue`); + stop.call(this); + }; } - if (debugSetup.logStore) { + if (options.logStore) { let dispatch = owl.Store.prototype.dispatch; owl.Store.prototype.dispatch = function(action, ...payload) { console.log(`${prefix} store: action '${action}' dispatched. Payload: '${toStr(payload)}'`); @@ -135,3 +123,17 @@ let debugSetup = { }; } } + + +// This debug function can then be used like this: +// +// debugOwl(owl, { +// componentBlackList: /App/, // regexp +// componentWhiteList: /SomeComponent/, // regexp +// methodBlackList: ["mounted"], // list of method names +// methodWhiteList: ["willStart"], // list of method names +// logScheduler: true, // display/mute scheduler logs +// logStore: true // display/mute store logs +// }); + +module.exports.debugOwl = debugOwl;