diff --git a/doc/learning/testing_components.md b/doc/learning/testing_components.md index 6e4cb392..61c358b5 100644 --- a/doc/learning/testing_components.md +++ b/doc/learning/testing_components.md @@ -1,10 +1,9 @@ -# 🦉 Testing and Debugging Owl components 🦉 +# 🦉 Testing Owl components 🦉 ## Content - [Overview](#overview) - [Unit Tests](#unit-tests) -- [Debugging](#debugging) ## Overview @@ -131,81 +130,3 @@ function afterUpdates() { }); } ``` - -## Debugging - -Non trivial applications become quickly more difficult to understand. It is then -useful to have a solid understanding of what is going on. To help with that, -the following code can simply be copy/pasted in an application. Once it is -executed, it will log a lot of information on each component main hooks. - -```js -let current; -Object.defineProperty(owl.Component, "current", { - get() { - return current; - }, - set(comp) { - current = comp; - const name = comp.constructor.name; - let __owl__; - Object.defineProperty(current, "__owl__", { - get() { - return __owl__; - }, - set(val) { - __owl__ = val; - debugComponent(comp, name, __owl__.id); - } - }); - } -}); - -function toStr(props) { - let str = JSON.stringify(props || {}); - if (str.length > 200) { - str = str.slice(0, 200) + "..."; - } - return str; -} - -function debugComponent(component, name, id) { - console.log(`[DEBUG] constructor ${name}, props=${toStr(component.props)}`); - owl.hooks.onWillStart(() => { - console.log(`[DEBUG] willStart: '${name}'`); - }); - owl.hooks.onMounted(() => { - console.log(`[DEBUG] mounted: '${name}'`); - }); - owl.hooks.onWillUpdateProps(nextProps => { - console.log(`[DEBUG] willUpdateProps: '${name} nextprops=${toStr(nextProps)}`); - }); - owl.hooks.onWillPatch(() => { - console.log(`[DEBUG] willPatch: '${name}'`); - }); - owl.hooks.onPatched(() => { - console.log(`[DEBUG] patched: '${name}'`); - }); - owl.hooks.onWillUnmount(() => { - console.log(`[DEBUG] willUnmount: '${name}'`); - }); - const __render = component.__render.bind(component); - component.__render = function(...args) { - console.log(`[DEBUG] rendering template: '${name}'`); - __render(...args); - }; - const render = component.render.bind(component); - component.render = function(...args) { - console.log(`[DEBUG] render: '${name}'`); - return render(...args); - }; - const mount = component.mount.bind(component); - component.mount = function(...args) { - console.log(`[DEBUG] mount: '${name}'`); - return mount(...args); - }; -} -``` - -Note that it is certainly useful to run this code at some point in an application, -just to get a feel of what each user action implies, for the framework. diff --git a/doc/readme.md b/doc/readme.md index cbf253d9..5fdc87f1 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -25,7 +25,7 @@ - [Quick Start: create an (almost) empty Owl application](learning/quick_start.md) - [Tutorial: create a TodoList application](learning/tutorial_todoapp.md) -- [Testing and Debugging Owl components](learning/testing_components.md) +- [Testing Owl components](learning/testing_components.md) ## Miscellaneous diff --git a/doc/tooling.md b/doc/tooling.md index 19b1f97d..57bd37d8 100644 --- a/doc/tooling.md +++ b/doc/tooling.md @@ -6,6 +6,7 @@ - [Playground](#playground) - [Benchmarks](#benchmarks) - [Single File Component](#single-file-component) +- [Debugging Script](#debugging-script) ## Overview @@ -78,3 +79,29 @@ Note that the above example has an inline xml comment, just after the `xml` call This is useful for some editor plugins, such as the VS Code addon `Comment tagged template`, which, if installed, add syntax highlighting to the content of the template string. + +## Debugging Script + +## Debugging + +Non trivial applications become quickly more difficult to understand. It is then +useful to have a solid understanding of what is going on. To help with that, +logging useful information is extremely valuable. There is a [javascript file](../tools/debug.js) which can be evaluated in an application. + +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 = { + // 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, +just to get a feel of what each user action implies, for the framework. diff --git a/tests/doc_link_checker.test.ts b/tests/doc_link_checker.test.ts index 40fa3609..a11133ff 100644 --- a/tests/doc_link_checker.test.ts +++ b/tests/doc_link_checker.test.ts @@ -85,7 +85,7 @@ function getFiles(path: string[] = []): FileData[] { return Array.prototype.concat(...files); } -const LOCAL_FILES = ["LICENSE"]; +const LOCAL_FILES = ["LICENSE", "tools/debug.js"]; export function isLinkValid(link: MarkDownLink, current: FileData, files: FileData[]): boolean { if (link.link.startsWith("http")) { // no check on external links diff --git a/tools/debug.js b/tools/debug.js new file mode 100644 index 00000000..1dea15fa --- /dev/null +++ b/tools/debug.js @@ -0,0 +1,137 @@ +/** + * Debug Script + * + * This code is intended to be evaluated in an environment where owl is available, + * 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 +}; +{ + let prefix = "[OWL_DEBUG]"; + let current; + Object.defineProperty(owl.Component, "current", { + get() { + return current; + }, + set(comp) { + current = comp; + const name = comp.constructor.name; + if (debugSetup.componentBlackList && debugSetup.componentBlackList.test(name)) { + return; + } + if (debugSetup.componentWhiteList && !debugSetup.componentWhiteList.test(name)) { + return; + } + let __owl__; + Object.defineProperty(current, "__owl__", { + get() { + return __owl__; + }, + set(val) { + __owl__ = val; + debugComponent(comp, name, __owl__.id); + } + }); + } + }); + + function toStr(obj) { + let str = JSON.stringify(obj || {}); + if (str.length > 200) { + str = str.slice(0, 200) + "..."; + } + return str; + } + + function debugComponent(component, name, id) { + let fullName = `${name}`; + let shouldDebug = method => { + if (debugSetup.methodBlackList && debugSetup.methodBlackList.includes(method)) { + return false; + } + if (debugSetup.methodWhiteList && !debugSetup.methodWhiteList.includes(method)) { + return false; + } + return true; + }; + if (shouldDebug("constructor")) { + console.log(`${prefix} ${fullName} constructor, props=${toStr(component.props)}`); + } + if (shouldDebug("willStart")) { + owl.hooks.onWillStart(() => { + console.log(`${prefix} ${fullName} willStart`); + }); + } + if (shouldDebug("mounted")) { + owl.hooks.onMounted(() => { + console.log(`${prefix} ${fullName} mounted`); + }); + } + if (shouldDebug("willUpdateProps")) { + owl.hooks.onWillUpdateProps(nextProps => { + console.log(`${prefix} ${fullName} willUpdateProps, nextprops=${toStr(nextProps)}`); + }); + } + if (shouldDebug("willPatch")) { + owl.hooks.onWillPatch(() => { + console.log(`${prefix} ${fullName} willPatch`); + }); + } + if (shouldDebug("patched")) { + owl.hooks.onPatched(() => { + console.log(`${prefix} ${fullName} patched`); + }); + } + if (shouldDebug("willUnmount")) { + owl.hooks.onWillUnmount(() => { + console.log(`${prefix} ${fullName} willUnmount`); + }); + } + const __render = component.__render.bind(component); + component.__render = function(...args) { + console.log(`${prefix} ${fullName} rendering template`); + __render(...args); + }; + const render = component.render.bind(component); + component.render = function(...args) { + console.log(`${prefix} ${fullName} render`); + return render(...args); + }; + const mount = component.mount.bind(component); + component.mount = function(...args) { + console.log(`${prefix} ${fullName} mount`); + return mount(...args); + }; + } + + 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 (debugSetup.logStore) { + let dispatch = owl.Store.prototype.dispatch; + owl.Store.prototype.dispatch = function(action, ...payload) { + console.log(`${prefix} store: action '${action}' dispatched. Payload: '${toStr(payload)}'`); + return dispatch.call(this, action, ...payload); + }; + } +}