From e746574a1d16ef207925bc6f4435566a3be73e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 26 Nov 2020 16:45:25 +0100 Subject: [PATCH] [REF] initial prototype of owl 2 --- .github/workflows/deploy.yml | 2 +- doc/reference/qweb_engine.md | 2 + doc/reference/router.md | 2 + package.json | 3 +- rollup.config.js | 1 + src/app.ts | 45 + src/blockdom/attributes.ts | 155 + src/blockdom/block_compiler.ts | 602 +++ src/blockdom/config.ts | 16 + src/blockdom/events.ts | 32 + src/blockdom/html.ts | 87 + src/blockdom/index.ts | 44 + src/blockdom/list.ts | 240 + src/blockdom/multi.ts | 134 + src/blockdom/text.ts | 70 + src/blockdom/toggler.ts | 63 + src/browser.ts | 30 - src/component/component.ts | 790 +-- src/component/component_node.ts | 256 + src/component/directive.ts | 516 -- src/component/fiber.ts | 366 -- src/component/fibers.ts | 185 + src/component/handler.ts | 10 + src/component/props_validation.ts | 115 - src/component/scheduler.ts | 104 +- src/component/status.ts | 24 + src/component/styles.ts | 70 - src/config.ts | 42 - src/context.ts | 138 - src/core/event_bus.ts | 79 - src/core/observer.ts | 99 - src/core/owl_event.ts | 15 - src/event_bus.ts | 5 + src/hooks.ts | 182 - src/index.ts | 107 +- src/lifecycle_hooks.ts | 49 + src/misc/async_root.ts | 19 - src/misc/memo.ts | 47 + src/misc/portal.ts | 220 +- src/qweb/base_directives.ts | 395 -- src/qweb/compilation_context.ts | 229 - src/qweb/compiler.ts | 982 ++++ src/qweb/dom_helpers.ts | 74 + src/qweb/extensions.ts | 377 -- src/qweb/index.ts | 4 - ...ession_parser.ts => inline_expressions.ts} | 52 +- src/qweb/parser.ts | 798 +++ src/qweb/qweb.ts | 921 ---- src/qweb/template_helpers.ts | 129 + src/reactivity.ts | 144 + src/refs.ts | 36 + src/router/link.ts | 48 - src/router/route_component.ts | 19 - src/router/router.ts | 294 -- src/store.ts | 173 - src/tags.ts | 46 +- src/utils.ts | 103 - src/vdom/html_to_vdom.ts | 34 - src/vdom/index.ts | 9 - src/vdom/modules.ts | 247 - src/vdom/vdom.ts | 616 --- tests/__snapshots__/animations.test.ts.snap | 200 - tests/__snapshots__/store_hooks.test.ts.snap | 5 - tests/animations.test.ts | 452 -- tests/blockdom/block.test.ts | 265 + tests/blockdom/block_attributes.test.ts | 123 + tests/blockdom/block_event_handling.test.ts | 108 + tests/blockdom/block_refs.test.ts | 57 + tests/blockdom/helpers.ts | 11 + tests/blockdom/html.test.ts | 48 + tests/blockdom/list.test.ts | 458 ++ tests/blockdom/multi.test.ts | 79 + tests/blockdom/remove_hook.test.ts | 84 + tests/blockdom/text.test.ts | 69 + tests/blockdom/toggler.test.ts | 57 + .../__snapshots__/class_style.test.ts.snap | 156 - .../__snapshots__/component.test.ts.snap | 2598 ---------- .../props_validation.test.ts.snap | 39 - .../__snapshots__/slots.test.ts.snap | 758 --- tests/component/async.test.ts | 1620 ------ tests/component/class_style.test.ts | 273 - tests/component/component.test.ts | 4500 ----------------- tests/component/error_handling.test.ts | 620 --- tests/component/props_validation.test.ts | 868 ---- tests/component/styles.test.ts | 170 - tests/component/un_mounting.test.ts | 732 --- .../components/__snapshots__/app.test.ts.snap | 15 + .../__snapshots__/basics.test.ts.snap | 929 ++++ .../__snapshots__/concurrency.test.ts.snap | 1193 +++++ .../__snapshots__/error_handling.test.ts.snap | 31 + .../__snapshots__/event_handling.test.ts.snap | 78 + .../higher_order_component.test.ts.snap | 191 + .../__snapshots__/lifecycle.test.ts.snap | 641 +++ .../__snapshots__/props.test.ts.snap | 160 + .../__snapshots__/refs.test.ts.snap | 43 + .../__snapshots__/slots.test.ts.snap | 2202 ++++++++ .../__snapshots__/style_class.test.ts.snap | 395 ++ .../__snapshots__/t_call.test.ts.snap | 292 ++ .../__snapshots__/t_call_block.test.ts.snap | 16 + .../__snapshots__/t_component.test.ts.snap | 226 + .../__snapshots__/t_foreach.test.ts.snap | 244 + tests/components/app.test.ts | 28 + tests/components/basics.test.ts | 657 +++ tests/components/concurrency.test.ts | 2508 +++++++++ tests/components/env.test.ts | 51 + tests/components/error_handling.test.ts | 42 + tests/components/event_handling.test.ts | 75 + .../components/higher_order_component.test.ts | 119 + tests/components/lifecycle.test.ts | 912 ++++ tests/components/props.test.ts | 105 + tests/components/refs.test.ts | 47 + tests/{component => components}/slots.test.ts | 1376 +++-- tests/components/style_class.test.ts | 237 + tests/components/t_call.test.ts | 165 + tests/components/t_call_block.test.ts | 25 + tests/components/t_component.test.ts | 193 + tests/components/t_foreach.test.ts | 200 + tests/context.test.ts | 358 -- tests/core/event_bus.test.ts | 45 - tests/core/observer.test.ts | 449 -- tests/event_bus.test.ts | 35 + tests/helpers.ts | 268 +- tests/hooks.test.ts | 686 --- tests/misc/__snapshots__/memo.test.ts.snap | 87 + tests/misc/__snapshots__/portal.test.ts.snap | 466 ++ tests/misc/async_root.test.ts | 188 - tests/misc/memo.test.ts | 93 + tests/misc/portal.test.ts | 1020 ++-- tests/mocks/mockEventTarget.js | 42 + .../__snapshots__/attributes.test.ts.snap | 679 +++ .../qweb/__snapshots__/comments.test.ts.snap | 51 + .../__snapshots__/event_handling.test.ts.snap | 369 ++ tests/qweb/__snapshots__/misc.test.ts.snap | 324 ++ tests/qweb/__snapshots__/qweb.test.ts.snap | 4137 --------------- .../__snapshots__/qweb_memory.test.ts.snap | 37 - .../__snapshots__/qweb_t_att.test.ts.snap | 57 - .../__snapshots__/qweb_t_tag.test.ts.snap | 71 - .../simple_templates.test.ts.snap | 337 ++ tests/qweb/__snapshots__/t_call.test.ts.snap | 1031 ++++ .../__snapshots__/t_debug_log.test.ts.snap | 39 + tests/qweb/__snapshots__/t_esc.test.ts.snap | 200 + .../qweb/__snapshots__/t_foreach.test.ts.snap | 524 ++ tests/qweb/__snapshots__/t_if.test.ts.snap | 521 ++ tests/qweb/__snapshots__/t_raw.test.ts.snap | 181 + tests/qweb/__snapshots__/t_ref.test.ts.snap | 136 + tests/qweb/__snapshots__/t_set.test.ts.snap | 386 ++ .../__snapshots__/white_space.test.ts.snap | 89 + tests/qweb/attributes.test.ts | 382 ++ tests/qweb/comments.test.ts | 29 + tests/qweb/event_handling.test.ts | 304 ++ tests/qweb/inline_expressions.test.ts | 209 + tests/qweb/misc.test.ts | 262 + tests/qweb/parser.test.ts | 1129 +++++ tests/qweb/qweb.test.ts | 2267 --------- tests/qweb/qweb_expressions.test.ts | 226 - tests/qweb/qweb_memory.test.ts | 14 - tests/qweb/qweb_t_att.test.ts | 36 - tests/qweb/qweb_t_tag.test.ts | 42 - tests/qweb/simple_templates.test.ts | 125 + tests/qweb/t_call.test.ts | 448 ++ tests/qweb/t_debug_log.test.ts | 30 + tests/qweb/t_esc.test.ts | 109 + tests/qweb/t_foreach.test.ts | 237 + tests/qweb/t_if.test.ts | 223 + tests/qweb/t_raw.test.ts | 86 + tests/qweb/t_ref.test.ts | 113 + tests/qweb/t_set.test.ts | 182 + tests/qweb/validation.test.ts | 39 + tests/qweb/white_space.test.ts | 42 + tests/reactivity.test.ts | 515 ++ tests/router/__snapshots__/link.test.ts.snap | 22 - .../route_component.test.ts.snap | 46 - tests/router/link.test.ts | 91 - tests/router/route_component.test.ts | 130 - tests/router/router.test.ts | 282 -- tests/router/test_router.ts | 26 - tests/store.test.ts | 328 -- tests/store_hooks.test.ts | 1402 ----- tests/tooling/debug_script_1.test.ts | 57 - tests/tooling/debug_script_2.test.ts | 50 - tests/tooling/debug_script_3.test.ts | 50 - tests/tooling/debug_script_4.test.ts | 57 - tests/tooling/doc_link_checker.test.ts | 183 - tests/utils.test.ts | 35 - tests/vdom.test.ts | 1115 ---- tsconfig.json | 2 +- 186 files changed, 29153 insertions(+), 33120 deletions(-) create mode 100644 src/app.ts create mode 100644 src/blockdom/attributes.ts create mode 100644 src/blockdom/block_compiler.ts create mode 100644 src/blockdom/config.ts create mode 100644 src/blockdom/events.ts create mode 100644 src/blockdom/html.ts create mode 100644 src/blockdom/index.ts create mode 100644 src/blockdom/list.ts create mode 100644 src/blockdom/multi.ts create mode 100644 src/blockdom/text.ts create mode 100644 src/blockdom/toggler.ts delete mode 100644 src/browser.ts create mode 100644 src/component/component_node.ts delete mode 100644 src/component/directive.ts delete mode 100644 src/component/fiber.ts create mode 100644 src/component/fibers.ts create mode 100644 src/component/handler.ts delete mode 100644 src/component/props_validation.ts create mode 100644 src/component/status.ts delete mode 100644 src/component/styles.ts delete mode 100644 src/config.ts delete mode 100644 src/context.ts delete mode 100644 src/core/event_bus.ts delete mode 100644 src/core/observer.ts delete mode 100644 src/core/owl_event.ts create mode 100644 src/event_bus.ts delete mode 100644 src/hooks.ts create mode 100644 src/lifecycle_hooks.ts delete mode 100644 src/misc/async_root.ts create mode 100644 src/misc/memo.ts delete mode 100644 src/qweb/base_directives.ts delete mode 100644 src/qweb/compilation_context.ts create mode 100644 src/qweb/compiler.ts create mode 100644 src/qweb/dom_helpers.ts delete mode 100644 src/qweb/extensions.ts delete mode 100644 src/qweb/index.ts rename src/qweb/{expression_parser.ts => inline_expressions.ts} (87%) create mode 100644 src/qweb/parser.ts delete mode 100644 src/qweb/qweb.ts create mode 100644 src/qweb/template_helpers.ts create mode 100644 src/reactivity.ts create mode 100644 src/refs.ts delete mode 100644 src/router/link.ts delete mode 100644 src/router/route_component.ts delete mode 100644 src/router/router.ts delete mode 100644 src/store.ts delete mode 100644 src/utils.ts delete mode 100644 src/vdom/html_to_vdom.ts delete mode 100644 src/vdom/index.ts delete mode 100644 src/vdom/modules.ts delete mode 100644 src/vdom/vdom.ts delete mode 100644 tests/__snapshots__/animations.test.ts.snap delete mode 100644 tests/__snapshots__/store_hooks.test.ts.snap delete mode 100644 tests/animations.test.ts create mode 100644 tests/blockdom/block.test.ts create mode 100644 tests/blockdom/block_attributes.test.ts create mode 100644 tests/blockdom/block_event_handling.test.ts create mode 100644 tests/blockdom/block_refs.test.ts create mode 100644 tests/blockdom/helpers.ts create mode 100644 tests/blockdom/html.test.ts create mode 100644 tests/blockdom/list.test.ts create mode 100644 tests/blockdom/multi.test.ts create mode 100644 tests/blockdom/remove_hook.test.ts create mode 100644 tests/blockdom/text.test.ts create mode 100644 tests/blockdom/toggler.test.ts delete mode 100644 tests/component/__snapshots__/class_style.test.ts.snap delete mode 100644 tests/component/__snapshots__/component.test.ts.snap delete mode 100644 tests/component/__snapshots__/props_validation.test.ts.snap delete mode 100644 tests/component/__snapshots__/slots.test.ts.snap delete mode 100644 tests/component/async.test.ts delete mode 100644 tests/component/class_style.test.ts delete mode 100644 tests/component/component.test.ts delete mode 100644 tests/component/error_handling.test.ts delete mode 100644 tests/component/props_validation.test.ts delete mode 100644 tests/component/styles.test.ts delete mode 100644 tests/component/un_mounting.test.ts create mode 100644 tests/components/__snapshots__/app.test.ts.snap create mode 100644 tests/components/__snapshots__/basics.test.ts.snap create mode 100644 tests/components/__snapshots__/concurrency.test.ts.snap create mode 100644 tests/components/__snapshots__/error_handling.test.ts.snap create mode 100644 tests/components/__snapshots__/event_handling.test.ts.snap create mode 100644 tests/components/__snapshots__/higher_order_component.test.ts.snap create mode 100644 tests/components/__snapshots__/lifecycle.test.ts.snap create mode 100644 tests/components/__snapshots__/props.test.ts.snap create mode 100644 tests/components/__snapshots__/refs.test.ts.snap create mode 100644 tests/components/__snapshots__/slots.test.ts.snap create mode 100644 tests/components/__snapshots__/style_class.test.ts.snap create mode 100644 tests/components/__snapshots__/t_call.test.ts.snap create mode 100644 tests/components/__snapshots__/t_call_block.test.ts.snap create mode 100644 tests/components/__snapshots__/t_component.test.ts.snap create mode 100644 tests/components/__snapshots__/t_foreach.test.ts.snap create mode 100644 tests/components/app.test.ts create mode 100644 tests/components/basics.test.ts create mode 100644 tests/components/concurrency.test.ts create mode 100644 tests/components/env.test.ts create mode 100644 tests/components/error_handling.test.ts create mode 100644 tests/components/event_handling.test.ts create mode 100644 tests/components/higher_order_component.test.ts create mode 100644 tests/components/lifecycle.test.ts create mode 100644 tests/components/props.test.ts create mode 100644 tests/components/refs.test.ts rename tests/{component => components}/slots.test.ts (62%) create mode 100644 tests/components/style_class.test.ts create mode 100644 tests/components/t_call.test.ts create mode 100644 tests/components/t_call_block.test.ts create mode 100644 tests/components/t_component.test.ts create mode 100644 tests/components/t_foreach.test.ts delete mode 100644 tests/context.test.ts delete mode 100644 tests/core/event_bus.test.ts delete mode 100644 tests/core/observer.test.ts create mode 100644 tests/event_bus.test.ts delete mode 100644 tests/hooks.test.ts create mode 100644 tests/misc/__snapshots__/memo.test.ts.snap create mode 100644 tests/misc/__snapshots__/portal.test.ts.snap delete mode 100644 tests/misc/async_root.test.ts create mode 100644 tests/misc/memo.test.ts create mode 100644 tests/mocks/mockEventTarget.js create mode 100644 tests/qweb/__snapshots__/attributes.test.ts.snap create mode 100644 tests/qweb/__snapshots__/comments.test.ts.snap create mode 100644 tests/qweb/__snapshots__/event_handling.test.ts.snap create mode 100644 tests/qweb/__snapshots__/misc.test.ts.snap delete mode 100644 tests/qweb/__snapshots__/qweb.test.ts.snap delete mode 100644 tests/qweb/__snapshots__/qweb_memory.test.ts.snap delete mode 100644 tests/qweb/__snapshots__/qweb_t_att.test.ts.snap delete mode 100644 tests/qweb/__snapshots__/qweb_t_tag.test.ts.snap create mode 100644 tests/qweb/__snapshots__/simple_templates.test.ts.snap create mode 100644 tests/qweb/__snapshots__/t_call.test.ts.snap create mode 100644 tests/qweb/__snapshots__/t_debug_log.test.ts.snap create mode 100644 tests/qweb/__snapshots__/t_esc.test.ts.snap create mode 100644 tests/qweb/__snapshots__/t_foreach.test.ts.snap create mode 100644 tests/qweb/__snapshots__/t_if.test.ts.snap create mode 100644 tests/qweb/__snapshots__/t_raw.test.ts.snap create mode 100644 tests/qweb/__snapshots__/t_ref.test.ts.snap create mode 100644 tests/qweb/__snapshots__/t_set.test.ts.snap create mode 100644 tests/qweb/__snapshots__/white_space.test.ts.snap create mode 100644 tests/qweb/attributes.test.ts create mode 100644 tests/qweb/comments.test.ts create mode 100644 tests/qweb/event_handling.test.ts create mode 100644 tests/qweb/inline_expressions.test.ts create mode 100644 tests/qweb/misc.test.ts create mode 100644 tests/qweb/parser.test.ts delete mode 100644 tests/qweb/qweb.test.ts delete mode 100644 tests/qweb/qweb_expressions.test.ts delete mode 100644 tests/qweb/qweb_memory.test.ts delete mode 100644 tests/qweb/qweb_t_att.test.ts delete mode 100644 tests/qweb/qweb_t_tag.test.ts create mode 100644 tests/qweb/simple_templates.test.ts create mode 100644 tests/qweb/t_call.test.ts create mode 100644 tests/qweb/t_debug_log.test.ts create mode 100644 tests/qweb/t_esc.test.ts create mode 100644 tests/qweb/t_foreach.test.ts create mode 100644 tests/qweb/t_if.test.ts create mode 100644 tests/qweb/t_raw.test.ts create mode 100644 tests/qweb/t_ref.test.ts create mode 100644 tests/qweb/t_set.test.ts create mode 100644 tests/qweb/validation.test.ts create mode 100644 tests/qweb/white_space.test.ts create mode 100644 tests/reactivity.test.ts delete mode 100644 tests/router/__snapshots__/link.test.ts.snap delete mode 100644 tests/router/__snapshots__/route_component.test.ts.snap delete mode 100644 tests/router/link.test.ts delete mode 100644 tests/router/route_component.test.ts delete mode 100644 tests/router/router.test.ts delete mode 100644 tests/router/test_router.ts delete mode 100644 tests/store.test.ts delete mode 100644 tests/store_hooks.test.ts delete mode 100644 tests/tooling/debug_script_1.test.ts delete mode 100644 tests/tooling/debug_script_2.test.ts delete mode 100644 tests/tooling/debug_script_3.test.ts delete mode 100644 tests/tooling/debug_script_4.test.ts delete mode 100644 tests/tooling/doc_link_checker.test.ts delete mode 100644 tests/utils.test.ts delete mode 100644 tests/vdom.test.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c165b3a3..930796cb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,7 +5,7 @@ name: Node.js CI on: pull_request: - branches: [ master ] + branches: [ master, owl-next ] jobs: build: diff --git a/doc/reference/qweb_engine.md b/doc/reference/qweb_engine.md index 164b6633..0fe139b4 100644 --- a/doc/reference/qweb_engine.md +++ b/doc/reference/qweb_engine.md @@ -105,6 +105,8 @@ between Owl classes. This is the reason why `QWeb` actually extends [EventBus](e ### Translations +take care of this and "cherry-pick" 8464a1b04e7469434f9dcb3d68a543f58cb61b8e + If properly setup, Owl QWeb engine can translate all rendered templates. To do so, it needs a translate function, which takes a string and returns a string. diff --git a/doc/reference/router.md b/doc/reference/router.md index 251e1672..352b2646 100644 --- a/doc/reference/router.md +++ b/doc/reference/router.md @@ -1,5 +1,7 @@ # 🦉 Router 🦉 +Remove? + ## Content - [Overview](#overview) diff --git a/package.json b/package.json index 5ca3cb84..6b67e2b3 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "sass": "^1.16.1", "source-map-support": "^0.5.10", "ts-jest": "^27.0.5", - "typescript": "^3.7.2", + "typescript": "3.9.6", "uglify-es": "^3.3.9" }, "jest": { @@ -64,6 +64,7 @@ "/src", "/tests" ], + "setupFiles": ["./tests/mocks/mockEventTarget.js"], "transform": { "^.+\\.ts?$": "ts-jest" }, diff --git a/rollup.config.js b/rollup.config.js index c5071d91..bc12fc86 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -40,6 +40,7 @@ function getConfigForFormat(format, generatedFileName, minified = false) { name: name, extend: extend, outro: outro, + freeze: false, plugins: minified ? [terser()] : [], indent: ' ', // indent with 4 spaces }; diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 00000000..6b26f019 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,45 @@ +import { Component } from "./component/component"; +import { ComponentNode } from "./component/component_node"; +import { Scheduler } from "./component/scheduler"; +import { TemplateSet } from "./qweb/template_helpers"; + +// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f + +export class App extends TemplateSet { + Root: T; + props: any; + env: any = {}; + scheduler = new Scheduler(window.requestAnimationFrame.bind(window)); + root: ComponentNode | null = null; + dev: boolean = true; + + constructor(Root: T, props?: any) { + super(); + this.Root = Root; + this.props = props; + } + + configure(params: any) { + if (params.env) { + this.env = params.env; + } + } + + mount(target: HTMLElement): Promise> { + if (!(target instanceof HTMLElement)) { + throw new Error("Cannot mount component: the target is not a valid DOM element"); + } + if (!document.body.contains(target)) { + throw new Error("Cannot mount a component on a detached dom node"); + } + const node = new ComponentNode(this.Root, this.props, this); + this.root = node; + return node.mountComponent(target); + } + + destroy() { + if (this.root) { + this.root.destroy(); + } + } +} diff --git a/src/blockdom/attributes.ts b/src/blockdom/attributes.ts new file mode 100644 index 00000000..5217a295 --- /dev/null +++ b/src/blockdom/attributes.ts @@ -0,0 +1,155 @@ +import type { Setter } from "./block_compiler"; + +const { setAttribute, removeAttribute } = Element.prototype; +const tokenList = DOMTokenList.prototype; +const tokenListAdd = tokenList.add; +const tokenListRemove = tokenList.remove; +const isArray = Array.isArray; +const { split, trim } = String.prototype; +const wordRegexp = /\s+/; + +/** + * We regroup here all code related to updating attributes in a very loose sense: + * attributes, properties and classs are all managed by the functions in this + * file. + */ + +export function createAttrUpdater(attr: string): Setter { + return function (this: HTMLElement, value: any) { + if (value !== false) { + setAttribute.call(this, attr, value === true ? "" : value); + } + }; +} + +export function attrsSetter(this: HTMLElement, attrs: any) { + if (isArray(attrs)) { + setAttribute.call(this, attrs[0], attrs[1]); + } else { + for (let k in attrs) { + setAttribute.call(this, k, attrs[k]); + } + } +} + +export function attrsUpdater(this: HTMLElement, attrs: any, oldAttrs: any) { + if (isArray(attrs)) { + const name = attrs[0]; + const val = attrs[1]; + if (name === oldAttrs[0]) { + if (val === oldAttrs[1]) { + return; + } + setAttribute.call(this, name, val); + } else { + removeAttribute.call(this, oldAttrs[0]); + setAttribute.call(this, name, val); + } + } else { + for (let k in oldAttrs) { + if (!(k in attrs)) { + removeAttribute.call(this, k); + } + } + for (let k in attrs) { + const val = attrs[k]; + if (val !== oldAttrs[k]) { + setAttribute.call(this, k, val); + } + } + } +} + +function toClassObj(expr: string | number | { [c: string]: any }) { + const result: { [c: string]: any } = {}; + switch (typeof expr) { + case "string": + // we transform here a list of classes into an object: + // 'hey you' becomes {hey: true, you: true} + const str = trim.call(expr); + if (!str) { + return {}; + } + let words = split.call(str, wordRegexp); + for (let i = 0, l = words.length; i < l; i++) { + result[words[i]] = true; + } + return result; + case "object": + // this is already an object but we may need to split keys: + // {'a': true, 'b c': true} should become {a: true, b: true, c: true} + for (let key in expr as any) { + const value = (expr as any)[key]; + if (value) { + const words = split.call(key, wordRegexp); + for (let word of words) { + result[word] = value; + } + } + } + return result; + + case "undefined": + return {}; + case "number": + return { [expr as number]: true }; + default: + return { [expr as any]: true }; + } +} + +export function setClass(this: HTMLElement, val: any) { + val = val === "" ? {} : toClassObj(val); + // add classes + const cl = this.classList; + for (let c in val) { + tokenListAdd.call(cl, c); + } +} + +export function updateClass(this: HTMLElement, val: any, oldVal: any) { + oldVal = oldVal === "" ? {} : toClassObj(oldVal); + val = val === "" ? {} : toClassObj(val); + const cl = this.classList; + // remove classes + for (let c in oldVal) { + if (!(c in val)) { + tokenListRemove.call(cl, c); + } + } + // add classes + for (let c in val) { + if (!(c in oldVal)) { + tokenListAdd.call(cl, c); + } + } +} + +export function makePropSetter(name: string): Setter { + return function setProp(this: HTMLElement, value: any) { + (this as any)[name] = value; + }; +} + +export function isProp(tag: string, key: string): boolean { + switch (tag) { + case "input": + return ( + key === "checked" || + key === "indeterminate" || + key === "value" || + key === "readonly" || + key === "disabled" + ); + case "option": + return key === "selected" || key === "disabled"; + case "textarea": + return key === "readonly" || key === "disabled"; + break; + case "button": + case "select": + case "optgroup": + return key === "disabled"; + } + return false; +} diff --git a/src/blockdom/block_compiler.ts b/src/blockdom/block_compiler.ts new file mode 100644 index 00000000..0fd57806 --- /dev/null +++ b/src/blockdom/block_compiler.ts @@ -0,0 +1,602 @@ +import { + attrsSetter, + attrsUpdater, + createAttrUpdater, + isProp, + makePropSetter, + setClass, + updateClass, +} from "./attributes"; +import { config } from "./config"; +import { createEventHandler } from "./events"; +import type { VNode } from "./index"; +import { VMulti } from "./multi"; +import { toText } from "./text"; + +const getDescriptor = (o: any, p: any) => Object.getOwnPropertyDescriptor(o, p)!; +const nodeProto = Node.prototype; +const elementProto = Element.prototype; +const characterDataProto = CharacterData.prototype; + +const characterDataSetData = getDescriptor(characterDataProto, "data").set!; +const nodeGetFirstChild = getDescriptor(nodeProto, "firstChild").get!; +const nodeGetNextSibling = getDescriptor(nodeProto, "nextSibling").get!; + +const NO_OP = () => {}; + +// ----------------------------------------------------------------------------- +// Main compiler code +// ----------------------------------------------------------------------------- + +type BlockType = (data?: any[], children?: VNode[]) => VNode; + +const cache: { [key: string]: BlockType } = {}; + +/** + * Compiling blocks is a multi-step process: + * + * 1. build an IntermediateTree from the HTML element. This intermediate tree + * is a binary tree structure that encode dynamic info sub nodes, and the + * path required to reach them + * 2. process the tree to build a block context, which is an object that aggregate + * all dynamic info in a list, and also, all ref indexes. + * 3. process the context to build appropriate builder/setter functions + * 4. make a dynamic block class, which will efficiently collect references and + * create/update dynamic locations/children + * + * @param str + * @returns a new block type, that can build concrete blocks + */ +export function createBlock(str: string): BlockType { + if (str in cache) { + return cache[str]; + } + + // step 0: prepare html base element + const doc = new DOMParser().parseFromString(`${str}`, "text/xml"); + const node = doc.firstChild!.firstChild!; + if (config.shouldNormalizeDom) { + normalizeNode(node as any); + } + + // step 1: prepare intermediate tree + const tree = buildTree(node); + + // step 2: prepare block context + const context = buildContext(tree); + + // step 3: build the final block class + const template = tree.el as HTMLElement; + const Block = buildBlock(template, context); + cache[str] = Block; + return Block; +} + +// ----------------------------------------------------------------------------- +// Helper +// ----------------------------------------------------------------------------- + +function normalizeNode(node: HTMLElement | Text) { + if (node.nodeType === Node.TEXT_NODE) { + if (!/\S/.test((node as Text).textContent!)) { + (node as Text).remove(); + return; + } + } + if (node.nodeType === Node.ELEMENT_NODE) { + if ((node as HTMLElement).tagName === "pre") { + return; + } + } + for (let i = node.childNodes.length - 1; i >= 0; --i) { + normalizeNode(node.childNodes.item(i) as any); + } +} + +// ----------------------------------------------------------------------------- +// building a intermediate tree +// ----------------------------------------------------------------------------- + +interface DynamicInfo { + idx: number; + refIdx?: number; + type: "text" | "child" | "handler" | "attribute" | "attributes" | "ref"; + isOnlyChild?: boolean; + name?: string; + tag?: string; + event?: string; +} + +interface IntermediateTree { + parent: IntermediateTree | null; + firstChild: IntermediateTree | null; + nextSibling: IntermediateTree | null; + el: Node; + info: DynamicInfo[]; + forceRef?: boolean; + refIdx?: number; + refN: number; +} + +function buildTree( + node: Node, + parent: IntermediateTree | null = null, + domParentTree: IntermediateTree | null = null +): IntermediateTree { + switch (node.nodeType) { + case 1: { + // HTMLElement + let isActive = false; + const tagName = (node as Element).tagName; + let el: Node | undefined = undefined; + const info: DynamicInfo[] = []; + if (tagName.startsWith("block-text-")) { + const index = parseInt(tagName.slice(11), 10); + info.push({ type: "text", idx: index }); + el = document.createTextNode(""); + isActive = true; + } + if (tagName.startsWith("block-child-")) { + domParentTree!.forceRef = true; + const index = parseInt(tagName.slice(12), 10); + info.push({ type: "child", idx: index }); + el = document.createTextNode(""); + isActive = true; + } + if (!el) { + el = document.createElement(tagName); + } + if (el instanceof HTMLElement) { + const attrs = (node as Element).attributes; + for (let i = 0; i < attrs.length; i++) { + const attrName = attrs[i].name; + const attrValue = attrs[i].value; + if (attrName.startsWith("block-handler-")) { + isActive = true; + const idx = parseInt(attrName.slice(14), 10); + info.push({ + type: "handler", + idx, + event: attrValue, + }); + } else if (attrName.startsWith("block-attribute-")) { + isActive = true; + const idx = parseInt(attrName.slice(16), 10); + info.push({ + type: "attribute", + idx, + name: attrValue, + tag: tagName, + }); + } else if (attrName === "block-attributes") { + isActive = true; + info.push({ + type: "attributes", + idx: parseInt(attrValue, 10), + }); + } else if (attrName === "block-ref") { + isActive = true; + info.push({ + type: "ref", + idx: parseInt(attrValue, 10), + }); + } else { + el.setAttribute(attrs[i].name, attrValue); + } + } + } + + const tree: IntermediateTree = { + parent, + firstChild: null, + nextSibling: null, + el, + info, + refN: isActive ? 1 : 0, + }; + + if (node.firstChild) { + const childNode = node.childNodes[0]; + if ( + node.childNodes.length === 1 && + childNode.nodeType === 1 && + (childNode as Element).tagName.startsWith("block-child-") + ) { + const tagName = (childNode as Element).tagName; + const index = parseInt(tagName.slice(12), 10); + info.push({ idx: index, type: "child", isOnlyChild: true }); + isActive = true; + tree.refN = 1; + } else { + tree.firstChild = buildTree(node.firstChild, tree, tree); + el.appendChild(tree.firstChild.el); + let curNode: Node | null = node.firstChild; + let curTree: IntermediateTree | null = tree.firstChild; + while ((curNode = curNode.nextSibling)) { + curTree.nextSibling = buildTree(curNode, curTree, tree); + el.appendChild(curTree.nextSibling.el); + curTree = curTree.nextSibling; + } + } + } + if (isActive) { + let cur: IntermediateTree | null = tree; + while ((cur = cur.parent)) { + cur.refN++; + } + } + return tree; + } + case 3: + case 8: { + // text node or comment node + const el = + node.nodeType === 3 + ? document.createTextNode(node.textContent!) + : document.createComment(node.textContent!); + return { + parent: parent, + firstChild: null, + nextSibling: null, + el, + info: [], + refN: 0, + }; + } + } + throw new Error("boom"); +} + +function parentTree(tree: IntermediateTree): IntermediateTree | null { + let parent = tree.parent; + while (parent && parent.nextSibling === tree) { + tree = parent; + parent = parent.parent; + } + return parent; +} + +// ----------------------------------------------------------------------------- +// Building a block context +// ----------------------------------------------------------------------------- + +interface RefCollector { + idx: number; + prevIdx: number; + getVal: Function; +} + +export type Setter = (this: T, value: any) => void; +export type Updater = (this: T, value: any, oldVal: any) => void; + +interface Location { + refIdx: number; + setData: Setter; + updateData: Updater; +} + +interface IndexedLocation extends Location { + idx: number; +} + +interface Child { + parentRefIdx: number; + afterRefIdx?: number; + isOnlyChild?: boolean; +} + +interface BlockCtx { + refN: number; + collectors: RefCollector[]; + locations: IndexedLocation[]; + children: Child[]; + cbRefs: number[]; +} + +function buildContext( + tree: IntermediateTree, + ctx?: BlockCtx, + fromIdx?: number, + toIdx?: number +): BlockCtx { + if (!ctx) { + const children = new Array(tree.info.filter((v) => v.type === "child").length); + ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN }; + fromIdx = 0; + toIdx = tree.refN - 1; + } + if (tree.refN) { + const initialIdx = fromIdx!; + const isRef = tree.forceRef || tree.info.length > 0; + const firstChild = tree.firstChild ? tree.firstChild.refN : 0; + const nextSibling = tree.nextSibling ? tree.nextSibling.refN : 0; + + //node + if (isRef) { + for (let info of tree.info) { + info.refIdx = initialIdx!; + } + tree.refIdx = initialIdx!; + updateCtx(ctx, tree); + fromIdx!++; + } + + // right + if (nextSibling) { + const idx = fromIdx! + firstChild; + ctx.collectors.push({ idx, prevIdx: initialIdx, getVal: nodeGetNextSibling }); + buildContext(tree.nextSibling!, ctx, idx, toIdx); + } + + // left + if (firstChild) { + ctx.collectors.push({ idx: fromIdx!, prevIdx: initialIdx, getVal: nodeGetFirstChild }); + buildContext(tree.firstChild!, ctx, fromIdx!, toIdx! - nextSibling); + } + } + + return ctx; +} + +function updateCtx(ctx: BlockCtx, tree: IntermediateTree) { + for (let info of tree.info) { + switch (info.type) { + case "text": + ctx.locations.push({ + idx: info.idx, + refIdx: info.refIdx!, + setData: setText, + updateData: setText, + }); + break; + case "child": + if (info.isOnlyChild) { + // tree is the parentnode here + ctx.children[info.idx] = { + parentRefIdx: info.refIdx!, + isOnlyChild: true, + }; + } else { + // tree is the anchor text node + ctx.children[info.idx] = { + parentRefIdx: parentTree(tree)!.refIdx!, + afterRefIdx: info.refIdx!, + }; + } + break; + case "attribute": { + const refIdx = info.refIdx!; + let updater: any; + let setter: any; + if (isProp(info.tag!, info.name!)) { + const setProp = makePropSetter(info.name!); + setter = setProp; + updater = setProp; + } else if (info.name === "class") { + setter = setClass; + updater = updateClass; + } else { + setter = createAttrUpdater(info.name!); + updater = setter; + } + ctx.locations.push({ + idx: info.idx, + refIdx, + setData: setter, + updateData: updater, + }); + break; + } + case "attributes": + ctx.locations.push({ + idx: info.idx, + refIdx: info.refIdx!, + setData: attrsSetter, + updateData: attrsUpdater, + }); + break; + case "handler": { + const setupHandler = createEventHandler(info.event!); + ctx.locations.push({ + idx: info.idx, + refIdx: info.refIdx!, + setData: setupHandler, + updateData: setupHandler, + }); + break; + } + case "ref": + ctx.cbRefs.push(info.idx); + ctx.locations.push({ + idx: info.idx, + refIdx: info.refIdx!, + setData: setRef, + updateData: NO_OP, + }); + } + } +} +// ----------------------------------------------------------------------------- +// building the concrete block class +// ----------------------------------------------------------------------------- + +function buildBlock(template: HTMLElement, ctx: BlockCtx): BlockType { + let B = createBlockClass(template, ctx); + + if (ctx.cbRefs.length) { + const refs = ctx.cbRefs; + B = class extends B { + remove() { + super.remove(); + for (let ref of refs) { + let fn = (this as any).data[ref]; + fn(null); + } + } + }; + } + + if (ctx.children.length) { + B = class extends B { + children: (VNode | undefined)[] | undefined; + constructor(data?: any[], children?: VNode[]) { + super(data); + this.children = children; + } + }; + B.prototype.beforeRemove = VMulti.prototype.beforeRemove; + return (data?: any[], children: (VNode | undefined)[] = []) => new B(data, children); + } + + return (data?: any[]) => new B(data); +} + +type Constructor = new (...args: any[]) => T; +type BlockClass = Constructor>; + +function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { + const { refN, collectors, children } = ctx; + const colN = collectors.length; + ctx.locations.sort((a, b) => a.idx - b.idx); + const locations: Location[] = ctx.locations.map((loc) => ({ + refIdx: loc.refIdx, + setData: loc.setData, + updateData: loc.updateData, + })); + const locN = locations.length; + const childN = children.length; + const childrenLocs = children; + const isDynamic = refN > 0; + + // these values are defined here to make them faster to lookup in the class + // block scope + const nodeCloneNode = nodeProto.cloneNode; + const nodeInsertBefore = nodeProto.insertBefore; + const elementRemove = elementProto.remove; + + return class Block { + el: HTMLElement | undefined; + refs: Node[] | undefined; + data: any[] | undefined; + parentEl?: HTMLElement | undefined; + children?: (VNode | undefined)[]; + + constructor(data?: any[]) { + this.data = data; + } + + beforeRemove() {} + + remove() { + elementRemove.call(this.el); + } + + firstNode(): Node { + return this.el!; + } + + moveBefore(other: Block | null, afterNode: Node | null) { + const target = other ? other.el! : afterNode; + nodeInsertBefore.call(this.parentEl, this.el!, target); + } + + mount(parent: HTMLElement, afterNode: Node | null) { + const el = nodeCloneNode.call(template, true); + nodeInsertBefore.call(parent, el, afterNode); + if (isDynamic) { + // collecting references + const refs: Node[] = new Array(refN); + this.refs = refs; + refs[0] = el; + for (let i = 0; i < colN; i++) { + const w = collectors[i]; + refs[w.idx] = w.getVal.call(refs[w.prevIdx]); + } + + // applying data to all update points + if (locN) { + const data = this.data!; + for (let i = 0; i < locN; i++) { + const loc = locations[i]; + loc.setData.call(refs[loc.refIdx], data[i]); + } + } + + // preparing all children + if (childN) { + const children = this.children; + for (let i = 0; i < childN; i++) { + const child = children![i]; + if (child) { + const loc = childrenLocs[i]; + const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null; + child.isOnlyChild = loc.isOnlyChild; + child.mount(refs[loc.parentRefIdx] as any, afterNode); + } + } + } + } + this.el = el as HTMLElement; + this.parentEl = parent; + } + patch(other: Block, withBeforeRemove: boolean) { + if (this === other) { + return; + } + const refs = this.refs!; + // update texts/attributes/ + if (locN) { + const data1 = this.data!; + const data2 = other.data!; + for (let i = 0; i < locN; i++) { + const val1 = data1[i]; + const val2 = data2[i]; + if (val1 !== val2) { + const loc = locations[i]; + loc.updateData.call(refs[loc.refIdx], val2, val1); + } + } + this.data = data2; + } + + // update children + if (childN) { + let children1 = this.children; + const children2 = other.children; + for (let i = 0; i < childN; i++) { + const child1 = children1![i]; + const child2 = children2![i]; + if (child1) { + if (child2) { + child1.patch(child2, withBeforeRemove); + } else { + if (withBeforeRemove) { + child1.beforeRemove(); + } + child1.remove(); + children1![i] = undefined; + } + } else if (child2) { + const loc = childrenLocs[i]; + const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null; + child2.mount(refs[loc.parentRefIdx] as any, afterNode); + children1![i] = child2; + } + } + } + } + toString() { + const div = document.createElement("div"); + this.mount(div, null); + return div.innerHTML; + } + }; +} + +function setText(this: Text, value: any) { + characterDataSetData.call(this, toText(value)); +} + +function setRef(this: HTMLElement, fn: any) { + fn(this); +} diff --git a/src/blockdom/config.ts b/src/blockdom/config.ts new file mode 100644 index 00000000..8d5a38cb --- /dev/null +++ b/src/blockdom/config.ts @@ -0,0 +1,16 @@ +export const config = { + // whether or not blockdom should normalize DOM whenever a block is created. + // Normalizing dom mean removing empty text nodes (or containing only spaces) + shouldNormalizeDom: true, + + // this is the main event handler. Every event handler registered with blockdom + // will go through this function, giving it the data registered in the block + // and the event + mainEventHandler: (data: any, ev: Event) => { + if (typeof data === "function") { + data(ev); + } else if (Array.isArray(data)) { + data[0](data[1], ev); + } + }, +}; diff --git a/src/blockdom/events.ts b/src/blockdom/events.ts new file mode 100644 index 00000000..577d20e9 --- /dev/null +++ b/src/blockdom/events.ts @@ -0,0 +1,32 @@ +import { config } from "./config"; + +export function createEventHandler(event: string) { + setupSyntheticEvent(event); + const key = `__event__${event}`; + return function setupHandler(this: HTMLElement, data: any) { + (this as any)[key] = data; + }; +} + +function nativeToSyntheticEvent(event: Event, name: string) { + const eventKey = `__event__${name}`; + let dom = event.target; + while (dom !== null) { + const data = (dom as any)[eventKey]; + if (data) { + config.mainEventHandler(data, event); + return; + } + dom = (dom as any).parentNode; + } +} + +const CONFIGURED_SYNTHETIC_EVENTS: { [event: string]: boolean } = {}; + +function setupSyntheticEvent(name: string) { + if (CONFIGURED_SYNTHETIC_EVENTS[name]) { + return; + } + document.addEventListener(name, (event) => nativeToSyntheticEvent(event, name)); + CONFIGURED_SYNTHETIC_EVENTS[name] = true; +} diff --git a/src/blockdom/html.ts b/src/blockdom/html.ts new file mode 100644 index 00000000..de4748d6 --- /dev/null +++ b/src/blockdom/html.ts @@ -0,0 +1,87 @@ +import type { VNode } from "./index"; + +const nodeProto = Node.prototype; + +const nodeInsertBefore = nodeProto.insertBefore; +const nodeRemoveChild = nodeProto.removeChild; + +class VHtml { + html: string; + parentEl?: HTMLElement | undefined; + content: ChildNode[] = []; + + constructor(html: string) { + this.html = html; + } + + mount(parent: HTMLElement, afterNode: Node | null) { + this.parentEl = parent; + const template = document.createElement("template"); + template.innerHTML = this.html; + this.content = [...(template.content.childNodes as any)]; + for (let elem of this.content) { + nodeInsertBefore.call(parent, elem, afterNode); + } + if (!this.content.length) { + const textNode = document.createTextNode(""); + this.content.push(textNode); + nodeInsertBefore.call(parent, textNode, afterNode); + } + } + + moveBefore(other: VHtml | null, afterNode: Node | null) { + const target = other ? other.content[0] : afterNode; + const parent = this.parentEl; + for (let elem of this.content) { + nodeInsertBefore.call(parent, elem, target); + } + } + + patch(other: VHtml) { + if (this === other) { + return; + } + const html2 = other.html; + if (this.html !== html2) { + const parent = this.parentEl; + // insert new html in front of current + const afterNode = this.content[0]; + const template = document.createElement("template"); + template.innerHTML = html2; + const content = [...(template.content.childNodes as any)]; + for (let elem of content) { + nodeInsertBefore.call(parent, elem, afterNode); + } + if (!content.length) { + const textNode = document.createTextNode(""); + content.push(textNode); + nodeInsertBefore.call(parent, textNode, afterNode); + } + + // remove current content + this.remove(); + this.content = content; + } + } + + beforeRemove() {} + + remove() { + const parent = this.parentEl; + for (let elem of this.content) { + nodeRemoveChild.call(parent, elem); + } + } + + firstNode(): Node { + return this.content[0]!; + } + + toString() { + return this.html; + } +} + +export function html(str: string): VNode { + return new VHtml(str); +} diff --git a/src/blockdom/index.ts b/src/blockdom/index.ts new file mode 100644 index 00000000..1ac9b61e --- /dev/null +++ b/src/blockdom/index.ts @@ -0,0 +1,44 @@ +export { config } from "./config"; + +export { toggler } from "./toggler"; +export { createBlock } from "./block_compiler"; +export { list } from "./list"; +export { multi } from "./multi"; +export { text } from "./text"; +export { html } from "./html"; + +export interface VNode { + mount(parent: HTMLElement, afterNode: Node | null): void; + moveBefore(other: T | null, afterNode: Node | null): void; + patch(other: T, withBeforeRemove: boolean): void; + beforeRemove(): void; + remove(): void; + firstNode(): Node | undefined; + + el?: undefined | HTMLElement | Text; + parentEl?: undefined | HTMLElement; + isOnlyChild?: boolean | undefined; + key?: any; +} + +export type BDom = VNode; + +export function mount(vnode: VNode, fixture: HTMLElement) { + vnode.mount(fixture, null); +} + +export function patch(vnode1: VNode, vnode2: VNode, withBeforeRemove: boolean = false) { + vnode1.patch(vnode2, withBeforeRemove); +} + +export function remove(vnode: VNode, withBeforeRemove: boolean = false) { + if (withBeforeRemove) { + vnode.beforeRemove(); + } + vnode.remove(); +} + +export function withKey(vnode: VNode, key: any) { + vnode.key = key; + return vnode; +} diff --git a/src/blockdom/list.ts b/src/blockdom/list.ts new file mode 100644 index 00000000..7386f5ed --- /dev/null +++ b/src/blockdom/list.ts @@ -0,0 +1,240 @@ +import type { VNode } from "./index"; + +const getDescriptor = (o: any, p: any) => Object.getOwnPropertyDescriptor(o, p)!; +const nodeProto = Node.prototype; + +const nodeInsertBefore = nodeProto.insertBefore; +const nodeAppendChild = nodeProto.appendChild; +const nodeRemoveChild = nodeProto.removeChild; +const nodeSetTextContent = getDescriptor(nodeProto, "textContent").set!; + +// ----------------------------------------------------------------------------- +// List Node +// ----------------------------------------------------------------------------- + +class VList { + children: VNode[]; + anchor: Node | undefined; + parentEl?: HTMLElement | undefined; + isOnlyChild?: boolean | undefined; + + constructor(children: VNode[]) { + this.children = children; + } + + mount(parent: HTMLElement, afterNode: Node | null) { + const children = this.children; + const _anchor = document.createTextNode(""); + this.anchor = _anchor; + nodeInsertBefore.call(parent, _anchor, afterNode); + const l = children.length; + if (l) { + const mount = children[0].mount; + for (let i = 0; i < l; i++) { + mount.call(children[i], parent, _anchor); + } + } + + this.parentEl = parent; + } + + moveBefore(other: VList | null, afterNode: Node | null) { + if (other) { + const next = other!.children[0]; + afterNode = (next ? next.firstNode() : other!.anchor) || null; + } + const children = this.children; + for (let i = 0, l = children.length; i < l; i++) { + children[i].moveBefore(null, afterNode); + } + this.parentEl!.insertBefore(this.anchor!, afterNode); + } + + patch(other: VList, withBeforeRemove: boolean) { + if (this === other) { + return; + } + const ch1 = this.children; + const ch2: VNode[] = other.children; + if (ch2.length === 0 && ch1.length === 0) { + return; + } + this.children = ch2; + const proto = ch2[0] || ch1[0]; + const { + mount: cMount, + patch: cPatch, + remove: cRemove, + beforeRemove, + moveBefore: cMoveBefore, + firstNode: cFirstNode, + } = proto; + + const _anchor = this.anchor!; + const isOnlyChild = this.isOnlyChild; + const parent = this.parentEl!; + + // fast path: no new child => only remove + if (ch2.length === 0 && isOnlyChild) { + if (withBeforeRemove) { + for (let i = 0, l = ch1.length; i < l; i++) { + beforeRemove.call(ch1[i]); + } + } + + nodeSetTextContent.call(parent, ""); + nodeAppendChild.call(parent, _anchor); + return; + } + + let startIdx1 = 0; + let startIdx2 = 0; + let startVn1 = ch1[0]; + let startVn2 = ch2[0]; + + let endIdx1 = ch1.length - 1; + let endIdx2 = ch2.length - 1; + let endVn1 = ch1[endIdx1]; + let endVn2 = ch2[endIdx2]; + + let mapping: any = undefined; + // let noFullRemove = this.hasNoComponent; + + while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) { + // ------------------------------------------------------------------- + if (startVn1 === null) { + startVn1 = ch1[++startIdx1]; + continue; + } + // ------------------------------------------------------------------- + if (endVn1 === null) { + endVn1 = ch1[--endIdx1]; + continue; + } + // ------------------------------------------------------------------- + let startKey1 = startVn1.key; + let startKey2 = startVn2.key; + if (startKey1 === startKey2) { + cPatch.call(startVn1, startVn2, withBeforeRemove); + ch2[startIdx2] = startVn1; + startVn1 = ch1[++startIdx1]; + startVn2 = ch2[++startIdx2]; + continue; + } + // ------------------------------------------------------------------- + let endKey1 = endVn1.key; + let endKey2 = endVn2.key; + if (endKey1 === endKey2) { + cPatch.call(endVn1, endVn2, withBeforeRemove); + ch2[endIdx2] = endVn1; + endVn1 = ch1[--endIdx1]; + endVn2 = ch2[--endIdx2]; + continue; + } + // ------------------------------------------------------------------- + if (startKey1 === endKey2) { + // bnode moved right + cPatch.call(startVn1, endVn2, withBeforeRemove); + ch2[endIdx2] = startVn1; + const nextChild = ch2[endIdx2 + 1]; + cMoveBefore.call(startVn1, nextChild, _anchor); + startVn1 = ch1[++startIdx1]; + endVn2 = ch2[--endIdx2]; + continue; + } + // ------------------------------------------------------------------- + if (endKey1 === startKey2) { + // bnode moved left + cPatch.call(endVn1, startVn2, withBeforeRemove); + ch2[startIdx2] = endVn1; + const nextChild = ch1[startIdx1]; + cMoveBefore.call(endVn1, nextChild, _anchor); + endVn1 = ch1[--endIdx1]; + startVn2 = ch2[++startIdx2]; + continue; + } + // ------------------------------------------------------------------- + mapping = mapping || createMapping(ch1, startIdx1, endIdx1); + let idxInOld = mapping[startKey2]; + if (idxInOld === undefined) { + cMount.call(startVn2, parent, cFirstNode.call(startVn1) || null); + } else { + const elmToMove = ch1[idxInOld]; + cMoveBefore.call(elmToMove, startVn1, null); + cPatch.call(elmToMove, startVn2, withBeforeRemove); + ch2[startIdx2] = elmToMove; + ch1[idxInOld] = null as any; + } + startVn2 = ch2[++startIdx2]; + } + // --------------------------------------------------------------------- + if (startIdx1 <= endIdx1 || startIdx2 <= endIdx2) { + if (startIdx1 > endIdx1) { + const nextChild = ch2[endIdx2 + 1]; + const anchor = nextChild ? cFirstNode.call(nextChild) || null : _anchor; + for (let i = startIdx2; i <= endIdx2; i++) { + cMount.call(ch2[i], parent, anchor); + } + } else { + for (let i = startIdx1; i <= endIdx1; i++) { + let ch = ch1[i]; + if (ch) { + if (withBeforeRemove) { + beforeRemove.call(ch); + } + cRemove.call(ch); + } + } + } + } + } + + beforeRemove() { + const children = this.children; + const l = children.length; + if (l) { + const beforeRemove = children[0].beforeRemove; + for (let i = 0; i < l; i++) { + beforeRemove.call(children[i]); + } + } + } + + remove() { + const { parentEl, anchor } = this; + if (this.isOnlyChild) { + nodeSetTextContent.call(parentEl, ""); + } else { + const children = this.children; + const l = children.length; + if (l) { + const remove = children[0].remove; + for (let i = 0; i < l; i++) { + remove.call(children[i]); + } + } + nodeRemoveChild.call(parentEl, anchor!); + } + } + + firstNode(): Node | undefined { + const child = this.children[0]; + return child ? child.firstNode() : undefined; + } + + toString(): string { + return this.children.map((c) => c!.toString()).join(""); + } +} + +export function list(children: VNode[]): VNode { + return new VList(children); +} + +function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } { + let mapping: any = {}; + for (let i = startIdx1; i <= endIdx2; i++) { + mapping[ch1[i].key] = i; + } + return mapping; +} diff --git a/src/blockdom/multi.ts b/src/blockdom/multi.ts new file mode 100644 index 00000000..727b0190 --- /dev/null +++ b/src/blockdom/multi.ts @@ -0,0 +1,134 @@ +import type { VNode } from "./index"; + +const getDescriptor = (o: any, p: any) => Object.getOwnPropertyDescriptor(o, p)!; +const nodeProto = Node.prototype; +const nodeInsertBefore = nodeProto.insertBefore; +const nodeSetTextContent = getDescriptor(nodeProto, "textContent").set!; +const nodeRemoveChild = nodeProto.removeChild; + +// ----------------------------------------------------------------------------- +// Multi NODE +// ----------------------------------------------------------------------------- + +export class VMulti { + children: (VNode | undefined)[]; + anchors?: Node[] | undefined; + parentEl?: HTMLElement | undefined; + isOnlyChild?: boolean | undefined; + + constructor(children: (VNode | undefined)[]) { + this.children = children; + } + + mount(parent: HTMLElement, afterNode: Node | null) { + const children = this.children; + const l = children.length; + const anchors = new Array(l); + for (let i = 0; i < l; i++) { + let child = children[i]; + if (child) { + child.mount(parent, afterNode); + } else { + const childAnchor = document.createTextNode(""); + anchors[i] = childAnchor; + nodeInsertBefore.call(parent, childAnchor, afterNode); + } + } + this.anchors = anchors; + this.parentEl = parent; + } + + moveBefore(other: VMulti | null, afterNode: Node | null) { + if (other) { + const next = other!.children[0]; + afterNode = (next ? next.firstNode() : other!.anchors![0]) || null; + } + const children = this.children; + const parent = this.parentEl; + const anchors = this.anchors; + for (let i = 0, l = children.length; i < l; i++) { + let child = children[i]; + if (child) { + child.moveBefore(null, afterNode); + } else { + const anchor = anchors![i]; + nodeInsertBefore.call(parent, anchor, afterNode); + } + } + } + + patch(other: VMulti, withBeforeRemove: boolean) { + if (this === other) { + return; + } + const children1 = this.children; + const children2 = other.children; + const anchors = this.anchors!; + const parentEl = this.parentEl!; + for (let i = 0, l = children1.length; i < l; i++) { + const vn1 = children1[i]; + const vn2 = children2[i]; + if (vn1) { + if (vn2) { + vn1.patch(vn2, withBeforeRemove); + } else { + const afterNode = vn1.firstNode()!; + const anchor = document.createTextNode(""); + anchors[i] = anchor; + nodeInsertBefore.call(parentEl, anchor, afterNode); + if (withBeforeRemove) { + vn1.beforeRemove(); + } + vn1.remove(); + children1[i] = undefined; + } + } else if (vn2) { + children1[i] = vn2; + const anchor = anchors[i]; + vn2.mount(parentEl, anchor); + nodeRemoveChild.call(parentEl, anchor); + } + } + } + + beforeRemove() { + const children = this.children; + for (let i = 0, l = children.length; i < l; i++) { + const child = children[i]; + if (child) { + child.beforeRemove(); + } + } + } + + remove() { + const parentEl = this.parentEl; + if (this.isOnlyChild) { + nodeSetTextContent.call(parentEl, ""); + } else { + const children = this.children; + const anchors = this.anchors; + for (let i = 0, l = children.length; i < l; i++) { + const child = children[i]; + if (child) { + child.remove(); + } else { + nodeRemoveChild.call(parentEl, anchors![i]); + } + } + } + } + + firstNode(): Node | undefined { + const child = this.children[0]; + return child ? child.firstNode() : this.anchors![0]; + } + + toString(): string { + return this.children.map((c) => c!.toString()).join(""); + } +} + +export function multi(children: (VNode | undefined)[]): VNode { + return new VMulti(children); +} diff --git a/src/blockdom/text.ts b/src/blockdom/text.ts new file mode 100644 index 00000000..084d53db --- /dev/null +++ b/src/blockdom/text.ts @@ -0,0 +1,70 @@ +import type { VNode } from "./index"; + +const getDescriptor = (o: any, p: any) => Object.getOwnPropertyDescriptor(o, p)!; +const nodeProto = Node.prototype; +const characterDataProto = CharacterData.prototype; + +const nodeInsertBefore = nodeProto.insertBefore; +const characterDataSetData = getDescriptor(characterDataProto, "data").set!; +const nodeRemoveChild = nodeProto.removeChild; + +class VText { + text: string; + parentEl?: HTMLElement | undefined; + el?: Text; + + constructor(text: string) { + this.text = text; + } + + mount(parent: HTMLElement, afterNode: Node | null) { + this.parentEl = parent; + const node = document.createTextNode(toText(this.text)); + nodeInsertBefore.call(parent, node, afterNode); + this.el = node; + } + + moveBefore(other: VText | null, afterNode: Node | null) { + const target = other ? other.el! : afterNode; + nodeInsertBefore.call(this.parentEl, this.el!, target); + } + + patch(other: VText) { + const text2 = other.text; + if (this.text !== text2) { + characterDataSetData.call(this.el!, toText(text2)); + this.text = text2; + } + } + + beforeRemove() {} + + remove() { + nodeRemoveChild.call(this.parentEl, this.el!); + } + + firstNode(): Node { + return this.el!; + } + + toString() { + return this.text; + } +} + +export function text(str: string): VNode { + return new VText(str); +} + +export function toText(value: any): string { + switch (typeof value) { + case "string": + return value; + case "number": + return String(value); + case "boolean": + return value ? "true" : "false"; + default: + return value || ""; + } +} diff --git a/src/blockdom/toggler.ts b/src/blockdom/toggler.ts new file mode 100644 index 00000000..367be569 --- /dev/null +++ b/src/blockdom/toggler.ts @@ -0,0 +1,63 @@ +import type { VNode } from "./index"; + +// ----------------------------------------------------------------------------- +// Toggler node +// ----------------------------------------------------------------------------- + +class VToggler { + key: string; + child: VNode; + + parentEl?: HTMLElement | undefined; + + constructor(key: string, child: VNode) { + this.key = key; + this.child = child; + } + + mount(parent: HTMLElement, afterNode: Node | null) { + this.parentEl = parent; + this.child.mount(parent, afterNode); + } + + moveBefore(other: VToggler | null, afterNode: Node | null) { + this.child.moveBefore(other ? other.child : null, afterNode); + } + + patch(other: VToggler, withBeforeRemove: boolean) { + if (this === other) { + return; + } + let child1 = this.child; + let child2 = other.child; + if (this.key === other.key) { + child1.patch(child2, withBeforeRemove); + } else { + child2.mount(this.parentEl!, child1.firstNode()!); + if (withBeforeRemove) { + child1.beforeRemove(); + } + child1.remove(); + this.child = child2; + this.key = other.key; + } + } + + beforeRemove() {} + + remove() { + this.child.remove(); + } + + firstNode(): Node | undefined { + return this.child.firstNode(); + } + + toString(): string { + return this.child.toString(); + } +} + +export function toggler(key: string, child: VNode): VNode { + return new VToggler(key, child); +} diff --git a/src/browser.ts b/src/browser.ts deleted file mode 100644 index b18d44dd..00000000 --- a/src/browser.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface Browser { - setTimeout: Window["setTimeout"]; - clearTimeout: Window["clearTimeout"]; - setInterval: Window["setInterval"]; - clearInterval: Window["clearInterval"]; - requestAnimationFrame: Window["requestAnimationFrame"]; - random: Math["random"]; - Date: typeof Date; - fetch: Window["fetch"]; - localStorage: Window["localStorage"]; -} - -let localStorage: Window["localStorage"] | null = null; - -export const browser: Browser = { - setTimeout: window.setTimeout.bind(window), - clearTimeout: window.clearTimeout.bind(window), - setInterval: window.setInterval.bind(window), - clearInterval: window.clearInterval.bind(window), - requestAnimationFrame: window.requestAnimationFrame.bind(window), - random: Math.random, - Date: window.Date, - fetch: (window.fetch || (() => {})).bind(window), - get localStorage() { - return localStorage || window.localStorage; - }, - set localStorage(newLocalStorage: Window["localStorage"]) { - localStorage = newLocalStorage; - }, -}; diff --git a/src/component/component.ts b/src/component/component.ts index 1eec362e..2a0e1d72 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -1,781 +1,29 @@ -import { Observer } from "../core/observer"; -import { OwlEvent } from "../core/owl_event"; -import { CompiledTemplate, QWeb } from "../qweb/index"; -import { patch, VNode } from "../vdom/index"; -import "./directive"; -import { Fiber } from "./fiber"; -import "./props_validation"; -import { Scheduler, scheduler } from "./scheduler"; -import { activateSheet } from "./styles"; -import { Browser, browser } from "../browser"; +import type { ComponentNode } from "./component_node"; -/** - * Owl Component System - * - * This file introduces a declarative and composable component system. It - * contains: - * - * - the Env interface (generic type for the environment) - * - the Internal interface (the owl specific metadata attached to a component) - * - the Component class - */ +// ----------------------------------------------------------------------------- +// Component Class +// ----------------------------------------------------------------------------- -//------------------------------------------------------------------------------ -// Types/helpers -//------------------------------------------------------------------------------ +export class Component { + static template: string = ""; -/** - * An Env (environment) is an object that will be (mostly) shared between all - * components of an Owl application. It is the location which should contain - * the qweb instance necessary to render all components. - * - * Note that it is totally fine to extend the environment with application - * specific keys/objects/whatever. For example, a key `isMobile` (to declare - * if we are in "mobile" mode), or a shared bus could be useful. - */ -export interface Env { - qweb: QWeb; - browser: Browser; -} + props: any; + env: any; + __owl__: ComponentNode; -export type MountPosition = "first-child" | "last-child" | "self"; - -interface MountOptions { - position?: MountPosition; -} - -export const enum STATUS { - CREATED, - WILLSTARTED, // willstart has been called - RENDERED, // first render is completed (so, vnode is now defined) - MOUNTED, // is ready, and in DOM. It has a valid el - UNMOUNTED, // has a valid el, but is not in DOM - DESTROYED, -} - -/** - * This is mostly an internal detail of implementation. The Meta interface is - * useful to typecheck and describe the internal keys used by Owl to manage the - * component tree. - */ -interface Internal { - // each component has a unique id, useful mostly to handle parent/child - // relationships - readonly id: number; - depth: number; - vnode: VNode | null; - pvnode: VNode | null; - status: STATUS; - - // parent and children keys are obviously useful to setup the parent-children - // relationship. - parent: Component | null; - children: { [key: number]: Component }; - // children mapping: from templateID to componentID. templateID identifies a - // place in a template. The t-component directive needs it to be able to get - // the component instance back whenever the template is rerendered. - cmap: { [key: number]: number }; - - currentFiber: Fiber | null; - // parentLastFiberId is there to help the parent component to detect, among - // its children, those that are not used anymore and thus can be destroyed - parentLastFiberId: number; - - // when a rendering is initiated by a parent, it may set variables in 'scope' - // (typically when the component is rendered in a slot). We need to - // store that information in case the component would be re-rendered later on. - scope: any; - - boundHandlers: { [key: number]: any }; - observer: Observer | null; - renderFn: CompiledTemplate; - mountedCB: Function | null; - willUnmountCB: Function | null; - willPatchCB: Function | null; - patchedCB: Function | null; - willStartCB: Function | null; - willUpdatePropsCB: Function | null; - classObj: { [key: string]: boolean } | null; - refs: { [key: string]: Component | HTMLElement | undefined } | null; -} - -export const portalSymbol = Symbol("portal"); // FIXME - -//------------------------------------------------------------------------------ -// Component -//------------------------------------------------------------------------------ -let nextId = 1; - -export class Component { - readonly __owl__: Internal; - static template?: string | null = null; - static _template?: string | null = null; - static current: Component | null = null; - static components = {}; - static props?: any; - static defaultProps?: any; - static env: any = {}; - // expose scheduler s.t. it can be mocked for testing purposes - static scheduler: Scheduler = scheduler; - - /** - * The `el` is the root element of the component. Note that it could be null: - * this is the case if the component is not mounted yet, or is destroyed. - */ - get el(): HTMLElement | null { - return this.__owl__.vnode ? (this).__owl__.vnode.elm : null; + constructor(props: any, env: any, node: ComponentNode) { + this.props = props; + this.env = env; + this.__owl__ = node; + } + get el(): HTMLElement | Text | undefined { + const node = this.__owl__; + return node.bdom ? (node.bdom.firstNode() as any) : undefined; } - env: T; - props: Props; - - //-------------------------------------------------------------------------- - // Lifecycle - //-------------------------------------------------------------------------- - - /** - * Creates an instance of Component. - * - * Note that most of the time, only the root component needs to be created by - * hand. Other components should be created automatically by the framework (with - * the t-component directive in a template) - */ - constructor(parent?: Component | null, props?: Props) { - Component.current = this; - - let constr = this.constructor as any; - const defaultProps = constr.defaultProps; - if (defaultProps) { - props = props || ({} as Props); - this.__applyDefaultProps(props, defaultProps); - } - this.props = props; - if (QWeb.dev) { - QWeb.utils.validateProps(constr, this.props); - } - - const id: number = nextId++; - let depth; - if (parent) { - this.env = parent.env; - const __powl__ = parent.__owl__; - __powl__.children[id] = this; - depth = __powl__.depth + 1; - } else { - // we are the root component - this.env = (this.constructor as any).env; - if (!this.env.qweb) { - this.env.qweb = new QWeb(); - } - // TODO: remove this in owl 2.0 - if (!this.env.browser) { - this.env.browser = browser; - } - this.env.qweb.on("update", this, () => { - switch (this.__owl__.status) { - case STATUS.MOUNTED: - this.render(true); - break; - case STATUS.DESTROYED: - // this is unlikely to happen, but if a root widget is destroyed, - // we want to remove our subscription. The usual way to do that - // would be to perform some check in the destroy method, but since - // it is very performance sensitive, and since this is a rare event, - // we simply do it lazily - this.env.qweb.off("update", this); - break; - } - }); - depth = 0; - } - - const qweb = this.env.qweb; - const template = constr.template || this.__getTemplate(qweb); - this.__owl__ = { - id: id, - depth: depth, - vnode: null, - pvnode: null, - status: STATUS.CREATED, - parent: parent || null, - children: {}, - cmap: {}, - currentFiber: null, - parentLastFiberId: 0, - boundHandlers: {}, - mountedCB: null, - willUnmountCB: null, - willPatchCB: null, - patchedCB: null, - willStartCB: null, - willUpdatePropsCB: null, - observer: null, - renderFn: qweb.render.bind(qweb, template), - classObj: null, - refs: null, - scope: null, - }; - if (constr.style) { - this.__applyStyles(constr); - } - this.setup(); - } - - /** - * setup is run just after the component is constructed. This is the standard - * location where the component can setup its hooks. It has some advantages - * over the constructor: - * - it can be patched (useful in odoo ecosystem) - * - it does not need to propagate the arguments to the super call - * - * Note: this method should not be called manually. - */ setup() {} - /** - * willStart is an asynchronous hook that can be implemented to perform some - * action before the initial rendering of a component. - * - * It will be called exactly once before the initial rendering. It is useful - * in some cases, for example, to load external assets (such as a JS library) - * before the component is rendered. - * - * Note that a slow willStart method will slow down the rendering of the user - * interface. Therefore, some effort should be made to make this method as - * fast as possible. - * - * Note: this method should not be called manually. - */ - async willStart() {} - - /** - * mounted is a hook that is called each time a component is attached to the - * DOM. This is a good place to add some listeners, or to interact with the - * DOM, if the component needs to perform some measure for example. - * - * Note: this method should not be called manually. - * - * @see willUnmount - */ - mounted() {} - - /** - * The willUpdateProps is an asynchronous hook, called just before new props - * are set. This is useful if the component needs some asynchronous task - * performed, depending on the props (for example, assuming that the props are - * some record Id, fetching the record data). - * - * This hook is not called during the first render (but willStart is called - * and performs a similar job). - */ - async willUpdateProps(nextProps: Props) {} - - /** - * The willPatch hook is called just before the DOM patching process starts. - * It is not called on the initial render. This is useful to get some - * information which are in the DOM. For example, the current position of the - * scrollbar - */ - willPatch(): any {} - - /** - * This hook is called whenever a component did actually update its props, - * state or env. - * - * This method is not called on the initial render. It is useful to interact - * with the DOM (for example, through an external library) whenever the - * component was updated. - * - * Updating the component state in this hook is possible, but not encouraged. - * One need to be careful, because updates here will cause rerender, which in - * turn will cause other calls to updated. So, we need to be particularly - * careful at avoiding endless cycles. - */ - patched() {} - - /** - * willUnmount is a hook that is called each time just before a component is - * unmounted from the DOM. This is a good place to remove some listeners, for - * example. - * - * Note: this method should not be called manually. - * - * @see mounted - */ - willUnmount() {} - - /** - * catchError is a method called whenever some error happens in the rendering or - * lifecycle hooks of a child. - * - * It needs to be implemented by a component that is designed to handle the - * error properly. - */ - catchError?(error?: Error): void; - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Mount the component to a target element. - * - * This should only be done if the component was created manually. Components - * created declaratively in templates are managed by the Owl system. - * - * Note that a component can be mounted an unmounted several times - */ - async mount(target: HTMLElement | DocumentFragment, options: MountOptions = {}): Promise { - if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) { - let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`; - message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`; - throw new Error(message); - } - const position = options.position || "last-child"; - const __owl__ = this.__owl__; - const currentFiber = __owl__.currentFiber; - - switch (__owl__.status) { - case STATUS.CREATED: { - const fiber = new Fiber(null, this, true, target, position); - fiber.shouldPatch = false; - this.__prepareAndRender(fiber, () => {}); - return scheduler.addFiber(fiber); - } - case STATUS.WILLSTARTED: - case STATUS.RENDERED: - currentFiber.target = target; - currentFiber.position = position; - return scheduler.addFiber(currentFiber); - - case STATUS.UNMOUNTED: { - const fiber = new Fiber(null, this, true, target, position); - fiber.shouldPatch = false; - this.__render(fiber); - return scheduler.addFiber(fiber); - } - - case STATUS.MOUNTED: { - if (position !== "self" && this.el!.parentNode !== target) { - const fiber = new Fiber(null, this, true, target, position); - fiber.shouldPatch = false; - this.__render(fiber); - return scheduler.addFiber(fiber); - } else { - return Promise.resolve(); - } - } - - case STATUS.DESTROYED: - throw new Error("Cannot mount a destroyed component"); - } - } - - /** - * The unmount method is the opposite of the mount method. It is useful - * to call willUnmount calls and remove the component from the DOM. - */ - unmount() { - if (this.__owl__.status === STATUS.MOUNTED) { - this.__callWillUnmount(); - this.el!.remove(); - } - } - - /** - * The render method is the main entry point to render a component (once it - * is ready. This method is not initially called when the component is - * rendered the first time). - * - * This method will cause all its sub components to potentially rerender - * themselves. Note that `render` is not called if a component is updated via - * its props. - */ - async render(force: boolean = false): Promise { - const __owl__ = this.__owl__; - const currentFiber = __owl__.currentFiber; - if (!__owl__.vnode && !currentFiber) { - return; - } - if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) { - return scheduler.addFiber(currentFiber.root); - } - - // if we aren't mounted at this point, it implies that there is a - // currentFiber that is already rendered (isRendered is true), so we are - // about to be mounted - const status = __owl__.status; - const fiber = new Fiber(null, this, force, null, null); - Promise.resolve().then(() => { - if (__owl__.status === STATUS.MOUNTED || status !== STATUS.MOUNTED) { - if (fiber.isCompleted || fiber.isRendered) { - return; - } - this.__render(fiber); - } else { - // we were mounted when render was called, but we aren't anymore, so we - // were actually about to be unmounted ; we can thus forget about this - // fiber - fiber.isCompleted = true; - __owl__.currentFiber = null; - } - }); - return scheduler.addFiber(fiber); - } - - /** - * Destroy the component. This operation is quite complex: - * - it recursively destroy all children - * - call the willUnmount hooks if necessary - * - remove the dom node from the dom - * - * This should only be called manually if you created the component. Most - * components will be automatically destroyed. - */ - destroy() { - const __owl__ = this.__owl__; - if (__owl__.status !== STATUS.DESTROYED) { - const el = this.el; - this.__destroy(__owl__.parent); - if (el) { - el.remove(); - } - } - } - - /** - * This method is called by the component system whenever its props are - * updated. If it returns true, then the component will be rendered. - * Otherwise, it will skip the rendering (also, its props will not be updated) - */ - shouldUpdate(nextProps: Props): boolean { - return true; - } - - /** - * Emit a custom event of type 'eventType' with the given 'payload' on the - * component's el, if it exists. However, note that the event will only bubble - * up to the parent DOM nodes. Thus, it must be called between mounted() and - * willUnmount(). - */ - trigger(eventType: string, payload?: T) { - this.__trigger(this, eventType, payload); - } - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Private helper to perform a full destroy, from the point of view of an Owl - * component. It does not remove the el (this is done only once on the top - * level destroyed component, for performance reasons). - * - * The job of this method is mostly to call willUnmount hooks, and to perform - * all necessary internal cleanup. - * - * Note that it does not call the __callWillUnmount method to avoid visiting - * all children many times. - */ - __destroy(parent: Component | null) { - const __owl__ = this.__owl__; - if (__owl__.status === STATUS.MOUNTED) { - if (__owl__.willUnmountCB) { - __owl__.willUnmountCB(); - } - this.willUnmount(); - __owl__.status = STATUS.UNMOUNTED; - } - const children = __owl__.children; - for (let key in children) { - children[key].__destroy(this); - } - if (parent) { - let id = __owl__.id; - delete parent.__owl__.children[id]; - __owl__.parent = null; - } - __owl__.status = STATUS.DESTROYED; - delete __owl__.vnode; - if (__owl__.currentFiber) { - __owl__.currentFiber.isCompleted = true; - } - } - - __callMounted() { - const __owl__ = this.__owl__; - - __owl__.status = STATUS.MOUNTED; - this.mounted(); - if (__owl__.mountedCB) { - __owl__.mountedCB(); - } - } - - __callWillUnmount() { - const __owl__ = this.__owl__; - if (__owl__.willUnmountCB) { - __owl__.willUnmountCB(); - } - this.willUnmount(); - __owl__.status = STATUS.UNMOUNTED; - if (__owl__.currentFiber) { - __owl__.currentFiber.isCompleted = true; - __owl__.currentFiber.root.counter = 0; - } - const children = __owl__.children; - for (let id in children) { - const comp = children[id]; - if (comp.__owl__.status === STATUS.MOUNTED) { - comp.__callWillUnmount(); - } - } - } - /** - * Private trigger method, allows to choose the component which triggered - * the event in the first place - */ - __trigger(component: Component, eventType: string, payload?: T) { - if (this.el) { - const ev = new OwlEvent(component, eventType, { - bubbles: true, - cancelable: true, - detail: payload, - }); - const triggerHook = this.env[portalSymbol as any]; - if (triggerHook) { - triggerHook(ev); - } - this.el.dispatchEvent(ev); - } - } - /** - * The __updateProps method is called by the t-component directive whenever - * it updates a component (so, when the parent template is rerendered). - */ - async __updateProps(nextProps: Props, parentFiber: Fiber, scope: any): Promise { - this.__owl__.scope = scope; - const shouldUpdate = parentFiber.force || this.shouldUpdate(nextProps); - if (shouldUpdate) { - const __owl__ = this.__owl__; - const fiber = new Fiber(parentFiber, this, parentFiber.force, null, null); - if (!parentFiber.child) { - parentFiber.child = fiber; - } else { - parentFiber.lastChild!.sibling = fiber; - } - parentFiber.lastChild = fiber; - - const defaultProps = (this.constructor).defaultProps; - if (defaultProps) { - this.__applyDefaultProps(nextProps, defaultProps); - } - if (QWeb.dev) { - QWeb.utils.validateProps(this.constructor, nextProps); - } - await Promise.all([ - this.willUpdateProps(nextProps), - __owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps), - ]); - if (fiber.isCompleted) { - return; - } - this.props = nextProps; - - this.__render(fiber); - } - } - - /** - * Main patching method. We call the virtual dom patch method here to convert - * a virtual dom vnode into some actual dom. - */ - __patch(target: HTMLElement | VNode | DocumentFragment, vnode: VNode) { - this.__owl__.vnode = patch(target as any, vnode); - } - - /** - * The __prepare method is only called by the t-component directive, when a - * subcomponent is created. It gets its scope, if any, from the - * parent template. - */ - __prepare(parentFiber: Fiber, scope: any, cb: CallableFunction): Fiber { - this.__owl__.scope = scope; - const fiber = new Fiber(parentFiber, this, parentFiber.force, null, null); - fiber.shouldPatch = false; - if (!parentFiber.child) { - parentFiber.child = fiber; - } else { - parentFiber.lastChild!.sibling = fiber; - } - parentFiber.lastChild = fiber; - this.__prepareAndRender(fiber, cb); - return fiber; - } - - /** - * Apply the stylesheets defined by the component. Note that we need to make - * sure all inherited stylesheets are applied as well. We then delete the - * `style` key from the constructor to make sure we do not apply it again. - */ - private __applyStyles(constr) { - while (constr && constr.style) { - if (constr.hasOwnProperty("style")) { - activateSheet(constr.style, constr.name); - delete constr.style; - } - constr = constr.__proto__; - } - } - __getTemplate(qweb: QWeb): string { - let p = (this).constructor; - if (!p.hasOwnProperty("_template")) { - // here, the component and none of its superclasses defines a static `template` - // key. So we fall back on looking for a template matching its name (or - // one of its subclass). - - let template: string = p.name; - while (!(template in qweb.templates) && p !== Component) { - p = p.__proto__; - template = p.name; - } - if (p === Component) { - throw new Error(`Could not find template for component "${this.constructor.name}"`); - } else { - p._template = template; - } - } - return p._template; - } - - async __prepareAndRender(fiber: Fiber, cb: CallableFunction) { - try { - const proms = Promise.all([ - this.willStart(), - this.__owl__.willStartCB && this.__owl__.willStartCB(), - ]); - this.__owl__.status = STATUS.WILLSTARTED; - await proms; - if (this.__owl__.status === STATUS.DESTROYED) { - return Promise.resolve(); - } - } catch (e) { - fiber.handleError(e); - return Promise.resolve(); - } - if (!fiber.isCompleted) { - this.__render(fiber); - this.__owl__.status = STATUS.RENDERED; - cb(); - } - } - - __render(fiber: Fiber) { - const __owl__ = this.__owl__; - if (__owl__.observer) { - __owl__.observer.allowMutations = false; - } - let error; - try { - let vnode = __owl__.renderFn!(this, { - handlers: __owl__.boundHandlers, - fiber: fiber, - }); - // we iterate over the children to detect those that no longer belong to the - // current rendering: those ones, if not mounted yet, can (and have to) be - // destroyed right now, because they are not in the DOM, and thus we won't - // be notified later on (when patching), that they are removed from the DOM - for (let childKey in __owl__.children) { - const child = __owl__.children[childKey]; - const childOwl = child.__owl__; - if (childOwl.status !== STATUS.MOUNTED && childOwl.parentLastFiberId < fiber.id) { - // we only do here a "soft" destroy, meaning that we leave the child - // dom node alone, without removing it. Most of the time, it does not - // matter, because the child component is already unmounted. However, - // if some of its parent have been unmounted, the child could actually - // still be attached to its parent, and this may be important if we - // want to remount the parent, because the vdom need to match the - // actual DOM - child.__destroy(childOwl.parent); - if (childOwl.pvnode) { - // we remove the key here to make sure that the patching algorithm - // is able to make the difference between this pvnode and an eventual - // other instance of the same component - delete childOwl.pvnode.key; - // Since the component has been unmounted, we do not want to actually - // call a remove hook. This is pretty important, since the t-component - // directive actually disabled it, so the vdom algorithm will just - // not remove the child elm if we don't remove the hook. - delete childOwl.pvnode.data!.hook!.remove; - } - } - } - if (!vnode) { - throw new Error(`Rendering '${this.constructor.name}' did not return anything`); - } - fiber.vnode = vnode; - // we apply here the class information described on the component by the - // template (so, something like ) to the actual - // root vnode - if (__owl__.classObj) { - const data = vnode.data!; - data.class = Object.assign(data.class || {}, __owl__.classObj); - } - } catch (e) { - error = e; - } - if (__owl__.observer) { - __owl__.observer.allowMutations = true; - } - - fiber.root.counter--; - fiber.isRendered = true; - if (error) { - fiber.handleError(error); - } - } - - /** - * Apply default props (only top level). - * - * Note that this method does modify in place the props - */ - __applyDefaultProps(props: Object, defaultProps: Object) { - for (let propName in defaultProps) { - if (props![propName] === undefined) { - props![propName] = defaultProps[propName]; - } - } + render(): Promise { + return this.__owl__.render(); } } - -interface MountParameters { - env?: Env; - target: HTMLElement | DocumentFragment; - props?: any; - position?: MountOptions["position"]; -} - -interface Type extends Function { - new (...args: any[]): T; -} - -export async function mount>( - C: T, - params: MountParameters -): Promise> { - const { env, props, target } = params; - let origEnv = C.hasOwnProperty("env") ? (C as any).env : null; - if (env) { - (C as any as typeof Component).env = env; - } - const component: Component = new C(null, props); - if (origEnv) { - (C as any).env = origEnv; - } else { - delete (C as any).env; - } - const position = params.position || "last-child"; - await component.mount(target, { position }); - return component as any; -} diff --git a/src/component/component_node.ts b/src/component/component_node.ts new file mode 100644 index 00000000..ac673047 --- /dev/null +++ b/src/component/component_node.ts @@ -0,0 +1,256 @@ +import type { App } from "../app"; +import { BDom, VNode } from "../blockdom"; +import { Component } from "./component"; +import { + Fiber, + makeChildFiber, + makeRootFiber, + MountFiber, + RootFiber, + __internal__destroyed, +} from "./fibers"; +import { STATUS } from "./status"; + +export function component( + name: string | typeof Component, + props: any, + key: string, + ctx: ComponentNode, + parent: any +): ComponentNode { + let node: any = ctx.children[key]; + let isDynamic = typeof name !== "string"; + + if (node && node.status < STATUS.MOUNTED) { + node.destroy(); + node = undefined; + } + if (isDynamic && node && node.component.constructor !== name) { + node = undefined; + } + + const parentFiber = ctx.fiber!; + if (node) { + node.updateAndRender(props, parentFiber); + } else { + // new component + const C = isDynamic ? name : parent.constructor.components[name as any]; + node = new ComponentNode(C, props, ctx.app); + ctx.children[key] = node; + + const fiber = makeChildFiber(node, parentFiber); + node.initiateRender(fiber); + } + return node; +} + +// ----------------------------------------------------------------------------- +// Component VNode +// ----------------------------------------------------------------------------- + +let currentNode: ComponentNode | null = null; + +export function getCurrent(): ComponentNode | null { + return currentNode; +} + +type LifecycleHook = Function; + +export class ComponentNode implements VNode { + el?: HTMLElement | Text | undefined; + handlers: any = null; + app: App; + fiber: Fiber | null = null; + component: InstanceType; + bdom: BDom | null = null; + status: STATUS = STATUS.NEW; + + renderFn: Function; + children: { [key: string]: ComponentNode } = Object.create(null); + slots: any = {}; + refs: any = {}; + + willStart: LifecycleHook[] = []; + willUpdateProps: LifecycleHook[] = []; + willUnmount: LifecycleHook[] = []; + mounted: LifecycleHook[] = []; + willPatch: LifecycleHook[] = []; + patched: LifecycleHook[] = []; + destroyed: LifecycleHook[] = []; + + constructor(C: T, props: any, app: App) { + currentNode = this; + this.app = app; + this.component = new C(props, app.env, this) as any; + this.renderFn = app.getTemplate(C.template).bind(null, this.component, this); + this.component.setup(); + } + + mountComponent(target: any): Promise> { + const fiber = new MountFiber(this, target); + this.app.scheduler.addFiber(fiber); + this.initiateRender(fiber); + return fiber.promise.then(() => this.component); + } + + async initiateRender(fiber: Fiber | MountFiber) { + if (this.mounted.length) { + fiber.root.mounted.push(fiber); + } + const component = this.component; + const prom = Promise.all(this.willStart.map((f) => f.call(component))); + await prom; + if (this.status === STATUS.NEW && this.fiber === fiber) { + this._render(fiber); + } + } + + async render() { + if (this.fiber && !this.fiber.bdom) { + return this.fiber.root.promise; + } + if (!this.bdom && !this.fiber) { + // should find a way to return the future mounting promise + return; + } + + const fiber = makeRootFiber(this); + this.app.scheduler.addFiber(fiber); + await Promise.resolve(); + if (this.status === STATUS.DESTROYED) { + return; + } + if (this.fiber === fiber) { + this._render(fiber); + } + return fiber.root.promise; + } + + _render(fiber: Fiber | RootFiber) { + try { + fiber.bdom = this.renderFn(); + } catch (e) { + fiber.root.error = e; + this.handleError(fiber); + } + fiber.root.counter--; + } + + handleError(fiber: Fiber) { + fiber.node.app.destroy(); + } + + destroy() { + if (this.status === STATUS.MOUNTED) { + callWillUnmount(this); + this.bdom!.remove(); + } + callDestroyed(this); + + function callWillUnmount(node: ComponentNode) { + const component = node.component; + for (let cb of node.willUnmount) { + cb.call(component); + } + for (let child of Object.values(node.children)) { + if (child.status === STATUS.MOUNTED) { + callWillUnmount(child); + } + } + } + + function callDestroyed(node: ComponentNode) { + const component = node.component; + node.status = STATUS.DESTROYED; + for (let child of Object.values(node.children)) { + callDestroyed(child); + } + for (let cb of node.destroyed) { + cb.call(component); + } + } + } + + async updateAndRender(props: any, parentFiber: Fiber) { + // update + const fiber = makeChildFiber(this, parentFiber); + if (this.willPatch.length) { + parentFiber.root.willPatch.push(fiber); + } + if (this.patched.length) { + parentFiber.root.patched.push(fiber); + } + const component = this.component; + const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props))); + await prom; + if (fiber !== this.fiber) { + return; + } + this.component.props = props; + this._render(fiber); + } + + // --------------------------------------------------------------------------- + // Block DOM methods + // --------------------------------------------------------------------------- + + firstNode(): Node | undefined { + const bdom = this.bdom; + return bdom ? bdom.firstNode() : undefined; + } + + mount(parent: HTMLElement, anchor: ChildNode) { + const bdom = this.fiber!.bdom!; + this.bdom = bdom; + bdom.mount(parent, anchor); + this.status = STATUS.MOUNTED; + this.fiber!.appliedToDom = true; + this.fiber = null; + if (this.handlers) { + for (let i = 0; i < this.handlers.length; i++) { + const handler = this.handlers[i]; + const eventType = handler[0]; + const el = bdom.el!; + el.addEventListener(eventType, (ev: Event) => { + const info = this.handlers![i]; + const [, ctx, method] = info; + (ctx.__owl__.component as any)[method](ev); + }); + } + } + } + + moveBefore(other: ComponentNode | null, afterNode: Node | null) { + this.bdom!.moveBefore(other ? other.bdom : null, afterNode); + } + + patch() { + this.bdom!.patch(this!.fiber!.bdom!, false); + this.fiber!.appliedToDom = true; + this.fiber = null; + } + + beforeRemove() { + visitRemovedNodes(this); + } + + remove() { + this.bdom!.remove(); + } +} + +function visitRemovedNodes(node: ComponentNode) { + if (node.status === STATUS.MOUNTED) { + const component = node.component; + for (let cb of node.willUnmount) { + cb.call(component); + } + } + for (let child of Object.values(node.children)) { + visitRemovedNodes(child); + } + node.status = STATUS.DESTROYED; + if (node.destroyed.length) { + __internal__destroyed.push(node); + } +} diff --git a/src/component/directive.ts b/src/component/directive.ts deleted file mode 100644 index 5d2d7588..00000000 --- a/src/component/directive.ts +++ /dev/null @@ -1,516 +0,0 @@ -import { QWeb } from "../qweb/index"; -import { INTERP_REGEXP } from "../qweb/compilation_context"; -import { makeHandlerCode, MODS_CODE } from "../qweb/extensions"; -import { STATUS } from "./component"; - -//------------------------------------------------------------------------------ -// t-component -//------------------------------------------------------------------------------ - -const T_COMPONENT_MODS_CODE = Object.assign({}, MODS_CODE, { - self: "if (e.target !== vn.elm) {return}", -}); - -QWeb.utils.defineProxy = function defineProxy(target, source) { - for (let k in source) { - Object.defineProperty(target, k, { - get() { - return source[k]; - }, - set(val) { - source[k] = val; - }, - }); - } -}; - -QWeb.utils.assignHooks = function assignHooks(dataObj, hooks) { - if ("hook" in dataObj) { - const hookObject = dataObj.hook; - for (let name in hooks) { - const current = hookObject[name]; - const fn = hooks[name]; - if (current) { - hookObject[name] = (...args) => { - current(...args); - fn(...args); - }; - } else { - hookObject[name] = fn; - } - } - } else { - dataObj.hook = hooks; - } -}; - -/** - * The t-component directive is certainly a complicated and hard to maintain piece - * of code. To help you, fellow developer, if you have to maintain it, I offer - * you this advice: Good luck... - * - * Since it is not 'direct' code, but rather code that generates other code, it - * is not easy to understand. To help you, here is a detailed and commented - * explanation of the code generated by the t-component directive for the following - * situation: - * ```xml - * - * ``` - * - * ```js - * // we assign utils on top of the function because it will be useful for - * // each components - * let utils = this.utils; - * - * // this is the virtual node representing the parent div - * let c1 = [], p1 = { key: 1 }; - * var vn1 = h("div", p1, c1); - * - * // t-component directive: we start by evaluating the expression given by t-key: - * let key5 = "somestring"; - * - * // def3 is the promise that will contain later either the new component - * // creation, or the props update... - * let def3; - * - * // this is kind of tricky: we need here to find if the component was already - * // created by a previous rendering. This is done by checking the internal - * // `cmap` (children map) of the parent component: it maps keys to component ids, - * // and, then, if there is an id, we look into the children list to get the - * // instance - * let w4 = - * key5 in context.__owl__.cmap - * ? context.__owl__.children[context.__owl__.cmap[key5]] - * : false; - * - * // We keep the index of the position of the component in the closure. We push - * // null to reserve the slot, and will replace it later by the component vnode, - * // when it will be ready (do not forget that preparing/rendering a component is - * // asynchronous) - * let _2_index = c1.length; - * c1.push(null); - * - * // we evaluate here the props given to the component. It is done here to be - * // able to easily reference it later, and also, it might be an expensive - * // computation, so it is certainly better to do it only once - * let props4 = { flag: context["state"].flag }; - * - * // If we have a component, currently rendering, but not ready yet, we do not want - * // to wait for it to be ready if we can avoid it - * if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) { - * // we check if the props are the same. In that case, we can simply reuse - * // the previous rendering and skip all useless work - * if (utils.shallowEqual(props4, w4.__owl__.renderProps)) { - * def3 = w4.__owl__.renderPromise; - * } else { - * // if the props are not the same, we destroy the component and starts anew. - * // this will be faster than waiting for its rendering, then updating it - * w4.destroy(); - * w4 = false; - * } - * } - * - * if (!w4) { - * // in this situation, we need to create a new component. First step is - * // to get a reference to the class, then create an instance with - * // current context as parent, and the props. - * let W4 = context.component && context.components[componentKey4] || QWeb.component[componentKey4]; - - * if (!W4) { - * throw new Error("Cannot find the definition of component 'child'"); - * } - * w4 = new W4(owner, props4); - * - * // Whenever we rerender the parent component, we need to be sure that we - * // are able to find the component instance. To do that, we register it to - * // the parent cmap (children map). Note that the 'template' key is - * // used here, since this is what identify the component from the template - * // perspective. - * context.__owl__.cmap[key5] = w4.__owl__.id; - * - * // __prepare is called, to basically call willStart, then render the - * // component - * def3 = w4.__prepare(); - * - * def3 = def3.then(vnode => { - * // we create here a virtual node for the parent (NOT the component). This - * // means that the vdom of the parent will be stopped here, and from - * // the parent's perspective, it simply is a vnode with no children. - * // However, it shares the same dom element with the component root - * // vnode. - * let pvnode = h(vnode.sel, { key: key5 }); - * - * // we add hooks to the parent vnode so we can interact with the new - * // component at the proper time - * pvnode.data.hook = { - * insert(vn) { - * // the __mount method will patch the component vdom into the elm vn.elm, - * // then call the mounted hooks. However, suprisingly, the snabbdom - * // patch method actually replace the elm by a new elm, so we need - * // to synchronise the pvnode elm with the resulting elm - * let nvn = w4.__mount(vnode, vn.elm); - * pvnode.elm = nvn.elm; - * // what follows is only present if there are animations on the component - * utils.transitionInsert(vn, "fade"); - * }, - * remove() { - * // override with empty function to prevent from removing the node - * // directly. It will be removed when destroy is called anyway, which - * // delays the removal if there are animations. - * }, - * destroy() { - * // if there are animations, we delay the call to destroy on the - * // component, if not, we call it directly. - * let finalize = () => { - * w4.destroy(); - * }; - * utils.transitionRemove(vn, "fade", finalize); - * } - * }; - * // the pvnode is inserted at the correct position in the div's children - * c1[_2_index] = pvnode; - * - * // we keep here a reference to the parent vnode (representing the - * // component, so we can reuse it later whenever we update the component - * w4.__owl__.pvnode = pvnode; - * }); - * } else { - * // this is the 'update' path of the directive. - * // the call to __updateProps is the actual component update - * // Note that we only update the props if we cannot reuse the previous - * // rendering work (in the case it was rendered with the same props) - * def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue); - * def3 = def3.then(() => { - * // if component was destroyed in the meantime, we do nothing (so, this - * // means that the parent's element children list will have a null in - * // the component's position, which will cause the pvnode to be removed - * // when it is patched. - * if (w4.__owl__.isDestroyed) { - * return; - * } - * // like above, we register the pvnode to the children list, so it - * // will not be patched out of the dom. - * let pvnode = w4.__owl__.pvnode; - * c1[_2_index] = pvnode; - * }); - * } - * - * // we register the deferred here so the parent can coordinate its patch operation - * // with all the children. - * extra.promises.push(def3); - * return vn1; - * ``` - */ - -QWeb.addDirective({ - name: "component", - extraNames: ["props"], - priority: 100, - atNodeEncounter({ ctx, value, node, qweb }): boolean { - ctx.addLine(`// Component '${value}'`); - ctx.rootContext.shouldDefineQWeb = true; - ctx.rootContext.shouldDefineParent = true; - ctx.rootContext.shouldDefineUtils = true; - ctx.rootContext.shouldDefineScope = true; - let hasDynamicProps = node.getAttribute("t-props") ? true : false; - - // t-on- events and t-transition - const events: [string, string][] = []; - let transition: string = ""; - const attributes = (node).attributes; - const props: { [key: string]: string } = {}; - for (let i = 0; i < attributes.length; i++) { - const name = attributes[i].name; - const value = attributes[i].textContent!; - if (name.startsWith("t-on-")) { - events.push([name, value]); - } else if (name === "t-transition") { - if (QWeb.enableTransitions) { - transition = value; - } - } else if (!name.startsWith("t-")) { - if (name !== "class" && name !== "style") { - // this is a prop! - if (value.includes("=>")) { - props[name] = ctx.captureExpression(value); - } else { - props[name] = ctx.formatExpression(value) || "undefined"; - } - } - } - } - - // computing the props string representing the props object - let propStr = Object.keys(props) - .map((k) => k + ":" + props[k]) - .join(","); - let componentID = ctx.generateID(); - - let hasDefinedKey = false; - let templateKey; - if (node.tagName === "t" && !node.hasAttribute("t-key") && value.match(INTERP_REGEXP)) { - defineComponentKey(); - const id = ctx.generateID(); - // the ___ is to make sure we have no possible conflict with normal - // template keys - ctx.addLine(`let k${id} = '___' + componentKey${componentID}`); - templateKey = `k${id}`; - } else { - templateKey = ctx.generateTemplateKey(); - } - let ref = node.getAttribute("t-ref"); - let refExpr = ""; - let refKey: string = ""; - if (ref) { - ctx.rootContext.shouldDefineRefs = true; - refKey = `ref${ctx.generateID()}`; - ctx.addLine(`const ${refKey} = ${ctx.interpolate(ref)};`); - refExpr = `context.__owl__.refs[${refKey}] = w${componentID};`; - } - let finalizeComponentCode = `w${componentID}.destroy();`; - if (ref) { - finalizeComponentCode += `delete context.__owl__.refs[${refKey}];`; - } - if (transition) { - finalizeComponentCode = `let finalize = () => { - ${finalizeComponentCode} - }; - delete w${componentID}.__owl__.transitionInserted; - utils.transitionRemove(vn, '${transition}', finalize);`; - } - - let createHook = ""; - let classAttr = node.getAttribute("class"); - let tattClass = node.getAttribute("t-att-class"); - let styleAttr = node.getAttribute("style"); - let tattStyle = node.getAttribute("t-att-style"); - if (tattStyle) { - const attVar = `_${ctx.generateID()}`; - ctx.addLine(`const ${attVar} = ${ctx.formatExpression(tattStyle)};`); - tattStyle = attVar; - } - let classObj = ""; - if (classAttr || tattClass || styleAttr || tattStyle || events.length) { - if (classAttr) { - let classDef = classAttr - .trim() - .split(/\s+/) - .map((a) => `'${a}':true`) - .join(","); - classObj = `_${ctx.generateID()}`; - ctx.addLine(`let ${classObj} = {${classDef}};`); - } - if (tattClass) { - let tattExpr = ctx.formatExpression(tattClass); - if (tattExpr[0] !== "{" || tattExpr[tattExpr.length - 1] !== "}") { - tattExpr = `utils.toClassObj(${tattExpr})`; - } - if (classAttr) { - ctx.addLine(`Object.assign(${classObj}, ${tattExpr})`); - } else { - classObj = `_${ctx.generateID()}`; - ctx.addLine(`let ${classObj} = ${tattExpr};`); - } - } - let eventsCode = events - .map(function ([name, value]) { - const capture = name.match(/\.capture/); - name = capture ? name.replace(/\.capture/, "") : name; - const { event, handler } = makeHandlerCode( - ctx, - name, - value, - false, - T_COMPONENT_MODS_CODE - ); - if (capture) { - return `vn.elm.addEventListener('${event}', ${handler}, true);`; - } - return `vn.elm.addEventListener('${event}', ${handler});`; - }) - .join(""); - const styleExpr = tattStyle || (styleAttr ? `'${styleAttr}'` : false); - const styleCode = styleExpr ? `vn.elm.style = ${styleExpr};` : ""; - createHook = `utils.assignHooks(vnode.data, {create(_, vn){${styleCode}${eventsCode}}});`; - } - - ctx.addLine( - `let w${componentID} = ${templateKey} in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[${templateKey}]] : false;` - ); - let shouldProxy = !ctx.parentNode; - if (shouldProxy) { - let id = ctx.generateID(); - ctx.rootContext.rootNode = id; - shouldProxy = true; - ctx.rootContext.shouldDefineResult = true; - ctx.addLine(`let vn${id} = {};`); - ctx.addLine(`result = vn${id};`); - } - if (hasDynamicProps) { - const dynamicProp = ctx.formatExpression(node.getAttribute("t-props")!); - ctx.addLine(`let props${componentID} = Object.assign({}, ${dynamicProp}, {${propStr}});`); - } else { - ctx.addLine(`let props${componentID} = {${propStr}};`); - } - ctx.addIf( - `w${componentID} && w${componentID}.__owl__.currentFiber && !w${componentID}.__owl__.vnode` - ); - ctx.addLine(`w${componentID}.destroy();`); - ctx.addLine(`w${componentID} = false;`); - ctx.closeIf(); - - let registerCode = ""; - if (shouldProxy) { - registerCode = `utils.defineProxy(vn${ctx.rootNode}, pvnode);`; - } - - // SLOTS - const hasSlots = node.childNodes.length; - - let scope = hasSlots ? `utils.combine(context, scope)` : "undefined"; - - ctx.addIf(`w${componentID}`); - - // need to update component - let styleCode = ""; - if (tattStyle) { - styleCode = `.then(()=>{if (w${componentID}.__owl__.status === ${STATUS.DESTROYED}) {return};w${componentID}.el.style=${tattStyle};});`; - } - ctx.addLine( - `w${componentID}.__updateProps(props${componentID}, extra.fiber, ${scope})${styleCode};` - ); - ctx.addLine(`let pvnode = w${componentID}.__owl__.pvnode;`); - if (registerCode) { - ctx.addLine(registerCode); - } - if (ctx.parentNode) { - ctx.addLine(`c${ctx.parentNode}.push(pvnode);`); - } - - ctx.addElse(); - - // new component - function defineComponentKey() { - if (!hasDefinedKey) { - const interpValue = ctx.interpolate(value); - ctx.addLine(`let componentKey${componentID} = ${interpValue};`); - hasDefinedKey = true; - } - } - defineComponentKey(); - const contextualValue = value.match(INTERP_REGEXP) ? "false" : ctx.formatExpression(value); - ctx.addLine( - `let W${componentID} = ${contextualValue} || context.constructor.components[componentKey${componentID}] || QWeb.components[componentKey${componentID}];` - ); - - // maybe only do this in dev mode... - ctx.addLine( - `if (!W${componentID}) {throw new Error('Cannot find the definition of component "' + componentKey${componentID} + '"')}` - ); - ctx.addLine(`w${componentID} = new W${componentID}(parent, props${componentID});`); - if (transition) { - ctx.addLine(`const __patch${componentID} = w${componentID}.__patch;`); - ctx.addLine( - `w${componentID}.__patch = (t, vn) => {__patch${componentID}.call(w${componentID}, t, vn); if(!w${componentID}.__owl__.transitionInserted){w${componentID}.__owl__.transitionInserted = true;utils.transitionInsert(w${componentID}.__owl__.vnode, '${transition}');}};` - ); - } - ctx.addLine(`parent.__owl__.cmap[${templateKey}] = w${componentID}.__owl__.id;`); - - if (hasSlots) { - const clone = node.cloneNode(true); - - // The next code is a fallback for compatibility reason. It accepts t-set - // elements that are direct children with a non empty body as nodes defining - // the content of a slot. - // - // This is wrong, but is necessary to prevent breaking all existing Owl - // code using slots. This will be removed in v2.0 someday. Meanwhile, - // please use t-set-slot everywhere you need to set the content of a - // slot. - for (let node of clone.children) { - if (node.hasAttribute("t-set") && node.hasChildNodes()) { - node.setAttribute("t-set-slot", node.getAttribute("t-set")!); - node.removeAttribute("t-set"); - } - } - const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]")); - const slotNames = new Set(); - const slotId = QWeb.nextSlotId++; - ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`); - if (slotNodes.length) { - for (let i = 0, length = slotNodes.length; i < length; i++) { - const slotNode = slotNodes[i]; - // check if this is defined in a sub component (in which case it should - // be ignored) - let el = slotNode.parentElement; - let isInSubComponent = false; - while (el !== clone) { - if ( - el!.hasAttribute("t-component") || - el!.tagName[0] === el!.tagName[0].toUpperCase() - ) { - isInSubComponent = true; - break; - } - el = el.parentElement; - } - if (isInSubComponent) { - continue; - } - let key = slotNode.getAttribute("t-set-slot")!; - if (slotNames.has(key)) { - continue; - } - slotNames.add(key); - slotNode.removeAttribute("t-set-slot"); - slotNode.parentElement!.removeChild(slotNode); - - const slotFn = qweb._compile(`slot_${key}_template`, { elem: slotNode, hasParent: true }); - QWeb.slots[`${slotId}_${key}`] = slotFn; - } - } - if (clone.childNodes.length) { - let hasContent = false; - const t = clone.ownerDocument!.createElement("t"); - for (let child of Object.values(clone.childNodes)) { - hasContent = - hasContent || (child instanceof Text ? Boolean(child.textContent.trim().length) : true); - t.appendChild(child); - } - if (hasContent) { - const slotFn = qweb._compile(`slot_default_template`, { elem: t, hasParent: true }); - QWeb.slots[`${slotId}_default`] = slotFn; - } - } - } - - ctx.addLine( - `let fiber = w${componentID}.__prepare(extra.fiber, ${scope}, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});` - ); - // hack: specify empty remove hook to prevent the node from being removed from the DOM - const insertHook = refExpr ? `insert(vn) {${refExpr}},` : ""; - ctx.addLine( - `let pvnode = h('dummy', {key: ${templateKey}, hook: {${insertHook}remove() {},destroy(vn) {${finalizeComponentCode}}}});` - ); - if (registerCode) { - ctx.addLine(registerCode); - } - if (ctx.parentNode) { - ctx.addLine(`c${ctx.parentNode}.push(pvnode);`); - } - ctx.addLine(`w${componentID}.__owl__.pvnode = pvnode;`); - - ctx.closeIf(); - - if (classObj) { - ctx.addLine(`w${componentID}.__owl__.classObj=${classObj};`); - } - - ctx.addLine(`w${componentID}.__owl__.parentLastFiberId = extra.fiber.id;`); - - return true; - }, -}); diff --git a/src/component/fiber.ts b/src/component/fiber.ts deleted file mode 100644 index 3ca2fe9b..00000000 --- a/src/component/fiber.ts +++ /dev/null @@ -1,366 +0,0 @@ -import { h, VNode } from "../vdom/index"; -import { Component, MountPosition, STATUS } from "./component"; -import { scheduler } from "./scheduler"; - -/** - * Owl Fiber Class - * - * Fibers are small abstractions designed to contain all the internal state - * associated with a "rendering work unit", relative to a specific component. - * - * A rendering will cause the creation of a fiber for each impacted components. - * - * Fibers capture all that necessary information, which is critical to owl - * asynchronous rendering pipeline. Fibers can be cancelled, can be in different - * states and in general determine the state of the rendering. - */ - -export class Fiber { - static nextId: number = 1; - id: number = Fiber.nextId++; - - // The force attribute determines if a rendering should bypass the `shouldUpdate` - // method potentially implemented by a component. It is usually set to false. - force: boolean; - - // isCompleted means that the rendering corresponding to this fiber's work is - // done, either because the component has been mounted or patched, or because - // fiber has been cancelled. - isCompleted: boolean = false; - - // the fibers corresponding to component updates (updateProps) need to call - // the willPatch and patched hooks from the corresponding component. However, - // fibers corresponding to a new component do not need to do that. So, the - // shouldPatch hook is the boolean that we check whenever we need to apply - // a patch. - shouldPatch: boolean = true; - - // isRendered is the last state of a fiber. If true, this means that it has - // been rendered and is inert (so, it should not be taken into account when - // counting the number of active fibers). - isRendered: boolean = false; - - // the counter number is a critical information. It is only necessary for a - // root fiber. For that fiber, this number counts the number of active sub - // fibers. When that number reaches 0, the fiber can be applied by the - // scheduler. - counter: number = 0; - - target: HTMLElement | DocumentFragment | null; - position: MountPosition | null; - - scope: any; - - component: Component; - vnode: VNode | null = null; - - root: Fiber; - child: Fiber | null = null; - sibling: Fiber | null = null; - lastChild: Fiber | null = null; - parent: Fiber | null = null; - - error?: Error; - - constructor( - parent: Fiber | null, - component: Component, - force: boolean, - target: HTMLElement | DocumentFragment | null, - position: MountPosition | null - ) { - this.component = component; - this.force = force; - this.target = target; - this.position = position; - - const __owl__ = component.__owl__; - this.scope = __owl__.scope; - - this.root = parent ? parent.root : this; - this.parent = parent; - - let oldFiber = __owl__.currentFiber; - if (oldFiber && !oldFiber.isCompleted) { - this.force = true; - if (oldFiber.root === oldFiber && !parent) { - // both oldFiber and this fiber are root fibers - this._reuseFiber(oldFiber); - return oldFiber; - } else { - this._remapFiber(oldFiber); - } - } - - this.root.counter++; - - __owl__.currentFiber = this; - } - - /** - * When the oldFiber is not completed yet, and both oldFiber and this fiber - * are root fibers, we want to reuse the oldFiber instead of creating a new - * one. Doing so will guarantee that the initiator(s) of those renderings will - * be notified (the promise will resolve) when the last rendering will be done. - * - * This function thus assumes that oldFiber is a root fiber. - */ - _reuseFiber(oldFiber: Fiber) { - oldFiber.cancel(); // cancel children fibers - oldFiber.target = this.target || oldFiber.target; - oldFiber.position = this.position || oldFiber.position; - oldFiber.isCompleted = false; // keep the root fiber alive - oldFiber.isRendered = false; // the fiber has to be re-rendered - if (oldFiber.child) { - // remove relation to children - oldFiber.child.parent = null; - oldFiber.child = null; - oldFiber.lastChild = null; - } - oldFiber.counter = 1; // re-initialize counter - oldFiber.id = Fiber.nextId++; - } - - /** - * In some cases, a rendering initiated at some component can detect that it - * should be part of a larger rendering initiated somewhere up the component - * tree. In that case, it needs to cancel the previous rendering and - * remap itself as a part of the current parent rendering. - */ - _remapFiber(oldFiber: Fiber) { - oldFiber.cancel(); - this.shouldPatch = oldFiber.shouldPatch; - if (oldFiber === oldFiber.root) { - oldFiber.counter++; - } - if (oldFiber.parent && !this.parent) { - // re-map links - this.parent = oldFiber.parent; - this.root = this.parent.root; - this.sibling = oldFiber.sibling; - if (this.parent.lastChild === oldFiber) { - this.parent.lastChild = this; - } - if (this.parent.child === oldFiber) { - this.parent.child = this; - } else { - let current = this.parent.child!; - while (true) { - if (current.sibling === oldFiber) { - current.sibling = this; - break; - } - current = current.sibling!; - } - } - } - } - - /** - * This function has been taken from - * https://medium.com/react-in-depth/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7 - */ - _walk(doWork: (f: Fiber) => Fiber | null) { - let root = this; - let current: Fiber = this; - while (true) { - const child = doWork(current); - if (child) { - current = child; - continue; - } - if (current === root) { - return; - } - while (!current.sibling) { - if (!current.parent || current.parent === root) { - return; - } - current = current.parent; - } - current = current.sibling; - } - } - - /** - * Successfully complete the work of the fiber: call the mount or patch hooks - * and patch the DOM. This function is called once the fiber and its children - * are ready, and the scheduler decides to process it. - */ - complete() { - let component = this.component; - this.isCompleted = true; - const status = component.__owl__.status; - if (status === STATUS.DESTROYED) { - return; - } - - // build patchQueue - const patchQueue: Fiber[] = []; - const doWork: (Fiber) => Fiber | null = function (f) { - f.component.__owl__.currentFiber = null; - patchQueue.push(f); - return f.child; - }; - this._walk(doWork); - const patchLen = patchQueue.length; - - // call willPatch hook on each fiber of patchQueue - if (status === STATUS.MOUNTED) { - for (let i = 0; i < patchLen; i++) { - const fiber = patchQueue[i]; - if (fiber.shouldPatch) { - component = fiber.component; - if (component.__owl__.willPatchCB) { - component.__owl__.willPatchCB(); - } - component.willPatch(); - } - } - } - - // call __patch on each fiber of (reversed) patchQueue - for (let i = patchLen - 1; i >= 0; i--) { - const fiber = patchQueue[i]; - component = fiber.component; - if (fiber.target && i === 0) { - let target; - if (fiber.position === "self") { - target = fiber.target; - if ((target as HTMLElement).tagName.toLowerCase() !== fiber.vnode!.sel) { - throw new Error( - `Cannot attach '${component.constructor.name}' to target node (not same tag name)` - ); - } - // In self mode, we *know* we are to take possession of the target - // Hence we manually create the corresponding VNode and copy the "key" in data - const selfVnodeData = fiber.vnode!.data ? { key: fiber.vnode!.data.key } : {}; - const selfVnode = h(fiber.vnode!.sel, selfVnodeData); - selfVnode.elm = target; - target = selfVnode; - } else { - target = component.__owl__.vnode || document.createElement(fiber.vnode!.sel!); - } - component.__patch(target!, fiber.vnode!); - } else { - const vnode = component.__owl__.vnode; - if (fiber.shouldPatch && vnode) { - component.__patch(vnode, fiber.vnode!); - // When updating a Component's props (in directive), - // the component has a pvnode AND should be patched. - // However, its pvnode.elm may have changed if it is a High Order Component - if (component.__owl__.pvnode) { - component.__owl__.pvnode.elm = component.__owl__.vnode!.elm; - } - } else { - component.__patch(document.createElement(fiber.vnode!.sel!), fiber.vnode!); - component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm; - } - } - } - - // insert into the DOM (mount case) - let inDOM = false; - if (this.target) { - switch (this.position) { - case "first-child": - this.target.prepend(this.component.el!); - break; - case "last-child": - this.target.appendChild(this.component.el!); - break; - } - inDOM = document.body.contains(this.component.el); - this.component.env.qweb.trigger("dom-appended"); - } - - // call patched/mounted hook on each fiber of (reversed) patchQueue - if (status === STATUS.MOUNTED || inDOM) { - for (let i = patchLen - 1; i >= 0; i--) { - const fiber = patchQueue[i]; - component = fiber.component; - if (fiber.shouldPatch && !this.target) { - component.patched(); - if (component.__owl__.patchedCB) { - component.__owl__.patchedCB(); - } - } else { - component.__callMounted(); - } - } - } else { - for (let i = patchLen - 1; i >= 0; i--) { - const fiber = patchQueue[i]; - component = fiber.component; - component.__owl__.status = STATUS.UNMOUNTED; - } - } - } - - /** - * Cancel a fiber and all its children. - */ - cancel() { - this._walk((f) => { - if (!f.isRendered) { - f.root.counter--; - } - f.isCompleted = true; - return f.child; - }); - } - - /** - * This is the global error handler for errors occurring in Owl main lifecycle - * methods. Caught errors are triggered on the QWeb instance, and are - * potentially given to some parent component which implements `catchError`. - * - * If there are no such component, we destroy everything. This is better than - * being in a corrupted state. - */ - handleError(error: Error) { - let component = this.component; - this.vnode = component.__owl__.vnode || h("div"); - - const qweb = component.env.qweb; - let root = component; - - function handle(error) { - let canCatch = false; - qweb.trigger("error", error); - while (component && !(canCatch = !!component.catchError)) { - root = component; - component = component.__owl__.parent!; - } - if (canCatch) { - try { - component.catchError!(error); - } catch (e) { - root = component; - component = component.__owl__.parent!; - return handle(e); - } - return true; - } - return false; - } - - let isHandled = handle(error); - - if (!isHandled) { - // the 3 next lines aim to mark the root fiber as being in error, and - // to force it to end, without waiting for its children - this.root.counter = 0; - this.root.error = error; - scheduler.flush(); - // at this point, the state of the application is corrupted and we could - // have a lot of issues or crashes. So we destroy the application in a try - // catch and swallow these errors because the fiber is already in error, - // and this is the actual issue that needs to be solved, not those followup - // errors. - try { - root.destroy(); - } catch (e) {} - } - } -} diff --git a/src/component/fibers.ts b/src/component/fibers.ts new file mode 100644 index 00000000..9e7ee369 --- /dev/null +++ b/src/component/fibers.ts @@ -0,0 +1,185 @@ +import type { BDom } from "../blockdom"; +import { mount } from "../blockdom"; +import type { ComponentNode } from "./component_node"; +import { STATUS } from "./status"; +// import { mountBlock } from "./bdom/block"; + +export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber { + let current = node.fiber; + if (current) { + // current is necessarily a rootfiber here + let root = parent.root; + cancelFibers(root, current.children); + current.children = []; + current.parent = parent; + root.counter++; + current.root = root; + return current; + } + return new Fiber(node, parent); +} + +export function makeRootFiber(node: ComponentNode): Fiber { + let current = node.fiber; + if (current) { + let root = current.root; + root.counter -= cancelFibers(root, current.children); + current.children = []; + root.counter++; + current.bdom = null; + return current; + } + const fiber = new RootFiber(node); + if (node.willPatch.length) { + fiber.willPatch.push(fiber); + } + if (node.patched.length) { + fiber.patched.push(fiber); + } + + return fiber; +} + +/** + * @returns number of not-yet rendered fibers cancelled + */ +function cancelFibers(root: any, fibers: Fiber[]): number { + let result = 0; + for (let fiber of fibers) { + fiber.node.fiber = null; + fiber.root = root; + if (!fiber.bdom) { + result++; + } + result += cancelFibers(root, fiber.children); + } + return result; +} + +export class Fiber { + node: ComponentNode; + bdom: BDom | null = null; + root: RootFiber; + parent: Fiber | null; + children: Fiber[] = []; + appliedToDom = false; + + constructor(node: ComponentNode, parent: Fiber | null) { + this.node = node; + node.fiber = this; + this.parent = parent; + if (parent) { + const root = parent.root; + root.counter++; + this.root = root; + parent.children.push(this); + } else { + this.root = this as any; + } + } +} + +export class RootFiber extends Fiber { + counter: number = 1; + error: Error | null = null; + resolve: any; + promise: Promise; + reject: any; + + // only add stuff in this if they have registered some hooks + willPatch: Fiber[] = []; + patched: Fiber[] = []; + mounted: Fiber[] = []; + + constructor(node: ComponentNode) { + super(node, null); + this.counter = 1; + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } + + complete() { + const node = this.node; + + // Step 1: calling all willPatch lifecycle hooks + for (let fiber of this.willPatch) { + // because of the asynchronous nature of the rendering, some parts of the + // UI may have been rendered, then deleted in a followup rendering, and we + // do not want to call onWillPatch in that case. + let node = fiber.node; + if (node.fiber === fiber) { + const component = node.component; + for (let cb of node.willPatch) { + cb.call(component); + } + } + } + + // Step 2: patching the dom + node.bdom!.patch(this.bdom!, Object.keys(node.children).length > 0); + this.appliedToDom = true; + + // Step 3: calling all destroyed hooks + for (let node of __internal__destroyed) { + for (let cb of node.destroyed) { + cb(); + } + } + __internal__destroyed.length = 0; + + // Step 4: calling all mounted lifecycle hooks + let current; + let mountedFibers = this.mounted; + while ((current = mountedFibers.pop())) { + if (current.appliedToDom) { + for (let cb of current.node.mounted) { + cb(); + } + } + } + + // Step 5: calling all patched hooks + let patchedFibers = this.patched; + while ((current = patchedFibers.pop())) { + if (current.appliedToDom) { + for (let cb of current.node.patched) { + cb(); + } + } + } + + // unregistering the fiber + node.fiber = null; + } +} + +export let __internal__destroyed: ComponentNode[] = []; + +export class MountFiber extends RootFiber { + target: HTMLElement; + + constructor(node: ComponentNode, target: HTMLElement) { + super(node); + this.target = target; + } + complete() { + const node = this.node; + node.bdom = this.bdom; + mount(node.bdom!, this.target); + node.status = STATUS.MOUNTED; + this.appliedToDom = true; + let current; + let mountedFibers = this.mounted; + while ((current = mountedFibers.pop())) { + if (current.appliedToDom) { + for (let cb of current.node.mounted) { + cb(); + } + } + } + node.fiber = null; + } +} diff --git a/src/component/handler.ts b/src/component/handler.ts new file mode 100644 index 00000000..c8336d15 --- /dev/null +++ b/src/component/handler.ts @@ -0,0 +1,10 @@ +export function mainEventHandler(data: any, ev: Event) { + if (typeof data === "function") { + data(ev); + } else { + const ctx = data[0]; + const method = data[1]; + const args = data[2] || []; + ctx.__owl__.component[method](...args, ev); + } +} diff --git a/src/component/props_validation.ts b/src/component/props_validation.ts deleted file mode 100644 index 4fad585e..00000000 --- a/src/component/props_validation.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { QWeb } from "../qweb/index"; - -//------------------------------------------------------------------------------ -// Prop validation helper -//------------------------------------------------------------------------------ - -/** - * Validate the component props (or next props) against the (static) props - * description. This is potentially an expensive operation: it may needs to - * visit recursively the props and all the children to check if they are valid. - * This is why it is only done in 'dev' mode. - */ - -QWeb.utils.validateProps = function (Widget, props: Object) { - const propsDef = (Widget).props; - if (propsDef instanceof Array) { - // list of strings (prop names) - for (let i = 0, l = propsDef.length; i < l; i++) { - const propName = propsDef[i]; - if (propName[propName.length - 1] === "?") { - // optional prop - break; - } - if (!(propName in props)) { - throw new Error(`Missing props '${propsDef[i]}' (component '${Widget.name}')`); - } - } - for (let key in props) { - if (!propsDef.includes(key) && !propsDef.includes(key + "?")) { - throw new Error(`Unknown prop '${key}' given to component '${Widget.name}'`); - } - } - } else if (propsDef) { - // propsDef is an object now - for (let propName in propsDef) { - if (props[propName] === undefined) { - if (propsDef[propName] && !propsDef[propName].optional) { - throw new Error(`Missing props '${propName}' (component '${Widget.name}')`); - } else { - continue; - } - } - let isValid; - try { - isValid = isValidProp(props[propName], propsDef[propName]); - } catch (e) { - e.message = `Invalid prop '${propName}' in component ${Widget.name} (${e.message})`; - throw e; - } - if (!isValid) { - throw new Error(`Invalid Prop '${propName}' in component '${Widget.name}'`); - } - } - for (let propName in props) { - if (!(propName in propsDef)) { - throw new Error(`Unknown prop '${propName}' given to component '${Widget.name}'`); - } - } - } -}; - -/** - * Check if an invidual prop value matches its (static) prop definition - */ -function isValidProp(prop, propDef): boolean { - if (propDef === true) { - return true; - } - if (typeof propDef === "function") { - // Check if a value is constructed by some Constructor. Note that there is a - // slight abuse of language: we want to consider primitive values as well. - // - // So, even though 1 is not an instance of Number, we want to consider that - // it is valid. - if (typeof prop === "object") { - return prop instanceof propDef; - } - return typeof prop === propDef.name.toLowerCase(); - } else if (propDef instanceof Array) { - // If this code is executed, this means that we want to check if a prop - // matches at least one of its descriptor. - let result = false; - for (let i = 0, iLen = propDef.length; i < iLen; i++) { - result = result || isValidProp(prop, propDef[i]); - } - return result; - } - // propsDef is an object - if (propDef.optional && prop === undefined) { - return true; - } - let result = propDef.type ? isValidProp(prop, propDef.type) : true; - if (propDef.validate) { - result = result && propDef.validate(prop); - } - if (propDef.type === Array && propDef.element) { - for (let i = 0, iLen = prop.length; i < iLen; i++) { - result = result && isValidProp(prop[i], propDef.element); - } - } - if (propDef.type === Object && propDef.shape) { - const shape = propDef.shape; - for (let key in shape) { - result = result && isValidProp(prop[key], shape[key]); - } - if (result) { - for (let propName in prop) { - if (!(propName in shape)) { - throw new Error(`unknown prop '${propName}'`); - } - } - } - } - return result; -} diff --git a/src/component/scheduler.ts b/src/component/scheduler.ts index b8117d6b..0f32c79f 100644 --- a/src/component/scheduler.ts +++ b/src/component/scheduler.ts @@ -1,24 +1,12 @@ -import { Fiber } from "./fiber"; -import { browser } from "../browser"; +import { Fiber, RootFiber } from "./fibers"; +import { STATUS } from "./status"; -/** - * Owl Scheduler Class - * - * The scheduler is the part of Owl that will effectively apply a rendering - * whenever a fiber is ready. - * - * Briefly, it can be used to register root fibers. Whenever there is an - * active root fiber, it will poll continuously each animation frame (so, about - * once every 16ms) and whenever a root fiber is ready, it will apply it. - */ - -interface Task { - fiber: Fiber; - callback: (err?: Error) => void; -} +// ----------------------------------------------------------------------------- +// Scheduler +// ----------------------------------------------------------------------------- export class Scheduler { - tasks: Task[] = []; + tasks: Set = new Set(); isRunning: boolean = false; requestAnimationFrame: Window["requestAnimationFrame"]; @@ -35,37 +23,10 @@ export class Scheduler { this.isRunning = false; } - addFiber(fiber: 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 - fiber = fiber.root; - return new Promise((resolve, reject) => { - if (fiber.error) { - return reject(fiber.error); - } - this.tasks.push({ - fiber, - callback: () => { - if (fiber.error) { - return reject(fiber.error); - } - resolve(); - }, - }); - if (!this.isRunning) { - this.start(); - } - }); - } - - rejectFiber(fiber: Fiber, reason: string) { - fiber = fiber.root; - const index = this.tasks.findIndex((t) => t.fiber === fiber); - if (index >= 0) { - const [task] = this.tasks.splice(index, 1); - fiber.cancel(); - fiber.error = new Error(reason); - task.callback(); + addFiber(fiber: Fiber) { + this.tasks.add(fiber.root); + if (!this.isRunning) { + this.start(); } } @@ -74,28 +35,35 @@ export class Scheduler { * Other tasks are left unchanged. */ flush() { - let tasks = this.tasks; - this.tasks = []; - tasks = tasks.filter((task) => { - if (task.fiber.isCompleted) { - task.callback(); - return false; + this.tasks.forEach((fiber) => { + if (fiber.root !== fiber) { + // this is wrong! should be something like + // if (this.tasks.has(fiber.root)) { + // // parent rendering has completed + // fiber.resolve(); + // this.tasks.delete(fiber); + // } + this.tasks.delete(fiber); + return; } - if (task.fiber.counter === 0) { - if (!task.fiber.error) { - try { - task.fiber.complete(); - } catch (e) { - task.fiber.handleError(e); - } + if (fiber.error) { + this.tasks.delete(fiber); + fiber.reject(fiber.error); + return; + } + if (fiber.node.status === STATUS.DESTROYED) { + this.tasks.delete(fiber); + return; + } + if (fiber.counter === 0) { + if (!fiber.error) { + fiber.complete(); } - task.callback(); - return false; + fiber.resolve(); + this.tasks.delete(fiber); } - return true; }); - this.tasks = tasks.concat(this.tasks); - if (this.tasks.length === 0) { + if (this.tasks.size === 0) { this.stop(); } } @@ -109,5 +77,3 @@ export class Scheduler { }); } } - -export const scheduler = new Scheduler(browser.requestAnimationFrame); diff --git a/src/component/status.ts b/src/component/status.ts new file mode 100644 index 00000000..9f48552c --- /dev/null +++ b/src/component/status.ts @@ -0,0 +1,24 @@ +import type { Component } from "./component"; + +// ----------------------------------------------------------------------------- +// Status +// ----------------------------------------------------------------------------- + +export const enum STATUS { + NEW, + MOUNTED, // is ready, and in DOM. It has a valid el + DESTROYED, +} + +type STATUS_DESCR = "new" | "mounted" | "destroyed"; + +export function status(component: Component): STATUS_DESCR { + switch (component.__owl__.status) { + case STATUS.NEW: + return "new"; + case STATUS.MOUNTED: + return "mounted"; + case STATUS.DESTROYED: + return "destroyed"; + } +} diff --git a/src/component/styles.ts b/src/component/styles.ts deleted file mode 100644 index 9fd25f33..00000000 --- a/src/component/styles.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Owl Style System - * - * This files contains the Owl code related to processing (extended) css strings - * and creating/adding `); - - await app.mount(fixture); - const style = getComputedStyle(app.el!); - expect(style.color).toBe("red"); - expect(fixture.innerHTML).toBe('
text
'); - }); - - test("inherited components properly apply css", async () => { - class App extends Component { - static template = xml`
text
`; - static style = css` - .app { - color: red; - } - `; - } - class SubApp extends App { - static style = css` - .app { - font-weight: bold; - } - `; - } - expect(document.head.innerHTML).toBe(""); - const app = new SubApp(); - - expect(document.head.innerHTML).toBe(``); - - await app.mount(fixture); - const style = getComputedStyle(app.el!); - expect(style.color).toBe("red"); - expect(style.fontWeight).toBe("bold"); - expect(fixture.innerHTML).toBe('
text
'); - }); - - test("get a meaningful error message if css helper is missing", async () => { - class App extends Component { - static template = xml`
text
`; - static style = `.app {color: red;}`; - } - let error; - try { - new App(); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.message).toBe( - "Invalid css stylesheet for component 'App'. Did you forget to use the 'css' tag helper?" - ); - }); - - test("inline stylesheets are processed", async () => { - class App extends Component { - static template = xml`
text
`; - static style = css` - .app { - color: red; - .some-class { - font-weight: bold; - width: 40px; - } - - display: block; - } - `; - } - new App(); - - expect(document.head.querySelector("style")!.innerHTML).toBe(`.app { - color: red; -} -.app .some-class { - font-weight: bold; - width: 40px; -} -.app { - display: block; -}`); - }); - - test("properly handle rules with commas", async () => { - const sheet = processSheet(`.parent-a, .parent-b { - .child-a, .child-b { - color: red; - } - }`); - - expect(sheet) - .toBe(`.parent-a .child-a, .parent-a .child-b, .parent-b .child-a, .parent-b .child-b { - color: red; -}`); - }); - - test("handle & selector", async () => { - let sheet = processSheet(`.btn { - &.danger { - color: red; - } - }`); - - expect(sheet).toBe(`.btn.danger { - color: red; -}`); - - sheet = processSheet(`.some-class { - &.btn { - .other-class ~ & { - color: red; - } - } - }`); - - expect(sheet).toBe(`.other-class ~ .some-class.btn { - color: red; -}`); - }); -}); diff --git a/tests/component/un_mounting.test.ts b/tests/component/un_mounting.test.ts deleted file mode 100644 index 1ad872a5..00000000 --- a/tests/component/un_mounting.test.ts +++ /dev/null @@ -1,732 +0,0 @@ -import { Component, Env, mount } from "../../src/component/component"; -import { useState } from "../../src/hooks"; -import { xml } from "../../src/tags"; -import { makeDeferred, makeTestEnv, makeTestFixture, nextTick, nextMicroTick } from "../helpers"; -import { scheduler } from "../../src/component/scheduler"; - -//------------------------------------------------------------------------------ -// Setup and helpers -//------------------------------------------------------------------------------ - -// We create before each test: -// - fixture: a div, appended to the DOM, intended to be the target of dom -// manipulations. Note that it is removed after each test. -// - env: a WEnv, necessary to create new components - -let fixture: HTMLElement; -let env: Env; - -beforeEach(() => { - fixture = makeTestFixture(); - env = makeTestEnv(); - Component.env = env; -}); - -afterEach(() => { - fixture.remove(); -}); - -describe("mount targets", () => { - test("can attach a component to an existing node (if same tagname)", async () => { - class App extends Component { - static template = xml`
app

another tag

`; - state = useState({ customClass: "custom" }); - } - const div = document.createElement("div"); - div.classList.add("arbitrary"); - div.innerHTML = `

pre-existing

`; - fixture.appendChild(div); - - const app = await mount(App, { target: div, position: "self" }); - - expect(fixture.innerHTML).toBe( - `

pre-existing

app

another tag

` - ); - expect(div).toBe(app.el); - - app.state.customClass = "custom2"; - await nextTick(); - expect(fixture.innerHTML).toBe( - `

pre-existing

app

another tag

` - ); - expect(div).toBe(app.el); - - app.unmount(); - // This assert is a best guess - // The use case it covers was not really thought through - // and may change in the future - expect(fixture.innerHTML).toBe(""); - }); - - test("cannot attach a component to an existing node (if not same tagname)", async () => { - class App extends Component { - static template = xml`app`; - } - const div = document.createElement("div"); - fixture.appendChild(div); - - let error; - try { - await mount(App, { target: div, position: "self" }); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.message).toBe("Cannot attach 'App' to target node (not same tag name)"); - }); - - test("can mount a component (with position='first-child')", async () => { - class App extends Component { - static template = xml`
app
`; - } - const span = document.createElement("span"); - fixture.appendChild(span); - - await mount(App, { target: fixture, position: "first-child" }); - expect(fixture.innerHTML).toBe("
app
"); - }); - - test("can mount a component (with position='last-child')", async () => { - class App extends Component { - static template = xml`
app
`; - } - const span = document.createElement("span"); - fixture.appendChild(span); - - await mount(App, { target: fixture, position: "last-child" }); - expect(fixture.innerHTML).toBe("
app
"); - }); - - test("default mount option is 'last-child'", async () => { - class App extends Component { - static template = xml`
app
`; - } - const span = document.createElement("span"); - fixture.appendChild(span); - - await mount(App, { target: fixture }); - expect(fixture.innerHTML).toBe("
app
"); - }); -}); - -describe("unmounting and remounting", () => { - test("widget can be unmounted and remounted", async () => { - const steps: string[] = []; - class MyWidget extends Component { - static template = xml`
Hey
`; - async willStart() { - steps.push("willstart"); - } - mounted() { - steps.push("mounted"); - } - willUnmount() { - steps.push("willunmount"); - } - patched() { - throw new Error("patched should not be called"); - } - } - - const w = await mount(MyWidget, { target: fixture }); - expect(fixture.innerHTML).toBe("
Hey
"); - expect(steps).toEqual(["willstart", "mounted"]); - - w.unmount(); - expect(fixture.innerHTML).toBe(""); - expect(steps).toEqual(["willstart", "mounted", "willunmount"]); - - await w.mount(fixture); - expect(fixture.innerHTML).toBe("
Hey
"); - expect(steps).toEqual(["willstart", "mounted", "willunmount", "mounted"]); - }); - - test("widget can be mounted twice without ill effect", async () => { - const steps: string[] = []; - class MyWidget extends Component { - static template = xml`
Hey
`; - async willStart() { - steps.push("willstart"); - } - mounted() { - steps.push("mounted"); - } - willUnmount() { - steps.push("willunmount"); - } - } - - const w = await mount(MyWidget, { target: fixture }); - await w.mount(fixture); - expect(fixture.innerHTML).toBe("
Hey
"); - expect(steps).toEqual(["willstart", "mounted"]); - }); - - test("state changes in willUnmount do not trigger rerender", async () => { - const steps: string[] = []; - - class Child extends Component { - static template = xml` - - `; - state = useState({ n: 2 }); - __render(f) { - steps.push("render"); - return super.__render(f); - } - willPatch() { - steps.push("willPatch"); - } - patched() { - steps.push("patched"); - } - - willUnmount() { - steps.push("willUnmount"); - this.state.n = 3; - } - } - class Parent extends Component { - static template = xml` -
- -
- `; - static components = { Child }; - state = useState({ val: 1, flag: true }); - } - - const widget = await mount(Parent, { target: fixture }); - expect(steps).toEqual(["render"]); - expect(fixture.innerHTML).toBe("
12
"); - widget.state.flag = false; - await nextTick(); - // we make sure here that no call to __render is done - expect(steps).toEqual(["render", "willUnmount"]); - }); - - test("state changes in willUnmount will be applied on remount", async () => { - class TestWidget extends Component { - static template = xml` -
- `; - state = useState({ val: 1 }); - willUnmount() { - this.state.val = 3; - } - } - - const widget = new TestWidget(); - await widget.mount(fixture); - expect(fixture.innerHTML).toBe("
1
"); - widget.unmount(); - expect(fixture.innerHTML).toBe(""); - await nextTick(); // wait for changes to be detected before remounting - await widget.mount(fixture); - expect(fixture.innerHTML).toBe("
3
"); - // we want to make sure that there are no remaining tasks left at this point. - expect(Component.scheduler.tasks.length).toBe(0); - }); - - test("sub component is still active after being unmounted and remounted", async () => { - class Child extends Component { - static template = xml` -

- -

`; - - state = useState({ value: 1 }); - } - - class Parent extends Component { - static components = { Child }; - static template = xml`
`; - } - - const w = new Parent(); - await w.mount(fixture); - expect(fixture.innerHTML).toBe("

1

"); - - fixture.querySelector("p")!.click(); - await nextTick(); - expect(fixture.innerHTML).toBe("

2

"); - w.unmount(); - await nextTick(); - await w.mount(fixture); - expect(fixture.innerHTML).toBe("

2

"); - fixture.querySelector("p")!.click(); - await nextTick(); - expect(fixture.innerHTML).toBe("

3

"); - }); - - test("change state just before mounting component", async () => { - const steps: number[] = []; - class TestWidget extends Component { - static template = xml` -
- `; - state = useState({ val: 1 }); - __render(f) { - steps.push(this.state.val); - return super.__render(f); - } - } - TestWidget.prototype.__render = jest.fn(TestWidget.prototype.__render); - - const widget = new TestWidget(); - widget.state.val = 2; - await widget.mount(fixture); - expect(fixture.innerHTML).toBe("
2
"); - expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(1); - - // unmount and re-mount, as in this case, willStart won't be called, so it's - // slightly different - widget.unmount(); - widget.state.val = 3; - await widget.mount(fixture); - expect(fixture.innerHTML).toBe("
3
"); - expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(2); - expect(steps).toEqual([2, 3]); - }); - - test("change state while mounting component", async () => { - const steps: number[] = []; - class TestWidget extends Component { - static template = xml` -
- `; - state = useState({ val: 1 }); - __render(f) { - steps.push(this.state.val); - return super.__render(f); - } - } - TestWidget.prototype.__render = jest.fn(TestWidget.prototype.__render); - TestWidget.prototype.__patch = jest.fn(TestWidget.prototype.__patch); - - const widget = new TestWidget(); - let prom = widget.mount(fixture); - widget.state.val = 2; - await prom; - expect(fixture.innerHTML).toBe("
2
"); - expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(1); - - // unmount and re-mount, as in this case, willStart won't be called, so it's - // slightly different - widget.unmount(); - prom = widget.mount(fixture); - widget.state.val = 3; - await prom; - expect(fixture.innerHTML).toBe("
3
"); - expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(3); - expect(TestWidget.prototype.__patch).toHaveBeenCalledTimes(2); - expect(steps).toEqual([2, 2, 3]); - }); - - test("change state and render while mounted in detached dom", async () => { - class App extends Component { - static template = xml`
`; - state = useState({ val: 1 }); - } - - const detachedDiv = document.createElement("div"); - const app = await mount(App, { target: detachedDiv }); - - expect(detachedDiv.innerHTML).toBe("
1
"); - app.state.val = 2; - await nextTick(); - expect(detachedDiv.innerHTML).toBe("
2
"); - }); - - test("change state and render while not mounted ", async () => { - class App extends Component { - static template = xml`
`; - state = useState({ val: 1 }); - } - - const app = new App(null); - - app.state.val = 2; // will call the render method (before being mounted) - await nextTick(); - - await app.mount(fixture); - - expect(fixture.innerHTML).toBe("
2
"); - }); - - test("destroy and change state after mounted in detached dom", async () => { - class App extends Component { - static template = xml`
`; - state = useState({ val: 1 }); - } - - const detachedDiv = document.createElement("div"); - const app = await mount(App, { target: detachedDiv }); - - expect(detachedDiv.innerHTML).toBe("
1
"); - - app.destroy(); - app.state.val = 2; - await nextTick(); - expect(detachedDiv.innerHTML).toBe(""); - }); - - test("change state while component is unmounted", async () => { - let child; - class Child extends Component { - static template = xml``; - state = useState({ - val: "C1", - }); - constructor(parent, props) { - super(parent, props); - child = this; - } - } - - class Parent extends Component { - static components = { Child }; - static template = xml`
`; - state = useState({ val: "P1" }); - } - - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
P1C1
"); - - parent.unmount(); - expect(fixture.innerHTML).toBe(""); - - parent.state.val = "P2"; - child.state.val = "C2"; - - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
P2C2
"); - }); - - test("change state while component is mounted in a fragment", async () => { - class Child1 extends Component { - static template = xml`C1`; - } - - class Child2 extends Component { - static template = xml`C2`; - } - - class Parent extends Component { - static components = { Child1, Child2 }; - static template = xml` -
- - -
`; - child: string | false = false; - } - - const fragment = document.createDocumentFragment(); - const parent = new Parent(); - await parent.mount(fragment); - expect(parent.el.outerHTML).toBe("
"); - - parent.child = "c1"; - parent.render(); - await Promise.resolve(); - - parent.child = "c2"; - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
C2
"); - }); - - test("unmount component during a re-rendering", async () => { - const def = makeDeferred(); - class Child extends Component { - static template = xml``; - willUpdateProps() { - return def; - } - } - Child.prototype.__render = jest.fn(Child.prototype.__render); - - class Parent extends Component { - static template = xml`
`; - static components = { Child }; - state = useState({ val: 1 }); - } - - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
1
"); - expect(Child.prototype.__render).toBeCalledTimes(1); - - parent.state.val = 2; - await nextTick(); - expect(fixture.innerHTML).toBe("
1
"); - - parent.unmount(); - expect(fixture.innerHTML).toBe(""); - - def.resolve(); - await nextTick(); - expect(fixture.innerHTML).toBe(""); - expect(Child.prototype.__render).toBeCalledTimes(1); - }); - - test("widget can be mounted on different target", async () => { - class MyWidget extends Component { - static template = xml`
Hey
`; - patched() { - throw new Error("patched should not be called"); - } - } - const div = document.createElement("div"); - const span = document.createElement("span"); - fixture.appendChild(div); - fixture.appendChild(span); - const w = new MyWidget(); - await w.mount(div); - - expect(fixture.innerHTML).toBe("
Hey
"); - - await w.mount(span); - expect(fixture.innerHTML).toBe("
Hey
"); - }); - - test("widget can be mounted on different target, another situation", async () => { - const def = makeDeferred(); - const steps: string[] = []; - - class MyWidget extends Component { - static template = xml`
Hey
`; - async willStart() { - return def; - } - patched() { - throw new Error("patched should not be called"); - } - } - const div = document.createElement("div"); - const span = document.createElement("span"); - fixture.appendChild(div); - fixture.appendChild(span); - const w = new MyWidget(); - - w.mount(div).catch(() => steps.push("1 catch")); - - await nextTick(); - expect(fixture.innerHTML).toBe("
"); - - w.mount(span).then(() => steps.push("2 resolved")); - - // we wait two microticks because this is the number of internal promises - // that need to be resolved/rejected, and because we want to prove here - // that the first mount operation is cancelled immediately, and not after - // one full tick. - await nextMicroTick(); - await nextMicroTick(); - expect(steps).toEqual([]); - await nextTick(); - expect(fixture.innerHTML).toBe("
"); - - def.resolve(); - await nextTick(); - expect(steps).toEqual(["2 resolved"]); - expect(fixture.innerHTML).toBe("
Hey
"); - }); - - test("component can be mounted on same target, another situation", async () => { - const def = makeDeferred(); - const steps: string[] = []; - - class MyWidget extends Component { - static template = xml`
Hey
`; - async willStart() { - return def; - } - patched() { - throw new Error("patched should not be called"); - } - } - const w = new MyWidget(); - - w.mount(fixture).then(() => steps.push("1 resolved")); - - await nextTick(); - expect(fixture.innerHTML).toBe(""); - - w.mount(fixture).then(() => steps.push("2 resolved")); - - await nextTick(); - expect(steps).toEqual([]); - expect(fixture.innerHTML).toBe(""); - - def.resolve(); - await nextTick(); - expect(fixture.innerHTML).toBe("
Hey
"); - expect(steps).toEqual(["1 resolved", "2 resolved"]); - }); - - test("mounting a destroyed widget", async () => { - class MyWidget extends Component { - static template = xml`
Hey
`; - } - const w = new MyWidget(); - w.destroy(); // because, why not - - let error; - try { - await w.mount(fixture); - } catch (e) { - error = e; - } - expect(scheduler.tasks.length).toBe(0); - expect(error).toBeDefined(); - expect(error.message).toBe("Cannot mount a destroyed component"); - }); - - test("destroying a sub-component cleans itself from parent's vnode", async () => { - class C1 extends Component { - static template = xml`
`; - } - class P extends Component { - static components = { C1 }; - static template = xml`
`; - state = { - a: "first", - }; - } - const parent = new P(); - await parent.mount(fixture); - expect(fixture.textContent).toBe("first"); - parent.unmount(); - parent.state.a = ""; - parent.mount(fixture); - parent.state.a = "fixed"; - await parent.render(); - expect(fixture.textContent).toBe("fixed"); - }); - - test("destroying a sub-component cleans itself from parent's vnode, part 2", async () => { - class C1 extends Component { - static template = xml`
`; - } - class P extends Component { - static components = { C1 }; - static template = xml`
some text
`; - state = { - a: "first", - }; - } - const parent = new P(); - await parent.mount(fixture); - expect(fixture.textContent).toBe("firstsome text"); - parent.unmount(); - parent.state.a = ""; - parent.mount(fixture); - parent.state.a = "fixed"; - await parent.render(); - expect(fixture.textContent).toBe("fixedsome text"); - }); - - test("destroying a sub-component cleans itself from parent's vnode, part 3", async () => { - class C1 extends Component { - static template = xml`
`; - } - - class C2 extends Component { - static template = xml``; - static components = { C1 }; - } - - class P extends Component { - static components = { C2 }; - static template = xml`
`; - state = { - a: "first", - }; - } - const parent = new P(); - await parent.mount(fixture); - expect(fixture.textContent).toBe("first"); - parent.unmount(); - parent.state.a = ""; - parent.mount(fixture); - parent.state.a = "fixed"; - await parent.render(); - expect(fixture.textContent).toBe("fixed"); - }); - - test("destroying a sub-component cleans itself from parent's vnode, part 4", async () => { - class C1 extends Component { - static template = xml`
`; - } - - class C2 extends Component { - static template = xml``; - static components = { C1 }; - } - class P extends Component { - static components = { C2 }; - static template = xml`
some text
`; - state = { - a: "first", - }; - } - const parent = new P(); - await parent.mount(fixture); - expect(fixture.textContent).toBe("firstsome text"); - parent.unmount(); - parent.state.a = ""; - parent.mount(fixture); - parent.state.a = "fixed"; - await parent.render(); - expect(fixture.textContent).toBe("fixedsome text"); - }); - - test("remounting component tree where a component implement shouldupdate", async () => { - let state: any; - const steps = []; - class Child extends Component { - static template = xml`
`; - - state = useState({ word: "hello" }); - - constructor(parent, props) { - super(parent, props); - state = this.state; - } - patched() { - steps.push("patched"); - } - mounted() { - steps.push("mounted"); - } - willUnmount() { - steps.push("willUnmount"); - } - shouldUpdate() { - return false; - } - } - - class Parent extends Component { - static template = xml`
`; - static components = { Child }; - state = useState({ name: "World" }); - } - - const parent = await mount(Parent, { target: fixture }); - expect(fixture.innerHTML).toBe("
helloWorld
"); - - parent.unmount(); - expect(fixture.innerHTML).toBe(""); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
helloWorld
"); - - state.word = "test"; - - await nextTick(); - expect(fixture.innerHTML).toBe("
testWorld
"); - expect(steps).toEqual(["mounted", "willUnmount", "mounted", "patched"]); - }); -}); diff --git a/tests/components/__snapshots__/app.test.ts.snap b/tests/components/__snapshots__/app.test.ts.snap new file mode 100644 index 00000000..03f8245d --- /dev/null +++ b/tests/components/__snapshots__/app.test.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app destroy remove the widget from the DOM 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; diff --git a/tests/components/__snapshots__/basics.test.ts.snap b/tests/components/__snapshots__/basics.test.ts.snap new file mode 100644 index 00000000..5158f02e --- /dev/null +++ b/tests/components/__snapshots__/basics.test.ts.snap @@ -0,0 +1,929 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`basics Multi root component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`1\`); + let block4 = createBlock(\`2\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = block2(); + let b3 = text(\`text\`); + let b4 = block4(); + return multi([b2, b3, b4]); + } +}" +`; + +exports[`basics a class component inside a class component, no external dom 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
simple vnode
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics a class component inside a class component, no external dom 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics a component cannot be mounted in a detached node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics a component inside a component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
simple vnode
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics a component inside a component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`basics can be clicked on and updated 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].counter; + const v1 = ctx['state']; + let d2 = (e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}; + return block1([d1, d2]); + } +}" +`; + +exports[`basics can handle empty props 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`basics can handle empty props 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {val: undefined}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`basics can mount a component with just some text 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`just text\`); + } +}" +`; + +exports[`basics can mount a component with no text 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`\`); + } +}" +`; + +exports[`basics can mount a simple component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`simple vnode\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics can mount a simple component with multiple roots 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`\`); + let block3 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = block2(); + let b3 = block3(); + return multi([b2, b3]); + } +}" +`; + +exports[`basics can mount a simple component with props 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].value; + return block1([d1]); + } +}" +`; + +exports[`basics cannot mount on a documentFragment 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
content
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics child can be updated 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(ctx['props'].value); + } +}" +`; + +exports[`basics child can be updated 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {value: ctx['state'].counter}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics class component with dynamic text 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`My value: \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`basics class parent, class child component with props 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].value; + return block1([d1]); + } +}" +`; + +exports[`basics class parent, class child component with props 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {value: 42}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics component with dynamic content can be updated 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`basics do not remove previously rendered dom if not necessary 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics do not remove previously rendered dom if not necessary, variation 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

h1

\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].value; + return block1([d1]); + } +}" +`; + +exports[`basics has no el after creation 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`simple\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics higher order components parent and child 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
a
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics higher order components parent and child 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`b\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics higher order components parent and child 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['props'].child==='a') { + b2 = component(\`ChildA\`, {}, key + \`__1\`, node, ctx); + } else { + b3 = component(\`ChildB\`, {}, key + \`__2\`, node, ctx); + } + return multi([b2, b3]); + } +}" +`; + +exports[`basics higher order components parent and child 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {child: ctx['state'].child}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics parent, child and grandchild 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
hey
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics parent, child and grandchild 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics parent, child and grandchild 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics props is set on root component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`simple vnode\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics reconciliation alg is not confused in some specific situation 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics reconciliation alg is not confused in some specific situation 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + let b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx); + return block1([], [b2, b3]); + } +}" +`; + +exports[`basics same t-keys in two different places 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].blip; + return block1([d1]); + } +}" +`; + +exports[`basics same t-keys in two different places 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {blip: '1'}, key + \`__1\`, node, ctx); + let b3 = component(\`Child\`, {blip: '2'}, key + \`__2\`, node, ctx); + return block1([], [b2, b3]); + } +}" +`; + +exports[`basics simple component with a dynamic text 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`basics simple component, useState 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].value; + return block1([d1]); + } +}" +`; + +exports[`basics some simple sanity checks (el/status) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`simple vnode\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics t-elif works with t-component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`hey\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics t-elif works with t-component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`
somediv
\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['state'].flag) { + b2 = block2(); + } else if (!ctx['state'].flag) { + b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`basics t-else with empty string works with t-component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`hey\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics t-else with empty string works with t-component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`
somediv
\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['state'].flag) { + b2 = block2(); + } else { + b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`basics t-else works with t-component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`hey\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics t-else works with t-component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`
somediv
\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['state'].flag) { + b2 = block2(); + } else { + b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`basics t-if works with t-component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`hey\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics t-if works with t-component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].flag) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return block1([], [b2]); + } +}" +`; + +exports[`basics t-key on a component with t-if, and a sibling component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics t-key on a component with t-if, and a sibling component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (false) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx); + return block1([], [b2, b3]); + } +}" +`; + +exports[`basics text after a conditional component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

simple vnode

\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics text after a conditional component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].hasChild) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + let d1 = ctx['state'].text; + return block1([d1], [b2]); + } +}" +`; + +exports[`basics three level of components with collapsing root nodes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
2
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics three level of components with collapsing root nodes 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics three level of components with collapsing root nodes 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics throws if mounting on target=null 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`simple vnode\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics two child components 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
simple vnode
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics two child components 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + let b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx); + return multi([b2, b3]); + } +}" +`; + +exports[`basics updating a component with t-foreach as root 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k1, v1, l1, c1] = prepareList(ctx['items']); + for (let i1 = 0; i1 < l1; i1++) { + ctx[\`item\`] = v1[i1]; + let key1 = ctx['item']; + c1[i1] = withKey(text(ctx['item']), key1); + } + return list(c1); + } +}" +`; + +exports[`basics widget after a t-foreach 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics widget after a t-foreach 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(Array(2)); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`elem\`] = v2[i1]; + ctx[\`elem_index\`] = i1; + let key1 = ctx['elem_index']; + c2[i1] = withKey(text(\`txt\`), key1); + } + ctx = ctx.__proto__; + let b2 = list(c2); + let b4 = component(\`SomeComponent\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2, b4]); + } +}" +`; + +exports[`basics zero or one child components 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
simple vnode
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics zero or one child components 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].hasChild) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return multi([b2]); + } +}" +`; diff --git a/tests/components/__snapshots__/concurrency.test.ts.snap b/tests/components/__snapshots__/concurrency.test.ts.snap new file mode 100644 index 00000000..d99f7854 --- /dev/null +++ b/tests/components/__snapshots__/concurrency.test.ts.snap @@ -0,0 +1,1193 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`async rendering destroying a widget before start is over 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`change state and call manually render: no unnecessary rendering 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`changing state before first render does not trigger a render (with parent) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`changing state before first render does not trigger a render (with parent) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].flag) { + b2 = component(\`TestW\`, {}, key + \`__1\`, node, ctx); + } + return block1([], [b2]); + } +}" +`; + +exports[`changing state before first render does not trigger a render 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`concurrent renderings scenario 1 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['someValue'](); + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 1 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 1 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['props'].fromB; + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 2 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].fromA; + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([d1], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 2bis 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['props'].fromB; + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 2bis 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 2bis 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['someValue'](); + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 3 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 3 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 3 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 4 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['someValue'](); + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 4 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 4 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 4 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 5 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['someValue'](); + return block1([d1]); + } +}" +`; + +exports[`concurrent renderings scenario 5 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 6 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['someValue'](); + return block1([d1]); + } +}" +`; + +exports[`concurrent renderings scenario 6 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 7 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['someValue'](); + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 7 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 8 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['state'].fromB; + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 8 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 9 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['props'].fromC; + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 9 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 9 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + return block1([d1]); + } +}" +`; + +exports[`concurrent renderings scenario 9 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].fromA; + let b2 = component(\`ComponentB\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + let b3 = component(\`ComponentC\`, {fromA: ctx['state'].fromA}, key + \`__2\`, node, ctx); + return block1([d1], [b2, b3]); + } +}" +`; + +exports[`concurrent renderings scenario 10 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`concurrent renderings scenario 10 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].hasChild) { + b2 = component(\`ComponentC\`, {value: ctx['props'].value}, key + \`__1\`, node, ctx); + } + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 10 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 11 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`|\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + let d2 = ctx['val']; + return block1([d1, d2]); + } +}" +`; + +exports[`concurrent renderings scenario 11 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 12 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`concurrent renderings scenario 12 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {val: ctx['val']}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 13 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].val; + return block1([d1]); + } +}" +`; + +exports[`concurrent renderings scenario 13 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + if (ctx['state'].bool) { + b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`concurrent renderings scenario 14 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['props'].fromB; + let d3 = ctx['state'].fromC; + return block1([d1, d2, d3]); + } +}" +`; + +exports[`concurrent renderings scenario 14 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 14 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`B\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 15 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].fromA; + let d2 = ctx['props'].fromB; + let d3 = ctx['state'].fromC; + return block1([d1, d2, d3]); + } +}" +`; + +exports[`concurrent renderings scenario 15 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 15 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`B\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 16 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    DDD
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`concurrent renderings scenario 16 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + let d1 = ctx['props'].fromA; + let d2 = ctx['props'].fromB; + let d3 = ctx['state'].fromC; + if (ctx['state'].fromC===13) { + b2 = component(\`D\`, {}, key + \`__1\`, node, ctx); + } + return block1([d1, d2, d3], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 16 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`concurrent renderings scenario 16 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`B\`, {fromA: ctx['state'].fromA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`creating two async components, scenario 1 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['getValue'](); + return block1([d1]); + } +}" +`; + +exports[`creating two async components, scenario 1 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`b\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`creating two async components, scenario 1 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['state'].flagA) { + b2 = component(\`ChildA\`, {}, key + \`__1\`, node, ctx); + } + if (ctx['state'].flagB) { + b3 = component(\`ChildB\`, {}, key + \`__2\`, node, ctx); + } + return multi([b2, b3]); + } +}" +`; + +exports[`creating two async components, scenario 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`a\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`creating two async components, scenario 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`b\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`creating two async components, scenario 2 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx); + if (ctx['state'].flagB) { + b3 = component(\`ChildB\`, {val: ctx['state'].valB}, key + \`__2\`, node, ctx); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`creating two async components, scenario 3 (patching in the same frame) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`a\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`creating two async components, scenario 3 (patching in the same frame) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`b\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`creating two async components, scenario 3 (patching in the same frame) 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx); + if (ctx['state'].flagB) { + b3 = component(\`ChildB\`, {val: ctx['state'].valB}, key + \`__2\`, node, ctx); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`destroying/recreating a subwidget with different props (if start is not over) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child:\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`destroying/recreating a subwidget with different props (if start is not over) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].val>1) { + b2 = component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); + } + return block1([], [b2]); + } +}" +`; + +exports[`properly behave when destroyed/unmounted while rendering 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`properly behave when destroyed/unmounted while rendering 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`SubChild\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`properly behave when destroyed/unmounted while rendering 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].flag) { + b2 = component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); + } + return block1([], [b2]); + } +}" +`; + +exports[`render method wait until rendering is done 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].drinks; + return block1([d1]); + } +}" +`; + +exports[`rendering component again in next microtick 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
Child
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`rendering component again in next microtick 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + let d1 = [ctx, 'onClick']; + if (ctx['env'].flag) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return block1([d1], [b2]); + } +}" +`; + +exports[`two renderings initiated between willPatch and patched 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`two renderings initiated between willPatch and patched 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].flag) { + b2 = component(\`Panel\`, {val: ctx['state'].panel}, key + \`__1\`, node, ctx); + } + return block1([], [b2]); + } +}" +`; + +exports[`update a sub-component twice in the same frame 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`update a sub-component twice in the same frame 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`update a sub-component twice in the same frame, 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['val'](); + return block1([d1]); + } +}" +`; + +exports[`update a sub-component twice in the same frame, 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ChildA\`, {val: ctx['state'].valA}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; diff --git a/tests/components/__snapshots__/error_handling.test.ts.snap b/tests/components/__snapshots__/error_handling.test.ts.snap new file mode 100644 index 00000000..f39ed6f7 --- /dev/null +++ b/tests/components/__snapshots__/error_handling.test.ts.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`basics no component catching error lead to full app destruction 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
hey
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].flag&&ctx['state'].this.will.crash; + return block1([d1]); + } +}" +`; + +exports[`basics no component catching error lead to full app destruction 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; diff --git a/tests/components/__snapshots__/event_handling.test.ts.snap b/tests/components/__snapshots__/event_handling.test.ts.snap new file mode 100644 index 00000000..f4fdcc0d --- /dev/null +++ b/tests/components/__snapshots__/event_handling.test.ts.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`event handling can set handler on sub component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
simple vnode
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`event handling can set handler on sub component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + return function template(ctx, node, key = \\"\\") { + let h2 = [\`click\`, ctx, 'inc']; + let b2 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {handlers: [h2]}); + let b3 = text(ctx['state'].value); + return multi([b2, b3]); + } +}" +`; + +exports[`event handling handler receive the event as argument 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
simple vnode
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`event handling handler receive the event as argument 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + return function template(ctx, node, key = \\"\\") { + let h2 = [\`click\`, ctx, 'inc']; + let b2 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {handlers: [h2]}); + let b3 = text(ctx['state'].value); + return multi([b2, b3]); + } +}" +`; + +exports[`event handling support for callable expression in event handler 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].value; + const v1 = ctx['obj']; + let d2 = (e) => {const res = (() => { return v1.onInput })(); if (typeof res === 'function') { res(e) }}; + return block1([d1, d2]); + } +}" +`; diff --git a/tests/components/__snapshots__/higher_order_component.test.ts.snap b/tests/components/__snapshots__/higher_order_component.test.ts.snap new file mode 100644 index 00000000..64ad0d18 --- /dev/null +++ b/tests/components/__snapshots__/higher_order_component.test.ts.snap @@ -0,0 +1,191 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`basics basic use 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].p; + return block1([d1]); + } +}" +`; + +exports[`basics basic use 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {p: 1}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics can select a sub widget 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`CHILD 1\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics can select a sub widget 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
CHILD 2
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics can select a sub widget 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['env'].flag) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + if (!ctx['env'].flag) { + b3 = component(\`OtherChild\`, {}, key + \`__2\`, node, ctx); + } + return multi([b2, b3]); + } +}" +`; + +exports[`basics can select a sub widget, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`CHILD 1\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics can select a sub widget, part 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
CHILD 2
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics can select a sub widget, part 2 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['state'].flag) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + if (!ctx['state'].flag) { + b3 = component(\`OtherChild\`, {}, key + \`__2\`, node, ctx); + } + return multi([b2, b3]); + } +}" +`; + +exports[`basics sub widget is interactive 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'inc']; + let d2 = ctx['state'].val; + return block1([d1, d2]); + } +}" +`; + +exports[`basics sub widget is interactive 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {p: 1}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics top level sub widget with a parent 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`Hello\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`basics top level sub widget with a parent 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`ComponentC\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`basics top level sub widget with a parent 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ComponentB\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; diff --git a/tests/components/__snapshots__/lifecycle.test.ts.snap b/tests/components/__snapshots__/lifecycle.test.ts.snap new file mode 100644 index 00000000..582b8181 --- /dev/null +++ b/tests/components/__snapshots__/lifecycle.test.ts.snap @@ -0,0 +1,641 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lifecycle hooks basic checks for a component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`test\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks components are unmounted and destroyed if no longer in DOM 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks components are unmounted and destroyed if no longer in DOM 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].ok) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return multi([b2]); + } +}" +`; + +exports[`lifecycle hooks components are unmounted and destroyed if no longer in DOM, even after updateprops 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].n; + return block1([d1]); + } +}" +`; + +exports[`lifecycle hooks components are unmounted and destroyed if no longer in DOM, even after updateprops 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].flag) { + let b3 = component(\`Child\`, {n: ctx['state'].n}, key + \`__1\`, node, ctx); + b2 = block2([], [b3]); + } + return multi([b2]); + } +}" +`; + +exports[`lifecycle hooks hooks are called in proper order in widget creation/destruction 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks hooks are called in proper order in widget creation/destruction 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {a: ctx['state'].a}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 2 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].hasChild) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return multi([b2]); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 3 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 3 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].hasChild) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return multi([b2]); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 4 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 4 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 4 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].hasChild) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return multi([b2]); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 5 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 5 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].hasChild) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return multi([b2]); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 6 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks lifecycle semantics, part 6 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`lifecycle hooks mounted hook is called if mounted in DOM 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks mounted hook is called on subcomponents, in proper order 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks mounted hook is called on subcomponents, in proper order 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`lifecycle hooks mounted hook is called on subsubcomponents, in proper order 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks mounted hook is called on subsubcomponents, in proper order 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ChildChild\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`lifecycle hooks mounted hook is called on subsubcomponents, in proper order 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['state'].flag) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } + return block1([], [b2]); + } +}" +`; + +exports[`lifecycle hooks onRender 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'increment']; + let d2 = ctx['state'].value; + return block1([d1, d2]); + } +}" +`; + +exports[`lifecycle hooks onRender 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`lifecycle hooks patched hook is called after updateProps 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks patched hook is called after updateProps 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {a: ctx['state'].a}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`lifecycle hooks patched hook is called after updating State 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks willPatch, patched hook are called on subsubcomponents, in proper order 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].n; + return block1([d1]); + } +}" +`; + +exports[`lifecycle hooks willPatch, patched hook are called on subsubcomponents, in proper order 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`ChildChild\`, {n: ctx['props'].n}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`lifecycle hooks willPatch, patched hook are called on subsubcomponents, in proper order 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {n: ctx['state'].n}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`lifecycle hooks willStart hook is called on sub component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks willStart hook is called on sub component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`lifecycle hooks willStart is called 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`simple vnode\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks willStart is called with component as this 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`simple vnode\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks willStart, mounted on subwidget rendered after main is mounted in some other position 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`lifecycle hooks willStart, mounted on subwidget rendered after main is mounted in some other position 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + let block3 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['state'].ok) { + b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + } else { + b3 = block3(); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`lifecycle hooks willUpdateProps hook is called 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].n; + return block1([d1]); + } +}" +`; + +exports[`lifecycle hooks willUpdateProps hook is called 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {n: ctx['state'].n}, key + \`__1\`, node, ctx); + } +}" +`; diff --git a/tests/components/__snapshots__/props.test.ts.snap b/tests/components/__snapshots__/props.test.ts.snap new file mode 100644 index 00000000..ea2de893 --- /dev/null +++ b/tests/components/__snapshots__/props.test.ts.snap @@ -0,0 +1,160 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`basics accept ES6-like syntax for props (with getters) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].greetings; + return block1([d1]); + } +}" +`; + +exports[`basics accept ES6-like syntax for props (with getters) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {greetings: ctx['greetings']}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`basics explicit object prop 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].someval; + return block1([d1]); + } +}" +`; + +exports[`basics explicit object prop 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {value: ctx['state'].val}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`basics t-set with a body expression can be passed in props, and then t-raw 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + let b2 = html(ctx['props'].val); + return block1([d1], [b2]); + } +}" +`; + +exports[`basics t-set with a body expression can be passed in props, and then t-raw 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`

43

\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = block2(); + ctx[\`abc\`] = b2; + let b3 = component(\`Child\`, {val: ctx['abc']}, key + \`__1\`, node, ctx); + return block1([], [b3]); + } +}" +`; + +exports[`basics t-set with a body expression can be used as textual prop 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`basics t-set with a body expression can be used as textual prop 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`abc\`] = \`42\`; + let b2 = component(\`Child\`, {val: ctx['abc']}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`basics t-set works 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`basics t-set works 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`val\`] = 42; + let b2 = component(\`Child\`, {val: ctx['val']}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; diff --git a/tests/components/__snapshots__/refs.test.ts.snap b/tests/components/__snapshots__/refs.test.ts.snap new file mode 100644 index 00000000..fd0d90e4 --- /dev/null +++ b/tests/components/__snapshots__/refs.test.ts.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refs refs are properly bound in slots 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'footer'); + return block1([], [b2]); + } +}" +`; + +exports[`refs refs are properly bound in slots 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`\`); + + const slot3 = ctx => node => { + const refs = ctx.__owl__.refs + let d2 = [ctx, 'doSomething']; + let d3 = (el) => refs[\`myButton\`] = el; + return block2([d2, d3]); + } + + return function template(ctx, node, key = \\"\\") { + const refs = ctx.__owl__.refs; + let d1 = ctx['state'].val; + const ctx2 = capture(ctx); + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'footer': slot3(ctx2)}}); + return block1([d1], [b3]); + } +}" +`; diff --git a/tests/components/__snapshots__/slots.test.ts.snap b/tests/components/__snapshots__/slots.test.ts.snap new file mode 100644 index 00000000..0f3feb3d --- /dev/null +++ b/tests/components/__snapshots__/slots.test.ts.snap @@ -0,0 +1,2202 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`slots can define and call slots 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'header'); + let b3 = callSlot(ctx, node, key, 'footer'); + return block1([], [b2, b3]); + } +}" +`; + +exports[`slots can define and call slots 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`header\`); + let block3 = createBlock(\`footer\`); + + const slot3 = ctx => (node, key) => { + return block2(); + } + + const slot4 = ctx => (node, key) => { + return block3(); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b4 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'header': slot3(ctx2), 'footer': slot4(ctx2)}}); + return block1([], [b4]); + } +}" +`; + +exports[`slots content is the default slot (variation) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return callSlot(ctx, node, key, 'default'); + } +}" +`; + +exports[`slots content is the default slot (variation) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`sts rocks\`); + + const slot2 = ctx => (node, key) => { + return block1(); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots content is the default slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots content is the default slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`sts rocks\`); + + const slot2 = ctx => (node, key) => { + return block2(); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots dafault slots can define a default content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + const defaultSlot1 = ctx => { + return text(\`default content\`); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = callSlot(ctx, node, key, 'default', defaultSlot1, false); + return block1([], [b3]); + } +}" +`; + +exports[`slots dafault slots can define a default content 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Dialog\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`slots default content is not rendered if named slot is provided 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + const defaultSlot1 = ctx => { + return text(\`default content\`); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = callSlot(ctx, node, key, 'header', defaultSlot1, false); + return block1([], [b3]); + } +}" +`; + +exports[`slots default content is not rendered if named slot is provided 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + + const slot3 = ctx => (node, key) => { + return text(\`hey\`); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'header': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots default content is not rendered if slot is provided 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + const defaultSlot1 = ctx => { + return text(\`default content\`); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = callSlot(ctx, node, key, 'default', defaultSlot1, false); + return block1([], [b3]); + } +}" +`; + +exports[`slots default content is not rendered if slot is provided 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + + const slot2 = ctx => (node, key) => { + return text(\`hey\`); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots default slot next to named slot, with default content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + const defaultSlot1 = ctx => { + return text(\` Default content \`); + } + + const defaultSlot2 = ctx => { + return text(\` Default footer \`); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = callSlot(ctx, node, key, 'default', defaultSlot1, false); + let b5 = callSlot(ctx, node, key, 'footer', defaultSlot2, false); + return block1([], [b3, b5]); + } +}" +`; + +exports[`slots default slot next to named slot, with default content 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + + const slot3 = ctx => (node, key) => { + return text(\` Overridden footer \`); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'footer': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots default slot work with text nodes (variation) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return callSlot(ctx, node, key, 'default'); + } +}" +`; + +exports[`slots default slot work with text nodes (variation) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return text(\`sts rocks\`); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots default slot work with text nodes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots default slot work with text nodes 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + + const slot2 = ctx => (node, key) => { + return text(\`sts rocks\`); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots dynamic t-slot call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'toggle']; + const slot1 = (ctx['current'].slot); + let b2 = toggler(slot1, callSlot(ctx, node, key, slot1)); + return block1([d1], [b2]); + } +}" +`; + +exports[`slots dynamic t-slot call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block3 = createBlock(\`

slot1

\`); + let block4 = createBlock(\`content\`); + let block5 = createBlock(\`

slot2

\`); + + const slot3 = ctx => (node, key) => { + let b3 = block3(); + let b4 = block4(); + return multi([b3, b4]); + } + + const slot4 = ctx => (node, key) => { + return block5(); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b6 = assign(component(\`Toggler\`, {}, key + \`__1\`, node, ctx), {slots: {'slot1': slot3(ctx2), 'slot2': slot4(ctx2)}}); + return block1([], [b6]); + } +}" +`; + +exports[`slots dynamic t-slot call with default 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + const defaultSlot1 = ctx => { + return text(\` owl \`); + } + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'toggle']; + let b3 = callSlot(ctx, node, key, (ctx['current'].slot), defaultSlot1, true); + return block1([d1], [b3]); + } +}" +`; + +exports[`slots dynamic t-slot call with default 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block3 = createBlock(\`

slot1

\`); + let block4 = createBlock(\`content\`); + let block5 = createBlock(\`

slot2

\`); + + const slot3 = ctx => (node, key) => { + let b3 = block3(); + let b4 = block4(); + return multi([b3, b4]); + } + + const slot4 = ctx => (node, key) => { + return block5(); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b6 = assign(component(\`Toggler\`, {}, key + \`__1\`, node, ctx), {slots: {'slot1': slot3(ctx2), 'slot2': slot4(ctx2)}}); + return block1([], [b6]); + } +}" +`; + +exports[`slots fun: two calls to the same slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + let b3 = callSlot(ctx, node, key, 'default'); + return multi([b2, b3]); + } +}" +`; + +exports[`slots fun: two calls to the same slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return text(\`some text\`); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots missing slots are ignored 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`some content\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + let b3 = callSlot(ctx, node, key, 'footer'); + return block1([], [b2, b3]); + } +}" +`; + +exports[`slots missing slots are ignored 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Dialog\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`slots multiple roots are allowed in a default slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots multiple roots are allowed in a default slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block3 = createBlock(\`sts\`); + let block4 = createBlock(\`rocks\`); + + const slot2 = ctx => (node, key) => { + let b3 = block3(); + let b4 = block4(); + return multi([b3, b4]); + } + + return function template(ctx, node, key = \\"\\") { + let b5 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b5]); + } +}" +`; + +exports[`slots multiple roots are allowed in a named slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'content'); + return block1([], [b2]); + } +}" +`; + +exports[`slots multiple roots are allowed in a named slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block3 = createBlock(\`sts\`); + let block4 = createBlock(\`rocks\`); + + const slot3 = ctx => (node, key) => { + let b3 = block3(); + let b4 = block4(); + return multi([b3, b4]); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b5 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'content': slot3(ctx2)}}); + return block1([], [b5]); + } +}" +`; + +exports[`slots multiple slots containing components 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`slots multiple slots containing components 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 's1'); + let b3 = callSlot(ctx, node, key, 's2'); + return block1([], [b2, b3]); + } +}" +`; + +exports[`slots multiple slots containing components 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot3 = ctx => (node, key) => { + return component(\`C\`, {val: 1}, key + \`__4\`, node, ctx); + } + + const slot5 = ctx => (node, key) => { + return component(\`C\`, {val: 2}, key + \`__6\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + return assign(component(\`B\`, {}, key + \`__1\`, node, ctx), {slots: {'s1': slot3(ctx2), 's2': slot5(ctx2)}}); + } +}" +`; + +exports[`slots named slot inside slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'brol'); + let b3 = callSlot(ctx, node, key, 'default'); + return block1([], [b2, b3]); + } +}" +`; + +exports[`slots named slot inside slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`

A

\`); + let block3 = createBlock(\`

B

\`); + + const slot3 = ctx => (node, key) => { + let d1 = ctx['value']; + return block2([d1]); + } + + const slot4 = ctx => (node, key) => { + const ctx6 = capture(ctx); + return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx), {slots: {'brol': slot7(ctx6)}}); + } + + const slot7 = ctx => (node, key) => { + let d2 = ctx['value']; + return block3([d2]); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'brol': slot3(ctx2), 'default': slot4(ctx2)}}); + return block1([], [b5]); + } +}" +`; + +exports[`slots named slot inside slot, part 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'brol'); + let b3 = callSlot(ctx, node, key, 'default'); + return block1([], [b2, b3]); + } +}" +`; + +exports[`slots named slot inside slot, part 3 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`

A

\`); + let block3 = createBlock(\`

B

\`); + + const slot3 = ctx => (node, key) => { + let d1 = ctx['value']; + return block2([d1]); + } + + const slot4 = ctx => (node, key) => { + const ctx6 = capture(ctx); + return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx), {slots: {'brol': slot7(ctx6)}}); + } + + const slot7 = ctx => (node, key) => { + let d2 = ctx['value']; + return block3([d2]); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'brol': slot3(ctx2), 'default': slot4(ctx2)}}); + return block1([], [b5]); + } +}" +`; + +exports[`slots named slots can define a default content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + const defaultSlot1 = ctx => { + return text(\`default content\`); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = callSlot(ctx, node, key, 'header', defaultSlot1, false); + return block1([], [b3]); + } +}" +`; + +exports[`slots named slots can define a default content 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Dialog\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`slots named slots inside slot, again 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + const defaultSlot1 = ctx => { + return text(\`default1\`); + } + + const defaultSlot2 = ctx => { + return text(\`default2\`); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = callSlot(ctx, node, key, 'brol1', defaultSlot1, false); + let b5 = callSlot(ctx, node, key, 'brol2', defaultSlot2, false); + let b6 = callSlot(ctx, node, key, 'default'); + return block1([], [b3, b5, b6]); + } +}" +`; + +exports[`slots named slots inside slot, again 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`

A

\`); + let block3 = createBlock(\`

B

\`); + + const slot3 = ctx => (node, key) => { + let d1 = ctx['value']; + return block2([d1]); + } + + const slot4 = ctx => (node, key) => { + const ctx6 = capture(ctx); + return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx), {slots: {'brol2': slot7(ctx6)}}); + } + + const slot7 = ctx => (node, key) => { + let d2 = ctx['value']; + return block3([d2]); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'brol1': slot3(ctx2), 'default': slot4(ctx2)}}); + return block1([], [b5]); + } +}" +`; + +exports[`slots nested slots in same template 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots nested slots in same template 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots nested slots in same template 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`Child 3\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`slots nested slots in same template 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`\`); + + const slot2 = ctx => (node, key) => { + return assign(component(\`Child2\`, {}, key + \`__3\`, node, ctx), {slots: {'default': slot4(ctx)}}); + } + + const slot4 = ctx => (node, key) => { + return component(\`Child3\`, {}, key + \`__5\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b4 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b4]); + } +}" +`; + +exports[`slots nested slots: evaluation context and parented relationship 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`slots nested slots: evaluation context and parented relationship 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots nested slots: evaluation context and parented relationship 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return callSlot(ctx, node, key, 'default'); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`GrandChild\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots nested slots: evaluation context and parented relationship 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return component(\`Slot\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots no named slot content => just no children 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'header'); + return block1([], [b2]); + } +}" +`; + +exports[`slots no named slot content => just no children 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Dialog\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`slots simple default slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots simple default slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return text(\`some text\`); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots simple default slot, variation 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return callSlot(ctx, node, key, 'default'); + } +}" +`; + +exports[`slots simple default slot, variation 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return text(\`some text\`); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots slot and (inline) t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

sokka

\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`slots slot and (inline) t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slot and (inline) t-call 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + const callTemplate_5 = getTemplate(\`__template__9\`); + + let block1 = createBlock(\`
\`); + + const slot3 = ctx => (node, key) => { + return callTemplate_5(ctx, node, key + \`__4\`); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slot and (inline) t-esc 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slot and (inline) t-esc 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + + const slot2 = ctx => (node, key) => { + return text('toph'); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slot and t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

sokka

\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`slots slot and t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slot and t-call 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + const callTemplate_5 = getTemplate(\`__template__9\`); + + let block1 = createBlock(\`
\`); + + const slot3 = ctx => (node, key) => { + return callTemplate_5(ctx, node, key + \`__4\`); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slot and t-esc 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slot and t-esc 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + + const slot2 = ctx => (node, key) => { + return text('toph'); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slot are properly rendered if inner props are changed 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
SC:
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`slots slot are properly rendered if inner props are changed 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slot are properly rendered if inner props are changed 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + + const slot2 = ctx => (node, key) => { + return component(\`SomeComponent\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'inc']; + let d2 = ctx['state'].val; + let b3 = assign(component(\`GenericComponent\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([d1, d2], [b3]); + } +}" +`; + +exports[`slots slot content is bound to caller 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slot content is bound to caller 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`\`); + + const slot2 = ctx => (node, key) => { + let d1 = [ctx, 'inc']; + return block1([d1]); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots slot preserves properly parented relationship 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return callSlot(ctx, node, key, 'default'); + } +}" +`; + +exports[`slots slot preserves properly parented relationship 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`Grand Child\`); + } +}" +`; + +exports[`slots slot preserves properly parented relationship 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + + const slot2 = ctx => (node, key) => { + return component(\`GrandChild\`, {}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slot preserves properly parented relationship, even through t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return callSlot(ctx, node, key, 'default'); + } +}" +`; + +exports[`slots slot preserves properly parented relationship, even through t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`Grand Child\`); + } +}" +`; + +exports[`slots slot preserves properly parented relationship, even through t-call 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`GrandChild\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`slots slot preserves properly parented relationship, even through t-call 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + const callTemplate_5 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
\`); + + const slot3 = ctx => (node, key) => { + return callTemplate_5(ctx, node, key + \`__4\`); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slots and wrapper components 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slots and wrapper components 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return text(\`hey\`); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Link\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots slots are rendered with proper context 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'footer'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slots are rendered with proper context 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block2 = createBlock(\`\`); + + const slot3 = ctx => (node, key) => { + let d2 = [ctx, 'doSomething']; + return block2([d2]); + } + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].val; + const ctx2 = capture(ctx); + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'footer': slot3(ctx2)}}); + return block1([d1], [b3]); + } +}" +`; + +exports[`slots slots are rendered with proper context, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].to; + let b2 = callSlot(ctx, node, key, 'default'); + return block1([d1], [b2]); + } +}" +`; + +exports[`slots slots are rendered with proper context, part 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
\`); + let block3 = createBlock(\`
  • \`); + + const slot3 = ctx => (node, key) => { + let b5 = text(\`User \`); + let b6 = text(ctx['user'].name); + return multi([b5, b6]); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['state'].users); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`user\`] = v2[i1]; + let key1 = ctx['user'].id; + const ctx2 = capture(ctx); + let b7 = assign(component(\`Link\`, {to: '/user/'+ctx['user'].id}, key + \`__1__\${key1}\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + c2[i1] = withKey(block3([], [b7]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`slots slots are rendered with proper context, part 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].to; + let b2 = callSlot(ctx, node, key, 'default'); + return block1([d1], [b2]); + } +}" +`; + +exports[`slots slots are rendered with proper context, part 3 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`
  • \`); + + const slot3 = ctx => (node, key) => { + return text(ctx['userdescr']); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['state'].users); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`user\`] = v2[i1]; + let key1 = ctx['user'].id; + ctx[\`userdescr\`] = 'User '+ctx['user'].name; + const ctx2 = capture(ctx); + let b5 = assign(component(\`Link\`, {to: '/user/'+ctx['user'].id}, key + \`__1__\${key1}\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + c2[i1] = withKey(block3([], [b5]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`slots slots are rendered with proper context, part 4 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].to; + let b2 = callSlot(ctx, node, key, 'default'); + return block1([d1], [b2]); + } +}" +`; + +exports[`slots slots are rendered with proper context, part 4 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot3 = ctx => (node, key) => { + return text(ctx['userdescr']); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`userdescr\`] = 'User '+ctx['state'].user.name; + const ctx2 = capture(ctx); + let b3 = assign(component(\`Link\`, {to: '/user/'+ctx['state'].user.id}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slots in slots, with vars 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slots in slots, with vars 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + return callSlot(ctx, node, key, 'default'); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`B\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slots in slots, with vars 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`

    hey

    \`); + + const slot3 = ctx => (node, key) => { + let d1 = ctx['test']; + return block2([d1]); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`test\`] = ctx['state'].name; + const ctx2 = capture(ctx); + let b3 = assign(component(\`A\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots slots in t-foreach and re-rendering 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].val; + let b2 = callSlot(ctx, node, key, 'default'); + return block1([d1], [b2]); + } +}" +`; + +exports[`slots slots in t-foreach and re-rendering 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot3 = ctx => (node, key) => { + return text(ctx['n_index']); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(Array(2)); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`n\`] = v2[i1]; + ctx[\`n_index\`] = i1; + let key1 = ctx['n_index']; + const ctx2 = capture(ctx); + c2[i1] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx), {slots: {'default': slot3(ctx2)}}), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`slots slots in t-foreach in t-foreach 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots slots in t-foreach in t-foreach 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + let block4 = createBlock(\`
    \`); + let block5 = createBlock(\`
    \`); + let block7 = createBlock(\`
  • \`); + + const slot3 = ctx => (node, key) => { + let d2 = ctx['node1'].value; + return block7([d2]); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['tree']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`node1\`] = v2[i1]; + let key1 = ctx['node1'].key; + let d1 = ctx['node1'].value; + let b4 = block4([d1]); + ctx = Object.create(ctx); + const [k6, v6, l6, c6] = prepareList(ctx['node1'].nodes); + for (let i2 = 0; i2 < l6; i2++) { + ctx[\`node2\`] = v6[i2]; + let key2 = ctx['node2'].key; + const ctx2 = capture(ctx); + c6[i2] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}__\${key2}\`, node, ctx), {slots: {'default': slot3(ctx2)}}), key2); + } + ctx = ctx.__proto__; + let b6 = list(c6); + let b5 = block5([], [b6]); + c2[i1] = withKey(multi([b4, b5]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`slots slots in t-foreach with t-set and re-rendering 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].val; + let b2 = callSlot(ctx, node, key, 'default'); + return block1([d1], [b2]); + } +}" +`; + +exports[`slots slots in t-foreach with t-set and re-rendering 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot3 = ctx => (node, key) => { + return text(ctx['dummy']); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(Array(2)); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`n\`] = v2[i1]; + ctx[\`n_index\`] = i1; + let key1 = ctx['n_index']; + ctx[\`dummy\`] = ctx['n_index']; + const ctx2 = capture(ctx); + c2[i1] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx), {slots: {'default': slot3(ctx2)}}), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`slots t-debug on a t-set-slot (defining a slot) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'content'); + return block1([], [b2]); + } +}" +`; + +exports[`slots t-debug on a t-set-slot (defining a slot) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot3 = ctx => (node, key) => { + debugger; + return text(\`abc\`); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'content': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots t-set t-value in a slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots t-set t-value in a slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot3 = ctx => (node, key) => { + ctx[\`rainbow\`] = 'dash'; + return text(ctx['rainbow']); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const ctx2 = capture(ctx); + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots t-slot in recursive templates 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots t-slot in recursive templates 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + const callTemplate_5 = getTemplate(\`_test_recursive_template\`); + + const slot3 = ctx => (node, key) => { + let b2 = text(ctx['name']); + ctx = Object.create(ctx); + const [k3, v3, l3, c3] = prepareList(ctx['items']); + for (let i1 = 0; i1 < l3; i1++) { + ctx[\`item\`] = v3[i1]; + ctx[\`item_first\`] = i1 === 0; + ctx[\`item_last\`] = i1 === v3.length - 1; + ctx[\`item_index\`] = i1; + ctx[\`item_value\`] = k3[i1]; + let key1 = ctx['item'].name; + let b5,b6; + if (!ctx['item'].children.length) { + b5 = text(ctx['item'].name); + } else { + ctx = Object.create(ctx); + ctx[\`name\`] = ctx['item'].name; + ctx[\`items\`] = ctx['item'].children; + b6 = callTemplate_5(ctx, node, key + \`__4__\${key1}\`); + ctx = ctx.__proto__; + } + c3[i1] = withKey(multi([b5, b6]), key1); + } + ctx = ctx.__proto__; + let b3 = list(c3); + return multi([b2, b3]); + } + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const ctx2 = capture(ctx); + return assign(component(\`Wrapper\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + } +}" +`; + +exports[`slots t-slot nested within another slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`Child 3\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`slots t-slot nested within another slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots t-slot nested within another slot 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots t-slot nested within another slot 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`\`); + + const slot2 = ctx => (node, key) => { + return assign(component(\`Portal\`, {}, key + \`__3\`, node, ctx), {slots: {'default': slot4(ctx)}}); + } + + const slot4 = ctx => (node, key) => { + return callSlot(ctx, node, key, 'default'); + } + + return function template(ctx, node, key = \\"\\") { + let b4 = assign(component(\`Modal\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b4]); + } +}" +`; + +exports[`slots t-slot nested within another slot 5`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`\`); + + const slot2 = ctx => (node, key) => { + return component(\`Child3\`, {}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots t-slot scope context 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return callSlot(ctx, node, key, 'default'); + } +}" +`; + +exports[`slots t-slot scope context 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + let d1 = [ctx, 'onClick']; + let b2 = callSlot(ctx, node, key, 'default'); + return block1([d1], [b2]); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Wrapper\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots t-slot scope context 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`\`); + + const slot2 = ctx => (node, key) => { + return block1(); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`slots t-slot within dynamic t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`slots t-slot within dynamic t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callSlot(ctx, node, key, 'default'); + return block1([], [b2]); + } +}" +`; + +exports[`slots t-slot within dynamic t-call 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`slots t-slot within dynamic t-call 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot3 = ctx => (node, key) => { + const template5 = (ctx['tcallTemplate']); + return call(template5, ctx, node, key + \`__4\`); + } + + return function template(ctx, node, key = \\"\\") { + const ctx2 = capture(ctx); + let b3 = assign(component(\`Slotted\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}}); + return block1([], [b3]); + } +}" +`; + +exports[`slots template can just return a slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].value; + return block1([d1]); + } +}" +`; + +exports[`slots template can just return a slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return callSlot(ctx, node, key, 'default'); + } +}" +`; + +exports[`slots template can just return a slot 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + return component(\`Child\`, {value: ctx['state'].value}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`SlotComponent\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; diff --git a/tests/components/__snapshots__/style_class.test.ts.snap b/tests/components/__snapshots__/style_class.test.ts.snap new file mode 100644 index 00000000..7edc52c5 --- /dev/null +++ b/tests/components/__snapshots__/style_class.test.ts.snap @@ -0,0 +1,395 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`style and class handling can set class on multi root component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`
    a
    \`); + let block3 = createBlock(\`b\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = block2(); + let d1 = ctx['props'].class; + let b3 = block3([d1]); + return multi([b2, b3]); + } +}" +`; + +exports[`style and class handling can set class on multi root component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {class: 'fromparent'}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling can set class on sub component, as prop 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling can set class on sub component, as prop 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {class: 'some-class'}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling can set class on sub sub component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    childchild
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling can set class on sub sub component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`ChildChild\`, {class: (ctx['props'].class||'')+' fromchild'}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling can set class on sub sub component 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {class: 'fromparent'}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling can set more than one class on sub component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling can set more than one class on sub component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {class: 'a b'}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling can set style and class inside component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    world
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`style and class handling class on sub component, which is switched to another 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    a
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling class on sub component, which is switched to another 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`b\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling class on sub component, which is switched to another 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['props'].child==='a') { + b2 = component(\`ChildA\`, {class: ctx['props'].class}, key + \`__1\`, node, ctx); + } else { + b3 = component(\`ChildB\`, {class: ctx['props'].class}, key + \`__2\`, node, ctx); + } + return multi([b2, b3]); + } +}" +`; + +exports[`style and class handling class on sub component, which is switched to another 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {class: 'someclass',child: ctx['state'].child}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling class with extra whitespaces (variation) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling class with extra whitespaces (variation) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {class: 'a b c d'}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`style and class handling class with extra whitespaces 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling class with extra whitespaces 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {class: 'a b c d'}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling component class and parent class combine together 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling component class and parent class combine together 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {class: 'from parent'}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling empty class attribute is not added on widget root el 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling empty class attribute is not added on widget root el 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {class: undefined}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`style and class handling no class is set is child ignores it 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`style and class handling no class is set is child ignores it 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {class: 'hey'}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling no class is set is parent does not give it as prop 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].class; + return block1([d1]); + } +}" +`; + +exports[`style and class handling no class is set is parent does not give it as prop 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`style and class handling t-att-class is properly added/removed on widget root el (v2) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = {d:ctx['state'].d,...ctx['props'].class}; + return block1([d1]); + } +}" +`; + +exports[`style and class handling t-att-class is properly added/removed on widget root el (v2) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {class: {b:ctx['state'].b}}, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; diff --git a/tests/components/__snapshots__/t_call.test.ts.snap b/tests/components/__snapshots__/t_call.test.ts.snap new file mode 100644 index 00000000..a1ed3fb7 --- /dev/null +++ b/tests/components/__snapshots__/t_call.test.ts.snap @@ -0,0 +1,292 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-call dynamic t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b1 = text(\` owl \`); + ctx[zero] = b1; + const template2 = (ctx['current'].template); + return call(template2, ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call dynamic t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    foo
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call dynamic t-call 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`bar\`); + } +}" +`; + +exports[`t-call handlers are properly bound through a t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    lucas

    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'update']; + return block1([d1]); + } +}" +`; + +exports[`t-call handlers are properly bound through a t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`__template__9\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + let d1 = ctx['counter']; + return block1([d1], [b2]); + } +}" +`; + +exports[`t-call handlers with arguments are properly bound through a t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    lucas

    \`); + + return function template(ctx, node, key = \\"\\") { + const arg1 = [ctx['a']]; + let d1 = [ctx, 'update', arg1]; + return block1([d1]); + } +}" +`; + +exports[`t-call handlers with arguments are properly bound through a t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`__template__9\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call parent is set within t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`t-call parent is set within t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`lucas\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call parent is set within t-call 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`__template__9\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call parent is set within t-call with no parentNode 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`t-call parent is set within t-call with no parentNode 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`lucas\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call parent is set within t-call with no parentNode 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`__template__9\`); + + return function template(ctx, node, key = \\"\\") { + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call sub components in two t-calls 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`t-call sub components in two t-calls 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + const callTemplate_4 = getTemplate(\`sub\`); + + let block3 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['state'].val===1) { + b2 = callTemplate_2(ctx, node, key + \`__1\`); + } else { + let b4 = callTemplate_4(ctx, node, key + \`__3\`); + b3 = block3([], [b4]); + } + return multi([b2, b3]); + } +}" +`; + +exports[`t-call sub components in two t-calls 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`t-call t-call in t-foreach and children component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {val: ctx['val']}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`t-call t-call in t-foreach and children component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`t-call t-call in t-foreach and children component 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`__template__9\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(['a','b','c']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`val\`] = v2[i1]; + ctx[\`val_first\`] = i1 === 0; + ctx[\`val_last\`] = i1 === v2.length - 1; + ctx[\`val_index\`] = i1; + ctx[\`val_value\`] = k2[i1]; + let key1 = ctx['val']; + c2[i1] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}\`), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; diff --git a/tests/components/__snapshots__/t_call_block.test.ts.snap b/tests/components/__snapshots__/t_call_block.test.ts.snap new file mode 100644 index 00000000..e5d91448 --- /dev/null +++ b/tests/components/__snapshots__/t_call_block.test.ts.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-call-block simple t-call-block with static text 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = ctx['myBlock'](); + return block1([], [b2]); + } +}" +`; diff --git a/tests/components/__snapshots__/t_component.test.ts.snap b/tests/components/__snapshots__/t_component.test.ts.snap new file mode 100644 index 00000000..4cd15b9f --- /dev/null +++ b/tests/components/__snapshots__/t_component.test.ts.snap @@ -0,0 +1,226 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-component can switch between dynamic components without the need for a t-key 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child a\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-component can switch between dynamic components without the need for a t-key 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child b\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-component can switch between dynamic components without the need for a t-key 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let Comp2 = ctx['constructor'].components[ctx['state'].child]; + let b2 = toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx)); + return block1([], [b2]); + } +}" +`; + +exports[`t-component can use dynamic components (the class) if given (with different root tagname) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child a\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-component can use dynamic components (the class) if given (with different root tagname) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child b
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-component can use dynamic components (the class) if given (with different root tagname) 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let Comp2 = ctx['myComponent']; + return toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx)); + } +}" +`; + +exports[`t-component can use dynamic components (the class) if given 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child a\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-component can use dynamic components (the class) if given 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`child b\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-component can use dynamic components (the class) if given 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let Comp2 = ctx['myComponent']; + return toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx)); + } +}" +`; + +exports[`t-component modifying a sub widget 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].counter; + const v1 = ctx['state']; + let d2 = (e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}; + return block1([d1, d2]); + } +}" +`; + +exports[`t-component modifying a sub widget 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let Comp2 = ctx['Counter']; + let b2 = toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx)); + return block1([], [b2]); + } +}" +`; + +exports[`t-component switching dynamic component 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child a
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-component switching dynamic component 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`child b\`); + } +}" +`; + +exports[`t-component switching dynamic component 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let Comp2 = ctx['Child']; + return toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx)); + } +}" +`; + +exports[`t-component t-component works in simple case 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-component t-component works in simple case 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let Comp2 = ctx['Child']; + return toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx)); + } +}" +`; diff --git a/tests/components/__snapshots__/t_foreach.test.ts.snap b/tests/components/__snapshots__/t_foreach.test.ts.snap new file mode 100644 index 00000000..c8072b57 --- /dev/null +++ b/tests/components/__snapshots__/t_foreach.test.ts.snap @@ -0,0 +1,244 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`list of components components in a node in a t-foreach 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].item; + return block1([d1]); + } +}" +`; + +exports[`list of components components in a node in a t-foreach 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`
  • \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['items']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + let key1 = 'li_'+ctx['item']; + let b4 = component(\`Child\`, {item: ctx['item']}, key + \`__1__\${key1}\`, node, ctx); + c2[i1] = withKey(block3([], [b4]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`list of components reconciliation alg works for t-foreach in t-foreach 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].blip; + return block1([d1]); + } +}" +`; + +exports[`list of components reconciliation alg works for t-foreach in t-foreach 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['state'].s); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`section\`] = v2[i1]; + ctx[\`section_index\`] = i1; + let key1 = ctx['section_index']; + ctx = Object.create(ctx); + const [k3, v3, l3, c3] = prepareList(ctx['section'].blips); + for (let i2 = 0; i2 < l3; i2++) { + ctx[\`blip\`] = v3[i2]; + ctx[\`blip_index\`] = i2; + let key2 = ctx['blip_index']; + c3[i2] = withKey(component(\`Child\`, {blip: ctx['blip']}, key + \`__1__\${key1}__\${key2}\`, node, ctx), key2); + } + ctx = ctx.__proto__; + c2[i1] = withKey(list(c3), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`list of components reconciliation alg works for t-foreach in t-foreach, 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].row+'_'+ctx['props'].col; + return block1([d1]); + } +}" +`; + +exports[`list of components reconciliation alg works for t-foreach in t-foreach, 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`

    \`); + let block5 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['state'].rows); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`row\`] = v2[i1]; + let key1 = ctx['row']; + ctx = Object.create(ctx); + const [k4, v4, l4, c4] = prepareList(ctx['state'].cols); + for (let i2 = 0; i2 < l4; i2++) { + ctx[\`col\`] = v4[i2]; + let key2 = ctx['col']; + let b6 = component(\`Child\`, {row: ctx['row'],col: ctx['col']}, key + \`__1__\${key1}__\${key2}\`, node, ctx); + c4[i2] = withKey(block5([], [b6]), key2); + } + ctx = ctx.__proto__; + let b4 = list(c4); + c2[i1] = withKey(block3([], [b4]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`list of components simple list 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].value; + return block1([d1]); + } +}" +`; + +exports[`list of components simple list 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k1, v1, l1, c1] = prepareList(ctx['state'].elems); + for (let i1 = 0; i1 < l1; i1++) { + ctx[\`elem\`] = v1[i1]; + let key1 = ctx['elem'].id; + c1[i1] = withKey(component(\`Child\`, {value: ctx['elem'].value}, key + \`__1__\${key1}\`, node, ctx), key1); + } + return list(c1); + } +}" +`; + +exports[`list of components sub components rendered in a loop 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].n; + return block1([d1]); + } +}" +`; + +exports[`list of components sub components rendered in a loop 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['state'].numbers); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`number\`] = v2[i1]; + let key1 = ctx['number']; + c2[i1] = withKey(component(\`Child\`, {n: ctx['number']}, key + \`__1__\${key1}\`, node, ctx), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`list of components sub components with some state rendered in a loop 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['state'].n; + return block1([d1]); + } +}" +`; + +exports[`list of components sub components with some state rendered in a loop 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['state'].numbers); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`number\`] = v2[i1]; + let key1 = ctx['number']; + c2[i1] = withKey(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; diff --git a/tests/components/app.test.ts b/tests/components/app.test.ts new file mode 100644 index 00000000..c6a5dcf4 --- /dev/null +++ b/tests/components/app.test.ts @@ -0,0 +1,28 @@ +import { App, Component } from "../../src"; +import { status } from "../../src/component/status"; +import { xml } from "../../src/tags"; +import { makeTestFixture, snapshotEverything } from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("app", () => { + test("destroy remove the widget from the DOM", async () => { + class SomeComponent extends Component { + static template = xml`
    `; + } + + const app = new App(SomeComponent); + const comp = await app.mount(fixture); + const el = comp.el!; + expect(document.contains(el)).toBe(true); + app.destroy(); + expect(document.contains(el)).toBe(false); + expect(status(comp)).toBe("destroyed"); + }); +}); diff --git a/tests/components/basics.test.ts b/tests/components/basics.test.ts new file mode 100644 index 00000000..2b8ebd99 --- /dev/null +++ b/tests/components/basics.test.ts @@ -0,0 +1,657 @@ +import { App, Component, mount, status, useState } from "../../src"; +import { xml } from "../../src/tags"; +import { makeTestFixture, nextTick, snapshotEverything } from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("basics", () => { + test("can mount a simple component", async () => { + class Test extends Component { + static template = xml`simple vnode`; + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe("simple vnode"); + expect(component.el).toEqual(fixture.querySelector("span")); + }); + + test("has no el after creation", async () => { + let el: any = null; + class Test extends Component { + static template = xml`simple`; + setup() { + el = this.el; + } + } + + await mount(Test, fixture); + expect(el).toBeUndefined(); + }); + + test("cannot mount on a documentFragment", async () => { + class SomeWidget extends Component { + static template = xml`
    content
    `; + } + let error; + try { + await mount(SomeWidget, document.createDocumentFragment() as any); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toBe("Cannot mount component: the target is not a valid DOM element"); + }); + + test("can mount a simple component with props", async () => { + class Test extends Component { + static template = xml``; + } + + const app = new App(Test, { value: 3 }); + const component = await app.mount(fixture); + + expect(fixture.innerHTML).toBe("3"); + expect(component.el).toEqual(fixture.querySelector("span")); + }); + + test("can mount a component with just some text", async () => { + class Test extends Component { + static template = xml`just text`; + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe("just text"); + expect(component.el).toBeInstanceOf(Text); + }); + + test("can mount a component with no text", async () => { + class Test extends Component { + static template = xml``; + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe(""); + expect(component.el).toBeInstanceOf(Text); + }); + + test("can mount a simple component with multiple roots", async () => { + class Test extends Component { + static template = xml`
    `; + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe("
    "); + expect((component.el as any).tagName).toBe("SPAN"); + }); + + test("component with dynamic content can be updated", async () => { + class Test extends Component { + static template = xml``; + value = 1; + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe("1"); + + component.value = 2; + await component.render(); + expect(fixture.innerHTML).toBe("2"); + }); + + test("updating a component with t-foreach as root", async () => { + class Test extends Component { + static template = xml` + + + `; + items = ["one", "two", "three"]; + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe("onetwothree"); + component.items = ["two", "three", "one"]; + await component.render(); + expect(fixture.innerHTML).toBe("twothreeone"); + }); + + test("props is set on root component", async () => { + expect.assertions(2); + const p = {}; + class Test extends Component { + static template = xml`simple vnode`; + setup() { + expect(this.props).toBe(p); + } + } + + const app = new App(Test, p); + await app.mount(fixture); + }); + + test("some simple sanity checks (el/status)", async () => { + expect.assertions(4); + class Test extends Component { + static template = xml`simple vnode`; + setup() { + expect(this.el).toBe(undefined); + expect(status(this)).toBe("new"); + } + } + + const test = await mount(Test, fixture); + expect(status(test)).toBe("mounted"); + }); + + test("throws if mounting on target=null", async () => { + class Test extends Component { + static template = xml`simple vnode`; + } + + let error; + try { + await mount(Test, null as any); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toBe("Cannot mount component: the target is not a valid DOM element"); + }); + + test("a component cannot be mounted in a detached node", async () => { + class Test extends Component { + static template = xml`
    `; + } + let error; + try { + await mount(Test, document.createElement("div")); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toBe("Cannot mount a component on a detached dom node"); + }); + + test("crashes if it cannot find a template", async () => { + class Test extends Component { + static template = "wrongtemplate"; + } + + let error; + try { + await mount(Test, fixture); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toBe('Missing template: "wrongtemplate"'); + }); + + test("class component with dynamic text", async () => { + class Test extends Component { + static template = xml`My value: `; + + value = 42; + } + + await mount(Test, fixture); + expect(fixture.innerHTML).toBe("My value: 42"); + }); + + test("Multi root component", async () => { + class Test extends Component { + static template = xml`1text2`; + + value = 42; + } + + await mount(Test, fixture); + expect(fixture.innerHTML).toBe(`1text2`); + }); + + test("a component inside a component", async () => { + class Child extends Component { + static template = xml`
    simple vnode
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    simple vnode
    "); + }); + + test("a class component inside a class component, no external dom", async () => { + class Child extends Component { + static template = xml`
    simple vnode
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    simple vnode
    "); + }); + + test("simple component with a dynamic text", async () => { + class Test extends Component { + static template = xml`
    `; + value = 3; + } + + const test = await mount(Test, fixture); + expect(fixture.innerHTML).toBe("
    3
    "); + test.value = 5; + await test.render(); + expect(fixture.innerHTML).toBe("
    5
    "); + }); + + test("simple component, useState", async () => { + class Test extends Component { + static template = xml`
    `; + state = useState({ value: 3 }); + } + + const test = await mount(Test, fixture); + expect(fixture.innerHTML).toBe("
    3
    "); + test.state.value = 5; + await nextTick(); + expect(fixture.innerHTML).toBe("
    5
    "); + }); + + test("two child components", async () => { + class Child extends Component { + static template = xml`
    simple vnode
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    simple vnode
    simple vnode
    "); + }); + + test("class parent, class child component with props", async () => { + class Child extends Component { + static template = xml`
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    42
    "); + }); + + test("parent, child and grandchild", async () => { + class GrandChild extends Component { + static template = xml`
    hey
    `; + } + + class Child extends Component { + static template = xml``; + static components = { GrandChild }; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    hey
    "); + }); + + test("zero or one child components", async () => { + class Child extends Component { + static template = xml`
    simple vnode
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ hasChild: false }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(""); + parent.state.hasChild = true; + await nextTick(); + expect(fixture.innerHTML).toBe("
    simple vnode
    "); + }); + + test("text after a conditional component", async () => { + class Child extends Component { + static template = xml`

    simple vnode

    `; + } + + class Parent extends Component { + static template = xml` +
    + + +
    `; + static components = { Child }; + state = useState({ hasChild: false, text: "1" }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    1
    "); + + parent.state.hasChild = true; + await nextTick(); + expect(fixture.innerHTML).toBe("

    simple vnode

    1
    "); + + parent.state.hasChild = false; + parent.state.text = "2"; + await nextTick(); + expect(fixture.innerHTML).toBe("
    2
    "); + }); + + test("can be clicked on and updated", async () => { + class Counter extends Component { + static template = xml` +
    `; + state = useState({ + counter: 0, + }); + } + + const counter = await mount(Counter, fixture); + expect(fixture.innerHTML).toBe("
    0
    "); + const button = (counter.el).getElementsByTagName("button")[0]; + button.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    1
    "); + }); + + test("can handle empty props", async () => { + class Child extends Component { + static template = xml``; + } + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    "); + }); + + test("child can be updated", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ + counter: 0, + }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("0"); + + parent.state.counter = 1; + await nextTick(); + expect(fixture.innerHTML).toBe("1"); + }); + + test("higher order components parent and child", async () => { + class ChildA extends Component { + static template = xml`
    a
    `; + } + class ChildB extends Component { + static template = xml`b`; + } + class Child extends Component { + static template = xml``; + static components = { ChildA, ChildB }; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + + state = useState({ child: "a" }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    a
    `); + parent.state.child = "b"; + await nextTick(); + expect(fixture.innerHTML).toBe(`b`); + }); + + test("three level of components with collapsing root nodes", async () => { + class GrandChild extends Component { + static template = xml`
    2
    `; + } + class Child extends Component { + static components = { GrandChild }; + static template = xml``; + } + class Parent extends Component { + static components = { Child }; + static template = xml``; + } + + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    2
    "); + }); + + test("do not remove previously rendered dom if not necessary", async () => { + class SomeComponent extends Component { + static template = xml`
    `; + } + const widget = await mount(SomeComponent, fixture); + expect(fixture.innerHTML).toBe(`
    `); + widget.el!.appendChild(document.createElement("span")); + expect(fixture.innerHTML).toBe(`
    `); + await widget.render(); + expect(fixture.innerHTML).toBe(`
    `); + }); + + test("do not remove previously rendered dom if not necessary, variation", async () => { + class SomeComponent extends Component { + static template = xml`

    h1

    `; + state = useState({ value: 1 }); + } + const comp = await mount(SomeComponent, fixture); + expect(fixture.innerHTML).toBe(`

    h1

    1
    `); + (comp.el! as any).querySelector("h1")!.appendChild(document.createElement("p")); + expect(fixture.innerHTML).toBe("

    h1

    1
    "); + + comp.state.value++; + await nextTick(); + expect(fixture.innerHTML).toBe("

    h1

    2
    "); + }); + + test("reconciliation alg is not confused in some specific situation", async () => { + // in this test, we set t-key to 4 because it was in conflict with the + // template id corresponding to the first child. + class Child extends Component { + static template = xml`child`; + } + + class Parent extends Component { + static template = xml` +
    + + +
    + `; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    childchild
    "); + }); + + test("same t-keys in two different places", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml` +
    +
    +
    +
    `; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    1
    2
    "); + }); + + test("t-key on a component with t-if, and a sibling component", async () => { + class Child extends Component { + static template = xml`child`; + } + + class Parent extends Component { + static template = xml` +
    + + +
    `; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    child
    "); + }); + + test("widget after a t-foreach", async () => { + class SomeComponent extends Component { + static template = xml`
    `; + } + + class Test extends Component { + static template = xml`
    txt
    `; + static components = { SomeComponent }; + } + + await mount(Test, fixture); + expect(fixture.innerHTML).toBe("
    txttxt
    "); + }); + + test("t-if works with t-component", async () => { + class Child extends Component { + static template = xml`hey`; + } + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + state = useState({ flag: true }); + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    hey
    "); + + parent.state.flag = false; + await nextTick(); + expect(fixture.innerHTML).toBe("
    "); + + parent.state.flag = true; + await nextTick(); + expect(fixture.innerHTML).toBe("
    hey
    "); + }); + + test("t-else works with t-component", async () => { + class Child extends Component { + static template = xml`hey`; + } + + class Parent extends Component { + static template = xml` +
    +
    somediv
    + +
    `; + static components = { Child }; + state = useState({ flag: true }); + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    somediv
    "); + + parent.state.flag = false; + await nextTick(); + expect(fixture.innerHTML).toBe("
    hey
    "); + }); + + test("t-elif works with t-component", async () => { + class Child extends Component { + static template = xml`hey`; + } + + class Parent extends Component { + static template = xml` +
    +
    somediv
    + +
    `; + static components = { Child }; + state = useState({ flag: true }); + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    somediv
    "); + + parent.state.flag = false; + await nextTick(); + expect(fixture.innerHTML).toBe("
    hey
    "); + }); + + test("t-else with empty string works with t-component", async () => { + class Child extends Component { + static template = xml`hey`; + } + + class Parent extends Component { + static template = xml` +
    +
    somediv
    + +
    `; + static components = { Child }; + state = useState({ flag: true }); + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    somediv
    "); + + parent.state.flag = false; + await nextTick(); + expect(fixture.innerHTML).toBe("
    hey
    "); + }); +}); diff --git a/tests/components/concurrency.test.ts b/tests/components/concurrency.test.ts new file mode 100644 index 00000000..c3e79ec0 --- /dev/null +++ b/tests/components/concurrency.test.ts @@ -0,0 +1,2508 @@ +import { App, Component, mount, onWillStart, onWillUpdateProps, useState } from "../../src"; +import { Fiber } from "../../src/component/fibers"; +import { onMounted, onPatched, onWillPatch, onWillUnmount } from "../../src/lifecycle_hooks"; +import { Scheduler } from "../../src/component/scheduler"; +import { status } from "../../src/component/status"; +import { xml } from "../../src/tags"; +import { + makeDeferred, + makeTestFixture, + nextMicroTick, + nextTick, + snapshotEverything, + useLogLifecycle, +} from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +// following code is there to prevent memory leaks in the scheduled tasks +let lastScheduler: Scheduler; +const addFiber = Scheduler.prototype.addFiber; +Scheduler.prototype.addFiber = function (fiber: Fiber) { + lastScheduler = this; + return addFiber.call(this, fiber); +}; + +afterEach(() => { + if (lastScheduler && lastScheduler.tasks.size > 0) { + throw new Error("we got a memory leak..."); + } +}); + +describe("async rendering", () => { + test("destroying a widget before start is over", async () => { + let steps: string[] = []; + let def = makeDeferred(); + let w: any = null; + class W extends Component { + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + expect(status(this)).toBe("new"); + w = this; + onWillStart(() => def); + } + } + const app = new App(W); + app.mount(fixture); + expect(status(w)).toBe("new"); + app.destroy(); + expect(status(w)).toBe("destroyed"); + def.resolve(); + await nextTick(); + expect(status(w)).toBe("destroyed"); + expect(steps).toEqual(["W:setup", "W:willStart", "W:destroyed"]); + }); +}); + +test("destroying/recreating a subwidget with different props (if start is not over)", async () => { + let steps: string[] = []; + let def = makeDeferred(); + let n = 0; + class Child extends Component { + static template = xml`child:`; + setup() { + useLogLifecycle(steps); + n++; + onWillStart(() => def); + } + } + + class W extends Component { + static template = xml` +
    + +
    `; + static components = { Child }; + state = useState({ val: 1 }); + setup() { + useLogLifecycle(steps); + } + } + + const w = await mount(W, fixture); + + expect(n).toBe(0); + w.state.val = 2; + + await nextMicroTick(); + expect(n).toBe(1); + w.state.val = 3; + await nextMicroTick(); + expect(n).toBe(2); + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    child:3
    "); + expect(Object.values(w.__owl__.children).length).toBe(1); + expect(steps).toEqual([ + "W:setup", + "W:willStart", + "W:render", + "W:mounted", + "W:render", + "Child:setup", + "Child:willStart", + "W:render", + "Child:destroyed", + "Child:setup", + "Child:willStart", + "Child:render", + "W:willPatch", + "Child:mounted", + "W:patched", + ]); + Object.freeze(steps); +}); + +test("creating two async components, scenario 1", async () => { + let steps: string[] = []; + + let defA = makeDeferred(); + let defB = makeDeferred(); + let nbRenderings: number = 0; + + class ChildA extends Component { + static template = xml``; + + setup() { + useLogLifecycle(steps); + onWillStart(() => defA); + } + + getValue() { + nbRenderings++; + return "a"; + } + } + + class ChildB extends Component { + static template = xml`b`; + setup() { + useLogLifecycle(steps); + onWillStart(() => defB); + } + } + + class Parent extends Component { + static template = xml` + + `; + + static components = { ChildA, ChildB }; + state = useState({ flagA: false, flagB: false }); + setup() { + useLogLifecycle(steps); + } + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(""); + parent.state.flagA = true; + await nextTick(); + expect(fixture.innerHTML).toBe(""); + parent.state.flagB = true; + await nextTick(); + expect(fixture.innerHTML).toBe(""); + defB.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe(""); + expect(nbRenderings).toBe(0); + defA.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("ab"); + expect(nbRenderings).toBe(1); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Parent:mounted", + "Parent:render", + "ChildA:setup", + "ChildA:willStart", + "Parent:render", + "ChildA:destroyed", + "ChildA:setup", + "ChildA:willStart", + "ChildB:setup", + "ChildB:willStart", + "ChildB:render", + "ChildA:render", + "Parent:willPatch", + "ChildB:mounted", + "ChildA:mounted", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("creating two async components, scenario 2", async () => { + let steps: string[] = []; + let defA = makeDeferred(); + let defB = makeDeferred(); + + class ChildA extends Component { + static template = xml`a`; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defA); + } + } + + class ChildB extends Component { + static template = xml`b`; + setup() { + useLogLifecycle(steps); + onWillStart(() => defB); + } + } + + class Parent extends Component { + static template = xml` +
    + + +
    `; + static components = { ChildA, ChildB }; + state = useState({ valA: 1, valB: 2, flagB: false }); + setup() { + useLogLifecycle(steps); + } + } + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    a1
    "); + parent.state.valA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("
    a1
    "); + parent.state.flagB = true; + await nextTick(); + expect(fixture.innerHTML).toBe("
    a1
    "); + defB.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    a1
    "); + defA.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    a2b2
    "); + + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "ChildA:setup", + "ChildA:willStart", + "ChildA:render", + "ChildA:mounted", + "Parent:mounted", + "Parent:render", + "ChildA:willUpdateProps", + "Parent:render", + "ChildA:willUpdateProps", + "ChildB:setup", + "ChildB:willStart", + "ChildB:render", + "ChildA:render", + "Parent:willPatch", + "ChildA:willPatch", + "ChildB:mounted", + "ChildA:patched", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("creating two async components, scenario 3 (patching in the same frame)", async () => { + let steps: string[] = []; + let defA = makeDeferred(); + let defB = makeDeferred(); + + class ChildA extends Component { + static template = xml`a`; + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defA); + } + } + class ChildB extends Component { + static template = xml`b`; + setup() { + useLogLifecycle(steps); + onWillStart(() => defB); + } + } + + class Parent extends Component { + static template = xml` +
    + + +
    `; + static components = { ChildA, ChildB }; + state = useState({ valA: 1, valB: 2, flagB: false }); + setup() { + useLogLifecycle(steps); + } + } + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    a1
    "); + parent.state.valA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("
    a1
    "); + parent.state.flagB = true; + await nextTick(); + expect(fixture.innerHTML).toBe("
    a1
    "); + defB.resolve(); + expect(fixture.innerHTML).toBe("
    a1
    "); + defA.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    a2b2
    "); + + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "ChildA:setup", + "ChildA:willStart", + "ChildA:render", + "ChildA:mounted", + "Parent:mounted", + "Parent:render", + "ChildA:willUpdateProps", + "Parent:render", + "ChildA:willUpdateProps", + "ChildB:setup", + "ChildB:willStart", + "ChildB:render", + "ChildA:render", + "Parent:willPatch", + "ChildA:willPatch", + "ChildB:mounted", + "ChildA:patched", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("update a sub-component twice in the same frame", async () => { + const steps: string[] = []; + const defs = [makeDeferred(), makeDeferred()]; + let index = 0; + class ChildA extends Component { + static template = xml``; + setup() { + onWillUpdateProps(() => defs[index++]); + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml`
    `; + static components = { ChildA }; + state = useState({ valA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    1
    "); + parent.state.valA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("
    1
    "); + parent.state.valA = 3; + await nextTick(); + expect(fixture.innerHTML).toBe("
    1
    "); + defs[0].resolve(); + await Promise.resolve(); + defs[1].resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    3
    "); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "ChildA:setup", + "ChildA:willStart", + "ChildA:render", + "ChildA:mounted", + "Parent:mounted", + "Parent:render", + "ChildA:willUpdateProps", + "Parent:render", + "ChildA:willUpdateProps", + "ChildA:render", + "Parent:willPatch", + "ChildA:willPatch", + "ChildA:patched", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("update a sub-component twice in the same frame, 2", async () => { + const steps: string[] = []; + class ChildA extends Component { + static template = xml``; + + setup() { + useLogLifecycle(steps); + } + + val() { + steps.push("render"); + return this.props.val; + } + } + + class Parent extends Component { + static template = xml`
    `; + static components = { ChildA }; + state = useState({ valA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    1
    "); + parent.state.valA = 2; + await nextMicroTick(); + expect(steps.splice(0)).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "ChildA:setup", + "ChildA:willStart", + "ChildA:render", + "render", + "ChildA:mounted", + "Parent:mounted", + "Parent:render", + "ChildA:willUpdateProps", + ]); + await nextMicroTick(); + // For an unknown reason, this test fails on windows without the next microtick. It works + // in linux and osx, but fails on at least this machine. + // I do not see anything harmful in waiting an extra tick. But it is annoying to not + // know what is different. + await nextMicroTick(); + expect(steps.splice(0)).toEqual(["ChildA:render", "render"]); + expect(fixture.innerHTML).toBe("
    1
    "); + parent.state.valA = 3; + await nextMicroTick(); + expect(steps.splice(0)).toEqual(["Parent:render", "ChildA:willUpdateProps"]); + await nextMicroTick(); + // same as above + await nextMicroTick(); + expect(steps).toEqual(["ChildA:render", "render"]); + expect(fixture.innerHTML).toBe("
    1
    "); + await nextTick(); + expect(fixture.innerHTML).toBe("
    3
    "); + expect(steps).toEqual([ + "ChildA:render", + "render", + "Parent:willPatch", + "ChildA:willPatch", + "ChildA:patched", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("properly behave when destroyed/unmounted while rendering ", async () => { + const steps: string[] = []; + const def = makeDeferred(); + + class SubChild extends Component { + static template = xml`
    `; + + setup() { + useLogLifecycle(steps); + onWillPatch(() => { + throw new Error("Should not happen!"); + }); + onPatched(() => { + throw new Error("Should not happen!"); + }); + onWillUpdateProps(() => { + return def; + }); + } + } + + class Child extends Component { + static template = xml`
    `; + static components = { SubChild }; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml` +
    `; + static components = { Child }; + state = useState({ flag: true, val: "Framboise Lindemans" }); + setup() { + useLogLifecycle(steps); + } + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    "); + + // this change triggers a rendering of the parent. This rendering is delayed, + // because child is now waiting for def to be resolved + parent.state.val = "Framboise Girardin"; + await nextTick(); + expect(fixture.innerHTML).toBe("
    "); + + // with this, we remove child, and subchild, even though it is not finished + // rendering from previous changes + parent.state.flag = false; + await nextTick(); + expect(fixture.innerHTML).toBe("
    "); + + // we now resolve def, so the child rendering is now complete. + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    "); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "SubChild:setup", + "SubChild:willStart", + "SubChild:render", + "SubChild:mounted", + "Child:mounted", + "Parent:mounted", + "Parent:render", + "Child:willUpdateProps", + "Child:render", + "SubChild:willUpdateProps", + "Parent:render", + "Parent:willPatch", + "Child:willUnmount", + "SubChild:willUnmount", + "SubChild:destroyed", + "Child:destroyed", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("rendering component again in next microtick", async () => { + const steps: string[] = []; + + class Child extends Component { + static template = xml`
    Child
    `; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml` +
    + + +
    `; + static components = { Child }; + + setup() { + useLogLifecycle(steps); + } + async onClick() { + this.env.flag = true; + this.render(); + await Promise.resolve(); + this.render(); + } + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    "); + fixture.querySelector("button")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    Child
    "); + + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Parent:mounted", + "Parent:render", + "Child:setup", + "Child:willStart", + "Parent:render", + "Child:destroyed", + "Child:setup", + "Child:willStart", + "Child:render", + "Parent:willPatch", + "Child:mounted", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 1", async () => { + const steps: string[] = []; + const def = makeDeferred(); + let stateB: any = null; + + class ComponentC extends Component { + static template = xml``; + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => def); + } + someValue() { + return this.props.fromB; + } + } + ComponentC.prototype.someValue = jest.fn(ComponentC.prototype.someValue); + + class ComponentB extends Component { + static template = xml`

    `; + static components = { ComponentC }; + state = useState({ fromB: "b" }); + + setup() { + stateB = this.state; + useLogLifecycle(steps); + } + } + + class ComponentA extends Component { + static template = xml`
    `; + static components = { ComponentB }; + state = useState({ fromA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("

    1b

    "); + + stateB.fromB = "c"; + await nextTick(); + + expect(fixture.innerHTML).toBe("

    1b

    "); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1b

    "); + + expect(ComponentC.prototype.someValue).toBeCalledTimes(1); + def.resolve(); + await nextTick(); + + expect(fixture.innerHTML).toBe("

    2c

    "); + expect(ComponentC.prototype.someValue).toBeCalledTimes(2); + + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentC:setup", + "ComponentC:willStart", + "ComponentC:render", + "ComponentC:mounted", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentB:render", + "ComponentC:willUpdateProps", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentB:render", + "ComponentC:willUpdateProps", + "ComponentC:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentC:willPatch", + "ComponentC:patched", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 2", async () => { + const steps: string[] = []; + // this test asserts that a rendering initiated before another one, and that + // ends after it, is re-mapped to that second rendering + const defs = [makeDeferred(), makeDeferred()]; + let index = 0; + let stateB: any = null; + class ComponentC extends Component { + static template = xml``; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defs[index++]); + } + } + + class ComponentB extends Component { + static template = xml`

    `; + static components = { ComponentC }; + state = useState({ fromB: "b" }); + + setup() { + useLogLifecycle(steps); + stateB = this.state; + } + } + + class ComponentA extends Component { + static template = xml`
    `; + static components = { ComponentB }; + state = useState({ fromA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("
    1

    1b

    "); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("
    1

    1b

    "); + + stateB.fromB = "c"; + await nextTick(); + expect(fixture.innerHTML).toBe("
    1

    1b

    "); + + defs[1].resolve(); // resolve rendering initiated in B + await nextTick(); + expect(fixture.innerHTML).toBe("
    2

    2c

    "); + + defs[0].resolve(); // resolve rendering initiated in A + await nextTick(); + expect(fixture.innerHTML).toBe("
    2

    2c

    "); + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentC:setup", + "ComponentC:willStart", + "ComponentC:render", + "ComponentC:mounted", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentB:render", + "ComponentC:willUpdateProps", + "ComponentB:render", + "ComponentC:willUpdateProps", + "ComponentC:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentC:willPatch", + "ComponentC:patched", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 2bis", async () => { + const steps: string[] = []; + const defs = [makeDeferred(), makeDeferred()]; + let index = 0; + let stateB: any = null; + class ComponentC extends Component { + static template = xml``; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defs[index++]); + } + } + + class ComponentB extends Component { + static template = xml`

    `; + static components = { ComponentC }; + state = useState({ fromB: "b" }); + + setup() { + useLogLifecycle(steps); + stateB = this.state; + } + } + + class ComponentA extends Component { + static template = xml`
    `; + static components = { ComponentB }; + state = useState({ fromA: 1 }); + + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("

    1b

    "); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1b

    "); + + stateB.fromB = "c"; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1b

    "); + + defs[0].resolve(); // resolve rendering initiated in A + await nextTick(); + expect(fixture.innerHTML).toBe("

    1b

    "); // TODO: is this what we want?? 2b could be ok too + + defs[1].resolve(); // resolve rendering initiated in B + await nextTick(); + expect(fixture.innerHTML).toBe("

    2c

    "); + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentC:setup", + "ComponentC:willStart", + "ComponentC:render", + "ComponentC:mounted", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentB:render", + "ComponentC:willUpdateProps", + "ComponentB:render", + "ComponentC:willUpdateProps", + "ComponentC:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentC:willPatch", + "ComponentC:patched", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 3", async () => { + const steps: string[] = []; + const defB = makeDeferred(); + const defsD = [makeDeferred(), makeDeferred()]; + let index = 0; + let stateC: any = null; + + class ComponentD extends Component { + static template = xml``; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defsD[index++]); + } + someValue() { + return this.props.fromC; + } + } + ComponentD.prototype.someValue = jest.fn(ComponentD.prototype.someValue); + + class ComponentC extends Component { + static template = xml``; + static components = { ComponentD }; + state = useState({ fromC: "c" }); + setup() { + useLogLifecycle(steps); + stateC = this.state; + } + } + + class ComponentB extends Component { + static template = xml`

    `; + static components = { ComponentC }; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defB); + } + } + + class ComponentA extends Component { + static components = { ComponentB }; + static template = xml`
    `; + state = useState({ fromA: 1 }); + + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("

    1c

    "); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1c

    "); + + stateC.fromC = "d"; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1c

    "); + + defB.resolve(); // resolve rendering initiated in A (still blocked in D) + await nextTick(); + expect(fixture.innerHTML).toBe("

    1c

    "); + + defsD[0].resolve(); // resolve rendering initiated in C (should be ignored) + await nextTick(); + expect(ComponentD.prototype.someValue).toBeCalledTimes(1); + expect(fixture.innerHTML).toBe("

    1c

    "); + + defsD[1].resolve(); // completely resolve rendering initiated in A + await nextTick(); + expect(fixture.innerHTML).toBe("

    2d

    "); + expect(ComponentD.prototype.someValue).toBeCalledTimes(2); + + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentC:setup", + "ComponentC:willStart", + "ComponentC:render", + "ComponentD:setup", + "ComponentD:willStart", + "ComponentD:render", + "ComponentD:mounted", + "ComponentC:mounted", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentC:render", + "ComponentD:willUpdateProps", + "ComponentB:render", + "ComponentC:willUpdateProps", + "ComponentC:render", + "ComponentD:willUpdateProps", + "ComponentD:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentC:willPatch", + "ComponentD:willPatch", + "ComponentD:patched", + "ComponentC:patched", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 4", async () => { + const steps: string[] = []; + const defB = makeDeferred(); + const defsD = [makeDeferred(), makeDeferred()]; + let index = 0; + let stateC: any = null; + + class ComponentD extends Component { + static template = xml``; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defsD[index++]); + } + someValue() { + return this.props.fromC; + } + } + ComponentD.prototype.someValue = jest.fn(ComponentD.prototype.someValue); + + class ComponentC extends Component { + static template = xml``; + static components = { ComponentD }; + state = useState({ fromC: "c" }); + setup() { + useLogLifecycle(steps); + stateC = this.state; + } + } + + class ComponentB extends Component { + static template = xml`

    `; + static components = { ComponentC }; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defB); + } + } + + class ComponentA extends Component { + static components = { ComponentB }; + static template = xml`
    `; + state = useState({ fromA: 1 }); + + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("

    1c

    "); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1c

    "); + + stateC.fromC = "d"; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1c

    "); + + defB.resolve(); // resolve rendering initiated in A (still blocked in D) + await nextTick(); + expect(fixture.innerHTML).toBe("

    1c

    "); + + defsD[1].resolve(); // completely resolve rendering initiated in A + await nextTick(); + expect(fixture.innerHTML).toBe("

    2d

    "); + expect(ComponentD.prototype.someValue).toBeCalledTimes(2); + + defsD[0].resolve(); // resolve rendering initiated in C (should be ignored) + await nextTick(); + expect(fixture.innerHTML).toBe("

    2d

    "); + expect(ComponentD.prototype.someValue).toBeCalledTimes(2); + + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentC:setup", + "ComponentC:willStart", + "ComponentC:render", + "ComponentD:setup", + "ComponentD:willStart", + "ComponentD:render", + "ComponentD:mounted", + "ComponentC:mounted", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentC:render", + "ComponentD:willUpdateProps", + "ComponentB:render", + "ComponentC:willUpdateProps", + "ComponentC:render", + "ComponentD:willUpdateProps", + "ComponentD:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentC:willPatch", + "ComponentD:willPatch", + "ComponentD:patched", + "ComponentC:patched", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 5", async () => { + const steps: string[] = []; + const defsB = [makeDeferred(), makeDeferred()]; + let index = 0; + + class ComponentB extends Component { + static template = xml`

    `; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defsB[index++]); + } + someValue() { + return this.props.fromA; + } + } + ComponentB.prototype.someValue = jest.fn(ComponentB.prototype.someValue); + + class ComponentA extends Component { + static components = { ComponentB }; + static template = xml`
    `; + state = useState({ fromA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("

    1

    "); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1

    "); + + component.state.fromA = 3; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1

    "); + + defsB[0].resolve(); // resolve first re-rendering (should be ignored) + await nextTick(); + expect(fixture.innerHTML).toBe("

    1

    "); + expect(ComponentB.prototype.someValue).toBeCalledTimes(1); + + defsB[1].resolve(); // resolve second re-rendering + await nextTick(); + expect(fixture.innerHTML).toBe("

    3

    "); + expect(ComponentB.prototype.someValue).toBeCalledTimes(2); + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentB:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 6", async () => { + const steps: string[] = []; + const defsB = [makeDeferred(), makeDeferred()]; + let index = 0; + + class ComponentB extends Component { + static template = xml`

    `; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => defsB[index++]); + } + someValue() { + return this.props.fromA; + } + } + ComponentB.prototype.someValue = jest.fn(ComponentB.prototype.someValue); + + class ComponentA extends Component { + static components = { ComponentB }; + static template = xml`
    `; + state = useState({ fromA: 1 }); + + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("

    1

    "); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1

    "); + + component.state.fromA = 3; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1

    "); + + defsB[1].resolve(); // resolve second re-rendering + await nextTick(); + expect(fixture.innerHTML).toBe("

    3

    "); + expect(ComponentB.prototype.someValue).toBeCalledTimes(2); + + defsB[0].resolve(); // resolve first re-rendering (should be ignored) + await nextTick(); + expect(fixture.innerHTML).toBe("

    3

    "); + expect(ComponentB.prototype.someValue).toBeCalledTimes(2); + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentB:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 7", async () => { + const steps: string[] = []; + + class ComponentB extends Component { + static template = xml`

    `; + state = useState({ fromB: "b" }); + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => { + this.state.fromB = "c"; + }); + } + someValue() { + return this.state.fromB; + } + } + ComponentB.prototype.someValue = jest.fn(ComponentB.prototype.someValue); + + class ComponentA extends Component { + static components = { ComponentB }; + static template = xml`
    `; + state = useState({ fromA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("

    1b

    "); + expect(ComponentB.prototype.someValue).toBeCalledTimes(1); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("

    2c

    "); + expect(ComponentB.prototype.someValue).toBeCalledTimes(2); + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentB:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 8", async () => { + const steps: string[] = []; + + const def = makeDeferred(); + let stateB: any = null; + class ComponentB extends Component { + static template = xml`

    `; + state = useState({ fromB: "b" }); + setup() { + useLogLifecycle(steps); + stateB = this.state; + onWillUpdateProps(() => def); + } + } + + class ComponentA extends Component { + static components = { ComponentB }; + static template = xml`
    `; + state = useState({ fromA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("

    1b

    "); + + component.state.fromA = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1b

    "); + + stateB.fromB = "c"; + await nextTick(); + expect(fixture.innerHTML).toBe("

    1b

    "); + + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("

    2c

    "); + + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentB:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 9", async () => { + // Here is the global idea of this scenario: + // A + // / \ + // B C + // | + // D + // A state is updated, triggering a whole re-rendering + // B is async, and blocked + // C (and D) are rendered + // C state is updated, producing a re-rendering of C and D + // this last re-rendering of C should be correctly re-mapped to the whole + // re-rendering + const steps: string[] = []; + const def = makeDeferred(); + let stateC: any = null; + + class ComponentD extends Component { + static template = xml``; + setup() { + useLogLifecycle(steps); + } + } + + class ComponentC extends Component { + static template = xml`

    `; + static components = { ComponentD }; + state = useState({ fromC: "b1" }); + + setup() { + stateC = this.state; + useLogLifecycle(steps); + } + } + class ComponentB extends Component { + static template = xml``; + + setup() { + onWillUpdateProps(() => def); + useLogLifecycle(steps); + } + } + class ComponentA extends Component { + static template = xml` +
    + + + +
    `; + static components = { ComponentB, ComponentC }; + state = useState({ fromA: "a1" }); + setup() { + useLogLifecycle(steps); + } + } + + const component = await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("
    a1a1

    a1b1

    "); + + component.state.fromA = "a2"; + await nextTick(); + expect(fixture.innerHTML).toBe("
    a1a1

    a1b1

    "); + + stateC.fromC = "b2"; + await nextTick(); + expect(fixture.innerHTML).toBe("
    a1a1

    a1b1

    "); + + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    a2a2

    a2b2

    "); + + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentC:setup", + "ComponentC:willStart", + "ComponentB:render", + "ComponentC:render", + "ComponentD:setup", + "ComponentD:willStart", + "ComponentD:render", + "ComponentD:mounted", + "ComponentC:mounted", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentC:willUpdateProps", + "ComponentC:render", + "ComponentD:willUpdateProps", + "ComponentD:render", + "ComponentC:render", + "ComponentD:willUpdateProps", + "ComponentD:render", + "ComponentB:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentC:willPatch", + "ComponentD:willPatch", + "ComponentD:patched", + "ComponentC:patched", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 10", async () => { + // Here is the global idea of this scenario: + // A + // | + // B <- async willUpdateProps + // ----- <- conditional (initialy false) + // | + // C <- async willStart + // Render A and B normally + // Change the condition on B to trigger a re-rendering with C (async willStart) + // Change the state on A to trigger a global re-rendering, which is blocked + // in B (async willUpdateProps) + // Resolve the willStart of C: the first re-rendering has been cancelled by + // the global re-rendering, but handlers waiting for the rendering promise to + // resolve might execute and we don't want them to crash/do anything + + const steps: string[] = []; + const defB = makeDeferred(); + const defC = makeDeferred(); + let stateB: any = null; + let rendered = 0; + class ComponentC extends Component { + static template = xml``; + setup() { + useLogLifecycle(steps); + onWillStart(() => defC); + } + get value() { + rendered++; + return this.props.value; + } + } + + class ComponentB extends Component { + static template = xml`

    `; + state = useState({ hasChild: false }); + static components = { ComponentC }; + setup() { + useLogLifecycle(steps); + stateB = this.state; + onWillUpdateProps(() => defB); + } + } + + class ComponentA extends Component { + static template = xml`
    `; + static components = { ComponentB }; + state = useState({ value: 1 }); + + setup() { + useLogLifecycle(steps); + } + } + + const componentA = await mount(ComponentA, fixture); + expect(fixture.innerHTML).toBe("

    "); + stateB.hasChild = true; + await nextTick(); + expect(fixture.innerHTML).toBe("

    "); + + componentA.state.value = 2; + defC.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("

    "); + + defB.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("

    2

    "); + expect(rendered).toBe(1); + expect(steps).toEqual([ + "ComponentA:setup", + "ComponentA:willStart", + "ComponentA:render", + "ComponentB:setup", + "ComponentB:willStart", + "ComponentB:render", + "ComponentB:mounted", + "ComponentA:mounted", + "ComponentB:render", + "ComponentC:setup", + "ComponentC:willStart", + "ComponentA:render", + "ComponentB:willUpdateProps", + "ComponentB:render", + "ComponentC:destroyed", + "ComponentC:setup", + "ComponentC:willStart", + "ComponentC:render", + "ComponentA:willPatch", + "ComponentB:willPatch", + "ComponentC:mounted", + "ComponentB:patched", + "ComponentA:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 11", async () => { + // This scenario is the following: we have a component being updated (by props), + // and then rendered (render method), but before the willUpdateProps resolves. + // We check that in that case, the return value of the render method is a promise + // that is resolved when the component is completely rendered (so, properly + // remapped to the promise of the ambient rendering) + const steps: string[] = []; + const def = makeDeferred(); + let child: any = null; + class Child extends Component { + static template = xml`|`; + val = 3; + + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => { + child = this; + return def; + }); + } + } + + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + state = useState({ valA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    1|3
    "); + parent.state.valA = 2; + + await nextTick(); + setTimeout(() => { + def.resolve(); + }, 20); + child.val = 5; + await child.render(); + expect(fixture.innerHTML).toBe("
    2|5
    "); + + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + "Parent:render", + "Child:willUpdateProps", + "Child:render", + "Parent:willPatch", + "Child:willPatch", + "Child:patched", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 12", async () => { + // In this scenario, we have a parent component that will be re-rendered + // several times simultaneously: + // - once in a tick: it will create a new fiber, render it, but will have + // to wait for its child (blocking) to be completed + // - twice in the next tick: it will twice reuse the same fiber (as it is + // rendered but not completed yet) + const steps: string[] = []; + const def = makeDeferred(); + + class Child extends Component { + static template = xml``; + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => def); + } + } + + let rendered = 0; + + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + state = useState({ val: 1 }); + setup() { + useLogLifecycle(steps); + } + + get val() { + rendered++; + return this.state.val; + } + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    1
    "); + expect(rendered).toBe(1); + + parent.state.val = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("
    1
    "); + expect(rendered).toBe(2); + + parent.state.val = 3; + parent.state.val = 4; + await nextTick(); + expect(fixture.innerHTML).toBe("
    1
    "); + expect(rendered).toBe(3); + + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    4
    "); + expect(rendered).toBe(3); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + "Parent:render", + "Child:willUpdateProps", + "Parent:render", + "Child:willUpdateProps", + "Child:render", + "Parent:willPatch", + "Child:willPatch", + "Child:patched", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 13", async () => { + const steps: string[] = []; + let lastChild: any = null; + + class Child extends Component { + static template = xml``; + state = useState({ val: 0 }); + setup() { + useLogLifecycle(steps); + onMounted(() => { + if (lastChild) { + lastChild.state.val = 0; + } + lastChild = this; + this.state.val = 1; + }); + } + } + + class Parent extends Component { + static template = xml` +
    + + +
    `; + static components = { Child }; + state = useState({ bool: false }); + setup() { + useLogLifecycle(steps); + } + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    0
    "); + + await nextTick(); // wait for changes triggered in mounted to be applied + expect(fixture.innerHTML).toBe("
    1
    "); + + parent.state.bool = true; + await nextTick(); // wait for this change to be applied + await nextTick(); // wait for changes triggered in mounted to be applied + expect(fixture.innerHTML).toBe("
    01
    "); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + "Child:render", + "Child:willPatch", + "Child:patched", + "Parent:render", + "Child:willUpdateProps", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:render", + "Parent:willPatch", + "Child:willPatch", + "Child:mounted", + "Child:patched", + "Parent:patched", + "Child:render", + "Child:render", + "Child:willPatch", + "Child:patched", + "Child:willPatch", + "Child:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 14", async () => { + const steps: string[] = []; + let b: B | undefined = undefined; + let c: C | undefined = undefined; + class C extends Component { + static template = xml` +

    + + + +

    `; + + state = useState({ fromC: 3 }); + setup() { + useLogLifecycle(steps); + c = this; + } + } + class B extends Component { + static template = xml`

    `; + static components = { C }; + setup() { + useLogLifecycle(steps); + b = this; + } + state = useState({ fromB: 2 }); + } + + class A extends Component { + static template = xml`

    `; + static components = { B }; + state = useState({ fromA: 1 }); + + setup() { + useLogLifecycle(steps); + } + } + const a = await mount(A, fixture); + expect(fixture.innerHTML).toBe("

    123

    "); + + // trigger a re-rendering of the whole tree + a.state.fromA += 10; + // wait enough for the whole tree to be re-rendered, but not patched yet + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + expect(fixture.innerHTML).toBe("

    123

    "); + + // trigger a re-rendering from C, which will remap its new fiber + c!.state.fromC += 10; + // trigger a re-rendering from B, which will remap its new fiber as well + b!.state.fromB += 10; + + await nextTick(); + // at this point, all re-renderings should have been done correctly, and + // the root fiber (A) counter should have been reset to 0, so the DOM should + // have been patched with the updated version of each component + expect(fixture.innerHTML).toBe( + "

    111213

    " + ); + expect(steps).toEqual([ + "A:setup", + "A:willStart", + "A:render", + "B:setup", + "B:willStart", + "B:render", + "C:setup", + "C:willStart", + "C:render", + "C:mounted", + "B:mounted", + "A:mounted", + "A:render", + "B:willUpdateProps", + "B:render", + "C:willUpdateProps", + "C:render", + "B:render", + "C:willUpdateProps", + "C:render", + "A:willPatch", + "B:willPatch", + "C:willPatch", + "C:patched", + "B:patched", + "A:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 15", async () => { + const steps: string[] = []; + let b: B | undefined = undefined; + let c: C | undefined = undefined; + class C extends Component { + static template = xml` +

    + + + +

    `; + + state = useState({ fromC: 3 }); + setup() { + useLogLifecycle(steps); + c = this; + } + } + class B extends Component { + static template = xml`

    `; + static components = { C }; + setup() { + useLogLifecycle(steps); + b = this; + } + state = useState({ fromB: 2 }); + } + class A extends Component { + static template = xml`

    `; + static components = { B }; + state = useState({ fromA: 1 }); + setup() { + useLogLifecycle(steps); + } + } + const app = new App(A); + const a = await app.mount(fixture); + expect(fixture.innerHTML).toBe("

    123

    "); + + // trigger a re-rendering of the whole tree + a.state.fromA += 10; + // wait enough for the whole tree to be re-rendered, but not patched yet + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + expect(fixture.innerHTML).toBe("

    123

    "); + + // trigger a re-rendering from C, which will remap its new fiber + c!.state.fromC += 10; + // trigger a re-rendering from B, which will remap its new fiber as well + b!.state.fromB += 10; + + // simulate a flush (nothing should have changed as no fiber should have its + // counter to 0) + app.scheduler.flush(); + expect(fixture.innerHTML).toBe("

    123

    "); + + // wait a bit and simulate another flush (we expect nothing to change as well) + await nextMicroTick(); + app.scheduler.flush(); + expect(fixture.innerHTML).toBe("

    123

    "); + + await nextTick(); + expect(fixture.innerHTML).toBe( + "

    111213

    " + ); + expect(steps).toEqual([ + "A:setup", + "A:willStart", + "A:render", + "B:setup", + "B:willStart", + "B:render", + "C:setup", + "C:willStart", + "C:render", + "C:mounted", + "B:mounted", + "A:mounted", + "A:render", + "B:willUpdateProps", + "B:render", + "C:willUpdateProps", + "C:render", + "B:render", + "C:willUpdateProps", + "C:render", + "A:willPatch", + "B:willPatch", + "C:willPatch", + "C:patched", + "B:patched", + "A:patched", + ]); + Object.freeze(steps); +}); + +test("concurrent renderings scenario 16", async () => { + const steps: string[] = []; + let b: B | undefined = undefined; + let c: C | undefined = undefined; + class D extends Component { + static template = xml`
      DDD
    `; + + setup() { + useLogLifecycle(steps); + onWillStart(async () => { + await nextTick(); + await nextTick(); + }); + } + } + class C extends Component { + static template = xml` +

    + + + + +

    `; + static components = { D }; + state = { fromC: 3 }; // not reactive + setup() { + useLogLifecycle(steps); + c = this; + } + } + class B extends Component { + static template = xml`

    `; + static components = { C }; + setup() { + useLogLifecycle(steps); + b = this; + } + state = useState({ fromB: 2 }); + } + class A extends Component { + static template = xml`

    `; + static components = { B }; + state = useState({ fromA: 1 }); + + setup() { + useLogLifecycle(steps); + } + } + const a = await mount(A, fixture); + expect(fixture.innerHTML).toBe("

    123

    "); + + // trigger a re-rendering of the whole tree + a.state.fromA += 10; + // wait enough for the whole tree to be re-rendered, but not patched yet + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + await nextMicroTick(); + expect(fixture.innerHTML).toBe("

    123

    "); + + // trigger a re-rendering from C, which will remap its new fiber + c!.state.fromC += 10; + const prom = c!.render(); + // trigger a re-rendering from B, which will remap its new fiber as well + b!.state.fromB += 10; + + await nextTick(); + // at this point, C rendering is still pending, and nothing should have been + // updated yet. + expect(fixture.innerHTML).toBe("

    123

    "); + await prom; + expect(fixture.innerHTML).toBe( + "

    111213

      DDD

    " + ); + expect(steps).toEqual([ + "A:setup", + "A:willStart", + "A:render", + "B:setup", + "B:willStart", + "B:render", + "C:setup", + "C:willStart", + "C:render", + "C:mounted", + "B:mounted", + "A:mounted", + "A:render", + "B:willUpdateProps", + "B:render", + "C:willUpdateProps", + "C:render", + "B:render", + "C:willUpdateProps", + "C:render", + "D:setup", + "D:willStart", + "D:render", + "A:willPatch", + "B:willPatch", + "C:willPatch", + "D:mounted", + "C:patched", + "B:patched", + "A:patched", + ]); + Object.freeze(steps); +}); + +// test.skip("concurrent renderings scenario 17", async () => { +// class Parent extends Component { +// static template = xml``; +// state = useState({ value: 1 }); +// } + +// const parent = await mount(Parent, fixture); +// expect(fixture.innerHTML).toBe("1"); + +// parent.state.value = 2; +// parent.__owl__.fiber!.cancel(); + +// parent.state.value = 3; // update value directly +// await nextTick(); +// expect(fixture.innerHTML).toBe("3"); + +// parent.state.value = 4; // update value after a tick +// await nextTick(); +// expect(fixture.innerHTML).toBe("4"); +// }); + +// test("calling render in destroy", async () => { +// let a: any = null; +// let c: any = null; + +// class C extends Component { +// static template = xml` +//
    +// +//
    `; +// } + +// let flag = false; +// class B extends Component { +// static template = xml``; +// static components = { C }; + +// setup() { +// c = this; +// } + +// mounted() { +// if (flag) { +// this.render(); +// } else { +// flag = true; +// } +// } +// willUnmount() { +// c.render(); +// } +// } + +// class A extends Component { +// static template = xml``; +// static components = { B }; +// state = "a"; +// key = 1; + +// setup() { +// a = this; +// } +// } + +// const parent = new A(); +// await parent.mount(fixture); +// expect(fixture.innerHTML).toBe("
    a
    "); + +// a.state = "A"; +// a.key = 2; +// await a.render(); +// // this nextTick is critical, otherwise jest may silently swallow errors +// await nextTick(); + +// expect(fixture.innerHTML).toBe("
    A
    "); +// }); + +test("change state and call manually render: no unnecessary rendering", async () => { + const steps: string[] = []; + let numberOfRender = 0; + + class Test extends Component { + static template = xml`
    `; + state = useState({ val: 1 }); + + setup() { + useLogLifecycle(steps); + } + get value() { + numberOfRender++; + return this.state.val; + } + } + + const test = await mount(Test, fixture); + expect(fixture.innerHTML).toBe("
    1
    "); + expect(numberOfRender).toBe(1); + + test.state.val = 2; + await test.render(); + expect(fixture.innerHTML).toBe("
    2
    "); + expect(numberOfRender).toBe(2); + expect(steps).toEqual([ + "Test:setup", + "Test:willStart", + "Test:render", + "Test:mounted", + "Test:render", + "Test:willPatch", + "Test:patched", + ]); + Object.freeze(steps); +}); + +test("changing state before first render does not trigger a render", async () => { + const steps: string[] = []; + let renders = 0; + + class TestW extends Component { + static template = xml`
    `; + state = useState({ drinks: 1 }); + setup() { + useLogLifecycle(steps); + this.state.drinks++; + onWillStart(() => { + this.state.drinks++; + }); + } + get value() { + renders++; + return this.state.drinks; + } + } + await mount(TestW, fixture); + + await nextTick(); + expect(renders).toBe(1); + expect(fixture.innerHTML).toBe("
    3
    "); + expect(steps).toEqual(["TestW:setup", "TestW:willStart", "TestW:render", "TestW:mounted"]); + Object.freeze(steps); +}); + +test("changing state before first render does not trigger a render (with parent)", async () => { + const steps: string[] = []; + let renders = 0; + + class TestW extends Component { + static template = xml`
    `; + state = useState({ drinks: 1 }); + setup() { + useLogLifecycle(steps); + this.state.drinks++; + onWillStart(() => { + this.state.drinks++; + }); + } + get value() { + renders++; + return this.state.drinks; + } + } + + class Parent extends Component { + static components = { TestW }; + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + } + state = useState({ flag: false }); + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    "); + expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]); + steps.splice(0); + parent.state.flag = true; + await nextTick(); + expect(fixture.innerHTML).toBe("
    3
    "); + expect(renders).toBe(1); + expect(steps).toEqual([ + "Parent:render", + "TestW:setup", + "TestW:willStart", + "TestW:render", + "Parent:willPatch", + "TestW:mounted", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +test("render method wait until rendering is done", async () => { + class TestW extends Component { + static template = xml`
    `; + state = { drinks: 1 }; + } + const widget = await mount(TestW, fixture); + expect(fixture.innerHTML).toBe("
    1
    "); + + widget.state.drinks = 2; + + const renderPromise = widget.render(); + expect(fixture.innerHTML).toBe("
    1
    "); + await renderPromise; + expect(fixture.innerHTML).toBe("
    2
    "); +}); + +test("two renderings initiated between willPatch and patched", async () => { + let parent: any = null; + let steps: string[] = []; + + class Panel extends Component { + static template = xml``; + setup() { + useLogLifecycle(steps); + onMounted(() => parent.render()); + onWillUnmount(() => parent.render()); + } + } + + // Main root component + class Parent extends Component { + static template = xml`
    `; + static components = { Panel }; + state = useState({ panel: "Panel1", flag: true }); + setup() { + useLogLifecycle(steps); + parent = this; + } + } + + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    Panel1
    "); + + parent.state.panel = "Panel2"; + await nextTick(); + expect(fixture.innerHTML).toBe("
    Panel2
    "); + + parent.state.flag = false; + await nextTick(); + expect(fixture.innerHTML).toBe("
    "); + + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Panel:setup", + "Panel:willStart", + "Panel:render", + "Panel:mounted", + "Parent:mounted", + "Parent:render", + "Panel:willUpdateProps", + "Panel:render", + "Parent:willPatch", + "Panel:willPatch", + "Panel:patched", + "Parent:patched", + "Parent:render", + "Parent:willPatch", + "Panel:willUnmount", + "Panel:destroyed", + "Parent:patched", + ]); + Object.freeze(steps); +}); + +// test.skip("components with shouldUpdate=false", async () => { +// const state = { p: 1, cc: 10 }; + +// class ChildChild extends Component { +// static template = xml` +//
    +// child child: +//
    `; +// state = state; +// shouldUpdate() { +// return false; +// } +// } + +// class Child extends Component { +// static components = { ChildChild }; +// static template = xml` +//
    +// child +// +//
    `; + +// shouldUpdate() { +// return false; +// } +// } + +// let parent: any; +// class Parent extends Component { +// static components = { Child }; +// static template = xml` +//
    +// parent: +// +//
    `; + +// state = state; +// constructor(a, b) { +// super(a, b); +// parent = this; +// } +// shouldUpdate() { +// return false; +// } +// } + +// class App extends Component { +// static components = { Parent }; +// static template = xml` +//
    +// +//
    `; +// } + +// var div = document.createElement("div"); +// fixture.appendChild(div); + +// const app = new App(); + +// await app.mount(fixture); +// expect(fixture.innerHTML).toBe( +// "
    parent: 1
    child
    child child: 10
    " +// ); +// app.mount(div); + +// // wait for rendering from second mount to go through parent +// await Promise.resolve(); +// await Promise.resolve(); +// state.cc++; +// state.p++; +// parent.render(); +// await nextTick(); +// expect(fixture.innerHTML).toBe( +// "
    parent: 2
    child
    child child: 11
    " +// ); +// }); + +// test.skip("components with shouldUpdate=false, part 2", async () => { +// const state = { p: 1, cc: 10 }; +// let shouldUpdate = true; + +// class ChildChild extends Component { +// static template = xml` +//
    +// child child: +//
    `; +// state = state; +// shouldUpdate() { +// return shouldUpdate; +// } +// } + +// class Child extends Component { +// static components = { ChildChild }; +// static template = xml` +//
    +// child +// +//
    `; + +// shouldUpdate() { +// return shouldUpdate; +// } +// } + +// let parent: any; +// class Parent extends Component { +// static components = { Child }; +// static template = xml` +//
    +// parent: +// +//
    `; + +// state = state; +// constructor(a, b) { +// super(a, b); +// parent = this; +// } +// shouldUpdate() { +// return shouldUpdate; +// } +// } + +// class App extends Component { +// static components = { Parent }; +// static template = xml` +//
    +// +//
    `; +// } + +// const app = new App(); + +// await app.mount(fixture); +// expect(fixture.innerHTML).toBe( +// "
    parent: 1
    child
    child child: 10
    " +// ); + +// state.cc++; +// state.p++; +// app.render(); + +// // wait for rendering to go through child +// await Promise.resolve(); +// await Promise.resolve(); +// await Promise.resolve(); + +// shouldUpdate = false; +// parent.render(); +// await nextTick(); +// expect(fixture.innerHTML).toBe( +// "
    parent: 2
    child
    child child: 11
    " +// ); +// }); +// }); diff --git a/tests/components/env.test.ts b/tests/components/env.test.ts new file mode 100644 index 00000000..9ba9e6f5 --- /dev/null +++ b/tests/components/env.test.ts @@ -0,0 +1,51 @@ +import { App, Component, mount, xml } from "../../src"; +import { makeTestFixture } from "../helpers"; + +let fixture: HTMLElement; + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("env handling", () => { + test("keeps a reference to env", async () => { + const env = {}; + class Test extends Component { + static template = xml`
    `; + } + const app = new App(Test); + app.configure({ env }); + const component = await app.mount(fixture); + expect(component.env).toBe(env); + }); + + test("has an env by default", async () => { + class Test extends Component { + static template = xml`
    `; + } + const component = await mount(Test, fixture); + expect(component.env).toEqual({}); + }); + + test("parent env is propagated to child components", async () => { + const env = {}; + let child: any = null; + + class Child extends Component { + static template = xml`
    `; + setup() { + child = this; + } + } + + class Test extends Component { + static template = xml``; + static components = { Child }; + } + + const app = new App(Test); + app.configure({ env }); + await app.mount(fixture); + expect(child.env).toBe(env); + }); +}); diff --git a/tests/components/error_handling.test.ts b/tests/components/error_handling.test.ts new file mode 100644 index 00000000..02320f42 --- /dev/null +++ b/tests/components/error_handling.test.ts @@ -0,0 +1,42 @@ +import { Component, mount } from "../../src"; +import { status } from "../../src/component/status"; +import { xml } from "../../src/tags"; +import { makeTestFixture, snapshotEverything } from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("basics", () => { + test("no component catching error lead to full app destruction", async () => { + class ErrorComponent extends Component { + static template = xml`
    hey
    `; + } + + class Parent extends Component { + static template = xml`
    `; + static components = { ErrorComponent }; + state = { flag: false }; + } + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    heyfalse
    "); + parent.state.flag = true; + + let error; + try { + await parent.render(); + } catch (e) { + error = e; + } + expect(fixture.innerHTML).toBe(""); + expect(status(parent)).toBe("destroyed"); + expect(error).toBeDefined(); + const regexp = + /Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/g; + expect(error.message).toMatch(regexp); + }); +}); diff --git a/tests/components/event_handling.test.ts b/tests/components/event_handling.test.ts new file mode 100644 index 00000000..49153ece --- /dev/null +++ b/tests/components/event_handling.test.ts @@ -0,0 +1,75 @@ +import { makeTestFixture, snapshotEverything, nextTick } from "../helpers"; +import { mount, Component, useState, xml } from "../../src/index"; + +snapshotEverything(); + +let fixture: HTMLElement; + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("event handling", () => { + test("can set handler on sub component", async () => { + class Child extends Component { + static template = xml`
    simple vnode
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ value: 1 }); + inc() { + this.state.value++; + } + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    simple vnode
    1"); + + fixture.querySelector("div")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    simple vnode
    2"); + }); + + test("handler receive the event as argument", async () => { + class Child extends Component { + static template = xml`
    simple vnode
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ value: 1 }); + inc(ev: any) { + this.state.value++; + expect(ev.type).toBe("click"); + } + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    simple vnode
    1"); + + fixture.querySelector("div")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    simple vnode
    2"); + }); + + test("support for callable expression in event handler", async () => { + class Counter extends Component { + static template = xml` +
    `; + state = useState({ value: "" }); + obj = { onInput: (ev: any) => (this.state.value = ev.target.value) }; + } + + const counter = await mount(Counter, fixture); + await nextTick(); + expect(fixture.innerHTML).toBe(`
    `); + const input = (counter.el).getElementsByTagName("input")[0]; + input.value = "test"; + input.dispatchEvent(new Event("input", { bubbles: true })); + await nextTick(); + expect(fixture.innerHTML).toBe(`
    test
    `); + }); +}); diff --git a/tests/components/higher_order_component.test.ts b/tests/components/higher_order_component.test.ts new file mode 100644 index 00000000..45c633f2 --- /dev/null +++ b/tests/components/higher_order_component.test.ts @@ -0,0 +1,119 @@ +import { App, Component, mount, useState } from "../../src"; +import { xml } from "../../src/tags"; +import { makeTestFixture, nextTick, snapshotEverything } from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("basics", () => { + test("basic use", async () => { + class Child extends Component { + static template = xml`child`; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("child1"); + }); + + test("sub widget is interactive", async () => { + class Child extends Component { + static template = xml` + + child + `; + state = useState({ val: 1 }); + inc() { + this.state.val++; + } + } + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("child1"); + const button = fixture.querySelector("button")!; + button.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("child2"); + }); + + test("can select a sub widget ", async () => { + class Child extends Component { + static template = xml`CHILD 1`; + } + class OtherChild extends Component { + static template = xml`
    CHILD 2
    `; + } + class Parent extends Component { + static template = xml` + + + + + `; + static components = { Child, OtherChild }; + } + const env = { flag: true }; + const app = new App(Parent); + app.configure({ env }); + const parent = await app.mount(fixture); + expect(fixture.innerHTML).toBe("CHILD 1"); + + env.flag = false; + await parent.render(); + expect(fixture.innerHTML).toBe("
    CHILD 2
    "); + }); + + test("can select a sub widget, part 2", async () => { + class Child extends Component { + static template = xml`CHILD 1`; + } + class OtherChild extends Component { + static template = xml`
    CHILD 2
    `; + } + class Parent extends Component { + static template = xml` + + + + + `; + state = useState({ flag: true }); + static components = { Child, OtherChild }; + } + let parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("CHILD 1"); + parent.state.flag = false; + await nextTick(); + expect(fixture.innerHTML).toBe("
    CHILD 2
    "); + }); + + test("top level sub widget with a parent", async () => { + class ComponentC extends Component { + static template = xml`Hello`; + } + class ComponentB extends Component { + static template = xml``; + static components = { ComponentC }; + } + class ComponentA extends Component { + static template = xml`
    `; + static components = { ComponentB }; + } + + await mount(ComponentA, fixture); + + expect(fixture.innerHTML).toBe("
    Hello
    "); + }); +}); diff --git a/tests/components/lifecycle.test.ts b/tests/components/lifecycle.test.ts new file mode 100644 index 00000000..c91bca52 --- /dev/null +++ b/tests/components/lifecycle.test.ts @@ -0,0 +1,912 @@ +import { App, Component, mount, onMounted, onWillStart, useState } from "../../src"; +import { + onWillPatch, + onWillUnmount, + onPatched, + onWillUpdateProps, + onRender, +} from "../../src/lifecycle_hooks"; +import { status } from "../../src/component/status"; +import { xml } from "../../src/tags"; +import { + makeDeferred, + makeTestFixture, + nextTick, + snapshotEverything, + useLogLifecycle, +} from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("lifecycle hooks", () => { + test("basic checks for a component", async () => { + expect.assertions(6); // 1 for snapshots + class Test extends Component { + static template = xml`test`; + + setup() { + expect(status(this)).toBe("new"); + } + } + + const app = new App(Test); + + const component = await app.mount(fixture); + + expect(fixture.innerHTML).toBe("test"); + expect(status(component)).toBe("mounted"); + + app.destroy(); + + expect(fixture.innerHTML).toBe(""); + expect(status(component)).toBe("destroyed"); + }); + + test("willStart is called", async () => { + let willstart = false; + class Test extends Component { + static template = xml`simple vnode`; + setup() { + onWillStart(() => { + willstart = true; + }); + } + } + + await mount(Test, fixture); + expect(willstart).toBe(true); + }); + + test("willStart hook is called on sub component", async () => { + let ok = false; + class Child extends Component { + static template = xml`
    `; + setup() { + onWillStart(() => { + ok = true; + }); + } + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(ok).toBe(true); + }); + + test("willStart is called with component as this", async () => { + expect.assertions(3); + let comp: any; + + class Test extends Component { + static template = xml`simple vnode`; + setup() { + comp = this; + onWillStart(this.willStart); + } + + willStart() { + expect(this).toBeInstanceOf(Test); + expect(this).toBe(comp); + } + } + + await mount(Test, fixture); + }); + + test("mounted hook is called if mounted in DOM", async () => { + let mounted = false; + class Test extends Component { + static template = xml`
    `; + + setup() { + onMounted(() => { + mounted = true; + }); + } + } + await mount(Test, fixture); + expect(mounted).toBe(true); + }); + + test("mounted hook is called on subcomponents, in proper order", async () => { + const steps: any[] = []; + + class Child extends Component { + static template = xml`
    `; + setup() { + onMounted(() => { + expect(document.body.contains(this.el!)).toBe(true); + steps.push("child:mounted"); + }); + } + } + + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + setup() { + onMounted(() => { + steps.push("parent:mounted"); + }); + } + } + await mount(Parent, fixture); + expect(steps).toEqual(["child:mounted", "parent:mounted"]); + Object.freeze(steps); + }); + + test("mounted hook is called on subsubcomponents, in proper order", async () => { + const steps: any[] = []; + + class ChildChild extends Component { + static template = xml`
    `; + setup() { + onMounted(() => { + steps.push("childchild:mounted"); + }); + onWillUnmount(() => { + steps.push("childchild:willUnmount"); + }); + } + } + + class Child extends Component { + static template = xml`
    `; + static components = { ChildChild }; + setup() { + onMounted(() => { + steps.push("child:mounted"); + }); + onWillUnmount(() => { + steps.push("child:willUnmount"); + }); + } + } + + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + state = useState({ flag: false }); + setup() { + onMounted(() => { + steps.push("parent:mounted"); + }); + onWillUnmount(() => { + steps.push("parent:willUnmount"); + }); + } + } + + const app = new App(Parent); + const widget = await app.mount(fixture); + expect(steps).toEqual(["parent:mounted"]); + widget.state.flag = true; + await nextTick(); + app.destroy(); + expect(steps).toEqual([ + "parent:mounted", + "childchild:mounted", + "child:mounted", + "parent:willUnmount", + "child:willUnmount", + "childchild:willUnmount", + ]); + Object.freeze(steps); + }); + + test("willPatch, patched hook are called on subsubcomponents, in proper order", async () => { + const steps: any[] = []; + + class ChildChild extends Component { + static template = xml` +
    + `; + + setup() { + onWillPatch(() => { + steps.push("childchild:willPatch"); + }); + onPatched(() => { + steps.push("childchild:patched"); + }); + } + } + + class Child extends Component { + static template = xml` +
    + `; + static components = { ChildChild }; + + setup() { + onWillPatch(() => { + steps.push("child:willPatch"); + }); + onPatched(() => { + steps.push("child:patched"); + }); + } + } + + class Parent extends Component { + static template = xml` +
    + `; + static components = { Child }; + + state = useState({ n: 1 }); + + setup() { + onWillPatch(() => { + steps.push("parent:willPatch"); + }); + onPatched(() => { + steps.push("parent:patched"); + }); + } + } + + const app = new App(Parent); + const parent = await app.mount(fixture); + expect(steps).toEqual([]); + parent.state.n = 2; + await nextTick(); + app.destroy(); + expect(steps).toEqual([ + "parent:willPatch", + "child:willPatch", + "childchild:willPatch", + "childchild:patched", + "child:patched", + "parent:patched", + ]); + Object.freeze(steps); + }); + + test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => { + const steps: string[] = []; + + class Child extends Component { + static template = xml`
    `; + + setup() { + onWillStart(() => { + steps.push("child:willStart"); + }); + onMounted(() => { + steps.push("child:mounted"); + }); + } + } + + class Parent extends Component { + // the t-else part in the template is important. This is + // necessary to have a situation that could confuse the vdom + // patching algorithm + static template = xml` +
    + + + + +
    + +
    `; + + static components = { Child }; + state = useState({ ok: false }); + } + const parent = await mount(Parent, fixture); + expect(steps).toEqual([]); + parent.state.ok = true; + await nextTick(); + expect(steps).toEqual(["child:willStart", "child:mounted"]); + Object.freeze(steps); + Object.freeze(steps); + }); + + test("components are unmounted and destroyed if no longer in DOM", async () => { + let steps: string[] = []; + + class Child extends Component { + static template = xml`
    `; + setup() { + steps.push("setup"); + onWillStart(() => { + steps.push("willstart"); + }); + onMounted(() => { + steps.push("mounted"); + }); + onWillUnmount(() => { + steps.push("willunmount"); + }); + } + } + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ ok: true }); + } + + const parent = await mount(Parent, fixture); + expect(steps).toEqual(["setup", "willstart", "mounted"]); + parent.state.ok = false; + await nextTick(); + expect(steps).toEqual(["setup", "willstart", "mounted", "willunmount"]); + Object.freeze(steps); + }); + + test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => { + let steps: string[] = []; + + class Child extends Component { + static template = xml``; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml` +
    + +
    + `; + static components = { Child }; + setup() { + useLogLifecycle(steps); + } + state = useState({ n: 0, flag: true }); + increment() { + this.state.n += 1; + } + toggleSubWidget() { + this.state.flag = !this.state.flag; + } + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    0
    "); + parent.increment(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    1
    "); + parent.toggleSubWidget(); + await nextTick(); + expect(fixture.innerHTML).toBe(""); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + "Parent:render", + "Child:willUpdateProps", + "Child:render", + "Parent:willPatch", + "Child:willPatch", + "Child:patched", + "Parent:patched", + "Parent:render", + "Parent:willPatch", + "Child:willUnmount", + "Child:destroyed", + "Parent:patched", + ]); + Object.freeze(steps); + }); + + test("hooks are called in proper order in widget creation/destruction", async () => { + let steps: string[] = []; + + class Child extends Component { + static template = xml`
    `; + setup() { + steps.push("c init"); + onWillStart(() => { + steps.push("c willstart"); + }); + onMounted(() => { + steps.push("c mounted"); + }); + onWillUnmount(() => { + steps.push("c willunmount"); + }); + } + } + + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + setup() { + steps.push("p init"); + onWillStart(() => { + steps.push("p willstart"); + }); + onMounted(() => { + steps.push("p mounted"); + }); + onWillUnmount(() => { + steps.push("p willunmount"); + }); + } + } + + const app = new App(Parent); + await app.mount(fixture); + app.destroy(); + expect(steps).toEqual([ + "p init", + "p willstart", + "c init", + "c willstart", + "c mounted", + "p mounted", + "p willunmount", + "c willunmount", + ]); + Object.freeze(steps); + }); + + test("willUpdateProps hook is called", async () => { + let def = makeDeferred(); + + class Child extends Component { + static template = xml``; + + setup() { + onWillUpdateProps((nextProps) => { + expect(nextProps.n).toBe(2); + return def; + }); + } + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ n: 1 }); + } + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("1"); + parent.state.n = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("1"); + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("2"); + }); + + test("patched hook is called after updating State", async () => { + let n = 0; + + class Test extends Component { + static template = xml`
    `; + state = useState({ a: 1 }); + + setup() { + onPatched(() => n++); + } + } + const widget = await mount(Test, fixture); + expect(n).toBe(0); + + widget.state.a = 1; // empty update, should do nothing + await nextTick(); + expect(n).toBe(0); + + widget.state.a = 3; + await nextTick(); + expect(n).toBe(1); + }); + + test("patched hook is called after updateProps", async () => { + let n = 0; + + class Child extends Component { + static template = xml`
    `; + setup() { + onWillUpdateProps(() => { + n++; + }); + } + } + class Parent extends Component { + static template = xml`
    `; + state = useState({ a: 1 }); + static components = { Child }; + } + + const widget = await mount(Parent, fixture); + expect(n).toBe(0); + + widget.state.a = 2; + await nextTick(); + expect(n).toBe(1); + }); + + test("lifecycle semantics", async () => { + let steps: string[] = []; + + class Child extends Component { + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + } + } + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + state = useState({ a: 1 }); + setup() { + useLogLifecycle(steps); + } + } + + const app = new App(Parent); + await app.mount(fixture); + + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + ]); + + steps.splice(0); + app.destroy(); + expect(steps).toEqual([ + "Parent:willUnmount", + "Child:willUnmount", + "Child:destroyed", + "Parent:destroyed", + ]); + Object.freeze(steps); + }); + + test("lifecycle semantics, part 2", async () => { + let steps: string[] = []; + + class GrandChild extends Component { + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + } + } + class Child extends Component { + static template = xml``; + static components = { GrandChild }; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ hasChild: false }); + setup() { + useLogLifecycle(steps); + } + } + + const app = new App(Parent); + const parent = await app.mount(fixture); + + expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]); + + steps.splice(0); + + parent.state.hasChild = true; + await nextTick(); + expect(steps).toEqual([ + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "GrandChild:setup", + "GrandChild:willStart", + "GrandChild:render", + "Parent:willPatch", + "GrandChild:mounted", + "Child:mounted", + "Parent:patched", + ]); + + steps.splice(0); + + app.destroy(); + expect(steps).toEqual([ + "Parent:willUnmount", + "Child:willUnmount", + "GrandChild:willUnmount", + "GrandChild:destroyed", + "Child:destroyed", + "Parent:destroyed", + ]); + Object.freeze(steps); + }); + + test("lifecycle semantics, part 3", async () => { + let steps: string[] = []; + + class GrandChild extends Component { + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + } + } + class Child extends Component { + static template = xml``; + static components = { GrandChild }; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ hasChild: false }); + setup() { + useLogLifecycle(steps); + } + } + + const app = new App(Parent); + const parent = await app.mount(fixture); + + expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]); + + steps.splice(0); + + parent.state.hasChild = true; + + // immediately destroy everything + app.destroy(); + await nextTick(); + expect(steps).toEqual(["Parent:willUnmount", "Parent:destroyed"]); + Object.freeze(steps); + }); + + test("lifecycle semantics, part 4", async () => { + let def = makeDeferred(); + + let steps: string[] = []; + + class GrandChild extends Component { + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + onWillStart(() => def); + } + } + class Child extends Component { + static template = xml``; + static components = { GrandChild }; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ hasChild: false }); + setup() { + useLogLifecycle(steps); + } + } + + const app = new App(Parent); + const parent = await app.mount(fixture); + + expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]); + + steps.splice(0); + + parent.state.hasChild = true; + + await nextTick(); + expect(steps).toEqual([ + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "GrandChild:setup", + "GrandChild:willStart", + ]); + + steps.splice(0); + + app.destroy(); + expect(steps).toEqual([ + "Parent:willUnmount", + "GrandChild:destroyed", + "Child:destroyed", + "Parent:destroyed", + ]); + Object.freeze(steps); + }); + + test("lifecycle semantics, part 5", async () => { + let steps: string[] = []; + + class Child extends Component { + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ hasChild: true }); + setup() { + useLogLifecycle(steps); + } + } + + const parent = await mount(Parent, fixture); + + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + ]); + + steps.splice(0); + + parent.state.hasChild = false; + + await nextTick(); + expect(steps).toEqual([ + "Parent:render", + "Parent:willPatch", + "Child:willUnmount", + "Child:destroyed", + "Parent:patched", + ]); + Object.freeze(steps); + }); + + test("lifecycle semantics, part 6", async () => { + let steps: string[] = []; + + class Child extends Component { + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ value: 1 }); + setup() { + useLogLifecycle(steps); + } + } + + const parent = await mount(Parent, fixture); + + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + ]); + + steps.splice(0); + + parent.state.value = 2; + + await nextTick(); + expect(steps).toEqual([ + "Parent:render", + "Child:willUpdateProps", + "Child:render", + "Parent:willPatch", + "Child:willPatch", + "Child:patched", + "Parent:patched", + ]); + Object.freeze(steps); + }); + + test("onRender", async () => { + let steps: string[] = []; + const def = makeDeferred(); + + class Child extends Component { + static template = xml``; + state = useState({ value: 1 }); + visibleState = this.state.value; + setup() { + useLogLifecycle(steps); + onWillUpdateProps(() => def); + onRender(() => (this.visibleState = this.state.value)); + } + increment() { + this.state.value++; + steps.push(`inc:${this.visibleState}`); + } + } + + class Parent extends Component { + static template = xml` + `; + static components = { Child }; + setup() { + useLogLifecycle(steps); + } + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe(""); + + parent.render(); // to block child render + await nextTick(); + + fixture.querySelector("button")!.click(); + + await nextTick(); + fixture.querySelector("button")!.click(); + + await nextTick(); + + expect(fixture.innerHTML).toBe(""); + + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe(""); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + "Parent:render", + "Child:willUpdateProps", + "inc:1", + "inc:1", + "Child:render", + "Parent:willPatch", + "Child:willPatch", + "Child:patched", + "Parent:patched", + ]); + Object.freeze(steps); + }); +}); diff --git a/tests/components/props.test.ts b/tests/components/props.test.ts new file mode 100644 index 00000000..2f83a07c --- /dev/null +++ b/tests/components/props.test.ts @@ -0,0 +1,105 @@ +import { Component, mount, useState } from "../../src"; +import { xml } from "../../src/tags"; +import { makeTestFixture, snapshotEverything } from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("basics", () => { + test("explicit object prop", async () => { + class Child extends Component { + static template = xml``; + state: any; + setup() { + this.state = useState({ someval: this.props.value }); + } + } + + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + state = useState({ val: 42 }); + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    42
    "); + }); + + test("accept ES6-like syntax for props (with getters)", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + get greetings() { + const name = "aaron"; + return `hello ${name}`; + } + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    hello aaron
    "); + }); + + test("t-set works ", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml` +
    + + +
    `; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    42
    "); + }); + + test("t-set with a body expression can be used as textual prop", async () => { + class Child extends Component { + static template = xml``; + } + class Parent extends Component { + static components = { Child }; + static template = xml` +
    + 42 + +
    `; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    42
    "); + }); + + test("t-set with a body expression can be passed in props, and then t-raw", async () => { + class Child extends Component { + static template = xml` + + + + `; + } + class Parent extends Component { + static components = { Child }; + static template = xml` +
    +

    43

    + +
    `; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    <p>43</p>

    43

    "); + }); +}); diff --git a/tests/components/refs.test.ts b/tests/components/refs.test.ts new file mode 100644 index 00000000..4693541d --- /dev/null +++ b/tests/components/refs.test.ts @@ -0,0 +1,47 @@ +import { Component, mount, useRef, useState } from "../../src/index"; +import { makeTestFixture, nextTick, snapshotEverything } from "../helpers"; +import { xml } from "../../src/index"; + +snapshotEverything(); +let fixture: HTMLElement; + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("refs", () => { + test("refs are properly bound in slots", async () => { + class Dialog extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml` +
    + + + + +
    + `; + static components = { Dialog }; + state = useState({ val: 0 }); + button = useRef("myButton"); + doSomething() { + this.state.val++; + } + } + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe( + '
    0
    ' + ); + + parent.button.el!.click(); + await nextTick(); + + expect(fixture.innerHTML).toBe( + '
    1
    ' + ); + }); +}); diff --git a/tests/component/slots.test.ts b/tests/components/slots.test.ts similarity index 62% rename from tests/component/slots.test.ts rename to tests/components/slots.test.ts index ca9f88e4..3048924c 100644 --- a/tests/component/slots.test.ts +++ b/tests/components/slots.test.ts @@ -1,75 +1,77 @@ -import { Component, Env, mount } from "../../src/component/component"; -import { QWeb } from "../../src/qweb/qweb"; -import { xml } from "../../src/tags"; -import { useState, useRef } from "../../src/hooks"; -import { makeTestFixture, makeTestEnv, nextTick } from "../helpers"; - -//------------------------------------------------------------------------------ -// Setup and helpers -//------------------------------------------------------------------------------ - -// We create before each test: -// - fixture: a div, appended to the DOM, intended to be the target of dom -// manipulations. Note that it is removed after each test. -// - env: a WEnv, necessary to create new components +import { App, Component, mount, onMounted, useState, xml } from "../../src/index"; +import { addTemplate, children, makeTestFixture, nextTick, snapshotEverything } from "../helpers"; +snapshotEverything(); let fixture: HTMLElement; -let env: Env; beforeEach(() => { fixture = makeTestFixture(); - env = makeTestEnv(); - Component.env = env; }); -afterEach(() => { - fixture.remove(); -}); - -function children(w: Component): Component[] { - const childrenMap = w.__owl__.children; - return Object.keys(childrenMap).map((id) => childrenMap[id]); -} - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("t-slot directive", () => { - test("can define and call slots", async () => { - env.qweb.addTemplates(` - -
    - - header - footer - -
    -
    -
    -
    -
    -
    - `); - class Dialog extends Component {} - class Parent extends Component { - static components = { Dialog }; +describe("slots", () => { + test("simple default slot", async () => { + class Child extends Component { + static template = xml``; } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe( - "
    header
    footer
    " - ); - expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot(); - expect(env.qweb.templates.Dialog.fn.toString()).toMatchSnapshot(); - expect(QWeb.slots["1_header"].toString()).toMatchSnapshot(); - expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); + class Parent extends Component { + static template = xml`some text`; + static components = { Child }; + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("some text"); }); - test("can define and call slots using old t-set keyword", async () => { - // NOTE: this test should be removed once we stop supporting the t-set directive - // for defining slot content. + test("simple default slot, variation", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml`some text`; + static components = { Child }; + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("some text"); + }); + + test("fun: two calls to the same slot", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml`some text`; + static components = { Child }; + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("some textsome text"); + }); + + test("slot content is bound to caller", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + state = useState({ value: 0 }); + inc() { + this.state.value++; + } + } + const parent = await mount(Parent, fixture); + + expect(parent.state.value).toBe(0); + fixture.querySelector("button")!.click(); + expect(parent.state.value).toBe(1); + }); + + test("can define and call slots", async () => { class Dialog extends Component { static template = xml`
    @@ -77,44 +79,51 @@ describe("t-slot directive", () => {
    `; } + class Parent extends Component { + static components = { Dialog }; static template = xml`
    - header - footer + header + footer
    `; - static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe( "
    header
    footer
    " ); - expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot(); - expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); - expect(QWeb.slots["1_header"].toString()).toMatchSnapshot(); - expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); + }); + + test("no named slot content => just no children", async () => { + class Dialog extends Component { + static template = xml``; + } + class Parent extends Component { + static template = xml``; + static components = { Dialog }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(""); }); test("named slots can define a default content", async () => { class Dialog extends Component { static template = xml` - - default content - `; + + default content + `; } class Parent extends Component { static template = xml`
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
    default content
    "); - expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); }); test("dafault slots can define a default content", async () => { @@ -128,26 +137,23 @@ describe("t-slot directive", () => { static template = xml`
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
    default content
    "); - expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); }); test("default content is not rendered if slot is provided", async () => { class Dialog extends Component { static template = xml` - - default content - `; + + default content + `; } class Parent extends Component { static template = xml`
    hey
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
    hey
    "); }); @@ -155,42 +161,42 @@ describe("t-slot directive", () => { test("default content is not rendered if named slot is provided", async () => { class Dialog extends Component { static template = xml` - - default content - `; + + default content + `; } class Parent extends Component { static template = xml`
    hey
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
    hey
    "); }); test("slots are rendered with proper context", async () => { - env.qweb.addTemplates(` - -
    - - - - -
    - -
    - `); - class Dialog extends Component {} + class Dialog extends Component { + static template = xml``; + } + class Parent extends Component { + static template = xml` +
    + + + + + + +
    `; static components = { Dialog }; state = useState({ val: 0 }); doSomething() { this.state.val++; } } - const parent = new Parent(); - await parent.mount(fixture); + + await mount(Parent, fixture); expect(fixture.innerHTML).toBe( '
    0
    ' @@ -202,25 +208,24 @@ describe("t-slot directive", () => { expect(fixture.innerHTML).toBe( '
    1
    ' ); - expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); }); test("slots are rendered with proper context, part 2", async () => { - env.qweb.addTemplates(` - - - - -
    -
  • - User -
  • -
    -
    - `); - class Link extends Component {} + class Link extends Component { + static template = xml` + + + `; + } class App extends Component { + static template = xml` +
    +
  • + User +
  • +
    `; + state = useState({ users: [ { id: 1, name: "Aaron" }, @@ -230,14 +235,11 @@ describe("t-slot directive", () => { static components = { Link }; } - const app = new App(); - await app.mount(fixture); + const app = await mount(App, fixture); expect(fixture.innerHTML).toBe( '' ); - expect(env.qweb.templates.Link.fn.toString()).toMatchSnapshot(); - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); // test updateprops here app.state.users[1].name = "Mathieu"; @@ -245,26 +247,24 @@ describe("t-slot directive", () => { expect(fixture.innerHTML).toBe( '' ); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); }); test("slots are rendered with proper context, part 3", async () => { - env.qweb.addTemplates(` - - - - -
    -
  • - - -
  • -
    -
    - `); - class Link extends Component {} + class Link extends Component { + static template = xml` + + + `; + } class App extends Component { + static template = xml` +
    +
  • + + +
  • +
    `; state = useState({ users: [ { id: 1, name: "Aaron" }, @@ -274,14 +274,11 @@ describe("t-slot directive", () => { static components = { Link }; } - const app = new App(); - await app.mount(fixture); + const app = await mount(App, fixture); expect(fixture.innerHTML).toBe( '' ); - expect(env.qweb.templates.Link.fn.toString()).toMatchSnapshot(); - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); // test updateprops here app.state.users[1].name = "Mathieu"; @@ -289,40 +286,386 @@ describe("t-slot directive", () => { expect(fixture.innerHTML).toBe( '' ); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); }); test("slots are rendered with proper context, part 4", async () => { - env.qweb.addTemplates(` - - - - -
    - - -
    -
    - `); - class Link extends Component {} - - class App extends Component { - state = useState({ user: { id: 1, name: "Aaron" } }); - static components = { Link }; + class Link extends Component { + static template = xml` + + + `; } - const app = new App(); - await app.mount(fixture); + class App extends Component { + static template = xml` +
    + + +
    `; + static components = { Link }; + state = useState({ user: { id: 1, name: "Aaron" } }); + } + + const app = await mount(App, fixture); expect(fixture.innerHTML).toBe(''); - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); - // test updateprops here app.state.user.name = "David"; await nextTick(); expect(fixture.innerHTML).toBe(''); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); + }); + + test("content is the default slot", async () => { + class Dialog extends Component { + static template = xml`
    `; + } + class Parent extends Component { + static template = xml` +
    + + sts rocks + +
    `; + static components = { Dialog }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    sts rocks
    "); + }); + + test("content is the default slot (variation)", async () => { + class Dialog extends Component { + static template = xml``; + } + class Parent extends Component { + static template = xml` + + sts rocks + `; + static components = { Dialog }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("sts rocks"); + }); + + test("default slot work with text nodes", async () => { + class Dialog extends Component { + static template = xml`
    `; + } + class Parent extends Component { + static template = xml` +
    + sts rocks +
    `; + static components = { Dialog }; + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    sts rocks
    "); + }); + + test("default slot work with text nodes (variation)", async () => { + class Dialog extends Component { + static template = xml``; + } + class Parent extends Component { + static template = xml`sts rocks`; + static components = { Dialog }; + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("sts rocks"); + }); + + test("multiple roots are allowed in a named slot", async () => { + class Dialog extends Component { + static template = xml`
    `; + } + class Parent extends Component { + static template = xml` +
    + + + sts + rocks + + +
    `; + static components = { Dialog }; + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    stsrocks
    "); + }); + + test("multiple roots are allowed in a default slot", async () => { + class Dialog extends Component { + static template = xml`
    `; + } + class Parent extends Component { + static template = xml` +
    + + sts + rocks + +
    `; + static components = { Dialog }; + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    stsrocks
    "); + }); + + test("missing slots are ignored", async () => { + class Dialog extends Component { + static template = xml` + + + some content + + `; + } + class Parent extends Component { + static template = xml`
    `; + static components = { Dialog }; + } + + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    some content
    "); + }); + + test("t-debug on a t-set-slot (defining a slot)", async () => { + const consoleLog = console.log; + console.log = jest.fn(); + + class Dialog extends Component { + static template = xml``; + } + class Parent extends Component { + static template = xml` +
    + abc +
    `; + static components = { Dialog }; + } + + await mount(Parent, fixture); + expect(console.log).toHaveBeenCalledTimes(0); + console.log = consoleLog; + }); + + test("slot preserves properly parented relationship", async () => { + class Child extends Component { + static template = xml``; + } + class GrandChild extends Component { + static template = xml`Grand Child`; + } + class Parent extends Component { + static template = xml` +
    + + + +
    `; + static components = { Child, GrandChild }; + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    Grand Child
    "); + + const parentChildren = children(parent); + expect(parentChildren.length).toBe(1); + expect(parentChildren[0]).toBeInstanceOf(Child); + + const childrenChildren = children(parentChildren[0]); + expect(childrenChildren.length).toBe(1); + expect(childrenChildren[0]).toBeInstanceOf(GrandChild); + }); + + test("slot preserves properly parented relationship, even through t-call", async () => { + class Child extends Component { + static template = xml``; + } + + class GrandChild extends Component { + static template = xml`Grand Child`; + } + + addTemplate("sub", ``); + + class Parent extends Component { + static template = xml` +
    + + + +
    `; + static components = { Child, GrandChild }; + } + + // throw new Error("boom") + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    Grand Child
    "); + + const parentChildren = children(parent); + expect(parentChildren.length).toBe(1); + expect(parentChildren[0]).toBeInstanceOf(Child); + + const childrenChildren = children(parentChildren[0]); + expect(childrenChildren.length).toBe(1); + expect(childrenChildren[0]).toBeInstanceOf(GrandChild); + }); + + test("t-slot scope context", async () => { + expect.assertions(4); + + class Wrapper extends Component { + static template = xml``; + } + + let dialog: any; + + class Dialog extends Component { + static template = xml` + +
    + +
    +
    + `; + + static components = { Wrapper }; + + setup() { + dialog = this; + } + + onClick() { + // we do not use expect(this).toBe(dialog) here because if it fails, it + // may blow up jest because it then tries to compute a diff, which is + // infinite if there is a cycle + expect(this === dialog).toBe(true); + } + } + + class Parent extends Component { + static template = xml` + + + `; + static components = { Dialog }; + } + + await mount(Parent, fixture); + + fixture.querySelector("button")!.click(); + await nextTick(); + }); + + test("t-slot in recursive templates", async () => { + class Wrapper extends Component { + static template = xml` + + + `; + } + + class Parent extends Component { + static template = "_test_recursive_template"; + static components = { Wrapper }; + name = "foo"; + items = [ + { + name: "foo-0", + children: [ + { name: "foo-00", children: [] }, + { + name: "foo-01", + children: [ + { name: "foo-010", children: [] }, + { name: "foo-011", children: [] }, + { + name: "foo-012", + children: [ + { name: "foo-0120", children: [] }, + { name: "foo-0121", children: [] }, + { name: "foo-0122", children: [] }, + ], + }, + ], + }, + { name: "foo-02", children: [] }, + ], + }, + { name: "foo-1", children: [] }, + { name: "foo-2", children: [] }, + ]; + } + + const recursiveTemplate = ` + + + + + + + + + + + + `; + const app = new App(Parent); + app.addTemplate("_test_recursive_template", recursiveTemplate); + + await app.mount(fixture); + + expect(fixture.innerHTML).toBe( + "foofoo-0foo-00foo-01foo-010foo-011foo-012foo-0120foo-0121foo-0122foo-02foo-1foo-2" + ); + }); + + test("t-slot within dynamic t-call", async () => { + class Child extends Component { + static template = xml`
    `; + } + + class Slotted extends Component { + static template = xml`
    `; + } + + addTemplate("sometemplate", `
    `); + class UsingTcallInSlotted extends Component { + tcallTemplate = "sometemplate"; + static template = xml` +
    + + + +
    `; + static components = { Slotted, Child }; + } + + const parent = await mount(UsingTcallInSlotted, fixture); + + expect(parent).toBeInstanceOf(UsingTcallInSlotted); + expect(children(parent).length).toBe(1); + const slotted = children(parent)[0]; + expect(slotted).toBeInstanceOf(Slotted); + expect(children(slotted).length).toBe(1); + const child = children(slotted)[0]; + expect(child).toBeInstanceOf(Child); + + expect(fixture.innerHTML).toBe( + `
    ` + ); }); test("slots in t-foreach in t-foreach", async () => { @@ -381,90 +724,11 @@ describe("t-slot directive", () => { ]; } - await mount(App, { target: fixture }); + await mount(App, fixture); expect(fixture.innerHTML).toBe( "
    A
    • A
    • A
    B
    • B
    • B
    " ); - expect(env.qweb.templates[App.template].fn.toString()).toMatchSnapshot(); - }); - - test("refs are properly bound in slots", async () => { - class Dialog extends Component { - static template = xml``; - } - class Parent extends Component { - static template = xml` -
    - - - - -
    - `; - static components = { Dialog }; - state = useState({ val: 0 }); - button = useRef("myButton"); - doSomething() { - this.state.val++; - } - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe( - '
    0
    ' - ); - - parent.button.el!.click(); - await nextTick(); - - expect(fixture.innerHTML).toBe( - '
    1
    ' - ); - expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); - }); - - test("content is the default slot", async () => { - env.qweb.addTemplates(` - -
    - - sts rocks - -
    -
    -
    - `); - class Dialog extends Component {} - class Parent extends Component { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    sts rocks
    "); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); - }); - - test("default slot work with text nodes", async () => { - env.qweb.addTemplates(` - -
    - sts rocks -
    -
    -
    - `); - class Dialog extends Component {} - class Parent extends Component { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    sts rocks
    "); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); }); test("default slot next to named slot, with default content", async () => { @@ -501,194 +765,95 @@ describe("t-slot directive", () => { `; } - await mount(App, { target: fixture }); + await mount(App, fixture); expect(fixture.innerHTML).toBe( '
    Default content
    ' ); - expect(QWeb.TEMPLATES[App.template].fn.toString()).toMatchSnapshot(); }); - test("multiple roots are allowed in a named slot", async () => { - env.qweb.addTemplates(` - -
    - - - sts - rocks - - -
    -
    -
    - `); - class Dialog extends Component {} - class Parent extends Component { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    stsrocks
    "); - expect(QWeb.slots["1_content"].toString()).toMatchSnapshot(); - }); - - test("multiple roots are allowed in a default slot", async () => { - env.qweb.addTemplates(` - -
    - - sts - rocks - -
    -
    -
    - `); - class Dialog extends Component {} - class Parent extends Component { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    stsrocks
    "); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); - }); - - test("missing slots are ignored", async () => { - env.qweb.addTemplates(` - -
    - -
    - - - some content - - -
    - `); - class Dialog extends Component {} - class Parent extends Component { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    some content
    "); - }); - - test("t-debug on a t-set-slot (defining a slot)", async () => { - const consoleLog = console.log; - console.log = jest.fn(); - - env.qweb.addTemplates(` - -
    - abc -
    - - - -
    - `); - class Dialog extends Component {} - class Parent extends Component { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - expect(console.log).toHaveBeenCalledTimes(0); - console.log = consoleLog; - }); - - test("slot preserves properly parented relationship", async () => { - env.qweb.addTemplates(` - -
    - - - -
    -
    -
    Grand Child
    -
    - `); - class Child extends Component {} - class GrandChild extends Component {} - class Parent extends Component { - static components = { Child, GrandChild }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    Grand Child
    "); - - const parentChildren = children(parent); - expect(parentChildren.length).toBe(1); - expect(parentChildren[0]).toBeInstanceOf(Child); - - const childrenChildren = children(parentChildren[0]); - expect(childrenChildren.length).toBe(1); - expect(childrenChildren[0]).toBeInstanceOf(GrandChild); - }); - - test("nested slots: evaluation context and parented relationship", async () => { - let slot; - class Slot extends Component { - static template = xml``; - constructor(parent, props) { - super(parent, props); - slot = this; + test("dynamic t-slot call", async () => { + class Toggler extends Component { + static template = xml``; + current = useState({ slot: "slot1" }); + toggle() { + this.current.slot = this.current.slot === "slot1" ? "slot2" : "slot1"; } } - class GrandChild extends Component { - static template = xml`
    `; - } - class Child extends Component { - static components = { GrandChild }; - static template = xml` - - - `; - } - class Parent extends Component { - static components = { Child, Slot }; - static template = xml``; - state = useState({ val: 3 }); - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    3
    "); - expect(slot.__owl__.parent).toBeInstanceOf(GrandChild); + class Parent extends Component { + static template = xml` +
    + +

    slot1

    content
    +

    slot2

    +
    +
    `; + static components = { Toggler }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    "); + + fixture.querySelector("button")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    "); + + fixture.querySelector("button")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    "); + }); + + test("dynamic t-slot call with default", async () => { + class Toggler extends Component { + static template = xml` + `; + current = useState({ slot: "slot1" }); + toggle() { + this.current.slot = this.current.slot === "slot1" ? "slot2" : "slot1"; + } + } + + class Parent extends Component { + static template = xml` +
    + +

    slot1

    content
    +

    slot2

    +
    +
    `; + static components = { Toggler }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    "); + + fixture.querySelector("button")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    "); }); test("slot are properly rendered if inner props are changed", async () => { - env.qweb.addTemplates(` - -
    - SC: -
    - - -
    - -
    - -
    - - - - -
    -
    - `); - class SomeComponent extends Component {} - class GenericComponent extends Component {} + class SomeComponent extends Component { + static template = xml` +
    + SC: +
    `; + } + class GenericComponent extends Component { + static template = xml`
    `; + } + class App extends Component { + static template = xml` +
    + + + + +
    `; static components = { GenericComponent, SomeComponent }; state = useState({ val: 4 }); @@ -696,8 +861,7 @@ describe("t-slot directive", () => { this.state.val++; } } - const app = new App(); - await app.mount(fixture); + await mount(App, fixture); expect(fixture.innerHTML).toBe("
    SC:4
    "); (fixture.querySelector("button")).click(); @@ -718,8 +882,7 @@ describe("t-slot directive", () => { static components = { Link: Link }; } - const a = new A(); - await a.mount(fixture); + await mount(A, fixture); expect(fixture.innerHTML).toBe(`hey`); }); @@ -740,11 +903,9 @@ describe("t-slot directive", () => { static components = { SlotComponent, Child }; state = useState({ value: 3 }); } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    3
    "); - expect(QWeb.TEMPLATES[SlotComponent.template].fn.toString()).toMatchSnapshot(); + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    3
    "); parent.state.value = 5; await nextTick(); @@ -767,8 +928,7 @@ describe("t-slot directive", () => { static components = { B, C }; } - const a = new A(); - await a.mount(fixture); + await mount(A, fixture); expect(fixture.innerHTML).toBe(`
    12
    `); }); @@ -777,8 +937,10 @@ describe("t-slot directive", () => { class Child extends Component { static template = xml``; state = useState({ val: "A" }); - mounted() { - this.state.val = "B"; + setup() { + onMounted(() => { + this.state.val = "B"; + }); } } class Parent extends Component { @@ -790,8 +952,7 @@ describe("t-slot directive", () => {
    `; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
    A0A1
    "); await nextTick(); // wait for the changes triggered in mounted to be applied @@ -806,8 +967,10 @@ describe("t-slot directive", () => {
    `; state = useState({ val: "A" }); - mounted() { - this.state.val = "B"; + setup() { + onMounted(() => { + this.state.val = "B"; + }); } } class ParentWidget extends Component { @@ -821,8 +984,7 @@ describe("t-slot directive", () => {
    `; } - const widget = new ParentWidget(); - await widget.mount(fixture); + await mount(ParentWidget, fixture); expect(fixture.innerHTML).toBe("
    A0A1
    "); await nextTick(); // wait for changes triggered in mounted to be applied @@ -830,7 +992,10 @@ describe("t-slot directive", () => { }); test("nested slots in same template", async () => { - let child, child2, child3; + let child: any = null; + let child2: any = null; + let child3: any = null; + class Child extends Component { static template = xml` @@ -838,8 +1003,7 @@ describe("t-slot directive", () => {
    `; - constructor(parent, props) { - super(parent, props); + setup() { child = this; } } @@ -848,16 +1012,14 @@ describe("t-slot directive", () => { `; - constructor(parent, props) { - super(parent, props); + setup() { child2 = this; } } class Child3 extends Component { static template = xml` Child 3`; - constructor(parent, props) { - super(parent, props); + setup() { child3 = this; } } @@ -873,44 +1035,36 @@ describe("t-slot directive", () => { `; } - const widget = new Parent(); - await widget.mount(fixture); + const parent = await mount(Parent, fixture); expect(fixture.innerHTML).toBe( '
    Child 3
    ' ); - expect(child3.__owl__.parent).toStrictEqual(child2); - expect(child2.__owl__.parent).toStrictEqual(child); - expect(child.__owl__.parent).toStrictEqual(widget); + expect(children(child2)).toEqual([child3]); + expect(children(child)).toEqual([child2]); + expect(children(parent)).toEqual([child]); }); test("t-slot nested within another slot", async () => { - let portal, modal, child3; + let portal: any = null; + let modal: any = null; + let child3: any = null; + class Child3 extends Component { - static template = xml` - Child 3`; - constructor(parent, props) { - super(parent, props); + static template = xml`Child 3`; + setup() { child3 = this; } } class Modal extends Component { - static template = xml` - - - `; - constructor(parent, props) { - super(parent, props); + static template = xml``; + setup() { modal = this; } } class Portal extends Component { - static template = xml` - - - `; - constructor(parent, props) { - super(parent, props); + static template = xml``; + setup() { portal = this; } } @@ -935,51 +1089,13 @@ describe("t-slot directive", () => { `; } - const widget = new Parent(); - await widget.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe( 'Child 3' ); - expect(child3.__owl__.parent).toStrictEqual(portal); - expect(portal.__owl__.parent).toStrictEqual(modal); - }); - - test("t-slot supports many instances", async () => { - let child3; - class Child3 extends Component { - static template = xml` - Child 3`; - constructor(parent, props) { - super(parent, props); - child3 = this; - } - } - class Dialog extends Component { - static template = xml` - - - `; - } - class Parent extends Component { - static components = { Child3, Dialog }; - static template = xml` - - - - - `; - state = { lol: "k" }; - } - - const widget = new Parent(); - await widget.mount(fixture); - expect(child3.props.val).toBe("k"); - - const widget_1 = new Parent(); - widget_1.state.lol = "m"; - await widget_1.mount(fixture); - expect(child3.props.val).toBe("m"); + expect(children(portal)[0]).toBe(child3); + expect(children(modal)[0]).toBe(portal); }); test("slots in slots, with vars", async () => { @@ -1007,8 +1123,7 @@ describe("t-slot directive", () => { state = { name: "aaron" }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("

    heyaaron

    "); }); @@ -1029,11 +1144,9 @@ describe("t-slot directive", () => {
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
    dash
    "); - expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); }); test("slot and t-esc", async () => { @@ -1044,8 +1157,7 @@ describe("t-slot directive", () => { static template = xml`
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
    toph
    "); }); @@ -1058,42 +1170,74 @@ describe("t-slot directive", () => { static template = xml`
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
    toph
    "); }); test("slot and t-call", async () => { - env.qweb.addTemplate("sokka", "

    sokka

    "); + let sokka = xml`

    sokka

    `; class Dialog extends Component { static template = xml``; } class Parent extends Component { - static template = xml`
    `; + static template = xml`
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("

    sokka

    "); }); test("slot and (inline) t-call", async () => { - env.qweb.addTemplate("sokka", "

    sokka

    "); + let sokka = xml`

    sokka

    `; class Dialog extends Component { static template = xml``; } class Parent extends Component { - static template = xml`
    `; + static template = xml`
    `; static components = { Dialog }; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("

    sokka

    "); }); + test("nested slots: evaluation context and parented relationship", async () => { + let slot: any = null; + let grandChild: any = null; + + class Slot extends Component { + static template = xml``; + setup() { + slot = this; + } + } + class GrandChild extends Component { + static template = xml`
    `; + setup() { + grandChild = this; + } + } + class Child extends Component { + static components = { GrandChild }; + static template = xml` + + + `; + } + class Parent extends Component { + static components = { Child, Slot }; + static template = xml``; + state = useState({ val: 3 }); + } + + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    3
    "); + expect(children(grandChild)).toEqual([slot]); + }); + test("named slot inside slot", async () => { class Child extends Component { static template = xml` @@ -1119,8 +1263,7 @@ describe("t-slot directive", () => { static components = { Child }; value = "blip"; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("

    Ablip

    Bblip

    "); }); @@ -1151,8 +1294,7 @@ describe("t-slot directive", () => { static components = { Child }; value = "blip"; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe( "

    Ablip

    default2default1

    Bblip

    " @@ -1186,188 +1328,8 @@ describe("t-slot directive", () => { static components = { Child }; value = "blip"; } - const parent = new Parent(); - await parent.mount(fixture); + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("

    Ablip

    Bblip

    "); }); - - test("dynamic t-slot call", async () => { - class Toggler extends Component { - static template = xml``; - current = useState({ slot: "slot1" }); - toggle() { - this.current.slot = this.current.slot === "slot1" ? "slot2" : "slot1"; - } - } - - class Parent extends Component { - static template = xml` -
    - -

    slot1

    content
    -

    slot2

    -
    -
    `; - static components = { Toggler }; - } - await mount(Parent, { target: fixture }); - expect(fixture.innerHTML).toBe("
    "); - - fixture.querySelector("button")!.click(); - await nextTick(); - expect(fixture.innerHTML).toBe("
    "); - - fixture.querySelector("button")!.click(); - await nextTick(); - expect(fixture.innerHTML).toBe("
    "); - - expect(env.qweb.templates[Toggler.template].fn.toString()).toMatchSnapshot(); - }); - - test("t-slot within dynamic t-call", async () => { - let child; - class Child extends Component { - static template = xml`
    `; - constructor(...args) { - super(...args); - child = this; - } - } - - class Slotted extends Component { - static template = xml`
    `; - } - - class UsingTcallInSlotted extends Component { - tcallTemplate = xml`
    `; - static template = xml` -
    - - - -
    `; - static components = { Slotted, Child }; - } - - await mount(UsingTcallInSlotted, { target: fixture }); - - expect(child.__owl__.parent).toBeInstanceOf(Slotted); - expect(fixture.innerHTML).toBe( - `
    ` - ); - }); - - test("t-slot scope context", async () => { - expect.assertions(1); - - class Wrapper extends Component { - static template = xml``; - } - - let dialog; - - class Dialog extends Component { - static template = xml` - -
    - -
    -
    - `; - - static components = { Wrapper }; - - setup() { - dialog = this; - } - - onClick(ev) { - // we do not use expect(this).toBe(dialog) here because if it fails, it - // may blow up jest because it then tries to compute a diff, which is - // infinite if there is a cycle - expect(this === dialog).toBe(true); - } - } - - class Parent extends Component { - static template = xml` - - - `; - static components = { Dialog }; - } - - await mount(Parent, { target: fixture }); - - document.querySelector("button").click(); - await nextTick(); - }); - - test("t-slot in recursive templates", async () => { - QWeb.registerTemplate( - "_test_recursive_template", - ` - - - - - - - - - - - - - - - ` - ); - - class Wrapper extends Component { - static template = xml` - - - `; - } - - class Parent extends Component { - static template = "_test_recursive_template"; - static components = { Wrapper }; - name = "foo"; - items = [ - { - name: "foo-0", - children: [ - { name: "foo-00", children: [] }, - { - name: "foo-01", - children: [ - { name: "foo-010", children: [] }, - { name: "foo-011", children: [] }, - { - name: "foo-012", - children: [ - { name: "foo-0120", children: [] }, - { name: "foo-0121", children: [] }, - { name: "foo-0122", children: [] }, - ], - }, - ], - }, - { name: "foo-02", children: [] }, - ], - }, - { name: "foo-1", children: [] }, - { name: "foo-2", children: [] }, - ]; - } - - await mount(Parent, { target: fixture }); - - expect(fixture.innerHTML).toBe( - "foofoo-0foo-00foo-01foo-010foo-011foo-012foo-0120foo-0121foo-0122foo-02foo-1foo-2" - ); - }); }); diff --git a/tests/components/style_class.test.ts b/tests/components/style_class.test.ts new file mode 100644 index 00000000..9a89fe91 --- /dev/null +++ b/tests/components/style_class.test.ts @@ -0,0 +1,237 @@ +import { Component, mount, useState, xml } from "../../src"; +import { makeTestFixture, nextTick, snapshotEverything } from "../helpers"; + +snapshotEverything(); +let fixture: HTMLElement; + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("style and class handling", () => { + test("can set style and class inside component", async () => { + class Test extends Component { + static template = xml` +
    world
    + `; + } + await mount(Test, fixture); + expect(fixture.innerHTML).toBe(`
    world
    `); + }); + + test("can set class on sub component, as prop", async () => { + class Child extends Component { + static template = xml`
    child
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    child
    `); + }); + + test("no class is set is parent does not give it as prop", async () => { + class Child extends Component { + static template = xml`
    child
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    child
    `); + }); + + test("no class is set is child ignores it", async () => { + class Child extends Component { + static template = xml`
    child
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    child
    `); + }); + + test("empty class attribute is not added on widget root el", async () => { + class Child extends Component { + static template = xml``; + } + class Parent extends Component { + static template = xml`
    `; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    `); + }); + + test("can set more than one class on sub component", async () => { + class Child extends Component { + static template = xml`
    child
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    child
    `); + }); + + test("component class and parent class combine together", async () => { + class Child extends Component { + static template = xml`
    child
    `; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    child
    `); + }); + + test("can set class on sub sub component", async () => { + class ChildChild extends Component { + static template = xml`
    childchild
    `; + } + + class Child extends Component { + static template = xml``; + static components = { ChildChild }; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    childchild
    `); + }); + + test("can set class on multi root component", async () => { + class Child extends Component { + static template = xml`
    a
    b`; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    a
    b`); + }); + + test("class on sub component, which is switched to another", async () => { + class ChildA extends Component { + static template = xml`
    a
    `; + } + class ChildB extends Component { + static template = xml`b`; + } + class Child extends Component { + static template = xml``; + static components = { ChildA, ChildB }; + } + + class Parent extends Component { + static template = xml``; + static components = { Child }; + + state = useState({ child: "a" }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    a
    `); + parent.state.child = "b"; + await nextTick(); + expect(fixture.innerHTML).toBe(`b`); + }); + + // test("t-att-class is properly added/removed on widget root el", async () => { + // class Child extends Component { + // static template = xml`
    `; + // } + // class Parent extends Component { + // static template = xml`
    `; + // static components = { Child }; + // state = useState({ a: true, b: false }); + // } + // const widget = await mount(Parent, fixture); + // expect(fixture.innerHTML).toBe(`
    `); + + // widget.state.a = false; + // widget.state.b = true; + // await nextTick(); + // expect(fixture.innerHTML).toBe(`
    `); + // }); + + test("class with extra whitespaces", async () => { + class Child extends Component { + static template = xml`
    `; + } + class Parent extends Component { + static template = xml``; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    `); + }); + + test("class with extra whitespaces (variation)", async () => { + class Child extends Component { + static template = xml`
    `; + } + class Parent extends Component { + static template = xml`

    `; + static components = { Child }; + } + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`

    `); + }); + + test("t-att-class is properly added/removed on widget root el (v2)", async () => { + let child: any = null; + class Child extends Component { + static template = xml``; + state = useState({ d: true }); + setup() { + child = this; + } + } + + class Parent extends Component { + static template = xml` +
    + +
    `; + static components = { Child }; + state = useState({ b: true }); + } + const widget = await mount(Parent, fixture); + + const span = fixture.querySelector("span")!; + expect(span.className).toBe("c d b"); + + widget.state.b = false; + await nextTick(); + expect(span.className).toBe("c d"); + + child.state.d = false; + await nextTick(); + expect(span.className).toBe("c"); + + widget.state.b = true; + await nextTick(); + expect(span.className).toBe("c b"); + + child.state.d = true; + await nextTick(); + expect(span.className).toBe("c b d"); + }); +}); diff --git a/tests/components/t_call.test.ts b/tests/components/t_call.test.ts new file mode 100644 index 00000000..1e24ef3a --- /dev/null +++ b/tests/components/t_call.test.ts @@ -0,0 +1,165 @@ +import { App, Component, mount, useState, xml } from "../../src/index"; +import { + addTemplate, + isDirectChildOf, + makeTestFixture, + nextTick, + snapshotEverything, +} from "../helpers"; + +snapshotEverything(); + +let fixture: HTMLElement; + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("t-call", () => { + test("dynamic t-call", async () => { + class App extends Component { + static template = xml` + + owl + `; + current = useState({ template: "foo" }); + } + addTemplate("foo", "
    foo
    "); + addTemplate("bar", "bar"); + + const app = await mount(App, fixture); + expect(fixture.innerHTML).toBe("
    foo
    "); + + app.current.template = "bar"; + await nextTick(); + expect(fixture.innerHTML).toBe("bar"); + }); + + test("sub components in two t-calls", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml` + + + +
    `; + static components = { Child }; + state = useState({ val: 1 }); + } + const app = new App(Parent); + app.addTemplate("sub", ``); + + const parent = await app.mount(fixture); + expect(fixture.innerHTML).toBe("1"); + parent.state.val = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("
    2
    "); + }); + + test("handlers are properly bound through a t-call", async () => { + let parent: any; + + const subTemplate = xml`

    lucas

    `; + class Parent extends Component { + static template = xml` +
    `; + counter = 0; + + update() { + expect(this).toBe(parent); + this.counter++; + this.render(); + } + } + parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("

    lucas

    0
    "); + fixture.querySelector("p")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("

    lucas

    1
    "); + }); + + test("parent is set within t-call", async () => { + const sub = xml``; + let child: any = null; + class Child extends Component { + static template = xml`lucas`; + setup() { + child = this; + } + } + class Parent extends Component { + static components = { Child }; + static template = xml`
    `; + } + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    lucas
    "); + expect(isDirectChildOf(child, parent)).toBeTruthy(); + }); + + test("t-call in t-foreach and children component", async () => { + const sub = xml``; + + class Child extends Component { + static template = xml``; + } + class Parent extends Component { + static components = { Child }; + static template = xml` +
    + + + +
    `; + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    abc
    "); + }); + + test("parent is set within t-call with no parentNode", async () => { + const sub = xml``; + + let child: any = null; + + class Child extends Component { + setup() { + child = this; + } + static template = xml`lucas`; + } + + class Parent extends Component { + static components = { Child }; + static template = xml``; + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("lucas"); + expect(isDirectChildOf(child, parent)).toBe(true); + }); + + test("handlers with arguments are properly bound through a t-call", async () => { + const sub = xml`

    lucas

    `; + + let value: any = null; + + class Parent extends Component { + static template = xml`
    `; + update(a: any) { + expect(this).toBe(parent); + value = a; + } + a = 3; + } + const parent = await mount(Parent, fixture); + + fixture.querySelector("p")!.click(); + expect(value).toBe(3); + }); +}); diff --git a/tests/components/t_call_block.test.ts b/tests/components/t_call_block.test.ts new file mode 100644 index 00000000..11e355ba --- /dev/null +++ b/tests/components/t_call_block.test.ts @@ -0,0 +1,25 @@ +import { text } from "../../src/blockdom"; +import { Component, mount, xml } from "../../src/index"; +import { makeTestFixture, snapshotEverything } from "../helpers"; + +snapshotEverything(); + +let fixture: HTMLElement; + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("t-call-block", () => { + test("simple t-call-block with static text", async () => { + class Test extends Component { + static template = xml`
    `; + myBlock() { + return text("hello"); + } + } + + await mount(Test, fixture); + expect(fixture.innerHTML).toBe("
    hello
    "); + }); +}); diff --git a/tests/components/t_component.test.ts b/tests/components/t_component.test.ts new file mode 100644 index 00000000..b959a855 --- /dev/null +++ b/tests/components/t_component.test.ts @@ -0,0 +1,193 @@ +import { Component, mount, useState } from "../../src"; +import { xml } from "../../src/tags"; +import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("t-component", () => { + test("t-component works in simple case", async () => { + let steps: string[] = []; + + class Child extends Component { + static template = xml`
    child
    `; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml``; + Child = Child; + setup() { + useLogLifecycle(steps); + } + } + + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    child
    "); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:mounted", + "Parent:mounted", + ]); + }); + + test("switching dynamic component", async () => { + let steps: string[] = []; + + class ChildA extends Component { + static template = xml`
    child a
    `; + setup() { + useLogLifecycle(steps); + } + } + + class ChildB extends Component { + static template = xml`child b`; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml``; + Child = ChildA; + setup() { + useLogLifecycle(steps); + } + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("
    child a
    "); + + parent.Child = ChildB; + parent.render(); + await nextTick(); + expect(fixture.innerHTML).toBe("child b"); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "ChildA:setup", + "ChildA:willStart", + "ChildA:render", + "ChildA:mounted", + "Parent:mounted", + "Parent:render", + "ChildB:setup", + "ChildB:willStart", + "ChildB:render", + "Parent:willPatch", + "ChildA:willUnmount", + "ChildA:destroyed", + "ChildB:mounted", + "Parent:patched", + ]); + }); + + test("can switch between dynamic components without the need for a t-key", async () => { + class A extends Component { + static template = xml`child a`; + } + class B extends Component { + static template = xml`child b`; + } + class App extends Component { + static template = xml` +
    + +
    `; + static components = { A, B }; + + state = useState({ child: "A" }); + } + const app = await mount(App, fixture); + expect(fixture.innerHTML).toBe("
    child a
    "); + app.state.child = "B"; + await nextTick(); + expect(fixture.innerHTML).toBe("
    child b
    "); + }); + + test("can use dynamic components (the class) if given", async () => { + class A extends Component { + static template = xml`child a`; + } + class B extends Component { + static template = xml`child b`; + } + class Parent extends Component { + static template = xml``; + state = useState({ + child: "a", + }); + get myComponent() { + return this.state.child === "a" ? A : B; + } + } + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("child a"); + parent.state.child = "b"; + await nextTick(); + expect(fixture.innerHTML).toBe("child b"); + }); + + test("can use dynamic components (the class) if given (with different root tagname)", async () => { + class A extends Component { + static template = xml`child a`; + } + class B extends Component { + static template = xml`
    child b
    `; + } + class Parent extends Component { + static template = xml``; + state = useState({ + child: "a", + }); + get myComponent() { + return this.state.child === "a" ? A : B; + } + } + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("child a"); + parent.state.child = "b"; + await nextTick(); + expect(fixture.innerHTML).toBe("
    child b
    "); + }); + + test("modifying a sub widget", async () => { + class Counter extends Component { + static template = xml` +
    `; + state = useState({ + counter: 0, + }); + } + + class ParentWidget extends Component { + static template = xml`
    `; + + get Counter() { + return Counter; + } + } + await mount(ParentWidget, fixture); + expect(fixture.innerHTML).toBe("
    0
    "); + const button = fixture.getElementsByTagName("button")[0]; + await button.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    1
    "); + }); +}); diff --git a/tests/components/t_foreach.test.ts b/tests/components/t_foreach.test.ts new file mode 100644 index 00000000..7445168b --- /dev/null +++ b/tests/components/t_foreach.test.ts @@ -0,0 +1,200 @@ +import { Component, mount, useState, xml } from "../../src/index"; +import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers"; + +snapshotEverything(); + +let fixture: HTMLElement; + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("list of components", () => { + test("simple list", async () => { + class Child extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml` + + + `; + static components = { Child }; + + state = useState({ + elems: [ + { id: 1, value: "a" }, + { id: 2, value: "b" }, + ], + }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("ab"); + parent.state.elems.push({ id: 4, value: "d" }); + await nextTick(); + expect(fixture.innerHTML).toBe("abd"); + + parent.state.elems.pop(); + + await nextTick(); + expect(fixture.innerHTML).toBe("ab"); + }); + + test("components in a node in a t-foreach ", async () => { + const steps: string[] = []; + + class Child extends Component { + static template = xml`
    `; + setup() { + useLogLifecycle(steps); + } + } + + class Parent extends Component { + static template = xml` +
    +
      + +
    • + +
    • +
      +
    +
    `; + static components = { Child }; + + setup() { + useLogLifecycle(steps); + } + + get items() { + return [1, 2]; + } + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe( + "
    • 1
    • 2
    " + ); + expect(steps).toEqual([ + "Parent:setup", + "Parent:willStart", + "Parent:render", + "Child:setup", + "Child:willStart", + "Child:setup", + "Child:willStart", + "Child:render", + "Child:render", + "Child:mounted", + "Child:mounted", + "Parent:mounted", + ]); + }); + + test("reconciliation alg works for t-foreach in t-foreach", async () => { + class Child extends Component { + static template = xml`
    `; + } + + class Parent extends Component { + static template = xml` +
    + + + + + +
    `; + static components = { Child }; + state = { s: [{ blips: ["a1", "a2"] }, { blips: ["b1"] }] }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("
    a1
    a2
    b1
    "); + }); + + test("reconciliation alg works for t-foreach in t-foreach, 2", async () => { + class Child extends Component { + static template = xml`
    `; + } + + class Parent extends Component { + static template = xml` +
    +

    +

    + +

    +

    +
    `; + static components = { Child }; + state = useState({ rows: [1, 2], cols: ["a", "b"] }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe( + "

    1_a

    1_b

    2_a

    2_b

    " + ); + parent.state.rows = [2, 1]; + await nextTick(); + expect(fixture.innerHTML).toBe( + "

    2_a

    2_b

    1_a

    1_b

    " + ); + }); + + test("sub components rendered in a loop", async () => { + class Child extends Component { + static template = xml`

    `; + } + + class Parent extends Component { + static template = xml` +
    + + + +
    `; + static components = { Child }; + + state = useState({ numbers: [1, 2, 3] }); + } + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe(`

    1

    2

    3

    `); + }); + + test("sub components with some state rendered in a loop", async () => { + let n = 1; + + class Child extends Component { + static template = xml`

    `; + state: any; + setup() { + this.state = useState({ n }); + n++; + } + } + + class Parent extends Component { + static template = xml` +
    + + + +
    `; + static components = { Child }; + + state = useState({ + numbers: [1, 2, 3], + }); + } + const parent = await mount(Parent, fixture); + + parent.state.numbers = [1, 3]; + await nextTick(); + expect(fixture.innerHTML).toBe(`

    1

    3

    `); + }); +}); diff --git a/tests/context.test.ts b/tests/context.test.ts deleted file mode 100644 index 9e9b7226..00000000 --- a/tests/context.test.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { makeDeferred, makeTestEnv, makeTestFixture, nextTick } from "./helpers"; -import { Component } from "../src/component/component"; -import { Context, useContext } from "../src/context"; -import { xml } from "../src/tags"; -import { useState } from "../src/hooks"; - -//------------------------------------------------------------------------------ -// Setup and helpers -//------------------------------------------------------------------------------ - -// We create before each test: -// - fixture: a div, appended to the DOM, intended to be the target of dom -// manipulations. Note that it is removed after each test. -// - a test env, necessary to create components, that is set as env - -let fixture: HTMLElement; - -beforeEach(() => { - fixture = makeTestFixture(); - Component.env = makeTestEnv(); -}); - -afterEach(() => { - fixture.remove(); -}); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("Context", () => { - test("very simple use, with initial value", async () => { - const testContext = new Context({ value: 123 }); - - class Test extends Component { - static template = xml`
    `; - contextObj = useContext(testContext); - } - const test = new Test(); - await test.mount(fixture); - expect(fixture.innerHTML).toBe("
    123
    "); - }); - - test("useContext hook is reactive, for one component", async () => { - const testContext = new Context({ value: 123 }); - - class Test extends Component { - static template = xml`
    `; - contextObj = useContext(testContext); - } - const test = new Test(); - await test.mount(fixture); - expect(fixture.innerHTML).toBe("
    123
    "); - test.contextObj.value = 321; - await nextTick(); - expect(fixture.innerHTML).toBe("
    321
    "); - }); - - test("two components can subscribe to same context", async () => { - const testContext = new Context({ value: 123 }); - - class Child extends Component { - static template = xml``; - contextObj = useContext(testContext); - } - class Parent extends Component { - static template = xml`
    `; - static components = { Child }; - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    123123
    "); - testContext.state.value = 321; - await nextTick(); - expect(fixture.innerHTML).toBe("
    321321
    "); - }); - - test("two async components are updated in parallel", async () => { - const testContext = new Context({ value: 123 }); - const def = makeDeferred(); - const steps: string[] = []; - - class Child extends Component { - static template = xml``; - contextObj = useContext(testContext); - async render() { - steps.push("render"); - await def; - return super.render(); - } - } - - class Parent extends Component { - static template = xml`
    `; - static components = { Child }; - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    123123
    "); - testContext.state.value = 321; - await nextTick(); - expect(steps).toEqual(["render", "render"]); - expect(fixture.innerHTML).toBe("
    123123
    "); - def.resolve(); - await nextTick(); - expect(fixture.innerHTML).toBe("
    321321
    "); - }); - - test("two async components on two levels are updated (mostly) in parallel", async () => { - const testContext = new Context({ value: 123 }); - const def = makeDeferred(); - const steps: string[] = []; - - class SlowComp extends Component { - static template = xml`

    `; - willUpdateProps() { - return def; - } - } - class Child extends Component { - static template = xml``; - static components = { SlowComp }; - contextObj = useContext(testContext); - - render() { - steps.push("render"); - return super.render(); - } - } - - class Parent extends Component { - static template = xml`
    `; - static components = { Child }; - } - - class App extends Component { - static template = xml`
    `; - static components = { Child, Parent }; - } - - const app = new App(); - await app.mount(fixture); - expect(fixture.innerHTML).toBe( - "

    123

    123

    123

    " - ); - testContext.state.value = 321; - await nextTick(); - expect(steps).toEqual(["render"]); - expect(fixture.innerHTML).toBe( - "

    123

    123

    123

    " - ); - def.resolve(); - await nextTick(); - // we need to wait for an extra tick because it could happen (even though it - // is rare) that the second batch of renderings is not done yet, because - // the initial promise has been given to the macrotask queue, so a small - // delay happens. - await nextTick(); - expect(steps).toEqual(["render", "render", "render"]); - expect(fixture.innerHTML).toBe( - "

    321

    321

    321

    " - ); - }); - - test("one components can subscribe twice to same context", async () => { - const testContext = new Context({ a: 1, b: 2 }); - const steps: string[] = []; - - class Child extends Component { - static template = xml``; - contextObj1 = useContext(testContext); - contextObj2 = useContext(testContext); - __render(fiber) { - steps.push("child"); - return super.__render(fiber); - } - } - class Parent extends Component { - static template = xml`
    `; - static components = { Child }; - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    12
    "); - expect(steps).toEqual(["child"]); - testContext.state.a = 3; - await nextTick(); - expect(fixture.innerHTML).toBe("
    32
    "); - expect(steps).toEqual(["child", "child"]); - }); - - test("parent and children subscribed to same context", async () => { - const testContext = new Context({ a: 123, b: 321 }); - const steps: string[] = []; - - class Child extends Component { - static template = xml``; - contextObj = useContext(testContext); - __render(fiber) { - steps.push("child"); - return super.__render(fiber); - } - } - class Parent extends Component { - static template = xml`
    `; - static components = { Child }; - contextObj = useContext(testContext); - __render(fiber) { - steps.push("parent"); - return super.__render(fiber); - } - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    123321
    "); - expect(steps).toEqual(["parent", "child"]); - - parent.contextObj.a = 124; - await nextTick(); - expect(fixture.innerHTML).toBe("
    124321
    "); - - // we only want one render from the child here, not two - expect(steps).toEqual(["parent", "child", "parent", "child"]); - }); - - test("destroyed component is inactive", async () => { - const testContext = new Context({ a: 123 }); - const steps: string[] = []; - - class Child extends Component { - static template = xml``; - contextObj = useContext(testContext); - __render(fiber) { - steps.push("child"); - return super.__render(fiber); - } - } - class Parent extends Component { - static template = xml`
    `; - static components = { Child }; - state = useState({ flag: true }); - __render(fiber) { - steps.push("parent"); - return super.__render(fiber); - } - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    123
    "); - expect(steps).toEqual(["parent", "child"]); - expect(testContext.subscriptions.update.length).toBe(1); - - parent.state.flag = false; - await nextTick(); - expect(fixture.innerHTML).toBe("
    "); - expect(steps).toEqual(["parent", "child", "parent"]); - // kind of whitebox... - // we make sure we do not have any pending subscriptions to the 'update' - // event - expect(testContext.subscriptions.update.length).toBe(0); - }); - - test("destroyed component before being mounted is inactive", async () => { - const testContext = new Context({ a: 123 }); - - class Child extends Component { - static template = xml``; - contextObj = useContext(testContext); - willStart() { - return makeDeferred(); - } - } - class Parent extends Component { - static template = xml`
    `; - static components = { Child }; - state = useState({ flag: true }); - } - - const parent = new Parent(); - const prom = parent.mount(fixture); - await nextTick(); // wait for Child to be instantiated - expect(testContext.subscriptions.update.length).toBe(1); - parent.state.flag = false; - await prom; - expect(fixture.innerHTML).toBe("
    "); - // kind of whitebox... - // we make sure we do not have any pending subscriptions to the 'update' - // event - expect(testContext.subscriptions.update.length).toBe(0); - }); - - test.skip("concurrent renderings", async () => { - /** - * Note: this test is interesting, but sadly just an incomplete attempt at - * protecting users against themselves. With the context API, it is not - * possible for the framework to protect completely against crashes. Maybe - * like in this case, when a component is in a simple hierarchy where all - * renderings come from the context changes, but in a real case, where some - * code can trigger a rendering independently, it is insufficient. - * - * The main problem is that the sub component depends on some external state, - * which may be modified, and then incompatible with the component actual - * state (for example, if the sub component has an id key related to some - * object that has been removed from the context). - * - * For now, sadly, the only solution is that components that depends on external - * state should guarantee their own integrity themselves. Then maybe this - * could be solved at the level of a state management solution that has a - * more advanced API, to let components determine if they should be updated - * or not (so, something slightly more advanced that the useStore hook). - */ - const testContext = new Context({ x: { n: 1 }, key: "x" }); - const def = makeDeferred(); - let stateC; - class ComponentC extends Component { - static template = xml``; - context = useContext(testContext); - state = useState({ x: "a" }); - - constructor(parent, props) { - super(parent, props); - stateC = this.state; - } - } - class ComponentB extends Component { - static components = { ComponentC }; - static template = xml`

    `; - - willUpdateProps() { - return def; - } - } - class ComponentA extends Component { - static components = { ComponentB }; - static template = xml`
    `; - context = useContext(testContext); - } - - const component = new ComponentA(); - await component.mount(fixture); - - expect(fixture.innerHTML).toBe("

    1a

    "); - testContext.state.key = "y"; - testContext.state.y = { n: 2 }; - delete testContext.state.x; - await nextTick(); - - expect(fixture.innerHTML).toBe("

    1a

    "); - stateC.x = "b"; - await nextTick(); - - expect(fixture.innerHTML).toBe("

    1a

    "); - def.resolve(); - await nextTick(); - - expect(fixture.innerHTML).toBe("

    2b

    "); - }); -}); diff --git a/tests/core/event_bus.test.ts b/tests/core/event_bus.test.ts deleted file mode 100644 index dc59b459..00000000 --- a/tests/core/event_bus.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { EventBus } from "../../src/core/event_bus"; - -describe("event bus behaviour", () => { - test("can subscribe and be notified", () => { - const bus = new EventBus(); - let notified = false; - bus.on("event", {}, () => (notified = true)); - expect(notified).toBe(false); - bus.trigger("event"); - expect(notified).toBe(true); - }); - - test("callbacks are called with proper 'this'", () => { - expect.assertions(1); - const bus = new EventBus(); - const owner = {}; - bus.on("event", owner, function (this: any) { - expect(this).toBe(owner); - }); - bus.trigger("event"); - }); - - test("throw error if callback is undefined", () => { - expect.assertions(1); - const bus = new EventBus(); - expect(() => bus.on("event", {}, undefined)).toThrow(`Missing callback`); - }); - - test("can unsubscribe", () => { - const bus = new EventBus(); - let notified = false; - let owner = {}; - bus.on("event", owner, () => (notified = true)); - bus.off("event", owner); - bus.trigger("event"); - expect(notified).toBe(false); - }); - - test("arguments are properly propagated", () => { - expect.assertions(1); - const bus = new EventBus(); - bus.on("event", {}, (arg: any) => expect(arg).toBe("hello world")); - bus.trigger("event", "hello world"); - }); -}); diff --git a/tests/core/observer.test.ts b/tests/core/observer.test.ts deleted file mode 100644 index 25a5ac81..00000000 --- a/tests/core/observer.test.ts +++ /dev/null @@ -1,449 +0,0 @@ -import { Observer } from "../../src/core/observer"; -import { nextMicroTick } from "../helpers"; - -describe("observer", () => { - test("properly observe objects", () => { - const observer = new Observer(); - const obj: any = observer.observe({}); - - expect(typeof obj).toBe("object"); - expect(observer.revNumber(obj)).toBe(1); - expect(observer.rev).toBe(1); - - const obj2: any = observer.observe({ a: 1 }); - expect(observer.revNumber(obj2)).toBe(1); - expect(observer.revNumber(obj)).toBe(1); - expect(observer.rev).toBe(1); - - obj2.a = 2; - - expect(observer.revNumber(obj)).toBe(1); - expect(observer.revNumber(obj2)).toBe(2); - expect(observer.rev).toBe(2); - - expect(obj2).toEqual({ - a: 2, - }); - }); - - test("properly handle null or undefined", () => { - const observer = new Observer(); - const obj: any = observer.observe({ a: null, b: undefined }); - - expect(observer.revNumber(obj)).toBe(1); - expect(observer.rev).toBe(1); - - obj.a = 3; - expect(observer.revNumber(obj)).toBe(2); - expect(observer.rev).toBe(2); - - obj.b = 5; - expect(observer.revNumber(obj)).toBe(3); - expect(observer.rev).toBe(3); - - obj.a = null; - obj.b = undefined; - expect(observer.revNumber(obj)).toBe(5); - expect(observer.rev).toBe(5); - expect(obj).toEqual({ - a: null, - b: undefined, - }); - }); - - test("properly handle dates", () => { - const observer = new Observer(); - const date = new Date(); - const obj: any = observer.observe({ date }); - - expect(observer.revNumber(obj)).toBe(1); - expect(observer.rev).toBe(1); - expect(typeof obj.date.getFullYear()).toBe("number"); - expect(obj.date).toBe(date); - - obj.date = new Date(); - - expect(observer.revNumber(obj)).toBe(2); - expect(observer.rev).toBe(2); - expect(obj.date).not.toBe(date); - }); - - test("properly handle promises (i.e.: treat them like primitive values", async () => { - const observer = new Observer(); - let resolved = false; - const prom = new Promise((r) => r()); - const obj: any = observer.observe({ prom }); - expect(obj.prom).toBeInstanceOf(Promise); - - obj.prom.then(() => (resolved = true)); - - expect(observer.revNumber(obj)).toBe(1); - - expect(resolved).toBe(false); - await Promise.resolve(); - expect(resolved).toBe(true); - expect(observer.revNumber(obj)).toBe(1); - }); - - test("can change values in array", () => { - const observer = new Observer(); - const obj: any = observer.observe({ arr: [1, 2] }); - - expect(Array.isArray(obj.arr)).toBe(true); - expect(observer.revNumber(obj.arr)).toBe(1); - expect(observer.rev).toBe(1); - - obj.arr[0] = "nope"; - expect(observer.revNumber(obj.arr)).toBe(2); - expect(observer.revNumber(obj)).toBe(2); - expect(observer.rev).toBe(2); - }); - - test("various object property changes", () => { - const observer = new Observer(); - const obj: any = observer.observe({ a: 1 }); - - expect(observer.revNumber(obj)).toBe(1); - expect(observer.rev).toBe(1); - - obj.a = 2; - - expect(observer.revNumber(obj)).toBe(2); - expect(observer.rev).toBe(2); - - // same value again - obj.a = 2; - expect(observer.revNumber(obj)).toBe(2); - expect(observer.rev).toBe(2); - - obj.a = 3; - expect(observer.revNumber(obj)).toBe(3); - expect(observer.rev).toBe(3); - }); - - test("properly observe arrays", () => { - const observer = new Observer(); - const arr: any = observer.observe([]); - - expect(Array.isArray(arr)).toBe(true); - expect(arr.length).toBe(0); - expect(observer.revNumber(arr)).toBe(1); - expect(observer.rev).toBe(1); - - arr.push(1); - expect(observer.revNumber(arr)).toBe(2); - expect(observer.rev).toBe(2); - expect(arr.length).toBe(1); - expect(arr).toEqual([1]); - - arr.splice(1, 0, "hey"); - expect(observer.revNumber(arr)).toBe(3); - expect(observer.rev).toBe(3); - expect(arr).toEqual([1, "hey"]); - expect(arr.length).toBe(2); - - arr.unshift("lindemans"); - //it generates 3 primitive operations - expect(observer.revNumber(arr)).toBe(6); - expect(observer.rev).toBe(6); - expect(arr).toEqual(["lindemans", 1, "hey"]); - expect(arr.length).toBe(3); - - arr.reverse(); - //it generates 2 primitive operations - expect(observer.revNumber(arr)).toBe(8); - expect(observer.rev).toBe(8); - expect(arr).toEqual(["hey", 1, "lindemans"]); - expect(arr.length).toBe(3); - - arr.pop(); // one set, one delete - expect(observer.revNumber(arr)).toBe(10); - expect(observer.rev).toBe(10); - expect(arr).toEqual(["hey", 1]); - expect(arr.length).toBe(2); - - arr.shift(); // 2 sets, 1 delete - expect(observer.revNumber(arr)).toBe(13); - expect(observer.rev).toBe(13); - expect(arr).toEqual([1]); - expect(arr.length).toBe(1); - }); - - test("object pushed into arrays are observed", () => { - const observer = new Observer(); - const arr: any = observer.observe([]); - expect(observer.rev).toBe(1); - - arr.push({ kriek: 5 }); - expect(observer.rev).toBe(2); - expect(observer.revNumber(arr)).toBe(2); - expect(observer.revNumber(arr[0])).toBe(2); - - arr[0].kriek = 6; - - expect(observer.rev).toBe(3); - expect(observer.revNumber(arr)).toBe(3); - expect(observer.revNumber(arr[0])).toBe(3); - }); - - test("set new property on observed object", async () => { - const observer = new Observer(); - observer.notifyCB = jest.fn(); - const state: any = observer.observe({ a: 1 }); - expect(observer.notifyCB).toBeCalledTimes(0); - expect(observer.rev).toBe(1); - expect(observer.revNumber(state)).toBe(1); - - state.b = 8; - - await nextMicroTick(); - - expect(observer.notifyCB).toBeCalledTimes(1); - expect(observer.rev).toBe(2); - expect(observer.revNumber(state)).toBe(2); - - expect(state.b).toBe(8); - }); - - test("delete property from observed object", async () => { - const observer = new Observer(); - observer.notifyCB = jest.fn(); - const state: any = observer.observe({ a: 1, b: 8 }); - observer.observe(state); - - expect(observer.notifyCB).toBeCalledTimes(0); - expect(observer.rev).toBe(1); - expect(observer.revNumber(state)).toBe(1); - - delete state.b; - await nextMicroTick(); - - expect(observer.notifyCB).toBeCalledTimes(1); - expect(observer.rev).toBe(2); - expect(observer.revNumber(state)).toBe(2); - - expect(state).toEqual({ a: 1 }); - }); - - test("set element in observed array", async () => { - const observer = new Observer(); - observer.notifyCB = jest.fn(); - const state: any = observer.observe(["a"]); - - expect(observer.rev).toBe(1); - expect(observer.revNumber(state)).toBe(1); - expect(observer.notifyCB).toBeCalledTimes(0); - - state[1] = "b"; - - await nextMicroTick(); - - expect(observer.rev).toBe(2); - expect(observer.revNumber(state)).toBe(2); - expect(observer.notifyCB).toBeCalledTimes(1); - - expect(state).toEqual(["a", "b"]); - }); - - test("properly observe arrays in object", () => { - const observer = new Observer(); - const state: any = observer.observe({ arr: [] }); - - expect(observer.rev).toBe(1); - expect(observer.revNumber(state.arr)).toBe(1); - expect(state.arr.length).toBe(0); - - state.arr.push(1); - expect(observer.rev).toBe(2); - expect(observer.revNumber(state.arr)).toBe(2); - expect(state.arr.length).toBe(1); - }); - - test("properly observe objects in array", () => { - const observer = new Observer(); - const state: any = observer.observe({ arr: [{ something: 1 }] }); - observer.observe(state); - - expect(observer.rev).toBe(1); - expect(observer.revNumber(state.arr)).toBe(1); - expect(observer.revNumber(state.arr[0])).toBe(1); - - state.arr[0].something = 2; - expect(observer.rev).toBe(2); - expect(observer.revNumber(state.arr)).toBe(2); - expect(observer.revNumber(state.arr[0])).toBe(2); - }); - - test("properly observe objects in object", () => { - const observer = new Observer(); - const state: any = observer.observe({ a: { b: 1 } }); - - expect(observer.rev).toBe(1); - expect(observer.revNumber(state)).toBe(1); - expect(observer.revNumber(state.a)).toBe(1); - - state.a.b = 2; - expect(observer.rev).toBe(2); - expect(observer.revNumber(state)).toBe(2); - expect(observer.revNumber(state.a)).toBe(2); - }); - - test("reobserve new object values", () => { - const observer = new Observer(); - const obj: any = observer.observe({ a: 1 }); - - expect(observer.rev).toBe(1); - expect(observer.revNumber(obj)).toBe(1); - - obj.a = { b: 2 }; - - expect(observer.rev).toBe(2); - expect(observer.revNumber(obj)).toBe(2); - expect(observer.revNumber(obj.a)).toBe(2); - obj.a.b = 3; - expect(observer.rev).toBe(3); - expect(observer.revNumber(obj)).toBe(3); - expect(observer.revNumber(obj.a)).toBe(3); - }); - - test("deep observe misc changes", () => { - const observer = new Observer(); - const state: any = observer.observe({ o: { a: 1 }, arr: [1], n: 13 }); - expect(observer.revNumber(state)).toBe(1); - - state.o.a = 2; - expect(observer.rev).toBe(2); - expect(observer.revNumber(state)).toBe(2); - - state.arr.push(2); - expect(observer.rev).toBe(3); - expect(observer.revNumber(state)).toBe(3); - - state.n = 155; - expect(observer.rev).toBe(4); - expect(observer.revNumber(state)).toBe(4); - }); - - test("properly handle already observed state", () => { - const observer = new Observer(); - const obj1: any = observer.observe({ a: 1 }); - const obj2: any = observer.observe({ b: 1 }); - - expect(observer.revNumber(obj1)).toBe(1); - expect(observer.revNumber(obj2)).toBe(1); - - obj1.a = 2; - obj2.b = 3; - expect(observer.revNumber(obj1)).toBe(2); - expect(observer.revNumber(obj2)).toBe(2); - - obj2.b = obj1; - expect(observer.revNumber(obj1)).toBe(2); - expect(observer.revNumber(obj2)).toBe(3); - }); - - test("can set a property more than once", () => { - const observer = new Observer(); - const obj: any = observer.observe({}); - - expect(observer.revNumber(obj)).toBe(1); - expect(observer.rev).toBe(1); - - obj.aku = "always finds annoying problems"; - expect(observer.revNumber(obj)).toBe(2); - expect(observer.rev).toBe(2); - - obj.aku = "always finds good problems"; - - expect(observer.revNumber(obj)).toBe(3); - expect(observer.rev).toBe(3); - }); - - test("properly handle swapping elements", () => { - const observer = new Observer(); - const obj: any = observer.observe({ a: { arr: [] }, b: 1 }); - - // swap a and b - const b = obj.b; - obj.b = obj.a; - obj.a = b; - expect(observer.rev).toBe(3); - - // push something into array to make sure it works - obj.b.arr.push("blanche"); - expect(observer.rev).toBe(4); - }); - - test("properly handle assigning observed obj containing array", () => { - const observer = new Observer(); - const obj: any = observer.observe({ a: { arr: [], val: "test" } }); - - expect(observer.rev).toBe(1); - obj.a = { ...obj.a, val: "test2" }; - expect(observer.rev).toBe(2); - - // push something into array to make sure it works - obj.a.arr.push("blanche"); - expect(observer.rev).toBe(3); - }); - - test("accept cycles in observed state", () => { - const observer = new Observer(); - let obj1: any = {}; - let obj2: any = { b: obj1, key: 1 }; - obj1.a = obj2; - obj1 = observer.observe(obj1); - obj2 = obj1.a; - - expect(observer.revNumber(obj1)).toBe(1); - expect(observer.revNumber(obj2)).toBe(1); - - obj2.key = 3; - expect(observer.revNumber(obj1)).toBe(2); - expect(observer.revNumber(obj2)).toBe(2); - }); - - test("call callback when state is changed", async () => { - const observer = new Observer(); - observer.notifyCB = jest.fn(); - const obj: any = observer.observe({ a: 1, b: { c: 2 }, d: [{ e: 3 }], f: 4 }); - - expect(observer.notifyCB).toBeCalledTimes(0); - - obj.a = 2; - await nextMicroTick(); - expect(observer.notifyCB).toBeCalledTimes(1); - - obj.b.c = 3; - await nextMicroTick(); - expect(observer.notifyCB).toBeCalledTimes(2); - - obj.d[0].e = 5; - await nextMicroTick(); - expect(observer.notifyCB).toBeCalledTimes(3); - - obj.a = 111; - obj.f = 222; - await nextMicroTick(); - expect(observer.notifyCB).toBeCalledTimes(5); - }); - - test("throw error when state is mutated in object if allowMutation=false", async () => { - const observer = new Observer(); - observer.allowMutations = false; - const obj: any = observer.observe({ a: 1 }); - expect(() => { - obj.a = 2; - }).toThrow('Observed state cannot be changed here! (key: "a", val: "2")'); - }); - - test("throw error when state is mutated in array if allowMutation=false", async () => { - const observer = new Observer(); - observer.allowMutations = false; - const obj: any = observer.observe({ a: [1] }); - - expect(() => { - obj.a.push(2); - }).toThrow('Observed state cannot be changed here! (key: "1", val: "2")'); - }); -}); diff --git a/tests/event_bus.test.ts b/tests/event_bus.test.ts new file mode 100644 index 00000000..ac2b67f0 --- /dev/null +++ b/tests/event_bus.test.ts @@ -0,0 +1,35 @@ +import { EventBus } from "../src/event_bus"; + +describe("event bus behaviour", () => { + test("can subscribe and be notified", () => { + const bus = new EventBus(); + let notified = false; + bus.addEventListener("event", () => { + notified = true; + }); + expect(notified).toBe(false); + bus.trigger("event"); + expect(notified).toBe(true); + }); + + test("can unsubscribe", () => { + const bus = new EventBus(); + let n = 0; + let cb = () => n++; + bus.addEventListener("event", cb); + expect(n).toBe(0); + bus.trigger("event"); + expect(n).toBe(1); + bus.removeEventListener("event", cb); + expect(n).toBe(1); + bus.trigger("event"); + expect(n).toBe(1); + }); + + test("arguments are properly propagated", () => { + expect.assertions(1); + const bus = new EventBus(); + bus.addEventListener("event", (ev: any) => expect(ev.detail).toBe("hello world")); + bus.trigger("event", "hello world"); + }); +}); diff --git a/tests/helpers.ts b/tests/helpers.ts index c569329d..4c21dc4d 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,59 +1,49 @@ -import { Env, Component, STATUS } from "../src/component/component"; -import { scheduler } from "../src/component/scheduler"; -import { EvalContext, QWeb } from "../src/qweb/qweb"; -import { CompilationContext } from "../src/qweb/compilation_context"; -import { patch } from "../src/vdom"; -import "../src/qweb/base_directives"; -import "../src/qweb/extensions"; -import "../src/component/directive"; -import { browser } from "../src/browser"; +import { + onDestroyed, + onWillPatch, + onWillUnmount, + onMounted, + onPatched, + onWillStart, + onWillUpdateProps, + useComponent, + status, + onRender, + Component, +} from "../src"; +import { TemplateSet, globalTemplates, UTILS } from "../src/qweb/template_helpers"; +import { blockDom } from "../src"; +import { BDom } from "../src/blockdom"; +// import { mountBlock } from "../src/blockdom/block"; +import { compileTemplate, Template } from "../src/qweb/compiler"; +import { xml } from "../src/tags"; +// import { UTILS } from "../src/template_utils"; -// Some static cleanup -let nextSlotId; -let slots; -let nextId; -let TEMPLATES; +const mount = blockDom.mount; -beforeEach(() => { - nextSlotId = QWeb.nextSlotId; - CompilationContext.nextID = 1; - slots = Object.assign({}, QWeb.slots); - nextId = QWeb.nextId; - TEMPLATES = Object.assign({}, QWeb.TEMPLATES); - Component.scheduler.tasks = []; -}); - -afterEach(() => { - QWeb.nextSlotId = nextSlotId; - QWeb.slots = slots; - QWeb.nextId = nextId; - QWeb.TEMPLATES = TEMPLATES; - Component.scheduler.tasks = []; -}); - -// helpers export function nextMicroTick(): Promise { return Promise.resolve(); } -export async function nextTick(): Promise { - await new Promise((resolve) => setTimeout(resolve)); - await new Promise((resolve) => scheduler.requestAnimationFrame(resolve)); -} - -export async function nextFrame(): Promise { - await new Promise((resolve) => scheduler.requestAnimationFrame(resolve)); - await new Promise((resolve) => scheduler.requestAnimationFrame(resolve)); -} +let lastFixture: any = null; export function makeTestFixture() { let fixture = document.createElement("div"); document.body.appendChild(fixture); + if (lastFixture) { + lastFixture.remove(); + } + lastFixture = fixture; return fixture; } -export function normalize(str: string): string { - return str.replace(/\s+/g, ""); +export function snapshotTemplateCode(template: string) { + expect(compileTemplate(template).toString()).toMatchSnapshot(); +} + +export async function nextTick(): Promise { + await new Promise((resolve) => setTimeout(resolve)); + await new Promise((resolve) => requestAnimationFrame(resolve)); } interface Deferred extends Promise { @@ -67,85 +57,149 @@ export function makeDeferred(): Deferred { resolve = _resolve; reject = _reject; }); - (def).resolve = resolve; - (def).reject = reject; + (def as any).resolve = resolve; + (def as any).reject = reject; return def; } -export function makeTestEnv(): Env { - return { - qweb: new QWeb(), - browser: browser, - }; -} +/** + * Return the global template xml string corresponding to the given name + */ +// export function fromName(name: string): string { +// return globalTemplates[name]; +// } export function trim(str: string): string { return str.replace(/\s/g, ""); } -export function renderToDOM( - qweb: QWeb, - template: string, - context: EvalContext = {}, - extra?: any -): HTMLElement | Text { - if (!context.__owl__) { - // we add `__owl__` to better simulate a component as context. This is - // particularly important for event handlers added with the `t-on` directive. - context.__owl__ = { status: STATUS.MOUNTED }; - } - const vnode = qweb.render(template, context, extra); - - // we snapshot here the compiled code. This is useful to prevent unwanted code - // change. - expect(qweb.templates[template].fn.toString()).toMatchSnapshot(); - - if (vnode.sel === undefined) { - return document.createTextNode(vnode.text!); - } - const node = document.createElement(vnode.sel!); - const result = patch(node, vnode); - return result.elm as HTMLElement; +export function addTemplate(name: string, template: string): string { + globalTemplates[name] = template; + return name; } -/** - * Render a template to an html string. The big difference with the - * renderToString method in QWeb is that we use the renderToDom method, which - * snapshots the resulting template function. Doing so gives us a large body - * of reference to evaluate changes in the generated QWeb code. - * - * Note that the result of renderToString is guaranteed to be the same as the - * one from QWeb. - */ -export function renderToString( - qweb: QWeb, - t: string, - context: EvalContext = {}, - extra?: any -): string { - const result = qweb.renderToString(t, context, extra); - expect(qweb.templates[t].fn.toString()).toMatchSnapshot(); - return result; +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- + +export function compile(template: string): Template { + // register here the template globally so snapshotEverything + // can get it + globalTemplates[template] = template; + const templateFunction = compileTemplate(template); + return templateFunction(blockDom, UTILS); } -// hereafter, we define two helpers to patch/unpatch the nextFrame utils. This -// is useful for animations tests, as we hook before repaints to trigger -// animations (thanks to requestAnimationFrame). Patching nextFrame allows to -// simulate calls to this hook. One must not forget to unpatch afterwards. -let _nextFrame = QWeb.utils.nextFrame; -export function patchNextFrame(f: Function) { - QWeb.utils.nextFrame = (cb: () => void) => { - setTimeout(() => f(cb)); +export function renderToBdom(template: string, context: any = {}, node: any = {}): BDom { + return compile(template)(context, node); +} + +export function renderToString(template: string, context: any = {}): string { + const fixture = makeTestFixture(); + const bdom = renderToBdom(template, context); + mount(bdom, fixture); + return fixture.innerHTML; +} + +export class TestContext extends TemplateSet { + renderToString(name: string, context: any = {}): string { + const renderFn = this.getTemplate(name); + const bdom = renderFn(context, {}); + const fixture = makeTestFixture(); + mount(bdom, fixture); + return fixture.innerHTML; + } +} + +export function snapshotEverything() { + const consolewarn = console.warn; + + const originalAddTemplate = TemplateSet.prototype.addTemplate; + TemplateSet.prototype.addTemplate = function (name: string, template: string, options) { + originalAddTemplate.call(this, name, template, options); + // register it so snapshotEverything can get it + globalTemplates[name] = template; }; + + let globalSet: any; + + beforeAll(() => { + globalSet = new Set(Object.keys(globalTemplates)); + }); + + beforeEach(() => { + xml.nextId = 9; + }); + + afterEach(() => { + console.warn = () => {}; + for (let k in globalTemplates) { + if (globalSet.has(k)) { + // ignore generic templates + continue; + } + try { + snapshotTemplateCode(globalTemplates[k]); + } catch (e) { + // ignore error + } + delete globalTemplates[k]; + } + console.warn = consolewarn; + }); } -export function unpatchNextFrame() { - QWeb.utils.nextFrame = _nextFrame; +export function useLogLifecycle(steps: string[]) { + const component = useComponent(); + const name = component.constructor.name; + steps.push(`${name}:setup`); + expect(name + ": " + status(component)).toBe(name + ": " + "new"); + + onWillStart(() => { + expect(name + ": " + status(component)).toBe(name + ": " + "new"); + steps.push(`${name}:willStart`); + }); + + onMounted(() => { + expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); + steps.push(`${name}:mounted`); + }); + + onWillUpdateProps(() => { + expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); + steps.push(`${name}:willUpdateProps`); + }); + + onRender(() => { + steps.push(`${name}:render`); + }); + + onWillPatch(() => { + expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); + steps.push(`${name}:willPatch`); + }); + + onPatched(() => { + expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); + steps.push(`${name}:patched`); + }); + + onWillUnmount(() => { + expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); + steps.push(`${name}:willUnmount`); + }); + + onDestroyed(() => { + expect(name + ": " + status(component)).toBe(name + ": " + "destroyed"); + steps.push(`${name}:destroyed`); + }); } -export async function editInput(input: HTMLInputElement | HTMLTextAreaElement, value: string) { - input.value = value; - input.dispatchEvent(new Event("input")); - input.dispatchEvent(new Event("change")); - return nextTick(); +export function children(w: Component): Component[] { + const childrenMap = w.__owl__.children; + return Object.keys(childrenMap).map((id) => childrenMap[id].component); +} + +export function isDirectChildOf(child: Component, parent: Component): boolean { + return children(parent).includes(child); } diff --git a/tests/hooks.test.ts b/tests/hooks.test.ts deleted file mode 100644 index 52456e11..00000000 --- a/tests/hooks.test.ts +++ /dev/null @@ -1,686 +0,0 @@ -import { makeTestEnv, makeTestFixture, nextTick } from "./helpers"; -import { Component, Env } from "../src/component/component"; -import { - useState, - onMounted, - onWillUnmount, - useRef, - onPatched, - onWillPatch, - onWillStart, - onWillUpdateProps, - useEnv, - useSubEnv, - useExternalListener, - useComponent, -} from "../src/hooks"; -import { xml } from "../src/tags"; - -//------------------------------------------------------------------------------ -// Setup and helpers -//------------------------------------------------------------------------------ - -// We create before each test: -// - fixture: a div, appended to the DOM, intended to be the target of dom -// manipulations. Note that it is removed after each test. -// - env: a WEnv, necessary to create new components - -let fixture: HTMLElement; -let env: Env; - -beforeEach(() => { - fixture = makeTestFixture(); - env = makeTestEnv(); - Component.env = env; -}); - -afterEach(() => { - fixture.remove(); -}); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("hooks", () => { - test("can use a state hook", async () => { - class Counter extends Component { - static template = xml`
    `; - counter = useState({ value: 42 }); - } - const counter = new Counter(); - await counter.mount(fixture); - expect(fixture.innerHTML).toBe("
    42
    "); - counter.counter.value = 3; - await nextTick(); - expect(fixture.innerHTML).toBe("
    3
    "); - }); - - test("can use onMounted, onWillUnmount", async () => { - const steps: string[] = []; - function useMyHook() { - onMounted(() => { - steps.push("mounted"); - }); - onWillUnmount(() => { - steps.push("willunmount"); - }); - } - class MyComponent extends Component { - static template = xml`
    hey
    `; - constructor() { - super(); - useMyHook(); - } - } - const component = new MyComponent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    hey
    "); - expect(steps).toEqual(["mounted"]); - component.unmount(); - expect(fixture.innerHTML).toBe(""); - expect(steps).toEqual(["mounted", "willunmount"]); - }); - - test("can use onMounted, onWillUnmount, part 2", async () => { - const steps: string[] = []; - function useMyHook() { - onMounted(() => { - steps.push("mounted"); - }); - onWillUnmount(() => { - steps.push("willunmount"); - }); - } - class MyComponent extends Component { - static template = xml`
    hey
    `; - constructor(parent, props) { - super(parent, props); - useMyHook(); - } - } - - class Parent extends Component { - static template = xml`
    `; - static components = { MyComponent }; - state = useState({ flag: true }); - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    hey
    "); - expect(steps).toEqual(["mounted"]); - - parent.state.flag = false; - await nextTick(); - expect(fixture.innerHTML).toBe("
    "); - expect(steps).toEqual(["mounted", "willunmount"]); - }); - - test("mounted, willUnmount, onMounted, onWillUnmount order", async () => { - const steps: string[] = []; - function useMyHook() { - onMounted(() => { - steps.push("hook:mounted"); - }); - onWillUnmount(() => { - steps.push("hook:willunmount"); - }); - } - class MyComponent extends Component { - static template = xml`
    hey
    `; - constructor() { - super(); - useMyHook(); - } - mounted() { - steps.push("comp:mounted"); - } - willUnmount() { - steps.push("comp:willunmount"); - } - } - const component = new MyComponent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    hey
    "); - component.unmount(); - expect(fixture.innerHTML).toBe(""); - expect(steps).toEqual(["comp:mounted", "hook:mounted", "hook:willunmount", "comp:willunmount"]); - }); - - test("mounted, willUnmount, onMounted, onWillUnmount order, part 2", async () => { - const steps: string[] = []; - function useMyHook() { - onMounted(() => { - steps.push("hook:mounted"); - }); - onWillUnmount(() => { - steps.push("hook:willunmount"); - }); - } - class MyComponent extends Component { - static template = xml`
    hey
    `; - constructor(parent, props) { - super(parent, props); - useMyHook(); - } - mounted() { - steps.push("comp:mounted"); - } - willUnmount() { - steps.push("comp:willunmount"); - } - } - - class Parent extends Component { - static template = xml`
    `; - static components = { MyComponent }; - state = useState({ flag: true }); - } - - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    hey
    "); - parent.state.flag = false; - await nextTick(); - expect(fixture.innerHTML).toBe("
    "); - - expect(steps).toEqual(["comp:mounted", "hook:mounted", "hook:willunmount", "comp:willunmount"]); - }); - - test("two different call to mounted/willunmount should work", async () => { - const steps: string[] = []; - function useMyHook(i) { - onMounted(() => { - steps.push("hook:mounted" + i); - }); - onWillUnmount(() => { - steps.push("hook:willunmount" + i); - }); - } - class MyComponent extends Component { - static template = xml`
    hey
    `; - constructor() { - super(); - useMyHook(1); - useMyHook(2); - } - } - const component = new MyComponent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    hey
    "); - component.unmount(); - expect(fixture.innerHTML).toBe(""); - expect(steps).toEqual([ - "hook:mounted1", - "hook:mounted2", - "hook:willunmount2", - "hook:willunmount1", - ]); - }); - - test("useRef hook", async () => { - class Counter extends Component { - static template = xml`
    `; - button = useRef("button"); - value = 0; - increment() { - this.value++; - (this.button.el as HTMLButtonElement).innerHTML = String(this.value); - } - } - const counter = new Counter(); - expect(counter.button.el).toBe(null); - await counter.mount(fixture); - expect(fixture.innerHTML).toBe("
    "); - expect(counter.button.el).not.toBe(null); - expect(counter.button.el).toBe(fixture.querySelector("button")); - counter.increment(); - await nextTick(); - expect(fixture.innerHTML).toBe("
    "); - }); - - test("useRef hook is null if ref is removed ", async () => { - expect.assertions(4); - class TestRef extends Component { - static template = xml`
    owl
    `; - spanRef = useRef("span"); - state = useState({ flag: true }); - willPatch() { - expect(this.spanRef.el).not.toBeNull(); - } - patched() { - expect(this.spanRef.el).toBeNull(); - } - } - const component = new TestRef(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    owl
    "); - component.state.flag = false; - await nextTick(); - expect(fixture.innerHTML).toBe("
    "); - }); - - test("t-refs on widget are components", async () => { - class WidgetB extends Component { - static template = xml`
    b
    `; - } - class WidgetC extends Component { - static template = xml`
    Hello
    `; - static components = { WidgetB }; - ref = useRef("mywidgetb"); - } - - const widget = new WidgetC(); - expect(widget.ref.comp).toBe(null); - expect(widget.ref.el).toBe(null); - await widget.mount(fixture); - expect(widget.ref.comp).toBeInstanceOf(WidgetB); - expect(widget.ref.el).toEqual(fixture.querySelector(".outer-div > div")); - }); - - test("t-refs are bound at proper timing", async () => { - expect.assertions(2); - class Widget extends Component { - static template = xml`
    widget
    `; - } - - class ParentWidget extends Component { - static template = xml` -
    - -
    - `; - static components = { Widget }; - state = useState({ list: [] }); - child = useRef("child"); - willPatch() { - expect(this.child.comp).toBeNull(); - } - patched() { - expect(this.child.comp).not.toBeNull(); - } - } - - const parent = new ParentWidget(); - await parent.mount(fixture); - parent.state.list.push(1); - await nextTick(); - }); - - test("t-refs are bound at proper timing (2)", async () => { - expect.assertions(10); - class Widget extends Component { - static template = xml`
    widget
    `; - } - class ParentWidget extends Component { - static template = xml` -
    - - -
    `; - static components = { Widget }; - state = useState({ child1: true, child2: false }); - child1 = useRef("child1"); - child2 = useRef("child2"); - count = 0; - mounted() { - expect(this.child1.comp).toBeDefined(); - expect(this.child2.comp).toBeNull(); - } - willPatch() { - if (this.count === 0) { - expect(this.child1.comp).toBeDefined(); - expect(this.child2.comp).toBeNull(); - } - if (this.count === 1) { - expect(this.child1.comp).toBeDefined(); - expect(this.child2.comp).toBeDefined(); - } - } - patched() { - if (this.count === 0) { - expect(this.child1.comp).toBeDefined(); - expect(this.child2.comp).toBeDefined(); - } - if (this.count === 1) { - expect(this.child1.comp).toBeNull(); - expect(this.child2.comp).toBeDefined(); - } - this.count++; - } - } - - const parent = new ParentWidget(); - await parent.mount(fixture); - parent.state.child2 = true; - await nextTick(); - parent.state.child1 = false; - await nextTick(); - }); - - test("can use onPatched, onWillPatch", async () => { - const steps: string[] = []; - function useMyHook() { - onWillPatch(() => { - steps.push("willPatch"); - }); - onPatched(() => { - steps.push("patched"); - }); - } - - class MyComponent extends Component { - static template = xml`
    hey
    `; - state = useState({ flag: true }); - - constructor() { - super(); - useMyHook(); - } - } - - const component = new MyComponent(); - await component.mount(fixture); - expect(steps).toEqual([]); - - expect(fixture.innerHTML).toBe("
    hey
    "); - component.state.flag = false; - await nextTick(); - expect(fixture.innerHTML).toBe("
    "); - - expect(steps).toEqual(["willPatch", "patched"]); - }); - - test("patched, willPatch, onPatched, onWillPatch order", async () => { - const steps: string[] = []; - function useMyHook() { - onPatched(() => { - steps.push("hook:patched"); - }); - onWillPatch(() => { - steps.push("hook:willPatch"); - }); - } - class MyComponent extends Component { - static template = xml`
    hey
    `; - state = useState({ flag: true }); - - constructor() { - super(); - useMyHook(); - } - willPatch() { - steps.push("comp:willPatch"); - } - patched() { - steps.push("comp:patched"); - } - } - const component = new MyComponent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    hey
    "); - component.state.flag = false; - await nextTick(); - - expect(steps).toEqual(["hook:willPatch", "comp:willPatch", "comp:patched", "hook:patched"]); - }); - - test("two different call to willPatch/patched should work", async () => { - const steps: string[] = []; - function useMyHook(i) { - onPatched(() => { - steps.push("hook:patched" + i); - }); - onWillPatch(() => { - steps.push("hook:willPatch" + i); - }); - } - class MyComponent extends Component { - static template = xml`
    hey
    `; - state = useState({ value: 1 }); - constructor() { - super(); - useMyHook(1); - useMyHook(2); - } - } - const component = new MyComponent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    hey1
    "); - component.state.value++; - await nextTick(); - expect(fixture.innerHTML).toBe("
    hey2
    "); - - expect(steps).toEqual(["hook:willPatch2", "hook:willPatch1", "hook:patched1", "hook:patched2"]); - }); - - describe("autofocus hook", () => { - function useAutofocus(name) { - let ref = useRef(name); - let isInDom = false; - function updateFocus() { - if (!isInDom && ref.el) { - isInDom = true; - ref.el.focus(); - } else if (isInDom && !ref.el) { - isInDom = false; - } - } - onPatched(updateFocus); - onMounted(updateFocus); - } - - test("simple input", async () => { - class SomeComponent extends Component { - static template = xml` -
    - - -
    `; - - constructor() { - super(); - useAutofocus("input2"); - } - } - - const component = new SomeComponent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    "); - const input2 = fixture.querySelectorAll("input")[1]; - expect(input2).toBe(document.activeElement); - }); - - test("input in a t-if", async () => { - class SomeComponent extends Component { - static template = xml` -
    - - -
    `; - - state = useState({ flag: false }); - constructor() { - super(); - useAutofocus("input2"); - } - } - - const component = new SomeComponent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    "); - expect(document.activeElement).toBe(document.body); - - component.state.flag = true; - await nextTick(); - const input2 = fixture.querySelectorAll("input")[1]; - expect(input2).toBe(document.activeElement); - }); - }); - - test("can use useEnv", async () => { - expect.assertions(1); - class TestComponent extends Component { - static template = xml`
    `; - constructor() { - super(); - expect(useEnv()).toBe(env); - } - } - const component = new TestComponent(); - await component.mount(fixture); - }); - - test("can use sub env", async () => { - class TestComponent extends Component { - static template = xml`
    `; - constructor() { - super(); - useSubEnv({ val: 3 }); - } - } - const component = new TestComponent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    3
    "); - expect(env).not.toHaveProperty("val"); - expect(component.env).toHaveProperty("val"); - }); - - test("can use useComponent", async () => { - expect.assertions(1); - class TestComponent extends Component { - static template = xml`
    `; - constructor() { - super(); - expect(useComponent()).toBe(this); - } - } - const component = new TestComponent(); - await component.mount(fixture); - }); - - test("parent and child env", async () => { - class Child extends Component { - static template = xml`
    `; - constructor(parent, props) { - super(parent, props); - useSubEnv({ val: 5 }); - } - } - - class Parent extends Component { - static template = xml`
    `; - static components = { Child }; - constructor() { - super(); - useSubEnv({ val: 3 }); - } - } - const component = new Parent(); - await component.mount(fixture); - expect(fixture.innerHTML).toBe("
    3
    5
    "); - }); - - test("can use onWillStart, onWillUpdateProps", async () => { - const steps: string[] = []; - async function slow(): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve("slow"); - }, 0); - }); - } - function useMyHook() { - onWillStart(async () => { - steps.push(await slow()); - steps.push("onWillStart"); - }); - onWillUpdateProps(async (nextProps) => { - expect(nextProps).toEqual({ value: 2 }); - steps.push(await slow()); - steps.push("onWillUpdateProps"); - }); - } - function use2ndHook() { - onWillStart(() => { - steps.push("on2ndStart"); - }); - onWillUpdateProps((nextProps) => { - expect(nextProps).toEqual({ value: 2 }); - steps.push("on2ndUpdate"); - }); - } - class MyComponent extends Component { - static template = xml``; - constructor(parent, props) { - super(parent, props); - useMyHook(); - use2ndHook(); - } - } - class App extends Component { - static template = xml`
    `; - static components = { MyComponent }; - state = useState({ value: 1 }); - } - - const app = new App(); - await app.mount(fixture); - expect(fixture.innerHTML).toBe("
    1
    "); - - // NOTE: 'on2ndStart' appears first in the list even though - // the 'use2ndHook' is declared after 'useMyHook'. This is - // because Promise.all is used to call the callbacks specified - // in the hooks, which runs them simultaneously. - // Additionally, 'slow' should be listed before 'onWillStart' - // because call to `slow` is awaited. - expect(steps).toEqual(["on2ndStart", "slow", "onWillStart"]); - - app.state.value = 2; - await nextTick(); - expect(fixture.innerHTML).toBe("
    2
    "); - expect(steps).toEqual([ - "on2ndStart", - "slow", - "onWillStart", - "on2ndUpdate", - "slow", - "onWillUpdateProps", - ]); - }); - - test("useExternalListener", async () => { - let n = 0; - - class MyComponent extends Component { - static template = xml``; - constructor(parent, props) { - super(parent, props); - useExternalListener(window as any, "click", this.increment); - } - increment() { - n++; - } - } - class App extends Component { - static template = xml`
    `; - static components = { MyComponent }; - state = useState({ flag: false }); - } - - const app = new App(); - await app.mount(fixture); - - expect(n).toBe(0); - window.dispatchEvent(new Event("click")); - expect(n).toBe(0); - app.state.flag = true; - await nextTick(); - window.dispatchEvent(new Event("click")); - expect(n).toBe(1); - app.state.flag = false; - await nextTick(); - window.dispatchEvent(new Event("click")); - expect(n).toBe(1); - }); -}); diff --git a/tests/misc/__snapshots__/memo.test.ts.snap b/tests/misc/__snapshots__/memo.test.ts.snap new file mode 100644 index 00000000..f21e3032 --- /dev/null +++ b/tests/misc/__snapshots__/memo.test.ts.snap @@ -0,0 +1,87 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Memo if no prop change, prevent renderings from above 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(ctx['props'].value); + } +}" +`; + +exports[`Memo if no prop change, prevent renderings from above 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + let b6 = text(ctx['state'].a); + let b7 = text(ctx['state'].b); + let b8 = text(ctx['state'].c); + return multi([b6, b7, b8]); + } + + return function template(ctx, node, key = \\"\\") { + let b2 = text(ctx['state'].a); + let b3 = text(ctx['state'].b); + let b4 = text(ctx['state'].c); + let b9 = assign(component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return multi([b2, b3, b4, b9]); + } +}" +`; + +exports[`Memo if no props, prevent renderings from above 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(ctx['props'].value); + } +}" +`; + +exports[`Memo if no props, prevent renderings from above 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot3 = ctx => (node, key) => { + return component(\`Child\`, {value: ctx['state'].value}, key + \`__4\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx); + let b4 = assign(component(\`Memo\`, {}, key + \`__2\`, node, ctx), {slots: {'default': slot3(ctx)}}); + return multi([b2, b4]); + } +}" +`; + +exports[`Memo if no props, prevent renderings from above (work with simple html) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return text(ctx['state'].value); + } + + return function template(ctx, node, key = \\"\\") { + let b2 = text(ctx['state'].value); + let b4 = assign(component(\`Memo\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return multi([b2, b4]); + } +}" +`; diff --git a/tests/misc/__snapshots__/portal.test.ts.snap b/tests/misc/__snapshots__/portal.test.ts.snap new file mode 100644 index 00000000..b45539fe --- /dev/null +++ b/tests/misc/__snapshots__/portal.test.ts.snap @@ -0,0 +1,466 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Portal Portal composed with t-slot 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    child2
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`Portal Portal composed with t-slot 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + const slot2 = ctx => (node, key) => { + return callSlot(ctx, node, key, 'default'); + } + + return function template(ctx, node, key = \\"\\") { + return assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } +}" +`; + +exports[`Portal Portal composed with t-slot 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + let h4 = [\`custom\`, ctx, '_handled']; + return assign(component(\`Child2\`, {}, key + \`__3\`, node, ctx), {handlers: [h4]}); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`Portal basic use of portal 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    1
    \`); + let block2 = createBlock(\`

    2

    \`); + + const slot2 = ctx => (node, key) => { + return block2(); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`Portal conditional use of Portal (with sub Component) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`Portal conditional use of Portal (with sub Component) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block2 = createBlock(\`1\`); + + const slot2 = ctx => (node, key) => { + return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b2,b4; + b2 = block2(); + if (ctx['state'].hasPortal) { + b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } + return multi([b2, b4]); + } +}" +`; + +exports[`Portal conditional use of Portal 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block2 = createBlock(\`1\`); + let block3 = createBlock(\`

    2

    \`); + + const slot2 = ctx => (node, key) => { + return block3(); + } + + return function template(ctx, node, key = \\"\\") { + let b2,b4; + b2 = block2(); + if (ctx['state'].hasPortal) { + b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } + return multi([b2, b4]); + } +}" +`; + +exports[`Portal lifecycle hooks of portal sub component are properly called 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`Portal lifecycle hooks of portal sub component are properly called 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b3; + if (ctx['state'].hasChild) { + b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + } + return block1([], [b3]); + } +}" +`; + +exports[`Portal portal could have dynamically no content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`\`); + + const slot2 = ctx => (node, key) => { + let b3; + if (ctx['state'].val) { + let d1 = ctx['state'].val; + b3 = block3([d1]); + } + return multi([b3]); + } + + return function template(ctx, node, key = \\"\\") { + let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b4]); + } +}" +`; + +exports[`Portal portal with child and props 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`Portal portal with child and props 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`Portal portal with dynamic body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`\`); + let block4 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + let b3,b4; + if (ctx['state'].val) { + let d1 = ctx['state'].val; + b3 = block3([d1]); + } else { + b4 = block4(); + } + return multi([b3, b4]); + } + + return function template(ctx, node, key = \\"\\") { + let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b5]); + } +}" +`; + +exports[`Portal portal with many children 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`
    1
    \`); + let block4 = createBlock(\`

    2

    \`); + + const slot2 = ctx => (node, key) => { + let b3 = block3(); + let b4 = block4(); + return multi([b3, b4]); + } + + return function template(ctx, node, key = \\"\\") { + let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b5]); + } +}" +`; + +exports[`Portal portal with no content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + let b3; + if (false) { + b3 = text('ABC'); + } + return multi([b3]); + } + + return function template(ctx, node, key = \\"\\") { + let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b4]); + } +}" +`; + +exports[`Portal portal with only text as content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + return text('only text'); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`Portal portal with target not in dom 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`
    2
    \`); + + const slot2 = ctx => (node, key) => { + return block2(); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(node.getChild(\`Portal\`, {target: '#does-not-exist'}, key + \`__1\`, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`Portal portal's parent's env is not polluted 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`Portal portal's parent's env is not polluted 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + return component(\`Child\`, {}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`Portal with target in template (after portal) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    1
    \`); + let block2 = createBlock(\`

    2

    \`); + + const slot2 = ctx => (node, key) => { + return block2(); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`Portal with target in template (before portal) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    1
    \`); + let block2 = createBlock(\`

    2

    \`); + + const slot2 = ctx => (node, key) => { + return block2(); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; + +exports[`Portal: UI/UX focus is kept across re-renders 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].val; + return block1([d1]); + } +}" +`; + +exports[`Portal: UI/UX focus is kept across re-renders 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + + const slot2 = ctx => (node, key) => { + return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); + } + + return function template(ctx, node, key = \\"\\") { + let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}}); + return block1([], [b3]); + } +}" +`; diff --git a/tests/misc/async_root.test.ts b/tests/misc/async_root.test.ts deleted file mode 100644 index 16856ffa..00000000 --- a/tests/misc/async_root.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { AsyncRoot } from "../../src/misc/async_root"; -import { useState } from "../../src/hooks"; -import { xml } from "../../src/tags"; -import { makeDeferred, makeTestFixture, makeTestEnv, nextTick } from "../helpers"; -import { Component } from "../../src/component/component"; - -//------------------------------------------------------------------------------ -// Setup and helpers -//------------------------------------------------------------------------------ - -// We create before each test: -// - fixture: a div, appended to the DOM, intended to be the target of dom -// manipulations. Note that it is removed after each test. -// - a test env, necessary to create components, that is set as env - -let fixture: HTMLElement; - -beforeEach(() => { - fixture = makeTestFixture(); - Component.env = makeTestEnv(); -}); - -afterEach(() => { - fixture.remove(); -}); - -describe("Asyncroot", () => { - test("delayed component with AsyncRoot component", async () => { - let def; - class Child extends Component { - static template = xml``; - } - class AsyncChild extends Child { - willUpdateProps() { - return def; - } - } - class Parent extends Component { - static template = xml` -
    - -
    - - - - -
    -
    `; - static components = { Child, AsyncChild, AsyncRoot }; - state = useState({ val: 0 }); - - updateApp() { - this.state.val++; - } - } - - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("00"); - - // click on button to increment Parent counter - def = makeDeferred(); - fixture.querySelector("button")!.click(); - await nextTick(); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("10"); - - def.resolve(); - await nextTick(); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("11"); - }); - - test("fast component with AsyncRoot", async () => { - let def; - class Child extends Component { - static template = xml``; - } - class AsyncChild extends Child { - willUpdateProps() { - return def; - } - } - - class Parent extends Component { - static template = xml` -
    - -
    - - - - -
    -
    `; - static components = { Child, AsyncChild, AsyncRoot }; - state = useState({ val: 0 }); - - updateApp() { - this.state.val++; - } - } - - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("00"); - - // click on button to increment Parent counter - def = makeDeferred(); - fixture.querySelector("button")!.click(); - await nextTick(); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("10"); - - def.resolve(); - await nextTick(); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("11"); - }); - - test("asyncroot component: mixed re-renderings", async () => { - let def; - class Child extends Component { - static template = xml` - - / - `; - state = useState({ val: 0 }); - - increment() { - this.state.val++; - } - } - class AsyncChild extends Child { - willUpdateProps() { - return def; - } - } - class Parent extends Component { - static template = xml` -
    - -
    - - - - -
    -
    `; - static components = { Child, AsyncChild, AsyncRoot }; - state = useState({ val: 0 }); - - updateApp() { - this.state.val++; - } - } - - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("0/00/0"); - - // click on button to increment Parent counter - def = makeDeferred(); - fixture.querySelector("button")!.click(); - await nextTick(); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("0/10/0"); - - // click on each Child to increment their local counter - const children = parent.el!.querySelectorAll("span"); - children[0]!.click(); - await nextTick(); - expect(fixture.querySelector(".children")!.innerHTML).toBe("1/10/0"); - - children[1]!.click(); - await nextTick(); - expect(fixture.querySelector(".children")!.innerHTML).toBe("1/10/0"); - - // finalize first re-rendering (coming from the props update) - def.resolve(); - await nextTick(); - - expect(fixture.querySelector(".children")!.innerHTML).toBe("1/11/1"); - }); -}); diff --git a/tests/misc/memo.test.ts b/tests/misc/memo.test.ts new file mode 100644 index 00000000..5360dae2 --- /dev/null +++ b/tests/misc/memo.test.ts @@ -0,0 +1,93 @@ +import { Component, mount, useState } from "../../src"; +import { Memo } from "../../src/"; +import { xml } from "../../src/tags"; +import { makeTestFixture, nextTick, snapshotEverything } from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +describe("Memo", () => { + test("if no props, prevent renderings from above ", async () => { + class Child extends Component { + static template = xml``; + } + class Test extends Component { + static template = xml` + + + + `; + + static components = { Memo, Child }; + + state = useState({ value: 1 }); + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe("11"); + component.state.value = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("21"); + }); + + test("if no props, prevent renderings from above (work with simple html) ", async () => { + class Test extends Component { + static template = xml` + + + + `; + + static components = { Memo }; + + state = useState({ value: 1 }); + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe("11"); + component.state.value = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("21"); + }); + + test("if no prop change, prevent renderings from above ", async () => { + class Child extends Component { + static template = xml``; + } + + class Test extends Component { + static template = xml` + + + + + + + + `; + + static components = { Memo, Child }; + + state = useState({ a: "a", b: "b", c: "c" }); + } + + const component = await mount(Test, fixture); + + expect(fixture.innerHTML).toBe("abcabc"); + + component.state.c = "C"; + await nextTick(); + expect(fixture.innerHTML).toBe("abCabc"); + + component.state.a = "A"; + await nextTick(); + expect(fixture.innerHTML).toBe("AbCAbC"); + }); +}); diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts index da2a6900..04526f75 100644 --- a/tests/misc/portal.test.ts +++ b/tests/misc/portal.test.ts @@ -1,211 +1,132 @@ -import { Portal } from "../../src/misc/portal"; +import { + App, + Component, + mount, + onMounted, + onPatched, + onWillPatch, + onWillUnmount, + useState, +} from "../../src"; +import { Portal } from "../../src/"; import { xml } from "../../src/tags"; -import { makeTestFixture, makeTestEnv, nextTick } from "../helpers"; -import { Component } from "../../src/component/component"; -import { useState } from "../../src/hooks"; -import { QWeb } from "../../src/qweb"; - -//------------------------------------------------------------------------------ -// Setup and helpers -//------------------------------------------------------------------------------ - -// We create before each test: -// - fixture: a div, appended to the DOM, intended to be the target of dom -// manipulations. Note that it is removed after each test. -// - outside: a div with id #outside appended into fixture, meant to be used as -// target by Portal component -// - a test env, necessary to create components, that is set on Component +import { makeTestFixture, nextTick, snapshotEverything } from "../helpers"; let fixture: HTMLElement; -let outside: HTMLElement; + +function addOutsideDiv(fixture: HTMLElement): HTMLElement { + let outside = document.createElement("div"); + outside.setAttribute("id", "outside"); + fixture.appendChild(outside); + return outside; +} + +snapshotEverything(); beforeEach(() => { fixture = makeTestFixture(); - outside = document.createElement("div"); - outside.setAttribute("id", "outside"); - fixture.appendChild(outside); - - Component.env = makeTestEnv(); }); -afterEach(() => { - fixture.remove(); -}); - -describe("Portal: Props validation", () => { - test("target is mandatory", async () => { - const dev = QWeb.dev; - QWeb.dev = true; - class Parent extends Component { - static components = { Portal }; - static template = xml` -
    - -
    2
    -
    -
    `; - } - let error; - try { - const parent = new Parent(); - await parent.mount(fixture); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.message).toBe(`Missing props 'target' (component 'Portal')`); - - QWeb.dev = dev; - }); - - test("target is not list", async () => { - const dev = QWeb.dev; - QWeb.dev = true; - class Parent extends Component { - static components = { Portal }; - static template = xml` -
    - -
    2
    -
    -
    `; - } - let error; - try { - const parent = new Parent(); - await parent.mount(fixture); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.message).toBe(`Invalid Prop 'target' in component 'Portal'`); - - QWeb.dev = dev; - }); -}); - -describe("Portal: Basic use and DOM placement", () => { +describe("Portal", () => { test("basic use of portal", async () => { - const dev = QWeb.dev; - QWeb.dev = true; class Parent extends Component { static components = { Portal }; static template = xml` -
    - 1 - -
    2
    -
    -
    `; +
    + 1 + +

    2

    +
    +
    `; } - let error; - let parent; - try { - parent = new Parent(); - } catch (e) { - error = e; - } - expect(error).toBeUndefined(); - await parent.mount(fixture); - expect(outside.innerHTML).toBe("
    2
    "); - expect(parent.el!.outerHTML).toBe("
    1
    "); - QWeb.dev = dev; + + addOutsideDiv(fixture); + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe('

    2

    1
    '); }); test("conditional use of Portal", async () => { class Parent extends Component { static components = { Portal }; static template = xml` -
    - 1 - -
    2
    -
    -
    `; + 1 + +

    2

    +
    `; state = useState({ hasPortal: false }); } - const parent = new Parent(); - await parent.mount(fixture); - expect(outside.innerHTML).toBe(""); - expect(parent.el!.outerHTML).toBe("
    1
    "); + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe('
    1'); parent.state.hasPortal = true; await nextTick(); - expect(outside.innerHTML).toBe("
    2
    "); - expect(parent.el!.outerHTML).toBe("
    1
    "); + expect(fixture.innerHTML).toBe('

    2

    1'); parent.state.hasPortal = false; await nextTick(); - expect(outside.innerHTML).toBe(""); - expect(parent.el!.outerHTML).toBe("
    1
    "); + expect(fixture.innerHTML).toBe('
    1'); parent.state.hasPortal = true; await nextTick(); - expect(outside.innerHTML).toBe("
    2
    "); - expect(parent.el!.outerHTML).toBe("
    1
    "); + expect(fixture.innerHTML).toBe('

    2

    1'); }); test("conditional use of Portal (with sub Component)", async () => { class Child extends Component { - static template = xml`
    `; + static template = xml`

    `; } class Parent extends Component { static components = { Portal, Child }; static template = xml` -
    - 1 - - - -
    `; + 1 + + + `; state = useState({ hasPortal: false, val: 1 }); } - const parent = new Parent(); - await parent.mount(fixture); - expect(outside.innerHTML).toBe(""); - expect(parent.el!.outerHTML).toBe("
    1
    "); + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe('
    1'); parent.state.hasPortal = true; await nextTick(); - expect(outside.innerHTML).toBe("
    1
    "); - expect(parent.el!.outerHTML).toBe("
    1
    "); + expect(fixture.innerHTML).toBe('

    1

    1'); parent.state.hasPortal = false; await nextTick(); - expect(outside.innerHTML).toBe(""); - expect(parent.el!.outerHTML).toBe("
    1
    "); + expect(fixture.innerHTML).toBe('
    1'); parent.state.val = 2; await nextTick(); - expect(outside.innerHTML).toBe(""); - expect(parent.el!.outerHTML).toBe("
    1
    "); + expect(fixture.innerHTML).toBe('
    1'); parent.state.hasPortal = true; await nextTick(); - expect(outside.innerHTML).toBe("
    2
    "); - expect(parent.el!.outerHTML).toBe("
    1
    "); + expect(fixture.innerHTML).toBe('

    2

    1'); }); test("with target in template (before portal)", async () => { class Parent extends Component { static components = { Portal }; static template = xml` -
    -
    - 1 - -

    2

    -
    -
    `; +
    +
    + 1 + +

    2

    +
    +
    `; } - const parent = new Parent(); - await parent.mount(fixture); - expect(parent.el!.innerHTML).toBe( - '

    2

    1' + const parent = await mount(Parent, fixture); + expect((parent.el as any)!.innerHTML).toBe( + '

    2

    1' ); }); @@ -213,186 +134,149 @@ describe("Portal: Basic use and DOM placement", () => { class Parent extends Component { static components = { Portal }; static template = xml` -
    - 1 - -

    2

    -
    -
    -
    `; +
    + 1 + +

    2

    +
    +
    +
    `; } - const parent = new Parent(); - await parent.mount(fixture); - expect(parent.el!.innerHTML).toBe( - '1

    2

    ' + const parent = await mount(Parent, fixture); + expect((parent.el as any)!.innerHTML).toBe( + '1

    2

    ' ); }); - test("portal with target not in dom", async () => { - const consoleError = console.error; - console.error = jest.fn(() => {}); + test.skip("portal with target not in dom", async () => { + // need error handling to unskip this one class Parent extends Component { static components = { Portal }; static template = xml` -
    - -
    2
    -
    -
    `; +
    + +
    2
    +
    +
    `; } - const parent = new Parent(); let error; try { - await parent.mount(fixture); + await mount(Parent, fixture); } catch (e) { error = e; } expect(error).toBeDefined(); expect(error.message).toBe('Could not find any match for "#does-not-exist"'); - expect(console.error).toBeCalledTimes(0); expect(fixture.innerHTML).toBe(`
    `); - console.error = consoleError; }); test("portal with child and props", async () => { const steps: string[] = []; + const outside = addOutsideDiv(fixture); + class Child extends Component { static template = xml``; - mounted() { - steps.push("mounted"); - expect(outside.innerHTML).toBe("1"); - } - patched() { - steps.push("patched"); - expect(outside.innerHTML).toBe("2"); + + setup() { + onMounted(() => { + steps.push("mounted"); + expect(outside.innerHTML).toBe("1"); + }); + onPatched(() => { + steps.push("patched"); + expect(outside.innerHTML).toBe("2"); + }); } } class Parent extends Component { static components = { Portal, Child }; static template = xml` -
    - - - -
    `; +
    + + + +
    `; state = useState({ val: 1 }); } - const parent = new Parent(); - await parent.mount(fixture); + const parent = await mount(Parent, fixture); expect(outside.innerHTML).toBe("1"); - expect(parent.el!.innerHTML).toBe(""); + expect((parent.el as any).innerHTML).toBe(""); parent.state.val = 2; await nextTick(); expect(outside.innerHTML).toBe("2"); - expect(parent.el!.innerHTML).toBe(""); + expect((parent.el as any).innerHTML).toBe(""); expect(steps).toEqual(["mounted", "patched"]); }); test("portal with only text as content", async () => { - const consoleError = console.error; - console.error = jest.fn(() => {}); - class Parent extends Component { static components = { Portal }; static template = xml` -
    - - - -
    `; +
    + + + +
    `; } - const parent = new Parent(); - let error; - try { - await parent.mount(fixture); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.message).toBe("Portal must have exactly one non-text child (has 0)"); - expect(console.error).toBeCalledTimes(0); - expect(fixture.innerHTML).toBe(`
    `); - console.error = consoleError; + addOutsideDiv(fixture); + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe('
    only text
    '); }); test("portal with no content", async () => { - const consoleError = console.error; - console.error = jest.fn(() => {}); - class Parent extends Component { static components = { Portal }; static template = xml` -
    - - - -
    `; +
    + + + +
    `; } - const parent = new Parent(); - let error; - try { - await parent.mount(fixture); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.message).toBe("Portal must have exactly one non-text child (has 0)"); - expect(console.error).toBeCalledTimes(0); - expect(fixture.innerHTML).toBe(`
    `); - console.error = consoleError; + addOutsideDiv(fixture); + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe(`
    `); }); test("portal with many children", async () => { - const consoleError = console.error; - console.error = jest.fn(() => {}); - class Parent extends Component { static components = { Portal }; static template = xml` -
    - -
    1
    -

    2

    -
    -
    `; +
    + +
    1
    +

    2

    +
    +
    `; } - const parent = new Parent(); - let error; - try { - await parent.mount(fixture); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.message).toBe("Portal must have exactly one non-text child (has 2)"); - expect(console.error).toBeCalledTimes(0); - expect(fixture.innerHTML).toBe(`
    `); - console.error = consoleError; + addOutsideDiv(fixture); + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe('
    1

    2

    '); }); test("portal with dynamic body", async () => { class Parent extends Component { static components = { Portal }; static template = xml` -
    - - -
    - -
    `; +
    + + +
    + +
    `; state = useState({ val: "ab" }); } - const parent = new Parent(); - await parent.mount(fixture); + const outside = addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); expect(outside.innerHTML).toBe(`ab`); @@ -402,38 +286,24 @@ describe("Portal: Basic use and DOM placement", () => { }); test("portal could have dynamically no content", async () => { - const consoleError = console.error; - console.error = jest.fn(() => {}); - class Parent extends Component { static components = { Portal }; static template = xml` -
    - - - -
    `; - state = { val: "ab" }; +
    + + + +
    `; + state = useState({ val: "ab" }); } - const parent = new Parent(); - await parent.mount(fixture); + const outside = addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); expect(outside.innerHTML).toBe(`ab`); - let error; - try { - parent.state.val = ""; - await parent.render(); - } catch (e) { - error = e; - } + parent.state.val = ""; + await nextTick(); expect(outside.innerHTML).toBe(``); - - expect(error).toBeDefined(); - expect(error.message).toBe("Portal must have exactly one non-text child (has 0)"); - - expect(console.error).toBeCalledTimes(0); - console.error = consoleError; }); test("lifecycle hooks of portal sub component are properly called", async () => { @@ -441,43 +311,33 @@ describe("Portal: Basic use and DOM placement", () => { class Child extends Component { static template = xml``; - mounted() { - steps.push("child:mounted"); - } - willPatch() { - steps.push("child:willPatch"); - } - patched() { - steps.push("child:patched"); - } - willUnmount() { - steps.push("child:willUnmount"); + setup() { + onMounted(() => steps.push("child:mounted")); + onWillPatch(() => steps.push("child:willPatch")); + onPatched(() => steps.push("child:patched")); + onWillUnmount(() => steps.push("child:willUnmount")); } } + class Parent extends Component { static components = { Portal, Child }; static template = xml` -
    - - - -
    `; +
    + + + +
    `; state = useState({ hasChild: false, val: 1 }); - mounted() { - steps.push("parent:mounted"); - } - willPatch() { - steps.push("parent:willPatch"); - } - patched() { - steps.push("parent:patched"); - } - willUnmount() { - steps.push("parent:willUnmount"); + setup() { + onMounted(() => steps.push("parent:mounted")); + onWillPatch(() => steps.push("parent:willPatch")); + onPatched(() => steps.push("parent:patched")); + onWillUnmount(() => steps.push("parent:willUnmount")); } } - const parent = new Parent(); - await parent.mount(fixture); + + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); expect(steps).toEqual(["parent:mounted"]); parent.state.hasChild = true; @@ -519,303 +379,304 @@ describe("Portal: Basic use and DOM placement", () => { ]); }); - test("portal destroys on crash", async () => { - class Child extends Component { - static template = xml``; - state = {}; - } - class Parent extends Component { - static components = { Portal, Child }; - static template = xml` -
    - - - -
    `; - state = { error: false }; - } + // test.skip("portal destroys on crash", async () => { + // class Child extends Component { + // static template = xml``; + // state = {}; + // } + // class Parent extends Component { + // static components = { Portal, Child }; + // static template = xml` + //
    + // + // + // + //
    `; + // state = { error: false }; + // } - const parent = new Parent(); - await parent.mount(fixture); - parent.state.error = true; + // const parent = new Parent(); + // await parent.mount(fixture); + // parent.state.error = true; - let error; - try { - await parent.render(); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - const regexp = - /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g; - expect(error.message).toMatch(regexp); - }); + // let error; + // try { + // await parent.render(); + // } catch (e) { + // error = e; + // } + // expect(error).toBeDefined(); + // const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g + // expect(error.message).toMatch(regexp); + // }); - test("portal manual unmount", async () => { - class Parent extends Component { - static components = { Portal }; - static template = xml` -
    - - gloria - -
    `; - } + // test.skip("portal manual unmount", async () => { + // class Parent extends Component { + // static components = { Portal }; + // static template = xml` + //
    + // + // gloria + // + //
    `; + // } - const parent = new Parent(); - await parent.mount(fixture); + // const parent = new Parent(); + // await parent.mount(fixture); - expect(outside.innerHTML).toBe("gloria"); - expect(parent.el!.innerHTML).toBe(""); + // expect(outside.innerHTML).toBe("gloria"); + // expect(parent.el!.innerHTML).toBe(""); - parent.unmount(); - expect(outside.innerHTML).toBe(""); - expect(parent.el!.innerHTML).toBe("gloria"); + // parent.unmount(); + // expect(outside.innerHTML).toBe(""); + // expect(parent.el!.innerHTML).toBe("gloria"); - await parent.mount(fixture); - expect(outside.innerHTML).toBe("gloria"); - expect(parent.el!.innerHTML).toBe(""); - }); + // await parent.mount(fixture); + // expect(outside.innerHTML).toBe("gloria"); + // expect(parent.el!.innerHTML).toBe(""); + // }); - test("portal manual unmount with subcomponent", async () => { - expect.assertions(9); - class Child extends Component { - static template = xml`gloria`; - mounted() { - expect(outside.contains(this.el)).toBeTruthy(); - } - willUnmount() { - expect(outside.contains(this.el)).toBeTruthy(); - } - } - class Parent extends Component { - static components = { Portal, Child }; - static template = xml` -
    - - - -
    `; - } + // test.skip("portal manual unmount with subcomponent", async () => { + // expect.assertions(9); + // class Child extends Component { + // static template = xml`gloria`; + // mounted() { + // expect(outside.contains(this.el)).toBeTruthy(); + // } + // willUnmount() { + // expect(outside.contains(this.el)).toBeTruthy(); + // } + // } + // class Parent extends Component { + // static components = { Portal, Child }; + // static template = xml` + //
    + // + // + // + //
    `; + // } - const parent = new Parent(); - await parent.mount(fixture); + // const parent = new Parent(); + // await parent.mount(fixture); - expect(outside.innerHTML).toBe("gloria"); - expect(parent.el!.innerHTML).toBe(""); + // expect(outside.innerHTML).toBe("gloria"); + // expect(parent.el!.innerHTML).toBe(""); - parent.unmount(); - expect(outside.innerHTML).toBe(""); - expect(parent.el!.innerHTML).toBe("gloria"); + // parent.unmount(); + // expect(outside.innerHTML).toBe(""); + // expect(parent.el!.innerHTML).toBe("gloria"); - await parent.mount(fixture); - expect(outside.innerHTML).toBe("gloria"); - expect(parent.el!.innerHTML).toBe(""); - }); -}); + // await parent.mount(fixture); + // expect(outside.innerHTML).toBe("gloria"); + // expect(parent.el!.innerHTML).toBe(""); + // }); + // }); -describe("Portal: Events handling", () => { - test("events triggered on movable pure node are handled", async () => { - class Parent extends Component { - static components = { Portal }; - static template = xml` -
    - - - -
    `; - state = useState({ val: "ab" }); + // describe("Portal: Events handling", () => { + // test.skip("events triggered on movable pure node are handled", async () => { + // class Parent extends Component { + // static components = { Portal }; + // static template = xml` + //
    + // + // + // + //
    `; + // state = useState({ val: "ab" }); - _onCustom() { - this.state.val = "triggered"; - } - } - const parent = new Parent(); - await parent.mount(fixture); + // _onCustom() { + // this.state.val = "triggered"; + // } + // } + // const parent = new Parent(); + // await parent.mount(fixture); - expect(outside.innerHTML).toBe(`ab`); - outside.querySelector("#trigger-me")!.dispatchEvent(new Event("custom")); - await nextTick(); - expect(outside.innerHTML).toBe(`triggered`); - }); + // expect(outside.innerHTML).toBe(`ab`); + // outside.querySelector("#trigger-me")!.dispatchEvent(new Event("custom")); + // await nextTick(); + // expect(outside.innerHTML).toBe(`triggered`); + // }); - test("events triggered on movable owl components are redirected", async () => { - let childInst: Component | null = null; - class Child extends Component { - static template = xml` - `; + // test.skip("events triggered on movable owl components are redirected", async () => { + // let childInst: Component | null = null; + // class Child extends Component { + // static template = xml` + // `; - constructor(parent, props) { - super(parent, props); - childInst = this; - } + // constructor(parent, props) { + // super(parent, props); + // childInst = this; + // } - _onCustom() { - this.trigger("custom-portal"); - } - } - class Parent extends Component { - static components = { Portal, Child }; - static template = xml` -
    - - - -
    `; - state = useState({ val: "ab" }); + // _onCustom() { + // this.trigger("custom-portal"); + // } + // } + // class Parent extends Component { + // static components = { Portal, Child }; + // static template = xml` + //
    + // + // + // + //
    `; + // state = useState({ val: "ab" }); - _onCustomPortal() { - this.state.val = "triggered"; - } - } - const parent = new Parent(); - await parent.mount(fixture); + // _onCustomPortal() { + // this.state.val = "triggered"; + // } + // } + // const parent = new Parent(); + // await parent.mount(fixture); - expect(outside.innerHTML).toBe(`ab`); - childInst!.trigger("custom"); - await nextTick(); - expect(outside.innerHTML).toBe(`triggered`); - }); + // expect(outside.innerHTML).toBe(`ab`); + // childInst!.trigger("custom"); + // await nextTick(); + // expect(outside.innerHTML).toBe(`triggered`); + // }); - test("events triggered on contained movable owl components are redirected", async () => { - const steps: string[] = []; - let childInst: Component | null = null; - class Child extends Component { - static template = xml` - `; + // test.skip("events triggered on contained movable owl components are redirected", async () => { + // const steps: string[] = []; + // let childInst: Component | null = null; + // class Child extends Component { + // static template = xml` + // `; - constructor(parent, props) { - super(parent, props); - childInst = this; - } + // constructor(parent, props) { + // super(parent, props); + // childInst = this; + // } - _onCustom() { - this.trigger("custom-portal"); - } - } - class Parent extends Component { - static components = { Portal, Child }; - static template = xml` -
    - -
    - -
    -
    -
    `; + // _onCustom() { + // this.trigger("custom-portal"); + // } + // } + // class Parent extends Component { + // static components = { Portal, Child }; + // static template = xml` + //
    + // + //
    + // + //
    + //
    + //
    `; - _handled(ev) { - steps.push(ev.type); - } - } - const parent = new Parent(); - await parent.mount(fixture); + // _handled(ev) { + // steps.push(ev.type); + // } + // } + // const parent = new Parent(); + // await parent.mount(fixture); - childInst!.trigger("custom"); - await nextTick(); + // childInst!.trigger("custom"); + // await nextTick(); - // This is expected because trigger is synchronous - expect(steps).toMatchObject(["custom-portal", "custom"]); - }); + // // This is expected because trigger is synchronous + // expect(steps).toMatchObject(["custom-portal", "custom"]); + // }); - test("Dom events are not mapped", async () => { - let childInst: Component | null = null; - const steps: string[] = []; - class Child extends Component { - static template = xml` - `; + // test.skip("Dom events are not mapped", async () => { + // let childInst: Component | null = null; + // const steps: string[] = []; + // class Child extends Component { + // static template = xml` + // `; - constructor(parent, props) { - super(parent, props); - childInst = this; - } - } - class Parent extends Component { - static components = { Portal, Child }; - static template = xml` -
    - - - -
    `; + // constructor(parent, props) { + // super(parent, props); + // childInst = this; + // } + // } + // class Parent extends Component { + // static components = { Portal, Child }; + // static template = xml` + //
    + // + // + // + //
    `; - _handled(ev) { - steps.push(ev.type as string); - } - } - const bodyListener = (ev) => { - steps.push(`body: ${ev.type}`); - }; - document.body.addEventListener("click", bodyListener); + // _handled(ev) { + // steps.push(ev.type as string); + // } + // } + // const bodyListener = (ev) => { + // steps.push(`body: ${ev.type}`); + // }; + // document.body.addEventListener("click", bodyListener); - const parent = new Parent(); - await parent.mount(fixture); - childInst!.el!.click(); + // const parent = new Parent(); + // await parent.mount(fixture); + // childInst!.el!.click(); - expect(steps).toEqual(["body: click"]); - document.body.removeEventListener("click", bodyListener); - }); + // expect(steps).toEqual(["body: click"]); + // document.body.removeEventListener("click", bodyListener); + // }); - test("Nested portals event propagation", async () => { - const outside2 = document.createElement("div"); - outside2.setAttribute("id", "outside2"); - fixture.appendChild(outside2); + // test.skip("Nested portals event propagation", async () => { + // const outside2 = document.createElement("div"); + // outside2.setAttribute("id", "outside2"); + // fixture.appendChild(outside2); - const steps: Array = []; - let childInst: Component | null = null; - class Child2 extends Component { - static template = xml`
    child2
    `; - constructor(parent, props) { - super(parent, props); - childInst = this; - } - } - class Child extends Component { - static components = { Portal, Child2 }; - static template = xml` - - - `; - } - class Parent extends Component { - static components = { Portal, Child }; - static template = xml` -
    - - - -
    `; + // const steps: Array = []; + // let childInst: Component | null = null; + // class Child2 extends Component { + // static template = xml`
    child2
    `; + // constructor(parent, props) { + // super(parent, props); + // childInst = this; + // } + // } + // class Child extends Component { + // static components = { Portal, Child2 }; + // static template = xml` + // + // + // `; + // } + // class Parent extends Component { + // static components = { Portal, Child }; + // static template = xml` + //
    + // + // + // + //
    `; - _handled(ev) { - steps.push(`${ev.type} from ${ev.originalComponent.constructor.name}`); - } - } + // _handled(ev) { + // steps.push(`${ev.type} from ${ev.originalComponent.constructor.name}`); + // } + // } - const parent = new Parent(); - await parent.mount(fixture); + // const parent = new Parent(); + // await parent.mount(fixture); - childInst!.trigger("custom"); - expect(steps).toEqual(["custom from Child2"]); - }); + // childInst!.trigger("custom"); + // expect(steps).toEqual(["custom from Child2"]); + // }); test("portal's parent's env is not polluted", async () => { class Child extends Component { static template = xml` - `; + `; } class Parent extends Component { static components = { Portal, Child }; static template = xml` -
    - - - -
    `; +
    + + + +
    `; } - const parent = new Parent(); - const parentEnv = Object.assign({}, parent.env); - await parent.mount(fixture); - expect(parentEnv).toStrictEqual(parent.env); + const env = {}; + const app = new App(Parent); + app.configure({ env }); + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); + expect(parent.env).toStrictEqual({}); }); test("Portal composed with t-slot", async () => { @@ -823,36 +684,35 @@ describe("Portal: Events handling", () => { let childInst: Component | null = null; class Child2 extends Component { static template = xml`
    child2
    `; - constructor(parent, props) { - super(parent, props); + setup() { childInst = this; } } class Child extends Component { static components = { Portal, Child2 }; static template = xml` - - - `; + + + `; } class Parent extends Component { static components = { Child, Child2 }; static template = xml` -
    - - - -
    `; +
    + + + +
    `; - _handled(ev) { + _handled(ev: Event) { steps.push(ev.type as string); } } - const parent = new Parent(); - await parent.mount(fixture); + addOutsideDiv(fixture); + await mount(Parent, fixture); - childInst!.trigger("custom"); + childInst!.el!.dispatchEvent(new CustomEvent("custom")); expect(steps).toEqual(["custom"]); }); }); @@ -861,20 +721,20 @@ describe("Portal: UI/UX", () => { test("focus is kept across re-renders", async () => { class Child extends Component { static template = xml` - `; + `; } class Parent extends Component { static components = { Portal, Child }; static template = xml` -
    - - - -
    `; +
    + + + +
    `; state = useState({ val: "ab" }); } - const parent = new Parent(); - await parent.mount(fixture); + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); const input = document.querySelector("#target-me"); expect(input!.nodeName).toBe("INPUT"); expect((input as HTMLInputElement).placeholder).toBe("ab"); diff --git a/tests/mocks/mockEventTarget.js b/tests/mocks/mockEventTarget.js new file mode 100644 index 00000000..e2372c40 --- /dev/null +++ b/tests/mocks/mockEventTarget.js @@ -0,0 +1,42 @@ +// mockEventTarget.js + +// copy the code from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget#Simple_implementation_of_EventTarget +var EventTarget = function() { + this.listeners = {}; + }; + + EventTarget.prototype.listeners = null; + EventTarget.prototype.addEventListener = function(type, callback) { + if (!(type in this.listeners)) { + this.listeners[type] = []; + } + this.listeners[type].push(callback); + }; + + EventTarget.prototype.removeEventListener = function(type, callback) { + if (!(type in this.listeners)) { + return; + } + var stack = this.listeners[type]; + for (var i = 0, l = stack.length; i < l; i++) { + if (stack[i] === callback){ + stack.splice(i, 1); + return; + } + } + }; + + EventTarget.prototype.dispatchEvent = function(event) { + if (!(event.type in this.listeners)) { + return true; + } + var stack = this.listeners[event.type].slice(); + + for (var i = 0, l = stack.length; i < l; i++) { + stack[i].call(this, event); + } + return !event.defaultPrevented; + }; + + // make the EventTarget global + global.EventTarget = EventTarget \ No newline at end of file diff --git a/tests/qweb/__snapshots__/attributes.test.ts.snap b/tests/qweb/__snapshots__/attributes.test.ts.snap new file mode 100644 index 00000000..3fbf6bdb --- /dev/null +++ b/tests/qweb/__snapshots__/attributes.test.ts.snap @@ -0,0 +1,679 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`attributes changing a class with t-att-class (preexisting class 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['v']; + return block1([d1]); + } +}" +`; + +exports[`attributes changing a class with t-att-class 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['v']; + return block1([d1]); + } +}" +`; + +exports[`attributes changing an attribute with t-att- 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['v']; + return block1([d1]); + } +}" +`; + +exports[`attributes class and t-att-class should combine together 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`attributes class and t-attf-class with ternary operation 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = (ctx['value']?'world':''); + return block1([d1]); + } +}" +`; + +exports[`attributes dynamic attribute evaluating to 0 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`attributes dynamic attribute falsy variable 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`attributes dynamic attribute with a dash 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['id']; + return block1([d1]); + } +}" +`; + +exports[`attributes dynamic attributes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = 'bar'; + return block1([d1]); + } +}" +`; + +exports[`attributes dynamic class attribute 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['c']; + return block1([d1]); + } +}" +`; + +exports[`attributes dynamic class attribute evaluating to 0 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`attributes dynamic empty class attribute 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['c']; + return block1([d1]); + } +}" +`; + +exports[`attributes dynamic formatted attributes with a dash 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = \`Some text \${ctx['id']}\`; + return block1([d1]); + } +}" +`; + +exports[`attributes fixed variable 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`attributes format expression 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = (ctx['value']+37); + return block1([d1]); + } +}" +`; + +exports[`attributes format literal 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = \`bar\`; + return block1([d1]); + } +}" +`; + +exports[`attributes format multiple 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = \`a \${ctx['value1']} is \${ctx['value2']} of \${ctx['value3']} ]\`; + return block1([d1]); + } +}" +`; + +exports[`attributes format value 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = \`b\${ctx['value']}r\`; + return block1([d1]); + } +}" +`; + +exports[`attributes from object variables set previously 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`o\`] = {a:'b'}; + let d1 = ctx['o'].a; + return block1([d1]); + } +}" +`; + +exports[`attributes from variables set previously (no external node) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`abc\`] = 'def'; + let d1 = ctx['abc']; + return block1([d1]); + } +}" +`; + +exports[`attributes from variables set previously 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`abc\`] = 'def'; + let d1 = ctx['abc']; + return block1([d1]); + } +}" +`; + +exports[`attributes object 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`attributes static attributes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`attributes static attributes on void elements 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\\"Test\\"/\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`attributes static attributes with dashes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`attributes t-att-class and class should combine together 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`attributes t-att-class with multiple classes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = {'a b c':ctx['value']}; + return block1([d1]); + } +}" +`; + +exports[`attributes t-att-class with multiple classes 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = {['a b c']:ctx['value']}; + return block1([d1]); + } +}" +`; + +exports[`attributes t-att-class with multiple classes, some of which are duplicate 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = {'a b c':ctx['value'],'a b d':!ctx['value']}; + return block1([d1]); + } +}" +`; + +exports[`attributes t-att-class with object 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = {a:ctx['b'],c:ctx['d'],e:ctx['f']}; + return block1([d1]); + } +}" +`; + +exports[`attributes t-attf-class 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = \`hello\`; + return block1([d1]); + } +}" +`; + +exports[`attributes t-attf-class should combine with class 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = \`world\`; + return block1([d1]); + } +}" +`; + +exports[`attributes t-attf-class with multiple classes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = \`hello \${ctx['word']}\`; + return block1([d1]); + } +}" +`; + +exports[`attributes t-attf-class with multiple classes separated by multiple spaces 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = \`hello \${ctx['word']}\`; + return block1([d1]); + } +}" +`; + +exports[`attributes tuple literal 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ['foo','bar']; + return block1([d1]); + } +}" +`; + +exports[`attributes tuple variable 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`attributes two classes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`attributes two dynamic attributes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = 'bar'; + let d2 = 'foo'; + return block1([d1, d2]); + } +}" +`; + +exports[`attributes updating classes (with obj notation) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = {'a b':ctx['condition']}; + return block1([d1]); + } +}" +`; + +exports[`attributes various escapes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['bar']; + let d2 = \`<\${ctx['baz']}>\`; + let d3 = ctx['qux']; + return block1([d1, d2, d3]); + } +}" +`; + +exports[`attributes various escapes 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    <
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`special cases for some specific html attributes/properties input of type checkbox with t-att-indeterminate 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['v']; + return block1([d1]); + } +}" +`; + +exports[`special cases for some specific html attributes/properties input type= checkbox, with t-att-checked 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['flag']; + return block1([d1]); + } +}" +`; + +exports[`special cases for some specific html attributes/properties input with t-att-value 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['v']; + return block1([d1]); + } +}" +`; + +exports[`special cases for some specific html attributes/properties various boolean html attributes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; diff --git a/tests/qweb/__snapshots__/comments.test.ts.snap b/tests/qweb/__snapshots__/comments.test.ts.snap new file mode 100644 index 00000000..3e42785c --- /dev/null +++ b/tests/qweb/__snapshots__/comments.test.ts.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`comments only a comment 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`comments properly handle comments 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    hello owl
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`comments properly handle comments between t-if/t-else 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`true\`); + let block3 = createBlock(\`owl\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (true) { + b2 = block2(); + } else { + b3 = block3(); + } + return block1([], [b2, b3]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/event_handling.test.ts.snap b/tests/qweb/__snapshots__/event_handling.test.ts.snap new file mode 100644 index 00000000..97f01bd7 --- /dev/null +++ b/tests/qweb/__snapshots__/event_handling.test.ts.snap @@ -0,0 +1,369 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-on can bind event handler 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'add']; + return block1([d1]); + } +}" +`; + +exports[`t-on can bind handlers with arguments 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const arg1 = [5]; + let d1 = [ctx, 'add', arg1]; + return block1([d1]); + } +}" +`; + +exports[`t-on can bind handlers with empty object 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const arg1 = [{}]; + let d1 = [ctx, 'doSomething', arg1]; + return block1([d1]); + } +}" +`; + +exports[`t-on can bind handlers with empty object (with non empty inner string) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const arg1 = [{}]; + let d1 = [ctx, 'doSomething', arg1]; + return block1([d1]); + } +}" +`; + +exports[`t-on can bind handlers with empty object (with non empty inner string) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`
  • link
  • \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(['someval']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`action\`] = v2[i1]; + ctx[\`action_index\`] = i1; + let key1 = ctx['action_index']; + const arg1 = [ctx['action']]; + let d1 = [ctx, 'activate', arg1]; + c2[i1] = withKey(block3([d1]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-on can bind handlers with object arguments 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const arg1 = [{val:5}]; + let d1 = [ctx, 'add', arg1]; + return block1([d1]); + } +}" +`; + +exports[`t-on can bind two event handlers 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'handleClick']; + let d2 = [ctx, 'handleDblClick']; + return block1([d1, d2]); + } +}" +`; + +exports[`t-on handler is bound to proper owner 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'add']; + return block1([d1]); + } +}" +`; + +exports[`t-on handler is bound to proper owner, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k1, v1, l1, c1] = prepareList([1]); + for (let i1 = 0; i1 < l1; i1++) { + ctx[\`value\`] = v1[i1]; + let key1 = ctx['value']; + let d1 = [ctx, 'add']; + c1[i1] = withKey(block2([d1]), key1); + } + return list(c1); + } +}" +`; + +exports[`t-on handler is bound to proper owner, part 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'add']; + return block1([d1]); + } +}" +`; + +exports[`t-on handler is bound to proper owner, part 3 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + return function template(ctx, node, key = \\"\\") { + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-on handler is bound to proper owner, part 4 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'add']; + return block1([d1]); + } +}" +`; + +exports[`t-on handler is bound to proper owner, part 4 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k1, v1, l1, c1] = prepareList([1]); + for (let i1 = 0; i1 < l1; i1++) { + ctx[\`value\`] = v1[i1]; + ctx[\`value_first\`] = i1 === 0; + ctx[\`value_last\`] = i1 === v1.length - 1; + ctx[\`value_index\`] = i1; + ctx[\`value_value\`] = k1[i1]; + let key1 = ctx['value']; + c1[i1] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}\`), key1); + } + return list(c1); + } +}" +`; + +exports[`t-on receive event in first argument 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'add']; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on with inline statement (function call) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const v1 = ctx['state']; + let d1 = (e) => {const res = (() => { return v1.incrementCounter(2) })(); if (typeof res === 'function') { res(e) }}; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on with inline statement 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const v1 = ctx['state']; + let d1 = (e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on with inline statement, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const v1 = ctx['state']; + let d1 = (e) => {const res = (() => { return v1.flag=!v1.flag })(); if (typeof res === 'function') { res(e) }}; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on with inline statement, part 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const v1 = ctx['state']; + const v2 = ctx['someFunction']; + let d1 = (e) => {const res = (() => { return v1.n=v2(3) })(); if (typeof res === 'function') { res(e) }}; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on with t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    lucas

    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'update']; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on with t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-on t-on, with arguments and t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    lucas

    \`); + + return function template(ctx, node, key = \\"\\") { + const arg1 = [ctx['value']]; + let d1 = [ctx, 'update', arg1]; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on, with arguments and t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/misc.test.ts.snap b/tests/qweb/__snapshots__/misc.test.ts.snap new file mode 100644 index 00000000..f9c3efae --- /dev/null +++ b/tests/qweb/__snapshots__/misc.test.ts.snap @@ -0,0 +1,324 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`misc complex template 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`\`); + let block3 = createBlock(\` preparing\`); + let block7 = createBlock(\`
    \`); + let block9 = createBlock(\`
    \`); + let block10 = createBlock(\`\`); + let block11 = createBlock(\`\`); + let block12 = createBlock(\`\`); + let block13 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3,b4,b6,b8; + let d1 = \`batch_tile \${ctx['options'].more?'more':'nomore'}\`; + let d2 = \`card bg-\${ctx['klass']}-light\`; + let d3 = \`/runbot/batch/\${ctx['batch'].id}\`; + let d4 = \`badge badge-\${ctx['batch'].has_warning?'warning':'light'}\`; + let d5 = ctx['batch'].formated_age; + if (ctx['batch'].has_warning) { + b2 = block2(); + } + if (ctx['batch'].state=='preparing') { + b3 = block3(); + } + ctx = Object.create(ctx); + const [k4, v4, l4, c4] = prepareList(ctx['batch'].slot_ids.filter(slot=>slot.build_id.id&&!slot.trigger_id.manual&&(ctx['options'].trigger_display[slot.trigger_id.id]))); + for (let i1 = 0; i1 < l4; i1++) { + ctx[\`slot\`] = v4[i1]; + let key1 = ctx['slot'].id; + c4[i1] = withKey(component(\`SlotButton\`, {class: ctx['slot_container'],slot: ctx['slot']}, key + \`__1__\${key1}\`, node, ctx), key1); + } + ctx = ctx.__proto__; + b4 = list(c4); + ctx = Object.create(ctx); + const [k6, v6, l6, c6] = prepareList([1,2,3,4]); + for (let i1 = 0; i1 < l6; i1++) { + ctx[\`x\`] = v6[i1]; + let key1 = ctx['x']; + c6[i1] = withKey(block7(), key1); + } + ctx = ctx.__proto__; + b6 = list(c6); + ctx = Object.create(ctx); + const [k8, v8, l8, c8] = prepareList(ctx['commit_links']); + for (let i1 = 0; i1 < l8; i1++) { + ctx[\`commit_link\`] = v8[i1]; + let key1 = ctx['commit_link'].id; + let b10,b11,b12,b13; + let d6 = \`/runbot/commit/\${ctx['commit_link'].commit_id}\`; + let d7 = \`badge badge-light batch_commit match_type_\${ctx['commit_link'].match_type}\`; + if (ctx['commit_link'].match_type=='new') { + b10 = block10(); + } + if (ctx['commit_link'].match_type=='head') { + b11 = block11(); + } + if (ctx['commit_link'].match_type=='base_match') { + b12 = block12(); + } + if (ctx['commit_link'].match_type=='base_head') { + b13 = block13(); + } + let d8 = ctx['commit_link'].commit_dname; + let d9 = 'https://%s/commit/%s'%(ctx['commit_link'].commit_remote_url,ctx['commit_link'].commit_name); + let d10 = ctx['commit_link'].commit_subject; + c8[i1] = withKey(block9([d6, d7, d8, d9, d10], [b10, b11, b12, b13]), key1); + } + b8 = list(c8); + return block1([d1, d2, d3, d4, d5], [b2, b3, b4, b6, b8]); + } +}" +`; + +exports[`misc global 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = 'agüero'; + let b2 = html(ctx[zero]); + return block1([d1], [b2]); + } +}" +`; + +exports[`misc global 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = withDefault(ctx['foo'], \`foo default\`); + return block1([d1]); + } +}" +`; + +exports[`misc global 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b3 = text(\`toto default\`); + let b2 = html(withDefault(ctx['toto'], b3)); + return block1([], [b2]); + } +}" +`; + +exports[`misc global 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`_callee-uses-foo\`); + const callTemplate_4 = getTemplate(\`_callee-uses-foo\`); + const callTemplate_6 = getTemplate(\`_callee-uses-foo\`); + const callTemplate_8 = getTemplate(\`_callee-asc\`); + const callTemplate_10 = getTemplate(\`_callee-asc-toto\`); + + let block1 = createBlock(\`
    \`); + let block4 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList([4,5,6]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`value\`] = v2[i1]; + ctx[\`value_first\`] = i1 === 0; + ctx[\`value_last\`] = i1 === v2.length - 1; + ctx[\`value_index\`] = i1; + ctx[\`value_value\`] = k2[i1]; + let key1 = ctx['value']; + let d1 = ctx['value']; + let b4 = block4([d1]); + ctx = Object.create(ctx); + ctx = Object.create(ctx); + ctx[\`foo\`] = 'aaa'; + let b6 = callTemplate_2(ctx, node, key + \`__1__\${key1}\`); + ctx = ctx.__proto__; + let b7 = callTemplate_4(ctx, node, key + \`__3__\${key1}\`); + ctx[\`foo\`] = 'bbb'; + let b8 = callTemplate_6(ctx, node, key + \`__5__\${key1}\`); + let b5 = multi([b6, b7, b8]); + ctx[zero] = b5; + let b9 = callTemplate_8(ctx, node, key + \`__7__\${key1}\`); + ctx = ctx.__proto__; + c2[i1] = withKey(multi([b4, b9]), key1); + } + ctx = ctx.__proto__; + let b2 = list(c2); + let b10 = callTemplate_10(ctx, node, key + \`__9\`); + return block1([], [b2, b10]); + } +}" +`; + +exports[`misc other complex template 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_3 = getTemplate(\`LOAD_INFOS_TEMPLATE\`); + + let block1 = createBlock(\`
    Triggers
    \`); + let block3 = createBlock(\`
  • \`); + let block5 = createBlock(\`
  • Login
  • \`); + let block8 = createBlock(\`
  • \`); + let block9 = createBlock(\`
  • \`); + let block11 = createBlock(\`
  • \`); + let block12 = createBlock(\`
  • \`); + let block13 = createBlock(\`
  • \`); + let block14 = createBlock(\`\`); + let block16 = createBlock(\`\`); + let block20 = createBlock(\`
    \`); + let block21 = createBlock(\`
    \`); + let block23 = createBlock(\`
    \`); + let block24 = createBlock(\`

    No project

    \`); + let block25 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + const refs = ctx.__owl__.refs; + let b2,b4,b14,b17,b22,b23,b24,b25; + let d1 = \`/runbot/\${ctx['project'].slug}\`; + let d2 = ctx['project'].name; + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['projects']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`project\`] = v2[i1]; + let key1 = ctx['project'].id; + const arg1 = [ctx['project']]; + let d3 = [ctx, 'selectProject', arg1]; + let d4 = ctx['project'].name; + c2[i1] = withKey(block3([d3, d4]), key1); + } + ctx = ctx.__proto__; + b2 = list(c2); + if (ctx['user']) { + let b5,b6; + if (ctx['user'].public) { + let d5 = \`/web/login?redirect=/\`; + b5 = block5([d5]); + } else { + let b7,b10,b13; + if (ctx['nb_assigned_errors']&&ctx['nb_assigned_errors']>0) { + let d6 = \`You have \${ctx['nb_assigned_errors']} random bug assigned\`; + let d7 = ctx['nb_assigned_errors']; + let b8 = block8([d6, d7]); + let b9 = block9(); + b7 = multi([b8, b9]); + } else if (ctx['nb_build_errors']&&ctx['nb_build_errors']>0) { + let b11 = block11(); + let b12 = block12(); + b10 = multi([b11, b12]); + } + let d8 = ctx['user'].name.length>25?ctx['user'].namesubstring(0,23)+'...':ctx['user'].name; + let d9 = \`/web/session/logout?redirect=/\`; + let d10 = \`/web\`; + b13 = block13([d8, d9, d10]); + b6 = multi([b7, b10, b13]); + } + b4 = multi([b5, b6]); + } + let d11 = [ctx, 'toggleSettingsMenu']; + let d12 = [ctx, 'toggleMore']; + if (ctx['categories']&&ctx['categories'].length>1) { + ctx = Object.create(ctx); + const [k15, v15, l15, c15] = prepareList(ctx['categories']); + for (let i1 = 0; i1 < l15; i1++) { + ctx[\`category\`] = v15[i1]; + let key1 = ctx['category'].id; + let d13 = ctx['category'].id; + let d14 = ctx['category'].id==ctx['options'].active_category_id; + let d15 = ctx['category'].name; + c15[i1] = withKey(block16([d13, d14, d15]), key1); + } + ctx = ctx.__proto__; + let b15 = list(c15); + b14 = block14([], [b15]); + } + let d16 = ctx['search'].value; + let d17 = [ctx, 'updateFilter']; + let d18 = [ctx, 'updateFilter']; + let d19 = (el) => refs[\`search_input\`] = el; + let d20 = [ctx, 'clearSearch']; + let d21 = (el) => refs[\`settings_menu\`] = el; + if (ctx['triggers']) { + ctx = Object.create(ctx); + const [k18, v18, l18, c18] = prepareList(ctx['triggers']); + for (let i1 = 0; i1 < l18; i1++) { + ctx[\`trigger\`] = v18[i1]; + let key1 = ctx['trigger'].id; + let b20; + if (!ctx['trigger'].manual&&ctx['trigger'].project_id===ctx['project'].id&&ctx['trigger'].category_id===ctx['options'].active_category_id) { + let d22 = \`trigger_\${ctx['trigger'].id}\`; + let d23 = \`trigger_\${ctx['trigger'].id}\`; + let d24 = ctx['options'].trigger_display[ctx['trigger'].id]; + let d25 = ctx['trigger'].id; + let d26 = [ctx, 'updateTriggerDisplay']; + let d27 = \`trigger_\${ctx['trigger'].id}\`; + let d28 = ctx['trigger'].name; + b20 = block20([d22, d23, d24, d25, d26, d27, d28]); + } + c18[i1] = withKey(multi([b20]), key1); + } + ctx = ctx.__proto__; + let b18 = list(c18); + let d29 = [ctx, 'triggerAll']; + let d30 = [ctx, 'triggerNone']; + let d31 = [ctx, 'triggerDefault']; + let d32 = [ctx, 'toggleSettingsMenu']; + let b21 = block21([d29, d30, d31, d32]); + b17 = multi([b18, b21]); + } + if (ctx['load_infos']) { + b22 = callTemplate_3(ctx, node, key + \`__2\`); + } + if (ctx['message']) { + let d33 = ctx['message']; + b23 = block23([d33]); + } + if (!ctx['project']) { + b24 = block24(); + } else { + let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__4\`, node, ctx); + let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__5\`, node, ctx); + b25 = block25([], [b26, b27]); + } + return block1([d1, d2, d11, d12, d16, d17, d18, d19, d20, d21], [b2, b4, b14, b17, b22, b23, b24, b25]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap deleted file mode 100644 index a783d931..00000000 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ /dev/null @@ -1,4137 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`attributes class and t-att-class should combine together 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _1 = utils.toClassObj(scope['value']); - Object.assign(_1, {'hello':true}) - let c3 = [], p3 = {key:3,class:_1}; - let vn3 = h('div', p3, c3); - return vn3; -}" -`; - -exports[`attributes class and t-attf-class with ternary operation 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = 'hello ' + (scope['value']?'world':''); - let c2 = [], p2 = {key:2,attrs:{class: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes dynamic attribute falsy variable 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = scope['value']; - let c2 = [], p2 = {key:2,attrs:{foo: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes dynamic attribute with a dash 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = scope['id']; - let c2 = [], p2 = {key:2,attrs:{\\"data-action-id\\": _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes dynamic attributes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = 'bar'; - let c2 = [], p2 = {key:2,attrs:{foo: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes dynamic class attribute 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _1 = utils.toClassObj(scope['c']); - let c2 = [], p2 = {key:2,class:_1}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes dynamic empty class attribute 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _1 = utils.toClassObj(scope['c']); - let c2 = [], p2 = {key:2,class:_1}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes dynamic formatted attributes with a dash 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = \`Some text \${scope['id']}\`; - let c2 = [], p2 = {key:2,attrs:{\\"aria-label\\": _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes fixed variable 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = scope['value']; - let c2 = [], p2 = {key:2,attrs:{foo: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes format expression 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = (scope['value']+37); - let c2 = [], p2 = {key:2,attrs:{foo: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes format expression, other format 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = (scope['value']+37); - let c2 = [], p2 = {key:2,attrs:{foo: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes format literal 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _1 = \`bar\`; - let c2 = [], p2 = {key:2,attrs:{foo: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes format multiple 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = \`a \${scope['value1']} is \${scope['value2']} of \${scope['value3']} ]\`; - let c2 = [], p2 = {key:2,attrs:{foo: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes format value 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = \`b\${scope['value']}r\`; - let c2 = [], p2 = {key:2,attrs:{foo: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes from object variables set previously 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.o = {a:'b'}; - let _2 = utils.toClassObj(scope.o.a); - let c3 = [], p3 = {key:3,class:_2}; - let vn3 = h('span', p3, c3); - c1.push(vn3); - return vn1; -}" -`; - -exports[`attributes from variables set previously 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.abc = 'def'; - let _2 = utils.toClassObj(scope.abc); - let c3 = [], p3 = {key:3,class:_2}; - let vn3 = h('span', p3, c3); - c1.push(vn3); - return vn1; -}" -`; - -exports[`attributes object 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = scope['value']; - let c2 = [], p2 = {key:2,attrs:{}}; - if (_1 instanceof Array) { - p2.attrs[_1[0]] = _1[1]; - } else { - for (let key in _1) { - p2.attrs[key] = _1[key]; - } - } - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes static attributes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _1 = 'a'; - let _2 = 'b'; - let _3 = 'c'; - let c4 = [], p4 = {key:4,attrs:{foo: _1,bar: _2,baz: _3}}; - let vn4 = h('div', p4, c4); - return vn4; -}" -`; - -exports[`attributes static attributes on void elements 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _1 = '/test.jpg'; - let _2 = 'Test'; - let c3 = [], p3 = {key:3,attrs:{src: _1,alt: _2}}; - let vn3 = h('img', p3, c3); - return vn3; -}" -`; - -exports[`attributes static attributes with dashes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _1 = 'Close'; - let c2 = [], p2 = {key:2,attrs:{\\"aria-label\\": _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes t-att-class and class should combine together 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _2 = {'hello':true}; - Object.assign(_2, utils.toClassObj(scope['value'])) - let c3 = [], p3 = {key:3,class:_2}; - let vn3 = h('div', p3, c3); - return vn3; -}" -`; - -exports[`attributes t-att-class with object 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _2 = {'static':true}; - Object.assign(_2, utils.toClassObj({a:scope['b'],c:scope['d'],e:scope['f']})) - let c3 = [], p3 = {key:3,class:_2}; - let vn3 = h('div', p3, c3); - return vn3; -}" -`; - -exports[`attributes t-attf-class should combine with class 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _1 = 'hello ' + \`world\`; - let c2 = [], p2 = {key:2,attrs:{class: _1}}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes tuple literal 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = ['foo','bar']; - let c2 = [], p2 = {key:2,attrs:{}}; - if (_1 instanceof Array) { - p2.attrs[_1[0]] = _1[1]; - } else { - for (let key in _1) { - p2.attrs[key] = _1[key]; - } - } - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes tuple variable 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = scope['value']; - let c2 = [], p2 = {key:2,attrs:{}}; - if (_1 instanceof Array) { - p2.attrs[_1[0]] = _1[1]; - } else { - for (let key in _1) { - p2.attrs[key] = _1[key]; - } - } - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`attributes various escapes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = '\`; - let _4 = scope['qux']; - let c5 = [], p5 = {key:5,attrs:{foo: _1,bar: _2,baz: _3}}; - if (_4 instanceof Array) { - p5.attrs[_4[0]] = _4[1]; - } else { - for (let key in _4) { - p5.attrs[key] = _4[key]; - } - } - let vn5 = h('div', p5, c5); - return vn5; -}" -`; - -exports[`debugging t-debug 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - debugger; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (true) { - debugger; - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`hey\`}); - } - return vn1; -}" -`; - -exports[`debugging t-log 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.foo = 42; - console.log(scope.foo+3) - return vn1; -}" -`; - -exports[`foreach does not pollute the rendering context 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = [1]; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.item_first = i1 === 0 - scope.item_last = i1 === _length3 - 1 - scope.item_index = i1 - scope.item = _3[i1] - scope.item_value = _4[i1] - let key1 = i1; - let _6 = scope['item']; - if (_6 != null) { - c1.push({text: _6}); - } - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`foreach iterate on items (on a element node) 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = [1,2]; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.item_first = i1 === 0 - scope.item_last = i1 === _length3 - 1 - scope.item_index = i1 - scope.item = _3[i1] - scope.item_value = _4[i1] - let key1 = scope['item']; - let c6 = [], p6 = {key:\`\${key1}_6\`}; - let vn6 = h('span', p6, c6); - c1.push(vn6); - let _7 = scope['item']; - if (_7 != null) { - c6.push({text: _7}); - } - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`foreach iterate on items 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = [3,2,1]; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.item_first = i1 === 0 - scope.item_last = i1 === _length3 - 1 - scope.item_index = i1 - scope.item = _3[i1] - scope.item_value = _4[i1] - let key1 = i1; - c1.push({text: \` [\`}); - let _6 = scope['item_index']; - if (_6 != null) { - c1.push({text: _6}); - } - c1.push({text: \`: \`}); - let _7 = scope['item']; - if (_7 != null) { - c1.push({text: _7}); - } - c1.push({text: \` \`}); - let _8 = scope['item_value']; - if (_8 != null) { - c1.push({text: _8}); - } - c1.push({text: \`] \`}); - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`foreach iterate, dict param 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = scope['value']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.item_first = i1 === 0 - scope.item_last = i1 === _length3 - 1 - scope.item_index = i1 - scope.item = _3[i1] - scope.item_value = _4[i1] - let key1 = i1; - c1.push({text: \` [\`}); - let _6 = scope['item_index']; - if (_6 != null) { - c1.push({text: _6}); - } - c1.push({text: \`: \`}); - let _7 = scope['item']; - if (_7 != null) { - c1.push({text: _7}); - } - c1.push({text: \` \`}); - let _8 = scope['item_value']; - if (_8 != null) { - c1.push({text: _8}); - } - c1.push({text: \`] \`}); - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`foreach iterate, position 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = Array(5); - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.elem_first = i1 === 0 - scope.elem_last = i1 === _length3 - 1 - scope.elem_index = i1 - scope.elem = _3[i1] - scope.elem_value = _4[i1] - let key1 = i1; - c1.push({text: \` -\`}); - if (scope['elem_first']) { - c1.push({text: \` first\`}); - } - if (scope['elem_last']) { - c1.push({text: \` last\`}); - } - c1.push({text: \` (\`}); - let _6 = scope['elem_index']; - if (_6 != null) { - c1.push({text: _6}); - } - c1.push({text: \`) \`}); - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`foreach t-call with body in t-foreach in t-foreach 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = scope['numbers']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.a_first = i1 === 0 - scope.a_last = i1 === _length3 - 1 - scope.a_index = i1 - scope.a = _3[i1] - scope.a_value = _4[i1] - let key1 = i1; - let _6 = scope['letters']; - if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _6; - let _8 = _6; - if (!(_6 instanceof Array)) { - _7 = Object.keys(_6); - _8 = Object.values(_6); - } - let _length7 = _7.length; - let _origScope9 = scope; - scope = Object.create(scope); - for (let i2 = 0; i2 < _length7; i2++) { - scope.b_first = i2 === 0 - scope.b_last = i2 === _length7 - 1 - scope.b_index = i2 - scope.b = _7[i2] - scope.b_value = _8[i2] - let key2 = i2; - let _origScope14 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'c').c = 'x'+'_'+scope['a']+'_'+scope['b']; - scope[utils.zero] = c__0; - } - let k15 = \`__15__\${key1}__\${key2}__\`; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k15})); - } - scope = _origScope14; - } - scope = _origScope9; - let c16 = [], p16 = {key:\`\${key1}_16\`}; - let vn16 = h('span', p16, c16); - c1.push(vn16); - if (scope.c != null) { - c16.push({text: scope.c}); - } - } - scope = _origScope5; - let c17 = [], p17 = {key:17}; - let vn17 = h('span', p17, c17); - c1.push(vn17); - c17.push({text: \`[\`}); - let _18 = scope['a']; - if (_18 != null) { - c17.push({text: _18}); - } - c17.push({text: \`][\`}); - let _19 = scope['b']; - if (_19 != null) { - c17.push({text: _19}); - } - c17.push({text: \`][\`}); - if (scope.c != null) { - c17.push({text: scope.c}); - } - c17.push({text: \`]\`}); - return vn1; -}" -`; - -exports[`foreach t-call without body in t-foreach in t-foreach 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = scope['numbers']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.a_first = i1 === 0 - scope.a_last = i1 === _length3 - 1 - scope.a_index = i1 - scope.a = _3[i1] - scope.a_value = _4[i1] - let key1 = i1; - let _6 = scope['letters']; - if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _6; - let _8 = _6; - if (!(_6 instanceof Array)) { - _7 = Object.keys(_6); - _8 = Object.values(_6); - } - let _length7 = _7.length; - let _origScope9 = scope; - scope = Object.create(scope); - for (let i2 = 0; i2 < _length7; i2++) { - scope.b_first = i2 === 0 - scope.b_last = i2 === _length7 - 1 - scope.b_index = i2 - scope.b = _7[i2] - scope.b_value = _8[i2] - let key2 = i2; - let _origScope13 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - let k14 = \`__14__\${key1}__\${key2}__\`; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k14})); - scope = _origScope13; - } - scope = _origScope9; - let c15 = [], p15 = {key:\`\${key1}_15\`}; - let vn15 = h('span', p15, c15); - c1.push(vn15); - let _16 = scope['c']; - if (_16 != null) { - c15.push({text: _16}); - } - } - scope = _origScope5; - let c17 = [], p17 = {key:17}; - let vn17 = h('span', p17, c17); - c1.push(vn17); - c17.push({text: \`[\`}); - let _18 = scope['a']; - if (_18 != null) { - c17.push({text: _18}); - } - c17.push({text: \`][\`}); - let _19 = scope['b']; - if (_19 != null) { - c17.push({text: _19}); - } - c17.push({text: \`][\`}); - let _20 = scope['c']; - if (_20 != null) { - c17.push({text: _20}); - } - c17.push({text: \`]\`}); - return vn1; -}" -`; - -exports[`foreach t-foreach in t-forach 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = scope['numbers']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.number_first = i1 === 0 - scope.number_last = i1 === _length3 - 1 - scope.number_index = i1 - scope.number = _3[i1] - scope.number_value = _4[i1] - let key1 = i1; - let _6 = scope['letters']; - if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _6; - let _8 = _6; - if (!(_6 instanceof Array)) { - _7 = Object.keys(_6); - _8 = Object.values(_6); - } - let _length7 = _7.length; - let _origScope9 = scope; - scope = Object.create(scope); - for (let i2 = 0; i2 < _length7; i2++) { - scope.letter_first = i2 === 0 - scope.letter_last = i2 === _length7 - 1 - scope.letter_index = i2 - scope.letter = _7[i2] - scope.letter_value = _8[i2] - let key2 = i2; - c1.push({text: \` [\`}); - let _10 = scope['number']; - if (_10 != null) { - c1.push({text: _10}); - } - let _11 = scope['letter']; - if (_11 != null) { - c1.push({text: _11}); - } - c1.push({text: \`] \`}); - } - scope = _origScope9; - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`foreach warn if no key in some case 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = [1,2]; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.item_first = i1 === 0 - scope.item_last = i1 === _length3 - 1 - scope.item_index = i1 - scope.item = _3[i1] - scope.item_value = _4[i1] - let key1 = i1; - let c6 = [], p6 = {key:\`\${key1}_6\`}; - let vn6 = h('span', p6, c6); - c1.push(vn6); - let _7 = scope['item']; - if (_7 != null) { - c6.push({text: _7}); - } - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`loading templates can initialize qweb with a string 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"hey\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - c1.push({text: \`jupiler\`}); - return vn1; -}" -`; - -exports[`loading templates can load a few templates from a xml string 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('ul', p1, c1); - let _origScope5 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'})); - scope = _origScope5; - return vn1; -}" -`; - -exports[`misc global 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = [4,5,6]; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.value_first = i1 === 0 - scope.value_last = i1 === _length3 - 1 - scope.value_index = i1 - scope.value = _3[i1] - scope.value_value = _4[i1] - let key1 = i1; - let c6 = [], p6 = {key:\`\${key1}_6\`}; - let vn6 = h('span', p6, c6); - c1.push(vn6); - let _7 = scope['value']; - if (_7 != null) { - c6.push({text: _7}); - } - let _origScope11 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - let _origScope15 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'foo').foo = 'aaa'; - scope[utils.zero] = c__0; - } - let k16 = \`__16__\${key1}__\`; - this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k16})); - } - scope = _origScope15; - let _origScope17 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - let k18 = \`__18__\${key1}__\`; - this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k18})); - scope = _origScope17; - utils.getScope(scope, 'foo').foo = 'bbb'; - let _origScope19 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - let k20 = \`__20__\${key1}__\`; - this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k20})); - scope = _origScope19; - scope[utils.zero] = c__0; - } - let k21 = \`__21__\${key1}__\`; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k21})); - } - scope = _origScope11; - } - scope = _origScope5; - let _origScope25 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['3'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__26__'})); - scope = _origScope25; - return vn1; -}" -`; - -exports[`properly support svg add proper namespace to g tags 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('g', p1, c1); - let _2 = '50'; - let _3 = '50'; - let _4 = '4'; - let _5 = 'green'; - let _6 = '1'; - let _7 = 'yellow'; - let c8 = [], p8 = {key:8,attrs:{cx: _2,cy: _3,r: _4,stroke: _5,\\"stroke-width\\": _6,fill: _7}}; - let vn8 = h('circle', p8, c8); - c1.push(vn8); - c1.push({text: \` \`}); - utils.addNameSpace(vn1); - return vn1; -}" -`; - -exports[`properly support svg add proper namespace to svg 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let _1 = '100px'; - let _2 = '90px'; - let c3 = [], p3 = {key:3,attrs:{width: _1,height: _2}}; - let vn3 = h('svg', p3, c3); - let _4 = '50'; - let _5 = '50'; - let _6 = '4'; - let _7 = 'green'; - let _8 = '1'; - let _9 = 'yellow'; - let c10 = [], p10 = {key:10,attrs:{cx: _4,cy: _5,r: _6,stroke: _7,\\"stroke-width\\": _8,fill: _9}}; - let vn10 = h('circle', p10, c10); - c3.push(vn10); - c3.push({text: \` \`}); - utils.addNameSpace(vn3); - return vn3; -}" -`; - -exports[`special cases for some specific html attributes/properties input type= checkbox, with t-att-checked 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let _1 = 'checkbox'; - let _2 = scope['flag']; - let c3 = [], p3 = {key:3,attrs:{type: _1,checked: _2},props:{checked: _2}}; - let vn3 = h('input', p3, c3); - return vn3; -}" -`; - -exports[`special cases for some specific html attributes/properties select with t-att-value 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let expr1 = scope['value']; - let c3 = [], p3 = {key:3,attrs:{value: expr1},props:{value: expr1}}; - let vn3 = h('select', p3, c3); - p3.hook = { - create: (_, n) => { - n.elm.value=expr1; - }, - }; - let _4 = 'potato'; - let c5 = [], p5 = {key:5,attrs:{value: _4}}; - let vn5 = h('option', p5, c5); - c3.push(vn5); - c5.push({text: \`Potato\`}); - let _6 = 'tomato'; - let c7 = [], p7 = {key:7,attrs:{value: _6}}; - let vn7 = h('option', p7, c7); - c3.push(vn7); - c7.push({text: \`Tomato\`}); - let _8 = 'onion'; - let c9 = [], p9 = {key:9,attrs:{value: _8}}; - let vn9 = h('option', p9, c9); - c3.push(vn9); - c9.push({text: \`Onion\`}); - return vn3; -}" -`; - -exports[`special cases for some specific html attributes/properties various boolean html attributes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = 'checkbox'; - let _3 = 'checked'; - let c4 = [], p4 = {key:4,attrs:{type: _2,checked: _3},props:{checked: _3}}; - let vn4 = h('input', p4, c4); - c1.push(vn4); - let _5 = 'checked'; - let c6 = [], p6 = {key:6,attrs:{checked: _5}}; - let vn6 = h('input', p6, c6); - c1.push(vn6); - let _7 = 'checked'; - let c8 = [], p8 = {key:8,attrs:{checked: _7}}; - let vn8 = h('div', p8, c8); - c1.push(vn8); - let _9 = 'selected'; - let c10 = [], p10 = {key:10,attrs:{selected: _9}}; - let vn10 = h('div', p10, c10); - c1.push(vn10); - let _11 = 'selected'; - let _12 = '1'; - let c13 = [], p13 = {key:13,attrs:{selected: _11,other: _12},props:{selected: _11}}; - let vn13 = h('option', p13, c13); - c1.push(vn13); - let _14 = 'readonly'; - let c15 = [], p15 = {key:15,attrs:{readonly: _14},props:{readonly: _14}}; - let vn15 = h('input', p15, c15); - c1.push(vn15); - let _16 = 'disabled'; - let c17 = [], p17 = {key:17,attrs:{disabled: _16},props:{disabled: _16}}; - let vn17 = h('button', p17, c17); - c1.push(vn17); - return vn1; -}" -`; - -exports[`static templates div with a class attribute 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _2 = {'abc':true}; - let c3 = [], p3 = {key:3,class:_2}; - let vn3 = h('div', p3, c3); - c3.push({text: \`word\`}); - return vn3; -}" -`; - -exports[`static templates div with a class attribute with a quote 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _2 = {'a\\\\'bc':true}; - let c3 = [], p3 = {key:3,class:_2}; - let vn3 = h('div', p3, c3); - c3.push({text: \`word\`}); - return vn3; -}" -`; - -exports[`static templates div with a empty class attribute 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c2 = [], p2 = {key:2}; - let vn2 = h('div', p2, c2); - c2.push({text: \`word\`}); - return vn2; -}" -`; - -exports[`static templates div with a span child node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`word\`}); - return vn1; -}" -`; - -exports[`static templates div with a text node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - c1.push({text: \`word\`}); - return vn1; -}" -`; - -exports[`static templates div with an arbitrary attribute with a quote 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _1 = 'a\\\\'bc'; - let c2 = [], p2 = {key:2,attrs:{abc: _1}}; - let vn2 = h('div', p2, c2); - c2.push({text: \`word\`}); - return vn2; -}" -`; - -exports[`static templates empty div 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - return vn1; -}" -`; - -exports[`static templates inline template string in t-esc 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - let _1 = \`text\`; - if (_1 != null) { - let vn2 = {text: _1}; - result = vn2 - } - return result; -}" -`; - -exports[`static templates inline template string with content in t-esc 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - scope.v = 1; - let _1 = \`text\${scope.v}\`; - if (_1 != null) { - let vn2 = {text: _1}; - result = vn2 - } - return result; -}" -`; - -exports[`static templates inline template string with variable in context 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - let _1 = \`text \${scope['v']}\`; - if (_1 != null) { - let vn2 = {text: _1}; - result = vn2 - } - return result; -}" -`; - -exports[`static templates properly handle comments 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - c1.push({text: \`hello \`}); - c1.push(h('!', \` comment\`)); - c1.push({text: \`owl\`}); - return vn1; -}" -`; - -exports[`static templates properly handle comments between t-if/t-else 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (true) { - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`true\`}); - } - else { - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c1.push(vn3); - c3.push({text: \`owl\`}); - } - return vn1; -}" -`; - -exports[`static templates simple dynamic value 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - let _1 = scope['text']; - if (_1 != null) { - let vn2 = {text: _1}; - result = vn2 - } - return result; -}" -`; - -exports[`static templates simple string 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let result; - let h = this.h; - let vn1 = {text: \`hello vdom\`}; - result = vn1; - return result; -}" -`; - -exports[`static templates simple string, with some dynamic value 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - let vn1 = {text: \`hello \`}; - result = vn1; - let _2 = scope['text']; - if (_2 != null) { - vn1.text += _2; - } - return result; -}" -`; - -exports[`t-call (template calling basic caller 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-call (template calling basic caller, no parent node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let result; - let h = this.h; - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - result = [] - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'})); - result = result[0] - scope = _origScope3; - return result; -}" -`; - -exports[`t-call (template calling call with several sub nodes on same line 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - let c5 = [], p5 = {key:5}; - let vn5 = h('span', p5, c5); - c__0.push(vn5); - c5.push({text: \`hey\`}); - c__0.push({text: \` \`}); - let c6 = [], p6 = {key:6}; - let vn6 = h('span', p6, c6); - c__0.push(vn6); - c6.push({text: \`yay\`}); - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__7__'})); - } - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-call (template calling call with several sub nodes on same line 2`] = `"1"`; - -exports[`t-call (template calling cascading t-call t-raw='0' 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope15 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - let c16 = [], p16 = {key:16}; - let vn16 = h('span', p16, c16); - c__0.push(vn16); - c16.push({text: \`hey\`}); - c__0.push({text: \` \`}); - let c17 = [], p17 = {key:17}; - let vn17 = h('span', p17, c17); - c__0.push(vn17); - c17.push({text: \`yay\`}); - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__18__'})); - } - scope = _origScope15; - return vn1; -}" -`; - -exports[`t-call (template calling dynamic t-call 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let tname2 = (scope['template']); - let tid2 = this.subTemplates[tname2]; - if (!tid2) { - tid2 = this.constructor.nextId++; - this.subTemplates[tname2] = tid2; - this.constructor.subTemplates[tid2] = this._compile(tname2, {hasParent: true, defineKey: true}); - } - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates[tid2].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'})); - scope = _origScope3; - return vn1; -}" -`; - -exports[`t-call (template calling dynamic t-call 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let tname2 = (scope['template']); - let tid2 = this.subTemplates[tname2]; - if (!tid2) { - tid2 = this.constructor.nextId++; - this.subTemplates[tname2] = tid2; - this.constructor.subTemplates[tid2] = this._compile(tname2, {hasParent: true, defineKey: true}); - } - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates[tid2].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'})); - scope = _origScope3; - return vn1; -}" -`; - -exports[`t-call (template calling dynamic t-call 3`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let tname2 = (scope['template']); - let tid2 = this.subTemplates[tname2]; - if (!tid2) { - tid2 = this.constructor.nextId++; - this.subTemplates[tname2] = tid2; - this.constructor.subTemplates[tid2] = this._compile(tname2, {hasParent: true, defineKey: true}); - } - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates[tid2].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'})); - scope = _origScope3; - return vn1; -}" -`; - -exports[`t-call (template calling inherit context 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.foo = 1; - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-call (template calling recursive template, part 1 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"recursive\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`hey\`}); - if (false) { - let _origScope8 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__9__'})); - scope = _origScope8; - } - return vn1; -}" -`; - -exports[`t-call (template calling recursive template, part 1 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"recursive\\" - let utils = this.constructor.utils; - let parent = extra.parent; - let scope = Object.create(context); - let h = this.h; - let c3 = extra.parentNode; - let key0 = extra.key || \\"\\"; - let c4 = [], p4 = {key:\`\${key0}_4\`}; - let vn4 = h('div', p4, c4); - c3.push(vn4); - let c5 = [], p5 = {key:\`\${key0}_5\`}; - let vn5 = h('span', p5, c5); - c4.push(vn5); - c5.push({text: \`hey\`}); - if (false) { - let _origScope6 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - let k7 = \`__7__\${key0}__\`; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c4, parent: parent, key: k7})); - scope = _origScope6; - } -}" -`; - -exports[`t-call (template calling recursive template, part 2 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"Parent\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope12 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'node').node = scope['root']; - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__13__'})); - } - scope = _origScope12; - return vn1; -}" -`; - -exports[`t-call (template calling recursive template, part 2 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"nodeTemplate\\" - let utils = this.constructor.utils; - let parent = extra.parent; - let scope = Object.create(context); - let h = this.h; - let c2 = extra.parentNode; - let key0 = extra.key || \\"\\"; - let c3 = [], p3 = {key:\`\${key0}_3\`}; - let vn3 = h('div', p3, c3); - c2.push(vn3); - let c4 = [], p4 = {key:\`\${key0}_4\`}; - let vn4 = h('p', p4, c4); - c3.push(vn4); - let _5 = scope['node'].val; - if (_5 != null) { - c4.push({text: _5}); - } - let _6 = scope['node'].children||[]; - if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _6; - let _8 = _6; - if (!(_6 instanceof Array)) { - _7 = Object.keys(_6); - _8 = Object.values(_6); - } - let _length7 = _7.length; - let _origScope9 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length7; i1++) { - scope.subtree_first = i1 === 0 - scope.subtree_last = i1 === _length7 - 1 - scope.subtree_index = i1 - scope.subtree = _7[i1] - scope.subtree_value = _8[i1] - let key1 = i1; - let _origScope10 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'node').node = scope['subtree']; - scope[utils.zero] = c__0; - } - let k11 = \`__11__\${key0}__\${key1}__\`; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c3, parent: parent, key: k11})); - } - scope = _origScope10; - } - scope = _origScope9; -}" -`; - -exports[`t-call (template calling recursive template, part 3 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"Parent\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope12 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'node').node = scope['root']; - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__13__'})); - } - scope = _origScope12; - return vn1; -}" -`; - -exports[`t-call (template calling recursive template, part 3 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"nodeTemplate\\" - let utils = this.constructor.utils; - let parent = extra.parent; - let scope = Object.create(context); - let h = this.h; - let c2 = extra.parentNode; - let key0 = extra.key || \\"\\"; - let c3 = [], p3 = {key:\`\${key0}_3\`}; - let vn3 = h('div', p3, c3); - c2.push(vn3); - let c4 = [], p4 = {key:\`\${key0}_4\`}; - let vn4 = h('p', p4, c4); - c3.push(vn4); - let _5 = scope['node'].val; - if (_5 != null) { - c4.push({text: _5}); - } - let _6 = scope['node'].children||[]; - if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _6; - let _8 = _6; - if (!(_6 instanceof Array)) { - _7 = Object.keys(_6); - _8 = Object.values(_6); - } - let _length7 = _7.length; - let _origScope9 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length7; i1++) { - scope.subtree_first = i1 === 0 - scope.subtree_last = i1 === _length7 - 1 - scope.subtree_index = i1 - scope.subtree = _7[i1] - scope.subtree_value = _8[i1] - let key1 = i1; - let _origScope10 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'node').node = scope['subtree']; - scope[utils.zero] = c__0; - } - let k11 = \`__11__\${key0}__\${key1}__\`; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c3, parent: parent, key: k11})); - } - scope = _origScope10; - } - scope = _origScope9; -}" -`; - -exports[`t-call (template calling recursive template, part 4: with t-set recursive index 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"Parent\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope12 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'recursive_idx').recursive_idx = 1; - utils.getScope(scope, 'node').node = scope['root']; - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__13__'})); - } - scope = _origScope12; - return vn1; -}" -`; - -exports[`t-call (template calling recursive template, part 4: with t-set recursive index 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"nodeTemplate\\" - let utils = this.constructor.utils; - let parent = extra.parent; - let scope = Object.create(context); - let h = this.h; - let c2 = extra.parentNode; - let key0 = extra.key || \\"\\"; - let c3 = [], p3 = {key:\`\${key0}_3\`}; - let vn3 = h('div', p3, c3); - c2.push(vn3); - scope.recursive_idx = scope.recursive_idx+1; - let c4 = [], p4 = {key:\`\${key0}_4\`}; - let vn4 = h('p', p4, c4); - c3.push(vn4); - let _5 = scope['node'].val; - if (_5 != null) { - c4.push({text: _5}); - } - c4.push({text: \` \`}); - if (scope.recursive_idx != null) { - c4.push({text: scope.recursive_idx}); - } - let _6 = scope['node'].children||[]; - if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _6; - let _8 = _6; - if (!(_6 instanceof Array)) { - _7 = Object.keys(_6); - _8 = Object.values(_6); - } - let _length7 = _7.length; - let _origScope9 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length7; i1++) { - scope.subtree_first = i1 === 0 - scope.subtree_last = i1 === _length7 - 1 - scope.subtree_index = i1 - scope.subtree = _7[i1] - scope.subtree_value = _8[i1] - let key1 = i1; - let _origScope10 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'node').node = scope['subtree']; - scope[utils.zero] = c__0; - } - let k11 = \`__11__\${key0}__\${key1}__\`; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c3, parent: parent, key: k11})); - } - scope = _origScope10; - } - scope = _origScope9; -}" -`; - -exports[`t-call (template calling scoped parameters 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'foo').foo = 42; - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'})); - } - scope = _origScope3; - if (scope.foo != null) { - c1.push({text: scope.foo}); - } - return vn1; -}" -`; - -exports[`t-call (template calling t-call allowed on a non t node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-call (template calling t-call with body content as root of a template 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let result; - let h = this.h; - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - let c4 = [], p4 = {key:4}; - let vn4 = h('p', p4, c4); - c__0.push(vn4); - c4.push({text: \`antony\`}); - scope[utils.zero] = c__0; - } - result = [] - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__5__'})); - result = result[0] - } - scope = _origScope3; - return result; -}" -`; - -exports[`t-call (template calling t-call with t-if 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['flag']) { - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - scope = _origScope4; - } - return vn1; -}" -`; - -exports[`t-call (template calling t-call with t-set inside and body text content 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope5 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - let c6 = new utils.VDomArray(); - c6.push({text: \`yip yip\`}); - scope.val = c6 - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__7__'})); - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-call (template calling t-call with t-set inside and outside 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = scope['list']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.v_first = i1 === 0 - scope.v_last = i1 === _length3 - 1 - scope.v_index = i1 - scope.v = _3[i1] - scope.v_value = _4[i1] - let key1 = i1; - utils.getScope(scope, 'val').val = scope['v'].val; - let _origScope9 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'val3').val3 = scope.val*3; - scope[utils.zero] = c__0; - } - let k10 = \`__10__\${key1}__\`; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k10})); - } - scope = _origScope9; - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-call (template calling t-call with t-set inside and outside. 2 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"wrapper\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('p', p1, c1); - scope.w = 'fromwrapper'; - let _origScope14 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__15__'})); - scope = _origScope14; - return vn1; -}" -`; - -exports[`t-call (template calling t-call, conditional and t-set in t-call body 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.v1 = 'elif'; - if (scope.v1==='if') { - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - scope = _origScope4; - } - else if (scope.v1==='elif') { - let _origScope9 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'v').v = 'success'; - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__10__'})); - } - scope = _origScope9; - } - return vn1; -}" -`; - -exports[`t-call (template calling t-call, global templates 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"abcd\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-call (template calling two different QWeb instances, and shared templates 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-call (template calling two different QWeb instances, and shared templates 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-call (template calling with unused body 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let result; - let h = this.h; - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - c__0.push({text: \`WHEEE\`}); - scope[utils.zero] = c__0; - } - result = [] - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'})); - result = result[0] - } - scope = _origScope3; - return result; -}" -`; - -exports[`t-call (template calling with unused setbody 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let result; - let h = this.h; - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'qux').qux = 3; - scope[utils.zero] = c__0; - } - result = [] - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'})); - result = result[0] - } - scope = _origScope3; - return result; -}" -`; - -exports[`t-call (template calling with used body 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let result; - let h = this.h; - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - c__0.push({text: \`ok\`}); - scope[utils.zero] = c__0; - } - result = [] - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'})); - result = result[0] - } - scope = _origScope3; - return result; -}" -`; - -exports[`t-call (template calling with used set body 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"caller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - utils.getScope(scope, 'foo').foo = 'ok'; - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'})); - } - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-esc div with falsy values 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('p', p2, c2); - c1.push(vn2); - let _3 = scope['v1']; - if (_3 != null) { - c2.push({text: _3}); - } - let c4 = [], p4 = {key:4}; - let vn4 = h('p', p4, c4); - c1.push(vn4); - let _5 = scope['v2']; - if (_5 != null) { - c4.push({text: _5}); - } - let c6 = [], p6 = {key:6}; - let vn6 = h('p', p6, c6); - c1.push(vn6); - let _7 = scope['v3']; - if (_7 != null) { - c6.push({text: _7}); - } - let c8 = [], p8 = {key:8}; - let vn8 = h('p', p8, c8); - c1.push(vn8); - let _9 = scope['v4']; - if (_9 != null) { - c8.push({text: _9}); - } - let c10 = [], p10 = {key:10}; - let vn10 = h('p', p10, c10); - c1.push(vn10); - let _11 = scope['v5']; - if (_11 != null) { - c10.push({text: _11}); - } - return vn1; -}" -`; - -exports[`t-esc escaping 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = scope['var']; - if (_2 != null) { - c1.push({text: _2}); - } - return vn1; -}" -`; - -exports[`t-esc escaping on a node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = 'ok'; - if (_2 != null) { - c1.push({text: _2}); - } - return vn1; -}" -`; - -exports[`t-esc escaping on a node with a body 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = 'ok'; - if (_2 != null) { - c1.push({text: _2}); - } else { - c1.push({text: \`nope\`}); - } - return vn1; -}" -`; - -exports[`t-esc escaping on a node with a body, as a default 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = scope['var']; - if (_2 != null) { - c1.push({text: _2}); - } else { - c1.push({text: \`nope\`}); - } - return vn1; -}" -`; - -exports[`t-esc literal 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = 'ok'; - if (_2 != null) { - c1.push({text: _2}); - } - return vn1; -}" -`; - -exports[`t-esc t-esc inside t-call, with t-set outside 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = new utils.VDomArray(); - c2.push({text: \`Hi\`}); - scope.v = c2 - let _origScope6 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__7__'})); - scope = _origScope6; - return vn1; -}" -`; - -exports[`t-esc t-esc is escaped 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = new utils.VDomArray(); - let c3 = [], p3 = {key:3}; - let vn3 = h('p', p3, c3); - c2.push(vn3); - c3.push({text: \`escaped\`}); - scope.var = c2 - if (scope.var != null) { - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.var = scope.var instanceof utils.VDomArray ? utils.vDomToString(scope.var) : scope.var; - c1.push({text: scope.var}); - scope = _origScope4; - } - return vn1; -}" -`; - -exports[`t-esc t-esc work with spread operator 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = [...scope['state'].list]; - if (_2 != null) { - c1.push({text: _2}); - } - return vn1; -}" -`; - -exports[`t-esc t-esc=0 is escaped 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"testCaller\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - { - { - let c__0 = []; - let c5 = [], p5 = {key:5}; - let vn5 = h('p', p5, c5); - c__0.push(vn5); - c5.push({text: \`escaped\`}); - scope[utils.zero] = c__0; - } - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'})); - } - scope = _origScope4; - return vn1; -}" -`; - -exports[`t-esc variable 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = scope['var']; - if (_2 != null) { - c1.push({text: _2}); - } - return vn1; -}" -`; - -exports[`t-if boolean value condition elif 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['color']=='black') { - c1.push({text: \`black pearl\`}); - } - else if (scope['color']=='yellow') { - c1.push({text: \`yellow submarine\`}); - } - else if (scope['color']=='red') { - c1.push({text: \`red is dead\`}); - } - else { - c1.push({text: \`beer\`}); - } - return vn1; -}" -`; - -exports[`t-if boolean value condition else 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`begin\`}); - if (scope['condition']) { - c1.push({text: \`ok\`}); - } - else { - c1.push({text: \`ok-else\`}); - } - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c1.push(vn3); - c3.push({text: \`end\`}); - return vn1; -}" -`; - -exports[`t-if boolean value condition false else 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`begin\`}); - if (scope['condition']) { - c1.push({text: \`fail\`}); - } - else { - c1.push({text: \`fail-else\`}); - } - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c1.push(vn3); - c3.push({text: \`end\`}); - return vn1; -}" -`; - -exports[`t-if boolean value condition missing 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - if (scope['condition']) { - c1.push({text: \`fail\`}); - } - return vn1; -}" -`; - -exports[`t-if boolean value false condition 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['condition']) { - c1.push({text: \`ok\`}); - } - return vn1; -}" -`; - -exports[`t-if boolean value true condition 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['condition']) { - c1.push({text: \`ok\`}); - } - return vn1; -}" -`; - -exports[`t-if can use some boolean operators in expressions 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['cond1']&&scope['cond2']) { - c1.push({text: \`and\`}); - } - if (scope['cond1']&&scope['cond3']) { - c1.push({text: \`nope\`}); - } - if (scope['cond1']||scope['cond3']) { - c1.push({text: \`or\`}); - } - if (scope['cond3']||scope['cond4']) { - c1.push({text: \`nope\`}); - } - if (scope['m']>3) { - c1.push({text: \`mgt\`}); - } - if (scope['n']>3) { - c1.push({text: \`ngt\`}); - } - if (scope['m']<3) { - c1.push({text: \`mlt\`}); - } - if (scope['n']<3) { - c1.push({text: \`nlt\`}); - } - return vn1; -}" -`; - -exports[`t-if t-esc with t-elif 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (false) { - c1.push({text: \`abc\`}); - } - else { - let _2 = 'x'; - if (_2 != null) { - c1.push({text: _2}); - } - } - return vn1; -}" -`; - -exports[`t-if t-esc with t-if 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (true) { - let _2 = 'x'; - if (_2 != null) { - c1.push({text: _2}); - } - } - return vn1; -}" -`; - -exports[`t-if t-set, then t-elif, part 3 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.y = false; - scope.x = scope.y; - if (scope.x) { - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`AAA\`}); - } - else if (!scope.x) { - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c1.push(vn3); - c3.push({text: \`BBB\`}); - } - return vn1; -}" -`; - -exports[`t-if t-set, then t-if 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.title = 'test'; - if (scope.title) { - if (scope.title != null) { - c1.push({text: scope.title}); - } - } - return vn1; -}" -`; - -exports[`t-if t-set, then t-if, part 2 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.y = true; - scope.x = scope.y; - if (scope.x) { - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`COUCOU\`}); - } - return vn1; -}" -`; - -exports[`t-key can use t-key directive on a node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - { - let key0 = scope['beer'].id; - let c1 = [], p1 = {key:\`\${key0}_1\`}; - let vn1 = h('div', p1, c1); - result = vn1; - result = vn1; - let _2 = scope['beer'].name; - if (_2 != null) { - c1.push({text: _2}); - } - } - return result; -}" -`; - -exports[`t-key t-key directive in a list 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('ul', p1, c1); - let _2 = scope['beers']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.beer_first = i1 === 0 - scope.beer_last = i1 === _length3 - 1 - scope.beer_index = i1 - scope.beer = _3[i1] - scope.beer_value = _4[i1] - let key1 = scope['beer'].id; - let c6 = [], p6 = {key:\`\${key1}_6\`}; - let vn6 = h('li', p6, c6); - c1.push(vn6); - let _7 = scope['beer'].name; - if (_7 != null) { - c6.push({text: _7}); - } - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-key t-key directive in a list 2`] = `"
    • Chimay Rouge
    "`; - -exports[`t-on can bind event handler 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](e);}; - p1.on['click'] = extra.handlers['click__2__']; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on can bind handlers with arguments 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - let args2 = [5]; - p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](...args2, e);}; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on can bind handlers with empty object (with non empty inner string) 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - let args2 = [{}]; - p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](...args2, e);}; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on can bind handlers with empty object 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - let args2 = [{}]; - p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](...args2, e);}; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on can bind handlers with loop variable as argument 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('ul', p1, c1); - let _2 = ['someval']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.action_first = i1 === 0 - scope.action_last = i1 === _length3 - 1 - scope.action_index = i1 - scope.action = _3[i1] - scope.action_value = _4[i1] - let key1 = scope['action_index']; - let c6 = [], p6 = {key:\`\${key1}_6\`}; - let vn6 = h('li', p6, c6); - c1.push(vn6); - let c7 = [], p7 = {key:\`\${key1}_7\`,on:{}}; - let vn7 = h('a', p7, c7); - c6.push(vn7); - let args8 = [scope['action']]; - p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['activate'](...args8, e);}; - c7.push({text: \`link\`}); - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-on can bind handlers with object arguments 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - let args2 = [{val:5}]; - p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](...args2, e);}; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on can bind two event handlers 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handleClick'](e);}; - p1.on['click'] = extra.handlers['click__2__']; - extra.handlers['dblclick__3__'] = extra.handlers['dblclick__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handleDblClick'](e);}; - p1.on['dblclick'] = extra.handlers['dblclick__3__']; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on handler is bound to proper owner 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](e);}; - p1.on['click'] = extra.handlers['click__2__']; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on t-on combined with t-esc 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2,on:{}}; - let vn2 = h('button', p2, c2); - c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; - p2.on['click'] = extra.handlers['click__3__']; - let _4 = scope['text']; - if (_4 != null) { - c2.push({text: _4}); - } - return vn1; -}" -`; - -exports[`t-on t-on combined with t-raw 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2,on:{}}; - let vn2 = h('button', p2, c2); - c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; - p2.on['click'] = extra.handlers['click__3__']; - let _4 = scope['html']; - if (_4 != null) { - c2.push(...utils.htmlToVDOM(_4)); - } - return vn1; -}" -`; - -exports[`t-on t-on with .capture modifier 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('div', p1, c1); - extra.handlers['!click__2__'] = extra.handlers['!click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onCapture'](e);}; - p1.on['!click'] = extra.handlers['!click__2__']; - let c3 = [], p3 = {key:3,on:{}}; - let vn3 = h('button', p3, c3); - c1.push(vn3); - extra.handlers['click__4__'] = extra.handlers['click__4__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);}; - p3.on['click'] = extra.handlers['click__4__']; - c3.push({text: \`Button\`}); - return vn1; -}" -`; - -exports[`t-on t-on with empty handler (only modifiers) 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2,on:{}}; - let vn2 = h('button', p2, c2); - c1.push(vn2); - p2.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();const res = (() => { return })(); if (typeof res === 'function') { res(e) }}; - c2.push({text: \`Button\`}); - return vn1; -}" -`; - -exports[`t-on t-on with inline statement (function call) 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - const state_2 = scope['state']; - p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.incrementCounter(2) })(); if (typeof res === 'function') { res(e) }}; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on t-on with inline statement 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - const state_2 = scope['state']; - p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.counter++ })(); if (typeof res === 'function') { res(e) }}; - c1.push({text: \`Click\`}); - return vn1; -}" -`; - -exports[`t-on t-on with inline statement, part 2 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - const state_2 = scope['state']; - p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.flag=!state_2.flag })(); if (typeof res === 'function') { res(e) }}; - c1.push({text: \`Toggle\`}); - return vn1; -}" -`; - -exports[`t-on t-on with inline statement, part 3 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1,on:{}}; - let vn1 = h('button', p1, c1); - const state_2 = scope['state']; - p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.n=scope['someFunction'](3) })(); if (typeof res === 'function') { res(e) }}; - c1.push({text: \`Toggle\`}); - return vn1; -}" -`; - -exports[`t-on t-on with prevent and self modifiers (order matters) 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2,on:{}}; - let vn2 = h('button', p2, c2); - c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();if (e.target !== this.elm) {return}utils.getComponent(context)['onClick'](e);}; - p2.on['click'] = extra.handlers['click__3__']; - let c4 = [], p4 = {key:4}; - let vn4 = h('span', p4, c4); - c2.push(vn4); - c4.push({text: \`Button\`}); - return vn1; -}" -`; - -exports[`t-on t-on with prevent and/or stop modifiers 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2,on:{}}; - let vn2 = h('button', p2, c2); - c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onClickPrevented'](e);}; - p2.on['click'] = extra.handlers['click__3__']; - c2.push({text: \`Button 1\`}); - let c4 = [], p4 = {key:4,on:{}}; - let vn4 = h('button', p4, c4); - c1.push(vn4); - extra.handlers['click__5__'] = extra.handlers['click__5__'] || function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();utils.getComponent(context)['onClickStopped'](e);}; - p4.on['click'] = extra.handlers['click__5__']; - c4.push({text: \`Button 2\`}); - let c6 = [], p6 = {key:6,on:{}}; - let vn6 = h('button', p6, c6); - c1.push(vn6); - extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();e.stopPropagation();utils.getComponent(context)['onClickPreventedAndStopped'](e);}; - p6.on['click'] = extra.handlers['click__7__']; - c6.push({text: \`Button 3\`}); - return vn1; -}" -`; - -exports[`t-on t-on with prevent modifier in t-foreach 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = scope['projects']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.project_first = i1 === 0 - scope.project_last = i1 === _length3 - 1 - scope.project_index = i1 - scope.project = _3[i1] - scope.project_value = _4[i1] - let key1 = i1; - { - let key1 = scope['project']; - let _6 = '#'; - let c7 = [], p7 = {key:\`\${key1}_7\`,attrs:{href: _6},on:{}}; - let vn7 = h('a', p7, c7); - c1.push(vn7); - let args8 = [scope['project'].id]; - p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onEdit'](...args8, e);}; - c7.push({text: \` Edit \`}); - let _9 = scope['project'].name; - if (_9 != null) { - c7.push({text: _9}); - } - } - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-on t-on with self and prevent modifiers (order matters) 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2,on:{}}; - let vn2 = h('button', p2, c2); - c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}if (e.target !== this.elm) {return}e.preventDefault();utils.getComponent(context)['onClick'](e);}; - p2.on['click'] = extra.handlers['click__3__']; - let c4 = [], p4 = {key:4}; - let vn4 = h('span', p4, c4); - c2.push(vn4); - c4.push({text: \`Button\`}); - return vn1; -}" -`; - -exports[`t-on t-on with self modifier 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2,on:{}}; - let vn2 = h('button', p2, c2); - c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; - p2.on['click'] = extra.handlers['click__3__']; - let c4 = [], p4 = {key:4}; - let vn4 = h('span', p4, c4); - c2.push(vn4); - c4.push({text: \`Button\`}); - let c5 = [], p5 = {key:5,on:{}}; - let vn5 = h('button', p5, c5); - c1.push(vn5); - extra.handlers['click__6__'] = extra.handlers['click__6__'] || function (e) {if (context.__owl__.status === 5){return}if (e.target !== this.elm) {return}utils.getComponent(context)['onClickSelf'](e);}; - p5.on['click'] = extra.handlers['click__6__']; - let c7 = [], p7 = {key:7}; - let vn7 = h('span', p7, c7); - c5.push(vn7); - c7.push({text: \`Button\`}); - return vn1; -}" -`; - -exports[`t-on t-on with t-call 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope5 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'})); - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-on t-on, with arguments and t-call 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"main\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _origScope5 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'})); - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-raw literal 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = 'ok'; - if (_2 != null) { - c1.push(...utils.htmlToVDOM(_2)); - } - return vn1; -}" -`; - -exports[`t-raw not escaping 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = scope['var']; - if (_2 != null) { - c1.push(...utils.htmlToVDOM(_2)); - } - return vn1; -}" -`; - -exports[`t-raw t-raw and another sibling node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`hello\`}); - let _3 = scope['var']; - if (_3 != null) { - c1.push(...utils.htmlToVDOM(_3)); - } - return vn1; -}" -`; - -exports[`t-raw t-raw with comment 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = scope['var']; - if (_2 != null) { - c1.push(...utils.htmlToVDOM(_2)); - } - return vn1; -}" -`; - -exports[`t-raw variable 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('span', p1, c1); - let _2 = scope['var']; - if (_2 != null) { - c1.push(...utils.htmlToVDOM(_2)); - } - return vn1; -}" -`; - -exports[`t-ref can get a dynamic ref on a node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - context.__owl__.refs = context.__owl__.refs || {}; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - const ref3 = \`myspan\${scope['id']}\`; - p2.hook = { - create: (_, n) => { - context.__owl__.refs[ref3] = n.elm; - }, - destroy: () => { - delete context.__owl__.refs[ref3]; - }, - }; - return vn1; -}" -`; - -exports[`t-ref can get a ref on a node 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - context.__owl__.refs = context.__owl__.refs || {}; - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - const ref3 = \`myspan\`; - p2.hook = { - create: (_, n) => { - context.__owl__.refs[ref3] = n.elm; - }, - destroy: () => { - delete context.__owl__.refs[ref3]; - }, - }; - return vn1; -}" -`; - -exports[`t-ref refs in a loop 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - context.__owl__.refs = context.__owl__.refs || {}; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = scope['items']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.item_first = i1 === 0 - scope.item_last = i1 === _length3 - 1 - scope.item_index = i1 - scope.item = _3[i1] - scope.item_value = _4[i1] - let key1 = i1; - { - let key1 = scope['item']; - let c6 = [], p6 = {key:\`\${key1}_6\`}; - let vn6 = h('div', p6, c6); - c1.push(vn6); - const ref7 = (scope['item']); - p6.hook = { - create: (_, n) => { - context.__owl__.refs[ref7] = n.elm; - }, - destroy: () => { - delete context.__owl__.refs[ref7]; - }, - }; - let _8 = scope['item']; - if (_8 != null) { - c6.push({text: _8}); - } - } - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-set evaluate value expression 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.value = 1+2; - if (scope.value != null) { - c1.push({text: scope.value}); - } - return vn1; -}" -`; - -exports[`t-set evaluate value expression, part 2 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.value = scope['somevariable']+2; - if (scope.value != null) { - c1.push({text: scope.value}); - } - return vn1; -}" -`; - -exports[`t-set set from attribute literal 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.value = 'ok'; - if (scope.value != null) { - c1.push({text: scope.value}); - } - return vn1; -}" -`; - -exports[`t-set set from attribute lookup 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.stuff = scope['value']; - if (scope.stuff != null) { - c1.push({text: scope.stuff}); - } - return vn1; -}" -`; - -exports[`t-set set from body literal 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let result; - let h = this.h; - let c1 = new utils.VDomArray(); - c1.push({text: \`ok\`}); - scope.value = c1 - if (scope.value != null) { - let _origScope2 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value; - let vn3 = {text: scope.value}; - result = vn3 - scope = _origScope2; - } - return result; -}" -`; - -exports[`t-set set from body lookup 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = new utils.VDomArray(); - let _3 = scope['value']; - if (_3 != null) { - c2.push({text: _3}); - } - scope.stuff = c2 - if (scope.stuff != null) { - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.stuff = scope.stuff instanceof utils.VDomArray ? utils.vDomToString(scope.stuff) : scope.stuff; - c1.push({text: scope.stuff}); - scope = _origScope4; - } - return vn1; -}" -`; - -exports[`t-set set from empty body 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope.stuff != null) { - c1.push({text: scope.stuff}); - } - return vn1; -}" -`; - -exports[`t-set t-set and t-if 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.v = scope['value']; - if (scope.v==='ok') { - c1.push({text: \`grimbergen\`}); - } - return vn1; -}" -`; - -exports[`t-set t-set body is evaluated immediately 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.v1 = 'before'; - let c2 = new utils.VDomArray(); - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c2.push(vn3); - if (scope.v1 != null) { - c3.push({text: scope.v1}); - } - scope.v2 = c2 - scope.v1 = 'after'; - if (scope.v2 != null) { - const vnodeArray = scope.v2 instanceof utils.VDomArray ? scope.v2 : utils.htmlToVDOM(scope.v2); - c1.push(...vnodeArray); - } - return vn1; -}" -`; - -exports[`t-set t-set evaluates an expression only once 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.v = scope['value']+' artois'; - if (scope.v != null) { - c1.push({text: scope.v}); - } - if (scope.v != null) { - c1.push({text: scope.v}); - } - return vn1; -}" -`; - -exports[`t-set t-set should reuse variable if possible 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.v = 1; - let _2 = scope['list']; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.elem_first = i1 === 0 - scope.elem_last = i1 === _length3 - 1 - scope.elem_index = i1 - scope.elem = _3[i1] - scope.elem_value = _4[i1] - let key1 = scope['elem_index']; - let c6 = [], p6 = {key:\`\${key1}_6\`}; - let vn6 = h('div', p6, c6); - c1.push(vn6); - let c7 = [], p7 = {key:\`\${key1}_7\`}; - let vn7 = h('span', p7, c7); - c6.push(vn7); - c7.push({text: \`v\`}); - if (scope.v != null) { - c7.push({text: scope.v}); - } - utils.getScope(scope, 'v').v = scope['elem']; - } - scope = _origScope5; - return vn1; -}" -`; - -exports[`t-set t-set with content and sub t-esc 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = new utils.VDomArray(); - let _3 = scope['beep']; - if (_3 != null) { - c2.push({text: _3}); - } - c2.push({text: \` boop\`}); - scope.setvar = c2 - if (scope.setvar != null) { - let _origScope4 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.setvar = scope.setvar instanceof utils.VDomArray ? utils.vDomToString(scope.setvar) : scope.setvar; - c1.push({text: scope.setvar}); - scope = _origScope4; - } - return vn1; -}" -`; - -exports[`t-set t-set with t-value (falsy) and body 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.v3 = false; - scope.v1 = 'before'; - scope.v2 = scope.v3; - if (!(scope.v2)) { - let c2 = new utils.VDomArray(); - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c2.push(vn3); - if (scope.v1 != null) { - c3.push({text: scope.v1}); - } - scope.v2 = c2 - } - scope.v1 = 'after'; - scope.v3 = true; - if (scope.v2 != null) { - const vnodeArray = scope.v2 instanceof utils.VDomArray ? scope.v2 : utils.htmlToVDOM(scope.v2); - c1.push(...vnodeArray); - } - return vn1; -}" -`; - -exports[`t-set t-set with t-value (truthy) and body 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.v3 = 'Truthy'; - scope.v1 = 'before'; - scope.v2 = scope.v3; - if (!(scope.v2)) { - let c2 = new utils.VDomArray(); - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c2.push(vn3); - if (scope.v1 != null) { - c3.push({text: scope.v1}); - } - scope.v2 = c2 - } - scope.v1 = 'after'; - scope.v3 = false; - if (scope.v2 != null) { - const vnodeArray = scope.v2 instanceof utils.VDomArray ? scope.v2 : utils.htmlToVDOM(scope.v2); - c1.push(...vnodeArray); - } - return vn1; -}" -`; - -exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['flag']) { - let c2 = new utils.VDomArray(); - c2.push({text: \`1\`}); - scope.ourvar = c2 - } - else { - scope.ourvar = 0; - } - if (scope.ourvar != null) { - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; - c1.push({text: scope.ourvar}); - scope = _origScope3; - } - return vn1; -}" -`; - -exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['flag']) { - let c2 = new utils.VDomArray(); - c2.push({text: \`1\`}); - scope.ourvar = c2 - } - else { - scope.ourvar = 0; - } - if (scope.ourvar != null) { - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; - c1.push({text: scope.ourvar}); - scope = _origScope3; - } - return vn1; -}" -`; - -exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['flag']) { - scope.ourvar = 1; - } - else { - let c2 = new utils.VDomArray(); - c2.push({text: \`0\`}); - scope.ourvar = c2 - } - if (scope.ourvar != null) { - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; - c1.push({text: scope.ourvar}); - scope = _origScope3; - } - return vn1; -}" -`; - -exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - if (scope['flag']) { - scope.ourvar = 1; - } - else { - let c2 = new utils.VDomArray(); - c2.push({text: \`0\`}); - scope.ourvar = c2 - } - if (scope.ourvar != null) { - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; - c1.push({text: scope.ourvar}); - scope = _origScope3; - } - return vn1; -}" -`; - -exports[`t-set value priority 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - scope.value = 1; - if (!(scope.value)) { - let c2 = new utils.VDomArray(); - c2.push({text: \`2\`}); - scope.value = c2 - } - if (scope.value != null) { - let _origScope3 = scope; - scope = Object.create(scope); - scope.__access_mode__ = 'ro'; - scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value; - c1.push({text: scope.value}); - scope = _origScope3; - } - return vn1; -}" -`; - -exports[`translation support can add additional attributes to the list of translatable attributes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let _1 = 'word'; - let _2 = 'mot'; - let c3 = [], p3 = {key:3,attrs:{tomato: _1,potato: _2}}; - let vn3 = h('div', p3, c3); - c3.push({text: \`text\`}); - return vn3; -}" -`; - -exports[`translation support can translate node content 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - c1.push({text: \`mot\`}); - return vn1; -}" -`; - -exports[`translation support does not translate node content if disabled 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`mot\`}); - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c1.push(vn3); - c3.push({text: \`word\`}); - return vn1; -}" -`; - -exports[`translation support some attributes are translated 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let _2 = 'mot'; - let c3 = [], p3 = {key:3,attrs:{label: _2}}; - let vn3 = h('p', p3, c3); - c1.push(vn3); - c3.push({text: \`mot\`}); - let _4 = 'mot'; - let c5 = [], p5 = {key:5,attrs:{title: _4}}; - let vn5 = h('p', p5, c5); - c1.push(vn5); - c5.push({text: \`mot\`}); - let _6 = 'mot'; - let c7 = [], p7 = {key:7,attrs:{placeholder: _6}}; - let vn7 = h('p', p7, c7); - c1.push(vn7); - c7.push({text: \`mot\`}); - let _8 = 'mot'; - let c9 = [], p9 = {key:9,attrs:{alt: _8}}; - let vn9 = h('p', p9, c9); - c1.push(vn9); - c9.push({text: \`mot\`}); - let _10 = 'word'; - let c11 = [], p11 = {key:11,attrs:{something: _10}}; - let vn11 = h('p', p11, c11); - c1.push(vn11); - c11.push({text: \`mot\`}); - return vn1; -}" -`; - -exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - c1.push({text: \` mot \`}); - return vn1; -}" -`; - -exports[`whitespace handling consecutives whitespaces are condensed into a single space 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - c1.push({text: \` abc \`}); - return vn1; -}" -`; - -exports[`whitespace handling nothing is done in pre tags 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('pre', p1, c1); - c1.push({text: \` \`}); - return vn1; -}" -`; - -exports[`whitespace handling nothing is done in pre tags 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test2\\" - let h = this.h; - let c2 = [], p2 = {key:2}; - let vn2 = h('pre', p2, c2); - c2.push({text: \` - some text - \`}); - return vn2; -}" -`; - -exports[`whitespace handling nothing is done in pre tags 3`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test3\\" - let h = this.h; - let c3 = [], p3 = {key:3}; - let vn3 = h('pre', p3, c3); - c3.push({text: \` - - \`}); - return vn3; -}" -`; - -exports[`whitespace handling white space only text nodes are condensed into a single space 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - c1.push({text: \` \`}); - return vn1; -}" -`; - -exports[`whitespace handling whitespace only text nodes with newlines are removed 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - let vn2 = h('span', p2, c2); - c1.push(vn2); - c2.push({text: \`abc\`}); - return vn1; -}" -`; diff --git a/tests/qweb/__snapshots__/qweb_memory.test.ts.snap b/tests/qweb/__snapshots__/qweb_memory.test.ts.snap deleted file mode 100644 index 3b0b0892..00000000 --- a/tests/qweb/__snapshots__/qweb_memory.test.ts.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`memory t-foreach does not leak stuff in global scope 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let h = this.h; - let c1 = [], p1 = {key:1}; - let vn1 = h('p', p1, c1); - let _2 = [3,2,1]; - if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _2; - let _4 = _2; - if (!(_2 instanceof Array)) { - _3 = Object.keys(_2); - _4 = Object.values(_2); - } - let _length3 = _3.length; - let _origScope5 = scope; - scope = Object.create(scope); - for (let i1 = 0; i1 < _length3; i1++) { - scope.item_first = i1 === 0 - scope.item_last = i1 === _length3 - 1 - scope.item_index = i1 - scope.item = _3[i1] - scope.item_value = _4[i1] - let key1 = i1; - let _6 = scope['item']; - if (_6 != null) { - c1.push({text: _6}); - } - } - scope = _origScope5; - return vn1; -}" -`; diff --git a/tests/qweb/__snapshots__/qweb_t_att.test.ts.snap b/tests/qweb/__snapshots__/qweb_t_att.test.ts.snap deleted file mode 100644 index be011d1a..00000000 --- a/tests/qweb/__snapshots__/qweb_t_att.test.ts.snap +++ /dev/null @@ -1,57 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`qweb t-att t-att-class with multiple classes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _1 = utils.toClassObj({'a b c':scope['value']}); - let c2 = [], p2 = {key:2,class:_1}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`qweb t-att t-att-class with multiple classes 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _3 = utils.toClassObj({['a b c']:scope['value']}); - let c4 = [], p4 = {key:4,class:_3}; - let vn4 = h('div', p4, c4); - return vn4; -}" -`; - -exports[`qweb t-att t-att-class with multiple classes, some of which are duplicate 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _1 = utils.toClassObj({'a b c':scope['value'],'a b d':!scope['value']}); - let c2 = [], p2 = {key:2,class:_1}; - let vn2 = h('div', p2, c2); - return vn2; -}" -`; - -exports[`qweb t-att t-att-class with multiple classes, some of which are duplicate 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let utils = this.constructor.utils; - let scope = Object.create(context); - let h = this.h; - let _3 = utils.toClassObj({'a b c':scope['value'],'a b d':!scope['value']}); - let c4 = [], p4 = {key:4,class:_3}; - let vn4 = h('div', p4, c4); - return vn4; -}" -`; diff --git a/tests/qweb/__snapshots__/qweb_t_tag.test.ts.snap b/tests/qweb/__snapshots__/qweb_t_tag.test.ts.snap deleted file mode 100644 index 05ce570c..00000000 --- a/tests/qweb/__snapshots__/qweb_t_tag.test.ts.snap +++ /dev/null @@ -1,71 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`qweb t-tag simple usecases 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - let c1 = [], p1 = {key:1}; - let tag2 = 'div'; - let vn1 = h(tag2, p1, c1); - result = vn1; - return result; -}" -`; - -exports[`qweb t-tag simple usecases 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - let c3 = [], p3 = {key:3}; - let tag4 = scope['tag']; - let vn3 = h(tag4, p3, c3); - result = vn3; - c3.push({text: \`text\`}); - return result; -}" -`; - -exports[`qweb t-tag with multiple attributes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - let _2 = {'blueberry':true}; - let _3 = 'raspberry'; - let c4 = [], p4 = {key:4,attrs:{taste: _3},class:_2}; - let tag5 = scope['tag']; - let vn4 = h(tag5, p4, c4); - result = vn4; - c4.push({text: \`gooseberry\`}); - return result; -}" -`; - -exports[`qweb t-tag with multiple child nodes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"test\\" - let scope = Object.create(context); - let result; - let h = this.h; - let c1 = [], p1 = {key:1}; - let tag2 = scope['tag']; - let vn1 = h(tag2, p1, c1); - result = vn1; - c1.push({text: \` pear \`}); - let c3 = [], p3 = {key:3}; - let vn3 = h('span', p3, c3); - c1.push(vn3); - c3.push({text: \`apple\`}); - c1.push({text: \` strawberry \`}); - return result; -}" -`; diff --git a/tests/qweb/__snapshots__/simple_templates.test.ts.snap b/tests/qweb/__snapshots__/simple_templates.test.ts.snap new file mode 100644 index 00000000..85046bd2 --- /dev/null +++ b/tests/qweb/__snapshots__/simple_templates.test.ts.snap @@ -0,0 +1,337 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`simple templates, mostly static can render a table row 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`cell\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`simple templates, mostly static div with a class attribute 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    foo
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`simple templates, mostly static div with a class attribute with a quote 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    word
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`simple templates, mostly static div with a span child node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    word
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`simple templates, mostly static div with an arbitrary attribute with a quote 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    word
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`simple templates, mostly static div with an empty class attribute 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    word
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`simple templates, mostly static div with content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    foo
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`simple templates, mostly static dom node with t-esc 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['text']; + return block1([d1]); + } +}" +`; + +exports[`simple templates, mostly static dom node with t-esc 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['text']; + return block1([d1]); + } +}" +`; + +exports[`simple templates, mostly static dynamic text value 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(ctx['text']); + } +}" +`; + +exports[`simple templates, mostly static empty div 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`simple templates, mostly static empty string 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`\`); + } +}" +`; + +exports[`simple templates, mostly static empty string in a template set 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`\`); + } +}" +`; + +exports[`simple templates, mostly static multiple root nodes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`
    foo
    \`); + let block3 = createBlock(\`hey\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = block2(); + let b3 = block3(); + return multi([b2, b3]); + } +}" +`; + +exports[`simple templates, mostly static simple string 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`hello vdom\`); + } +}" +`; + +exports[`simple templates, mostly static simple string in t tag 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`hello vdom\`); + } +}" +`; + +exports[`simple templates, mostly static static text and dynamic text (no t tag) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2 = text(\`hello \`); + let b3 = text(ctx['text']); + return multi([b2, b3]); + } +}" +`; + +exports[`simple templates, mostly static static text and dynamic text 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2 = text(\`hello \`); + let b3 = text(ctx['text']); + return multi([b2, b3]); + } +}" +`; + +exports[`simple templates, mostly static t-esc in dom node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['text']; + return block1([d1]); + } +}" +`; + +exports[`simple templates, mostly static t-esc in dom node, variations 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    hello
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['text']; + return block1([d1]); + } +}" +`; + +exports[`simple templates, mostly static t-esc in dom node, variations 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    hello world
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['text']; + return block1([d1]); + } +}" +`; + +exports[`simple templates, mostly static two t-escs next to each other 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2 = text(ctx['text1']); + let b3 = text(ctx['text2']); + return multi([b2, b3]); + } +}" +`; + +exports[`simple templates, mostly static two t-escs next to each other 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2 = text(ctx['text1']); + let b3 = text(ctx['text2']); + return multi([b2, b3]); + } +}" +`; + +exports[`simple templates, mostly static two t-escs next to each other, in a div 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['text1']; + let d2 = ctx['text2']; + return block1([d1, d2]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/t_call.test.ts.snap b/tests/qweb/__snapshots__/t_call.test.ts.snap new file mode 100644 index 00000000..7ce3b1f6 --- /dev/null +++ b/tests/qweb/__snapshots__/t_call.test.ts.snap @@ -0,0 +1,1031 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-call (template calling) basic caller 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`ok\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call (template calling) basic caller 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`_basic-callee\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) basic caller, no parent node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`ok\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call (template calling) basic caller, no parent node 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`_basic-callee\`); + + return function template(ctx, node, key = \\"\\") { + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call (template calling) call with several sub nodes on same line 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html(ctx[zero]); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) call with several sub nodes on same line 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`hey\`); + let block5 = createBlock(\`yay\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b3 = block3(); + let b4 = text(\` \`); + let b5 = block5(); + let b2 = multi([b3, b4, b5]); + ctx[zero] = b2; + let b6 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b6]); + } +}" +`; + +exports[`t-call (template calling) cascading t-call t-raw='0' 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    cascade 2
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html(ctx[zero]); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) cascading t-call t-raw='0' 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`finalTemplate\`); + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`cascade 1\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b3 = block3(); + let b4 = html(ctx[zero]); + let b2 = multi([b3, b4]); + ctx[zero] = b2; + let b5 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b5]); + } +}" +`; + +exports[`t-call (template calling) cascading t-call t-raw='0' 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`subSubTemplate\`); + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`cascade 0\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b3 = block3(); + let b4 = html(ctx[zero]); + let b2 = multi([b3, b4]); + ctx[zero] = b2; + let b5 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b5]); + } +}" +`; + +exports[`t-call (template calling) cascading t-call t-raw='0' 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`subTemplate\`); + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`hey\`); + let block5 = createBlock(\`yay\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b3 = block3(); + let b4 = text(\` \`); + let b5 = block5(); + let b2 = multi([b3, b4, b5]); + ctx[zero] = b2; + let b6 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b6]); + } +}" +`; + +exports[`t-call (template calling) cascading t-call t-raw='0', without external divs 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`cascade 2\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = block2(); + let b3 = html(ctx[zero]); + return multi([b2, b3]); + } +}" +`; + +exports[`t-call (template calling) cascading t-call t-raw='0', without external divs 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`finalTemplate\`); + + let block2 = createBlock(\`cascade 1\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = block2(); + let b3 = html(ctx[zero]); + let b1 = multi([b2, b3]); + ctx[zero] = b1; + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call (template calling) cascading t-call t-raw='0', without external divs 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`subSubTemplate\`); + + let block2 = createBlock(\`cascade 0\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = block2(); + let b3 = html(ctx[zero]); + let b1 = multi([b2, b3]); + ctx[zero] = b1; + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call (template calling) cascading t-call t-raw='0', without external divs 4`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`subTemplate\`); + + let block2 = createBlock(\`hey\`); + let block4 = createBlock(\`yay\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = block2(); + let b3 = text(\` \`); + let b4 = block4(); + let b1 = multi([b2, b3, b4]); + ctx[zero] = b1; + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call (template calling) dynamic t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['val']; + return block1([d1]); + } +}" +`; + +exports[`t-call (template calling) dynamic t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['val']; + return block1([d1]); + } +}" +`; + +exports[`t-call (template calling) dynamic t-call 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + const template2 = (ctx['template']); + let b2 = call(template2, ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) inherit context 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(ctx['foo']); + } +}" +`; + +exports[`t-call (template calling) inherit context 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`foo\`] = 1; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) recursive template, part 1 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`recursive\`); + + let block1 = createBlock(\`
    hey
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (false) { + b2 = callTemplate_2(ctx, node, key + \`__1\`); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) recursive template, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`nodeTemplate\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + ctx[\`node\`] = ctx['root']; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) recursive template, part 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`nodeTemplate\`); + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let d1 = ctx['node'].val; + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['node'].children||[]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`subtree\`] = v2[i1]; + ctx[\`subtree_first\`] = i1 === 0; + ctx[\`subtree_last\`] = i1 === v2.length - 1; + ctx[\`subtree_index\`] = i1; + ctx[\`subtree_value\`] = k2[i1]; + let key1 = ctx['subtree_index']; + ctx = Object.create(ctx); + ctx[\`node\`] = ctx['subtree']; + c2[i1] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}\`), key1); + ctx = ctx.__proto__; + } + let b2 = list(c2); + return block1([d1], [b2]); + } +}" +`; + +exports[`t-call (template calling) recursive template, part 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`nodeTemplate\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + ctx[\`node\`] = ctx['root']; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) recursive template, part 3 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`nodeTemplate\`); + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let d1 = ctx['node'].val; + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['node'].children||[]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`subtree\`] = v2[i1]; + ctx[\`subtree_first\`] = i1 === 0; + ctx[\`subtree_last\`] = i1 === v2.length - 1; + ctx[\`subtree_index\`] = i1; + ctx[\`subtree_value\`] = k2[i1]; + let key1 = ctx['subtree_index']; + ctx = Object.create(ctx); + ctx[\`node\`] = ctx['subtree']; + c2[i1] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}\`), key1); + ctx = ctx.__proto__; + } + let b2 = list(c2); + return block1([d1], [b2]); + } +}" +`; + +exports[`t-call (template calling) recursive template, part 4: with t-set recursive index 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`nodeTemplate\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + ctx[\`recursive_idx\`] = 1; + ctx[\`node\`] = ctx['root']; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) recursive template, part 4: with t-set recursive index 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`nodeTemplate\`); + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`recursive_idx\`] = ctx['recursive_idx']+1; + let d1 = ctx['node'].val; + let d2 = ctx['recursive_idx']; + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['node'].children||[]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`subtree\`] = v2[i1]; + ctx[\`subtree_first\`] = i1 === 0; + ctx[\`subtree_last\`] = i1 === v2.length - 1; + ctx[\`subtree_index\`] = i1; + ctx[\`subtree_value\`] = k2[i1]; + let key1 = ctx['subtree_index']; + ctx = Object.create(ctx); + ctx[\`node\`] = ctx['subtree']; + c2[i1] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}\`), key1); + ctx = ctx.__proto__; + } + let b2 = list(c2); + return block1([d1, d2], [b2]); + } +}" +`; + +exports[`t-call (template calling) scoped parameters 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(\`ok\`); + } +}" +`; + +exports[`t-call (template calling) scoped parameters 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + ctx[\`foo\`] = 42; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + ctx = ctx.__proto__; + let d1 = ctx['foo']; + return block1([d1], [b2]); + } +}" +`; + +exports[`t-call (template calling) scoped parameters, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(ctx['foo']); + } +}" +`; + +exports[`t-call (template calling) scoped parameters, part 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`foo\`] = 11; + ctx = Object.create(ctx); + ctx[\`foo\`] = 42; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + ctx = ctx.__proto__; + let d1 = ctx['foo']; + return block1([d1], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-call allowed on a non t node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-call allowed on a non t node 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`ok\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call (template calling) t-call with body content as root of a template 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html(ctx[zero]); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-call with body content as root of a template 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`antony\`); + + let block1 = createBlock(\`

    antony

    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b1 = block1(); + ctx[zero] = b1; + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call (template calling) t-call with t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['flag']) { + b2 = callTemplate_2(ctx, node, key + \`__1\`); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-call with t-if 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`ok\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call (template calling) t-call with t-set inside and body text content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + ctx[\`val\`] = \`yip yip\`; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-call with t-set inside and body text content 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['val']; + return block1([d1]); + } +}" +`; + +exports[`t-call (template calling) t-call with t-set inside and outside 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['list']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`v\`] = v2[i1]; + ctx[\`v_first\`] = i1 === 0; + ctx[\`v_last\`] = i1 === v2.length - 1; + ctx[\`v_index\`] = i1; + ctx[\`v_value\`] = k2[i1]; + let key1 = ctx['v_index']; + ctx[\`val\`] = ctx['v'].val; + ctx = Object.create(ctx); + ctx[\`val3\`] = ctx['val']*3; + c2[i1] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}\`), key1); + ctx = ctx.__proto__; + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-call with t-set inside and outside 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['val3']; + return block1([d1]); + } +}" +`; + +exports[`t-call (template calling) t-call with t-set inside and outside. 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['list']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`v\`] = v2[i1]; + ctx[\`v_first\`] = i1 === 0; + ctx[\`v_last\`] = i1 === v2.length - 1; + ctx[\`v_index\`] = i1; + ctx[\`v_value\`] = k2[i1]; + let key1 = ctx['v_index']; + ctx[\`val\`] = ctx['v'].val; + ctx = Object.create(ctx); + ctx[\`val3\`] = ctx['val']*3; + c2[i1] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}\`), key1); + ctx = ctx.__proto__; + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-call with t-set inside and outside. 2 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['val3']; + let b2 = block2([d1]); + let b3 = text(ctx['w']); + return multi([b2, b3]); + } +}" +`; + +exports[`t-call (template calling) t-call with t-set inside and outside. 2 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`main\`); + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`w\`] = 'fromwrapper'; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-call, conditional and t-set in t-call body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    callee1
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call (template calling) t-call, conditional and t-set in t-call body 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    callee2
    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['v']; + return block1([d1]); + } +}" +`; + +exports[`t-call (template calling) t-call, conditional and t-set in t-call body 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`callee1\`); + const callTemplate_4 = getTemplate(\`callee2\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2,b3; + ctx[\`v1\`] = 'elif'; + if (ctx['v1']==='if') { + b2 = callTemplate_2(ctx, node, key + \`__1\`); + } else if (ctx['v1']==='elif') { + ctx = Object.create(ctx); + ctx[\`v\`] = 'success'; + b3 = callTemplate_4(ctx, node, key + \`__3\`); + ctx = ctx.__proto__; + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`t-call (template calling) t-esc inside t-call, with t-set outside 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`v\`] = \`Hi\`; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-call (template calling) t-esc inside t-call, with t-set outside 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['v']; + return block1([d1]); + } +}" +`; + +exports[`t-call (template calling) with unused body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    ok
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call (template calling) with unused body 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b1 = text(\`WHEEE\`); + ctx[zero] = b1; + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call (template calling) with unused setbody 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    ok
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`t-call (template calling) with unused setbody 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + ctx[\`qux\`] = 3; + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call (template calling) with used body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx[zero]; + return block1([d1]); + } +}" +`; + +exports[`t-call (template calling) with used body 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b1 = text(\`ok\`); + ctx[zero] = b1; + return callTemplate_2(ctx, node, key + \`__1\`); + } +}" +`; + +exports[`t-call (template calling) with used setbody 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return text(ctx['foo']); + } +}" +`; + +exports[`t-call (template calling) with used setbody 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + ctx[\`foo\`] = 'ok'; + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/t_debug_log.test.ts.snap b/tests/qweb/__snapshots__/t_debug_log.test.ts.snap new file mode 100644 index 00000000..38a44e29 --- /dev/null +++ b/tests/qweb/__snapshots__/t_debug_log.test.ts.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`debugging t-debug 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`hey\`); + + return function template(ctx, node, key = \\"\\") { + debugger; + let b2; + if (true) { + debugger; + b2 = block2(); + } + return block1([], [b2]); + } +}" +`; + +exports[`debugging t-log 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`foo\`] = 42; + console.log(ctx['foo']+3); + return block1(); + } +}" +`; diff --git a/tests/qweb/__snapshots__/t_esc.test.ts.snap b/tests/qweb/__snapshots__/t_esc.test.ts.snap new file mode 100644 index 00000000..b00e302b --- /dev/null +++ b/tests/qweb/__snapshots__/t_esc.test.ts.snap @@ -0,0 +1,200 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-esc div with falsy values 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['v1']; + let d2 = ctx['v2']; + let d3 = ctx['v3']; + let d4 = ctx['v4']; + let d5 = ctx['v5']; + return block1([d1, d2, d3, d4, d5]); + } +}" +`; + +exports[`t-esc escaping 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['var']; + return block1([d1]); + } +}" +`; + +exports[`t-esc escaping on a node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = 'ok'; + return block1([d1]); + } +}" +`; + +exports[`t-esc escaping on a node with a body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = withDefault('ok', \`nope\`); + return block1([d1]); + } +}" +`; + +exports[`t-esc escaping on a node with a body, as a default 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = withDefault(ctx['var'], \`nope\`); + return block1([d1]); + } +}" +`; + +exports[`t-esc falsy values in text nodes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2 = text(ctx['v1']); + let b3 = text(\`:\`); + let b4 = text(ctx['v2']); + let b5 = text(\`:\`); + let b6 = text(ctx['v3']); + let b7 = text(\`:\`); + let b8 = text(ctx['v4']); + let b9 = text(\`:\`); + let b10 = text(ctx['v5']); + return multi([b2, b3, b4, b5, b6, b7, b8, b9, b10]); + } +}" +`; + +exports[`t-esc literal 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = 'ok'; + return block1([d1]); + } +}" +`; + +exports[`t-esc t-esc is escaped 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`

    escaped

    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = block2(); + ctx[\`var\`] = b2; + let d1 = ctx['var']; + return block1([d1]); + } +}" +`; + +exports[`t-esc t-esc work with spread operator 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [...ctx['state'].list]; + return block1([d1]); + } +}" +`; + +exports[`t-esc t-esc=0 is escaped 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx[zero]; + return block1([d1]); + } +}" +`; + +exports[`t-esc t-esc=0 is escaped 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`

    escaped

    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = block2(); + ctx[zero] = b2; + let b3 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b3]); + } +}" +`; + +exports[`t-esc variable 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['var']; + return block1([d1]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/t_foreach.test.ts.snap b/tests/qweb/__snapshots__/t_foreach.test.ts.snap new file mode 100644 index 00000000..5a377cd3 --- /dev/null +++ b/tests/qweb/__snapshots__/t_foreach.test.ts.snap @@ -0,0 +1,524 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-foreach does not pollute the rendering context 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList([1]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + let key1 = ctx['item']; + c2[i1] = withKey(text(ctx['item']), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach iterate on items (on a element node) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList([1,2]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + let key1 = ctx['item']; + let d1 = ctx['item']; + c2[i1] = withKey(block3([d1]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach iterate on items 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList([3,2,1]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + ctx[\`item_index\`] = i1; + ctx[\`item_value\`] = k2[i1]; + let key1 = ctx['item']; + let b4 = text(\` [\`); + let b5 = text(ctx['item_index']); + let b6 = text(\`: \`); + let b7 = text(ctx['item']); + let b8 = text(\` \`); + let b9 = text(ctx['item_value']); + let b10 = text(\`] \`); + c2[i1] = withKey(multi([b4, b5, b6, b7, b8, b9, b10]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach iterate, dict param 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['value']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + ctx[\`item_index\`] = i1; + ctx[\`item_value\`] = k2[i1]; + let key1 = ctx['item_index']; + let b4 = text(\` [\`); + let b5 = text(ctx['item_index']); + let b6 = text(\`: \`); + let b7 = text(ctx['item']); + let b8 = text(\` \`); + let b9 = text(ctx['item_value']); + let b10 = text(\`] \`); + c2[i1] = withKey(multi([b4, b5, b6, b7, b8, b9, b10]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach iterate, position 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(Array(5)); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`elem\`] = v2[i1]; + ctx[\`elem_first\`] = i1 === 0; + ctx[\`elem_last\`] = i1 === v2.length - 1; + ctx[\`elem_index\`] = i1; + let key1 = ctx['elem']; + let b4,b5,b6,b7,b8,b9; + b4 = text(\` -\`); + if (ctx['elem_first']) { + b5 = text(\` first\`); + } + if (ctx['elem_last']) { + b6 = text(\` last\`); + } + b7 = text(\` (\`); + b8 = text(ctx['elem_index']); + b9 = text(\`) \`); + c2[i1] = withKey(multi([b4, b5, b6, b7, b8, b9]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach simple iteration (in a node) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList([3,2,1]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + let key1 = ctx['item']; + c2[i1] = withKey(text(ctx['item']), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach simple iteration 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k1, v1, l1, c1] = prepareList([3,2,1]); + for (let i1 = 0; i1 < l1; i1++) { + ctx[\`item\`] = v1[i1]; + let key1 = ctx['item']; + c1[i1] = withKey(text(ctx['item']), key1); + } + return list(c1); + } +}" +`; + +exports[`t-foreach simple iteration with two nodes inside 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block3 = createBlock(\`a\`); + let block4 = createBlock(\`b\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k1, v1, l1, c1] = prepareList([3,2,1]); + for (let i1 = 0; i1 < l1; i1++) { + ctx[\`item\`] = v1[i1]; + let key1 = ctx['item']; + let d1 = ctx['item']; + let b3 = block3([d1]); + let d2 = ctx['item']; + let b4 = block4([d2]); + c1[i1] = withKey(multi([b3, b4]), key1); + } + return list(c1); + } +}" +`; + +exports[`t-foreach t-call with body in t-foreach in t-foreach 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2 = text(\` [\`); + let b3 = text(ctx['a']); + let b4 = text(\`] [\`); + let b5 = text(ctx['b']); + let b6 = text(\`] [\`); + let b7 = text(ctx['c']); + let b8 = text(\`] \`); + return multi([b2, b3, b4, b5, b6, b7, b8]); + } +}" +`; + +exports[`t-foreach t-call with body in t-foreach in t-foreach 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    [][][]
    \`); + let block6 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['numbers']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`a\`] = v2[i1]; + ctx[\`a_first\`] = i1 === 0; + ctx[\`a_last\`] = i1 === v2.length - 1; + ctx[\`a_index\`] = i1; + ctx[\`a_value\`] = k2[i1]; + let key1 = ctx['a']; + ctx = Object.create(ctx); + const [k4, v4, l4, c4] = prepareList(ctx['letters']); + for (let i2 = 0; i2 < l4; i2++) { + ctx[\`b\`] = v4[i2]; + ctx[\`b_first\`] = i2 === 0; + ctx[\`b_last\`] = i2 === v4.length - 1; + ctx[\`b_index\`] = i2; + ctx[\`b_value\`] = k4[i2]; + let key2 = ctx['b']; + ctx = Object.create(ctx); + ctx[\`c\`] = 'x'+'_'+ctx['a']+'_'+ctx['b']; + c4[i2] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}__\${key2}\`), key2); + ctx = ctx.__proto__; + } + ctx = ctx.__proto__; + let b4 = list(c4); + let d1 = ctx['c']; + let b6 = block6([d1]); + c2[i1] = withKey(multi([b4, b6]), key1); + } + ctx = ctx.__proto__; + let b2 = list(c2); + let d2 = ctx['a']; + let d3 = ctx['b']; + let d4 = ctx['c']; + return block1([d2, d3, d4], [b2]); + } +}" +`; + +exports[`t-foreach t-call without body in t-foreach in t-foreach 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`c\`] = 'x'+'_'+ctx['a']+'_'+ctx['b']; + let b2 = text(\` [\`); + let b3 = text(ctx['a']); + let b4 = text(\`] [\`); + let b5 = text(ctx['b']); + let b6 = text(\`] [\`); + let b7 = text(ctx['c']); + let b8 = text(\`] \`); + return multi([b2, b3, b4, b5, b6, b7, b8]); + } +}" +`; + +exports[`t-foreach t-call without body in t-foreach in t-foreach 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    [][][]
    \`); + let block6 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['numbers']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`a\`] = v2[i1]; + ctx[\`a_first\`] = i1 === 0; + ctx[\`a_last\`] = i1 === v2.length - 1; + ctx[\`a_index\`] = i1; + ctx[\`a_value\`] = k2[i1]; + let key1 = ctx['a']; + ctx = Object.create(ctx); + const [k4, v4, l4, c4] = prepareList(ctx['letters']); + for (let i2 = 0; i2 < l4; i2++) { + ctx[\`b\`] = v4[i2]; + ctx[\`b_first\`] = i2 === 0; + ctx[\`b_last\`] = i2 === v4.length - 1; + ctx[\`b_index\`] = i2; + ctx[\`b_value\`] = k4[i2]; + let key2 = ctx['b']; + c4[i2] = withKey(callTemplate_2(ctx, node, key + \`__1__\${key1}__\${key2}\`), key2); + } + ctx = ctx.__proto__; + let b4 = list(c4); + let d1 = ctx['c']; + let b6 = block6([d1]); + c2[i1] = withKey(multi([b4, b6]), key1); + } + ctx = ctx.__proto__; + let b2 = list(c2); + let d2 = ctx['a']; + let d3 = ctx['b']; + let d4 = ctx['c']; + return block1([d2, d3, d4], [b2]); + } +}" +`; + +exports[`t-foreach t-foreach in t-foreach 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['numbers']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`number\`] = v2[i1]; + let key1 = ctx['number']; + ctx = Object.create(ctx); + const [k3, v3, l3, c3] = prepareList(ctx['letters']); + for (let i2 = 0; i2 < l3; i2++) { + ctx[\`letter\`] = v3[i2]; + let key2 = ctx['letter']; + let b5 = text(\` [\`); + let b6 = text(ctx['number']); + let b7 = text(ctx['letter']); + let b8 = text(\`] \`); + c3[i2] = withKey(multi([b5, b6, b7, b8]), key2); + } + ctx = ctx.__proto__; + c2[i1] = withKey(list(c3), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach t-foreach with t-if inside (no external node) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block3 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k1, v1, l1, c1] = prepareList(ctx['elems']); + for (let i1 = 0; i1 < l1; i1++) { + ctx[\`elem\`] = v1[i1]; + let key1 = ctx['elem'].id; + let b3; + if (ctx['elem'].id<3) { + let d1 = ctx['elem'].text; + b3 = block3([d1]); + } + c1[i1] = withKey(multi([b3]), key1); + } + return list(c1); + } +}" +`; + +exports[`t-foreach t-foreach with t-if inside 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block4 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['elems']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`elem\`] = v2[i1]; + let key1 = ctx['elem'].id; + let b4; + if (ctx['elem'].id<3) { + let d1 = ctx['elem'].text; + b4 = block4([d1]); + } + c2[i1] = withKey(multi([b4]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach throws error if invalid loop expression 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['abc']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + ctx[\`item_index\`] = i1; + let key1 = ctx['item']; + c2[i1] = withKey(block3(), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach warn if no key in some case 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList([1,2]); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + let key1 = i1; + let d1 = ctx['item']; + c2[i1] = withKey(block3([d1]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-foreach with t-memo 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + let assign = Object.assign; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`

    \`); + + return function template(ctx, node, key = \\"\\") { + let cache = ctx.cache || {}; + let nextCache = ctx.cache = {}; + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['items']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + let key1 = ctx['item'].id; + let memo1 = [ctx['item'].x] + let vnode1 = cache[key1]; + if (vnode1) { + if (shallowEqual(vnode1.memo, memo1)) { + c2[i1] = vnode1; + nextCache[key1] = vnode1; + continue; + } + } + let d1 = ctx['item'].x; + let d2 = ctx['item'].y; + c2[i1] = withKey(block3([d1, d2]), key1); + nextCache[key1] = assign(c2[i1], {memo: memo1}); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/t_if.test.ts.snap b/tests/qweb/__snapshots__/t_if.test.ts.snap new file mode 100644 index 00000000..0a6ae288 --- /dev/null +++ b/tests/qweb/__snapshots__/t_if.test.ts.snap @@ -0,0 +1,521 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-if a t-if next to a div 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block2 = createBlock(\`
    foo
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + b2 = block2(); + if (ctx['cond']) { + b3 = text(\`1\`); + } + return multi([b2, b3]); + } +}" +`; + +exports[`t-if a t-if with two inner nodes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block3 = createBlock(\`yip\`); + let block4 = createBlock(\`
    yip
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['condition']) { + let b3 = block3(); + let b4 = block4(); + b2 = multi([b3, b4]); + } + return multi([b2]); + } +}" +`; + +exports[`t-if boolean value condition elif (no outside node) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3,b4,b5; + if (ctx['color']=='black') { + b2 = text(\`black pearl\`); + } else if (ctx['color']=='yellow') { + b3 = text(\`yellow submarine\`); + } else if (ctx['color']=='red') { + b4 = text(\`red is dead\`); + } else { + b5 = text(\`beer\`); + } + return multi([b2, b3, b4, b5]); + } +}" +`; + +exports[`t-if boolean value condition elif 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3,b4,b5; + if (ctx['color']=='black') { + b2 = text(\`black pearl\`); + } else if (ctx['color']=='yellow') { + b3 = text(\`yellow submarine\`); + } else if (ctx['color']=='red') { + b4 = text(\`red is dead\`); + } else { + b5 = text(\`beer\`); + } + return block1([], [b2, b3, b4, b5]); + } +}" +`; + +exports[`t-if boolean value condition else 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    beginend
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['condition']) { + b2 = text(\`ok\`); + } else { + b3 = text(\`ok-else\`); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`t-if boolean value condition false else 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    beginend
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['condition']) { + b2 = text(\`fail\`); + } else { + b3 = text(\`fail-else\`); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`t-if can use some boolean operators in expressions 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3,b4,b5,b6,b7,b8,b9; + if (ctx['cond1']&&ctx['cond2']) { + b2 = text(\`and\`); + } + if (ctx['cond1']&&ctx['cond3']) { + b3 = text(\`nope\`); + } + if (ctx['cond1']||ctx['cond3']) { + b4 = text(\`or\`); + } + if (ctx['cond3']||ctx['cond4']) { + b5 = text(\`nope\`); + } + if (ctx['m']>3) { + b6 = text(\`mgt\`); + } + if (ctx['n']>3) { + b7 = text(\`ngt\`); + } + if (ctx['m']<3) { + b8 = text(\`mlt\`); + } + if (ctx['n']<3) { + b9 = text(\`nlt\`); + } + return block1([], [b2, b3, b4, b5, b6, b7, b8, b9]); + } +}" +`; + +exports[`t-if div containing a t-if with two inner nodes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`yip\`); + let block4 = createBlock(\`
    yip
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['condition']) { + let b3 = block3(); + let b4 = block4(); + b2 = multi([b3, b4]); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-if dynamic content after t-if with two children nodes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`

    1

    \`); + let block4 = createBlock(\`

    2

    \`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['condition']) { + let b3 = block3(); + let b4 = block4(); + b2 = multi([b3, b4]); + } + let d1 = ctx['text']; + return block1([d1], [b2]); + } +}" +`; + +exports[`t-if just a t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['condition']) { + b2 = text(\`ok\`); + } + return multi([b2]); + } +}" +`; + +exports[`t-if simple t-if/t-else 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['condition']) { + b2 = text(\`1\`); + } else { + b3 = text(\`2\`); + } + return multi([b2, b3]); + } +}" +`; + +exports[`t-if simple t-if/t-else in a div 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['condition']) { + b2 = text(\`1\`); + } else { + b3 = text(\`2\`); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`t-if t-esc with t-elif 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (false) { + b2 = text(\`abc\`); + } else { + b3 = text('x'); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`t-if t-esc with t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (true) { + b2 = text('x'); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-if t-if and t-else with two nodes 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block4 = createBlock(\`a\`); + let block5 = createBlock(\`b\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['condition']) { + b2 = text(\`1\`); + } else { + let b4 = block4(); + let b5 = block5(); + b3 = multi([b4, b5]); + } + return multi([b2, b3]); + } +}" +`; + +exports[`t-if t-if in a div 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['condition']) { + b2 = text(\`ok\`); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-if t-if in a t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`1\`); + + return function template(ctx, node, key = \\"\\") { + let b2; + if (ctx['cond1']) { + let b3; + if (ctx['cond2']) { + b3 = text(\`2\`); + } + b2 = block2([], [b3]); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-if t-if/t-else with more content 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['condition']) { + if (ctx['condition']) { + b2 = text(\`asf\`); + } + } else { + b3 = text(\`coucou\`); + } + return multi([b2, b3]); + } +}" +`; + +exports[`t-if t-set, then t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2; + ctx[\`title\`] = 'test'; + if (ctx['title']) { + b2 = text(ctx['title']); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-if t-set, then t-if, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`COUCOU\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2; + ctx[\`y\`] = true; + ctx[\`x\`] = ctx['y']; + if (ctx['x']) { + b2 = block2(); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-if t-set, then t-if, part 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`AAA\`); + let block3 = createBlock(\`BBB\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2,b3; + ctx[\`y\`] = false; + ctx[\`x\`] = ctx['y']; + if (ctx['x']) { + b2 = block2(); + } else if (!ctx['x']) { + b3 = block3(); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`t-if two consecutive t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['cond1']) { + b2 = text(\`1\`); + } + if (ctx['cond2']) { + b3 = text(\`2\`); + } + return multi([b2, b3]); + } +}" +`; + +exports[`t-if two consecutive t-if in a div 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['cond1']) { + b2 = text(\`1\`); + } + if (ctx['cond2']) { + b3 = text(\`2\`); + } + return block1([], [b2, b3]); + } +}" +`; + +exports[`t-if two t-ifs next to each other 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`\`); + let block4 = createBlock(\`

    1

    \`); + let block5 = createBlock(\`

    2

    \`); + + return function template(ctx, node, key = \\"\\") { + let b2,b3; + if (ctx['condition']) { + let d1 = ctx['text']; + b2 = block2([d1]); + } + if (ctx['condition']) { + let b4 = block4(); + let b5 = block5(); + b3 = multi([b4, b5]); + } + return block1([], [b2, b3]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/t_raw.test.ts.snap b/tests/qweb/__snapshots__/t_raw.test.ts.snap new file mode 100644 index 00000000..1d288e5f --- /dev/null +++ b/tests/qweb/__snapshots__/t_raw.test.ts.snap @@ -0,0 +1,181 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-raw literal 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html('ok'); + return block1([], [b2]); + } +}" +`; + +exports[`t-raw literal, no outside html element 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return html('ok'); + } +}" +`; + +exports[`t-raw multiple calls to t-raw 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    Greeter
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html(ctx[zero]); + let b3 = html(ctx[zero]); + return block1([], [b2, b3]); + } +}" +`; + +exports[`t-raw multiple calls to t-raw 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`coucou\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = block2(); + ctx[zero] = b2; + let b3 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b3]); + } +}" +`; + +exports[`t-raw not escaping 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html(ctx['var']); + return block1([], [b2]); + } +}" +`; + +exports[`t-raw t-raw and another sibling node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`hello\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html(ctx['var']); + return block1([], [b2]); + } +}" +`; + +exports[`t-raw t-raw on a node with a body, as a default 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b3 = text(\`nope\`); + let b2 = html(withDefault(ctx['var'], b3)); + return block1([], [b2]); + } +}" +`; + +exports[`t-raw t-raw on a node with a dom node in body, as a default 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + let block3 = createBlock(\`
    nope
    \`); + + return function template(ctx, node, key = \\"\\") { + let b3 = block3(); + let b2 = html(withDefault(ctx['var'], b3)); + return block1([], [b2]); + } +}" +`; + +exports[`t-raw t-raw with a in body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return html(ctx['var']); + } +}" +`; + +exports[`t-raw t-raw with comment 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html(ctx['var']); + return block1([], [b2]); + } +}" +`; + +exports[`t-raw t-raw with just a t-set t-value in body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + return html(ctx['var']); + } +}" +`; + +exports[`t-raw variable 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + let b2 = html(ctx['var']); + return block1([], [b2]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/t_ref.test.ts.snap b/tests/qweb/__snapshots__/t_ref.test.ts.snap new file mode 100644 index 00000000..35a3e78b --- /dev/null +++ b/tests/qweb/__snapshots__/t_ref.test.ts.snap @@ -0,0 +1,136 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-ref can get a dynamic ref on a node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + const refs = ctx.__owl__.refs; + const v1 = ctx['id']; + let d1 = (el) => refs[\`myspan\${v1}\`] = el; + return block1([d1]); + } +}" +`; + +exports[`t-ref can get a ref on a node 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + const refs = ctx.__owl__.refs; + let d1 = (el) => refs[\`myspan\`] = el; + return block1([d1]); + } +}" +`; + +exports[`t-ref ref in a t-call 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + const callTemplate_2 = getTemplate(\`sub\`); + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + let b2 = callTemplate_2(ctx, node, key + \`__1\`); + return block1([], [b2]); + } +}" +`; + +exports[`t-ref ref in a t-call 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    12
    \`); + + return function template(ctx, node, key = \\"\\") { + const refs = ctx.__owl__.refs; + let d1 = (el) => refs[\`name\`] = el; + return block1([d1]); + } +}" +`; + +exports[`t-ref ref in a t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const refs = ctx.__owl__.refs; + let b2; + if (ctx['condition']) { + let d1 = (el) => refs[\`name\`] = el; + b2 = block2([d1]); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-ref refs in a loop 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + const refs = ctx.__owl__.refs; + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['items']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`item\`] = v2[i1]; + let key1 = ctx['item']; + const v1 = ctx['item']; + let d1 = (el) => refs[\`\${v1}\`] = el; + let d2 = ctx['item']; + c2[i1] = withKey(block3([d1, d2]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-ref two refs, one in a t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`

    \`); + let block2 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + const refs = ctx.__owl__.refs; + let b2; + if (ctx['condition']) { + let d1 = (el) => refs[\`name\`] = el; + b2 = block2([d1]); + } + let d2 = (el) => refs[\`p\`] = el; + return block1([d2], [b2]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/t_set.test.ts.snap b/tests/qweb/__snapshots__/t_set.test.ts.snap new file mode 100644 index 00000000..09a422cd --- /dev/null +++ b/tests/qweb/__snapshots__/t_set.test.ts.snap @@ -0,0 +1,386 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-set evaluate value expression 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`value\`] = 1+2; + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`t-set evaluate value expression, part 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`value\`] = ctx['somevariable']+2; + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`t-set set from attribute literal (no outside div) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`value\`] = 'ok'; + return text(ctx['value']); + } +}" +`; + +exports[`t-set set from attribute literal 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`value\`] = 'ok'; + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`t-set set from attribute lookup 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`stuff\`] = ctx['value']; + let d1 = ctx['stuff']; + return block1([d1]); + } +}" +`; + +exports[`t-set set from body literal 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`value\`] = \`ok\`; + return text(ctx['value']); + } +}" +`; + +exports[`t-set set from body lookup 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = text(ctx['value']); + ctx[\`stuff\`] = b2; + let d1 = ctx['stuff']; + return block1([d1]); + } +}" +`; + +exports[`t-set set from empty body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`stuff\`] = null; + let d1 = ctx['stuff']; + return block1([d1]); + } +}" +`; + +exports[`t-set t-set and t-if 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2; + ctx[\`v\`] = ctx['value']; + if (ctx['v']==='ok') { + b2 = text(\`grimbergen\`); + } + return block1([], [b2]); + } +}" +`; + +exports[`t-set t-set body is evaluated immediately 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`v1\`] = 'before'; + let d1 = ctx['v1']; + let b2 = block2([d1]); + ctx[\`v2\`] = b2; + ctx[\`v1\`] = 'after'; + let b3 = html(ctx['v2']); + return block1([], [b3]); + } +}" +`; + +exports[`t-set t-set evaluates an expression only once 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`v\`] = ctx['value']+' artois'; + let d1 = ctx['v']; + let d2 = ctx['v']; + return block1([d1, d2]); + } +}" +`; + +exports[`t-set t-set should reuse variable if possible 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block3 = createBlock(\`
    v
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`v\`] = 1; + ctx = Object.create(ctx); + const [k2, v2, l2, c2] = prepareList(ctx['list']); + for (let i1 = 0; i1 < l2; i1++) { + ctx[\`elem\`] = v2[i1]; + ctx[\`elem_index\`] = i1; + let key1 = ctx['elem_index']; + let d1 = ctx['v']; + ctx[\`v\`] = ctx['elem']; + c2[i1] = withKey(block3([d1]), key1); + } + let b2 = list(c2); + return block1([], [b2]); + } +}" +`; + +exports[`t-set t-set with content and sub t-esc 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b3 = text(ctx['beep']); + let b4 = text(\` boop\`); + let b2 = multi([b3, b4]); + ctx[\`setvar\`] = b2; + let d1 = ctx['setvar']; + return block1([d1]); + } +}" +`; + +exports[`t-set t-set with t-value (falsy) and body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`v3\`] = false; + ctx[\`v1\`] = 'before'; + let d1 = ctx['v1']; + let b2 = block2([d1]); + ctx[\`v2\`] = withDefault(ctx['v3'], b2); + ctx[\`v1\`] = 'after'; + ctx[\`v3\`] = true; + let b3 = html(ctx['v2']); + return block1([], [b3]); + } +}" +`; + +exports[`t-set t-set with t-value (truthy) and body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`v3\`] = 'Truthy'; + ctx[\`v1\`] = 'before'; + let d1 = ctx['v1']; + let b2 = block2([d1]); + ctx[\`v2\`] = withDefault(ctx['v3'], b2); + ctx[\`v1\`] = 'after'; + ctx[\`v3\`] = false; + let b3 = html(ctx['v2']); + return block1([], [b3]); + } +}" +`; + +exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + if (ctx['flag']) { + ctx[\`ourvar\`] = \`1\`; + } else { + ctx[\`ourvar\`] = 0; + } + let d1 = ctx['ourvar']; + return block1([d1]); + } +}" +`; + +exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + if (ctx['flag']) { + ctx[\`ourvar\`] = 1; + } else { + ctx[\`ourvar\`] = \`0\`; + } + let d1 = ctx['ourvar']; + return block1([d1]); + } +}" +`; + +exports[`t-set t-set, t-if, and mix of expression/body lookup, 3 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2; + if (ctx['flag']) { + ctx[\`ourvar\`] = 1; + } else { + ctx[\`ourvar\`] = \`0\`; + } + b2 = text(ctx['ourvar']); + return multi([b2]); + } +}" +`; + +exports[`t-set value priority (with non text body 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + let block2 = createBlock(\`2\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + let b2 = block2(); + ctx[\`value\`] = withDefault(1, b2); + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; + +exports[`t-set value priority 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[\`value\`] = withDefault(1, \`2\`); + let d1 = ctx['value']; + return block1([d1]); + } +}" +`; diff --git a/tests/qweb/__snapshots__/white_space.test.ts.snap b/tests/qweb/__snapshots__/white_space.test.ts.snap new file mode 100644 index 00000000..ec2276d4 --- /dev/null +++ b/tests/qweb/__snapshots__/white_space.test.ts.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`white space handling consecutives whitespaces are condensed into a single space 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    abc
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`white space handling nothing is done in pre tags 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
       
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`white space handling nothing is done in pre tags 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    +          some text
    +        
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`white space handling nothing is done in pre tags 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    +          
    +        
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`white space handling white space only text nodes are condensed into a single space 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`white space handling whitespace only text nodes with newlines are removed 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers; + + let block1 = createBlock(\`
    abc
    \`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; diff --git a/tests/qweb/attributes.test.ts b/tests/qweb/attributes.test.ts new file mode 100644 index 00000000..6652654e --- /dev/null +++ b/tests/qweb/attributes.test.ts @@ -0,0 +1,382 @@ +import { xml } from "../../src"; +import { mount, patch } from "../../src/blockdom"; +import { makeTestFixture, renderToBdom, renderToString, snapshotEverything } from "../helpers"; + +snapshotEverything(); +// ----------------------------------------------------------------------------- +// attributes +// ----------------------------------------------------------------------------- + +describe("attributes", () => { + test("static attributes", () => { + const template = `
    `; + expect(renderToString(template)).toBe(`
    `); + }); + + test("two classes", () => { + const template = `
    `; + expect(renderToString(template)).toBe(`
    `); + }); + + test("static attributes with dashes", () => { + const template = `
    `; + expect(renderToString(template)).toBe(`
    `); + }); + + test("static attributes on void elements", () => { + const template = `Test`; + expect(renderToString(template)).toBe(`Test`); + }); + + test("dynamic attributes", () => { + const template = `
    `; + const result = renderToString(template); + expect(result).toBe(`
    `); + }); + + test("two dynamic attributes", () => { + const template = `
    `; + const result = renderToString(template); + expect(result).toBe(`
    `); + }); + + test("dynamic class attribute", () => { + const template = `
    `; + const result = renderToString(template, { c: "abc" }); + expect(result).toBe(`
    `); + }); + + test("dynamic empty class attribute", () => { + const template = `
    `; + const result = renderToString(template, { c: "" }); + expect(result).toBe(`
    `); + }); + + test("dynamic attribute with a dash", () => { + const template = `
    `; + const result = renderToString(template, { id: 32 }); + expect(result).toBe(`
    `); + }); + + test("dynamic formatted attributes with a dash", () => { + const template = `
    `; + const result = renderToString(template, { id: 32 }); + expect(result).toBe(`
    `); + }); + + test("fixed variable", () => { + const template = `
    `; + const result = renderToString(template, { value: "ok" }); + expect(result).toBe(`
    `); + }); + + test("dynamic attribute evaluating to 0", () => { + const template = `
    `; + const result = renderToString(template, { value: 0 }); + expect(result).toBe(`
    `); + }); + + test("dynamic class attribute evaluating to 0", () => { + const template = `
    `; + const result = renderToString(template, { value: 0 }); + expect(result).toBe(`
    `); + }); + + test("dynamic attribute falsy variable ", () => { + const template = `
    `; + const result = renderToString(template, { value: false }); + expect(result).toBe(`
    `); + }); + + test("tuple literal", () => { + const template = `
    `; + const result = renderToString(template); + expect(result).toBe(`
    `); + }); + + test("tuple variable", () => { + const template = `
    `; + const result = renderToString(template, { value: ["foo", "bar"] }); + expect(result).toBe(`
    `); + }); + + test("object", () => { + const template = `
    `; + const result = renderToString(template, { + value: { a: 1, b: 2, c: 3 }, + }); + expect(result).toBe(`
    `); + }); + + test("format literal", () => { + const template = `
    `; + const result = renderToString(template); + expect(result).toBe(`
    `); + }); + + test("format value", () => { + const template = `
    `; + const result = renderToString(template, { value: "a" }); + expect(result).toBe(`
    `); + }); + + test("t-attf-class", () => { + const template = `
    `; + const result = renderToString(template); + expect(result).toBe(`
    `); + }); + + test("t-attf-class with multiple classes", () => { + const template = `
    `; + const result = renderToString(template, { word: "world" }); + expect(result).toBe(`
    `); + }); + + test("t-attf-class with multiple classes separated by multiple spaces", () => { + const template = `
    `; + const result = renderToString(template, { word: "world" }); + expect(result).toBe(`
    `); + }); + + test("t-attf-class should combine with class", () => { + const template = `
    `; + const result = renderToString(template); + expect(result).toBe(`
    `); + }); + + test("from variables set previously", () => { + const template = `
    `; + const result = renderToString(template); + expect(result).toBe('
    '); + }); + + test("from variables set previously (no external node)", () => { + const template = ` + + `; + const result = renderToString(template); + expect(result).toBe(''); + }); + + test("from object variables set previously", () => { + // Note: standard qweb does not allow this... + const template = ` +
    + + +
    `; + const result = renderToString(template); + expect(result).toBe('
    '); + }); + + test("format expression", () => { + const template = `
    `; + const result = renderToString(template, { value: 5 }); + expect(result).toBe(`
    `); + }); + + test("format multiple", () => { + const template = `
    `; + const result = renderToString(template, { + value1: 0, + value2: 1, + value3: 2, + }); + expect(result).toBe(`
    `); + }); + + test("various escapes", () => { + const template = ` +
    `; + + const result = renderToString(template, { + bar: 0, + baz: 1, + qux: { qux: "<>" }, + }); + const expected = '
    '; + expect(result).toBe(expected); + }); + + test("various escapes 2", () => { + const template = `
    <
    `; + + const result = renderToString(template, {}); + const expected = "
    <
    "; + expect(result).toBe(expected); + }); + + test("t-att-class and class should combine together", () => { + const template = `
    `; + const result = renderToString(template, { value: "world" }); + expect(result).toBe(`
    `); + }); + + test("class and t-att-class should combine together", () => { + const template = `
    `; + const result = renderToString(template, { value: "world" }); + expect(result).toBe(`
    `); + }); + + test("class and t-attf-class with ternary operation", () => { + const template = `
    `; + const result = renderToString(template, { value: true }); + expect(result).toBe(`
    `); + }); + + test("t-att-class with object", () => { + const template = `
    `; + const result = renderToString(template, { b: true, d: false, f: true }); + expect(result).toBe(`
    `); + }); + + test("t-att-class with multiple classes", () => { + expect(renderToString(`
    `, { value: true })).toBe( + '
    ' + ); + expect(renderToString(`
    `, { value: true })).toBe( + '
    ' + ); + }); + + test("t-att-class with multiple classes, some of which are duplicate", () => { + expect( + renderToString(`
    `, { value: true }) + ).toBe('
    '); + expect( + renderToString(`
    `, { value: false }) + ).toBe('
    '); + }); + + test("changing an attribute with t-att-", () => { + // render input with initial value + const template = `
    `; + const bnode1 = renderToBdom(template, { v: "zucchini" }); + const fixture = makeTestFixture(); + mount(bnode1, fixture); + + expect(fixture.innerHTML).toBe('
    '); + + const bnode2 = renderToBdom(template, { v: "potato" }); + patch(bnode1, bnode2); + expect(fixture.innerHTML).toBe('
    '); + + const bnode3 = renderToBdom(template, { v: "" }); + patch(bnode1, bnode3); + // not sure about this. maybe we want to remove the attribute? + expect(fixture.innerHTML).toBe('
    '); + }); + + test("changing a class with t-att-class", () => { + // render input with initial value + const template = `
    `; + const bnode1 = renderToBdom(template, { v: "zucchini" }); + const fixture = makeTestFixture(); + mount(bnode1, fixture); + + expect(fixture.innerHTML).toBe('
    '); + + const bnode2 = renderToBdom(template, { v: "potato" }); + patch(bnode1, bnode2); + expect(fixture.innerHTML).toBe('
    '); + + const bnode3 = renderToBdom(template, { v: "" }); + patch(bnode1, bnode3); + // not sure about this. maybe we want to remove the attribute? + expect(fixture.innerHTML).toBe('
    '); + }); + + test("changing a class with t-att-class (preexisting class", () => { + // render input with initial value + const template = `
    `; + const bnode1 = renderToBdom(template, { v: "zucchini" }); + const fixture = makeTestFixture(); + mount(bnode1, fixture); + + expect(fixture.innerHTML).toBe('
    '); + + const bnode2 = renderToBdom(template, { v: "potato" }); + patch(bnode1, bnode2); + expect(fixture.innerHTML).toBe('
    '); + + const bnode3 = renderToBdom(template, { v: "" }); + patch(bnode1, bnode3); + // not sure about this. maybe we want to remove the attribute? + expect(fixture.innerHTML).toBe('
    '); + }); + + test("updating classes (with obj notation)", () => { + // render input with initial value + const template = `
    `; + const bnode1 = renderToBdom(template, { condition: true }); + const fixture = makeTestFixture(); + mount(bnode1, fixture); + + expect(fixture.innerHTML).toBe('
    '); + + const bnode2 = renderToBdom(template, { condition: false }); + patch(bnode1, bnode2); + expect(fixture.innerHTML).toBe('
    '); + + const bnode3 = renderToBdom(template, { condition: true }); + patch(bnode1, bnode3); + // not sure about this. maybe we want to remove the attribute? + expect(fixture.innerHTML).toBe('
    '); + }); +}); + +describe("special cases for some specific html attributes/properties", () => { + test("input type= checkbox, with t-att-checked", () => { + const template = ``; + const result = renderToString(template, { flag: true }); + expect(result).toBe(``); + }); + + test("various boolean html attributes", () => { + // the unique assertion here is the code snapshot automatically done by + // renderToString + xml` +
    + + +
    +
    +
    + `; + }); + + test("input with t-att-value", () => { + // render input with initial value + const template = ``; + const bnode1 = renderToBdom(template, { v: "zucchini" }); + const fixture = makeTestFixture(); + mount(bnode1, fixture); + const input = fixture.querySelector("input")!; + expect(input.value).toBe("zucchini"); + + // change value manually in input, to simulate user input + input.value = "tomato"; + expect(input.value).toBe("tomato"); + + // rerender with a different value, and patch actual dom, to check that + // input value was properly reset by owl + const bnode2 = renderToBdom(template, { v: "potato" }); + patch(bnode1, bnode2); + expect(input.value).toBe("potato"); + }); + + test("input of type checkbox with t-att-indeterminate", () => { + const template = ``; + const bnode1 = renderToBdom(template, { v: true }); + const fixture = makeTestFixture(); + mount(bnode1, fixture); + const input = fixture.querySelector("input")!; + expect(input.indeterminate).toBe(true); + }); +}); diff --git a/tests/qweb/comments.test.ts b/tests/qweb/comments.test.ts new file mode 100644 index 00000000..6b45b53a --- /dev/null +++ b/tests/qweb/comments.test.ts @@ -0,0 +1,29 @@ +import { renderToString, snapshotEverything } from "../helpers"; + +snapshotEverything(); + +// ----------------------------------------------------------------------------- +// comments +// ----------------------------------------------------------------------------- + +describe("comments", () => { + test("properly handle comments", () => { + const template = `
    hello owl
    `; + expect(renderToString(template)).toBe("
    hello owl
    "); + }); + + test("only a comment", () => { + const template = ``; + expect(renderToString(template)).toBe(``); + }); + + test("properly handle comments between t-if/t-else", () => { + const template = ` +
    + true + + owl +
    `; + expect(renderToString(template)).toBe("
    true
    "); + }); +}); diff --git a/tests/qweb/event_handling.test.ts b/tests/qweb/event_handling.test.ts new file mode 100644 index 00000000..cac23173 --- /dev/null +++ b/tests/qweb/event_handling.test.ts @@ -0,0 +1,304 @@ +import { TemplateSet } from "../../src/qweb/template_helpers"; +import { mount } from "../../src/blockdom"; +import { makeTestFixture, renderToBdom, renderToString, snapshotEverything } from "../helpers"; + +snapshotEverything(); +// ----------------------------------------------------------------------------- +// t-on +// ----------------------------------------------------------------------------- + +describe("t-on", () => { + function mountToFixture(template: string, ctx: any = {}, node?: any): HTMLDivElement { + if (!node) { + node = { component: ctx }; + ctx.__owl__ = node; + } + const block = renderToBdom(template, ctx, node); + const fixture = makeTestFixture(); + + mount(block, fixture); + return fixture; + } + + test("can bind event handler", () => { + const template = ``; + let a = 1; + const fixture = mountToFixture(template, { add: () => (a = 3) }); + expect(a).toBe(1); + fixture.querySelector("button")!.click(); + expect(a).toBe(3); + }); + + test("receive event in first argument", () => { + expect.assertions(2); + const template = ``; + const fixture = mountToFixture(template, { + add: (ev: any) => { + expect(ev).toBeInstanceOf(Event); + }, + }); + fixture.querySelector("button")!.click(); + }); + + test("can bind two event handlers", () => { + const template = ` + `; + let steps: string[] = []; + const fixture = mountToFixture(template, { + handleClick() { + steps.push("click"); + }, + handleDblClick() { + steps.push("dblclick"); + }, + }); + expect(steps).toEqual([]); + fixture.querySelector("button")!.click(); + expect(steps).toEqual(["click"]); + fixture.querySelector("button")!.dispatchEvent(new Event("dblclick", { bubbles: true })); + expect(steps).toEqual(["click", "dblclick"]); + }); + + test("can bind handlers with arguments", () => { + const template = ``; + let a = 1; + const fixture = mountToFixture(template, { add: (n: number) => (a = a + n) }); + expect(a).toBe(1); + fixture.querySelector("button")!.click(); + expect(a).toBe(6); + }); + + test("can bind handlers with object arguments", () => { + const template = ``; + let a = 1; + const fixture = mountToFixture(template, { add: ({ val }: any) => (a = a + val) }); + expect(a).toBe(1); + fixture.querySelector("button")!.click(); + expect(a).toBe(6); + }); + + test("can bind handlers with empty object", () => { + expect.assertions(2); + const template = ``; + const fixture = mountToFixture(template, { + doSomething(arg: any) { + expect(arg).toEqual({}); + }, + }); + fixture.querySelector("button")!.click(); + }); + + test("can bind handlers with empty object (with non empty inner string)", () => { + expect.assertions(2); + const template = ``; + const fixture = mountToFixture(template, { + doSomething(arg: any) { + expect(arg).toEqual({}); + }, + }); + fixture.querySelector("button")!.click(); + }); + + test("can bind handlers with empty object (with non empty inner string)", () => { + expect.assertions(2); + const template = ` + `; + const fixture = mountToFixture(template, { + activate(action: string) { + expect(action).toBe("someval"); + }, + }); + fixture.querySelector("a")!.click(); + }); + + test("handler is bound to proper owner", () => { + expect.assertions(2); + const template = ``; + let owner = { + add() { + expect(this).toBe(owner); + }, + }; + const fixture = mountToFixture(template, owner); + fixture.querySelector("button")!.click(); + }); + + test("handler is bound to proper owner, part 2", () => { + expect.assertions(2); + const template = ` + + + `; + let owner = { + add() { + expect(this).toBe(owner); + }, + }; + const fixture = mountToFixture(template, owner); + fixture.querySelector("button")!.click(); + }); + + test("handler is bound to proper owner, part 3", () => { + expect.assertions(3); + const context = new TemplateSet(); + const sub = ``; + const main = ``; + context.addTemplate("sub", sub); + context.addTemplate("main", main); + + let owner: any = { + add() { + expect(this).toBe(owner); + }, + }; + const node = { component: owner }; + owner.__owl__ = node; + const fixture = makeTestFixture(); + const render = context.getTemplate("main"); + const bdom = render(owner, node); + mount(bdom, fixture); + fixture.querySelector("button")!.click(); + }); + + test("handler is bound to proper owner, part 4", () => { + expect.assertions(3); + const context = new TemplateSet(); + const sub = ``; + const main = ` + + + `; + context.addTemplate("sub", sub); + context.addTemplate("main", main); + + let owner: any = { + add() { + expect(this).toBe(owner); + }, + }; + const node = { component: owner }; + owner.__owl__ = node; + const fixture = makeTestFixture(); + const render = context.getTemplate("main"); + const bdom = render(owner, node); + mount(bdom, fixture); + fixture.querySelector("button")!.click(); + }); + + test("t-on with inline statement", () => { + const template = ``; + let owner = { state: { counter: 0 } }; + const fixture = mountToFixture(template, owner); + expect(owner.state.counter).toBe(0); + fixture.querySelector("button")!.click(); + expect(owner.state.counter).toBe(1); + }); + + test("t-on with inline statement (function call)", () => { + const template = ``; + let owner = { + state: { + counter: 0, + incrementCounter: (inc: number) => { + owner.state.counter += inc; + }, + }, + }; + const fixture = mountToFixture(template, owner); + expect(owner.state.counter).toBe(0); + fixture.querySelector("button")!.click(); + expect(owner.state.counter).toBe(2); + }); + + test("t-on with inline statement, part 2", () => { + const template = ``; + let owner = { + state: { + flag: true, + }, + }; + const fixture = mountToFixture(template, owner); + expect(owner.state.flag).toBe(true); + fixture.querySelector("button")!.click(); + expect(owner.state.flag).toBe(false); + fixture.querySelector("button")!.click(); + expect(owner.state.flag).toBe(true); + }); + + test("t-on with inline statement, part 3", () => { + const template = ``; + let owner = { + someFunction(n: number) { + return n + 1; + }, + state: { + n: 11, + }, + }; + + const fixture = mountToFixture(template, owner); + expect(owner.state.n).toBe(11); + fixture.querySelector("button")!.click(); + expect(owner.state.n).toBe(4); + }); + + test("t-on with t-call", async () => { + expect.assertions(3); + const app = new TemplateSet(); + const sub = `

    lucas

    `; + const main = `
    `; + app.addTemplate("sub", sub); + app.addTemplate("main", main); + + let owner: any = { + update() { + expect(this).toBe(owner); + }, + }; + const node = { component: owner }; + owner.__owl__ = node; + + const fixture = makeTestFixture(); + const render = app.getTemplate("main"); + const bdom = render(owner, node); + mount(bdom, fixture); + fixture.querySelector("p")!.click(); + }); + + test("t-on, with arguments and t-call", async () => { + expect.assertions(4); + const app = new TemplateSet(); + const sub = `

    lucas

    `; + const main = `
    `; + app.addTemplate("sub", sub); + app.addTemplate("main", main); + + let owner: any = { + update(val: number) { + expect(this).toBe(owner); + expect(val).toBe(444); + }, + value: 444, + }; + + const node = { component: owner }; + owner.__owl__ = node; + + const fixture = makeTestFixture(); + const render = app.getTemplate("main"); + const bdom = render(owner, node); + mount(bdom, fixture); + fixture.querySelector("p")!.click(); + }); + + test("nice error when t-on is evaluated with a missing event", () => { + const template = `
    `; + expect(() => renderToString(template, { someMethod() {} })).toThrow( + "Missing event name with t-on directive" + ); + }); +}); diff --git a/tests/qweb/inline_expressions.test.ts b/tests/qweb/inline_expressions.test.ts new file mode 100644 index 00000000..b08e9a61 --- /dev/null +++ b/tests/qweb/inline_expressions.test.ts @@ -0,0 +1,209 @@ +import { compileExpr, tokenize } from "../../src/qweb/inline_expressions"; + +describe("tokenizer", () => { + test("simple tokens", () => { + expect(tokenize("1.3")).toEqual([{ type: "VALUE", value: "1.3" }]); + + expect(tokenize("{}")).toEqual([ + { type: "LEFT_BRACE", value: "{" }, + { type: "RIGHT_BRACE", value: "}" }, + ]); + expect(tokenize("{ }}")).toEqual([ + { type: "LEFT_BRACE", value: "{" }, + { type: "RIGHT_BRACE", value: "}" }, + { type: "RIGHT_BRACE", value: "}" }, + ]); + expect(tokenize("a")).toEqual([{ type: "SYMBOL", value: "a" }]); + expect(tokenize("true")).toEqual([{ type: "SYMBOL", value: "true" }]); + expect(tokenize("abcde")).toEqual([{ type: "SYMBOL", value: "abcde" }]); + expect(tokenize("_ab2")).toEqual([{ type: "SYMBOL", value: "_ab2" }]); + expect(tokenize("$ab2")).toEqual([{ type: "SYMBOL", value: "$ab2" }]); + expect(tokenize("ABC")).toEqual([{ type: "SYMBOL", value: "ABC" }]); + + expect(tokenize("{a: 2}")).toEqual([ + { type: "LEFT_BRACE", value: "{" }, + { type: "SYMBOL", value: "a" }, + { type: "COLON", value: ":" }, + { type: "VALUE", value: "2" }, + { type: "RIGHT_BRACE", value: "}" }, + ]); + expect(tokenize("a,")).toEqual([ + { type: "SYMBOL", value: "a" }, + { type: "COMMA", value: "," }, + ]); + expect(tokenize("][")).toEqual([ + { type: "RIGHT_BRACKET", value: "]" }, + { type: "LEFT_BRACKET", value: "[" }, + ]); + }); + + test("various operators", () => { + expect(tokenize(">= <= < > !== !=")).toEqual([ + { type: "OPERATOR", value: ">=" }, + { type: "OPERATOR", value: "<=" }, + { type: "OPERATOR", value: "<" }, + { type: "OPERATOR", value: ">" }, + { type: "OPERATOR", value: "!==" }, + { type: "OPERATOR", value: "!=" }, + ]); + expect(tokenize("typeof a")).toEqual([ + { type: "OPERATOR", value: "typeof " }, + { type: "SYMBOL", value: "a" }, + ]); + + expect(tokenize("a...1")).toEqual([ + { type: "SYMBOL", value: "a" }, + { type: "OPERATOR", value: "..." }, + { type: "VALUE", value: "1" }, + ]); + + expect(tokenize("a in b")).toEqual([ + { type: "SYMBOL", value: "a" }, + { type: "OPERATOR", value: "in " }, + { type: "SYMBOL", value: "b" }, + ]); + }); + + test("strings", () => { + expect(() => tokenize("'")).toThrow("Invalid expression"); + expect(() => tokenize("'\\")).toThrow("Invalid expression"); + expect(() => tokenize("'\\'")).toThrow("Invalid expression"); + expect(tokenize("'hello ged'")).toEqual([{ type: "VALUE", value: "'hello ged'" }]); + expect(tokenize("'hello \\'ged\\''")).toEqual([{ type: "VALUE", value: "'hello \\'ged\\''" }]); + + expect(() => tokenize('"')).toThrow("Invalid expression"); + expect(() => tokenize('"\\"')).toThrow("Invalid expression"); + expect(tokenize('"hello ged"')).toEqual([{ type: "VALUE", value: '"hello ged"' }]); + expect(tokenize('"hello ged"}')).toEqual([ + { type: "VALUE", value: '"hello ged"' }, + { type: "RIGHT_BRACE", value: "}" }, + ]); + expect(tokenize('"hello \\"ged\\""')).toEqual([{ type: "VALUE", value: '"hello \\"ged\\""' }]); + }); +}); + +describe("expression evaluation", () => { + test("simple static values", () => { + expect(compileExpr("1")).toBe("1"); + expect(compileExpr("1 ")).toBe("1"); + expect(compileExpr("'some string#/, {' ")).toBe("'some string#/, {'"); + expect(compileExpr("{ } ")).toBe("{}"); + expect(compileExpr("{a: 1} ")).toBe("{a:1}"); + expect(compileExpr("{a: 1, b: 2 } ")).toBe("{a:1,b:2}"); + expect(compileExpr("[] ")).toBe("[]"); + expect(compileExpr("[1] ")).toBe("[1]"); + expect(compileExpr("['1', '2'] ")).toBe("['1','2']"); + expect(compileExpr("['1', \"2\"] ")).toBe("['1',\"2\"]"); + }); + + test("various types of 'words'", () => { + expect(compileExpr("true")).toBe("true"); + expect(compileExpr("false")).toBe("false"); + expect(compileExpr("debugger")).toBe("debugger"); + }); + + test("parenthesis", () => { + expect(compileExpr("(1)")).toBe("(1)"); + expect(compileExpr("a*(1 +3)")).toBe("ctx['a']*(1+3)"); + }); + + test("objects and sub objects", () => { + expect(compileExpr("{a:{b:1}} ")).toBe("{a:{b:1}}"); + }); + + test("arrays and objects", () => { + expect(compileExpr("[{b:1}] ")).toBe("[{b:1}]"); + expect(compileExpr("{a: []} ")).toBe("{a:[]}"); + expect(compileExpr("[{b:1, c: [1, {d: {e: 3}} ]}] ")).toBe("[{b:1,c:[1,{d:{e:3}}]}]"); + }); + + test("dot operator", () => { + expect(compileExpr("a.b")).toBe("ctx['a'].b"); + expect(compileExpr("a.b.c")).toBe("ctx['a'].b.c"); + }); + + test("various unary operators", () => { + expect(compileExpr("!flag")).toBe("!ctx['flag']"); + expect(compileExpr("-3")).toBe("-3"); + expect(compileExpr("-a")).toBe("-ctx['a']"); + expect(compileExpr("typeof a")).toBe("typeof ctx['a']"); + }); + + test("various binary operators", () => { + expect(compileExpr("color == 'black'")).toBe("ctx['color']=='black'"); + expect(compileExpr("a || b")).toBe("ctx['a']||ctx['b']"); + expect(compileExpr("color === 'black'")).toBe("ctx['color']==='black'"); + expect(compileExpr("'li_'+item")).toBe("'li_'+ctx['item']"); + expect(compileExpr("state.val > 1")).toBe("ctx['state'].val>1"); + expect(compileExpr("a in b")).toBe("ctx['a']in ctx['b']"); + }); + + test("boolean operations", () => { + expect(compileExpr("a && b")).toBe("ctx['a']&&ctx['b']"); + }); + + test("ternary operators", () => { + expect(compileExpr("a ? b: '2'")).toBe("ctx['a']?ctx['b']:'2'"); + expect(compileExpr("a ? b: (c or '2') ")).toBe("ctx['a']?ctx['b']:(ctx['c']||'2')"); + expect(compileExpr("a ? {test:c}: [1,u]")).toBe("ctx['a']?{test:ctx['c']}:[1,ctx['u']]"); + }); + + test("word replacement", () => { + expect(compileExpr("a or b")).toBe("ctx['a']||ctx['b']"); + expect(compileExpr("a and b")).toBe("ctx['a']&&ctx['b']"); + }); + + test("function calls", () => { + expect(compileExpr("a()")).toBe("ctx['a']()"); + expect(compileExpr("a(1)")).toBe("ctx['a'](1)"); + expect(compileExpr("a(1,2)")).toBe("ctx['a'](1,2)"); + expect(compileExpr("a(1,2,{a:[a]})")).toBe("ctx['a'](1,2,{a:[ctx['a']]})"); + expect(compileExpr("'x'.toUpperCase()")).toBe("'x'.toUpperCase()"); + expect(compileExpr("'x'.toUpperCase({a: 3})")).toBe("'x'.toUpperCase({a:3})"); + }); + + test("arrow functions", () => { + expect(compileExpr("list.map(e => e.val)")).toBe("ctx['list'].map(e=>e.val)"); + expect(compileExpr("list.map(e => a + e)")).toBe("ctx['list'].map(e=>ctx['a']+e)"); + expect(compileExpr("list.map((e) => e)")).toBe("ctx['list'].map((e)=>e)"); + expect(compileExpr("list.map((elem, index) => elem + index)")).toBe( + "ctx['list'].map((elem,index)=>elem+index)" + ); + }); + + test("assignation", () => { + expect(compileExpr("a = b")).toBe("ctx['a']=ctx['b']"); + expect(compileExpr("a += b")).toBe("ctx['a']+=ctx['b']"); + expect(compileExpr("a -= b")).toBe("ctx['a']-=ctx['b']"); + expect(compileExpr("a.b = !a.b")).toBe("ctx['a'].b=!ctx['a'].b"); + }); + + test("spread operator", () => { + expect(compileExpr("[...state.list]")).toBe("[...ctx['state'].list]"); + expect(compileExpr("f(...state.list)")).toBe("ctx['f'](...ctx['state'].list)"); + expect(compileExpr("f([...list])")).toBe("ctx['f']([...ctx['list']])"); + }); + + test("works with builtin properties", () => { + expect(compileExpr("state.constructor.name")).toBe("ctx['state'].constructor.name"); + }); + + test("works with shortcut object key description", () => { + expect(compileExpr("{a}")).toBe("{a:ctx['a']}"); + expect(compileExpr("{a,b}")).toBe("{a:ctx['a'],b:ctx['b']}"); + expect(compileExpr("{a,b:3,c}")).toBe("{a:ctx['a'],b:3,c:ctx['c']}"); + }); + + test("template strings", () => { + expect(compileExpr("`hey`")).toBe("`hey`"); + expect(compileExpr("`hey ${you}`")).toBe("`hey ${ctx['you']}`"); + expect(compileExpr("`hey ${1 + 2}`")).toBe("`hey ${1+2}`"); + }); + + test("works with short object description and lists ", () => { + expect(compileExpr("[a, b]")).toBe("[ctx['a'],ctx['b']]"); + expect(compileExpr("[a, b, c]")).toBe("[ctx['a'],ctx['b'],ctx['c']]"); + expect(compileExpr("[a, {b, c},d]")).toBe("[ctx['a'],{b:ctx['b'],c:ctx['c']},ctx['d']]"); + expect(compileExpr("{a:[b, {c, d: e}]}")).toBe("{a:[ctx['b'],{c:ctx['c'],d:ctx['e']}]}"); + }); +}); diff --git a/tests/qweb/misc.test.ts b/tests/qweb/misc.test.ts new file mode 100644 index 00000000..e3ff402c --- /dev/null +++ b/tests/qweb/misc.test.ts @@ -0,0 +1,262 @@ +import { xml } from "../../src"; +import { renderToString, snapshotEverything, TestContext, trim } from "../helpers"; + +snapshotEverything(); + +// ----------------------------------------------------------------------------- +// misc +// ----------------------------------------------------------------------------- + +describe("misc", () => { + test("global", () => { + const context = new TestContext(); + const _calleeAsc = ``; + const _calleeUsesFoo = `foo default`; + const _calleeAscToto = `
    toto default
    `; + const caller = ` +
    + + + + + + + + + + + + +
    `; + context.addTemplate("_callee-asc", _calleeAsc); + context.addTemplate("_callee-uses-foo", _calleeUsesFoo); + context.addTemplate("_callee-asc-toto", _calleeAscToto); + context.addTemplate("caller", caller); + + const result = trim(context.renderToString("caller")); + const expected = trim(` +
    + 4 + + aaa + foo default + bbb + + + 5 + + aaa + foo default + bbb + + + 6 + + aaa + foo default + bbb + + +
    toto default
    +
    + `); + expect(result).toBe(expected); + }); + + test("complex template", () => { + const template = ` +
    +
    + + + preparing + +
    + + + +
    +
    +
    + +
    +
    +
    `; + + const context = { + options: { more: true }, + klass: "info", + batch: { + id: 3, + has_warning: true, + formated_age: "1 year", + state: "preparing", + slot_ids: [], + }, + commit_links: [], + }; + const expected = `
    preparing
    `; + expect(renderToString(template, context)).toBe(expected); + }); + + test("other complex template", () => { + xml` +
    +
    + +
    + +
    +
    + +
    +
    Triggers
    + + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +

    No project

    +
    +
    + + +
    +
    +
    +
    +
    `; + }); +}); diff --git a/tests/qweb/parser.test.ts b/tests/qweb/parser.test.ts new file mode 100644 index 00000000..1ed8e5ce --- /dev/null +++ b/tests/qweb/parser.test.ts @@ -0,0 +1,1129 @@ +import { ASTType, parse } from "../../src/qweb/parser"; + +describe("qweb parser", () => { + // --------------------------------------------------------------------------- + // texts and basic stuff + // --------------------------------------------------------------------------- + + test("simple text node", async () => { + expect(parse("foo")).toEqual({ + type: ASTType.Text, + value: "foo", + }); + }); + + test("text in t tag", async () => { + expect(parse("foo")).toEqual({ + type: ASTType.Text, + value: "foo", + }); + }); + + test("empty string", async () => { + expect(parse("")).toEqual({ + type: ASTType.Text, + value: "", + }); + }); + + test("white spaces are condensed into a single space", async () => { + expect(parse(" ")).toEqual({ + type: ASTType.Text, + value: " ", + }); + }); + + test("white spaces only text nodes with newlines are removed", async () => { + const template = ` +
    +
    `; + expect(parse(template)).toEqual({ + type: ASTType.DomNode, + tag: "div", + content: [], + attrs: {}, + on: {}, + ref: null, + }); + }); + + test("empty string in t tag", async () => { + expect(parse("")).toEqual({ + type: ASTType.Text, + value: "", + }); + }); + + test("simple comment node", async () => { + expect(parse("")).toEqual({ + type: ASTType.Comment, + value: " comment ", + }); + }); + + test("empty div", async () => { + expect(parse("
    ")).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [], + }); + }); + + test("div with some text", async () => { + expect(parse("
    some text
    ")).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.Text, value: "some text" }], + }); + }); + + test("div with some more content", async () => { + expect(parse("
    some textinside
    ")).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [ + { type: ASTType.Text, value: "some text" }, + { + type: ASTType.DomNode, + tag: "span", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.Text, value: "inside" }], + }, + ], + }); + }); + + test("multiple root dom nodes", async () => { + expect(parse("
    ")).toEqual({ + type: ASTType.Multi, + content: [ + { type: ASTType.DomNode, tag: "div", attrs: {}, on: {}, ref: null, content: [] }, + { type: ASTType.DomNode, tag: "span", attrs: {}, on: {}, ref: null, content: [] }, + ], + }); + }); + + test("dom node next to text node", async () => { + expect(parse("some text")).toEqual({ + type: ASTType.Multi, + content: [ + { type: ASTType.Text, value: "some text" }, + { type: ASTType.DomNode, tag: "span", attrs: {}, on: {}, ref: null, content: [] }, + ], + }); + }); + + test("dom node with class attribute", async () => { + expect(parse(`
    foo
    `)).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: { class: "abc" }, + on: {}, + ref: null, + content: [{ type: ASTType.Text, value: "foo" }], + }); + }); + + // --------------------------------------------------------------------------- + // t-esc + // --------------------------------------------------------------------------- + + test("t-esc node", async () => { + expect(parse(``)).toEqual({ + type: ASTType.TEsc, + expr: "text", + defaultValue: "", + }); + expect(parse(``)).toEqual({ + type: ASTType.TEsc, + expr: "text", + defaultValue: "", + }); + }); + + test("dom node with t-esc", async () => { + expect(parse(``)).toEqual({ + type: ASTType.DomNode, + tag: "span", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "" }], + }); + }); + + test("t-esc node with default value", async () => { + expect(parse(`hey`)).toEqual({ + type: ASTType.TEsc, + expr: "text", + defaultValue: "hey", + }); + }); + + test("dom node with t-esc with default value", async () => { + expect(parse(`
    hey
    `)).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "hey" }], + }); + }); + + // --------------------------------------------------------------------------- + // t-raw + // --------------------------------------------------------------------------- + + test("t-raw node", async () => { + expect(parse(``)).toEqual({ + type: ASTType.TRaw, + expr: "text", + body: null, + }); + }); + + test("t-raw node on a dom node", async () => { + expect(parse(`
    `)).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.TRaw, expr: "text", body: null }], + }); + }); + + test("t-raw node with body", async () => { + expect(parse(`
    body
    `)).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [ + { type: ASTType.TRaw, expr: "text", body: [{ type: ASTType.Text, value: "body" }] }, + ], + }); + }); + + // --------------------------------------------------------------------------- + // t-if + // --------------------------------------------------------------------------- + + test("t-if", async () => { + expect(parse(`
    hey
    `)).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [ + { + type: ASTType.TIf, + condition: "condition", + content: { + type: ASTType.Text, + value: "hey", + }, + tElif: null, + tElse: null, + }, + ], + }); + }); + + test("t-if (on dom node", async () => { + expect(parse(`
    hey
    `)).toEqual({ + type: ASTType.TIf, + condition: "condition", + content: { + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.Text, value: "hey" }], + }, + tElif: null, + tElse: null, + }); + }); + + test("t-if and t else", async () => { + expect(parse(`heyelse`)).toEqual({ + type: ASTType.TIf, + condition: "condition", + content: { + type: ASTType.Text, + value: "hey", + }, + tElif: null, + tElse: { + type: ASTType.Text, + value: "else", + }, + }); + }); + + test("t-if and t elif", async () => { + expect(parse(`heyelif`)).toEqual({ + type: ASTType.TIf, + condition: "condition", + content: { + type: ASTType.Text, + value: "hey", + }, + tElif: [ + { + condition: "cond2", + content: { type: ASTType.Text, value: "elif" }, + }, + ], + tElse: null, + }); + }); + + test("t-if, t-elif and t-else", async () => { + expect(parse(`heyelifelse`)).toEqual({ + type: ASTType.TIf, + condition: "c1", + content: { + type: ASTType.Text, + value: "hey", + }, + tElif: [ + { + condition: "c2", + content: { type: ASTType.Text, value: "elif" }, + }, + ], + tElse: { + type: ASTType.Text, + value: "else", + }, + }); + }); + + test("t-if, t-elif and t-else on a node", async () => { + expect( + parse(`
    hey

    elif

    else

    `) + ).toEqual({ + type: ASTType.TIf, + condition: "c1", + content: { + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [ + { + type: ASTType.Text, + value: "hey", + }, + ], + }, + tElif: [ + { + condition: "c2", + content: { + type: ASTType.DomNode, + tag: "h1", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.Text, value: "elif" }], + }, + }, + ], + tElse: { + type: ASTType.DomNode, + tag: "h2", + attrs: {}, + on: {}, + ref: null, + content: [ + { + type: ASTType.Text, + value: "else", + }, + ], + }, + }); + }); + + // --------------------------------------------------------------------------- + // t-set + // --------------------------------------------------------------------------- + + test("simple t-set expression", async () => { + expect(parse(``)).toEqual({ + type: ASTType.TSet, + name: "key", + value: "value", + defaultValue: null, + body: null, + }); + }); + + test("t-set expression with body", async () => { + expect(parse(`ok`)).toEqual({ + type: ASTType.TSet, + name: "key", + defaultValue: "ok", + value: null, + body: null, + }); + + expect(parse(`
    ok
    `)).toEqual({ + type: ASTType.TSet, + name: "v", + defaultValue: null, + value: null, + body: [ + { + type: ASTType.DomNode, + attrs: {}, + on: {}, + tag: "div", + ref: null, + content: [{ type: ASTType.Text, value: "ok" }], + }, + ], + }); + + expect(parse(`
    ok
    abc
    `)).toEqual({ + type: ASTType.TSet, + name: "v", + defaultValue: null, + value: null, + body: [ + { + type: ASTType.DomNode, + attrs: {}, + on: {}, + tag: "div", + ref: null, + content: [{ type: ASTType.Text, value: "ok" }], + }, + { type: ASTType.Text, value: "abc" }, + ], + }); + }); + + test("t-if and t-set expression with body", async () => { + expect(parse(`ok`)).toEqual({ + type: ASTType.TIf, + condition: "flag", + content: { + type: ASTType.TSet, + name: "key", + defaultValue: "ok", + value: null, + body: null, + }, + tElif: null, + tElse: null, + }); + }); + + test("t-if, t-else and t-set", async () => { + expect( + parse(`
    1
    `) + ).toEqual({ + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [ + { + type: ASTType.TIf, + condition: "flag", + content: { type: ASTType.Text, value: "1" }, + tElif: null, + tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null }, + }, + ], + }); + }); + + // --------------------------------------------------------------------------- + // t-foreach + // --------------------------------------------------------------------------- + + test("simple t-foreach expression", async () => { + expect(parse(``)).toEqual({ + type: ASTType.TForEach, + collection: "list", + elem: "item", + key: null, + hasNoComponent: true, + isOnlyChild: false, + body: { type: ASTType.TEsc, expr: "item", defaultValue: "" }, + memo: "", + hasNoFirst: true, + hasNoIndex: true, + hasNoLast: true, + hasNoValue: true, + }); + }); + + test("t-foreach expression with t-esc", async () => { + expect(parse(``)).toEqual({ + type: ASTType.TForEach, + collection: "list", + elem: "item", + key: null, + hasNoComponent: true, + isOnlyChild: false, + body: { type: ASTType.TEsc, expr: "item", defaultValue: "" }, + memo: "", + hasNoFirst: true, + hasNoIndex: true, + hasNoLast: true, + hasNoValue: true, + }); + }); + + test("t-foreach on a div expression with t-esc", async () => { + expect(parse(`
    `)).toEqual({ + type: ASTType.TForEach, + collection: "list", + elem: "item", + hasNoComponent: true, + isOnlyChild: false, + key: null, + body: { + type: ASTType.DomNode, + tag: "div", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }], + }, + memo: "", + hasNoFirst: true, + hasNoIndex: true, + hasNoLast: true, + hasNoValue: true, + }); + }); + + test("simple keyed t-foreach expression", async () => { + expect(parse(``)).toEqual({ + type: ASTType.TForEach, + collection: "list", + elem: "item", + hasNoComponent: true, + isOnlyChild: false, + key: "item.id", + body: { type: ASTType.TEsc, expr: "item", defaultValue: "" }, + memo: "", + hasNoFirst: true, + hasNoIndex: true, + hasNoLast: true, + hasNoValue: true, + }); + }); + + test("t-foreach expression on a span", async () => { + expect(parse(``)).toEqual({ + type: ASTType.TForEach, + collection: "list", + elem: "item", + key: null, + hasNoComponent: true, + isOnlyChild: false, + body: { + type: ASTType.DomNode, + tag: "span", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }], + }, + memo: "", + hasNoFirst: true, + hasNoIndex: true, + hasNoLast: true, + hasNoValue: true, + }); + }); + + test("t-foreach expression on a span", async () => { + expect( + parse(``) + ).toEqual({ + type: ASTType.TForEach, + collection: "list", + elem: "item", + key: null, + hasNoComponent: true, + isOnlyChild: false, + body: { + type: ASTType.TIf, + condition: "condition", + tElif: null, + tElse: null, + content: { + type: ASTType.DomNode, + tag: "span", + attrs: {}, + on: {}, + ref: null, + content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }], + }, + }, + memo: "", + hasNoFirst: true, + hasNoIndex: true, + hasNoLast: true, + hasNoValue: true, + }); + }); + + test("more complex t-foreach expression on an option", async () => { + expect( + parse( + `