Compare commits

..

192 Commits

Author SHA1 Message Date
Lucas Perais (lpe) 7a56cc6402 wiip 2022-01-21 17:33:41 +01:00
Lucas Perais (lpe) 1cadae86fd dd 2022-01-21 17:33:41 +01:00
Lucas Perais (lpe) b88f7a03e9 [FIX] compiler: svg in new block takes the right namespace 2022-01-21 09:32:22 +01:00
Lucas Perais (lpe) 055cbda750 [FIX] compiler: t-key on a sub-domnode pushes an anchor in parent block 2022-01-20 18:13:01 +01:00
Géry Debongnie 45f6a8e97a [REF] utils: move batched from reactivity to utils 2022-01-20 15:48:50 +01:00
Géry Debongnie 6091912c54 [IMP] component: improve error message when invalid handler 2022-01-20 15:48:50 +01:00
Géry Debongnie 586f90f6d1 [FIX] doc: fix broken link, and fix doc link checker test 2022-01-20 12:41:31 +01:00
Géry Debongnie e62a0ca2d9 [DOC] update changelog (add browser removal to changes) 2022-01-20 12:29:51 +01:00
Géry Debongnie 3a4ee160ed [FIX] playground: update window management example 2022-01-20 12:29:51 +01:00
Géry Debongnie 5f24caf40d [FIX] scheduler: make sure raf is bound to window 2022-01-20 11:06:59 +01:00
Géry Debongnie 8e36a11b6e [REF] scheduler: capture requestAnimationFrame asap
This is useful to prevent interactions with other testing code.
2022-01-20 10:19:55 +01:00
Géry Debongnie 9a54afcdf5 [FIX] playground: update example to owl 2 2022-01-19 16:50:08 +01:00
Géry Debongnie 3eefa4b153 [DOC] improve useEffect doc 2022-01-19 16:50:08 +01:00
Géry Debongnie 2ffb94ed98 [FIX] reactivity: export Reactive type 2022-01-19 16:50:08 +01:00
Géry Debongnie 6a15874d23 [FIX] typing: Component class should be generic on Props and Env
Otherwise, it prevents proper typing with typescript
2022-01-19 14:46:26 +01:00
Géry Debongnie 07d34248ae [DOC] update changelog content 2022-01-19 14:45:53 +01:00
Géry Debongnie dfc090bb12 [IMP] portal: ensure that destroy is synchronous 2022-01-19 14:11:59 +01:00
Géry Debongnie 7b3593ff8f [DOC] document synthetic events 2022-01-19 14:00:24 +01:00
Géry Debongnie b06791c950 [IMP] components: crash when using unknown suffix/modifiers 2022-01-19 14:00:24 +01:00
Lucas Perais (lpe) f7843c7c00 [FIX] component, error_handling: do not cancel the error fiber twice 2022-01-19 12:28:28 +01:00
Géry Debongnie ca9d4ea762 [IMP] doc: add changelog to doc test, update changelog 2022-01-19 11:31:53 +01:00
Géry Debongnie 7a407a762d [IMP] ci: fails if circular dependencies are found in build 2022-01-19 10:29:41 +01:00
Géry Debongnie 8847824fb7 [FIX] remove circular dependency 2022-01-19 10:03:30 +01:00
Géry Debongnie 55b068faeb [DOC] remove reference to catchError, fix mistake in changelog 2022-01-19 10:03:30 +01:00
Lucas Perais (lpe) dbee8f50d1 [FIX] components: cascading render after microtaskTick (makeChildFiber)
Co-authored-by: Samuel Degueldre <sad@odoo.com>
Co-authored-by: François Georis <fge@odoo.com>
2022-01-19 09:23:17 +01:00
Géry Debongnie f5fec02d4c [DOC] update changelog 2022-01-19 09:12:26 +01:00
Géry Debongnie 651c53ebbf [DOC] update translations page 2022-01-19 09:12:26 +01:00
Géry Debongnie 3119497508 [DOC] add more information to the slots page 2022-01-19 09:12:26 +01:00
Michael (mcm) 057e6944d6 [FIX] bind lifecycle callbacks to component
Before this commit, some of the callbacks were bound to the component
and some were not.
This commit makes all the callbacks bind to the component.
2022-01-18 15:02:07 +01:00
Géry Debongnie 83d38a6f48 [DOC] reorganize and update documentation to owl 2 2022-01-18 11:07:35 +01:00
Jorge Pinna Puissant 9577d70b4b [IMP] portal: compile t-portal in an internal Component Portal
This commit also clean-up the deepRemove for the Portal that is not
needed any more.
2022-01-17 16:04:01 +01:00
Géry Debongnie 6ff6d59895 [FIX] components: only call handlers if component is mounted 2022-01-17 15:46:18 +01:00
Géry Debongnie 7ba0c9f8ba [FIX] blockdom: toString method in multi could crash 2022-01-17 13:55:39 +01:00
Lucas Perais (lpe) 79a51f2317 [FIX] app: t_call recursive template is bound to the correct this 2022-01-17 10:46:08 +01:00
Lucas Perais (lpe) 483335df6c [FIX] compiler: slot are called with a specific and different key 2022-01-14 15:46:02 +01:00
Samuel Degueldre 287a6d5ac4 [FIX] code_generator: stop matching other variables prefix
Previously, we would replace block declarations in some contexts with
the variable alone, and decalres the variable higher in the generated
code. Issues arise because whe sometimes try to replace "let b2" with
"b2" but end up matching "let b20" which is incorrect.

This commit fixes that by adding a space to the text that we are trying
to match ("let b2 " won't match "let b20")
2022-01-14 09:58:28 +01:00
Lucas Perais (lpe) 68684a9a45 [FIX] component, fiber: subchildren should also patch and destroy their children
Have a GrandParent which controls whether one of its GrandChildren is displayed or not.
First, the GrandChild is displayed. Then, change the state of the GrandParent in order to kill
the GrandChild.

Before this commit the GrandChild is only removed from the DOM, as bdom correctly works.
But it is not destroyed.

After this commit, the GrandChild is correctly destroyed.
2022-01-14 09:51:55 +01:00
Lucas Perais (lpe) e5d773daa8 [FIX] components tests: place tests in right submodule 2022-01-14 09:51:55 +01:00
Jorge Pinna Puissant 00ffab1f64 [IMP] portal: portal as a Directive
Before this commit, portal was a Component, now is a directive.
This commit also clean some unused code, and fix an issue on the clean
optimization when a portal is found in a condition or a loop.
2022-01-13 15:02:11 +01:00
Lucas Perais (lpe) a226f92def [FIX] components: avoid leaks when children are outdated/destroyed
Every use case involving some sort of key set on a component would give birth to a leak in an async context:
- If a key of a component changed, the outdated one was never destroyed.
- destroyed component were never removed from their parent's reference map.

This commit solves both issues, that are tightly linked anyway.
2022-01-13 10:17:47 +01:00
Lucas Perais (lpe) 1749bce155 [FIX] test/helpers: useLogLifeCycle supports custom key 2022-01-13 10:17:47 +01:00
Géry Debongnie c00127fa1e [IMP] tooling: add testTimeout argument to test:debug command 2022-01-13 10:05:10 +01:00
Géry Debongnie 0b4b7899f6 [FIX] portal: properly handle errors
Before this commit, Portal overrode the _render function for its
component node, which means it bypassed the error handling mechanism
that was implemented in that method.  It could have been fixed by
duplicating the error handling code as well, but a better solution in my
opinion is to simply override the renderFn function.  This is closer to
the actual intent of the portal implementation: wrap the result of the
rendering in a VPortal vnode.
2022-01-12 08:51:28 +01:00
Lucas Perais (lpe) 5c324fa075 [FIX] app, components: dynamic t-call should propagate the key
Have a Component which has a Component node, and a dynamic t-call itself having
a Component node.

Before this commit, both children had the same `key`, (as in the key in parent.children, which registers on the parent all its children).

As a result, the scheduler was endlessly hanging.

After this commit, it works as expected.
2022-01-10 15:29:25 +01:00
Géry Debongnie 3945c009ed [REF] compiler: factorize a common pattern 2022-01-10 12:31:37 +01:00
Géry Debongnie 5b1132f01a [FIX] compiler: handle t-set as functions 2021-12-28 13:50:35 +01:00
Géry Debongnie f014b1a33e [DOC] update quick_start and how to test pages 2021-12-22 15:03:38 +01:00
Géry Debongnie 3f09a953b0 [REM] doc: remove overview page 2021-12-22 15:03:38 +01:00
Géry Debongnie b8e7ee5ff7 [DOC] update the tutorial todo app 2021-12-22 15:03:38 +01:00
Géry Debongnie 7fdca35a74 [FIX] reactivity: work even if no callback is given 2021-12-22 14:59:28 +01:00
Géry Debongnie 489e20843c [FIX] components: make sure t-ref work with t-if/t-else 2021-12-22 13:58:02 +01:00
Samuel Degueldre 34b781aeed [IMP] compiler: scope generated ids to their prefix
This means that unrelated ids (eg the id of a template, variable or key)
not longer share the same incrementing counter, meaning that you no
longer see a variable named "v2" unless another variable "v1" was
generated previously, this is also true for block data.
2021-12-22 13:30:09 +01:00
Géry Debongnie e8b0f31da6 [IMP] compiler: improve generated compiled code 2021-12-22 13:30:09 +01:00
Géry Debongnie d160c4a628 [FIX] useEffect: can depend on dom dependencies
Because the dependencies are now computed in patched.
2021-12-22 10:47:28 +01:00
Géry Debongnie 4542171a31 [FIX] portal: do not crash in dev mode
Before this commit, the props validation would fail in dev mode because
it did not expect a slot prop.
2021-12-21 21:43:46 +01:00
Géry Debongnie 0b1c4dd4ef [IMP] app: improve API, small refactoring 2021-12-21 12:12:48 +01:00
Géry Debongnie f4da50d350 [REF] build: do not output const enum definitions
We only use 2 const enums in the codebase, but they are defined in the
output, even though this is not useful in any way. This commit reduces
the final output by about 30 loc.
2021-12-21 09:09:36 +01:00
Géry Debongnie f2921abda8 [REM] component: remove support for css tag 2021-12-21 09:09:36 +01:00
Géry Debongnie 107200fd94 [IMP] components: improve props validation
to be able to specify that additional props are allowed
2021-12-21 09:02:45 +01:00
Lucas Perais (lpe) 0f8c859d5f [FIX] component: error_handling when an error is rethrown 2021-12-20 16:55:50 +01:00
Géry Debongnie cca8438d38 [REF] components: remove .el 2021-12-20 14:43:21 +01:00
Géry Debongnie a727347d60 [REF] tests: improve test helpers
- remove snapshotApp
- remove addTemplates
- simplify helpers
- make sure snapshotted templates are snapshotted with the app config
2021-12-20 12:43:36 +01:00
Lucas Perais (lpe) f19de73b0f [FIX] fiber, lifecycle: trigger a render during the fiber.complete
Have a component which does a render in its onWillPatch, onPatched, onMounted hooks.

Before this commit, the result was incorrect: the second rendering was not taken into account.

After this commit, those renderings are correctly applied at the price of a delayed render when the fiber
is in a critical state.
2021-12-20 10:54:51 +01:00
Géry Debongnie a8ffcafd23 [FIX] blockdom: properly handle falsy attributes
This commit fixes some issues with falsy attributes not being properly
set/removed in various situations. Also, the behaviour was not
consistent between normal attribute (key/value) and generic attributes
(pair or object)
2021-12-16 15:07:27 +01:00
Géry Debongnie a2f01ef4ad [FIX] components: improve error handling
Owl provides a way to manage errors occuring in component lifecycle
methods. However, before this commit, these errors were not always
logged or visible, which is very annoying in the common developer
workflow (doing something, checking it works, seeing no error but a
broken interface).

In this commit, we make sure errors are logged/throws in all cases:

- if an error occurs in a mounting operation => the promise is rejected
(which will log the error)
- if an error occurs after the mounting operation and is not handled by
any error handlers => the error will be logged (with console.error).
Also, in that case, this commit adds a warning to explain that owl
destroys the root component, which will help developers understanding
what happened.
2021-12-16 13:29:31 +01:00
Géry Debongnie d7de25e867 [FIX] compiler: properly handle <t> tags in some cases
The problem was that the compiler is based on the assumption that the
multi block received by the parser only occurs in some cases
where the structure of the template require a multi block, and it does
not work when we have random multiblock elsewhere.

We could fix the issue by modifying the code generator code to support
these usecases, or by simply removing these cases in the parser. Since
this seems more efficient, this is the approach taken by this commit.

Note that it was a good opportunity to simplify the parser.
2021-12-15 15:32:56 +01:00
Géry Debongnie 424bc480e2 [DOC] update changelog 2021-12-14 16:03:10 +01:00
Géry Debongnie 421f400766 [DOC] reorganize doc, unskip test, fix some links 2021-12-14 09:45:25 +01:00
Géry Debongnie c37f3445f6 [FIX] blockdom: ignore attributes with undefined value 2021-12-13 15:07:03 +01:00
Géry Debongnie 4cde06d685 [IMP] component: add .bind suffix to props for easy binding 2021-12-13 12:43:23 +01:00
Lucas Perais (lpe) 7b95315b87 adapt to fix and to fixup 2021-12-13 12:39:43 +01:00
Géry Debongnie 09761ddd8e fixup, maybe 2021-12-13 12:39:43 +01:00
Lucas Perais (lpe) b3c6ec2b48 [FIX] component: render in delayed willUpdateProps
Have a child component on which a render is triggered.
This component delays its willUpdateProps and makes a rendering during the willUpdateProps

Before this commit, renderings of the child were inconsistent across
its parent's renderings.

After this commit, it works as expected.
2021-12-13 12:39:43 +01:00
Lucas Perais (lpe) b20462ada6 [FIX] app: factorize to allow smoother developments in projects
It should be usefull to allow developpers to implement mounting/unmounting
if they wish to.
2021-12-13 09:49:48 +01:00
Lucas Perais (lpe) 25000b36be [FIX] blockdom: do not propagate svg namespace to siblings 2021-12-03 14:01:44 +01:00
Lucas Perais (lpe) 144d4a253c [FIX] hooks: useSubEnv supports arbitrary descriptors in env
Before this commit, when defining a getter in the env passed to useSubEnv,
the value was read, losing the definition of the property.

After this commit, declaring a getter in the env works as expected:
the property stays a getter.
2021-12-03 08:06:29 +01:00
Lucas Perais (lpe) 9d48bda227 [FIX] app: support for arbitrary descriptors in env
Before this commit, when defining a getter in the env passed to the App,
the value was read, losing the definition of the property.

After this commit, declaring a getter in the env works as expected:
the property stays a getter.
2021-12-03 08:06:29 +01:00
Lucas Perais (lpe) c8db663869 [FIX] parser: correctly parse pre node within a div with new lines 2021-12-03 08:03:45 +01:00
Lucas Perais (lpe) 247200194f [FIX] component: error handling in class inheritance
Before this commit, class inheritance when using the onError hook was unclear nay wrong.

After this commit, error handlers are called from the bottom up  in the inheritance hierarchy.
If a handler doesn't rethrow the error, the handling stops there and no other handler is called.
If a handler does rethrow, the handlers declared in a parent class are executed.
2021-12-01 17:29:26 +01:00
Géry Debongnie 60b8817ac0 [REF] compiler: simplify all compiled templates 2021-12-01 17:25:31 +01:00
Géry Debongnie e008966291 [FIX] compiler: allow t-if with empty content 2021-12-01 13:22:32 +01:00
Géry Debongnie e2fedc6eff [REF] components: unskip concurrency test 2021-12-01 13:22:32 +01:00
Géry Debongnie b3fb9a35bf [FIX] move error handling out of fiber, fix complicated mounted issues 2021-12-01 12:07:26 +01:00
Géry Debongnie 240259568e [FIX] component: improve error handling
In the following situation: A parent of B, B parent of C, with an error
when C is mounted, caught by B and retriggering a rendering in B, then
the onMounted hook of A wasn't properly called. This commit fixes this
problem.
2021-12-01 12:07:26 +01:00
Géry Debongnie 18fb67eaad [FIX] blockdom: properly handle references
Before this commit, there were situations where the reference numbers
were not properly set, which caused the blocks generated to crash
because the algorithm could not get correct references.
2021-12-01 09:34:55 +01:00
Géry Debongnie e946967867 [IMP] component: render does not return a promise anymore 2021-11-30 08:03:17 +01:00
Géry Debongnie 777b2aae5e [IMP] add support for top level comments 2021-11-29 15:36:45 +01:00
Géry Debongnie 0831bb54e3 [REF] component: remove onDestroyed, implement onWillDestroy 2021-11-29 14:59:01 +01:00
Géry Debongnie 03f34a38dc [TESTS] test lifecycle in reactivity tests 2021-11-29 08:42:47 +01:00
Géry Debongnie dcefd26bee [REF] tests: improve useLogLifecycle and helpers 2021-11-29 08:42:47 +01:00
Samuel Degueldre 922dab8e8f [FIX] reactivity: only call clearReactivesForCallback once on unmount 2021-11-26 16:22:38 +01:00
Samuel Degueldre 99740f9993 [FIX] reactivity: fix memory leak 2021-11-26 15:55:02 +01:00
Géry Debongnie 0b79b1c8fd [FIX] compiler: readd template name in compiled code 2021-11-26 09:17:49 +01:00
Géry Debongnie 5b0dce94cf [REF] code_generator: move generating code to CodeTarget
Before this commit, we had two places with code that generate a function
code. Now, all this code is moved in a method 'generateCode' on
CodeTarget.
2021-11-26 09:17:49 +01:00
Géry Debongnie a1d435c5a5 [FIX] compiler: call dynamic templates with correct this 2021-11-25 17:29:30 +01:00
Géry Debongnie 13c3178760 [FIX] slots: properly bind this in t-on arrow functions 2021-11-25 14:45:30 +01:00
Géry Debongnie 2b565ce1b4 [REF] component: small cleanup
This commit makes it simpler to understand the way fibers are assigned
to nodes.
2021-11-25 11:34:23 +01:00
Géry Debongnie cfcf2c6714 [FIX] component: concurrency issue
When a parent and a child were rendered at the same time, it was
possible for the 2 renders to decrement the same fiber internal
counter, which meant that the render was stalled.
2021-11-25 11:12:31 +01:00
Samuel Degueldre 92174559a1 [FIX] slots: allow t-call and components in slot default content 2021-11-24 15:47:51 +01:00
Bruno Boi 840348892f [IMP] owl: upgrade rollup-plugin-typescript2 to version 0.31.1 2021-11-24 11:45:12 +01:00
Samuel Degueldre b988a68b6f [IMP] parser: normalize document before parsing 2021-11-24 11:19:45 +01:00
Samuel Degueldre 7869a1f2c6 [IMP] components: add test for template string in props 2021-11-24 10:09:52 +01:00
Samuel Degueldre 9e81d14d50 [IMP] parser: throw when using unsupported directive on component 2021-11-24 10:09:52 +01:00
Samuel Degueldre 0e4a55ba09 [FIX] components: allow prop names that are not valid bare property name 2021-11-24 10:09:52 +01:00
Mathieu Duckerts-Antoine 6e9b68dafa [FIX] props: prop names can contain - 2021-11-23 10:20:45 +01:00
Bruno Boi ba7c9063c0 Update CHANGELOG.md 2021-11-23 10:09:39 +01:00
Mathieu Duckerts-Antoine 3a361876ad [IMP] slots: via prop 'slots'
The slot inner working has been reworked. A prop "slots" is now passed
explicitely to the component. It looks like

{ slotName_1: slotInfo_1, ..., slotName_m: slotInfo_m }

with the objects slotInfo_i with mandatory keys "__render", "__ctx",
and optional key "__scope" and possibly others.

Here is how a slotInfo object can be created:
A slotInfo object is normally created by setting in a template something
like

<div>
    <t t-set-slot="foo" t-set-scope="scope" param_1="var" param_2="3">
        content
        <t t-esc="scope.bool"/>
        <t t-esc="scope.num"/>
    </t>
</div>

and it will be used somewhere like

<div>
    <t t-esc="props.slots.foo.param_1"/>
    <t t-slot="foo" bool="other_var" num="5">
</div>

In the above example, the function "__render" produces the block dom
element for the content of the t-set-slot.
The context "__ctx" will have a key "scope" with value { bool: ..., num: 5 }
and "__scope" will be set to "scope".
2021-11-22 16:36:43 +01:00
Samuel Degueldre 58b8572f0a [FIX] components: capture context in prop expressions 2021-11-22 13:18:17 +01:00
Samuel Degueldre c23637e6d8 [FIX] components: throw on duplicate t-key instead of hanging the app 2021-11-22 11:11:49 +01:00
Géry Debongnie 4a4b1fbba5 [IMP] app: add templates in app config
Also, improve the parsing code
2021-11-22 10:56:16 +01:00
Géry Debongnie 536d9e1762 [REM] tools: remove benchmarks/debug script
They are either no longer relevant, or less useful than some
alternatives (such as the js framework benchmark project)
2021-11-22 10:56:16 +01:00
Lucas Perais (lpe) 6ed68372a6 [FIX] package: bump owl version to 2.0.0-alpha1 2021-11-22 10:49:51 +01:00
Lucas Perais (lpe) efd934d2b1 [FIX] tools: adapt playground to owl 2 2021-11-19 16:11:28 +01:00
Lucas Perais (lpe) dabc24cee3 [FIX] index, reactivity: export reactive function in index 2021-11-19 16:11:28 +01:00
Samuel Degueldre 7f49796a07 [IMP] misc: update typescript to 4.5.2 2021-11-19 14:42:37 +01:00
Géry Debongnie 757dffefac [IMP] components: rename onRender->onWillRender, add onRendered 2021-11-19 13:26:44 +01:00
Samuel Degueldre bdfe058279 [IMP] reactivity: overhaul reactivity system
This commit makes the reactivity system more fine grained and makes it
more eager to stop observing keys or objects when they are modified,
this results in fewer "false positive" notifications.
2021-11-19 13:20:37 +01:00
Bruno Boi 345c44b952 fixup! [IMP] svg namespace support 2021-11-19 12:47:51 +01:00
Bruno Boi ea74739d46 [IMP] svg namespace support 2021-11-19 11:55:18 +01:00
Géry Debongnie 93b53d8017 [FIX] remove cyclic dependency, improve error typing (#982) 2021-11-18 10:44:49 +01:00
Lucas Perais (lpe) c2284bc6f5 [FIX] component, fiber: error_handling at the Fiber level
Before this commit, errors triggered at the level of the fiber (as opposed to at the level
of a component's rendering), were handled as the very top level of the rendering, that is,
in the scheduler.
This was wrong because components below in the rendering tree would not have a chance to handle their
children's or their own errors.

After this commit, error triggered in willPatch, onMounted and onPatched are correctly handled
at the closest component to where they were thrown.
2021-11-17 11:40:29 +01:00
Géry Debongnie 48650da62e [REM] remove some outdated tests 2021-11-17 09:21:55 +01:00
Géry Debongnie 2edd6cb8f3 [MOV] move memo and portal to root folder 2021-11-17 09:21:55 +01:00
Géry Debongnie 4b0a37c542 [REM] tests: remove async root tests 2021-11-17 09:21:55 +01:00
Lucas Perais (lpe) 50c0e30936 [FIX] component: error_handling on current component
Have a Child Compnent which has one component that succeeds and another
one that fails at its instanciation.
The Child component handles the Errors by rendering itself.

Before this commit, the error handling algorithm made impossible for the scheduler to finish.
This was because the current fiber was still counted as ongoing, when it was actually completed.

After this commit, this use case is handled correctly.
2021-11-16 17:05:25 +01:00
Géry Debongnie e97335d7ab [DOC] add a change log 2021-11-16 16:36:20 +01:00
Lucas Perais (lpe) 6ea40a66da [IMP] app, compiler: introduce t-out
t-out automatically escaped content when it is a string not marked
with the `markup` function

t-out renders the raw content if it is a Block, or if it has been marked
with the `markup` funtion.

t-esc has been kept since it is safe and is optimized to render text nodes.

all t-raw calls are in fact the same as t-out.
2021-11-16 14:37:53 +01:00
Lucas Perais (lpe) 2806cda420 [FIX][BREAKING] t-esc on component is not supported anymore 2021-11-16 14:37:53 +01:00
Bruno Boi cb0b525f32 [IMP] templates: can load multiple at once and also from XMLDocument 2021-11-16 13:10:09 +01:00
Mathieu Duckerts-Antoine 30d28670e8 [REF] reactivity: new API 2021-11-15 17:14:19 +01:00
Mathieu Duckerts-Antoine 7b761a2f8b [REF] tests: remove debugger 2021-11-15 17:14:19 +01:00
Mathieu Duckerts-Antoine d834bb2ac4 [FIX] reactivity: memory leak
The deletion of a key in an observed object did clear the observers of
that key but did not clear the atoms created (if any) when the key existed
(e.g. on the key value if it was trackable). This can lead to a growing
set of atoms that are useless but kept in memory if a lot of keys are
added/deleted. The same thing can happen if a key value is changed many
times and the values are trackable.

Here we fix the problem by
- keeping tracks of the objects that have been observed by an external call
  to the method "atom" (we call them seeds).
- remove all observer atoms that are not seeds for observers of at least
  one key deletion or key value change.

Note the fix is in some sense partial: a user could use the "atom" primitive
making the new system uncapable of avoiding a leak (see test "atom on an
object with a getter 3" where a getter is used in a weird way in an
observed object).
2021-11-15 17:14:19 +01:00
Bruno Boi 6ae92fa4b3 [IMP] hooks: reintroduce useExternalListener 2021-11-15 16:15:14 +01:00
Bruno Boi 3ceb118ace [IMP] hooks: introduce useEffect
Co-Authored-By: Samuel Degueldre <sad@odoo.com>
2021-11-15 16:07:25 +01:00
Bruno Boi fa426b7fc0 [IMP] owl: mount util now takes an AppConfig 2021-11-15 16:07:25 +01:00
Bruno Boi d7403871fc [IMP] env: env is now frozen, useSubEnv does not affect user env 2021-11-15 13:21:46 +01:00
Géry Debongnie 6aeff21d9a [FIX] tests: remove useless log 2021-11-15 09:35:13 +01:00
Géry Debongnie 429e145b68 [FIX] portal: unskip some tests 2021-11-15 09:35:13 +01:00
Géry Debongnie fb6aab8b70 [FIX] component: fix lifecycle order 2021-11-15 09:35:13 +01:00
Géry Debongnie 9e285e1e0a [FIX] unskip tests 2021-11-15 09:35:13 +01:00
Géry Debongnie 63bfdfb8db [REF] move event_bus into utils, readd 2 functions 2021-11-15 09:35:13 +01:00
Géry Debongnie d4c4fe853e [REM] tests: remove debug script tests 2021-11-15 09:35:13 +01:00
Géry Debongnie 9163bb1e08 [REF] app: move TemplateSet into its own file 2021-11-15 09:35:13 +01:00
Géry Debongnie a187a376a0 [REF] move app and compiler code around 2021-11-15 09:35:13 +01:00
Géry Debongnie 06ea6d2490 [REF] component: fix typescript error 2021-11-15 09:35:13 +01:00
Géry Debongnie 04334e23d7 [REF] compiler: rename qweb/ into compiler/ 2021-11-15 09:35:13 +01:00
Géry Debongnie 4efe1ae166 [IMP] reactivity: slightly improve code and typing 2021-11-15 09:35:13 +01:00
Lucas Perais (lpe) ad4673cb36 [IMP] component: re-introduce error handling in lifecycle 2021-11-12 16:54:40 +01:00
Samuel Degueldre e611c20ab4 [IMP] qweb: turn handlers into function expressions only
For the sake of consistency with vanilla JS, and to allow some things
that were previously not possible.
2021-11-12 13:17:43 +01:00
Mathieu Duckerts-Antoine d60a5a414e [IMP] reactivity: Context replacement
Aim to replace the abstraction "Context" from OWL 1 with the new primitives
"atom" and "useState":

- notification is done only after a batch of modifications.
- observers are notified at most once for a batch.
- an observer of type component is notified (and rerendered)
  only if it does not have an ancestor that has to be notified for the
  same batch of operations (anywhere in the web of references!).
- notification of components is done on all levels "simultaneously".

Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
2021-11-10 15:08:08 +01:00
Géry Debongnie d47dcf6be2 [IMP] pin prettier version to ensure consistent results 2021-11-10 14:03:17 +01:00
Samuel Degueldre 3e70b17da1 [FIX] qweb: fix crash with ref an component in same slot 2021-11-10 13:36:13 +01:00
Samuel Degueldre c718d8e6c6 [FIX] qweb: fix crash when component only renders empty slot 2021-11-10 13:36:13 +01:00
Samuel Degueldre a122a94180 [IMP] qweb/components: remove t-ref on components
Refs to component expose a lot of implementation details that should be
private to parents. Parent to child communication should go through
props.
2021-11-10 13:36:13 +01:00
Bruno Boi ca139166ab [IMP] qweb: reintroduce t-tag directive
will not be compatible with t-model directive !
2021-11-09 16:36:03 +01:00
Lucas Perais (lpe) ae71db28e4 [IMP] qweb: compiler: support t-key on node and component without t-foreach 2021-11-09 14:53:19 +01:00
Bruno Boi 779fc6b02b fixup! [IMP] bdom: support multiple synthetic events on one node 2021-11-08 13:36:09 +01:00
Bruno Boi 84913593b2 [IMP] bdom: support multiple synthetic events on one node 2021-11-08 13:36:09 +01:00
Bruno Boi 8d4eb21536 [IMP] qweb/attributes: uncomment two tests
- textarea with t-att-value
- select with t-att-value
2021-11-08 11:25:14 +01:00
Bruno Boi 4bdfaf96f3 fixup! [IMP] qweb: introduce t-model directive 2021-11-03 15:25:21 +01:00
Bruno Boi 233c953243 [IMP] qweb: introduce t-model directive
supported modifiers: lazy, trim, number
2021-11-03 15:25:21 +01:00
Bruno Boi ee5ad354ff [IMP] tags: reintroduce inline css tag
The CSS tag is useful to define a css stylesheet in the javascript file:
```js
class MyComponent extends Component {
  static template = xml`
        <div class="my-component">some template</div>
    `;
  static style = css`
    .my-component {
      color: red;
    }
  `;
}
```

The `css` tag registers internally the css information. Then, whenever the first instance of the component is created, will add a <style> tag to the document <head>.

Original commit in Owl v1: 953778dc5
2021-11-03 10:07:14 +01:00
Mathieu Duckerts-Antoine 4e4b85e2a9 [IMP] reactivity: new primitives for reactivity
fine grained reactivity:

existing key in source changes --> only observer having read the key are notified

add/delete key in source changes --> all source observers are notified

Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
2021-10-29 14:52:20 +02:00
Mathieu Duckerts-Antoine 6479631983 Code prettification 2021-10-29 14:52:20 +02:00
Lucas Perais (lpe) 2a0c410014 [IMP] qweb, blockdom, components: t-on with modifiers
supported modifiers: capture, prevent, stop, self.
2021-10-28 13:38:56 +02:00
Lucas Perais (lpe) bb4aecf638 [IMP] blockdom: t-on supports synthetic and native event handler
Synthetic handler is a sort of event delegation that allows placing
only one listener on the document to improve performance. It is an opt-in option.

Native listener places the listener on the node itself.
2021-10-28 13:38:56 +02:00
Lucas Perais (lpe) a1adfd5a1b [FIX] qweb, component: remove support for t-on on component node 2021-10-27 13:38:35 +02:00
Lucas Perais (lpe) dd4848f602 [FIX] qweb: t-key in t-foreach is mandatory, throws otherwise 2021-10-27 13:38:35 +02:00
Bruno Boi b25f476c22 [FIX] qweb: reintroduce test on t-debug 2021-10-26 09:33:56 +02:00
Mathieu Duckerts-Antoine 90cdb97b49 [IMP] tests: component mounting
We re-add some tests for component mounting.
2021-10-26 09:05:33 +02:00
Mathieu Duckerts-Antoine 7d7568d254 [IMP] app: mount app in "first-child" position
We reintroduce the possibility to mount the app in first position in
a target. The option "self" has been dropped since it is now possible
for a component to have several top level nodes.
2021-10-26 09:05:33 +02:00
Bruno Boi 06d207f60e [IMP] package.json: add watch arg to test:debug command
Before this commit
The command "npm run test:debug" runs the tests once.

After this commit
Jest runs in watch mode
2021-10-22 15:57:23 +02:00
Bruno Boi ff564421da [IMP] package.json: add remote test:debug command
Usage:
Open chrome://inspect then run in console:
> npm run test:debug ./path/to/your/testfile.ts
2021-10-22 14:51:23 +02:00
Mathieu Duckerts-Antoine c3695ec8db [IMP] component: defaultProps application
We re-add the application of defaultProps. Note that the application
is done twice in dev mode.
2021-10-22 12:46:57 +02:00
Mathieu Duckerts-Antoine 6e0834ce5e [IMP] component,qweb: props validation
We re-add the possibility to validate props when dev mode is active.
No change in the API right Now. The dev mode is activated via the
configure method of App class.
2021-10-22 12:46:57 +02:00
Samuel Degueldre d1425c7100 [FIX] component: correctly create a new node when previous is destroyed
Previously, when a component node had been created and destroyed, and
the corresponding component was then recreated, the destroyed node was
reused. This commit fixes that
2021-10-22 10:00:35 +02:00
Samuel Degueldre e01fe301c0 [IMP] *: re-add a bunch of tests 2021-10-21 10:34:54 +02:00
Mathieu Duckerts-Antoine ba2fe3ff55 [IMP] qweb: t-props directive
We reimplement the directive "t-props" and add some tests for it.
2021-10-20 15:52:43 +02:00
Géry Debongnie 700d574314 [MOV] move lifecycle_hooks into component/ 2021-10-20 15:16:28 +02:00
Mathieu Duckerts-Antoine ba32772a92 [FIX] qweb: re-add test on memory leak
Re-add test from 6e185f9.
2021-10-20 15:16:26 +02:00
Mathieu Duckerts-Antoine e4a0277f68 [FIX] qweb: t-set directive
This commit reintroduces some tests for the t-set directive and make
them pass. For that, it was necessary to adapt the qweb compiler in
order to get the following behaviors:

A t-set can affect parent contexts (up to the first parent tagged as
boundary) when the key changed is found in one of the parent contexts.
Some context are marked as boundaries in such a way that

   - rendering contexts (e.g. components) cannot be modified via a t-set.
   - a t-set in a t-call body or in a called template can never change a
     context above the t-call context.

Code prettification has been done.
Snapshots have been modified.
2021-10-20 15:16:25 +02:00
Lucas Perais (lpe) abe3748825 [FIX] re-introduce tests 2021-10-20 15:16:24 +02:00
Mathieu Duckerts-Antoine ee64de8b3f [IMP] qweb: throw error when t-component is not used with a 't' tag 2021-10-20 15:16:22 +02:00
Lucas Perais (lpe) ed831320db [FIX] re-introduce some missing tests
The point is to have visibility on the development of the owl2 features.
This commit reintroduces some tests keeping them skipped in order to fulfill that purpose.

There still are some missing tests though.
2021-10-20 15:16:21 +02:00
Mathieu Duckerts-Antoine 5f1e1e189f [IMP] qweb: re-add support of t-on directive
We add some test for the t-on directive.

For making them pass, it was necessary to change the code produced by
compileTForeach: the const declaration is not done by using generateId
and there was some conflict with the variable names produced in
captureExpression. Consequently, many snapshots had to be changed.

Code prettification has been done too.
2021-10-20 15:16:20 +02:00
Mathieu Duckerts-Antoine 55ef9ca116 [ADD] components: re-add a test for t-foreach directive 2021-10-20 15:16:18 +02:00
Samuel Degueldre 642ecf0ccd [IMP] components: re-add a bunch of components tests from owl 1
Some tests are skipped because they rely on not-yet-implemented
features.
2021-10-20 15:16:16 +02:00
Samuel Degueldre c9cc789f1d [IMP] components: add back style_class tests
The tests have been adapted to the new way of doing things, some tests
are skipped because they rely on features that are not implemented yet
2021-10-20 15:16:14 +02:00
Mathieu Duckerts-Antoine b8649f1add [ADD] Translation feature
This commit brings back the possibility to translate text nodes and
the attributes "label", "title", "placeholder", and "alt" in an app
configured with a suitable translation function.

It is also possible to deactivate translations under a node via
the directive t-translation="off".

For flexibility it is possible to define the list of translatable
attributes in the app.
2021-10-20 15:16:12 +02:00
Samuel Degueldre e2d98e4c26 [REF] doc: remove references to router and store
Owl 2 will not have a router or store implemented inside the library
2021-10-20 15:16:06 +02:00
Samuel Degueldre e6bb4ef286 [REF] qweb: use native Node and Element instead of custom Dom types 2021-10-20 15:11:32 +02:00
Géry Debongnie 7ac20f4fc2 [REF] initial prototype of owl 2 2021-10-20 15:11:25 +02:00
79 changed files with 683 additions and 2707 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ name: Node.js CI
on:
pull_request:
branches: [ master ]
branches: [ master, owl-next ]
jobs:
build:
+14 -50
View File
@@ -30,32 +30,19 @@ All changes are documented here in no particular order.
- breaking: `catchError` method is replaced by `onError` hook ([details](#36-catcherror-method-is-replaced-by-onerror-hook))
- breaking: Support for inline css (`css` tag and static `style`) has been removed ([details](#37-support-for-inline-css-css-tag-and-static-style-has-been-removed))
- new: prop validation system can now describe that additional props are allowed (with `*`) ([doc](doc/reference/props.md#props-validation))
- breaking: prop validation system does not allow default prop on a mandatory (not optional) prop ([doc](doc/reference/props.md#props-validation))
**Templates**
- breaking: `t-foreach` should always have a corresponding `t-key` ([details](#20-t-foreach-should-always-have-a-corresponding-t-key))
- breaking: `t-ref` does not work on components ([details](#29-t-ref-does-not-work-on-component))
- breaking: `t-on` does not accept expressions, only functions ([details](#30-t-on-does-not-accept-expressions-only-functions))
- breaking: `t-raw` directive has been removed (replaced by `t-out`) ([details](#38-t-raw-directive-has-been-removed-replaced-by-t-out))
- new: add support for synthetic events ([doc](doc/reference/event_handling.md#synthetic-events))
- breaking: style/class on components are now regular props ([details](#10-styleclass-on-components-are-now-regular-props))
- new: components can use the `.bind` suffix to bind function props ([doc](doc/reference/props.md#binding-function-props))
- breaking: `t-on` does not accept expressions, only functions ([details](#30-t-on-does-not-accept-expressions-only-functions))
- breaking: `t-on` does not work on components any more ([details](#12-t-on-does-not-work-on-components-any-more))
- new: an error is thrown if an handler defined in a `t-on-` directive is not a function (failed silently previously in some cases)
- breaking: `t-component` no longer accepts strings ([details](#17-t-component-no-longer-accepts-strings))
- new: the `this` variable in template expressions is now bound to the component
**Reactivity**
- finer grained reactivity: owl 2 tracks change per key/component
- finer grained reactivity: sub components can reobserve state ([doc](doc/reference/reactivity.md))
- new: `reactive` function: create reactive state (without being linked to a component) ([doc](doc/reference/reactivity.md#reactive))
- new: `markRaw` function: mark an object or array so that it is ignored by the reactivity system ([doc](doc/reference/reactivity.md#markraw))
- new: `toRaw` function: given a reactive objet, return the raw (non reactive) underlying object ([doc](doc/reference/reactivity.md#toraw))
**Slots**
@@ -79,13 +66,13 @@ All changes are documented here in no particular order.
- improved performance
- much simpler code
- finer grained reactivity: owl 2 tracks change per key/component
- finer grained reactivity: sub components can reobserve state
- new App class to encapsulate a root Owl component (with the config for that application) ([doc](doc/reference/app.md))
- new `Memo` component
- new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
- breaking: `Context` is removed ([details](#15-context-is-removed))
- breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty))
- breaking: `env` is now frozen ([details](#28-env-is-now-frozen))
- new hook: `useChildSubEnv` (only applies to child components) ([details](#27-usechildsubenv-only-applies-to-child-components))
- breaking: most exports are exported at top level ([details](#18-most-exports-are-exported-at-top-level))
- breaking: properties are no longer set as attributes ([details](#19-properties-are-no-longer-set-as-attributes))
- breaking: `EventBus` api changed: it is now an `EventTarget` ([details](#21-eventbus-api-changed-it-is-now-an-eventtarget))
@@ -94,6 +81,8 @@ All changes are documented here in no particular order.
- breaking: transition system is removed ([details](#24-transition-system-is-removed))
- breaking: no more global components or templates ([details](#25-no-more-global-components-or-templates))
- breaking: `AsyncRoot` utility component is removed ([details](#26-asyncroot-utility-component-is-removed))
- breaking: `useSubEnv` only applies to child components ([details](#27-usesubenv-only-applies-to-child-components))
- breaking: `env` is now frozen ([details](#28-env-is-now-frozen))
- breaking: `renderToString` function on qweb has been removed ([details](#32-rendertostring-on-qweb-has-been-removed))
- breaking: `debounce` utility function has been removed ([details](#34-debounce-utility-function-has-been-removed))
- breaking: `browser` object has been removed ([details](#39-browser-object-has-been-removed))
@@ -576,12 +565,15 @@ either a fallback when the data is not ready, or the actual component with data
as props. If there is no escape, and `AsyncRoot` is needed, please reach out to
us so we can study this usecase.
### 27. `useChildSubEnv` (only applies to child components)
### 27. `useSubEnv` only applies to child components
In Owl, a call to `useSubEnv` would define a new environment for the children
AND the component. It is very useful, but in some cases, one only need to update
the children component environment. This can now be done with a new hook:
[`useChildSubEnv`](doc/reference/hooks.md#usesubenv-and-usechildsubenv)
In Owl 1, a call to `useSubEnv` would define a new environment for the children
AND the component. It now only defines an environment for the children.
Rationale: This was a subtle cause for bugs: some code had to be rrun
before the call to `useSubEnv`, otherwise it could interfere with the sub environment.
Documentation: [Hooks](doc/reference/hooks.md#usesubenv)
### 28. `env` is now frozen
@@ -667,14 +659,10 @@ Also, this can easily be done in userspace, by mounting a component in a div. F
export async function renderToString(template, context) {
class C extends Component {
static template = template;
setup () {
Object.assign(this, context);
}
}
const div = document.createElement('div');
document.body.appendChild(div);
const app = new App(C);
await app.mount(div);
const component = await mount(C, div);
const result = div.innerHTML;
app.destroy();
div.remove();
@@ -682,30 +670,6 @@ export async function renderToString(template, context) {
}
```
The function above works for most cases, but is asynchronous. An alternative
function could look like this:
```js
const { App, blockDom } = owl;
const app = new App(Component); // act as a template repository
function renderToString(template, context = {}) {
app.addTemplate(template, template, { allowDuplicate: true });
const templateFn = app.getTemplate(template);
const bdom = templateFn(context, {});
const div = document.createElement('div')
blockDom.mount(bdom, div);
return div.innerHTML;
}
```
This is a synchronous function, so it will not work with components, but it should
be useful for most simple templates.
Also note that these two examples do not translate their templates. To do that,
they need to be modified to pass the proper translate function to the `App`
configuration.
### 33. Portal are now defined with `t-portal`
Before Owl 2, one could use the `Portal` component by importing it and using it.
+1 -1
View File
@@ -121,5 +121,5 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.4.10](https://github.com/odoo/owl/releases/tag/v1.4.10)
- [owl-1.4.7](https://github.com/odoo/owl/releases/tag/v1.4.7)
+2 -8
View File
@@ -7,14 +7,9 @@ Main entities:
- [`App`](reference/app.md): represent an Owl application (mainly a root component,a set of templates, and a config)
- [`Component`](reference/component.md): the main class to define a concrete Owl component
- [`mount`](reference/app.md#mount-helper): main entry point for most application: mount a component to a target
- [`xml`](reference/templates.md#inline-templates): helper to define an inline template
Reactivity
- [`useState`](reference/reactivity.md#usestate): create a reactive object (hook, linked to a specific component)
- [`reactive`](reference/reactivity.md#reactive): create a reactive object (not linked to any component)
- [`markRaw`](reference/reactivity.md#markraw): mark an object or array so that it is ignored by the reactivity system
- [`toRaw`](reference/reactivity.md#toraw): given a reactive objet, return the raw (non reactive) underlying object
- [`xml`](reference/templates.md#inline-templates): helper to define an inline template
Lifecycle hooks:
@@ -36,8 +31,7 @@ Other hooks:
- [`useEnv`](reference/hooks.md#useenv): return a reference to the current env
- [`useExternalListener`](reference/hooks.md#useexternallistener): add a listener outside of a component DOM
- [`useRef`](reference/hooks.md#useref): get an object representing a reference (`t-ref`)
- [`useChildSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for child components)
- [`useSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for current component and child components)
- [`useSubEnv`](reference/hooks.md#usesubenv): extend the current env with additional information for child components
Utility/helpers:
+16 -21
View File
@@ -3,7 +3,6 @@
## Content
- [Overview](#overview)
- [API](#api)
- [Configuration](#configuration)
- [`mount` helper](#mount-helper)
- [Loading templates](#loading-templates)
@@ -27,10 +26,24 @@ The basic workflow is: create an `App` instance configured with the root
component, the templates, and possibly other settings. Then, we mount that
instance somewhere in the DOM.
## API
## Configuration
- **`constructor(Root[, config])`**: first argument should be a component class (not
an instance), and the optional second argument is a configuration object (see below).
an instance), and the optional second argument is a configuration object.
The `config` object is an object with some of the following keys:
- **`env (object)`**: if given, this will be the shared `env` given to each component
- **`props (object)`**: the props given to the root component
- **`dev (boolean, default=false)`**: if `true`, the application is rendered in `dev`
mode, which activates some additional checks (in particular, the props validation
code is only performed in dev mode)
- **`translatableAttributes (string[])`**: a list of additional attributes that should
be translated (see [translations](translations.md))
- **`translateFn (function)`**: a function that will be called by owl to translate
templates (see [translations](translations.md))
- **`templates (string | xml document)`**: all the templates that will be used by
the components created by the application.
- **`mount(target, options)`**: first argument is an html element, and the optional
second argument is an object with mounting options (see below). Mount the app
@@ -45,24 +58,6 @@ instance somewhere in the DOM.
- **`destroy()`**: destroys the application
## Configuration
The `config` object is an object with some of the following keys:
- **`env (object)`**: if given, this will be the shared `env` given to each component
- **`props (object)`**: the props given to the root component
- **`dev (boolean, default=false)`**: if `true`, the application is rendered in `dev`
mode, which activates some additional checks (in particular, the props validation
code is only performed in dev mode)
- **`test (boolean, default=false)`**: `test` mode is the same as `dev` mode, except
that Owll will not log a message to warn that Owl is in `dev` mode.
- **`translatableAttributes (string[])`**: a list of additional attributes that should
be translated (see [translations](translations.md))
- **`translateFn (function)`**: a function that will be called by owl to translate
templates (see [translations](translations.md))
- **`templates (string | xml document)`**: all the templates that will be used by
the components created by the application.
## `mount` helper
Note that there is a `mount` helper to do that in just a line:
+1 -1
View File
@@ -52,7 +52,7 @@ It is sometimes useful to add one (or more) specific keys to the environment,
from the perspective of a specific component and its children. In that case, the
solution presented above will not work, since it sets the global environment.
There are two hooks for this situation: [`useSubEnv` and `useChildSubEnv`](hooks.md#usesubenv-and-usechildsubenv).
There is a hook for this situation: [`useSubEnv`](hooks.md#usesubenv).
```js
class SomeComponent extends Component {
+25
View File
@@ -0,0 +1,25 @@
# 🦉 Event Bus 🦉
It is sometimes useful to use a `Bus` to communicate informations between various
parts of the code. Owl has a very simple bus class, which manages subscriptions,
triggering events, and callbacks.
```js
const bus = new owl.core.EventBus();
bus.on("some-event", null, function (...args) {
console.log(...args);
});
bus.trigger("some-event", 1, 2, 3);
// [1,2,3] will be logged to the console
```
Its API is:
| Method | Description |
| -------------------------------- | --------------------------------- |
| `on(eventType, owner, callback)` | add a listener |
| `off(eventType, owner)` | remove all listeners for an owner |
| `trigger(eventType, ...args)` | trigger an event |
| `clear` | remove all subscriptions |
+7 -18
View File
@@ -8,7 +8,7 @@
- [Other hooks](#other-hooks)
- [`useState`](#usestate)
- [`useRef`](#useref)
- [`useSubEnv` and `useChildSubEnv`](#usesubenv-and-usechildsubenv)
- [`useSubEnv`](#usesubenv)
- [`useExternalListener`](#useexternallistener)
- [`useComponent`](#usecomponent)
- [`useEnv`](#useenv)
@@ -152,14 +152,14 @@ this.ref2 = useRef("component_2");
References are only guaranteed to be active while the parent component is mounted.
If this is not the case, accessing `el` or `comp` on it will return `null`.
### `useSubEnv` and `useChildSubEnv`
### `useSubEnv`
The environment is sometimes useful to share some common information between
all components. But sometimes, we want to _scope_ that knowledge to a subtree.
For example, if we have a form view component, maybe we would like to make some
`model` object available to all sub components, but not to the whole application.
This is where the `useChildSubEnv` hook may be useful: it lets a component add some
This is where the `useSubEnv` hook may be useful: it lets a component add some
information to the environment in a way that only its children
can access it:
@@ -167,26 +167,15 @@ can access it:
class FormComponent extends Component {
setup() {
const model = makeModel();
// model will be available on this.env for this component and all children
useSubEnv({ model });
// someKey will be available on this.env for all children
useChildSubEnv({ someKey: "value" });
}
}
```
The `useSubEnv` and `useChildSubEnv` hooks take one argument: an object which
contains some key/value that will be added to the current environment. These hooks
will create a new env object with the new information:
- `useSubEnv` will assign this new `env` to itself and to all children components
- `useChildSubEnv` will only assign this new `env` to all children components.
As usual in Owl, [environments](environment.md) created with these two hooks are
frozen, to prevent unwanted modifications.
Note that both these hooks can be called an arbitrary number of times. The `env`
will then be updated accordingly.
The `useSubEnv` takes one argument: an object which contains some key/value that
will be added to the parent environment. Note that it will extend, not replace
the parent environment. And of course, the parent environment will not be
affected.
### `useExternalListener`
+1 -6
View File
@@ -168,15 +168,11 @@ For each key, a `prop` definition is either a boolean, a constructor, a list of
- `shape`: if the type was `Object`, then the `shape` key describes the interface of the object. If it is not set, then we only validate the object, not its elements,
- `validate`: this is a function which should return a boolean to determine if
the value is valid or not. Useful for custom validation logic.
- `optional`: if true, the prop is not mandatory
There is a special `*` prop that means that additional prop are allowed. This is
sometimes useful for generic components that will propagate some or all their
props to their child components.
Note that default values cannot be defined for a mandatory props. Doing so will
result in a prop validation error.
Examples:
```js
@@ -194,8 +190,7 @@ class ComponentB extends owl.Component {
element: {type: Object, shape: {id: Boolean, text: String }
},
date: Date,
combinedVal: [Number, Boolean],
optionalProp: { type: Number, optional: true }
combinedVal: [Number, Boolean]
};
...
-46
View File
@@ -5,8 +5,6 @@
- [Overview](#overview)
- [`useState`](#usestate)
- [`reactive`](#reactive)
- [`markRaw`](#markraw)
- [`toRaw`](#toraw)
## Overview
@@ -80,47 +78,3 @@ obj2.b = 3; // log 'observer1' and 'observer2'
Obviously, one can use `reactive` on the result of a `useState` if wanted, this
is the proper way to watch for some state changes.
## `markRaw`
Marks an object so that it is ignored by the reactivity system. This function returns its argument.
```js
const someObject = markRaw(...);
const state = useState({
a: 1,
obj: someObject
});
// here, state.obj === someObject
```
This is useful in some rare cases. For example, some complex and large object such
that going through the reactivity system may cause a non trivial performance slowdown.
However, use this function with caution: this is an escape hatch from the reactivity
system, and as such, using it may cause subtle and unintended issues!
## `toRaw`
Given a reactive object, this function returns the underlying, non-reactive,
corresponding object.
```js
// in setup
const state = useState({ value: 1 });
// later:
const rawState = toRaw(this.state);
rawState.value = 3; // will NOT be picked up by the reactivity system!!!
```
Here again, this is useful in some situations where we want to explicitely bypass
Owl, but using this function means that the responsability of coordinating
state update is given to the user code, instead of Owl. Subtle bugs may arise!
Also, normal (non-reactive objects) will be directly returned by `toRaw`:
```js
const obj = { a: 1 };
console.log(toRaw(obj) === obj); // true
```
+12 -28
View File
@@ -87,8 +87,8 @@ grandparent of the slot content).
## Default Slot
All elements inside the component which are not a named slot will be treated as
part of the content of the `default` slot. For example:
The first element inside the component which is not a named slot will
be considered the `default` slot. For example:
```xml
<div t-name="Parent">
@@ -110,7 +110,6 @@ One can mix default slot and named slots:
default content
<t t-set-slot="footer">
content for footer slot here
</t>
</Child>
</div>
```
@@ -210,45 +209,30 @@ use this `Notebook` component:
</Notebook>
```
Slot params works like normal props, so one can use the `.bind` suffix to
bind a function if needed.
## Slot scopes
For other kinds of advanced use cases, the content of a slot may depends on some
information specific to the generic component. This is the opposite of the slot
params.
For other kind of advanced use cases, the content of a slot may depends on some
specific information specific to the generic component. This is the opposite
of the slot params.
To solve this kind of problems, one can use the `t-slot-scope` directive along
To solve this kind of problems, one can use the `t-set-scope` directive along
with the `t-set-slot`. This defines the name of a variable that can access
everything given by the child component:
```xml
<MyComponent>
<t t-set-slot="foo" t-slot-scope="scope">
<div>
<t t-set-slot="foo" t-set-scope="scope">
content
<t t-esc="scope.bool"/>
<t t-esc="scope.num"/>
</t>
</MyComponent>
</div>
```
And the child component that includes the slot can provide values like this:
```xml
<t t-slot="foo" bool="other_var" num="5">
<div>
<t t-slot="foo" bool="other_var" num="5">
</div>
```
In the case of the default slot, you may declare the slot scope directly on the
component itself:
```xml
<MyComponent t-slot-scope="scope">
content
<t t-esc="scope.bool"/>
<t t-esc="scope.num"/>
</MyComponent>
```
Slot values works like normal props, so one can use the `.bind` suffix to
bind a function if needed.
+4 -17
View File
@@ -21,7 +21,6 @@
- [Fragments](#fragments)
- [Inline templates](#inline-templates)
- [Rendering svg](#rendering-svg)
- [Restrictions](#restrictions)
## Overview
@@ -675,19 +674,7 @@ This `RootNode` component will then display a live SVG representation of the
graph described by the `graph` property. Note that there is a recursive structure
here: the `Node` component uses itself as a subcomponent.
**Important note:** Owl needs to properly set the namespace for each svg elements.
Since Owl compile each template separately, it is not able to determine easily
if a template is supposed to be included in a svg namespace or not. Therefore,
Owl depends on a heuristic: if a tag is either `svg`, `g` or `path`, then it will
be considered as svg. In practice, this means that each component or each sub
templates (included with `t-call`) should have one of these tag as root tag.
## Restrictions
Note that Owl templates forbid the use of tag and or attributes starting with
the `block-` string. This restriction prevents name collision with the internal
code of Owl.
```xml
<div><block-1>this will not be accepted by Owl</block-1></div>
```
Note that since SVG needs to be handled in a specific way (its namespace needs
to be properly set), there is a small constraint for Owl components: if an owl
component is supposed to be a part of an svg graph, then its root node needs to
be a `g` tag, so Owl can properly set the namespace.
+1 -12
View File
@@ -54,17 +54,6 @@ For example, one may want to also translate `data-title` attributes. To do that,
we can define additional attributes with the `translatableAttributes` option:
```js
const app = new App(Root, { templates, tranaslateFn, translatableAttributes: ["data-title"] });
const app = new App(Root, { templates, tranaslateFn, translatalbeAttributes: ["data-title"] });
// ...
```
It is also possible to remove an attribute from the default list by prefixing it with `-`:
```js
const app = new App(Root, {
templates,
tranaslateFn,
translatableAttributes: ["data-title", "-title"],
});
// data-title attribute will be translated, but not title attribute...
```
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.0.0-alpha.2",
"version": "2.0.0-alpha1",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js",
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.4.10
- Current version: 1.4.7
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
+32 -38
View File
@@ -1,10 +1,9 @@
import { Component, ComponentConstructor } from "../component/component";
import { Component } from "../component/component";
import { ComponentNode } from "../component/component_node";
import { MountOptions } from "../component/fibers";
import { Scheduler } from "../component/scheduler";
import { TemplateSet, TemplateSetConfig } from "./template_set";
import { nodeErrorHandlers } from "../component/error_handling";
import { validateTarget } from "../utils";
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
@@ -12,57 +11,52 @@ export interface Env {
[key: string]: any;
}
export interface AppConfig<P, E> extends TemplateSetConfig {
props?: P;
env?: E;
test?: boolean;
export interface AppConfig extends TemplateSetConfig {
env?: Env;
props?: any;
}
export const DEV_MSG = () => {
const hash = (window as any).owl ? (window as any).owl.__info__.hash : "master";
return `Owl is running in 'dev' mode.
export const DEV_MSG = `Owl is running in 'dev' mode.
This is not suitable for production use.
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
};
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`;
export class App<
T extends abstract new (...args: any) => any = any,
P = any,
E = any
> extends TemplateSet {
static validateTarget = validateTarget;
Root: ComponentConstructor<P, E>;
props: P;
env: E;
export class App<T extends typeof Component = any> extends TemplateSet {
Root: T;
props: any;
env: Env;
scheduler = new Scheduler();
root: ComponentNode<P, E> | null = null;
root: ComponentNode | null = null;
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
constructor(Root: T, config: AppConfig = {}) {
super(config);
this.Root = Root;
if (config.test) {
this.dev = true;
}
if (this.dev && !config.test) {
console.info(DEV_MSG());
if (config.dev) {
console.info(DEV_MSG);
}
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
this.env = Object.freeze(Object.defineProperties({}, descrs)) as E;
this.props = config.props || ({} as P);
this.env = Object.freeze(Object.defineProperties({}, descrs));
this.props = config.props || {};
}
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
App.validateTarget(target);
mount(target: HTMLElement, options?: MountOptions): Promise<InstanceType<T>> {
this.checkTarget(target);
const node = this.makeNode(this.Root, this.props);
const prom = this.mountNode(node, target, options);
this.root = node;
return prom;
}
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
checkTarget(target: HTMLElement) {
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");
}
}
makeNode(Component: T, props: any): ComponentNode {
return new ComponentNode(Component, props, this);
}
@@ -102,10 +96,10 @@ export class App<
}
}
export async function mount<T extends abstract new (...args: any) => any = any, P = any, E = any>(
C: T & ComponentConstructor<P, E>,
export async function mount<T extends typeof Component>(
C: T,
target: HTMLElement,
config: AppConfig<P, E> & MountOptions = {}
): Promise<Component<P, E> & InstanceType<T>> {
config: AppConfig & MountOptions = {}
): Promise<InstanceType<T>> {
return new App(C, config).mount(target, config);
}
+1 -1
View File
@@ -135,7 +135,7 @@ export function safeOutput(value: any): ReturnType<typeof toggler> {
} else if (value instanceof LazyValue) {
safeKey = `lazy_value`;
block = value.evaluate();
} else if (value instanceof String || typeof value === "string") {
} else if (typeof value === "string") {
safeKey = "string_unsafe";
block = text(value);
} else {
+13 -17
View File
@@ -1,11 +1,11 @@
import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
import { compile, Template } from "../compiler";
import { component, getCurrent } from "../component/component_node";
import { component } from "../component/component_node";
import { UTILS } from "./template_helpers";
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
export const globalTemplates: { [key: string]: string | Element } = {};
export const globalTemplates: { [key: string]: string | Node } = {};
function parseXML(xml: string): Document {
const parser = new DOMParser();
@@ -55,7 +55,7 @@ export class TemplateSet {
const template = this.getTemplate(subTemplate);
return toggler(subTemplate, template.call(owner, ctx, parent, key));
},
getTemplate: (name: string) => this.getTemplate(name),
getTemplate: (name: string, nameSpace?: string) => this.getTemplate(name, nameSpace),
});
constructor(config: TemplateSetConfig = {}) {
@@ -67,11 +67,7 @@ export class TemplateSet {
}
}
addTemplate(
name: string,
template: string | Element,
options: { allowDuplicate?: boolean } = {}
) {
addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) {
if (name in this.rawTemplates && !options.allowDuplicate) {
throw new Error(`Template ${name} already defined`);
}
@@ -86,22 +82,20 @@ export class TemplateSet {
xml = xml instanceof Document ? xml : parseXML(xml);
for (const template of xml.querySelectorAll("[t-name]")) {
const name = template.getAttribute("t-name")!;
template.removeAttribute("t-name");
this.addTemplate(name, template, options);
}
}
getTemplate(name: string): Template {
getTemplate(name: string, nameSpace?: string): Template {
if (!(name in this.templates)) {
const rawTemplate = this.rawTemplates[name];
if (rawTemplate === undefined) {
let extraInfo = "";
try {
const componentName = getCurrent().component.constructor.name;
extraInfo = ` (for component "${componentName}")`;
} catch {}
throw new Error(`Missing template: "${name}"${extraInfo}`);
throw new Error(`Missing template: "${name}"`);
}
const templateFn = this._compileTemplate(name, rawTemplate);
console.log("getTemplate", nameSpace);
const templateFn = this._compileTemplate(name, rawTemplate, nameSpace);
// first add a function to lazily get the template, in case there is a
// recursive call to the template name
const templates = this.templates;
@@ -114,12 +108,14 @@ export class TemplateSet {
return this.templates[name];
}
_compileTemplate(name: string, template: string | Element) {
_compileTemplate(name: string, template: string | Node, nameSpace?: string) {
console.log("CompileTpm", nameSpace)
return compile(template, {
name,
dev: this.dev,
translateFn: this.translateFn,
translatableAttributes: this.translatableAttributes,
nameSpace: nameSpace,
});
}
}
+1 -1
View File
@@ -139,7 +139,7 @@ export function updateClass(this: HTMLElement, val: any, oldVal: any) {
export function makePropSetter(name: string): Setter<HTMLElement> {
return function setProp(this: HTMLElement, value: any) {
(this as any)[name] = value || "";
(this as any)[name] = value;
};
}
+50 -74
View File
@@ -299,13 +299,12 @@ interface BlockCtx {
locations: IndexedLocation[];
children: Child[];
cbRefs: number[];
refList: (() => void)[][];
}
function buildContext(tree: IntermediateTree, ctx?: BlockCtx, fromIdx?: number): BlockCtx {
if (!ctx) {
const children = new Array(tree.info.filter((v) => v.type === "child").length);
ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN, refList: [] };
ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN };
fromIdx = 0;
}
if (tree.refN) {
@@ -409,11 +408,11 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
break;
}
case "ref":
const index = ctx.cbRefs.push(info.idx) - 1;
ctx.cbRefs.push(info.idx);
ctx.locations.push({
idx: info.idx,
refIdx: info.refIdx!,
setData: makeRefSetter(index, ctx.refList),
setData: setRef,
updateData: NO_OP,
});
}
@@ -427,21 +426,12 @@ function buildBlock(template: HTMLElement, ctx: BlockCtx): BlockType {
let B = createBlockClass(template, ctx);
if (ctx.cbRefs.length) {
const cbRefs = ctx.cbRefs;
const refList = ctx.refList;
let cbRefsNumber = cbRefs.length;
const refs = ctx.cbRefs;
B = class extends B {
mount(parent: HTMLElement, afterNode: Node | null) {
refList.push(new Array(cbRefsNumber));
super.mount(parent, afterNode);
for (let cbRef of refList.pop()!) {
cbRef();
}
}
remove() {
super.remove();
for (let cbRef of cbRefs) {
let fn = (this as any).data[cbRef];
for (let ref of refs) {
let fn = (this as any).data[ref];
fn(null);
}
}
@@ -486,12 +476,12 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
const nodeInsertBefore = nodeProto.insertBefore;
const elementRemove = elementProto.remove;
class Block {
return class Block {
el: HTMLElement | undefined;
parentEl?: HTMLElement | undefined;
data: any[] | undefined;
children?: (VNode | undefined)[];
refs: Node[] | undefined;
data: any[] | undefined;
parentEl?: HTMLElement | undefined;
children?: (VNode | undefined)[];
constructor(data?: any[]) {
this.data = data;
@@ -512,62 +502,46 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
nodeInsertBefore.call(this.parentEl, this.el!, target);
}
toString() {
const div = document.createElement("div");
this.mount(div, null);
return div.innerHTML;
}
mount(parent: HTMLElement, afterNode: Node | null) {
const el = nodeCloneNode.call(template, true) as HTMLElement;
nodeInsertBefore.call(parent, el, afterNode);
this.el = el;
this.parentEl = parent;
}
patch(other: Block, withBeforeRemove: boolean) {}
}
if (isDynamic) {
Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) {
const el = nodeCloneNode.call(template, true);
// 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]);
}
}
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]);
}
// 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);
// 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;
};
Block.prototype.patch = function patch(other: Block, withBeforeRemove: boolean) {
}
patch(other: Block, withBeforeRemove: boolean) {
if (this === other) {
return;
}
@@ -612,17 +586,19 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
}
}
}
};
}
return Block;
}
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 makeRefSetter(index: number, refs: (() => void)[][]): Setter<HTMLElement> {
return function setRef(this: HTMLElement, fn: any) {
refs[refs.length - 1][index] = () => fn(this);
};
function setRef(this: HTMLElement, fn: any) {
fn(this);
}
-1
View File
@@ -61,7 +61,6 @@ class VHtml {
// remove current content
this.remove();
this.content = content;
this.html = other.html;
}
}
+3 -3
View File
@@ -9,11 +9,11 @@ const characterDataSetData = getDescriptor(characterDataProto, "data").set!;
const nodeRemoveChild = nodeProto.removeChild;
abstract class VSimpleNode {
text: string | String;
text: string;
parentEl?: HTMLElement | undefined;
el?: any;
constructor(text: string | String) {
constructor(text: string) {
this.text = text;
}
@@ -65,7 +65,7 @@ class VComment extends VSimpleNode {
patch() {}
}
export function text(str: string | String): VNode<VText> {
export function text(str: string): VNode<VText> {
return new VText(str);
}
+63 -96
View File
@@ -33,6 +33,7 @@ export interface Config {
export interface CodeGenOptions extends Config {
hasSafeContext?: boolean;
name?: string;
nameSpace?: string;
}
// using a non-html document so that <inner/outer>HTML serializes as XML instead
@@ -128,7 +129,6 @@ interface Context {
translate: boolean;
tKeyExpr: string | null;
nameSpace?: string;
tModelSelectedExpr?: string;
}
function createContext(parentCtx: Context, params?: Partial<Context>) {
@@ -140,7 +140,6 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
translate: parentCtx.translate,
tKeyExpr: null,
nameSpace: parentCtx.nameSpace,
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
},
params
);
@@ -214,28 +213,21 @@ export class CodeGenerator {
templateName?: string;
dev: boolean;
translateFn: (s: string) => string;
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
translatableAttributes: string[];
ast: AST;
staticCalls: { id: string; template: string }[] = [];
staticCalls: { id: string; template: string, nameSpace?: string }[] = [];
helpers: Set<string> = new Set();
nameSpace?: string;
constructor(ast: AST, options: CodeGenOptions) {
this.translateFn = options.translateFn || ((s: string) => s);
if (options.translatableAttributes) {
const attrs = new Set(TRANSLATABLE_ATTRS);
for (let attr of options.translatableAttributes) {
if (attr.startsWith("-")) {
attrs.delete(attr.slice(1));
} else {
attrs.add(attr);
}
}
this.translatableAttributes = [...attrs];
}
this.translatableAttributes = options.translatableAttributes || TRANSLATABLE_ATTRS;
this.hasSafeContext = options.hasSafeContext || false;
this.dev = options.dev || false;
this.ast = ast;
this.templateName = options.name;
this.nameSpace = options.nameSpace;
console.log('ThisNS', this.nameSpace)
}
generateCode(): string {
@@ -250,6 +242,7 @@ export class CodeGenerator {
isLast: true,
translate: true,
tKeyExpr: null,
nameSpace: this.nameSpace,
});
// define blocks and utility functions
let mainCode = [
@@ -262,8 +255,12 @@ export class CodeGenerator {
mainCode.push(`// Template name: "${this.templateName}"`);
}
for (let { id, template } of this.staticCalls) {
mainCode.push(`const ${id} = getTemplate(${template});`);
for (let { id, template, nameSpace } of this.staticCalls) {
let ns;
if (nameSpace) {
ns = `, "${nameSpace}"`;
}
mainCode.push(`const ${id} = getTemplate(${template}${ns});`);
}
// define all blocks
@@ -534,10 +531,10 @@ export class CodeGenerator {
compileTDomNode(ast: ASTDomNode, ctx: Context) {
let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null;
let codeIdx = this.target.code.length;
if (isNewBlock) {
if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {
if ((ast.dynamicTag || ctx.tKeyExpr) && ctx.block) {
this.insertAnchor(ctx.block!);
}
block = this.createBlock(block, "block", ctx);
@@ -551,40 +548,29 @@ export class CodeGenerator {
// attributes
const attrs: { [key: string]: string } = {};
const nameSpace = ast.ns || ctx.nameSpace;
console.log(nameSpace)
if (nameSpace && isNewBlock) {
// specific namespace uri
attrs["block-ns"] = nameSpace;
}
for (let key in ast.attrs) {
let expr, attrName;
if (key.startsWith("t-attf")) {
expr = interpolate(ast.attrs[key]);
let expr = interpolate(ast.attrs[key]);
const idx = block!.insertData(expr, "attr");
attrName = key.slice(7);
attrs["block-attribute-" + idx] = attrName;
attrs["block-attribute-" + idx] = key.slice(7);
} else if (key.startsWith("t-att")) {
expr = compileExpr(ast.attrs[key]);
let expr = compileExpr(ast.attrs[key]);
const idx = block!.insertData(expr, "attr");
if (key === "t-att") {
attrs[`block-attributes`] = String(idx);
} else {
attrName = key.slice(6);
attrs[`block-attribute-${idx}`] = attrName;
attrs[`block-attribute-${idx}`] = key.slice(6);
}
} else if (this.translatableAttributes.includes(key)) {
attrs[key] = this.translateFn(ast.attrs[key]);
} else {
expr = `"${ast.attrs[key]}"`;
attrName = key;
attrs[key] = ast.attrs[key];
}
if (attrName === "value" && ctx.tModelSelectedExpr) {
let selectedId = block!.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, "attr");
attrs[`block-attribute-${selectedId}`] = "selected";
}
}
// event handlers
@@ -624,10 +610,8 @@ export class CodeGenerator {
}
// t-model
let tModelSelectedExpr;
if (ast.model) {
const {
hasDynamicChildren,
baseExpr,
expr,
eventType,
@@ -638,25 +622,20 @@ export class CodeGenerator {
} = ast.model;
const baseExpression = compileExpr(baseExpr);
const bExprId = this.generateId("bExpr");
this.addLine(`const ${bExprId} = ${baseExpression};`);
const id = this.generateId();
this.addLine(`const bExpr${id} = ${baseExpression};`);
const expression = compileExpr(expr);
const exprId = this.generateId("expr");
this.addLine(`const ${exprId} = ${expression};`);
const fullExpression = `${bExprId}[${exprId}]`;
let idx: number;
if (specialInitTargetAttr) {
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
idx = block!.insertData(
`${baseExpression}[${expression}] === '${attrs[targetAttr]}'`,
"attr"
);
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
} else if (hasDynamicChildren) {
const bValueId = this.generateId("bValue");
tModelSelectedExpr = `${bValueId}`;
this.addLine(`let ${tModelSelectedExpr} = ${fullExpression}`);
} else {
idx = block!.insertData(`${fullExpression}`, "attr");
idx = block!.insertData(`${baseExpression}[${expression}]`, "attr");
attrs[`block-attribute-${idx}`] = targetAttr;
}
this.helpers.add("toNumber");
@@ -664,7 +643,7 @@ export class CodeGenerator {
valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
const handler = `[(ev) => { ${fullExpression} = ${valueCode}; }]`;
const handler = `[(ev) => { bExpr${id}[${expression}] = ${valueCode}; }]`;
idx = block!.insertData(handler, "hdlr");
attrs[`block-handler-${idx}`] = eventType;
}
@@ -689,7 +668,6 @@ export class CodeGenerator {
isLast: ctx.isLast && i === children.length - 1,
tKeyExpr: ctx.tKeyExpr,
nameSpace,
tModelSelectedExpr,
});
this.compileAST(child, subCtx);
}
@@ -992,7 +970,7 @@ export class CodeGenerator {
} else {
const id = this.generateId(`callTemplate_`);
this.helpers.add("getTemplate");
this.staticCalls.push({ id, template: subTemplate });
this.staticCalls.push({ id, template: subTemplate, nameSpace: ctx.nameSpace });
block = this.createBlock(block, "multi", ctx);
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block!, {
...ctx,
@@ -1050,50 +1028,29 @@ export class CodeGenerator {
return parts.join("__");
}
/**
* Formats a prop name and value into a string suitable to be inserted in the
* generated code. For example:
*
* Name Value Result
* ---------------------------------------------------------
* "number" "state" "number: ctx['state']"
* "something" "" "something: undefined"
* "some-prop" "state" "'some-prop': ctx['state']"
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
*/
formatProp(name: string, value: string): string {
value = this.captureExpression(value);
if (name.includes(".")) {
let [_name, suffix] = name.split(".");
if (suffix === "bind") {
this.helpers.add("bind");
name = _name;
value = `bind(ctx, ${value || undefined})`;
} else {
throw new Error("Invalid prop suffix");
}
}
name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
return `${name}: ${value || undefined}`;
}
formatPropObject(obj: { [prop: string]: any }): string {
const params = [];
for (const [n, v] of Object.entries(obj)) {
params.push(this.formatProp(n, v));
}
return params.join(", ");
}
compileComponent(ast: ASTComponent, ctx: Context) {
let { block } = ctx;
// props
const hasSlotsProp = "slots" in ast.props;
const props: string[] = [];
const propExpr = this.formatPropObject(ast.props);
if (propExpr) {
props.push(propExpr);
let hasSlotsProp = false;
for (let propName in ast.props) {
let propValue = this.captureExpression(ast.props[propName]) || undefined;
if (propName.includes(".")) {
let [name, suffix] = propName.split(".");
if (suffix === "bind") {
this.helpers.add("bind");
propName = name;
propValue = `bind(ctx, ${propValue})`;
} else {
throw new Error("Invalid prop suffix");
}
}
propName = /^[a-z_]+$/i.test(propName) ? propName : `'${propName}'`;
props.push(`${propName}: ${propValue}`);
if (propName === "slots") {
hasSlotsProp = true;
}
}
// slots
@@ -1116,7 +1073,9 @@ export class CodeGenerator {
params.push(`__scope: "${scope}"`);
}
if (ast.slots[slotName].attrs) {
params.push(this.formatPropObject(ast.slots[slotName].attrs!));
for (const [n, v] of Object.entries(ast.slots[slotName].attrs!)) {
params.push(`${n}: ${compileExpr(v) || undefined}`);
}
}
const slotInfo = `{${params.join(", ")}}`;
slotStr.push(`'${slotName}': ${slotInfo}`);
@@ -1133,7 +1092,7 @@ export class CodeGenerator {
let propString = propStr;
if (ast.dynamicProps) {
if (!props.length) {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
propString = `${compileExpr(ast.dynamicProps)}`;
} else {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
}
@@ -1142,7 +1101,7 @@ export class CodeGenerator {
let propVar: string;
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
propVar = this.generateId("props");
this.addLine(`const ${propVar!} = ${propString};`);
this.addLine(`const ${propVar!} = ${propString}`);
propString = propVar!;
}
@@ -1161,7 +1120,7 @@ export class CodeGenerator {
}
if (this.dev) {
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx);`);
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`);
}
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
@@ -1195,7 +1154,15 @@ export class CodeGenerator {
slotName = "'" + ast.name + "'";
}
const scope = ast.attrs ? `{${this.formatPropObject(ast.attrs)}}` : null;
let scope = null;
if (ast.attrs) {
const params = [];
for (const [n, v] of Object.entries(ast.attrs!)) {
params.push(`${n}: ${compileExpr(v) || undefined}`);
}
scope = `{${params.join(", ")}}`;
}
if (ast.defaultContent) {
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
+4 -4
View File
@@ -8,11 +8,9 @@ export type TemplateFunction = (blocks: any, utils: any) => Template;
interface CompileOptions extends Config {
name?: string;
nameSpace?: string,
}
export function compile(
template: string | Element,
options: CompileOptions = {}
): TemplateFunction {
export function compile(template: string | Node, options: CompileOptions = {}): TemplateFunction {
// parsing
const ast = parse(template);
@@ -23,8 +21,10 @@ export function compile(
: !template.includes("t-set") && !template.includes("t-call");
// code generation
console.log("compile", options)
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
const code = codeGenerator.generateCode();
console.log(code)
// template function
return new Function("bdom, helpers", code) as TemplateFunction;
}
+1 -3
View File
@@ -329,9 +329,7 @@ export function compileExprToArray(expr: string): Token[] {
// Mark all variables that have been used locally.
// This assumes the expression has only one scope (incorrect but "good enough for now")
for (const token of tokens) {
if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
token.originalValue = token.value;
token.value = `_${token.value}`;
if (token.type === "SYMBOL" && localVars.has(token.value)) {
token.isLocal = true;
}
}
+29 -66
View File
@@ -33,17 +33,6 @@ export interface ASTComment {
value: string;
}
interface TModelInfo {
hasDynamicChildren?: boolean;
baseExpr: string;
expr: string;
targetAttr: string;
specialInitTargetAttr: string | null;
eventType: "change" | "click" | "input";
shouldTrim: boolean;
shouldNumberize: boolean;
}
export interface ASTDomNode {
type: ASTType.DomNode;
tag: string;
@@ -52,7 +41,15 @@ export interface ASTDomNode {
content: AST[];
ref: string | null;
on: { [key: string]: string };
model?: TModelInfo | null;
model: {
baseExpr: string;
expr: string;
targetAttr: string;
specialInitTargetAttr: string | null;
eventType: "change" | "click" | "input";
shouldTrim: boolean;
shouldNumberize: boolean;
} | null;
ns: string | null;
}
@@ -180,35 +177,23 @@ export type AST =
// -----------------------------------------------------------------------------
// Parser
// -----------------------------------------------------------------------------
const cache: WeakMap<Element, AST> = new WeakMap();
export function parse(xml: string | Element): AST {
if (typeof xml === "string") {
const elem = parseXML(`<t>${xml}</t>`).firstChild as Element;
return _parse(elem);
}
let ast = cache.get(xml);
if (!ast) {
// we clone here the xml to prevent modifying it in place
ast = _parse(xml.cloneNode(true) as Element);
cache.set(xml, ast);
}
return ast;
}
function _parse(xml: Element): AST {
normalizeXML(xml);
const ctx = { inPreTag: false, inSVG: false };
return parseNode(xml, ctx) || { type: ASTType.Text, value: "" };
}
interface ParsingContext {
tModelInfo?: TModelInfo | null;
inPreTag: boolean;
inSVG: boolean;
}
function parseNode(node: Node, ctx: ParsingContext): AST | null {
export function parse(xml: string | Node): AST {
const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element);
normalizeXML(node);
const ctx = { inPreTag: false, inSVG: false };
const ast = parseNode(node, ctx);
if (!ast) {
return { type: ASTType.Text, value: "" };
}
return ast;
}
function parseNode(node: ChildNode, ctx: ParsingContext): AST | null {
if (!(node instanceof Element)) {
return parseTextCommentNode(node, ctx);
}
@@ -248,7 +233,7 @@ function parseTNode(node: Element, ctx: ParsingContext): AST | null {
const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g;
function parseTextCommentNode(node: Node, ctx: ParsingContext): AST | null {
function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null {
if (node.nodeType === Node.TEXT_NODE) {
let value = node.textContent || "";
if (!ctx.inPreTag) {
@@ -296,8 +281,6 @@ function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null {
const hasDotAtTheEnd = /\.[\w_]+\s*$/;
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
const { tagName } = node;
const dynamicTag = node.getAttribute("t-tag");
@@ -305,23 +288,22 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
if (tagName === "t" && !dynamicTag) {
return null;
}
if (tagName.startsWith("block-")) {
throw new Error(`Invalid tag name: '${tagName}'`);
}
ctx = Object.assign({}, ctx);
if (tagName === "pre") {
ctx.inPreTag = true;
}
const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG;
const shouldAddSVGNS = tagName === "svg" || (tagName === "g" && !ctx.inSVG);
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
const ref = node.getAttribute("t-ref");
node.removeAttribute("t-ref");
const children = parseChildren(node, ctx);
const nodeAttrsNames = node.getAttributeNames();
const attrs: ASTDomNode["attrs"] = {};
const on: ASTDomNode["on"] = {};
let model: TModelInfo | null = null;
let model: ASTDomNode["model"] = null;
for (let attr of nodeAttrsNames) {
const value = node.getAttribute(attr)!;
@@ -369,26 +351,13 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
};
if (isSelect) {
// don't pollute the original ctx
ctx = Object.assign({}, ctx);
ctx.tModelInfo = model;
}
} else if (attr.startsWith("block-")) {
throw new Error(`Invalid attribute: '${attr}'`);
} else if (attr !== "t-name") {
} else {
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
throw new Error(`Unknown QWeb directive: '${attr}'`);
}
const tModel = ctx.tModelInfo;
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
tModel.hasDynamicChildren = true;
}
attrs[attr] = value;
}
}
const children = parseChildren(node, ctx);
return {
type: ASTType.DomNode,
tag: tagName,
@@ -682,9 +651,6 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const dynamicProps = node.getAttribute("t-props");
node.removeAttribute("t-props");
const defaultSlotScope = node.getAttribute("t-slot-scope");
node.removeAttribute("t-slot-scope");
const props: ASTComponent["props"] = {};
for (let name of node.getAttributeNames()) {
const value = node.getAttribute(name)!;
@@ -750,9 +716,6 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const defaultContent = parseChildNodes(clone, ctx);
if (defaultContent) {
slots.default = { content: defaultContent };
if (defaultSlotScope) {
slots.default.scope = defaultSlotScope;
}
}
}
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots };
@@ -823,7 +786,7 @@ function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
/**
* Parse all the child nodes of a given node and return a list of ast elements
*/
function parseChildren(node: Element, ctx: ParsingContext): AST[] {
function parseChildren(node: Node, ctx: ParsingContext): AST[] {
const children: AST[] = [];
for (let child of node.childNodes) {
const childAst = parseNode(child, ctx);
@@ -842,7 +805,7 @@ function parseChildren(node: Element, ctx: ParsingContext): AST[] {
* Parse all the child nodes of a given node and return an ast if possible.
* In the case there are multiple children, they are wrapped in a astmulti.
*/
function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
function parseChildNodes(node: Node, ctx: ParsingContext): AST | null {
const children = parseChildren(node, ctx);
switch (children.length) {
case 0:
-16
View File
@@ -4,25 +4,9 @@ import type { ComponentNode } from "./component_node";
// Component Class
// -----------------------------------------------------------------------------
type Props = { [key: string]: any };
interface StaticComponentProperties {
template: string;
defaultProps?: any;
props?: any;
}
export type ComponentConstructor<P extends Props = any, E = any> = (new (
props: P,
env: E,
node: ComponentNode
) => Component<P, E>) &
StaticComponentProperties;
export class Component<Props = any, Env = any> {
static template: string = "";
static props?: any;
static defaultProps?: any;
props: Props;
env: Env;
+42 -63
View File
@@ -1,6 +1,6 @@
import type { App, Env } from "../app/app";
import { BDom, VNode } from "../blockdom";
import { Component, ComponentConstructor } from "./component";
import { Component } from "./component";
import {
Fiber,
makeChildFiber,
@@ -15,10 +15,7 @@ import { STATUS } from "./status";
let currentNode: ComponentNode | null = null;
export function getCurrent(): ComponentNode {
if (!currentNode) {
throw new Error("No active component (a hook function should only be called in 'setup')");
}
export function getCurrent(): ComponentNode | null {
return currentNode;
}
@@ -77,11 +74,13 @@ export function component(
type LifecycleHook = Function;
export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E>> {
export class ComponentNode<T extends typeof Component = typeof Component>
implements VNode<ComponentNode>
{
el?: HTMLElement | Text | undefined;
app: App;
fiber: Fiber | null = null;
component: Component<P, E>;
component: InstanceType<T>;
bdom: BDom | null = null;
status: STATUS = STATUS.NEW;
@@ -100,7 +99,7 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
patched: LifecycleHook[] = [];
willDestroy: LifecycleHook[] = [];
constructor(C: ComponentConstructor<P, E>, props: P, app: App, parent?: ComponentNode) {
constructor(C: T, props: any, app: App, parent?: ComponentNode) {
currentNode = this;
this.app = app;
this.parent = parent || null;
@@ -111,7 +110,6 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
this.component = new C(props, env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
this.component.setup();
currentNode = null;
}
mountComponent(target: any, options?: MountOptions) {
@@ -120,30 +118,20 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
this.initiateRender(fiber);
}
initiateRender(fiber: Fiber | MountFiber) {
async initiateRender(fiber: Fiber | MountFiber) {
this.fiber = fiber;
if (this.mounted.length) {
fiber.root!.mounted.push(fiber);
}
const component = this.component;
const finish = () => {
if (this.status === STATUS.NEW && this.fiber === fiber) {
this._render(fiber);
}
}
let willStartResults;
try {
willStartResults = this.willStart.map((f) => f.call(component));
await Promise.all(this.willStart.map((f) => f.call(component)));
} catch (e) {
handleError({ node: this, error: e });
return;
}
if (willStartResults.some(result => result instanceof Promise)) {
Promise.all(willStartResults)
.then(finish)
.catch(e => handleError({ node: this, error: e }));
} else {
finish();
if (this.status === STATUS.NEW && this.fiber === fiber) {
this._render(fiber);
}
}
@@ -163,27 +151,25 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
const fiber = makeRootFiber(this);
this.fiber = fiber;
debugger;
window.requestAnimationFrame(() => {
if (this.status === STATUS.DESTROYED) {
return;
}
// We only want to actually render the component if the following two
// conditions are true:
// * this.fiber: it could be null, in which case the render has been cancelled
// * (current || !fiber.parent): if current is not null, this means that the
// render function was called when a render was already occurring. In this
// case, the pending rendering was cancelled, and the fiber needs to be
// rendered to complete the work. If current is null, we check that the
// fiber has no parent. If that is the case, the fiber was downgraded from
// a root fiber to a child fiber in the previous microtick, because it was
// embedded in a rendering coming from above, so the fiber will be rendered
// in the next microtick anyway, so we should not render it again.
if (this.fiber === fiber && (current || !fiber.parent)) {
this._render(fiber);
}
});
this.app.scheduler.addFiber(fiber);
await Promise.resolve();
if (this.status === STATUS.DESTROYED) {
return;
}
// We only want to actually render the component if the following two
// conditions are true:
// * this.fiber: it could be null, in which case the render has been cancelled
// * (current || !fiber.parent): if current is not null, this means that the
// render function was called when a render was already occurring. In this
// case, the pending rendering was cancelled, and the fiber needs to be
// rendered to complete the work. If current is null, we check that the
// fiber has no parent. If that is the case, the fiber was downgraded from
// a root fiber to a child fiber in the previous microtick, because it was
// embedded in a rendering coming from above, so the fiber will be rendered
// in the next microtick anyway, so we should not render it again.
if (this.fiber === fiber && (current || !fiber.parent)) {
this._render(fiber);
}
}
_render(fiber: Fiber | RootFiber) {
@@ -219,32 +205,25 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
this.status = STATUS.DESTROYED;
}
updateAndRender(props: any, parentFiber: Fiber) {
async updateAndRender(props: any, parentFiber: Fiber) {
// update
const fiber = makeChildFiber(this, parentFiber);
this.fiber = fiber;
const component = this.component;
applyDefaultProps(props, component.constructor as any);
const finish = () => {
if (fiber !== this.fiber) {
return;
}
component.props = props;
this._render(fiber);
const parentRoot = parentFiber.root!;
if (this.willPatch.length) {
parentRoot.willPatch.push(fiber);
}
if (this.patched.length) {
parentRoot.patched.push(fiber);
}
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
await prom;
if (fiber !== this.fiber) {
return;
}
const willUpdatePropsResults = this.willUpdateProps.map((f) => f.call(component, props));
if (willUpdatePropsResults.some(res => res instanceof Promise)) {
Promise.all(willUpdatePropsResults).then(finish);
} else {
finish();
component.props = props;
this._render(fiber);
const parentRoot = parentFiber.root!;
if (this.willPatch.length) {
parentRoot.willPatch.push(fiber);
}
if (this.patched.length) {
parentRoot.patched.push(fiber);
}
}
+6 -5
View File
@@ -6,7 +6,8 @@ import { STATUS } from "./status";
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
let current = node.fiber;
if (current) {
cancelFibers(current.children);
let root = parent.root;
cancelFibers(root, current.children);
current.root = null;
}
return new Fiber(node, parent);
@@ -16,7 +17,7 @@ export function makeRootFiber(node: ComponentNode): Fiber {
let current = node.fiber;
if (current) {
let root = current.root!;
root.counter -= cancelFibers(current.children);
root.counter -= cancelFibers(root, current.children);
current.children = [];
root.counter++;
current.bdom = null;
@@ -41,14 +42,15 @@ export function makeRootFiber(node: ComponentNode): Fiber {
/**
* @returns number of not-yet rendered fibers cancelled
*/
function cancelFibers(fibers: Fiber[]): number {
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(fiber.children);
result += cancelFibers(root, fiber.children);
}
return result;
}
@@ -157,7 +159,6 @@ export class MountFiber extends RootFiber {
let current: Fiber | undefined = this;
try {
const node = this.node;
(node.app.constructor as any).validateTarget(this.target);
if (node.bdom) {
// this is a complicated situation: if we mount a fiber with an existing
// bdom, this means that this same fiber was already completed, mounted,
+21 -56
View File
@@ -1,102 +1,67 @@
import { getCurrent } from "./component_node";
import { nodeErrorHandlers } from "./error_handling";
function wrapError(fn: (...args: any[]) => any, hookName: string) {
const error = new Error(`The following error occurred in ${hookName}: `) as Error & {
cause: any;
};
return (...args: any[]) => {
try {
const result = fn(...args);
if (result instanceof Promise) {
return result.catch((cause) => {
error.cause = cause;
if (cause instanceof Error) {
error.message += `"${cause.message}"`;
}
throw error;
});
}
return result;
} catch (cause) {
if (cause instanceof Error) {
error.message += `"${cause.message}"`;
}
throw error;
}
};
}
// -----------------------------------------------------------------------------
// hooks
// -----------------------------------------------------------------------------
export function onWillStart(fn: () => Promise<void> | void | any) {
const node = getCurrent();
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
const node = getCurrent()!;
node.willStart.push(fn.bind(node.component));
}
export function onWillUpdateProps(fn: (nextProps: any) => Promise<void> | void | any) {
const node = getCurrent();
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
const node = getCurrent()!;
node.willUpdateProps.push(fn.bind(node.component));
}
export function onMounted(fn: () => void | any) {
const node = getCurrent();
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
const node = getCurrent()!;
node.mounted.push(fn.bind(node.component));
}
export function onWillPatch(fn: () => Promise<void> | any | void) {
const node = getCurrent();
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
const node = getCurrent()!;
node.willPatch.unshift(fn.bind(node.component));
}
export function onPatched(fn: () => void | any) {
const node = getCurrent();
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
const node = getCurrent()!;
node.patched.push(fn.bind(node.component));
}
export function onWillUnmount(fn: () => Promise<void> | void | any) {
const node = getCurrent();
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
const node = getCurrent()!;
node.willUnmount.unshift(fn.bind(node.component));
}
export function onWillDestroy(fn: () => Promise<void> | void | any) {
const node = getCurrent();
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
const node = getCurrent()!;
node.willDestroy.push(fn.bind(node.component));
}
export function onWillRender(fn: () => void | any) {
const node = getCurrent();
const node = getCurrent()!;
const renderFn = node.renderFn;
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.renderFn = decorate(() => {
node.renderFn = () => {
fn.call(node.component);
return renderFn();
}, "onWillRender");
};
}
export function onRendered(fn: () => void | any) {
const node = getCurrent();
const node = getCurrent()!;
const renderFn = node.renderFn;
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.renderFn = decorate(() => {
node.renderFn = () => {
const result = renderFn();
fn.call(node.component);
return result;
}, "onRendered");
};
}
type OnErrorCallback = (error: any) => void | any;
export function onError(callback: OnErrorCallback) {
const node = getCurrent();
const node = getCurrent()!;
let handlers = nodeErrorHandlers.get(node);
if (!handlers) {
handlers = [];
+13 -30
View File
@@ -1,16 +1,16 @@
import { ComponentConstructor } from "./component";
import { Component } from "./component";
/**
* Apply default props (only top level).
*
* Note that this method does modify in place the props
*/
export function applyDefaultProps<P>(props: P, ComponentClass: ComponentConstructor<P>) {
const defaultProps = ComponentClass.defaultProps;
export function applyDefaultProps(props: { [key: string]: any }, ComponentClass: typeof Component) {
const defaultProps = (ComponentClass as any).defaultProps;
if (defaultProps) {
for (let propName in defaultProps) {
if ((props as any)[propName] === undefined) {
(props as any)[propName] = defaultProps[propName];
if (props![propName] === undefined) {
props![propName] = defaultProps[propName];
}
}
}
@@ -34,20 +34,13 @@ function getPropDescription(staticProps: any) {
* visit recursively the props and all the children to check if they are valid.
* This is why it is only done in 'dev' mode.
*/
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, parent?: any) {
const ComponentClass =
typeof name !== "string"
? name
: (parent.constructor.components[name] as ComponentConstructor<P> | undefined);
export const validateProps = function (name: string | typeof Component, props: any, parent?: any) {
const ComponentClass = (
typeof name !== "string" ? name : parent.constructor.components[name]
) as typeof Component;
if (!ComponentClass) {
// this is an error, wrong component. We silently return here instead so the
// error is triggered by the usual path ('component' function)
return;
}
applyDefaultProps(props, ComponentClass);
const defaultProps = ComponentClass.defaultProps || {};
let propsDef = getPropDescription(ComponentClass.props);
const allowAdditionalProps = "*" in propsDef;
@@ -55,18 +48,8 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
if (propName === "*") {
continue;
}
const propDef = propsDef[propName];
let isMandatory = !!propDef;
if (typeof propDef === "object" && "optional" in propDef) {
isMandatory = !propDef.optional;
}
if (isMandatory && propName in defaultProps) {
throw new Error(
`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`
);
}
if ((props as any)[propName] === undefined) {
if (isMandatory) {
if (props[propName] === undefined) {
if (propsDef[propName] && !propsDef[propName].optional) {
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
} else {
continue;
@@ -74,7 +57,7 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
}
let isValid;
try {
isValid = isValidProp((props as any)[propName], propDef);
isValid = isValidProp(props[propName], propsDef[propName]);
} catch (e) {
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
(e as Error).message
@@ -92,7 +75,7 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
}
}
}
}
};
/**
* Check if an invidual prop value matches its (static) prop definition
+7 -16
View File
@@ -11,7 +11,7 @@ import { onMounted, onPatched, onWillUnmount } from "./component/lifecycle_hooks
* html node or component.
*/
export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el: T | null } {
const node = getCurrent();
const node = getCurrent()!;
const refs = node.refs;
return {
get el(): T | null {
@@ -29,13 +29,7 @@ export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el:
* need a reference to the env of the component calling them.
*/
export function useEnv<E extends Env>(): E {
return getCurrent().component.env as any;
}
function extendEnv(currentEnv: Object, extension: Object): Object {
const env = Object.create(currentEnv);
const descrs = Object.getOwnPropertyDescriptors(extension);
return Object.freeze(Object.defineProperties(env, descrs));
return getCurrent()!.component.env as any;
}
/**
@@ -44,15 +38,12 @@ function extendEnv(currentEnv: Object, extension: Object): Object {
* constructor method.
*/
export function useSubEnv(envExtension: Env) {
const node = getCurrent();
node.component.env = extendEnv(node.component.env as any, envExtension);
useChildSubEnv(envExtension);
const node = getCurrent()!;
const env = Object.create(node.childEnv);
const descrs = Object.getOwnPropertyDescriptors(envExtension);
node.childEnv = Object.freeze(Object.defineProperties(env, descrs));
}
export function useChildSubEnv(envExtension: Env) {
const node = getCurrent();
node.childEnv = extendEnv(node.childEnv, envExtension);
}
// -----------------------------------------------------------------------------
// useEffect
// -----------------------------------------------------------------------------
@@ -121,7 +112,7 @@ export function useExternalListener(
handler: EventListener,
eventParams?: AddEventListenerOptions
) {
const node = getCurrent();
const node = getCurrent()!;
const boundHandler = handler.bind(node.component);
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
+2 -2
View File
@@ -42,8 +42,8 @@ export { useComponent } from "./component/component_node";
export { status } from "./component/status";
export { Memo } from "./memo";
export { xml } from "./app/template_set";
export { useState, reactive, markRaw, toRaw } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
export { useState, reactive } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
export { EventBus, whenReady, loadFile, markup } from "./utils";
export {
onWillStart,
+13 -49
View File
@@ -1,27 +1,18 @@
import { onWillDestroy } from "./component/lifecycle_hooks";
import { onWillUnmount } from "./component/lifecycle_hooks";
import { ComponentNode, getCurrent } from "./component/component_node";
import { batched, Callback } from "./utils";
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
const TARGET = Symbol("Target");
// Escape hatch to prevent reactivity system to turn something into a reactive
const SKIP = Symbol("Skip");
// Special key to subscribe to, to be notified of key creation/deletion
const KEYCHANGES = Symbol("Key changes");
type ObjectKey = string | number | symbol;
type Target = object;
export type Reactive<T extends Target = Target> = T & {
[TARGET]: any;
};
type NonReactive<T extends Target = Target> = T & {
[SKIP]: any;
};
const objectToString = Object.prototype.toString;
/**
* Checks whether a given value can be made into a reactive object.
*
@@ -29,35 +20,14 @@ const objectToString = Object.prototype.toString;
* @returns whether the value can be made reactive
*/
function canBeMadeReactive(value: any): boolean {
if (typeof value !== "object") {
return false;
}
// extract "RawType" from strings like "[object RawType]" => this lets us
// ignore many native objects such as Promise (whose toString is [object Promise])
// or Date ([object Date]).
const rawType = objectToString.call(value).slice(8, -1);
return rawType === "Object" || rawType === "Array";
}
/**
* Mark an object or array so that it is ignored by the reactivity system
*
* @param value the value to mark
* @returns the object itself
*/
export function markRaw<T extends Target>(value: T): NonReactive<T> {
(value as any)[SKIP] = true;
return value as NonReactive<T>;
}
/**
* Given a reactive objet, return the raw (non reactive) underlying object
*
* @param value a reactive value
* @returns the underlying value
*/
export function toRaw<T extends object>(value: Reactive<T>): T {
return value[TARGET] || value;
return (
typeof value === "object" &&
value !== null &&
!(value instanceof Date) &&
!(value instanceof Promise) &&
!(value instanceof String) &&
!(value instanceof Number)
);
}
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
@@ -160,16 +130,10 @@ const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive>>();
* reactive has changed
* @returns a proxy that tracks changes to it
*/
export function reactive<T extends Target>(
target: T,
callback: Callback = () => {}
): Reactive<T> | NonReactive<T> {
export function reactive<T extends Target>(target: T, callback: Callback = () => {}): Reactive<T> {
if (!canBeMadeReactive(target)) {
throw new Error(`Cannot make the given value reactive`);
}
if (SKIP in target) {
return target as NonReactive<T>;
}
const originalTarget = (target as Reactive)[TARGET];
if (originalTarget) {
return reactive(originalTarget, callback);
@@ -238,14 +202,14 @@ const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
* relevant changes
* @see reactive
*/
export function useState<T extends object>(state: T): Reactive<T> | NonReactive<T> {
const node = getCurrent();
export function useState<T extends object>(state: T): Reactive<T> {
const node = getCurrent()!;
if (!batchedRenderFunctions.has(node)) {
batchedRenderFunctions.set(
node,
batched(() => node.render())
);
onWillDestroy(() => clearReactivesForCallback(render));
onWillUnmount(() => clearReactivesForCallback(render));
}
const render = batchedRenderFunctions.get(node)!;
const reactiveState = reactive(state, render);
-9
View File
@@ -24,15 +24,6 @@ export function batched(callback: Callback): Callback {
};
}
export function validateTarget(target: HTMLElement) {
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");
}
}
export class EventBus extends EventTarget {
trigger(name: string, payload?: any) {
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
-9
View File
@@ -169,15 +169,6 @@ describe("properties", () => {
expect(input.value).toBe("potato");
});
test("input with value attribute, and undefined given", () => {
const block = createBlock(`<input block-attribute-0="value"/>`);
const tree = block([undefined]);
mount(tree, fixture);
const input = fixture.querySelector("input")!;
expect(input.value).toBe("");
});
test("input type=checkbox with checked attribute", () => {
// render input with initial value
const block = createBlock(`<input type="checkbox" block-attribute-0="checked"/>`);
-13
View File
@@ -1,5 +1,4 @@
import { createBlock, mount, patch, remove } from "../../src/blockdom";
import { logStep } from "../helpers";
import { makeTestFixture } from "./helpers";
//------------------------------------------------------------------------------
@@ -56,15 +55,3 @@ test("is in dom when callback is called", async () => {
mount(tree, fixture);
});
test("callback ref in callback ref with same block", async () => {
const block = createBlock('<p block-ref="0"><block-text-1/><block-child-0/></p>');
let refFn = (el: HTMLParagraphElement) => logStep(el.outerHTML);
const child = block([refFn, "child"], []);
const parent = block([refFn, "parent"], [child]);
mount(parent, fixture);
expect(fixture.innerHTML).toBe("<p>parent<p>child</p></p>");
expect(["<p>child</p>", "<p>parent<p>child</p></p>"]).toBeLogged();
});
@@ -353,7 +353,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-f
let key1 = ctx['project'];
const v1 = ctx['onEdit'];
const v2 = ctx['project'];
let hdlr1 = [\\"prevent\\", _ev=>v1(v2.id,_ev), ctx];
let hdlr1 = [\\"prevent\\", ev=>v1(v2.id,ev), ctx];
let txt1 = ctx['project'].name;
c_block2[i1] = withKey(block3([hdlr1, txt1]), key1);
}
@@ -30,11 +30,11 @@ exports[`misc complex template 1`] = `
b3 = block3();
}
ctx = Object.create(ctx);
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['batch'].slot_ids.filter(_slot=>_slot.build_id.id&&!_slot.trigger_id.manual&&(ctx['options'].trigger_display[_slot.trigger_id.id])));
const [k_block4, v_block4, l_block4, c_block4] = 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 < l_block4; i1++) {
ctx[\`slot\`] = v_block4[i1];
let key1 = ctx['slot'].id;
c_block4[i1] = withKey(component(\`SlotButton\`, {class: ctx['slot_container'], slot: ctx['slot']}, key + \`__1__\${key1}\`, node, ctx), key1);
c_block4[i1] = withKey(component(\`SlotButton\`, {class: ctx['slot_container'],slot: ctx['slot']}, key + \`__1__\${key1}\`, node, ctx), key1);
}
ctx = ctx.__proto__;
b4 = list(c_block4);
@@ -316,8 +316,8 @@ exports[`misc other complex template 1`] = `
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 + \`__2\`, node, ctx);
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev, search: ctx['search']}, key + \`__3\`, node, ctx);
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__2\`, node, ctx);
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__3\`, node, ctx);
b25 = block25([], [b26, b27]);
}
return block1([attr1, txt1, hdlr2, hdlr3, attr8, hdlr4, hdlr5, ref1, hdlr6, ref2], [b2, b4, b14, b17, b22, b23, b24, b25]);
+4 -39
View File
@@ -52,47 +52,12 @@ exports[`properly support svg namespace to svg tags added even if already in svg
}"
`;
exports[`properly support svg svg creates new block if it is within html -- 2 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/><block-child-0/></svg>\`);
let block3 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let b3;
if (ctx['hasPath']) {
b3 = block3();
}
let b2 = block2([], [b3]);
return block1([], [b2]);
}
}"
`;
exports[`properly support svg svg creates new block if it is within html 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/></svg>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = block2();
return block1([], [b2]);
}
}"
`;
exports[`properly support svg svg namespace added to sub templates if root tag is path 1`] = `
exports[`properly support svg svg namespace added to sub-blocks (t-call) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`path\`);
const callTemplate_1 = getTemplate(\`path\`, \\"http://www.w3.org/2000/svg\\");
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
@@ -103,12 +68,12 @@ exports[`properly support svg svg namespace added to sub templates if root tag i
}"
`;
exports[`properly support svg svg namespace added to sub templates if root tag is path 2`] = `
exports[`properly support svg svg namespace added to sub-blocks (t-call) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
let block1 = createBlock(\`<path/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
@@ -407,36 +407,6 @@ exports[`t-out variable 1`] = `
}"
`;
exports[`t-out with a String class 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = safeOutput(ctx['var']);
return block1([], [b2]);
}
}"
`;
exports[`t-out with an extended String class 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = safeOutput(ctx['var']);
return block1([], [b2]);
}
}"
`;
exports[`t-raw is deprecated should warn 1`] = `
"function anonymous(bdom, helpers
) {
@@ -1,19 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`loading templates addTemplates does not modify its xml document in place 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['value'];
return block1([txt1]);
}
}"
`;
exports[`loading templates can initialize qweb with a string 1`] = `
"function anonymous(bdom, helpers
) {
@@ -1,11 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`translation support can set and remove translatable attributes 1`] = `
exports[`translation support can set translatable attributes 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div tomato=\\"word\\" potato=\\"mot\\" title=\\"mot\\" label=\\"word\\">text</div>\`);
let block1 = createBlock(\`<div tomato=\\"word\\" potato=\\"mot\\" title=\\"word\\">text</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
-13
View File
@@ -1,13 +0,0 @@
import { renderToString } from "../helpers";
describe("blacklisted tags and attributes", () => {
test("template with block-text tag", () => {
const template = `<div><block-text-0/>hello</div>`;
expect(() => renderToString(template)).toThrow("Invalid tag name: 'block-text-0'");
});
test("template with block-handler tag", () => {
const template = `<div block-handler-0="click">hello</div>`;
expect(() => renderToString(template)).toThrow("Invalid attribute: 'block-handler-0'");
});
});
+6 -10
View File
@@ -163,21 +163,17 @@ describe("expression evaluation", () => {
});
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(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)"
);
expect(compileExpr("(ev => ev)(e)")).toBe("(_ev=>_ev)(ctx['e'])");
expect(compileExpr("(v1) => myFunc(v1)")).toBe("(_v1)=>ctx['myFunc'](_v1)");
expect(compileExpr("list.data.map((data) => data)")).toBe(
"ctx['list'].data.map((_data)=>_data)"
"ctx['list'].map((elem,index)=>elem+index)"
);
expect(compileExpr("(ev => ev)(e)")).toBe("(ev=>ev)(ctx['e'])");
});
test.skip("arrow functions: not yet supported", () => {
// e is added to localvars in inline_expression but not removed after the arrow func body
expect(compileExpr("(e => e)(e)")).toBe("(_e=>_e)(ctx['e'])");
expect(compileExpr("(e => e)(e)")).toBe("(e=>e)(ctx['e'])");
});
test("assignation", () => {
-75
View File
@@ -1132,81 +1132,6 @@ describe("qweb parser", () => {
});
});
// ---------------------------------------------------------------------------
// t-model
// ---------------------------------------------------------------------------
test("t-model select", async () => {
expect(parse(`<select t-model="state.model"><option value="1" /></select>`)).toEqual({
type: 2,
tag: "select",
dynamicTag: null,
attrs: {},
on: {},
ref: null,
content: [
{
type: 2,
tag: "option",
dynamicTag: null,
attrs: { value: "1" },
on: {},
ref: null,
content: [],
model: null,
ns: null,
},
],
model: {
baseExpr: "state",
expr: "'model'",
targetAttr: "value",
specialInitTargetAttr: null,
eventType: "change",
shouldTrim: false,
shouldNumberize: false,
},
ns: null,
});
});
test("t-model select dynamic options", async () => {
expect(
parse(`<select t-model="state.model"><option t-att-value="valueVar" /></select>`)
).toEqual({
type: 2,
tag: "select",
dynamicTag: null,
attrs: {},
on: {},
ref: null,
content: [
{
type: 2,
tag: "option",
dynamicTag: null,
attrs: { "t-att-value": "valueVar" },
on: {},
ref: null,
content: [],
model: null,
ns: null,
},
],
model: {
baseExpr: "state",
expr: "'model'",
targetAttr: "value",
specialInitTargetAttr: null,
eventType: "change",
shouldTrim: false,
shouldNumberize: false,
hasDynamicChildren: true,
},
ns: null,
});
});
// ---------------------------------------------------------------------------
// t-component
// ---------------------------------------------------------------------------
+4 -44
View File
@@ -1,6 +1,6 @@
import { renderToString, renderToBdom, snapshotEverything, makeTestFixture } from "../helpers";
import { mount } from "../../src/blockdom";
import { mount as mountComponent, Component, xml } from "../../src/index";
import { mount as mountComponent, Component } from "../../src/index"
// NB: check the snapshots to see where the SVG namespaces are added
snapshotEverything();
@@ -55,63 +55,23 @@ describe("properly support svg", () => {
}
});
test("svg namespace added to sub templates if root tag is path", async () => {
test.only("svg namespace added to sub-blocks (t-call)", async () => {
const templates = `<t>
<t t-name="svg"><svg><t t-call="path" /></svg></t>
<t t-name="path"><path /></t>
</t>
`;
`
const fixture = makeTestFixture();
class Svg extends Component {
static template = "svg";
}
await mountComponent(Svg, fixture, { templates });
await mountComponent(Svg, fixture, {templates})
const elems = fixture.querySelectorAll("svg, path");
expect(elems.length).toEqual(2);
for (const el of elems) {
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
}
});
test("svg creates new block if it is within html", async () => {
class Test extends Component {
static template = xml`
<div>
<svg>
<polygon fill="#000000" points="0 0 4 4 8 0" transform="translate(5 7)"/>
</svg>
</div>
`;
}
const fixture = makeTestFixture();
await mountComponent(Test, fixture);
const elems = fixture.querySelectorAll("svg, polygon");
expect(elems.length).toEqual(2);
for (const el of elems) {
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
}
});
test("svg creates new block if it is within html -- 2", async () => {
class Test extends Component {
static template = xml`
<div>
<svg>
<polygon fill="#000000" points="0 0 4 4 8 0" transform="translate(5 7)"/>
<path t-if="hasPath" />
</svg>
</div>
`;
hasPath = true;
}
const fixture = makeTestFixture();
await mountComponent(Test, fixture);
const elems = fixture.querySelectorAll("svg, polygon, path");
expect(elems.length).toEqual(3);
for (const el of elems) {
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
}
});
});
-20
View File
@@ -37,26 +37,6 @@ describe("t-out", () => {
expect(renderToString(template, { var: "ok" })).toBe("<span>ok</span>");
});
test("with a String class", () => {
const template = `<span><t t-out="var"/></span>`;
expect(renderToString(template, { var: new String("ok") })).toBe("<span>ok</span>");
});
test("with an extended String class", () => {
class LoveString extends String {
valueOf(): string {
return `<3 ${super.valueOf()} <3`;
}
toString(): string {
return this.valueOf();
}
}
const template = `<span><t t-out="var"/></span>`;
expect(renderToString(template, { var: new LoveString("ok") })).toBe(
"<span>&lt;3 ok &lt;3</span>"
);
});
test("not escaping", () => {
const template = `<div><t t-out="var"/></div>`;
expect(renderToString(template, { var: markup("<ok></ok>") })).toBe("<div><ok></ok></div>");
-11
View File
@@ -24,17 +24,6 @@ describe("loading templates", () => {
expect(context.renderToString("hey")).toBe("<div>jupiler</div>");
});
test("addTemplates does not modify its xml document in place", () => {
const data = `<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve"><div t-name="hey"><t t-esc="value"/></div></templates>`;
const xml = new DOMParser().parseFromString(data, "text/xml");
const context = new TestContext();
expect(xml.firstElementChild!.innerHTML).toBe(`<div t-name="hey"><t t-esc="value"/></div>`);
context.addTemplates(xml);
expect(context.renderToString("hey", { value: 123 })).toBe("<div>123</div>");
expect(xml.firstElementChild!.innerHTML).toBe(`<div t-name="hey"><t t-esc="value"/></div>`);
});
test("can load a few templates from a xml string", () => {
const data = `<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
+4 -6
View File
@@ -59,20 +59,18 @@ describe("translation support", () => {
);
});
test("can set and remove translatable attributes", async () => {
test("can set translatable attributes", async () => {
class SomeComponent extends Component {
static template = xml`
<div tomato="word" potato="word" title="word" label="word">text</div>
<div tomato="word" potato="word" title="word">text</div>
`;
}
await mount(SomeComponent, fixture, {
translateFn: (expr: string) => (expr === "word" ? "mot" : expr),
translatableAttributes: ["potato", "-label"],
translatableAttributes: ["potato"],
});
expect(fixture.innerHTML).toBe(
'<div tomato="word" potato="mot" title="mot" label="word">text</div>'
);
expect(fixture.innerHTML).toBe('<div tomato="word" potato="mot" title="word">text</div>');
});
test("translation is done on the trimmed text, with extra spaces readded after", async () => {
@@ -81,19 +81,6 @@ exports[`basics a class component inside a class component, no external dom 2`]
}"
`;
exports[`basics a component cannot be mounted in a detached node (even if node is detached later) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`basics a component inside a component 1`] = `
"function anonymous(bdom, helpers
) {
@@ -981,7 +968,7 @@ exports[`basics update props of component without concrete own node 1`] = `
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['childProps'].key;
let b2 = toggler(tKey_1, component(\`Child\`, Object.assign({}, ctx['childProps']), tKey_1 + key + \`__1\`, node, ctx));
let b2 = toggler(tKey_1, component(\`Child\`, ctx['childProps'], tKey_1 + key + \`__1\`, node, ctx));
return block1([], [b2]);
}
}"
@@ -994,7 +981,7 @@ exports[`basics update props of component without concrete own node 2`] = `
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['props'].subKey;
return toggler(tKey_1, component(\`Custom\`, {key: ctx['props'].key, subKey: ctx['props'].subKey}, tKey_1 + key + \`__1\`, node, ctx));
return toggler(tKey_1, component(\`Custom\`, {key: ctx['props'].key,subKey: ctx['props'].subKey}, tKey_1 + key + \`__1\`, node, ctx));
}
}"
`;
@@ -1230,20 +1217,6 @@ exports[`t-out in components can render list of t-out 1`] = `
}"
`;
exports[`t-out in components can switch the contents of two t-out repeatedly 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
let b2 = safeOutput(ctx['state'].a);
let b3 = safeOutput(ctx['state'].b);
return multi([b2, b3]);
}
}"
`;
exports[`t-out in components update properly on state changes 1`] = `
"function anonymous(bdom, helpers
) {
@@ -183,7 +183,7 @@ exports[`concurrent renderings scenario 1 2`] = `
let block1 = createBlock(\`<p><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -227,7 +227,7 @@ exports[`concurrent renderings scenario 2 2`] = `
let block1 = createBlock(\`<p><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -270,7 +270,7 @@ exports[`concurrent renderings scenario 2bis 2`] = `
let block1 = createBlock(\`<p><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA, fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
let b2 = component(\`ComponentC\`, {fromA: ctx['props'].fromA,fromB: ctx['state'].fromB}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -327,7 +327,7 @@ exports[`concurrent renderings scenario 3 3`] = `
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -384,7 +384,7 @@ exports[`concurrent renderings scenario 4 3`] = `
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -557,7 +557,7 @@ exports[`concurrent renderings scenario 9 3`] = `
let block1 = createBlock(\`<p><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA, fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
let b2 = component(\`ComponentD\`, {fromA: ctx['props'].fromA,fromC: ctx['state'].fromC}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -734,7 +734,7 @@ exports[`concurrent renderings scenario 14 2`] = `
let block1 = createBlock(\`<p><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
let b2 = component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -778,7 +778,7 @@ exports[`concurrent renderings scenario 15 2`] = `
let block1 = createBlock(\`<p><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
let b2 = component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -817,7 +817,7 @@ exports[`concurrent renderings scenario 16 2`] = `
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`C\`, {fromB: ctx['state'].fromB, fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
return component(\`C\`, {fromB: ctx['state'].fromB,fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
}
}"
`;
@@ -1,18 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`basics display a nice error if it cannot find component (in dev mode) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SomeMispelledComponent\`, props1, ctx);
return component(\`SomeMispelledComponent\`, props1, key + \`__1\`, node, ctx);
}
}"
`;
exports[`basics display a nice error if it cannot find component 1`] = `
"function anonymous(bdom, helpers
) {
@@ -85,17 +72,6 @@ exports[`basics simple catchError 2`] = `
}"
`;
exports[`can catch errors calling a hook outside setup should crash 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['state'].value);
}
}"
`;
exports[`can catch errors can catch an error in a component render function 1`] = `
"function anonymous(bdom, helpers
) {
@@ -823,7 +799,7 @@ exports[`can catch errors catching in child makes parent render 1`] = `
let key1 = ctx['elem'][0];
const v1 = ctx['elem'];
const ctx1 = capture(ctx);
c_block1[i1] = withKey(component(\`Catch\`, {onError: (_error)=>this.onError(v1[0],_error),slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__2__\${key1}\`, node, ctx), key1);
c_block1[i1] = withKey(component(\`Catch\`, {onError: (error)=>this.onError(v1[0],error),slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__2__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
}
@@ -1092,19 +1068,6 @@ exports[`errors and promises an error in mounted call will reject the mount prom
}"
`;
exports[`errors and promises an error in onMounted callback will have the component's setup in its stack trace 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div>abc</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`errors and promises an error in patched call will reject the render promise 1`] = `
"function anonymous(bdom, helpers
) {
@@ -105,7 +105,7 @@ exports[`event handling objects from scope are properly captured by t-on 1`] = `
let key1 = ctx['item'];
const v1 = ctx['onClick'];
const v2 = ctx['item'];
let hdlr1 = [_ev=>v1(v2.val,_ev), ctx];
let hdlr1 = [ev=>v1(v2.val,ev), ctx];
c_block2[i1] = withKey(block3([hdlr1]), key1);
}
let b2 = list(c_block2);
@@ -146,7 +146,7 @@ exports[`event handling t-on with handler bound to dynamic argument on a t-forea
let key1 = ctx['item'];
const v1 = ctx['onClick'];
const v2 = ctx['item'];
let hdlr1 = [_ev=>v1(v2,_ev), ctx];
let hdlr1 = [ev=>v1(v2,ev), ctx];
c_block2[i1] = withKey(block3([hdlr1]), key1);
}
let b2 = list(c_block2);
@@ -103,7 +103,7 @@ exports[`hooks mounted callbacks should be called in reverse order from willUnmo
}"
`;
exports[`hooks parent and child env (with useChildSubEnv then useSubEnv) 1`] = `
exports[`hooks parent and child env 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -116,65 +116,7 @@ exports[`hooks parent and child env (with useChildSubEnv then useSubEnv) 1`] = `
}"
`;
exports[`hooks parent and child env (with useChildSubEnv then useSubEnv) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block2 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['env'].hasParent) {
let txt1 = ctx['env'].val;
b2 = block2([txt1]);
}
return multi([b2]);
}
}"
`;
exports[`hooks parent and child env (with useChildSubEnv) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['env'].val);
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return multi([b2, b3]);
}
}"
`;
exports[`hooks parent and child env (with useChildSubEnv) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['env'].val;
return block1([txt1]);
}
}"
`;
exports[`hooks parent and child env (with useSubEnv) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['env'].val);
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return multi([b2, b3]);
}
}"
`;
exports[`hooks parent and child env (with useSubEnv) 2`] = `
exports[`hooks parent and child env 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -202,7 +144,7 @@ exports[`hooks two different call to willPatch/patched should work 1`] = `
}"
`;
exports[`hooks useChildSubEnv does not pollute user env 1`] = `
exports[`hooks use sub env does not pollute user env 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -216,7 +158,7 @@ exports[`hooks useChildSubEnv does not pollute user env 1`] = `
}"
`;
exports[`hooks useChildSubEnv supports arbitrary descriptor 1`] = `
exports[`hooks use sub env supports arbitrary descriptor 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -227,7 +169,7 @@ exports[`hooks useChildSubEnv supports arbitrary descriptor 1`] = `
}"
`;
exports[`hooks useChildSubEnv supports arbitrary descriptor 2`] = `
exports[`hooks use sub env supports arbitrary descriptor 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -345,43 +287,3 @@ exports[`hooks useRef hook: basic use 1`] = `
}
}"
`;
exports[`hooks useSubEnv modifies user env 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['env'].val;
return block1([txt1]);
}
}"
`;
exports[`hooks useSubEnv supports arbitrary descriptor 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`hooks useSubEnv supports arbitrary descriptor 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/> <block-text-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['env'].someVal;
let txt2 = ctx['env'].someVal2;
return block1([txt1, txt2]);
}
}"
`;
@@ -160,34 +160,6 @@ exports[`lifecycle hooks components are unmounted destroyed if no longer in DOM
}"
`;
exports[`lifecycle hooks destroy new children before being mountged 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2,b3,b4;
b2 = text(\`before\`);
if (ctx['state'].flag) {
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
b4 = text(\`after\`);
return multi([b2, b3, b4]);
}
}"
`;
exports[`lifecycle hooks destroy new children before being mountged 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
exports[`lifecycle hooks hooks are called in proper order in widget creation/destruction 1`] = `
"function anonymous(bdom, helpers
) {
@@ -42,7 +42,7 @@ exports[`basics arrow functions as prop correctly capture their scope 1`] = `
let key1 = ctx['item'].val;
const v1 = ctx['onClick'];
const v2 = ctx['item'];
c_block1[i1] = withKey(component(\`Child\`, {onClick: _ev=>v1(v2.val,_ev)}, key + \`__1__\${key1}\`, node, ctx), key1);
c_block1[i1] = withKey(component(\`Child\`, {onClick: ev=>v1(v2.val,ev)}, key + \`__1__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
}
@@ -273,7 +273,7 @@ exports[`bound functions is referentially equal after update 1`] = `
let { bind } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {val: ctx['state'].val, fn: bind(ctx, ctx['someFunction'])}, key + \`__1\`, node, ctx);
return component(\`Child\`, {val: ctx['state'].val,fn: bind(ctx, ctx['someFunction'])}, key + \`__1\`, node, ctx);
}
}"
`;
@@ -1,19 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`default props a default prop cannot be defined on a mandatory prop 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx);
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
}
}"
`;
exports[`default props can set default boolean values 1`] = `
exports[`default props can set default required boolean values 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -21,15 +8,15 @@ exports[`default props can set default boolean values 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
`;
exports[`default props can set default boolean values 2`] = `
exports[`default props can set default required boolean values 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -57,8 +44,8 @@ exports[`default props can set default values 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -87,8 +74,8 @@ exports[`default props default values are also set whenever component is updated
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -115,8 +102,8 @@ exports[`props validation can specify that additional props are allowed (array)
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 'm', otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, ctx);
const props1 = {message: 'm',otherProp: 'o'}
helpers.validateProps(\`Child\`, props1, ctx)
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
}
}"
@@ -141,8 +128,8 @@ exports[`props validation can specify that additional props are allowed (object)
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 'm', otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, ctx);
const props1 = {message: 'm',otherProp: 'o'}
helpers.validateProps(\`Child\`, props1, ctx)
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
}
}"
@@ -169,8 +156,8 @@ exports[`props validation can validate a prop with multiple types 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -198,8 +185,8 @@ exports[`props validation can validate a prop with multiple types 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -227,8 +214,8 @@ exports[`props validation can validate a prop with multiple types 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -243,8 +230,8 @@ exports[`props validation can validate an array with given primitive type 1`] =
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -272,8 +259,8 @@ exports[`props validation can validate an array with given primitive type 3`] =
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -301,8 +288,8 @@ exports[`props validation can validate an array with given primitive type 5`] =
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -317,8 +304,8 @@ exports[`props validation can validate an array with given primitive type 6`] =
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -333,8 +320,8 @@ exports[`props validation can validate an array with multiple sub element types
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -362,8 +349,8 @@ exports[`props validation can validate an array with multiple sub element types
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -391,8 +378,8 @@ exports[`props validation can validate an array with multiple sub element types
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -420,8 +407,8 @@ exports[`props validation can validate an array with multiple sub element types
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -436,8 +423,8 @@ exports[`props validation can validate an object with simple shape 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -465,8 +452,8 @@ exports[`props validation can validate an object with simple shape 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -481,8 +468,8 @@ exports[`props validation can validate an object with simple shape 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -497,8 +484,8 @@ exports[`props validation can validate an object with simple shape 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -513,8 +500,8 @@ exports[`props validation can validate an optional props 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -542,8 +529,8 @@ exports[`props validation can validate an optional props 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -571,8 +558,8 @@ exports[`props validation can validate an optional props 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -587,8 +574,8 @@ exports[`props validation can validate recursively complicated prop def 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -616,8 +603,8 @@ exports[`props validation can validate recursively complicated prop def 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -645,8 +632,8 @@ exports[`props validation can validate recursively complicated prop def 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -661,8 +648,8 @@ exports[`props validation default values are applied before validating props at
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -691,8 +678,8 @@ exports[`props validation missing required boolean prop causes an error 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -707,8 +694,8 @@ exports[`props validation mix of optional and mandatory 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx);
const props1 = {}
helpers.validateProps(\`Child\`, props1, ctx)
let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -723,8 +710,8 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 1};
helpers.validateProps(\`Child\`, props1, ctx);
const props1 = {message: 1}
helpers.validateProps(\`Child\`, props1, ctx)
let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -753,8 +740,8 @@ exports[`props validation props are validated whenever component is updated 1`]
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -783,8 +770,8 @@ exports[`props validation props: list of strings 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -799,8 +786,8 @@ exports[`props validation validate simple types 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -815,8 +802,8 @@ exports[`props validation validate simple types 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -844,8 +831,8 @@ exports[`props validation validate simple types 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -860,8 +847,8 @@ exports[`props validation validate simple types 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -876,8 +863,8 @@ exports[`props validation validate simple types 6`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -905,8 +892,8 @@ exports[`props validation validate simple types 8`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -921,8 +908,8 @@ exports[`props validation validate simple types 9`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -937,8 +924,8 @@ exports[`props validation validate simple types 10`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -966,8 +953,8 @@ exports[`props validation validate simple types 12`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -982,8 +969,8 @@ exports[`props validation validate simple types 13`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -998,8 +985,8 @@ exports[`props validation validate simple types 14`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1027,8 +1014,8 @@ exports[`props validation validate simple types 16`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1043,8 +1030,8 @@ exports[`props validation validate simple types 17`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1059,8 +1046,8 @@ exports[`props validation validate simple types 18`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1088,8 +1075,8 @@ exports[`props validation validate simple types 20`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1104,8 +1091,8 @@ exports[`props validation validate simple types 21`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1120,8 +1107,8 @@ exports[`props validation validate simple types 22`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1149,8 +1136,8 @@ exports[`props validation validate simple types 24`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1165,8 +1152,8 @@ exports[`props validation validate simple types, alternate form 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1181,8 +1168,8 @@ exports[`props validation validate simple types, alternate form 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1210,8 +1197,8 @@ exports[`props validation validate simple types, alternate form 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1226,8 +1213,8 @@ exports[`props validation validate simple types, alternate form 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1242,8 +1229,8 @@ exports[`props validation validate simple types, alternate form 6`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1271,8 +1258,8 @@ exports[`props validation validate simple types, alternate form 8`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1287,8 +1274,8 @@ exports[`props validation validate simple types, alternate form 9`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1303,8 +1290,8 @@ exports[`props validation validate simple types, alternate form 10`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1332,8 +1319,8 @@ exports[`props validation validate simple types, alternate form 12`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1348,8 +1335,8 @@ exports[`props validation validate simple types, alternate form 13`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1364,8 +1351,8 @@ exports[`props validation validate simple types, alternate form 14`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1393,8 +1380,8 @@ exports[`props validation validate simple types, alternate form 16`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1409,8 +1396,8 @@ exports[`props validation validate simple types, alternate form 17`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1425,8 +1412,8 @@ exports[`props validation validate simple types, alternate form 18`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1454,8 +1441,8 @@ exports[`props validation validate simple types, alternate form 20`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1470,8 +1457,8 @@ exports[`props validation validate simple types, alternate form 21`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1486,8 +1473,8 @@ exports[`props validation validate simple types, alternate form 22`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1515,8 +1502,8 @@ exports[`props validation validate simple types, alternate form 24`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1531,8 +1518,8 @@ exports[`props validation validation is only done in dev mode 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -38,26 +38,6 @@ exports[`refs can use 2 refs with same name in a t-if/t-else situation 1`] = `
}"
`;
exports[`refs refs and recursive templates 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<p block-ref=\\"0\\"><block-text-1/><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs;
const ref1 = (el) => refs[\`root\`] = el;
let b2;
let txt1 = ctx['props'].tree.value;
if (ctx['props'].tree.child) {
b2 = component(\`Test\`, {tree: ctx['props'].tree.child}, key + \`__1\`, node, ctx);
}
return block1([ref1, txt1], [b2]);
}
}"
`;
exports[`refs refs are properly bound in slots 1`] = `
"function anonymous(bdom, helpers
) {
+18 -139
View File
@@ -75,37 +75,6 @@ exports[`slots can define and call slots 2`] = `
}"
`;
exports[`slots can define and call slots with bound params 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { capture, bind } = helpers;
function slot1(ctx, node, key = \\"\\") {
return text(\`abc\`);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return component(\`Child\`, {slots: {'abc': {__render: slot1, __ctx: ctx1, getValue: bind(ctx, ctx['getValue'])}}}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`slots can define and call slots with bound params 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callSlot } = helpers;
return function template(ctx, node, key = \\"\\") {
let b2 = callSlot(ctx, node, key, 'abc', false, {});
let b3 = text(ctx['props'].slots['abc'].getValue());
return multi([b2, b3]);
}
}"
`;
exports[`slots can define and call slots with params 1`] = `
"function anonymous(bdom, helpers
) {
@@ -455,69 +424,6 @@ exports[`slots default slot next to named slot, with default content 2`] = `
}"
`;
exports[`slots default slot with params with - in it 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
function slot1(ctx, node, key = \\"\\") {
return text(ctx['slotScope']['some-value']);
}
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`slots default slot with params with - in it 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callSlot } = helpers;
return function template(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'default', false, {'some-value': ctx['state'].value});
}
}"
`;
exports[`slots default slot with slot scope: shorthand syntax 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
function slot1(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['slotScope'].bool) {
b2 = text(\`some text\`);
} else {
b3 = text(\`other text\`);
}
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`slots default slot with slot scope: shorthand syntax 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callSlot } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = callSlot(ctx, node, key, 'default', false, {bool: ctx['state'].bool});
return block1([], [b2]);
}
}"
`;
exports[`slots default slot work with text nodes (variation) 1`] = `
"function anonymous(bdom, helpers
) {
@@ -1233,6 +1139,7 @@ exports[`slots simple default slot with params 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { capture } = helpers;
function slot1(ctx, node, key = \\"\\") {
let b2,b3;
@@ -1245,7 +1152,8 @@ exports[`slots simple default slot with params 1`] = `
}
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
const ctx1 = capture(ctx);
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx1, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
}
}"
`;
@@ -1265,29 +1173,38 @@ exports[`slots simple default slot with params 2`] = `
}"
`;
exports[`slots simple default slot with params and bound function 1`] = `
exports[`slots simple default slot with params 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
function slot1(ctx, node, key = \\"\\") {
return text(ctx['slotScope'].fn());
let b2,b3;
if (ctx['slotScope'].bool) {
b2 = text(\`some text\`);
} else {
b3 = text(\`other text\`);
}
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`slots simple default slot with params and bound function 2`] = `
exports[`slots simple default slot with params 4`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callSlot, bind } = helpers;
let { callSlot } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'default', false, {fn: bind(ctx, ctx['getValue'])});
let b2 = callSlot(ctx, node, key, 'default', false, {bool: ctx['state'].bool});
return block1([], [b2]);
}
}"
`;
@@ -1319,44 +1236,6 @@ exports[`slots simple default slot, variation 2`] = `
}"
`;
exports[`slots simple slot with slot scope 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { capture } = helpers;
function slot1(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['slotScope'].bool) {
b2 = text(\`some text\`);
} else {
b3 = text(\`other text\`);
}
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return component(\`Child\`, {slots: {'slotName': {__render: slot1, __ctx: ctx1, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`slots simple slot with slot scope 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callSlot } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = callSlot(ctx, node, key, 'slotName', false, {bool: ctx['state'].bool});
return block1([], [b2]);
}
}"
`;
exports[`slots slot and (inline) t-call 1`] = `
"function anonymous(bdom, helpers
) {
@@ -147,7 +147,7 @@ exports[`style and class handling class on sub component, which is switched to a
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {class: 'someclass', child: ctx['state'].child}, key + \`__1\`, node, ctx);
return component(\`Child\`, {class: 'someclass',child: ctx['state'].child}, key + \`__1\`, node, ctx);
}
}"
`;
@@ -53,8 +53,8 @@ exports[`list of components crash on duplicate key in dev mode 1`] = `
let key1 = 'child';
if (keys1.has(key1)) { throw new Error(\`Got duplicate key in t-foreach: \${key1}\`)}
keys1.add(key1);
const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx);
const props1 = {}
helpers.validateProps(\`Child\`, props1, ctx)
c_block1[i1] = withKey(component(\`Child\`, props1, key + \`__1__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
@@ -177,7 +177,7 @@ exports[`list of components reconciliation alg works for t-foreach in t-foreach,
for (let i2 = 0; i2 < l_block4; i2++) {
ctx[\`col\`] = v_block4[i2];
let key2 = ctx['col'];
let b6 = component(\`Child\`, {row: ctx['row'], col: ctx['col']}, key + \`__1__\${key1}__\${key2}\`, node, ctx);
let b6 = component(\`Child\`, {row: ctx['row'],col: ctx['col']}, key + \`__1__\${key1}__\${key2}\`, node, ctx);
c_block4[i2] = withKey(block5([], [b6]), key2);
}
ctx = ctx.__proto__;
@@ -10,9 +10,8 @@ exports[`t-model directive .lazy modifier 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]);
}
@@ -29,9 +28,8 @@ exports[`t-model directive .number modifier 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'number';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = toNumber(ev.target.value); }];
let attr1 = ctx['state']['number'];
let hdlr1 = [(ev) => { bExpr1['number'] = toNumber(ev.target.value); }];
let txt1 = ctx['state'].number;
return block1([attr1, hdlr1, txt1]);
}
@@ -48,9 +46,8 @@ exports[`t-model directive .trim modifier 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value.trim(); }];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value.trim(); }];
let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]);
}
@@ -67,9 +64,8 @@ exports[`t-model directive basic use, on an input 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]);
}
@@ -86,9 +82,8 @@ exports[`t-model directive basic use, on an input with bracket expression 1`] =
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]);
}
@@ -105,9 +100,8 @@ exports[`t-model directive basic use, on another key in component 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['some'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['some']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
let txt1 = ctx['some'].text;
return block1([attr1, hdlr1, txt1]);
}
@@ -125,9 +119,8 @@ exports[`t-model directive can also define t-on directive on same event, part 1
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['onInput'], ctx];
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['text'];
let hdlr2 = [(ev) => { bExpr1['text'] = ev.target.value; }];
return block1([hdlr1, attr1, hdlr2]);
}
}"
@@ -144,19 +137,16 @@ exports[`t-model directive can also define t-on directive on same event, part 2
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['onClick'], ctx];
const bExpr1 = ctx['state'];
const expr1 = 'choice';
let attr1 = bExpr1[expr1] === 'One';
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['choice'] === 'One';
let hdlr2 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
let hdlr3 = [ctx['onClick'], ctx];
const bExpr2 = ctx['state'];
const expr2 = 'choice';
let attr2 = bExpr2[expr2] === 'Two';
let hdlr4 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let attr2 = ctx['state']['choice'] === 'Two';
let hdlr4 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
let hdlr5 = [ctx['onClick'], ctx];
const bExpr3 = ctx['state'];
const expr3 = 'choice';
let attr3 = bExpr3[expr3] === 'Three';
let hdlr6 = [(ev) => { bExpr3[expr3] = ev.target.value; }];
let attr3 = ctx['state']['choice'] === 'Three';
let hdlr6 = [(ev) => { bExpr3['choice'] = ev.target.value; }];
return block1([hdlr1, attr1, hdlr2, hdlr3, attr2, hdlr4, hdlr5, attr3, hdlr6]);
}
}"
@@ -175,9 +165,8 @@ exports[`t-model directive following a scope protecting directive (e.g. t-set) 1
ctx[isBoundary] = 1
setContextValue(ctx, \\"admiral\\", 'Bruno');
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
return block1([attr1, hdlr1]);
}
}"
@@ -199,9 +188,8 @@ exports[`t-model directive in a t-foreach 1`] = `
ctx[\`thing\`] = v_block2[i1];
let key1 = ctx['thing'].id;
const bExpr1 = ctx['thing'];
const expr1 = 'f';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.checked; }];
let attr1 = ctx['thing']['f'];
let hdlr1 = [(ev) => { bExpr1['f'] = ev.target.checked; }];
c_block2[i1] = withKey(block3([attr1, hdlr1]), key1);
}
let b2 = list(c_block2);
@@ -227,37 +215,8 @@ exports[`t-model directive in a t-foreach, part 2 1`] = `
ctx[\`thing_index\`] = i1;
let key1 = ctx['thing_index'];
const bExpr1 = ctx['state'];
const expr1 = ctx['thing_index'];
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
c_block2[i1] = withKey(block3([attr1, hdlr1]), key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
}
}"
`;
exports[`t-model directive in a t-foreach, part 3 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { prepareList, toNumber, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<input block-attribute-0=\\"value\\" block-handler-1=\\"input\\"/>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['names']);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`name\`] = v_block2[i1];
ctx[\`name_index\`] = i1;
let key1 = ctx['name_index'];
const bExpr1 = ctx['state'].values;
const expr1 = ctx['name'];
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state'][ctx['thing_index']];
let hdlr1 = [(ev) => { bExpr1[ctx['thing_index']] = ev.target.value; }];
c_block2[i1] = withKey(block3([attr1, hdlr1]), key1);
}
let b2 = list(c_block2);
@@ -276,9 +235,8 @@ exports[`t-model directive on a select 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'color';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['color'];
let hdlr1 = [(ev) => { bExpr1['color'] = ev.target.value; }];
let txt1 = ctx['state'].color;
return block1([attr1, hdlr1, txt1]);
}
@@ -295,9 +253,8 @@ exports[`t-model directive on a select, initial state 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'color';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['color'];
let hdlr1 = [(ev) => { bExpr1['color'] = ev.target.value; }];
return block1([attr1, hdlr1]);
}
}"
@@ -313,9 +270,8 @@ exports[`t-model directive on a sub state key 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'].something;
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state'].something['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
let txt1 = ctx['state'].something.text;
return block1([attr1, hdlr1, txt1]);
}
@@ -332,13 +288,11 @@ exports[`t-model directive on an input type=radio 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'choice';
let attr1 = bExpr1[expr1] === 'One';
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['choice'] === 'One';
let hdlr1 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
const bExpr2 = ctx['state'];
const expr2 = 'choice';
let attr2 = bExpr2[expr2] === 'Two';
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let attr2 = ctx['state']['choice'] === 'Two';
let hdlr2 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
let txt1 = ctx['state'].choice;
return block1([attr1, hdlr1, attr2, hdlr2, txt1]);
}
@@ -355,13 +309,11 @@ exports[`t-model directive on an input type=radio, with initial value 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'choice';
let attr1 = bExpr1[expr1] === 'One';
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['choice'] === 'One';
let hdlr1 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
const bExpr2 = ctx['state'];
const expr2 = 'choice';
let attr2 = bExpr2[expr2] === 'Two';
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let attr2 = ctx['state']['choice'] === 'Two';
let hdlr2 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
return block1([attr1, hdlr1, attr2, hdlr2]);
}
}"
@@ -378,9 +330,8 @@ exports[`t-model directive on an input, type=checkbox 1`] = `
return function template(ctx, node, key = \\"\\") {
let b2,b3;
const bExpr1 = ctx['state'];
const expr1 = 'flag';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.checked; }];
let attr1 = ctx['state']['flag'];
let hdlr1 = [(ev) => { bExpr1['flag'] = ev.target.checked; }];
if (ctx['state'].flag) {
b2 = text(\`yes\`);
} else {
@@ -401,155 +352,14 @@ exports[`t-model directive on an textarea 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]);
}
}"
`;
exports[`t-model directive t-model on an input with an undefined value 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<input block-attribute-0=\\"value\\" block-handler-1=\\"input\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
return block1([attr1, hdlr1]);
}
}"
`;
exports[`t-model directive t-model on select with static options 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><select block-attribute-0=\\"value\\" block-handler-1=\\"change\\"><option value=\\"a\\"><block-text-2/></option><option value=\\"b\\"><block-text-3/></option><option value=\\"c\\"><block-text-4/></option></select></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = 'a';
let txt2 = 'b';
let txt3 = 'c';
return block1([attr1, hdlr1, txt1, txt2, txt3]);
}
}"
`;
exports[`t-model directive t-model with dynamic values on select options -- 2 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option block-attribute-4=\\"value\\" block-attribute-5=\\"selected\\"><block-text-6/></option></select></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let bValue1 = bExpr1[expr1]
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['options'][0];
let attr2 = bValue1 === ctx['options'][0];
let txt1 = ctx['options'][0];
let attr3 = (ctx['options'][1]);
let attr4 = bValue1 === (ctx['options'][1]);
let txt2 = ctx['options'][1];
return block1([hdlr1, attr1, attr2, txt1, attr3, attr4, txt2]);
}
}"
`;
exports[`t-model directive t-model with dynamic values on select options -- 3 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option value=\\"b\\" block-attribute-4=\\"selected\\"><block-text-5/></option></select></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let bValue1 = bExpr1[expr1]
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['options'][0];
let attr2 = bValue1 === ctx['options'][0];
let txt1 = ctx['options'][0];
let attr3 = bValue1 === \\"b\\";
let txt2 = ctx['options'][1];
return block1([hdlr1, attr1, attr2, txt1, attr3, txt2]);
}
}"
`;
exports[`t-model directive t-model with dynamic values on select options 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option block-attribute-4=\\"value\\" block-attribute-5=\\"selected\\"><block-text-6/></option></select></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let bValue1 = bExpr1[expr1]
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['options'][0];
let attr2 = bValue1 === ctx['options'][0];
let txt1 = ctx['options'][0];
let attr3 = ctx['options'][1];
let attr4 = bValue1 === ctx['options'][1];
let txt2 = ctx['options'][1];
return block1([hdlr1, attr1, attr2, txt1, attr3, attr4, txt2]);
}
}"
`;
exports[`t-model directive t-model with dynamic values on select options in foreach 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber, prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><block-child-0/></select></div>\`);
let block3 = createBlock(\`<option block-attribute-0=\\"value\\" block-attribute-1=\\"selected\\"><block-text-2/></option>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let bValue1 = bExpr1[expr1]
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['options']);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`v\`] = v_block2[i1];
let key1 = ctx['v'];
let attr1 = ctx['v'];
let attr2 = bValue1 === ctx['v'];
let txt1 = ctx['v'];
c_block2[i1] = withKey(block3([attr1, attr2, txt1]), key1);
}
let b2 = list(c_block2);
return block1([hdlr1], [b2]);
}
}"
`;
exports[`t-model directive two inputs in a div alternating with a t-if 1`] = `
"function anonymous(bdom, helpers
) {
@@ -564,38 +374,17 @@ exports[`t-model directive two inputs in a div alternating with a t-if 1`] = `
let b2,b3;
if (ctx['state'].flag) {
const bExpr1 = ctx['state'];
const expr1 = 'text1';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['state']['text1'];
let hdlr1 = [(ev) => { bExpr1['text1'] = ev.target.value; }];
b2 = block2([attr1, hdlr1]);
}
if (!ctx['state'].flag) {
const bExpr2 = ctx['state'];
const expr2 = 'text2';
let attr2 = bExpr2[expr2];
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let attr2 = ctx['state']['text2'];
let hdlr2 = [(ev) => { bExpr2['text2'] = ev.target.value; }];
b3 = block3([attr2, hdlr2]);
}
return block1([], [b2, b3]);
}
}"
`;
exports[`t-model directive with expression having a changing key 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><input block-attribute-0=\\"value\\" block-handler-1=\\"input\\"/><span><block-text-2/></span></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'].something;
const expr1 = ctx['text'].key;
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].something[ctx['text'].key];
return block1([attr1, hdlr1, txt1]);
}
}"
`;
@@ -8,7 +8,7 @@ exports[`t-props basic use 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, Object.assign({}, ctx['some'].obj), key + \`__1\`, node, ctx);
let b2 = component(\`Child\`, ctx['some'].obj, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -28,30 +28,6 @@ exports[`t-props basic use 2`] = `
}"
`;
exports[`t-props child receives a copy of the t-props object, not the original 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, Object.assign({}, ctx['childProps']), key + \`__1\`, node, ctx);
}
}"
`;
exports[`t-props child receives a copy of the t-props object, not the original 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`t-props t-props and other props 1`] = `
"function anonymous(bdom, helpers
) {
@@ -89,7 +65,7 @@ exports[`t-props t-props only 1`] = `
let block1 = createBlock(\`<div><div><block-child-0/></div></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Comp\`, Object.assign({}, ctx['state']), key + \`__1\`, node, ctx);
let b2 = component(\`Comp\`, ctx['state'], key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -117,7 +93,7 @@ exports[`t-props t-props with props 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, Object.assign({}, ctx['childProps'], {a: 1, b: 2}), key + \`__1\`, node, ctx);
let b2 = component(\`Child\`, Object.assign({}, ctx['childProps'], {a: 1,b: 2}), key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
+1 -49
View File
@@ -171,27 +171,6 @@ describe("basics", () => {
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
});
test("a component cannot be mounted in a detached node (even if node is detached later)", async () => {
const warn = console.warn;
console.warn = jest.fn();
class Test extends Component {
static template = xml`<div/>`;
}
let error: Error;
const prom = mount(Test, fixture);
await Promise.resolve();
fixture.remove();
try {
await prom;
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
expect(console.warn).toBeCalledTimes(1);
console.warn = warn;
});
test("crashes if it cannot find a template", async () => {
class Test extends Component {
static template = "wrongtemplate";
@@ -204,7 +183,7 @@ describe("basics", () => {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe('Missing template: "wrongtemplate" (for component "Test")');
expect(error!.message).toBe('Missing template: "wrongtemplate"');
});
test("class component with dynamic text", async () => {
@@ -1027,31 +1006,4 @@ describe("t-out in components", () => {
"<div>&lt;b&gt;one&lt;/b&gt;<b>one</b>&lt;b&gt;two&lt;/b&gt;<b>two</b>&lt;b&gt;tree&lt;/b&gt;<b>tree</b></div>"
);
});
test("can switch the contents of two t-out repeatedly", async () => {
class Test extends Component {
static template = xml`
<t t-out="state.a"/>
<t t-out="state.b"/>
`;
state = useState({
a: markup("<div>1</div>"),
b: markup("<div>2</div>"),
});
reverse() {
const { state } = this;
[state.a, state.b] = [state.b, state.a];
}
}
const comp = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("<div>1</div><div>2</div>");
comp.reverse();
await nextTick();
expect(fixture.innerHTML).toBe("<div>2</div><div>1</div>");
comp.reverse();
await nextTick();
expect(fixture.innerHTML).toBe("<div>1</div><div>2</div>");
});
});
+5 -2
View File
@@ -97,14 +97,17 @@ test("destroying/recreating a subwidget with different props (if start is not ov
expect(["W:setup", "W:willStart", "W:willRender", "W:rendered", "W:mounted"]).toBeLogged();
expect(n).toBe(0);
w.state.val = 2;
await new Promise(requestAnimationFrame); // calls to _render are batched per animation frame
await nextMicroTick();
await nextMicroTick();
expect(n).toBe(1);
expect(["W:willRender", "Child:setup", "Child:willStart", "W:rendered"]).toBeLogged();
w.state.val = 3;
await new Promise(requestAnimationFrame);
await nextMicroTick();
await nextMicroTick();
expect(n).toBe(2);
expect([
+7 -73
View File
@@ -81,25 +81,6 @@ describe("basics", () => {
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("display a nice error if it cannot find component (in dev mode)", async () => {
class SomeComponent extends Component {}
class Parent extends Component {
static template = xml`<SomeMispelledComponent />`;
static components = { SomeComponent };
}
let error: Error;
try {
await mount(Parent, fixture, { test: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("simple catchError", async () => {
class Boom extends Component {
static template = xml`<div t-esc="a.b.c"/>`;
@@ -175,30 +156,6 @@ describe("errors and promises", () => {
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
class App extends Component {
static template = xml`<div>abc</div>`;
setup() {
onMounted(() => {
throw new Error("boom");
});
}
}
let error: Error;
try {
await mount(App, fixture, { test: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.stack).toContain("App.setup");
expect(error!.stack).toContain("error_handling.test.ts");
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("an error in willPatch call will reject the render promise", async () => {
class Root extends Component {
static template = xml`<div><t t-esc="val"/></div>`;
@@ -211,13 +168,13 @@ describe("errors and promises", () => {
}
}
const root = await mount(Root, fixture, { test: true });
const root = await mount(Root, fixture);
root.val = 4;
let error: Error;
root.render();
await nextTick();
expect(error!).toBeDefined();
expect(error!.message).toBe(`The following error occurred in onWillPatch: "boom"`);
expect(error!.message).toBe("boom");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
@@ -234,13 +191,13 @@ describe("errors and promises", () => {
}
}
const root = await mount(Root, fixture, { test: true });
const root = await mount(Root, fixture);
root.val = 4;
let error: Error;
root.render();
await nextTick();
expect(error!).toBeDefined();
expect(error!.message).toBe(`The following error occurred in onPatched: "boom"`);
expect(error!.message).toBe("boom");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
@@ -316,6 +273,7 @@ describe("errors and promises", () => {
});
test("errors in mounted and in willUnmount", async () => {
expect.assertions(4);
class Example extends Component {
static template = xml`<div/>`;
val: any;
@@ -332,11 +290,9 @@ describe("errors and promises", () => {
}
try {
await mount(Example, fixture, { test: true });
await mount(Example, fixture);
} catch (e) {
expect((e as Error).message).toBe(
`The following error occurred in onMounted: "Error in mounted"`
);
expect((e as Error).message).toBe("Error in mounted");
}
// 1 additional error is logged because the destruction of the app causes
// the onWillUnmount hook to be called and to fail
@@ -395,28 +351,6 @@ describe("can catch errors", () => {
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("calling a hook outside setup should crash", async () => {
class Root extends Component {
static template = xml`<t t-esc="state.value"/>`;
state = useState({ value: 1 });
setup() {
onWillStart(() => {
this.state = useState({ value: 2 });
});
}
}
let e: Error;
try {
await mount(Root, fixture, { test: true });
} catch (error) {
e = error as Error;
}
expect(e!.message).toBe(
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
);
});
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
class ErrorComponent extends Component {
static template = xml`<div>hey<t t-esc="state.this.will.crash"/></div>`;
+3 -87
View File
@@ -14,7 +14,6 @@ import {
useExternalListener,
useRef,
useState,
useChildSubEnv,
useSubEnv,
xml,
} from "../../src/index";
@@ -181,7 +180,7 @@ describe("hooks", () => {
expect(fixture.innerHTML).toBe("<div>1</div>");
});
test("useSubEnv modifies user env", async () => {
test("use sub env does not pollute user env", async () => {
class Test extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
setup() {
@@ -191,25 +190,11 @@ describe("hooks", () => {
const env = { val: 3 };
const component = await mount(Test, fixture, { env });
expect(fixture.innerHTML).toBe("<div>3</div>");
expect(component.env).toHaveProperty("val2");
expect(component.env).toHaveProperty("val");
});
test("useChildSubEnv does not pollute user env", async () => {
class Test extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
setup() {
useChildSubEnv({ val2: 1 });
}
}
const env = { val: 3 };
const component = await mount(Test, fixture, { env });
expect(fixture.innerHTML).toBe("<div>3</div>");
expect(component.env).not.toHaveProperty("val2");
expect(component.env).toHaveProperty("val");
});
test("useSubEnv supports arbitrary descriptor", async () => {
test("use sub env supports arbitrary descriptor", async () => {
let someVal = "maggot";
let someVal2 = "brain";
@@ -228,40 +213,6 @@ describe("hooks", () => {
});
}
}
const env = {
get someVal() {
return someVal;
},
};
const component = await mount(Test, fixture, { env });
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
someVal = "brain";
someVal2 = "maggot";
component.render();
await nextTick();
expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
});
test("useChildSubEnv supports arbitrary descriptor", async () => {
let someVal = "maggot";
let someVal2 = "brain";
class Child extends Component {
static template = xml`<div><t t-esc="env.someVal" /> <t t-esc="env.someVal2" /></div>`;
}
class Test extends Component {
static template = xml`<Child />`;
static components = { Child };
setup() {
useChildSubEnv({
get someVal2() {
return someVal2;
},
});
}
}
someVal = "maggot";
const env = {
get someVal() {
@@ -288,7 +239,7 @@ describe("hooks", () => {
await mount(Test, fixture);
});
test("parent and child env (with useSubEnv)", async () => {
test("parent and child env", async () => {
class Child extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
}
@@ -302,44 +253,9 @@ describe("hooks", () => {
}
const env = { val: 3 };
await mount(Parent, fixture, { env });
expect(fixture.innerHTML).toBe("5<div>5</div>");
});
test("parent and child env (with useChildSubEnv)", async () => {
class Child extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
}
class Parent extends Component {
static template = xml`<t t-esc="env.val"/><Child/>`;
static components = { Child };
setup() {
useChildSubEnv({ val: 5 });
}
}
const env = { val: 3 };
await mount(Parent, fixture, { env });
expect(fixture.innerHTML).toBe("3<div>5</div>");
});
test("parent and child env (with useChildSubEnv then useSubEnv)", async () => {
class Child extends Component {
static template = xml`<div t-if="env.hasParent"><t t-esc="env.val"/></div>`;
}
class Parent extends Component {
static template = xml`<t t-esc="env.val"/><Child/>`;
static components = { Child };
setup() {
useChildSubEnv({ hasParent: true });
useSubEnv({ val: 5 });
}
}
const env = { val: 3 };
await mount(Parent, fixture, { env });
expect(fixture.innerHTML).toBe("5<div>5</div>");
});
test("can use onWillStart, onWillUpdateProps", async () => {
const steps: string[] = [];
async function slow(): Promise<string> {
-52
View File
@@ -1253,56 +1253,4 @@ describe("lifecycle hooks", () => {
"onWillDestroy",
]).toBeLogged();
});
test("destroy new children before being mountged", async () => {
class Child extends Component {
static template = xml`child`;
setup() {
useLogLifecycle();
}
}
class Parent extends Component {
static template = xml`before<Child t-if="state.flag"/>after`;
static components = { Child };
state = useState({ flag: false });
setup() {
useLogLifecycle();
onRendered(async () => {
// we destroy here the app after the new child component has been
// created, but before this rendering has been patched to the DOM
if (this.state.flag) {
await Promise.resolve();
app.destroy();
}
});
}
}
const app = new App(Parent);
const parent = await app.mount(fixture);
expect(fixture.innerHTML).toBe("beforeafter");
expect([
"Parent:setup",
"Parent:willStart",
"Parent:willRender",
"Parent:rendered",
"Parent:mounted",
]).toBeLogged();
parent.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe("");
expect([
"Parent:willRender",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Parent:willUnmount",
"Child:willDestroy",
"Parent:willDestroy",
]).toBeLogged();
});
});
+4 -28
View File
@@ -12,7 +12,7 @@ let mockConsoleWarn: any;
beforeAll(() => {
console.info = (message: any) => {
if (message === DEV_MSG()) {
if (message === DEV_MSG) {
return;
}
info(message);
@@ -684,7 +684,7 @@ describe("props validation", () => {
test("default values are applied before validating props at update", async () => {
// need to do something about errors catched in render
class SubComp extends Component {
static props = { p: { type: Number, optional: true } };
static props = { p: { type: Number } };
static template = xml`<div><t t-esc="props.p"/></div>`;
static defaultProps = { p: 4 };
}
@@ -791,9 +791,9 @@ describe("default props", () => {
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
});
test("can set default boolean values", async () => {
test("can set default required boolean values", async () => {
class SubComp extends Component {
static props = ["p?", "q?"];
static props = ["p", "q"];
static defaultProps = { p: true, q: false };
static template = xml`<span><t t-if="props.p">hey</t><t t-if="!props.q">hey</t></span>`;
}
@@ -804,28 +804,4 @@ describe("default props", () => {
await mount(Parent, fixture, { dev: true });
expect(fixture.innerHTML).toBe("<div><span>heyhey</span></div>");
});
test("a default prop cannot be defined on a mandatory prop", async () => {
class Child extends Component {
static props = {
mandatory: Number,
};
static defaultProps = { mandatory: 3 };
static template = xml` <div><t t-esc="props.mandatory"/></div>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`<Child/>`;
}
let error: Error;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe(
"A default value cannot be defined for a mandatory prop (name: 'mandatory', component: Child)"
);
});
});
+2 -25
View File
@@ -1,5 +1,5 @@
import { Component, mount, onMounted, useRef, useState } from "../../src/index";
import { logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { Component, mount, useRef, useState } from "../../src/index";
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { xml } from "../../src/index";
snapshotEverything();
@@ -100,27 +100,4 @@ describe("refs", () => {
expect(console.warn).toBeCalledTimes(1);
console.warn = consoleWarn;
});
test("refs and recursive templates", async () => {
class Test extends Component {
static components = {};
static template = xml`
<p t-ref="root">
<t t-esc="props.tree.value"/>
<t t-if="props.tree.child"><Test tree="props.tree.child"/></t>
</p>`;
root = useRef("root");
setup() {
onMounted(() => logStep(this.root.el!.outerHTML));
}
}
Test.components = { Test };
const tree = { value: "a", child: { value: "b", child: null } };
await mount(Test, fixture, { props: { tree } });
expect(fixture.innerHTML).toBe("<p>a<p>b</p></p>");
expect(["<p>b</p>", "<p>a<p>b</p></p>"]).toBeLogged();
});
});
+6 -92
View File
@@ -45,36 +45,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("some text");
});
test("simple slot with slot scope", async () => {
let child: any;
class Child extends Component {
static template = xml`<span><t t-slot="slotName" bool="state.bool"/></span>`;
state = useState({ bool: true });
setup() {
child = this;
}
}
class Parent extends Component {
static template = xml`
<Child>
<t t-set-slot="slotName" t-slot-scope="slotScope">
<t t-if="slotScope.bool">some text</t>
<t t-else="slotScope.bool">other text</t>
</t>
</Child>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<span>some text</span>");
child.state.bool = false;
await nextTick();
expect(fixture.innerHTML).toBe("<span>other text</span>");
});
test("default slot with slot scope: shorthand syntax", async () => {
test("simple default slot with params", async () => {
let child: any;
class Child extends Component {
static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`;
@@ -86,9 +57,11 @@ describe("slots", () => {
class Parent extends Component {
static template = xml`
<Child t-slot-scope="slotScope">
<t t-if="slotScope.bool">some text</t>
<t t-else="slotScope.bool">other text</t>
<Child>
<t t-set-slot="default" t-slot-scope="slotScope">
<t t-if="slotScope.bool">some text</t>
<t t-else="slotScope.bool">other text</t>
</t>
</Child>`;
static components = { Child };
}
@@ -126,41 +99,6 @@ describe("slots", () => {
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("simple default slot with params and bound function", async () => {
class Child extends Component {
static template = xml`<t t-slot="default" fn.bind="getValue"/>`;
state = useState({ value: 123 });
getValue() {
return this.state.value;
}
}
class Parent extends Component {
static template = xml`
<Child t-slot-scope="slotScope"><t t-esc="slotScope.fn()"/></Child>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("123");
});
test("default slot with params with - in it", async () => {
class Child extends Component {
static template = xml`<t t-slot="default" some-value="state.value"/>`;
state = useState({ value: 123 });
}
class Parent extends Component {
static template = xml`
<Child t-slot-scope="slotScope"><t t-esc="slotScope['some-value']"/></Child>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("123");
});
test("fun: two calls to the same slot", async () => {
class Child extends Component {
static template = xml`<t t-slot="default"/><t t-slot="default"/>`;
@@ -277,30 +215,6 @@ describe("slots", () => {
);
});
test("can define and call slots with bound params", async () => {
class Child extends Component {
static template = xml`
<t t-slot="abc"/>
<t t-esc="props.slots['abc'].getValue()"/>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`
<Child>
<t t-set-slot="abc" getValue.bind="getValue">abc</t>
</Child>`;
state = useState({ value: 444 });
getValue() {
return this.state.value;
}
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("abc444");
});
test("no named slot content => just no children", async () => {
class Dialog extends Component {
static template = xml`<span><t t-slot="header"/></span>`;
-173
View File
@@ -30,19 +30,6 @@ describe("t-model directive", () => {
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
});
test("t-model on an input with an undefined value", async () => {
class SomeComponent extends Component {
static template = xml`<input t-model="state.text"/>`;
state = useState({ text: undefined });
}
await mount(SomeComponent, fixture);
expect(fixture.innerHTML).toBe("<input>");
const input = fixture.querySelector("input")!;
expect(input.value).toBe("");
});
test("basic use, on an input with bracket expression", async () => {
class SomeComponent extends Component {
static template = xml`
@@ -259,34 +246,6 @@ describe("t-model directive", () => {
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
});
test("with expression having a changing key", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<input t-model="state.something[text.key]"/>
<span><t t-esc="state.something[text.key]"/></span>
</div>
`;
state: { something: { [key: string]: string } } = useState({ something: {} });
text = useState({ key: "foo" });
}
const comp = await mount(SomeComponent, fixture);
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
let input = fixture.querySelector("input")!;
await editInput(input, "footest");
expect(comp.state.something[comp.text.key]).toBe("footest");
expect(fixture.innerHTML).toBe("<div><input><span>footest</span></div>");
comp.text.key = "bar";
await nextTick();
input = fixture.querySelector("input")!;
await editInput(input, "test bar");
expect(comp.state.something[comp.text.key]).toBe("test bar");
expect(fixture.innerHTML).toBe("<div><input><span>test bar</span></div>");
});
test(".lazy modifier", async () => {
class SomeComponent extends Component {
static template = xml`
@@ -401,26 +360,6 @@ describe("t-model directive", () => {
expect(comp.state).toEqual(["zuko", "uncle iroh"]);
});
test("in a t-foreach, part 3", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<t t-foreach="names" t-as="name" t-key="name_index">
<input t-model="state.values[name]"/>
</t>
</div>
`;
names = ["Crusher", "Data", "Riker", "Worf"];
state = useState({ values: {} });
}
const comp = await mount(SomeComponent, fixture);
expect(comp.state).toEqual({ values: {} });
const input = fixture.querySelectorAll("input")[1]!;
await editInput(input, "Commander");
expect(comp.state).toEqual({ values: { Data: "Commander" } });
});
test("two inputs in a div alternating with a t-if", async () => {
class SomeComponent extends Component {
static template = xml`
@@ -513,116 +452,4 @@ describe("t-model directive", () => {
expect(comp.state.choice).toBe("Three");
expect(comp.state.lastClicked).toBe("Three");
});
test("t-model on select with static options", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<option value="a" t-esc="'a'"/>
<option value="b" t-esc="'b'"/>
<option value="c" t-esc="'c'"/>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b", "c"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
test("t-model with dynamic values on select options", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<option t-att-value="options[0]" t-esc="options[0]"/>
<option t-att-value="options[1]" t-esc="options[1]"/>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
test("t-model with dynamic values on select options -- 2", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<option t-att-value="options[0]" t-esc="options[0]"/>
<option t-attf-value="{{ options[1] }}" t-esc="options[1]"/>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
test("t-model with dynamic values on select options -- 3", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<option t-att-value="options[0]" t-esc="options[0]"/>
<option value="b" t-esc="options[1]"/>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
test("t-model with dynamic values on select options in foreach", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<t t-foreach="options" t-as="v" t-key="v">
<option t-att-value="v" t-esc="v"/>
</t>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b", "c"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
});
+2 -20
View File
@@ -53,7 +53,7 @@ describe("t-props", () => {
});
test("basic use", async () => {
expect.assertions(4);
expect.assertions(5);
let props = { a: 1, b: 2 };
@@ -65,6 +65,7 @@ describe("t-props", () => {
`;
setup() {
expect(this.props).toEqual({ a: 1, b: 2 });
expect(this.props).toBe(props);
}
}
class Parent extends Component {
@@ -104,23 +105,4 @@ describe("t-props", () => {
await mount(Parent, fixture);
});
test("child receives a copy of the t-props object, not the original", async () => {
class Child extends Component {
static template = xml`<div/>`;
setup() {
expect(this.props).toEqual({ a: 1, b: 2 });
this.props.d = 5;
}
}
class Parent extends Component {
static template = xml`<Child t-props="childProps"/>`;
static components = { Child };
childProps = { a: 1, b: 2 };
}
const parent = await mount(Parent, fixture);
expect(parent.childProps).not.toHaveProperty("d");
});
});
+1 -1
View File
@@ -123,7 +123,7 @@ export function snapshotEverything() {
});
const originalCompileTemplate = TemplateSet.prototype._compileTemplate;
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Element) {
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Node) {
const fn = originalCompileTemplate.call(this, "", template);
if (!globalTemplateNames.has(name)) {
expect(fn.toString()).toMatchSnapshot();
+1 -1
View File
@@ -16,7 +16,7 @@ exports[`Memo if no prop change, prevent renderings from above 1`] = `
let b2 = text(ctx['state'].a);
let b3 = text(ctx['state'].b);
let b4 = text(ctx['state'].c);
let b9 = component(\`Memo\`, {a: ctx['state'].a, b: ctx['state'].b,slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
let b9 = component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b,slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return multi([b2, b3, b4, b9]);
}
}"
+1 -1
View File
@@ -29,7 +29,7 @@ snapshotEverything();
beforeAll(() => {
console.info = (message: any) => {
if (message === DEV_MSG()) {
if (message === DEV_MSG) {
return;
}
info(message);
+1 -46
View File
@@ -6,10 +6,8 @@ import {
onWillUpdateProps,
useState,
xml,
markRaw,
toRaw,
} from "../src";
import { reactive, Reactive } from "../src/reactivity";
import { reactive } from "../src/reactivity";
import { batched } from "../src/utils";
import {
makeDeferred,
@@ -1093,49 +1091,6 @@ describe("Reactivity", () => {
expect(n).toBe(1);
expect(state.k).toEqual({ n: 2 });
});
test("can add collections set/weakset/map/weakmap in a reactive object", () => {
const rawSet = new Set();
const rawWeakSet = new WeakSet();
const rawMap = new Map();
const rawWeakMap = new WeakMap();
const obj = reactive({ rawSet, rawWeakSet, rawMap, rawWeakMap });
expect(obj.rawSet).toBe(rawSet);
expect(obj.rawWeakSet).toBe(rawWeakSet);
expect(obj.rawMap).toBe(rawMap);
expect(obj.rawWeakMap).toBe(rawWeakMap);
});
});
describe("markRaw", () => {
test("markRaw works as expected: value is not observed", () => {
const obj1: any = markRaw({ value: 1 });
const obj2 = { value: 1 };
let n = 0;
const r = reactive({ obj1, obj2 }, () => n++);
expect(n).toBe(0);
r.obj1.value = r.obj1.value + 1;
expect(n).toBe(0);
r.obj2.value = r.obj2.value + 1;
expect(n).toBe(1);
expect(r.obj1).toBe(obj1);
expect(r.obj2).not.toBe(obj2);
});
});
describe("toRaw", () => {
test("toRaw works as expected", () => {
const obj = { value: 1 };
const reactiveObj = reactive(obj);
expect(reactiveObj).not.toBe(obj);
expect(toRaw(reactiveObj as Reactive<typeof obj>)).toBe(obj);
});
test("giving a non reactive to toRaw return the object itself", () => {
const obj = { value: 1 };
expect(toRaw(obj as Reactive<typeof obj>)).toBe(obj);
});
});
describe("Reactivity: useState", () => {
+2 -192
View File
@@ -24,8 +24,8 @@ class Root extends Component {
}
}
// dev=false for benchmarking. we don't want to benchmark dev code!
mount(Root, document.body, { templates: TEMPLATES, dev: false });`;
// Application setup
mount(Root, document.body, { templates: TEMPLATES, dev: true });`;
const COMPONENTS_XML = /*xml*/`
<templates>
@@ -1573,191 +1573,6 @@ class Root extends Component {
mount(Root, document.body, { templates: TEMPLATES, dev: true});
`;
const BENCHMARK_JS = `
const { Component, mount, xml, useState, onWillRender, onPatched} = owl;
// -----------------------------------------------------------------------------
// Data generation
// -----------------------------------------------------------------------------
let idCounter = 1;
const adjectives = [
"pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain",
"quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd",
"unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"];
const colours = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"];
const nouns = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"];
function _random (max) { return Math.round(Math.random() * 1000) % max; };
function buildData(count) {
const data = new Array(count);
for (let i = 0; i < count; i++) {
const label = \`\${adjectives[_random(adjectives.length)]} \${colours[_random(colours.length)]} \${nouns[_random(nouns.length)]}\`;
data[i] = {
id: idCounter++,
label,
};
}
return data;
}
// -----------------------------------------------------------------------------
// Components
// -----------------------------------------------------------------------------
class Button extends Component {
static template = xml\`
<div class='col-sm-6 smallpad'>
<button t-att-id="props.id" class='btn btn-primary btn-block' type='button' t-on-click="props.onClick">
<t t-esc="props.text"/>
</button>
</div>\`;
}
class Row extends Component {
static template = xml\`
<tr t-att-class="props.isSelected ? 'danger' : ''">
<td class="col-md-1" t-esc="props.row.id" />
<td class="col-md-4">
<a t-on-click="() => props.onSelect(props.row.id)" t-esc="props.row.label" />
</td>
<td class="col-md-1">
<a t-on-click="() => props.onRemove(props.row.id)" class="remove">[x]
<span class='glyphicon glyphicon-remove' aria-hidden="true" />
</a>
</td>
<td class='col-md-6'/>
</tr>\`
}
class Root extends Component {
static template = xml\`
<div class='container'>
<div class='jumbotron'>
<div class='row'>
<div class='col-md-6'>
<h1>Owl Keyed</h1>
</div>
<div class='col-md-6'>
<div class='row'>
<Button id="'run'" onClick.bind="run" text="'Create 1,000 rows'" />
<Button id="'runlots'" onClick.bind="runLots" text="'Create 10,000 rows'" />
<Button id="'add'" onClick.bind="add" text="'Append 1,000 rows'" />
<Button id="'update'" onClick.bind="update" text="'Update every 10th row'" />
<Button id="'clear'" onClick.bind="clear" text="'Clear'" />
<Button id="'swaprows'" onClick.bind="swapRows" text="'Swap Rows'" />
</div>
</div>
</div>
</div>
<table class='table table-hover table-striped test-data'>
<tbody>
<t t-foreach="state.rows" t-as="row" t-key="row.id">
<Row row="row" isSelected="row.id === state.selectedRowId" onSelect.bind="selectRow" onRemove.bind="removeRow"/>
</t>
</tbody>
</table>
<span class='preloadicon glyphicon glyphicon-remove' aria-hidden="true" />
</div>\`;
static components = { Button, Row };
setup() {
this.state = useState({
rows: [],
selectedRowId: null
});
this.benchmarking = false;
onPatched(() => {
if (this.benchmarking) {
this.stop();
}
});
}
start(descr) {
this.benchmarking = \`[\${descr}]\`;
console.time(this.benchmarking);
}
stop() {
console.timeEnd(this.benchmarking);
this.benchmarking = false;
}
run() {
this.start('add1000');
this.state.rows = buildData(1000);
this.state.selectedRowId = null;
}
runLots() {
this.start('add10_000');
this.state.rows = buildData(10_000);
this.state.selectedRowId = null;
}
add() {
this.start('append1000');
this.state.rows = this.state.rows.concat(buildData(1000));
}
update() {
this.start('update1/10th');
let index = 0;
const rows = this.state.rows;
while (index < rows.length) {
rows[index].label = rows[index].label + " !!!";
index += 10;
}
}
clear() {
this.start('clear');
this.state.rows = [];
this.state.selectedRowId = null;
}
swapRows() {
this.start('swap');
const rows = this.state.rows;
if (rows.length > 998) {
let tmp = rows[1];
rows[1] = rows[998];
rows[998] = tmp;
}
}
selectRow(id) {
this.start('select');
this.state.selectedRowId = id;
}
removeRow(id) {
this.start('remove1');
const rows = this.state.rows;
rows.splice(rows.findIndex(row => row.id === id), 1);
}
}
mount(Root, document.body, { templates: TEMPLATES, dev: true });
`;
const BENCHMARK_CSS = `
tr.danger {
font-weight: bold;
}
.remove:hover {
font-weight: bold;
}
.remove {
cursor: pointer;
}
`;
export const SAMPLES = [
{
description: "Components",
@@ -1810,9 +1625,4 @@ export const SAMPLES = [
xml: WMS_XML,
css: WMS_CSS,
},
{
description: "Benchmark example",
code: BENCHMARK_JS,
css: BENCHMARK_CSS,
},
];