Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8aabd68eb5 |
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x, 22.x]
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -15,7 +15,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
#ide's
|
||||
**/.vscode/*
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
node_modules
|
||||
@@ -26,10 +26,3 @@ release-notes.md
|
||||
|
||||
# useful in some cases
|
||||
/temp
|
||||
|
||||
# owl-vision
|
||||
*/owl-vision/out/
|
||||
*/owl-vision/.vs/
|
||||
**/*.vsix
|
||||
!*/owl-vision/.vscode/launch.json
|
||||
!*/owl-vision/.vscode/tasks.json
|
||||
|
||||
@@ -47,4 +47,3 @@ Utility/helpers:
|
||||
- [`status`](reference/component.md#status-helper): utility function to get the status of a component (new, mounted or destroyed)
|
||||
- [`validate`](reference/utils.md#validate): validates if an object satisfies a specified schema
|
||||
- [`whenReady`](reference/utils.md#whenready): utility function to execute code when DOM is ready
|
||||
- [`batched`](reference/utils.md#batched): utility function to batch function calls
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
- [API](#api)
|
||||
- [Configuration](#configuration)
|
||||
- [`mount` helper](#mount-helper)
|
||||
- [Roots](#roots)
|
||||
- [Loading templates](#loading-templates)
|
||||
|
||||
## Overview
|
||||
@@ -62,13 +61,8 @@ The `config` object is an object with some of the following keys:
|
||||
templates (see [translations](translations.md))
|
||||
- **`templates (string | xml document)`**: all the templates that will be used by
|
||||
the components created by the application.
|
||||
- **`getTemplate ((s: string) => Element | Function | string | void)`**: a function that will be called by owl when it
|
||||
needs a template. If undefined is returned, owl looks into the app templates.
|
||||
- **`warnIfNoStaticProps (boolean, default=false)`**: if true, Owl will log a warning
|
||||
whenever it encounters a component that does not provide a [static props description](props.md#props-validation).
|
||||
- **`customDirectives (object)`**: if given, the corresponding function on the object will be called
|
||||
on the template custom directives: `t-custom-*` (see [Custom Directives](templates.md#custom-directives)).
|
||||
- **`globalValues (object)`**: Global object of elements available at compilations.
|
||||
|
||||
## `mount` helper
|
||||
|
||||
@@ -96,33 +90,6 @@ Most of the time, the `mount` helper is more convenient, but whenever one needs
|
||||
a reference to the actual Owl App, then using the `App` class directly is
|
||||
possible.
|
||||
|
||||
## Roots
|
||||
|
||||
An application can have multiple roots. It is sometimes useful to instantiate
|
||||
sub components in places that are not managed by Owl, such as an html editor
|
||||
with dynamic content (the Knowledge application in Odoo).
|
||||
|
||||
To create a root, one can use the `createRoot` method, which takes two arguments:
|
||||
|
||||
- **`Component`**: a component class (Root component of the app)
|
||||
- **`config (optional)`**: a config object that may contain a `props` object or a
|
||||
`env` object.
|
||||
|
||||
The `createRoot` method returns an object with a `mount` method (same API as
|
||||
the `App.mount` method), and a `destroy` method.
|
||||
|
||||
```js
|
||||
const root = app.createRoot(MyComponent, { props: { someProps: true } });
|
||||
await root.mount(targetElement);
|
||||
|
||||
// later
|
||||
root.destroy();
|
||||
```
|
||||
|
||||
Note that, like with owl `App`, it is the responsibility of the code that created
|
||||
the root to properly destroy it (before it has been removed from the DOM!). Owl
|
||||
has no way of doing it itself.
|
||||
|
||||
## Loading templates
|
||||
|
||||
Most applications will need to load templates whenever they start. Here is
|
||||
|
||||
@@ -357,7 +357,7 @@ cleaning operation, since the component may be destroyed before it has even been
|
||||
mounted. The `willDestroy` hook is useful in that situation, since it is always
|
||||
called.
|
||||
|
||||
The `onWillDestroy` hook is used to register a function that will be executed at
|
||||
The `onWillUnmount` hook is used to register a function that will be executed at
|
||||
this moment:
|
||||
|
||||
```javascript
|
||||
|
||||
@@ -51,8 +51,8 @@ that render its content, and a fallback if an error happened.
|
||||
```js
|
||||
class ErrorBoundary extends Component {
|
||||
static template = xml`
|
||||
<t t-if="state.error" t-slot="fallback">An error occurred</t>
|
||||
<t t-else="" t-slot="default"/>`;
|
||||
<t t-if="error" t-slot="fallback">An error occurred</t>
|
||||
<t t-else="" t-slot="content"`;
|
||||
|
||||
setup() {
|
||||
this.state = useState({ error: false });
|
||||
|
||||
@@ -190,13 +190,12 @@ will then be updated accordingly.
|
||||
### `useExternalListener`
|
||||
|
||||
The `useExternalListener` hook helps solve a very common problem: adding and removing
|
||||
a listener on some target whenever a component is mounted/unmounted. It takes a target
|
||||
as its first argument, forwards the other arguments to `addEventListener`. For example,
|
||||
a listener on some target whenever a component is mounted/unmounted. For example,
|
||||
a dropdown menu (or its parent) may need to listen to a `click` event on `window`
|
||||
to be closed:
|
||||
|
||||
```js
|
||||
useExternalListener(window, "click", this.closeMenu, { capture: true });
|
||||
useExternalListener(window, "click", this.closeMenu);
|
||||
```
|
||||
|
||||
### `useComponent`
|
||||
|
||||
@@ -140,28 +140,6 @@ class SomeComponent extends Component {
|
||||
The `.bind` suffix also implies `.alike`, so these props will not cause additional
|
||||
renderings.
|
||||
|
||||
## Translatable props
|
||||
|
||||
When you need to pass a user-facing string to a subcomponent, you likely want it
|
||||
to be translated. Unfortunately, because props are arbitrary expressions, it wouldn't
|
||||
be practical for Owl to find out which parts of the expression are strings and translate
|
||||
them, and it also makes it difficult for tooling to extract these strings to generate
|
||||
terms to translate. While you can work around this issue by doing the translation in
|
||||
JavaScript, or by using `t-set` with a body (the body of `t-set` is translated),
|
||||
and passing the variable as a prop, this is a sufficiently common use case that Owl
|
||||
provides a suffix for this purpose: `.translate`.
|
||||
|
||||
```xml
|
||||
<t t-name="ParentComponent">
|
||||
<Child someProp.translate="some message"/>
|
||||
</t>
|
||||
```
|
||||
|
||||
Note that the content of this attribute is _NOT_ treated as a JavaScript expression:
|
||||
it is treated as a string, as if it was an attribute on an HTML element, and translated
|
||||
before being passed to the component. If you need to interpolate some data into the
|
||||
string, you will still have to do this in JavaScript.
|
||||
|
||||
## Dynamic Props
|
||||
|
||||
The `t-props` directive can be used to specify totally dynamic props:
|
||||
@@ -260,7 +238,7 @@ class ComponentB extends owl.Component {
|
||||
count: {type: Number},
|
||||
messages: {
|
||||
type: Array,
|
||||
element: {type: Object, shape: {id: Boolean, text: String }}
|
||||
element: {type: Object, shape: {id: Boolean, text: String }
|
||||
},
|
||||
date: Date,
|
||||
combinedVal: [Number, Boolean],
|
||||
@@ -298,8 +276,7 @@ class ComponentB extends owl.Component {
|
||||
id: Number,
|
||||
name: {type: String, optional: true},
|
||||
url: String
|
||||
}
|
||||
}, // object, with keys id (number), name (string, optional) and url (string)
|
||||
]}, // object, with keys id (number), name (string, optional) and url (string)
|
||||
someObj3: {
|
||||
type: Object,
|
||||
values: { type: Array, element: String },
|
||||
@@ -320,28 +297,6 @@ class ComponentB extends owl.Component {
|
||||
|
||||
Note: the props validation code is done by using the [validate utility function](utils.md#validate).
|
||||
|
||||
### `slots` prop
|
||||
|
||||
If a component that uses [slots](slots.md) also lists or validates its props, then
|
||||
you will have to explicitely allow the `slots` prop (with an `Object` type), or
|
||||
allow extra props using the `*` notation mentioned above. This is because slots
|
||||
are provided to a component [as props](slots.md#slots-and-props).
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
class MyComponent extends Component {
|
||||
static props = [someProp, slots?];
|
||||
}
|
||||
|
||||
class MyComponentWithValidation extends Component {
|
||||
static props = {
|
||||
someProp: {type: Number, optional: true},
|
||||
slots : {type: Object, optional: true},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Good Practices
|
||||
|
||||
A `props` object is a collection of values that come from the parent. As such,
|
||||
|
||||
@@ -152,7 +152,7 @@ This may seem counter-intuitive, but it makes perfect sense in the context of co
|
||||
```js
|
||||
class DoubleCounter extends Component {
|
||||
static template = xml`
|
||||
<t t-esc="'selected: ' + state.selected + ', value: ' + state[state.selected]"/>
|
||||
<t t-esc="state.selected + ': ' + state[state.selected].value"/>
|
||||
<button t-on-click="() => this.state.count1++">increment count 1</button>
|
||||
<button t-on-click="() => this.state.count2++">increment count 2</button>
|
||||
<button t-on-click="changeCounter">Switch counter</button>
|
||||
@@ -193,7 +193,7 @@ to be able to opt out of creating them in the first place. This is the purpose o
|
||||
### `markRaw`
|
||||
|
||||
Marks an object so that it is ignored by the reactivity system, meaning that if this object is ever
|
||||
part of a reactive object, it will be returned as is, and no keys in that object will be
|
||||
part of a of a reactive object, it will be returned as is, and no keys in that object will be
|
||||
observed.
|
||||
|
||||
```js
|
||||
|
||||
@@ -133,7 +133,7 @@ Slots can define a default content, in case the parent did not define them:
|
||||
## Dynamic Slots
|
||||
|
||||
The `t-slot` directive is actually able to use any expressions, using string
|
||||
interpolation:
|
||||
interplolation:
|
||||
|
||||
```xml
|
||||
<t t-slot="{{current}}" />
|
||||
@@ -201,17 +201,16 @@ use this `Notebook` component:
|
||||
|
||||
```xml
|
||||
<Notebook>
|
||||
<t t-set-slot="page1" title.translate="Page 1">
|
||||
<t t-set-slot="page1" title="'Page 1'">
|
||||
<div>this is in the page 1</div>
|
||||
</t>
|
||||
<t t-set-slot="page2" title.translate="Page 2" hidden="somevalue">
|
||||
<t t-set-slot="page2" title="'Page 2'" hidden="somevalue">
|
||||
<div>this is in the page 2</div>
|
||||
</t>
|
||||
</Notebook>
|
||||
```
|
||||
|
||||
Slot params works like normal props, so one can use suffixes like `.translate`
|
||||
when a prop is a user facing string and should be translated, or `.bind` to
|
||||
Slot params works like normal props, so one can use the `.bind` suffix to
|
||||
bind a function if needed.
|
||||
|
||||
## Slot scopes
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
- [Sub Templates](#sub-templates)
|
||||
- [Dynamic Sub Templates](#dynamic-sub-templates)
|
||||
- [Debugging](#debugging)
|
||||
- [Custom Directives](#custom-directives)
|
||||
- [Fragments](#fragments)
|
||||
- [Inline templates](#inline-templates)
|
||||
- [Rendering svg](#rendering-svg)
|
||||
@@ -56,19 +55,17 @@ extensions.
|
||||
|
||||
For reference, here is a list of all standard QWeb directives:
|
||||
|
||||
| Name | Description |
|
||||
| ------------------------------ | ----------------------------------------------------------------------- |
|
||||
| `t-esc` | [Outputting safely a value](#outputting-data) |
|
||||
| `t-out` | [Outputting value, possibly without escaping](#outputting-data) |
|
||||
| `t-set`, `t-value` | [Setting variables](#setting-variables) |
|
||||
| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) |
|
||||
| `t-foreach`, `t-as` | [Loops](#loops) |
|
||||
| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) |
|
||||
| `t-call` | [Rendering sub templates](#sub-templates) |
|
||||
| `t-debug`, `t-log` | [Debugging](#debugging) |
|
||||
| `t-translation` | [Disabling the translation of a node](translations.md) |
|
||||
| `t-translation-context` | [Context of translations within a node](translations.md) |
|
||||
| `t-translation-context-*` | [Context of translation for a specific node attribute](translations.md) |
|
||||
| Name | Description |
|
||||
| ------------------------------ | --------------------------------------------------------------- |
|
||||
| `t-esc` | [Outputting safely a value](#outputting-data) |
|
||||
| `t-out` | [Outputting value, possibly without escaping](#outputting-data) |
|
||||
| `t-set`, `t-value` | [Setting variables](#setting-variables) |
|
||||
| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) |
|
||||
| `t-foreach`, `t-as` | [Loops](#loops) |
|
||||
| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) |
|
||||
| `t-call` | [Rendering sub templates](#sub-templates) |
|
||||
| `t-debug`, `t-log` | [Debugging](#debugging) |
|
||||
| `t-translation` | [Disabling the translation of a node](translations.md) |
|
||||
|
||||
The component system in Owl requires additional directives, to express various
|
||||
needs. Here is a list of all Owl specific directives:
|
||||
@@ -83,7 +80,6 @@ needs. Here is a list of all Owl specific directives:
|
||||
| `t-slot`, `t-set-slot`, `t-slot-scope` | [Rendering a slot](slots.md) |
|
||||
| `t-model` | [Form input bindings](input_bindings.md) |
|
||||
| `t-tag` | [Rendering nodes with dynamic tag name](#dynamic-tag-names) |
|
||||
| `t-custom-*` | [Rendering nodes with custom directives](#custom-directives) |
|
||||
|
||||
## QWeb Template Reference
|
||||
|
||||
@@ -193,15 +189,6 @@ The first `t-out` will act as a `t-esc` directive, which means that the content
|
||||
of `value1` will be escaped. However, since `value2` has been tagged as a markup,
|
||||
this will be injected as html.
|
||||
|
||||
`markup` can also be used as a tag function, allowing the interpolated values to
|
||||
be safely escaped:
|
||||
|
||||
```js
|
||||
const maliciousInput = "<script>alert('💥💥')</script>";
|
||||
// <b><script>alert('💥💥')</script></b>
|
||||
const value = markup`<b>${maliciousInput}</b>`;
|
||||
```
|
||||
|
||||
### Setting Variables
|
||||
|
||||
QWeb allows creating variables from within the template, to memoize a computation (to use it multiple times), give a piece of data a clearer name, ...
|
||||
@@ -601,35 +588,6 @@ will stop execution if the browser dev tools are open.
|
||||
|
||||
will print 42 to the console.
|
||||
|
||||
### Custom Directives
|
||||
|
||||
Owl 2 supports the declaration of custom directives. To use them, an Object of functions needs to be configured on the owl APP:
|
||||
|
||||
```js
|
||||
new App(..., {
|
||||
customDirectives: {
|
||||
test_directive: function (el, value) {
|
||||
el.setAttribute("t-on-click", value);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The functions will be called when a custom directive with the name of the
|
||||
function is found. The original element will be replaced with the one
|
||||
modified by the function.
|
||||
This :
|
||||
|
||||
```xml
|
||||
<div t-custom-test_directive="click" />
|
||||
```
|
||||
|
||||
will be replaced by :
|
||||
|
||||
```xml
|
||||
<div t-on-click="value"/>
|
||||
```
|
||||
|
||||
## Fragments
|
||||
|
||||
Owl 2 supports templates with an arbitrary number of root elements, or even just
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
# 🦉 Translations 🦉
|
||||
|
||||
If properly setup, Owl can translate all rendered templates. To do
|
||||
so, it needs a translate function, which takes
|
||||
|
||||
- a string (the term to translate)
|
||||
- a string (the translation context of the term)
|
||||
and returns a string.
|
||||
so, it needs a translate function, which takes a string and returns a string.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const translations = {
|
||||
fr: {
|
||||
hello: "bonjour",
|
||||
yes: "oui",
|
||||
no: "non",
|
||||
},
|
||||
pt: {
|
||||
hello: "bom dia",
|
||||
yes: "sim",
|
||||
no: "não",
|
||||
},
|
||||
hello: "bonjour",
|
||||
yes: "oui",
|
||||
no: "non",
|
||||
};
|
||||
const translateFn = (str, ctx) => translations[ctx]?.[str] || str;
|
||||
const translateFn = (str) => translations[str] || str;
|
||||
|
||||
const app = new App(Root, { templates, tranaslateFn });
|
||||
// ...
|
||||
@@ -38,11 +27,6 @@ Once setup, all rendered templates will be translated using `translateFn`:
|
||||
`placeholder`, `label` and `alt`,
|
||||
- translating text nodes can be disabled with the special attribute `t-translation`,
|
||||
if its value is `off`.
|
||||
- the translate function receives as second parameter a context that can be used
|
||||
to contextualized the translation. That context can be set globally on a node
|
||||
and its children by using `t-translation-context`. If a specific node
|
||||
attribute `x` needs another context, that context can be specified with a
|
||||
special directive `t-translation-context-x`.
|
||||
|
||||
So, with the above `translateFn`, the following templates:
|
||||
|
||||
@@ -62,22 +46,6 @@ will be rendered as:
|
||||
<input placeholder="bonjour" other="yes"/>
|
||||
```
|
||||
|
||||
and the following template:
|
||||
|
||||
```xml
|
||||
<div t-translation-context="fr" title="hello">hello</div>
|
||||
<div>Are you sure?</div>
|
||||
<input t-translation-context-placeholder="pt" placeholder="hello" other="yes"/>
|
||||
```
|
||||
|
||||
will be rendered as:
|
||||
|
||||
```xml
|
||||
<div title="bonjour">bonjour</div>
|
||||
<div>Are you sure?</div>
|
||||
<input placeholder="bom dia" other="yes"/>
|
||||
```
|
||||
|
||||
Note that the translation is done during the compilation of the template, not
|
||||
when it is rendered.
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ functions are all available in the `owl.utils` namespace.
|
||||
- [`loadFile`](#loadfile): loading a file (useful for templates)
|
||||
- [`EventBus`](#eventbus): a simple EventBus
|
||||
- [`validate`](#validate): a validation function
|
||||
- [`batched`](#batched): batch function calls
|
||||
|
||||
## `whenReady`
|
||||
|
||||
@@ -79,22 +78,3 @@ validate(
|
||||
// - 'id' is missing (should be a number),
|
||||
// - 'url' is missing (should be a boolean or list of numbers),
|
||||
```
|
||||
|
||||
## `batched`
|
||||
|
||||
The `batched` function creates a batched version of a callback so that multiple calls to it within the same microtick will only result in a single invocation of the original callback.
|
||||
|
||||
```js
|
||||
function hello() {
|
||||
console.log("hello");
|
||||
}
|
||||
|
||||
const batchedHello = batched(hello);
|
||||
batchedHello();
|
||||
// Nothing is logged
|
||||
batchedHello();
|
||||
// Still not logged
|
||||
|
||||
await Promise.resolve(); // Await the next microtick
|
||||
// "hello" is logged only once
|
||||
```
|
||||
|
||||
@@ -35,13 +35,13 @@ The components tab is separated into two sub windows: the components tree in the
|
||||
the component details in the right. The components tree will display all the different
|
||||
components that are present in the tab in the form of a tree. The root of this tree is
|
||||
actually the app which is not a component but can still be inspected by the devtools like
|
||||
one. There can also be multiple apps loaded in the page like in website:
|
||||
one. There can also be multiple apps loaded in the page like in the following:
|
||||
|
||||
<img src="screenshots/multi_apps.png"/>
|
||||
|
||||
There is a convenient search bar at the top of the components tree which will help finding
|
||||
the components tou want in the tree and also, an element picker can be used to directly select
|
||||
the component you want to focus on in the page which is especially useful when 1trying to find
|
||||
the component you want to focus on in the page which is especially useful when trying to find
|
||||
what you want. Just click on the elements picker icon and click on the element you want to focus
|
||||
on in the page and it will be selected in the devtools accordingly. Hovering any element in the
|
||||
page in this mode will highlight it and the same happens anytime in the components tree.
|
||||
@@ -64,40 +64,31 @@ as its env, props, observed states and all the other variables that are present
|
||||
While the props and the env are already present on the actual instance of the component and are
|
||||
pretty explicit by themselves, the observed state value is a bit more complicated to grasp.
|
||||
|
||||
The observed state is actually information about which variables are being observed by the component:
|
||||
when any property of a reactive object is being read by the component, the component will subscribe
|
||||
to this property which means it will listen to any change that can occur on the property and render
|
||||
when such a change occurs. This can be visualized easily within the devtools inside of the observed
|
||||
state section: observed properties of the reactive object(s) are displayed in bold while the others
|
||||
are greyed out. Do keep in mind that a greyed out property in the observed state of one component
|
||||
may be observed by another and the other way around is also possible. Here is an example for some
|
||||
user Field component:
|
||||
The observed state is actually information about which variables are observed by the component
|
||||
which will trigger a rerender of the component when it is modified. The keys represent which part of the
|
||||
variable is actually observed and the target is the actual variable. For simplicity, the properties
|
||||
that are not observed by the component are greyed out while the others are in bold. This means that
|
||||
editing bold ones will trigger a rerender while the greyed out ones will not.
|
||||
|
||||
<img src="screenshots/states.png"/>
|
||||
|
||||
Navigation inside the properties is also similar to the one in console variables: properties have
|
||||
In the given example, we have two keys/target pairs for two different variables. The first one indicates
|
||||
that adding or removing an element to the array will trigger a rerender since the length will have changed.
|
||||
Replacing the element at index 0, 1 or 2 will also have the same effect as implied by the keys. It doesn't
|
||||
mean that editing the properties of element at index 0, 1 or 2 will rerender the component though. It may
|
||||
be the case for some but this will be described in another keys/target pair. The second keys/target pair
|
||||
is actually the element at index 0 of the first pair. It only has id in the keys meaning that only the
|
||||
id property will actually trigger a rerender the component when modified. Be aware however that the other
|
||||
properties may be in the observed state of another component like a child one in this case. A greyed out
|
||||
property only implies it is not reactive for the selected component and not for the others.
|
||||
|
||||
The navigation inside the properties is also similar to the one in console variables: properties have
|
||||
their prototype displayed and getters will get their value when clicked on (...). It is also possible to
|
||||
send any property to the console using the right-click context menu on it and functions can be inspected
|
||||
in the sources tab as well.
|
||||
|
||||
<img src="screenshots/function_menu.png"/>
|
||||
|
||||
Using the right-click context menu on a property also allows to observe variables. Observed variables will
|
||||
be sent to a dedicated section of the details window and their value will be refreshed every 200ms. These
|
||||
variables are only shown when they are found and their access path will be kept in memory inside the
|
||||
browser so that it will always persist until the user decides to stop observing the variable. As in the
|
||||
browser's devtools, observed objects are displayed in reduced form and cannot be interacted with. It is
|
||||
still possible to send them to the console or remove them from the list using right-click.
|
||||
|
||||
<img src="screenshots/observe_variables.png"/>
|
||||
|
||||
The last section of the details window is filled with the component's lifecycle hooks. Using right click on
|
||||
them allows to place breakpoints inside the hook (either on its instance or class, hooks like mounted and
|
||||
willStart cannot have instance-based breakpoints because they will never trigger). Conditions in conditional
|
||||
breakpoints will be evaluated in the context of the component's definition.
|
||||
|
||||
<img src="screenshots/hooks.png"/>
|
||||
|
||||
There are several icons available to perform several of the actions described before in the components
|
||||
tree context menu and all these actions are also available by opening the menu by right-clicking on the
|
||||
component's name. Using the left click on the component's name will focus it in the components tree.
|
||||
@@ -105,10 +96,8 @@ component's name. Using the left click on the component's name will focus it in
|
||||
It is also possible to edit any of the leaf node properties. To do so, you must double click on the
|
||||
property's value and modify it using the freshly created input then press enter to apply the changes.
|
||||
Do note that the modified values should be written in JSON format in order to be valid (examples:
|
||||
89, "yes", undefined, null, \["hello", 15\], {"a": 1}, true, ...). Editing any value will produce a
|
||||
manual render of the component (or the root component of the application in the case of env values).
|
||||
Whether the edition has an impact on the component or not and whether it produces an error is the
|
||||
responsability of the user.
|
||||
89, "yes", undefined, null, \["hello", 15\], {"a": 1}, true, ...). Whether it has an impact on the
|
||||
component or not and whether it produces an error is the responsability of the user.
|
||||
|
||||
<img src="screenshots/edit.png"/>
|
||||
|
||||
@@ -128,8 +117,7 @@ are intercepted by the devtools using the record button.
|
||||
The second button is used to clear all the events that have been recorded. The select can be used to
|
||||
switch between the tree view (which shows the causality between renders) and the events log view which
|
||||
simply displays the events in the exact order they were triggered. In this view, you can expand the create,
|
||||
update and destroy events which reveals the component that initiated the event. Also, a transition line will
|
||||
appear each time a new animation frame has been loaded between events.
|
||||
update and destroy events which reveals the component that initiated the event.
|
||||
|
||||
<img src="screenshots/events_log.png"/>
|
||||
|
||||
@@ -143,29 +131,20 @@ There is also the Trace Renderings and Trace Subscriptions features. These featu
|
||||
recording of events and have no effect on the profiler tab. The Trace Renderings option is used to log in
|
||||
the console all the render events and allows to show their traceback information. Similarly, the Trace
|
||||
Subscriptions option logs all the properties that caused a render event and also allows to see the traceback
|
||||
of the modification.
|
||||
of the modification
|
||||
|
||||
<img src="screenshots/trace_rendering.png"/>
|
||||
<img src="screenshots/trace_subscriptions.png"/>
|
||||
|
||||
The Owl Devtools also allow to inspect iframes coded in Owl: when an Owl iframe is detected in the page,
|
||||
the iframe selector will appear next to the tabs. This allows to switch from an iframe to another easily.
|
||||
Be aware that switching iframes will clear all record events from the profiler tab. Iframes detection is
|
||||
currently not working in the firefox version, we are aware of this issue and will try to address it in the
|
||||
future.
|
||||
|
||||
<img src="screenshots/iframes.png"/>
|
||||
|
||||
## Options
|
||||
|
||||
The owl devtools extension has a dark mode feature which defaults to your general devtools settings and can
|
||||
be toggled using the sun/moon icon at the top-right corner of the tab. There is also a refresh button to
|
||||
completely reset the owl devtools.
|
||||
be toggled using the sun/moon icon at the top-right corner of the tab. All the examples above were created
|
||||
with the dark mode enabled. There is also a refresh button to completely reset the owl devtools.
|
||||
|
||||
<img src="screenshots/darkmode.png"/>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the feedback from the page to the devtools seems to be cut, you can first try to use the refresh
|
||||
button mentioned above but if it still doesn't seem to work, just close the devtools and refresh the page.
|
||||
If the feedback from the page to the devtools seems to be cut, just close the devtools and refresh the page.
|
||||
This will eventually happen any time a tab stays opened for too long without being refreshed.
|
||||
|
||||
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 346 KiB After Width: | Height: | Size: 311 KiB |
|
Before Width: | Height: | Size: 488 KiB After Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 146 KiB |
@@ -41,6 +41,9 @@ const loadFile = (path) => {
|
||||
* Make an iframe, with all the js, css and xml properly injected.
|
||||
*/
|
||||
function makeCodeIframe(js, css, xml) {
|
||||
// escape backticks in the xml so they don't close the template string
|
||||
const escapedXml = xml.replace(/`/g, '\\\`');
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.onload = () => {
|
||||
const doc = iframe.contentDocument;
|
||||
@@ -52,8 +55,6 @@ function makeCodeIframe(js, css, xml) {
|
||||
|
||||
const script = doc.createElement("script");
|
||||
script.type = "module";
|
||||
// escape characters with special meaning in template literals
|
||||
const escapedXml = xml.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/, "\\${");
|
||||
script.textContent = `const TEMPLATES = \`${escapedXml}\`\n${js}`;
|
||||
doc.body.appendChild(script);
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ class TaskList {
|
||||
}
|
||||
}
|
||||
|
||||
toggleTask(task) {
|
||||
task.isCompleted = !task.isCompleted;
|
||||
}
|
||||
|
||||
toggleTask(id) {
|
||||
const task = this.tasks.find(t => t.id === id);
|
||||
task.isCompleted = !task.isCompleted;
|
||||
@@ -57,7 +61,7 @@ class TaskList {
|
||||
clearCompleted() {
|
||||
const tasks = this.tasks.filter(t => t.isCompleted);
|
||||
for (let task of tasks) {
|
||||
this.deleteTask(task.id);
|
||||
this.deleteTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.8.1",
|
||||
"version": "2.2.1",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"module": "dist/owl.es.js",
|
||||
@@ -9,7 +9,7 @@
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
"node": ">=12.18.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build:bundle": "rollup -c --failAfterWarnings",
|
||||
@@ -22,7 +22,7 @@
|
||||
"build:devtools-chrome": "npm run dev:devtools-chrome -- --config-env=production",
|
||||
"build:devtools-firefox": "npm run dev:devtools-firefox -- --config-env=production",
|
||||
"test": "jest",
|
||||
"test:debug": "node node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
|
||||
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
|
||||
"test:watch": "jest --watch",
|
||||
"playground:serve": "python3 tools/playground_server.py || python tools/playground_server.py",
|
||||
"playground": "npm run build && npm run playground:serve",
|
||||
@@ -32,10 +32,7 @@
|
||||
"check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md,tools/devtools/**/*.js} --check",
|
||||
"lint": "eslint src/**/*.ts tests/**/*.ts",
|
||||
"release": "node tools/release.js",
|
||||
"compile_templates": "node tools/compile_owl_templates.mjs"
|
||||
},
|
||||
"bin": {
|
||||
"compile_owl_templates": "tools/compile_owl_templates.mjs"
|
||||
"compile_templates": "node tools/compile_xml.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -49,7 +46,6 @@
|
||||
"homepage": "https://github.com/odoo/owl#readme",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^14.11.8",
|
||||
"@typescript-eslint/eslint-plugin": "5.48.1",
|
||||
"@typescript-eslint/parser": "5.48.1",
|
||||
@@ -101,8 +97,5 @@
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
"endOfLine": "auto"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsdom": "^25.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pkg from "./package.json";
|
||||
import git from "git-rev-sync";
|
||||
import typescript from "rollup-plugin-typescript2";
|
||||
import typescript from 'rollup-plugin-typescript2';
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import dts from "rollup-plugin-dts";
|
||||
|
||||
@@ -12,7 +12,7 @@ const ES_FILENAME = "dist/owl.es.js";
|
||||
|
||||
if (pkg.module !== ES_FILENAME || pkg.main !== CJS_FILENAME) {
|
||||
throw new Error("package.json has been modified. Build script should be updated accordingly");
|
||||
}
|
||||
}
|
||||
|
||||
const outro = `
|
||||
__info__.date = '${new Date().toISOString()}';
|
||||
@@ -21,37 +21,39 @@ __info__.url = 'https://github.com/odoo/owl';
|
||||
`;
|
||||
|
||||
switch (process.argv[4]) {
|
||||
case "compiler":
|
||||
(input = "src/compiler/index.ts"),
|
||||
(output = [getConfigForFormat("cjs", "dist/compiler.js", "")]);
|
||||
case "compiler":
|
||||
input = "src/compiler/index.ts",
|
||||
output = [
|
||||
getConfigForFormat('cjs', 'dist/compiler.js', ''),
|
||||
]
|
||||
break;
|
||||
case "runtime":
|
||||
input = "src/runtime/index.ts";
|
||||
output = [
|
||||
getConfigForFormat("esm", addSuffix(ES_FILENAME, "runtime"), outro),
|
||||
getConfigForFormat("cjs", addSuffix(CJS_FILENAME, "runtime"), outro),
|
||||
getConfigForFormat("iife", addSuffix(IIFE_FILENAME, "runtime"), outro),
|
||||
getConfigForFormat("iife", addSuffix(IIFE_FILENAME, "runtime"), outro, true),
|
||||
];
|
||||
getConfigForFormat('esm', addSuffix(ES_FILENAME, 'runtime'), outro),
|
||||
getConfigForFormat('cjs', addSuffix(CJS_FILENAME, 'runtime'), outro),
|
||||
getConfigForFormat('iife', addSuffix(IIFE_FILENAME, 'runtime'), outro),
|
||||
getConfigForFormat('iife', addSuffix(IIFE_FILENAME, 'runtime'), outro, true),
|
||||
]
|
||||
break;
|
||||
default:
|
||||
(input = "src/index.ts"),
|
||||
(output = [
|
||||
getConfigForFormat("esm", ES_FILENAME, outro),
|
||||
getConfigForFormat("cjs", CJS_FILENAME, outro),
|
||||
getConfigForFormat("iife", IIFE_FILENAME, outro),
|
||||
getConfigForFormat("iife", IIFE_FILENAME, outro, true),
|
||||
]);
|
||||
}
|
||||
input = "src/index.ts",
|
||||
output = [
|
||||
getConfigForFormat('esm', ES_FILENAME, outro),
|
||||
getConfigForFormat('cjs', CJS_FILENAME, outro),
|
||||
getConfigForFormat('iife', IIFE_FILENAME, outro),
|
||||
getConfigForFormat('iife', IIFE_FILENAME, outro, true),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate from a string depicting a path a new path for the minified version.
|
||||
* @param {string} pkgFileName file name
|
||||
*/
|
||||
function addSuffix(pkgFileName, suffix) {
|
||||
const parts = pkgFileName.split(".");
|
||||
const parts = pkgFileName.split('.');
|
||||
parts.splice(parts.length - 1, 0, suffix);
|
||||
return parts.join(".");
|
||||
return parts.join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +71,7 @@ function getConfigForFormat(format, generatedFileName, outro, minified = false)
|
||||
outro: outro,
|
||||
freeze: false,
|
||||
plugins: minified ? [terser()] : [],
|
||||
indent: " ", // indent with 4 spaces
|
||||
indent: ' ', // indent with 4 spaces
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,19 +81,9 @@ export default [
|
||||
output,
|
||||
plugins: [
|
||||
typescript({
|
||||
useTsconfigDeclarationDir: true,
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "src/compiler/standalone/index.ts",
|
||||
output: [{ file: "dist/compile_templates.mjs", format: "es" }],
|
||||
external: ["fs", "fs/promises", "path", "jsdom"],
|
||||
plugins: [
|
||||
typescript({
|
||||
useTsconfigDeclarationDir: true,
|
||||
}),
|
||||
],
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "dist/types/index.d.ts",
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# encountered issues
|
||||
## dropdown issue
|
||||
- there was a problem that writing in a state while the effect was updated.
|
||||
- the tracking of signal being written were dropped because we cleared it
|
||||
after re-running the effect that made a write.
|
||||
- solution: clear the tracked signal before re-executing the effects
|
||||
- reading signal A while also writing signal A makes an infinite loop
|
||||
- current solution: use toRaw in order to not track the read
|
||||
- possible better solution to explore: do not track read if there is a write in a effect.
|
||||
## website issue
|
||||
- a rpc request was made on onWillStart, onWillStart was tracking reads. (see WebsiteBuilderClientAction)
|
||||
- The read subsequently made a write, that re-triggered the onWillStart.
|
||||
- A similar situation happened with onWillUpdateProps (see Transition)
|
||||
- solution: prevent tracking reads in onWillStart and onWillUpdateProps
|
||||
|
||||
# future
|
||||
- worker for computation?
|
||||
- cap'n web
|
||||
@@ -1,4 +0,0 @@
|
||||
// Custom error class that wraps error that happen in the owl lifecycle
|
||||
export class OwlError extends Error {
|
||||
cause?: any;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
export type ExecutionContext = {
|
||||
onReadAtom: (atom: Atom) => void;
|
||||
unsubcribe?: (scheduledContexts: Set<ExecutionContext>) => void;
|
||||
update?: Function;
|
||||
atoms?: Set<Atom>;
|
||||
meta?: any;
|
||||
// getParent: () => ExecutionContext | undefined;
|
||||
// getChildren: () => ExecutionContext[];
|
||||
// schedule: () => void;
|
||||
};
|
||||
|
||||
export type customDirectives = Record<
|
||||
string,
|
||||
(node: Element, value: string, modifier: string[]) => void
|
||||
>;
|
||||
|
||||
export type Atom = {
|
||||
executionContexts: Set<ExecutionContext>;
|
||||
dependents: Set<DerivedAtom>;
|
||||
getValue: () => any;
|
||||
};
|
||||
|
||||
export type OldValue = any;
|
||||
|
||||
export type DerivedAtom = Atom & {
|
||||
dependencies: Map<Atom, OldValue>;
|
||||
computed: boolean;
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import { OwlError } from "./owl_error";
|
||||
|
||||
/**
|
||||
* Parses an XML string into an XML document, throwing errors on parser errors
|
||||
* instead of returning an XML document containing the parseerror.
|
||||
*
|
||||
* @param xml the string to parse
|
||||
* @returns an XML document corresponding to the content of the string
|
||||
*/
|
||||
export function parseXML(xml: string): XMLDocument {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(xml, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
let msg = "Invalid XML in template.";
|
||||
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
||||
if (parsererrorText) {
|
||||
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
||||
const re = /\d+/g;
|
||||
const firstMatch = re.exec(parsererrorText);
|
||||
if (firstMatch) {
|
||||
const lineNumber = Number(firstMatch[0]);
|
||||
const line = xml.split("\n")[lineNumber - 1];
|
||||
const secondMatch = re.exec(parsererrorText);
|
||||
if (line && secondMatch) {
|
||||
const columnIndex = Number(secondMatch[0]) - 1;
|
||||
if (line[columnIndex]) {
|
||||
msg +=
|
||||
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
||||
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new OwlError(msg);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
@@ -24,19 +24,18 @@ import {
|
||||
ASTTOut,
|
||||
ASTTPortal,
|
||||
ASTTranslation,
|
||||
ASTTranslationContext,
|
||||
ASTTSet,
|
||||
ASTType,
|
||||
Attrs,
|
||||
EventHandlers,
|
||||
} from "./parser";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { OwlError } from "../runtime/error_handling";
|
||||
|
||||
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
|
||||
const whitespaceRE = /\s+/g;
|
||||
|
||||
export interface Config {
|
||||
translateFn?: (s: string, translationCtx: string) => string;
|
||||
translateFn?: (s: string) => string;
|
||||
translatableAttributes?: string[];
|
||||
dev?: boolean;
|
||||
}
|
||||
@@ -44,7 +43,6 @@ export interface Config {
|
||||
export interface CodeGenOptions extends Config {
|
||||
hasSafeContext?: boolean;
|
||||
name?: string;
|
||||
hasGlobalValues: boolean;
|
||||
}
|
||||
|
||||
// using a non-html document so that <inner/outer>HTML serializes as XML instead
|
||||
@@ -84,14 +82,6 @@ function isProp(tag: string, key: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a template literal that evaluates to str. You can add interpolation
|
||||
* sigils into the string if required
|
||||
*/
|
||||
function toStringExpression(str: string) {
|
||||
return `\`${str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/, "\\${")}\``;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// BlockDescription
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -170,9 +160,9 @@ interface Context {
|
||||
block: BlockDescription | null;
|
||||
index: number | string;
|
||||
forceNewBlock: boolean;
|
||||
preventRoot?: boolean;
|
||||
isLast?: boolean;
|
||||
translate: boolean;
|
||||
translationCtx: string;
|
||||
tKeyExpr: string | null;
|
||||
nameSpace?: string;
|
||||
tModelSelectedExpr?: string;
|
||||
@@ -187,7 +177,6 @@ function createContext(parentCtx: Context, params?: Partial<Context>): Context {
|
||||
index: 0,
|
||||
forceNewBlock: true,
|
||||
translate: parentCtx.translate,
|
||||
translationCtx: parentCtx.translationCtx,
|
||||
tKeyExpr: null,
|
||||
nameSpace: parentCtx.nameSpace,
|
||||
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
|
||||
@@ -254,16 +243,7 @@ class CodeTarget {
|
||||
}
|
||||
}
|
||||
|
||||
const TRANSLATABLE_ATTRS = [
|
||||
"alt",
|
||||
"aria-label",
|
||||
"aria-placeholder",
|
||||
"aria-roledescription",
|
||||
"aria-valuetext",
|
||||
"label",
|
||||
"placeholder",
|
||||
"title",
|
||||
];
|
||||
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
||||
const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
|
||||
|
||||
export class CodeGenerator {
|
||||
@@ -275,7 +255,7 @@ export class CodeGenerator {
|
||||
target = new CodeTarget("template");
|
||||
templateName?: string;
|
||||
dev: boolean;
|
||||
translateFn: (s: string, translationCtx: string) => string;
|
||||
translateFn: (s: string) => string;
|
||||
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
|
||||
ast: AST;
|
||||
staticDefs: { id: string; expr: string }[] = [];
|
||||
@@ -299,9 +279,6 @@ export class CodeGenerator {
|
||||
this.dev = options.dev || false;
|
||||
this.ast = ast;
|
||||
this.templateName = options.name;
|
||||
if (options.hasGlobalValues) {
|
||||
this.helpers.add("__globals__");
|
||||
}
|
||||
}
|
||||
|
||||
generateCode(): string {
|
||||
@@ -315,7 +292,6 @@ export class CodeGenerator {
|
||||
forceNewBlock: false,
|
||||
isLast: true,
|
||||
translate: true,
|
||||
translationCtx: "",
|
||||
tKeyExpr: null,
|
||||
});
|
||||
// define blocks and utility functions
|
||||
@@ -336,13 +312,14 @@ export class CodeGenerator {
|
||||
mainCode.push(``);
|
||||
for (let block of this.blocks) {
|
||||
if (block.dom) {
|
||||
let xmlString = toStringExpression(block.asXmlString());
|
||||
let xmlString = block.asXmlString();
|
||||
xmlString = xmlString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
||||
if (block.dynamicTagName) {
|
||||
xmlString = xmlString.replace(/^`<\w+/, `\`<\${tag || '${block.dom.nodeName}'}`);
|
||||
xmlString = xmlString.replace(/\w+>`$/, `\${tag || '${block.dom.nodeName}'}>\``);
|
||||
mainCode.push(`let ${block.blockName} = tag => createBlock(${xmlString});`);
|
||||
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
||||
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
||||
mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
|
||||
} else {
|
||||
mainCode.push(`let ${block.blockName} = createBlock(${xmlString});`);
|
||||
mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,7 +377,7 @@ export class CodeGenerator {
|
||||
): BlockDescription {
|
||||
const hasRoot = this.target.hasRoot;
|
||||
const block = new BlockDescription(this.target, type);
|
||||
if (!hasRoot) {
|
||||
if (!hasRoot && !ctx.preventRoot) {
|
||||
this.target.hasRoot = true;
|
||||
block.isRoot = true;
|
||||
}
|
||||
@@ -426,7 +403,7 @@ export class CodeGenerator {
|
||||
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
|
||||
}
|
||||
|
||||
if (block.isRoot) {
|
||||
if (block.isRoot && !ctx.preventRoot) {
|
||||
if (this.target.on) {
|
||||
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
||||
}
|
||||
@@ -470,9 +447,9 @@ export class CodeGenerator {
|
||||
.join("");
|
||||
}
|
||||
|
||||
translate(str: string, translationCtx: string): string {
|
||||
translate(str: string): string {
|
||||
const match = translationRE.exec(str) as any;
|
||||
return match[1] + this.translateFn(match[2], translationCtx) + match[3];
|
||||
return match[1] + this.translateFn(match[2]) + match[3];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,8 +491,6 @@ export class CodeGenerator {
|
||||
return this.compileTSlot(ast, ctx);
|
||||
case ASTType.TTranslation:
|
||||
return this.compileTTranslation(ast, ctx);
|
||||
case ASTType.TTranslationContext:
|
||||
return this.compileTTranslationContext(ast, ctx);
|
||||
case ASTType.TPortal:
|
||||
return this.compileTPortal(ast, ctx);
|
||||
}
|
||||
@@ -541,7 +516,7 @@ export class CodeGenerator {
|
||||
const isNewBlock = !block || forceNewBlock;
|
||||
if (isNewBlock) {
|
||||
block = this.createBlock(block, "comment", ctx);
|
||||
this.insertBlock(`comment(${toStringExpression(ast.value)})`, block, {
|
||||
this.insertBlock(`comment(\`${ast.value}\`)`, block, {
|
||||
...ctx,
|
||||
forceNewBlock: forceNewBlock && !block,
|
||||
});
|
||||
@@ -557,7 +532,7 @@ export class CodeGenerator {
|
||||
|
||||
let value = ast.value;
|
||||
if (value && ctx.translate !== false) {
|
||||
value = this.translate(value, ctx.translationCtx);
|
||||
value = this.translate(value);
|
||||
}
|
||||
if (!ctx.inPreTag) {
|
||||
value = value.replace(whitespaceRE, " ");
|
||||
@@ -565,7 +540,7 @@ export class CodeGenerator {
|
||||
|
||||
if (!block || forceNewBlock) {
|
||||
block = this.createBlock(block, "text", ctx);
|
||||
this.insertBlock(`text(${toStringExpression(value)})`, block, {
|
||||
this.insertBlock(`text(\`${value}\`)`, block, {
|
||||
...ctx,
|
||||
forceNewBlock: forceNewBlock && !block,
|
||||
});
|
||||
@@ -611,6 +586,11 @@ export class CodeGenerator {
|
||||
}
|
||||
// attributes
|
||||
const attrs: Attrs = {};
|
||||
const nameSpace = ast.ns || ctx.nameSpace;
|
||||
if (nameSpace && isNewBlock) {
|
||||
// specific namespace uri
|
||||
attrs["block-ns"] = nameSpace;
|
||||
}
|
||||
|
||||
for (let key in ast.attrs) {
|
||||
let expr, attrName;
|
||||
@@ -646,8 +626,7 @@ export class CodeGenerator {
|
||||
}
|
||||
}
|
||||
} else if (this.translatableAttributes.includes(key)) {
|
||||
const attrTranslationCtx = ast.attrsTranslationCtx?.[key] || ctx.translationCtx;
|
||||
attrs[key] = this.translateFn(ast.attrs[key], attrTranslationCtx);
|
||||
attrs[key] = this.translateFn(ast.attrs[key]);
|
||||
} else {
|
||||
expr = `"${ast.attrs[key]}"`;
|
||||
attrName = key;
|
||||
@@ -740,10 +719,7 @@ export class CodeGenerator {
|
||||
attrs["block-ref"] = String(idx);
|
||||
}
|
||||
|
||||
const nameSpace = ast.ns || ctx.nameSpace;
|
||||
const dom = nameSpace
|
||||
? xmlDoc.createElementNS(nameSpace, ast.tag)
|
||||
: xmlDoc.createElement(ast.tag);
|
||||
const dom = xmlDoc.createElement(ast.tag);
|
||||
for (const [attr, val] of Object.entries(attrs)) {
|
||||
if (!(attr === "class" && val === "")) {
|
||||
dom.setAttribute(attr, val);
|
||||
@@ -785,7 +761,7 @@ export class CodeGenerator {
|
||||
if (!current) break;
|
||||
}
|
||||
}
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
|
||||
}
|
||||
}
|
||||
return block!.varName;
|
||||
@@ -801,8 +777,7 @@ export class CodeGenerator {
|
||||
expr = compileExpr(ast.expr);
|
||||
if (ast.defaultValue) {
|
||||
this.helpers.add("withDefault");
|
||||
// FIXME: defaultValue is not translated
|
||||
expr = `withDefault(${expr}, ${toStringExpression(ast.defaultValue)})`;
|
||||
expr = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
|
||||
}
|
||||
}
|
||||
if (!block || forceNewBlock) {
|
||||
@@ -888,7 +863,7 @@ export class CodeGenerator {
|
||||
if (!current) break;
|
||||
}
|
||||
}
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
|
||||
}
|
||||
|
||||
// note: this part is duplicated from end of compilemulti:
|
||||
@@ -919,18 +894,18 @@ export class CodeGenerator {
|
||||
}
|
||||
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
|
||||
this.target.indentLevel++;
|
||||
this.addLine(`ctx[\`${ast.elem}\`] = ${keys}[${loopVar}];`);
|
||||
this.addLine(`ctx[\`${ast.elem}\`] = ${vals}[${loopVar}];`);
|
||||
if (!ast.hasNoFirst) {
|
||||
this.addLine(`ctx[\`${ast.elem}_first\`] = ${loopVar} === 0;`);
|
||||
}
|
||||
if (!ast.hasNoLast) {
|
||||
this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${keys}.length - 1;`);
|
||||
this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${vals}.length - 1;`);
|
||||
}
|
||||
if (!ast.hasNoIndex) {
|
||||
this.addLine(`ctx[\`${ast.elem}_index\`] = ${loopVar};`);
|
||||
}
|
||||
if (!ast.hasNoValue) {
|
||||
this.addLine(`ctx[\`${ast.elem}_value\`] = ${vals}[${loopVar}];`);
|
||||
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
|
||||
}
|
||||
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
||||
if (this.dev) {
|
||||
@@ -995,7 +970,7 @@ export class CodeGenerator {
|
||||
const isNewBlock = !block || forceNewBlock;
|
||||
let codeIdx = this.target.code.length;
|
||||
if (isNewBlock) {
|
||||
const n = ast.content.filter((c) => !c.hasNoRepresentation).length;
|
||||
const n = ast.content.filter((c) => c.type !== ASTType.TSet).length;
|
||||
let result: string | null = null;
|
||||
if (n <= 1) {
|
||||
for (let child of ast.content) {
|
||||
@@ -1009,31 +984,34 @@ export class CodeGenerator {
|
||||
let index = 0;
|
||||
for (let i = 0, l = ast.content.length; i < l; i++) {
|
||||
const child = ast.content[i];
|
||||
const forceNewBlock = !child.hasNoRepresentation;
|
||||
const isTSet = child.type === ASTType.TSet;
|
||||
const subCtx = createContext(ctx, {
|
||||
block,
|
||||
index,
|
||||
forceNewBlock,
|
||||
forceNewBlock: !isTSet,
|
||||
preventRoot: ctx.preventRoot,
|
||||
isLast: ctx.isLast && i === l - 1,
|
||||
});
|
||||
this.compileAST(child, subCtx);
|
||||
if (forceNewBlock) {
|
||||
if (!isTSet) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
if (isNewBlock) {
|
||||
if (block!.hasDynamicChildren && block!.children.length) {
|
||||
const code = this.target.code;
|
||||
const children = block!.children.slice();
|
||||
let current = children.shift();
|
||||
for (let i = codeIdx; i < code.length; i++) {
|
||||
if (code[i].trimStart().startsWith(`const ${current!.varName} `)) {
|
||||
code[i] = code[i].replace(`const ${current!.varName}`, current!.varName);
|
||||
current = children.shift();
|
||||
if (!current) break;
|
||||
if (block!.hasDynamicChildren) {
|
||||
if (block!.children.length) {
|
||||
const code = this.target.code;
|
||||
const children = block!.children.slice();
|
||||
let current = children.shift();
|
||||
for (let i = codeIdx; i < code.length; i++) {
|
||||
if (code[i].trimStart().startsWith(`const ${current!.varName} `)) {
|
||||
code[i] = code[i].replace(`const ${current!.varName}`, current!.varName);
|
||||
current = children.shift();
|
||||
if (!current) break;
|
||||
}
|
||||
}
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
|
||||
}
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
||||
}
|
||||
|
||||
const args = block!.children.map((c) => c.varName).join(", ");
|
||||
@@ -1049,31 +1027,32 @@ export class CodeGenerator {
|
||||
ctxVar = generateId("ctx");
|
||||
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
|
||||
}
|
||||
const isDynamic = INTERP_REGEXP.test(ast.name);
|
||||
const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
|
||||
if (block && !forceNewBlock) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
if (ast.body) {
|
||||
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
|
||||
this.addLine(`${ctxVar}[isBoundary] = 1;`);
|
||||
this.helpers.add("isBoundary");
|
||||
const subCtx = createContext(ctx, { ctxVar });
|
||||
const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
|
||||
const bl = this.compileMulti({ type: ASTType.Multi, content: ast.body }, subCtx);
|
||||
if (bl) {
|
||||
this.helpers.add("zero");
|
||||
this.addLine(`${ctxVar}[zero] = ${bl};`);
|
||||
}
|
||||
}
|
||||
|
||||
const key = this.generateComponentKey();
|
||||
const isDynamic = INTERP_REGEXP.test(ast.name);
|
||||
const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
|
||||
if (block) {
|
||||
if (!forceNewBlock) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
}
|
||||
const key = `key + \`${this.generateComponentKey()}\``;
|
||||
if (isDynamic) {
|
||||
const templateVar = generateId("template");
|
||||
if (!this.staticDefs.find((d) => d.id === "call")) {
|
||||
this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
|
||||
}
|
||||
this.define(templateVar, subTemplate);
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block!, {
|
||||
...ctx,
|
||||
forceNewBlock: !block,
|
||||
@@ -1081,6 +1060,7 @@ export class CodeGenerator {
|
||||
} else {
|
||||
const id = generateId(`callTemplate_`);
|
||||
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block!, {
|
||||
...ctx,
|
||||
forceNewBlock: !block,
|
||||
@@ -1119,13 +1099,11 @@ export class CodeGenerator {
|
||||
} else {
|
||||
let value: string;
|
||||
if (ast.defaultValue) {
|
||||
const defaultValue = toStringExpression(
|
||||
ctx.translate ? this.translate(ast.defaultValue, ctx.translationCtx) : ast.defaultValue
|
||||
);
|
||||
const defaultValue = ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue;
|
||||
if (ast.value) {
|
||||
value = `withDefault(${expr}, ${defaultValue})`;
|
||||
value = `withDefault(${expr}, \`${defaultValue}\`)`;
|
||||
} else {
|
||||
value = defaultValue;
|
||||
value = `\`${defaultValue}\``;
|
||||
}
|
||||
} else {
|
||||
value = expr;
|
||||
@@ -1136,12 +1114,12 @@ export class CodeGenerator {
|
||||
return null;
|
||||
}
|
||||
|
||||
generateComponentKey(currentKey: string = "key") {
|
||||
generateComponentKey() {
|
||||
const parts = [generateId("__")];
|
||||
for (let i = 0; i < this.target.loopLevel; i++) {
|
||||
parts.push(`\${key${i + 1}}`);
|
||||
}
|
||||
return `${currentKey} + \`${parts.join("__")}\``;
|
||||
return parts.join("__");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1155,18 +1133,8 @@ export class CodeGenerator {
|
||||
* "some-prop" "state" "'some-prop': ctx['state']"
|
||||
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
||||
*/
|
||||
formatProp(
|
||||
name: string,
|
||||
value: string,
|
||||
attrsTranslationCtx: { [name: string]: string } | null,
|
||||
translationCtx: string
|
||||
): string {
|
||||
if (name.endsWith(".translate")) {
|
||||
const attrTranslationCtx = attrsTranslationCtx?.[name] || translationCtx;
|
||||
value = toStringExpression(this.translateFn(value, attrTranslationCtx));
|
||||
} else {
|
||||
value = this.captureExpression(value);
|
||||
}
|
||||
formatProp(name: string, value: string): string {
|
||||
value = this.captureExpression(value);
|
||||
if (name.includes(".")) {
|
||||
let [_name, suffix] = name.split(".");
|
||||
name = _name;
|
||||
@@ -1175,24 +1143,17 @@ export class CodeGenerator {
|
||||
value = `(${value}).bind(this)`;
|
||||
break;
|
||||
case "alike":
|
||||
case "translate":
|
||||
break;
|
||||
default:
|
||||
throw new OwlError(`Invalid prop suffix: ${suffix}`);
|
||||
throw new OwlError("Invalid prop suffix");
|
||||
}
|
||||
}
|
||||
name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
|
||||
return `${name}: ${value || undefined}`;
|
||||
}
|
||||
|
||||
formatPropObject(
|
||||
obj: { [prop: string]: any },
|
||||
attrsTranslationCtx: { [name: string]: string } | null,
|
||||
translationCtx: string
|
||||
): string[] {
|
||||
return Object.entries(obj).map(([k, v]) =>
|
||||
this.formatProp(k, v, attrsTranslationCtx, translationCtx)
|
||||
);
|
||||
formatPropObject(obj: { [prop: string]: any }): string[] {
|
||||
return Object.entries(obj).map(([k, v]) => this.formatProp(k, v));
|
||||
}
|
||||
|
||||
getPropString(props: string[], dynProps: string | null): string {
|
||||
@@ -1209,9 +1170,7 @@ export class CodeGenerator {
|
||||
let { block } = ctx;
|
||||
// props
|
||||
const hasSlotsProp = "slots" in (ast.props || {});
|
||||
const props: string[] = ast.props
|
||||
? this.formatPropObject(ast.props, ast.propsTranslationCtx, ctx.translationCtx)
|
||||
: [];
|
||||
const props: string[] = ast.props ? this.formatPropObject(ast.props) : [];
|
||||
|
||||
// slots
|
||||
let slotDef: string = "";
|
||||
@@ -1235,13 +1194,7 @@ export class CodeGenerator {
|
||||
params.push(`__scope: "${scope}"`);
|
||||
}
|
||||
if (ast.slots[slotName].attrs) {
|
||||
params.push(
|
||||
...this.formatPropObject(
|
||||
ast.slots[slotName].attrs!,
|
||||
ast.slots[slotName].attrsTranslationCtx,
|
||||
ctx.translationCtx
|
||||
)
|
||||
);
|
||||
params.push(...this.formatPropObject(ast.slots[slotName].attrs!));
|
||||
}
|
||||
const slotInfo = `{${params.join(", ")}}`;
|
||||
slotStr.push(`'${slotName}': ${slotInfo}`);
|
||||
@@ -1269,6 +1222,7 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
// cmap key
|
||||
const key = this.generateComponentKey();
|
||||
let expr: string;
|
||||
if (ast.isDynamic) {
|
||||
expr = generateId("Comp");
|
||||
@@ -1286,7 +1240,7 @@ export class CodeGenerator {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
|
||||
let keyArg = this.generateComponentKey();
|
||||
let keyArg = `key + \`${key}\``;
|
||||
if (ctx.tKeyExpr) {
|
||||
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
|
||||
}
|
||||
@@ -1359,17 +1313,16 @@ export class CodeGenerator {
|
||||
isMultiple = isMultiple || this.slotNames.has(ast.name);
|
||||
this.slotNames.add(ast.name);
|
||||
}
|
||||
const attrs = { ...ast.attrs };
|
||||
const dynProps = attrs["t-props"];
|
||||
delete attrs["t-props"];
|
||||
const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
|
||||
if (ast.attrs) {
|
||||
delete ast.attrs["t-props"];
|
||||
}
|
||||
let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
|
||||
if (isMultiple) {
|
||||
key = this.generateComponentKey(key);
|
||||
key = `${key} + \`${this.generateComponentKey()}\``;
|
||||
}
|
||||
|
||||
const props = ast.attrs
|
||||
? this.formatPropObject(attrs, ast.attrsTranslationCtx, ctx.translationCtx)
|
||||
: [];
|
||||
const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
|
||||
const scope = this.getPropString(props, dynProps);
|
||||
if (ast.defaultContent) {
|
||||
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
||||
@@ -1402,15 +1355,6 @@ export class CodeGenerator {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
compileTTranslationContext(ast: ASTTranslationContext, ctx: Context): string | null {
|
||||
if (ast.content) {
|
||||
return this.compileAST(
|
||||
ast.content,
|
||||
Object.assign({}, ctx, { translationCtx: ast.translationCtx })
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
compileTPortal(ast: ASTTPortal, ctx: Context): string {
|
||||
if (!this.staticDefs.find((d) => d.id === "Portal")) {
|
||||
this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
|
||||
@@ -1418,6 +1362,7 @@ export class CodeGenerator {
|
||||
|
||||
let { block } = ctx;
|
||||
const name = this.compileInNewTarget("slot", ast.content, ctx);
|
||||
const key = this.generateComponentKey();
|
||||
let ctxStr = "ctx";
|
||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||
ctxStr = generateId("ctx");
|
||||
@@ -1431,8 +1376,7 @@ export class CodeGenerator {
|
||||
});
|
||||
|
||||
const target = compileExpr(ast.target);
|
||||
const key = this.generateComponentKey();
|
||||
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, ${key}, node, ctx, Portal)`;
|
||||
const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
|
||||
if (block) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { customDirectives } from "../common/types";
|
||||
import type { TemplateSet } from "../runtime/template_set";
|
||||
import type { BDom } from "../runtime/blockdom";
|
||||
import { CodeGenerator, Config } from "./code_generator";
|
||||
import { parse } from "./parser";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
export type Template = (context: any, vnode: any, key?: string) => BDom;
|
||||
|
||||
@@ -11,17 +9,13 @@ export type TemplateFunction = (app: TemplateSet, bdom: any, helpers: any) => Te
|
||||
|
||||
interface CompileOptions extends Config {
|
||||
name?: string;
|
||||
customDirectives?: customDirectives;
|
||||
hasGlobalValues: boolean;
|
||||
}
|
||||
export function compile(
|
||||
template: string | Element,
|
||||
options: CompileOptions = {
|
||||
hasGlobalValues: false,
|
||||
}
|
||||
options: CompileOptions = {}
|
||||
): TemplateFunction {
|
||||
// parsing
|
||||
const ast = parse(template, options.customDirectives);
|
||||
const ast = parse(template);
|
||||
|
||||
// some work
|
||||
const hasSafeContext =
|
||||
@@ -33,15 +27,5 @@ export function compile(
|
||||
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
||||
const code = codeGenerator.generateCode();
|
||||
// template function
|
||||
try {
|
||||
return new Function("app, bdom, helpers", code) as TemplateFunction;
|
||||
} catch (originalError: any) {
|
||||
const { name } = options;
|
||||
const nameStr = name ? `template "${name}"` : "anonymous template";
|
||||
const err = new OwlError(
|
||||
`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`
|
||||
);
|
||||
err.cause = originalError;
|
||||
throw err;
|
||||
}
|
||||
return new Function("app, bdom, helpers", code) as TemplateFunction;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { OwlError } from "../runtime/error_handling";
|
||||
|
||||
/**
|
||||
* Owl QWeb Expression Parser
|
||||
@@ -28,7 +28,7 @@ import { OwlError } from "../common/owl_error";
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const RESERVED_WORDS =
|
||||
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__".split(
|
||||
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(
|
||||
","
|
||||
);
|
||||
|
||||
@@ -268,7 +268,7 @@ export function compileExprToArray(expr: string): Token[] {
|
||||
const localVars = new Set<string>();
|
||||
const tokens = tokenize(expr);
|
||||
let i = 0;
|
||||
let stack = []; // to track last opening (, [ or {
|
||||
let stack = []; // to track last opening [ or {
|
||||
|
||||
while (i < tokens.length) {
|
||||
let token = tokens[i];
|
||||
@@ -279,12 +279,10 @@ export function compileExprToArray(expr: string): Token[] {
|
||||
switch (token.type) {
|
||||
case "LEFT_BRACE":
|
||||
case "LEFT_BRACKET":
|
||||
case "LEFT_PAREN":
|
||||
stack.push(token.type);
|
||||
break;
|
||||
case "RIGHT_BRACE":
|
||||
case "RIGHT_BRACKET":
|
||||
case "RIGHT_PAREN":
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import type { customDirectives } from "../common/types";
|
||||
import { parseXML } from "../common/utils";
|
||||
import { OwlError } from "../runtime/error_handling";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// AST Type definition
|
||||
@@ -27,21 +25,15 @@ export const enum ASTType {
|
||||
TSlot,
|
||||
TCallBlock,
|
||||
TTranslation,
|
||||
TTranslationContext,
|
||||
TPortal,
|
||||
}
|
||||
|
||||
export interface BaseAST {
|
||||
type: ASTType;
|
||||
hasNoRepresentation?: true;
|
||||
}
|
||||
|
||||
export interface ASTText extends BaseAST {
|
||||
export interface ASTText {
|
||||
type: ASTType.Text;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ASTComment extends BaseAST {
|
||||
export interface ASTComment {
|
||||
type: ASTType.Comment;
|
||||
value: string;
|
||||
}
|
||||
@@ -57,12 +49,11 @@ interface TModelInfo {
|
||||
specialInitTargetAttr: string | null;
|
||||
}
|
||||
|
||||
export interface ASTDomNode extends BaseAST {
|
||||
export interface ASTDomNode {
|
||||
type: ASTType.DomNode;
|
||||
tag: string;
|
||||
content: AST[];
|
||||
attrs: Attrs | null;
|
||||
attrsTranslationCtx: Attrs | null;
|
||||
ref: string | null;
|
||||
on: EventHandlers | null;
|
||||
model: TModelInfo | null;
|
||||
@@ -70,24 +61,24 @@ export interface ASTDomNode extends BaseAST {
|
||||
ns: string | null;
|
||||
}
|
||||
|
||||
export interface ASTMulti extends BaseAST {
|
||||
export interface ASTMulti {
|
||||
type: ASTType.Multi;
|
||||
content: AST[];
|
||||
}
|
||||
|
||||
export interface ASTTEsc extends BaseAST {
|
||||
export interface ASTTEsc {
|
||||
type: ASTType.TEsc;
|
||||
expr: string;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
export interface ASTTOut extends BaseAST {
|
||||
export interface ASTTOut {
|
||||
type: ASTType.TOut;
|
||||
expr: string;
|
||||
body: AST[] | null;
|
||||
}
|
||||
|
||||
export interface ASTTif extends BaseAST {
|
||||
export interface ASTTif {
|
||||
type: ASTType.TIf;
|
||||
condition: string;
|
||||
content: AST;
|
||||
@@ -95,16 +86,15 @@ export interface ASTTif extends BaseAST {
|
||||
tElse: AST | null;
|
||||
}
|
||||
|
||||
export interface ASTTSet extends BaseAST {
|
||||
export interface ASTTSet {
|
||||
type: ASTType.TSet;
|
||||
name: string;
|
||||
value: string | null; // value defined in attribute
|
||||
defaultValue: string | null; // value defined in body, if text
|
||||
body: AST[] | null; // content of body if not text
|
||||
hasNoRepresentation: true;
|
||||
}
|
||||
|
||||
export interface ASTTForEach extends BaseAST {
|
||||
export interface ASTTForEach {
|
||||
type: ASTType.TForEach;
|
||||
collection: string;
|
||||
elem: string;
|
||||
@@ -117,13 +107,13 @@ export interface ASTTForEach extends BaseAST {
|
||||
key: string | null;
|
||||
}
|
||||
|
||||
export interface ASTTKey extends BaseAST {
|
||||
export interface ASTTKey {
|
||||
type: ASTType.TKey;
|
||||
expr: string;
|
||||
content: AST;
|
||||
}
|
||||
|
||||
export interface ASTTCall extends BaseAST {
|
||||
export interface ASTTCall {
|
||||
type: ASTType.TCall;
|
||||
name: string;
|
||||
body: AST[] | null;
|
||||
@@ -135,57 +125,48 @@ interface SlotDefinition {
|
||||
scope: string | null;
|
||||
on: EventHandlers | null;
|
||||
attrs: Attrs | null;
|
||||
attrsTranslationCtx: Attrs | null;
|
||||
}
|
||||
|
||||
export interface ASTComponent extends BaseAST {
|
||||
export interface ASTComponent {
|
||||
type: ASTType.TComponent;
|
||||
name: string;
|
||||
isDynamic: boolean;
|
||||
dynamicProps: string | null;
|
||||
on: EventHandlers | null;
|
||||
props: { [name: string]: string } | null;
|
||||
propsTranslationCtx: { [name: string]: string } | null;
|
||||
slots: { [name: string]: SlotDefinition } | null;
|
||||
}
|
||||
|
||||
export interface ASTSlot extends BaseAST {
|
||||
export interface ASTSlot {
|
||||
type: ASTType.TSlot;
|
||||
name: string;
|
||||
attrs: Attrs | null;
|
||||
attrsTranslationCtx: Attrs | null;
|
||||
on: EventHandlers | null;
|
||||
defaultContent: AST | null;
|
||||
}
|
||||
|
||||
export interface ASTTCallBlock extends BaseAST {
|
||||
export interface ASTTCallBlock {
|
||||
type: ASTType.TCallBlock;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ASTDebug extends BaseAST {
|
||||
export interface ASTDebug {
|
||||
type: ASTType.TDebug;
|
||||
content: AST | null;
|
||||
}
|
||||
|
||||
export interface ASTLog extends BaseAST {
|
||||
export interface ASTLog {
|
||||
type: ASTType.TLog;
|
||||
expr: string;
|
||||
content: AST | null;
|
||||
}
|
||||
|
||||
export interface ASTTranslation extends BaseAST {
|
||||
export interface ASTTranslation {
|
||||
type: ASTType.TTranslation;
|
||||
content: AST | null;
|
||||
}
|
||||
|
||||
export interface ASTTranslationContext extends BaseAST {
|
||||
type: ASTType.TTranslationContext;
|
||||
content: AST | null;
|
||||
translationCtx: string;
|
||||
}
|
||||
|
||||
export interface ASTTPortal extends BaseAST {
|
||||
export interface ASTTPortal {
|
||||
type: ASTType.TPortal;
|
||||
target: string;
|
||||
content: AST;
|
||||
@@ -209,7 +190,6 @@ export type AST =
|
||||
| ASTLog
|
||||
| ASTDebug
|
||||
| ASTTranslation
|
||||
| ASTTranslationContext
|
||||
| ASTTPortal;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -217,34 +197,30 @@ export type AST =
|
||||
// -----------------------------------------------------------------------------
|
||||
const cache: WeakMap<Element, AST> = new WeakMap();
|
||||
|
||||
export function parse(xml: string | Element, customDir?: customDirectives): AST {
|
||||
const ctx = {
|
||||
inPreTag: false,
|
||||
customDirectives: customDir,
|
||||
};
|
||||
export function parse(xml: string | Element): AST {
|
||||
if (typeof xml === "string") {
|
||||
const elem = parseXML(`<t>${xml}</t>`).firstChild as Element;
|
||||
return _parse(elem, ctx);
|
||||
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, ctx);
|
||||
ast = _parse(xml.cloneNode(true) as Element);
|
||||
cache.set(xml, ast);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
function _parse(xml: Element, ctx: ParsingContext): 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;
|
||||
nameSpace?: string;
|
||||
inPreTag: boolean;
|
||||
customDirectives?: customDirectives;
|
||||
inSVG: boolean;
|
||||
}
|
||||
|
||||
function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
@@ -252,18 +228,16 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
return parseTextCommentNode(node, ctx);
|
||||
}
|
||||
return (
|
||||
parseTCustom(node, ctx) ||
|
||||
parseTDebugLog(node, ctx) ||
|
||||
parseTForEach(node, ctx) ||
|
||||
parseTIf(node, ctx) ||
|
||||
parseTPortal(node, ctx) ||
|
||||
parseTCall(node, ctx) ||
|
||||
parseTCallBlock(node, ctx) ||
|
||||
parseTTranslation(node, ctx) ||
|
||||
parseTTranslationContext(node, ctx) ||
|
||||
parseTKey(node, ctx) ||
|
||||
parseTEscNode(node, ctx) ||
|
||||
parseTOutNode(node, ctx) ||
|
||||
parseTKey(node, ctx) ||
|
||||
parseTTranslation(node, ctx) ||
|
||||
parseTSlot(node, ctx) ||
|
||||
parseComponent(node, ctx) ||
|
||||
parseDOMNode(node, ctx) ||
|
||||
@@ -302,37 +276,6 @@ function parseTextCommentNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseTCustom(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!ctx.customDirectives) {
|
||||
return null;
|
||||
}
|
||||
const nodeAttrsNames = node.getAttributeNames();
|
||||
for (let attr of nodeAttrsNames) {
|
||||
if (attr === "t-custom" || attr === "t-custom-") {
|
||||
throw new OwlError("Missing custom directive name with t-custom directive");
|
||||
}
|
||||
if (attr.startsWith("t-custom-")) {
|
||||
const directiveName = attr.split(".")[0].slice(9);
|
||||
const customDirective = ctx.customDirectives[directiveName];
|
||||
if (!customDirective) {
|
||||
throw new OwlError(`Custom directive "${directiveName}" is not defined`);
|
||||
}
|
||||
const value = node.getAttribute(attr)!;
|
||||
const modifiers = attr.split(".").slice(1);
|
||||
node.removeAttribute(attr);
|
||||
try {
|
||||
customDirective(node, value, modifiers);
|
||||
} catch (error) {
|
||||
throw new OwlError(
|
||||
`Custom directive "${directiveName}" throw the following error: ${error}`
|
||||
);
|
||||
}
|
||||
return parseNode(node, ctx);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// debugging
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -340,30 +283,20 @@ function parseTCustom(node: Element, ctx: ParsingContext): AST | null {
|
||||
function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (node.hasAttribute("t-debug")) {
|
||||
node.removeAttribute("t-debug");
|
||||
const content = parseNode(node, ctx);
|
||||
const ast: ASTDebug = {
|
||||
return {
|
||||
type: ASTType.TDebug,
|
||||
content,
|
||||
content: parseNode(node, ctx),
|
||||
};
|
||||
if (content?.hasNoRepresentation) {
|
||||
ast.hasNoRepresentation = true;
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
if (node.hasAttribute("t-log")) {
|
||||
const expr = node.getAttribute("t-log")!;
|
||||
node.removeAttribute("t-log");
|
||||
const content = parseNode(node, ctx);
|
||||
const ast: ASTLog = {
|
||||
return {
|
||||
type: ASTType.TLog,
|
||||
expr,
|
||||
content,
|
||||
content: parseNode(node, ctx),
|
||||
};
|
||||
if (content?.hasNoRepresentation) {
|
||||
ast.hasNoRepresentation = true;
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -390,14 +323,14 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (tagName === "pre") {
|
||||
ctx.inPreTag = true;
|
||||
}
|
||||
|
||||
let ns = !ctx.nameSpace && ROOT_SVG_TAGS.has(tagName) ? "http://www.w3.org/2000/svg" : null;
|
||||
const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !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 nodeAttrsNames = node.getAttributeNames();
|
||||
let attrs: ASTDomNode["attrs"] = null;
|
||||
let attrsTranslationCtx: ASTDomNode["attrsTranslationCtx"] = null;
|
||||
let on: EventHandlers | null = null;
|
||||
let model: TModelInfo | null = null;
|
||||
|
||||
@@ -434,9 +367,9 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
const isSelect = tagName === "select";
|
||||
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
||||
const isRadioInput = isInput && typeAttr === "radio";
|
||||
const hasTrimMod = attr.includes(".trim");
|
||||
const hasLazyMod = hasTrimMod || attr.includes(".lazy");
|
||||
const hasLazyMod = attr.includes(".lazy");
|
||||
const hasNumberMod = attr.includes(".number");
|
||||
const hasTrimMod = attr.includes(".trim");
|
||||
const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
|
||||
|
||||
model = {
|
||||
@@ -456,12 +389,6 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
}
|
||||
} else if (attr.startsWith("block-")) {
|
||||
throw new OwlError(`Invalid attribute: '${attr}'`);
|
||||
} else if (attr === "xmlns") {
|
||||
ns = value;
|
||||
} else if (attr.startsWith("t-translation-context-")) {
|
||||
const attrName = attr.slice(22);
|
||||
attrsTranslationCtx = attrsTranslationCtx || {};
|
||||
attrsTranslationCtx[attrName] = value;
|
||||
} else if (attr !== "t-name") {
|
||||
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
||||
throw new OwlError(`Unknown QWeb directive: '${attr}'`);
|
||||
@@ -474,9 +401,6 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
attrs[attr] = value;
|
||||
}
|
||||
}
|
||||
if (ns) {
|
||||
ctx.nameSpace = ns;
|
||||
}
|
||||
|
||||
const children = parseChildren(node, ctx);
|
||||
return {
|
||||
@@ -484,7 +408,6 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
tag: tagName,
|
||||
dynamicTag,
|
||||
attrs,
|
||||
attrsTranslationCtx,
|
||||
on,
|
||||
ref,
|
||||
content: children,
|
||||
@@ -614,19 +537,11 @@ function parseTKey(node: Element, ctx: ParsingContext): AST | null {
|
||||
}
|
||||
const key = node.getAttribute("t-key")!;
|
||||
node.removeAttribute("t-key");
|
||||
const content = parseNode(node, ctx);
|
||||
if (!content) {
|
||||
const body = parseNode(node, ctx);
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
const ast: ASTTKey = {
|
||||
type: ASTType.TKey,
|
||||
expr: key,
|
||||
content,
|
||||
};
|
||||
if (content.hasNoRepresentation) {
|
||||
ast.hasNoRepresentation = true;
|
||||
}
|
||||
return ast;
|
||||
return { type: ASTType.TKey, expr: key, content: body };
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -652,15 +567,7 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (ast && ast.type === ASTType.TComponent) {
|
||||
return {
|
||||
...ast,
|
||||
slots: {
|
||||
default: {
|
||||
content: tcall,
|
||||
scope: null,
|
||||
on: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
},
|
||||
},
|
||||
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -748,7 +655,7 @@ function parseTSetNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (node.textContent !== node.innerHTML) {
|
||||
body = parseChildren(node, ctx);
|
||||
}
|
||||
return { type: ASTType.TSet, name, value, defaultValue, body, hasNoRepresentation: true };
|
||||
return { type: ASTType.TSet, name, value, defaultValue, body };
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -795,14 +702,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
||||
let on: ASTComponent["on"] = null;
|
||||
|
||||
let props: ASTComponent["props"] = null;
|
||||
let propsTranslationCtx: ASTComponent["propsTranslationCtx"] = null;
|
||||
for (let name of node.getAttributeNames()) {
|
||||
const value = node.getAttribute(name)!;
|
||||
if (name.startsWith("t-translation-context-")) {
|
||||
const attrName = name.slice(22);
|
||||
propsTranslationCtx = propsTranslationCtx || {};
|
||||
propsTranslationCtx[attrName] = value;
|
||||
} else if (name.startsWith("t-")) {
|
||||
if (name.startsWith("t-")) {
|
||||
if (name.startsWith("t-on-")) {
|
||||
on = on || {};
|
||||
on[name.slice(5)] = value;
|
||||
@@ -834,14 +736,14 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
||||
// be ignored)
|
||||
let el = slotNode.parentElement!;
|
||||
let isInSubComponent = false;
|
||||
while (el && el !== clone) {
|
||||
while (el !== clone) {
|
||||
if (el!.hasAttribute("t-component") || el!.tagName[0] === el!.tagName[0].toUpperCase()) {
|
||||
isInSubComponent = true;
|
||||
break;
|
||||
}
|
||||
el = el.parentElement!;
|
||||
}
|
||||
if (isInSubComponent || !el) {
|
||||
if (isInSubComponent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -850,17 +752,12 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
||||
const slotAst = parseNode(slotNode, ctx);
|
||||
let on: SlotDefinition["on"] = null;
|
||||
let attrs: Attrs | null = null;
|
||||
let attrsTranslationCtx: Attrs | null = null;
|
||||
let scope: string | null = null;
|
||||
for (let attributeName of slotNode.getAttributeNames()) {
|
||||
const value = slotNode.getAttribute(attributeName)!;
|
||||
if (attributeName === "t-slot-scope") {
|
||||
scope = value;
|
||||
continue;
|
||||
} else if (attributeName.startsWith("t-translation-context-")) {
|
||||
const attrName = attributeName.slice(22);
|
||||
attrsTranslationCtx = attrsTranslationCtx || {};
|
||||
attrsTranslationCtx[attrName] = value;
|
||||
} else if (attributeName.startsWith("t-on-")) {
|
||||
on = on || {};
|
||||
on[attributeName.slice(5)] = value;
|
||||
@@ -870,7 +767,7 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
||||
}
|
||||
}
|
||||
slots = slots || {};
|
||||
slots[name] = { content: slotAst, on, attrs, attrsTranslationCtx, scope };
|
||||
slots[name] = { content: slotAst, on, attrs, scope };
|
||||
}
|
||||
|
||||
// default slot
|
||||
@@ -878,25 +775,10 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
||||
slots = slots || {};
|
||||
// t-set-slot="default" has priority over content
|
||||
if (defaultContent && !slots.default) {
|
||||
slots.default = {
|
||||
content: defaultContent,
|
||||
on,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
scope: defaultSlotScope,
|
||||
};
|
||||
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: ASTType.TComponent,
|
||||
name,
|
||||
isDynamic,
|
||||
dynamicProps,
|
||||
props,
|
||||
propsTranslationCtx,
|
||||
slots,
|
||||
on,
|
||||
};
|
||||
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots, on };
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -910,17 +792,12 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
|
||||
const name = node.getAttribute("t-slot")!;
|
||||
node.removeAttribute("t-slot");
|
||||
let attrs: Attrs | null = null;
|
||||
let attrsTranslationCtx: Attrs | null = null;
|
||||
let on: ASTComponent["on"] = null;
|
||||
for (let attributeName of node.getAttributeNames()) {
|
||||
const value = node.getAttribute(attributeName)!;
|
||||
if (attributeName.startsWith("t-on-")) {
|
||||
on = on || {};
|
||||
on[attributeName.slice(5)] = value;
|
||||
} else if (attributeName.startsWith("t-translation-context-")) {
|
||||
const attrName = attributeName.slice(22);
|
||||
attrsTranslationCtx = attrsTranslationCtx || {};
|
||||
attrsTranslationCtx[attrName] = value;
|
||||
} else {
|
||||
attrs = attrs || {};
|
||||
attrs[attributeName] = value;
|
||||
@@ -930,65 +807,20 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
|
||||
type: ASTType.TSlot,
|
||||
name,
|
||||
attrs,
|
||||
attrsTranslationCtx,
|
||||
on,
|
||||
defaultContent: parseChildNodes(node, ctx),
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Translation
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function wrapInTTranslationAST(r: AST | null) {
|
||||
const ast: ASTTranslation = { type: ASTType.TTranslation, content: r };
|
||||
if (r?.hasNoRepresentation) {
|
||||
ast.hasNoRepresentation = true;
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (node.getAttribute("t-translation") !== "off") {
|
||||
return null;
|
||||
}
|
||||
node.removeAttribute("t-translation");
|
||||
const result = parseNode(node, ctx);
|
||||
if (result?.type === ASTType.Multi) {
|
||||
const children = result.content.map(wrapInTTranslationAST);
|
||||
return makeASTMulti(children);
|
||||
}
|
||||
return wrapInTTranslationAST(result);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Translation Context
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function wrapInTTranslationContextAST(r: AST | null, translationCtx: string) {
|
||||
const ast: ASTTranslationContext = {
|
||||
type: ASTType.TTranslationContext,
|
||||
content: r,
|
||||
translationCtx,
|
||||
return {
|
||||
type: ASTType.TTranslation,
|
||||
content: parseNode(node, ctx),
|
||||
};
|
||||
if (r?.hasNoRepresentation) {
|
||||
ast.hasNoRepresentation = true;
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
function parseTTranslationContext(node: Element, ctx: ParsingContext): AST | null {
|
||||
const translationCtx = node.getAttribute("t-translation-context");
|
||||
if (!translationCtx) {
|
||||
return null;
|
||||
}
|
||||
node.removeAttribute("t-translation-context");
|
||||
const result = parseNode(node, ctx);
|
||||
if (result?.type === ASTType.Multi) {
|
||||
const children = result.content.map((c) => wrapInTTranslationContextAST(c, translationCtx));
|
||||
return makeASTMulti(children);
|
||||
}
|
||||
return wrapInTTranslationContextAST(result, translationCtx);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -1037,14 +869,6 @@ function parseChildren(node: Element, ctx: ParsingContext): AST[] {
|
||||
return children;
|
||||
}
|
||||
|
||||
function makeASTMulti(children: AST[]) {
|
||||
const ast: ASTMulti = { type: ASTType.Multi, content: children };
|
||||
if (children.every((c) => c.hasNoRepresentation)) {
|
||||
ast.hasNoRepresentation = true;
|
||||
}
|
||||
return 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.
|
||||
@@ -1057,7 +881,7 @@ function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
|
||||
case 1:
|
||||
return children[0];
|
||||
default:
|
||||
return makeASTMulti(children);
|
||||
return { type: ASTType.Multi, content: children };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1144,3 +968,40 @@ function normalizeXML(el: Element) {
|
||||
normalizeTIf(el);
|
||||
normalizeTEscTOut(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an XML string into an XML document, throwing errors on parser errors
|
||||
* instead of returning an XML document containing the parseerror.
|
||||
*
|
||||
* @param xml the string to parse
|
||||
* @returns an XML document corresponding to the content of the string
|
||||
*/
|
||||
function parseXML(xml: string): XMLDocument {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(xml, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
let msg = "Invalid XML in template.";
|
||||
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
||||
if (parsererrorText) {
|
||||
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
||||
const re = /\d+/g;
|
||||
const firstMatch = re.exec(parsererrorText);
|
||||
if (firstMatch) {
|
||||
const lineNumber = Number(firstMatch[0]);
|
||||
const line = xml.split("\n")[lineNumber - 1];
|
||||
const secondMatch = re.exec(parsererrorText);
|
||||
if (line && secondMatch) {
|
||||
const columnIndex = Number(secondMatch[0]) - 1;
|
||||
if (line[columnIndex]) {
|
||||
msg +=
|
||||
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
||||
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new OwlError(msg);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file exports a function that allows compiling templates ahead of time.
|
||||
// It is used by the "compile_owl_template" command registered in the "bin"
|
||||
// section of owl's package.json
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import { readdir, readFile, stat } from "fs/promises";
|
||||
import path from "path";
|
||||
import "./setup_jsdom";
|
||||
// Owl imports must be made after setting up jsdom in the global namespace
|
||||
import { compile } from "..";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
async function getXmlFiles(paths: string[]): Promise<string[]> {
|
||||
return (
|
||||
await Promise.all(
|
||||
paths.map(async (file) => {
|
||||
const stats = await stat(path.join(file));
|
||||
if (stats.isDirectory()) {
|
||||
return await getXmlFiles(
|
||||
(await readdir(file)).map((fileName) => path.join(file, fileName))
|
||||
);
|
||||
}
|
||||
if (file.endsWith(".xml")) {
|
||||
return file;
|
||||
}
|
||||
return [];
|
||||
})
|
||||
)
|
||||
).flat();
|
||||
}
|
||||
|
||||
// adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
|
||||
const a = "·-_,:;";
|
||||
const p = new RegExp(a.split("").join("|"), "g");
|
||||
|
||||
function slugify(str: string) {
|
||||
return str
|
||||
.replace(/\//g, "") // remove /
|
||||
.replace(/\./g, "_") // Replace . with _
|
||||
.replace(p, (c) => "_") // Replace special characters
|
||||
.replace(/&/g, "_and_") // Replace & with ‘and’
|
||||
.replace(/[^\w\-]+/g, ""); // Remove all non-word characters
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// main
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export async function compileTemplates(paths: string[]) {
|
||||
const files = await getXmlFiles(paths);
|
||||
process.stdout.write(`Processing ${files.length} files`);
|
||||
let xmlStrings = await Promise.all(files.map((file) => readFile(file, "utf8")));
|
||||
|
||||
const templates = [];
|
||||
const errors = [];
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const fileName = files[i];
|
||||
const fileContent = xmlStrings[i];
|
||||
process.stdout.write(`.`);
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(fileContent, "text/xml");
|
||||
for (const template of doc.querySelectorAll("[t-name]")) {
|
||||
const name = template.getAttribute("t-name");
|
||||
if (template.hasAttribute("owl")) {
|
||||
template.removeAttribute("owl");
|
||||
}
|
||||
const fnName = slugify(name!);
|
||||
try {
|
||||
const fn = compile(template).toString().replace("anonymous", fnName);
|
||||
templates.push(`"${name}": ${fn},\n`);
|
||||
} catch (e) {
|
||||
errors.push({ name, fileName, e });
|
||||
}
|
||||
}
|
||||
}
|
||||
process.stdout.write(`\n`);
|
||||
|
||||
for (let { name, fileName, e } of errors) {
|
||||
console.warn(`Error while compiling '${name}' (in file ${fileName})`);
|
||||
console.error(e);
|
||||
}
|
||||
console.log(`${templates.length} templates compiled`);
|
||||
|
||||
return `export const templates = {\n ${templates.join("\n")} \n}`;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import jsdom from "jsdom";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// add global DOM stuff for compiler. Needs to be in a separate file so rollup
|
||||
// doesn't hoist the owl imports above this block of code.
|
||||
// -----------------------------------------------------------------------------
|
||||
var document = new jsdom.JSDOM("", {});
|
||||
var window = document.window;
|
||||
global.document = window.document;
|
||||
global.window = window as unknown as Window & typeof globalThis;
|
||||
global.DOMParser = window.DOMParser;
|
||||
global.Element = window.Element;
|
||||
global.Node = window.Node;
|
||||
@@ -12,7 +12,5 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(
|
||||
dev: this.dev,
|
||||
translateFn: this.translateFn,
|
||||
translatableAttributes: this.translatableAttributes,
|
||||
customDirectives: this.customDirectives,
|
||||
hasGlobalValues: this.hasGlobalValues,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { version } from "../version";
|
||||
import { Component, ComponentConstructor, Props } from "./component";
|
||||
import { ComponentNode, saveCurrent } from "./component_node";
|
||||
import { nodeErrorHandlers, handleError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { ComponentNode } from "./component_node";
|
||||
import { nodeErrorHandlers, OwlError, handleError } from "./error_handling";
|
||||
import { Fiber, RootFiber, MountOptions } from "./fibers";
|
||||
import { Scheduler } from "./scheduler";
|
||||
import { validateProps } from "./template_helpers";
|
||||
@@ -16,20 +15,24 @@ export interface Env {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface RootConfig<P, E> {
|
||||
export interface AppConfig<P, E> extends TemplateSetConfig {
|
||||
name?: string;
|
||||
props?: P;
|
||||
env?: E;
|
||||
}
|
||||
|
||||
export interface AppConfig<P, E> extends TemplateSetConfig, RootConfig<P, E> {
|
||||
name?: string;
|
||||
test?: boolean;
|
||||
warnIfNoStaticProps?: boolean;
|
||||
}
|
||||
|
||||
let hasBeenLogged = false;
|
||||
|
||||
const apps = new Set<App>();
|
||||
export const DEV_MSG = () => {
|
||||
const hash = (window as any).owl ? (window as any).owl.__info__.hash : "master";
|
||||
|
||||
return `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.`;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -43,13 +46,13 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
interface Root<P extends Props, E> {
|
||||
node: ComponentNode<P, E>;
|
||||
mount(target: HTMLElement | ShadowRoot, options?: MountOptions): Promise<Component<P, E>>;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, reactive };
|
||||
window.__OWL_DEVTOOLS__ ||= {
|
||||
apps: new Set<App>(),
|
||||
Fiber: Fiber,
|
||||
RootFiber: RootFiber,
|
||||
toRaw: toRaw,
|
||||
reactive: reactive,
|
||||
};
|
||||
|
||||
export class App<
|
||||
T extends abstract new (...args: any) => any = any,
|
||||
@@ -57,7 +60,6 @@ export class App<
|
||||
E = any
|
||||
> extends TemplateSet {
|
||||
static validateTarget = validateTarget;
|
||||
static apps = apps;
|
||||
static version = version;
|
||||
|
||||
name: string;
|
||||
@@ -65,7 +67,6 @@ export class App<
|
||||
props: P;
|
||||
env: E;
|
||||
scheduler = new Scheduler();
|
||||
subRoots: Set<ComponentNode> = new Set();
|
||||
root: ComponentNode<P, E> | null = null;
|
||||
warnIfNoStaticProps: boolean;
|
||||
|
||||
@@ -73,13 +74,13 @@ export class App<
|
||||
super(config);
|
||||
this.name = config.name || "";
|
||||
this.Root = Root;
|
||||
apps.add(this);
|
||||
window.__OWL_DEVTOOLS__.apps.add(this);
|
||||
if (config.test) {
|
||||
this.dev = true;
|
||||
}
|
||||
this.warnIfNoStaticProps = config.warnIfNoStaticProps || false;
|
||||
if (this.dev && !config.test && !hasBeenLogged) {
|
||||
console.info(`Owl is running in 'dev' mode.`);
|
||||
console.info(DEV_MSG());
|
||||
hasBeenLogged = true;
|
||||
}
|
||||
const env = config.env || {};
|
||||
@@ -92,49 +93,14 @@ export class App<
|
||||
target: HTMLElement | ShadowRoot,
|
||||
options?: MountOptions
|
||||
): Promise<Component<P, E> & InstanceType<T>> {
|
||||
const root = this.createRoot(this.Root, { props: this.props });
|
||||
this.root = root.node;
|
||||
this.subRoots.delete(root.node);
|
||||
return root.mount(target, options) as any;
|
||||
}
|
||||
|
||||
createRoot<Props extends object, SubEnv = any>(
|
||||
Root: ComponentConstructor<Props, E>,
|
||||
config: RootConfig<Props, SubEnv> = {}
|
||||
): Root<Props, SubEnv> {
|
||||
const props = config.props || ({} as Props);
|
||||
// hack to make sure the sub root get the sub env if necessary. for owl 3,
|
||||
// would be nice to rethink the initialization process to make sure that
|
||||
// we can create a ComponentNode and give it explicitely the env, instead
|
||||
// of looking it up in the app
|
||||
const env = this.env;
|
||||
if (config.env) {
|
||||
this.env = config.env as any;
|
||||
App.validateTarget(target);
|
||||
if (this.dev) {
|
||||
validateProps(this.Root, this.props, { __owl__: { app: this } });
|
||||
}
|
||||
|
||||
const restore = saveCurrent();
|
||||
const node = this.makeNode(Root, props);
|
||||
restore();
|
||||
if (config.env) {
|
||||
this.env = env;
|
||||
}
|
||||
this.subRoots.add(node);
|
||||
return {
|
||||
node,
|
||||
mount: (target: HTMLElement | ShadowRoot, options?: MountOptions) => {
|
||||
App.validateTarget(target);
|
||||
if (this.dev) {
|
||||
validateProps(Root, props, { __owl__: { app: this } });
|
||||
}
|
||||
const prom = this.mountNode(node, target, options);
|
||||
return prom;
|
||||
},
|
||||
destroy: () => {
|
||||
this.subRoots.delete(node);
|
||||
node.destroy();
|
||||
this.scheduler.processTasks();
|
||||
},
|
||||
};
|
||||
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 {
|
||||
@@ -170,13 +136,10 @@ export class App<
|
||||
|
||||
destroy() {
|
||||
if (this.root) {
|
||||
for (let subroot of this.subRoots) {
|
||||
subroot.destroy();
|
||||
}
|
||||
this.scheduler.flush();
|
||||
this.root.destroy();
|
||||
this.scheduler.processTasks();
|
||||
}
|
||||
apps.delete(this);
|
||||
window.__OWL_DEVTOOLS__.apps.delete(this);
|
||||
}
|
||||
|
||||
createComponent<P extends Props>(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "../../common/owl_error";
|
||||
import { OwlError } from "../error_handling";
|
||||
import { attrsSetter, attrsUpdater, createAttrUpdater, setClass, updateClass } from "./attributes";
|
||||
import { config } from "./config";
|
||||
import { createEventHandler } from "./events";
|
||||
@@ -144,7 +144,12 @@ function buildTree(
|
||||
info.push({ type: "child", idx: index });
|
||||
el = document.createTextNode("");
|
||||
}
|
||||
currentNS ||= (node as Element).namespaceURI;
|
||||
const attrs = (node as Element).attributes;
|
||||
const ns = attrs.getNamedItem("block-ns");
|
||||
if (ns) {
|
||||
attrs.removeNamedItem("block-ns");
|
||||
currentNS = ns.value;
|
||||
}
|
||||
if (!el) {
|
||||
el = currentNS
|
||||
? document.createElementNS(currentNS, tagName)
|
||||
@@ -160,7 +165,6 @@ function buildTree(
|
||||
const fragment = document.createElement("template").content;
|
||||
fragment.appendChild(el);
|
||||
}
|
||||
const attrs = (node as Element).attributes;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attrName = attrs[i].name;
|
||||
const attrValue = attrs[i].value;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
export type TaskContext = { isCancelled: boolean; cancel: () => void; meta: Record<string, any> };
|
||||
|
||||
export const taskContextStack: TaskContext[] = [];
|
||||
|
||||
export function getTaskContext() {
|
||||
return taskContextStack[taskContextStack.length - 1];
|
||||
}
|
||||
|
||||
export function makeTaskContext(): TaskContext {
|
||||
let isCancelled = false;
|
||||
return {
|
||||
get isCancelled() {
|
||||
return isCancelled;
|
||||
},
|
||||
cancel() {
|
||||
isCancelled = true;
|
||||
},
|
||||
meta: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function useTaskContext(ctx?: TaskContext) {
|
||||
ctx ??= makeTaskContext();
|
||||
taskContextStack.push(ctx);
|
||||
return {
|
||||
ctx,
|
||||
cleanup: () => {
|
||||
taskContextStack.pop();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function pushTaskContext(context: TaskContext) {
|
||||
taskContextStack.push(context);
|
||||
}
|
||||
|
||||
export function popTaskContext() {
|
||||
taskContextStack.pop();
|
||||
}
|
||||
|
||||
export function taskEffect(fn: Function) {
|
||||
const { ctx, cleanup } = useTaskContext();
|
||||
fn();
|
||||
cleanup();
|
||||
return ctx;
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export type ComponentConstructor<P extends Props = any, E = any> = (new (
|
||||
|
||||
export class Component<Props = any, Env = any> {
|
||||
static template: string = "";
|
||||
static props?: Schema;
|
||||
static props?: any;
|
||||
static defaultProps?: any;
|
||||
|
||||
props: Props;
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { Atom, ExecutionContext } from "../common/types";
|
||||
import type { App, Env } from "./app";
|
||||
import { BDom, VNode } from "./blockdom";
|
||||
import { makeTaskContext, TaskContext } from "./cancellableContext";
|
||||
import { Component, ComponentConstructor, Props } from "./component";
|
||||
import { fibersInError } from "./error_handling";
|
||||
import { fibersInError, OwlError } from "./error_handling";
|
||||
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
|
||||
import { addAtomToContext, reactive, targets, withoutReactivity } from "./reactivity";
|
||||
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
|
||||
import { STATUS } from "./status";
|
||||
import { batched, Callback } from "./utils";
|
||||
|
||||
let currentNode: ComponentNode | null = null;
|
||||
|
||||
export function saveCurrent() {
|
||||
let n = currentNode;
|
||||
return () => {
|
||||
currentNode = n;
|
||||
};
|
||||
}
|
||||
|
||||
export function getCurrent(): ComponentNode {
|
||||
if (!currentNode) {
|
||||
throw new OwlError("No active component (a hook function should only be called in 'setup')");
|
||||
@@ -43,7 +34,7 @@ function applyDefaultProps<P extends object>(props: P, defaultProps: Partial<P>)
|
||||
// Integration with reactivity system (useState)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
||||
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
||||
/**
|
||||
* Creates a reactive object that will be observed by the current component.
|
||||
* Reading data from the returned object (eg during rendering) will cause the
|
||||
@@ -55,7 +46,15 @@ function applyDefaultProps<P extends object>(props: P, defaultProps: Partial<P>)
|
||||
* @see reactive
|
||||
*/
|
||||
export function useState<T extends object>(state: T): T {
|
||||
return reactive(state);
|
||||
const node = getCurrent();
|
||||
let render = batchedRenderFunctions.get(node)!;
|
||||
if (!render) {
|
||||
render = batched(node.render.bind(node, false));
|
||||
batchedRenderFunctions.set(node, render);
|
||||
// manual implementation of onWillDestroy to break cyclic dependency
|
||||
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
||||
}
|
||||
return reactive(state, render);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -89,8 +88,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
willPatch: LifecycleHook[] = [];
|
||||
patched: LifecycleHook[] = [];
|
||||
willDestroy: LifecycleHook[] = [];
|
||||
taskContext: TaskContext;
|
||||
executionContext: ExecutionContext;
|
||||
|
||||
constructor(
|
||||
C: ComponentConstructor<P, E>,
|
||||
@@ -104,15 +101,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
this.parent = parent;
|
||||
this.props = props;
|
||||
this.parentKey = parentKey;
|
||||
this.taskContext = makeTaskContext();
|
||||
this.executionContext = {
|
||||
meta: this,
|
||||
update: () => {
|
||||
this.render(false);
|
||||
},
|
||||
onReadAtom: (atom: Atom) => addAtomToContext(atom, this.executionContext),
|
||||
atoms: new Set<Atom>(),
|
||||
};
|
||||
const defaultProps = C.defaultProps;
|
||||
props = Object.assign({}, props);
|
||||
if (defaultProps) {
|
||||
@@ -120,18 +108,16 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
}
|
||||
const env = (parent && parent.childEnv) || app.env;
|
||||
this.childEnv = env;
|
||||
// for (const key in props) {
|
||||
// const prop = props[key];
|
||||
// if (prop && typeof prop === "object" && targets.has(prop)) {
|
||||
// props[key] = useState(prop);
|
||||
// }
|
||||
// }
|
||||
for (const key in props) {
|
||||
const prop = props[key];
|
||||
if (prop && typeof prop === "object" && targets.has(prop)) {
|
||||
props[key] = useState(prop);
|
||||
}
|
||||
}
|
||||
this.component = new C(props, env, this);
|
||||
const ctx = Object.assign(Object.create(this.component), { this: this.component });
|
||||
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
|
||||
withoutReactivity(() => {
|
||||
this.component.setup();
|
||||
});
|
||||
this.component.setup();
|
||||
currentNode = null;
|
||||
}
|
||||
|
||||
@@ -148,11 +134,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
}
|
||||
const component = this.component;
|
||||
try {
|
||||
let prom: Promise<any[]>;
|
||||
withoutReactivity(() => {
|
||||
prom = Promise.all(this.willStart.map((f) => f.call(component)));
|
||||
});
|
||||
await prom!;
|
||||
await Promise.all(this.willStart.map((f) => f.call(component)));
|
||||
} catch (e) {
|
||||
this.app.handleError({ node: this, error: e });
|
||||
return;
|
||||
@@ -268,18 +250,15 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
}
|
||||
|
||||
currentNode = this;
|
||||
// for (const key in props) {
|
||||
// const prop = props[key];
|
||||
// if (prop && typeof prop === "object" && targets.has(prop)) {
|
||||
// props[key] = useState(prop);
|
||||
// }
|
||||
// }
|
||||
for (const key in props) {
|
||||
const prop = props[key];
|
||||
if (prop && typeof prop === "object" && targets.has(prop)) {
|
||||
props[key] = useState(prop);
|
||||
}
|
||||
}
|
||||
currentNode = null;
|
||||
let prom: Promise<any[]>;
|
||||
withoutReactivity(() => {
|
||||
prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||
});
|
||||
await prom!;
|
||||
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||
await prom;
|
||||
if (fiber !== this.fiber) {
|
||||
return;
|
||||
}
|
||||
@@ -397,8 +376,8 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
return this.component.constructor.name;
|
||||
}
|
||||
|
||||
// get subscriptions(): ReturnType<typeof getSubscriptions> {
|
||||
// const render = batchedRenderFunctions.get(this);
|
||||
// return render ? getSubscriptions(render) : [];
|
||||
// }
|
||||
get subscriptions(): ReturnType<typeof getSubscriptions> {
|
||||
const render = batchedRenderFunctions.get(this);
|
||||
return render ? getSubscriptions(render) : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import type { Fiber } from "./fibers";
|
||||
|
||||
// Custom error class that wraps error that happen in the owl lifecycle
|
||||
export class OwlError extends Error {
|
||||
cause?: any;
|
||||
}
|
||||
|
||||
// Maps fibers to thrown errors
|
||||
export const fibersInError: WeakMap<Fiber, any> = new WeakMap();
|
||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: any) => void)[]> = new WeakMap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { filterOutModifiersFromData } from "./blockdom/config";
|
||||
import { STATUS } from "./status";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { OwlError } from "./error_handling";
|
||||
|
||||
export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarget | null) => {
|
||||
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { ExecutionContext } from "../common/types";
|
||||
|
||||
export const executionContexts: ExecutionContext[] = [];
|
||||
(window as any).executionContexts = executionContexts;
|
||||
// export const scheduledContexts: Set<ExecutionContext> = new Set();
|
||||
|
||||
export function getExecutionContext() {
|
||||
return executionContexts[executionContexts.length - 1];
|
||||
}
|
||||
|
||||
export function pushExecutionContext(context: ExecutionContext) {
|
||||
executionContexts.push(context);
|
||||
}
|
||||
|
||||
export function popExecutionContext() {
|
||||
executionContexts.pop();
|
||||
}
|
||||
|
||||
// export function makeExecutionContext({ update, meta }: { update: () => void; meta?: any }) {
|
||||
// const executionContext: ExecutionContext = {
|
||||
// update,
|
||||
// atoms: new Set(),
|
||||
// meta: meta || {},
|
||||
// };
|
||||
// return executionContext;
|
||||
// }
|
||||
@@ -1,10 +1,7 @@
|
||||
import { BDom, mount } from "./blockdom";
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import { fibersInError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { fibersInError, OwlError } from "./error_handling";
|
||||
import { STATUS } from "./status";
|
||||
import { popTaskContext, pushTaskContext } from "./cancellableContext";
|
||||
import { popExecutionContext, pushExecutionContext } from "./executionContext";
|
||||
|
||||
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
||||
let current = node.fiber;
|
||||
@@ -32,13 +29,6 @@ export function makeRootFiber(node: ComponentNode): Fiber {
|
||||
fibersInError.delete(current);
|
||||
fibersInError.delete(root);
|
||||
current.appliedToDom = false;
|
||||
if (current instanceof RootFiber) {
|
||||
// it is possible that this fiber is a fiber that crashed while being
|
||||
// mounted, so the mounted list is possibly corrupted. We restore it to
|
||||
// its normal initial state (which is empty list or a list with a mount
|
||||
// fiber.
|
||||
current.mounted = current instanceof MountFiber ? [current] : [];
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
@@ -135,16 +125,12 @@ export class Fiber {
|
||||
const node = this.node;
|
||||
const root = this.root;
|
||||
if (root) {
|
||||
pushTaskContext(node.taskContext);
|
||||
pushExecutionContext(node.executionContext);
|
||||
try {
|
||||
(this.bdom as any) = true;
|
||||
this.bdom = node.renderFn();
|
||||
} catch (e) {
|
||||
node.app.handleError({ node, error: e });
|
||||
}
|
||||
popExecutionContext();
|
||||
popTaskContext();
|
||||
root.setCounter(root.counter - 1);
|
||||
}
|
||||
}
|
||||
@@ -165,7 +151,6 @@ export class RootFiber extends Fiber {
|
||||
const node = this.node;
|
||||
this.locked = true;
|
||||
let current: Fiber | undefined = undefined;
|
||||
let mountedFibers = this.mounted;
|
||||
try {
|
||||
// Step 1: calling all willPatch lifecycle hooks
|
||||
for (current of this.willPatch) {
|
||||
@@ -187,6 +172,7 @@ export class RootFiber extends Fiber {
|
||||
this.locked = false;
|
||||
|
||||
// Step 4: calling all mounted lifecycle hooks
|
||||
let mountedFibers = this.mounted;
|
||||
while ((current = mountedFibers.pop())) {
|
||||
current = current;
|
||||
if (current.appliedToDom) {
|
||||
@@ -207,15 +193,6 @@ export class RootFiber extends Fiber {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// if mountedFibers is not empty, this means that a crash occured while
|
||||
// calling the mounted hooks of some component. So, there may still be
|
||||
// some component that have been mounted, but for which the mounted hooks
|
||||
// have not been called. Here, we remove the willUnmount hooks for these
|
||||
// specific component to prevent a worse situation (willUnmount being
|
||||
// called even though mounted has not been called)
|
||||
for (let fiber of mountedFibers) {
|
||||
fiber.node.willUnmount = [];
|
||||
}
|
||||
this.locked = false;
|
||||
node.app.handleError({ fiber: current || this, error: e });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Env } from "./app";
|
||||
import { getCurrent } from "./component_node";
|
||||
import { popExecutionContext, pushExecutionContext } from "./executionContext";
|
||||
import { onMounted, onPatched, onWillUnmount } from "./lifecycle_hooks";
|
||||
import { inOwnerDocument } from "./utils";
|
||||
|
||||
@@ -60,7 +59,7 @@ export function useChildSubEnv(envExtension: Env) {
|
||||
// useEffect
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type EffectDeps<T extends unknown[]> = T | (T extends [...infer H, never] ? EffectDeps<H> : never);
|
||||
type EffectDeps<T extends any[]> = T | (T extends [...infer H, never] ? EffectDeps<H> : never);
|
||||
|
||||
/**
|
||||
* @template T
|
||||
@@ -68,7 +67,7 @@ type EffectDeps<T extends unknown[]> = T | (T extends [...infer H, never] ? Effe
|
||||
* @returns {void|(()=>void)} a cleanup function that reverses the side
|
||||
* effects of the effect callback.
|
||||
*/
|
||||
type Effect<T extends unknown[]> = (...dependencies: EffectDeps<T>) => void | (() => void);
|
||||
type Effect<T extends [...T]> = (...dependencies: EffectDeps<T>) => void | (() => void);
|
||||
|
||||
/**
|
||||
* This hook will run a callback when a component is mounted and patched, and
|
||||
@@ -77,53 +76,32 @@ type Effect<T extends unknown[]> = (...dependencies: EffectDeps<T>) => void | ((
|
||||
*
|
||||
* @template T
|
||||
* @param {Effect<T>} effect the effect to run on component mount and/or patch
|
||||
* @param {()=>[...T]} [computeDependencies=()=>[NaN]] a callback to compute
|
||||
* @param {()=>T} [computeDependencies=()=>[NaN]] a callback to compute
|
||||
* dependencies that will decide if the effect needs to be cleaned up and
|
||||
* run again. If the dependencies did not change, the effect will not run
|
||||
* again. The default value returns an array containing only NaN because
|
||||
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
||||
*/
|
||||
export function useEffect<T extends unknown[]>(
|
||||
export function useEffect<T extends [...T]>(
|
||||
effect: Effect<T>,
|
||||
computeDependencies: () => [...T] = () => [NaN] as never
|
||||
computeDependencies: () => T = () => [NaN] as never
|
||||
) {
|
||||
const context = getCurrent().component.__owl__.executionContext;
|
||||
let cleanup: (() => void) | void;
|
||||
let dependencies: T;
|
||||
|
||||
const runEffect = () => {
|
||||
pushExecutionContext(context);
|
||||
try {
|
||||
cleanup = effect(...dependencies);
|
||||
} finally {
|
||||
popExecutionContext();
|
||||
}
|
||||
};
|
||||
const computeDependenciesWithContext = () => {
|
||||
pushExecutionContext(context);
|
||||
let r: any;
|
||||
try {
|
||||
r = computeDependencies();
|
||||
} finally {
|
||||
popExecutionContext();
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
dependencies = computeDependenciesWithContext();
|
||||
runEffect();
|
||||
dependencies = computeDependencies();
|
||||
cleanup = effect(...dependencies);
|
||||
});
|
||||
|
||||
onPatched(() => {
|
||||
const newDeps = computeDependenciesWithContext();
|
||||
const shouldReapply = newDeps.some((val: any, i: number) => val !== dependencies[i]);
|
||||
const newDeps = computeDependencies();
|
||||
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
||||
if (shouldReapply) {
|
||||
dependencies = newDeps;
|
||||
if (cleanup) {
|
||||
cleanup();
|
||||
}
|
||||
runEffect();
|
||||
cleanup = effect(...dependencies);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ export { Component } from "./component";
|
||||
export type { ComponentConstructor } from "./component";
|
||||
export { useComponent, useState } from "./component_node";
|
||||
export { status } from "./status";
|
||||
export { reactive, markRaw, toRaw, effect, withoutReactivity } from "./reactivity";
|
||||
export { reactive, markRaw, toRaw } from "./reactivity";
|
||||
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
|
||||
export { batched, EventBus, htmlEscape, whenReady, loadFile, markup } from "./utils";
|
||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
||||
export {
|
||||
onWillStart,
|
||||
onMounted,
|
||||
@@ -55,7 +55,7 @@ export {
|
||||
onError,
|
||||
} from "./lifecycle_hooks";
|
||||
export { validate, validateType } from "./validation";
|
||||
export { OwlError } from "../common/owl_error";
|
||||
export { OwlError } from "./error_handling";
|
||||
|
||||
export const __info__ = {
|
||||
version: App.version,
|
||||
|
||||
@@ -1,52 +1,43 @@
|
||||
import { getCurrent } from "./component_node";
|
||||
import { nodeErrorHandlers } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { nodeErrorHandlers, OwlError } from "./error_handling";
|
||||
|
||||
const TIMEOUT = Symbol("timeout");
|
||||
const HOOK_TIMEOUT: { [key: string]: number } = {
|
||||
onWillStart: 3000,
|
||||
onWillUpdateProps: 3000,
|
||||
};
|
||||
function wrapError(fn: (...args: any[]) => any, hookName: string) {
|
||||
const error = new OwlError() as Error & {
|
||||
const error = new OwlError(`The following error occurred in ${hookName}: `) as Error & {
|
||||
cause: any;
|
||||
};
|
||||
const timeoutError = new OwlError();
|
||||
const timeoutError = new OwlError(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
||||
const node = getCurrent();
|
||||
return (...args: any[]) => {
|
||||
const onError = (cause: any) => {
|
||||
error.cause = cause;
|
||||
error.message =
|
||||
cause instanceof Error
|
||||
? `The following error occurred in ${hookName}: "${cause.message}"`
|
||||
: `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||
if (cause instanceof Error) {
|
||||
error.message += `"${cause.message}"`;
|
||||
} else {
|
||||
error.message = `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||
}
|
||||
throw error;
|
||||
};
|
||||
let result;
|
||||
try {
|
||||
result = fn(...args);
|
||||
const result = fn(...args);
|
||||
if (result instanceof Promise) {
|
||||
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
||||
const fiber = node.fiber;
|
||||
Promise.race([
|
||||
result.catch(() => {}),
|
||||
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
||||
]).then((res) => {
|
||||
if (res === TIMEOUT && node.fiber === fiber) {
|
||||
console.warn(timeoutError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.catch(onError);
|
||||
}
|
||||
return result;
|
||||
} catch (cause) {
|
||||
onError(cause);
|
||||
}
|
||||
if (!(result instanceof Promise)) {
|
||||
return result;
|
||||
}
|
||||
const timeout = HOOK_TIMEOUT[hookName];
|
||||
if (timeout) {
|
||||
const fiber = node.fiber;
|
||||
Promise.race([
|
||||
result.catch(() => {}),
|
||||
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), timeout)),
|
||||
]).then((res) => {
|
||||
if (res === TIMEOUT && node.fiber === fiber && node.status <= 2) {
|
||||
timeoutError.message = `${hookName}'s promise hasn't resolved after ${
|
||||
timeout / 1000
|
||||
} seconds`;
|
||||
console.log(timeoutError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.catch(onError);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { onMounted, onWillUnmount } from "./lifecycle_hooks";
|
||||
import { BDom, text, VNode } from "./blockdom";
|
||||
import { Component } from "./component";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { OwlError } from "./error_handling";
|
||||
|
||||
const VText: any = text("").constructor;
|
||||
|
||||
@@ -65,7 +65,7 @@ export class Portal extends Component {
|
||||
type: String,
|
||||
},
|
||||
slots: true,
|
||||
} as const;
|
||||
};
|
||||
|
||||
setup() {
|
||||
const node: any = this.__owl__;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { ExecutionContext, Atom, DerivedAtom, OldValue } from "../common/types";
|
||||
import { getExecutionContext, popExecutionContext, pushExecutionContext } from "./executionContext";
|
||||
import type { Callback } from "./utils";
|
||||
import { OwlError } from "./error_handling";
|
||||
|
||||
// Special key to subscribe to, to be notified of key creation/deletion
|
||||
const KEYCHANGES = Symbol("Key changes");
|
||||
// Used to specify the absence of a callback, can be used as WeakMap key but
|
||||
// should only be used as a sentinel value and never called.
|
||||
const NO_CALLBACK = () => {
|
||||
throw new Error("Called NO_CALLBACK. Owl is broken, please report this to the maintainers.");
|
||||
};
|
||||
|
||||
// The following types only exist to signify places where objects are expected
|
||||
// to be reactive or not, they provide no type checking benefit over "object"
|
||||
@@ -16,9 +20,8 @@ type CollectionRawType = "Set" | "Map" | "WeakMap";
|
||||
const objectToString = Object.prototype.toString;
|
||||
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
// Use arrays because Array.includes is faster than Set.has for small arrays
|
||||
const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"];
|
||||
const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
|
||||
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
|
||||
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
|
||||
|
||||
/**
|
||||
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
|
||||
@@ -42,7 +45,7 @@ function canBeMadeReactive(value: any): boolean {
|
||||
if (typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
return SUPPORTED_RAW_TYPES.includes(rawType(value));
|
||||
return SUPPORTED_RAW_TYPES.has(rawType(value));
|
||||
}
|
||||
/**
|
||||
* Creates a reactive from the given object/callback if possible and returns it,
|
||||
@@ -51,8 +54,8 @@ function canBeMadeReactive(value: any): boolean {
|
||||
* @param value the value make reactive
|
||||
* @returns a reactive for the given object when possible, the original otherwise
|
||||
*/
|
||||
function possiblyReactive(val: any) {
|
||||
return canBeMadeReactive(val) ? reactive(val) : val;
|
||||
function possiblyReactive(val: any, cb: Callback) {
|
||||
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
||||
}
|
||||
|
||||
const skipped = new WeakSet<Target>();
|
||||
@@ -77,37 +80,7 @@ export function toRaw<T extends Target, U extends Reactive<T>>(value: U | T): T
|
||||
return targets.has(value) ? (targets.get(value) as T) : value;
|
||||
}
|
||||
|
||||
const targetToKeysToAtomItem = new WeakMap<Target, Map<PropertyKey, Atom>>();
|
||||
const scheduledAtoms = new Set<Atom>();
|
||||
|
||||
function makeAtom(getValue: () => any): Atom {
|
||||
const atom: Atom = {
|
||||
executionContexts: new Set<ExecutionContext>(),
|
||||
dependents: new Set<Atom>(),
|
||||
// getValue,
|
||||
};
|
||||
return atom;
|
||||
}
|
||||
|
||||
function getTargetKeyAtom(target: Target, key: PropertyKey): Atom {
|
||||
let keyToAtomItem: Map<PropertyKey, Atom> = targetToKeysToAtomItem.get(target)!;
|
||||
if (!keyToAtomItem) {
|
||||
keyToAtomItem = new Map();
|
||||
targetToKeysToAtomItem.set(target, keyToAtomItem);
|
||||
}
|
||||
let atom = keyToAtomItem.get(key)!;
|
||||
if (!atom) {
|
||||
atom = makeAtom(() => Reflect.get(target, key));
|
||||
keyToAtomItem.set(key, atom);
|
||||
}
|
||||
return atom;
|
||||
}
|
||||
|
||||
export function addAtomToContext(atom: Atom, executionContext: ExecutionContext) {
|
||||
executionContext.atoms.add(atom);
|
||||
atom.executionContexts.add(executionContext);
|
||||
}
|
||||
|
||||
const targetToKeysToCallbacks = new WeakMap<Target, Map<PropertyKey, Set<Callback>>>();
|
||||
/**
|
||||
* Observes a given key on a target with an callback. The callback will be
|
||||
* called when the given key changes on the target.
|
||||
@@ -117,73 +90,23 @@ export function addAtomToContext(atom: Atom, executionContext: ExecutionContext)
|
||||
* or deletion)
|
||||
* @param callback the function to call when the key changes
|
||||
*/
|
||||
function onReadTargetKey(target: Target, key: PropertyKey, receiver: any): void {
|
||||
const executionContext = getExecutionContext();
|
||||
executionContext?.onReadAtom(getTargetKeyAtom(target, key));
|
||||
}
|
||||
|
||||
let scheduled = false;
|
||||
function scheduleAtom(atom: Atom) {
|
||||
scheduledAtoms.add(atom);
|
||||
// batched(processAtoms)();
|
||||
if (scheduled) return;
|
||||
scheduled = true;
|
||||
Promise.resolve().then(() => {
|
||||
scheduled = false;
|
||||
processAtoms();
|
||||
});
|
||||
}
|
||||
|
||||
function processDerivedAtoms() {
|
||||
const processedAtoms = new Set<Atom>();
|
||||
for (const atom of scheduledAtoms) {
|
||||
for (const dep of atom.dependents) {
|
||||
if (processedAtoms.has(dep)) continue;
|
||||
dep.computed = false;
|
||||
processedAtoms.add(dep);
|
||||
}
|
||||
function observeTargetKey(target: Target, key: PropertyKey, callback: Callback): void {
|
||||
if (callback === NO_CALLBACK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function processAtoms() {
|
||||
processDerivedAtoms();
|
||||
|
||||
const scheduledContexts = new Set(
|
||||
[...scheduledAtoms.values()].map((s) => [...s.executionContexts]).flat()
|
||||
);
|
||||
|
||||
// schedule before context.update in case there is write operations during update
|
||||
// todo: add a test in case there is write operations during update the test
|
||||
// will break is scheduledAtoms.clear(); is called after context.update();
|
||||
// that writes
|
||||
scheduledAtoms.clear();
|
||||
for (const ctx of [...scheduledContexts]) {
|
||||
removeAtomsFromContext(ctx);
|
||||
// custom unsubscribe depending on the context.
|
||||
// scheduledContexts might be updated while we're iterating over it.
|
||||
ctx.unsubcribe?.(scheduledContexts);
|
||||
if (!targetToKeysToCallbacks.get(target)) {
|
||||
targetToKeysToCallbacks.set(target, new Map());
|
||||
}
|
||||
|
||||
for (const context of scheduledContexts) {
|
||||
pushExecutionContext(context);
|
||||
try {
|
||||
context.update?.();
|
||||
} finally {
|
||||
popExecutionContext();
|
||||
}
|
||||
const keyToCallbacks = targetToKeysToCallbacks.get(target)!;
|
||||
if (!keyToCallbacks.get(key)) {
|
||||
keyToCallbacks.set(key, new Set());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify Reactives that are observing a given target that a key has changed on
|
||||
}
|
||||
});
|
||||
};
|
||||
for (const context of executionContexts) {
|
||||
context.update();
|
||||
keyToCallbacks.get(key)!.add(callback);
|
||||
if (!callbacksToTargets.has(callback)) {
|
||||
callbacksToTargets.set(callback, new Set());
|
||||
}
|
||||
callbacksToTargets.get(callback)!.add(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify Reactives that are observing a given target that a key has changed on
|
||||
* the target.
|
||||
@@ -193,21 +116,66 @@ function processAtoms() {
|
||||
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
||||
* or deleted)
|
||||
*/
|
||||
function onWriteTargetKey(target: Target, key: PropertyKey): void {
|
||||
const keyToAtomItem = targetToKeysToAtomItem.get(target)!;
|
||||
if (!keyToAtomItem) {
|
||||
function notifyReactives(target: Target, key: PropertyKey): void {
|
||||
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
||||
if (!keyToCallbacks) {
|
||||
return;
|
||||
}
|
||||
const atom = keyToAtomItem.get(key);
|
||||
if (!atom) {
|
||||
const callbacks = keyToCallbacks.get(key);
|
||||
if (!callbacks) {
|
||||
return;
|
||||
}
|
||||
scheduleAtom(atom);
|
||||
// Loop on copy because clearReactivesForCallback will modify the set in place
|
||||
for (const callback of [...callbacks]) {
|
||||
clearReactivesForCallback(callback);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
|
||||
/**
|
||||
* Clears all subscriptions of the Reactives associated with a given callback.
|
||||
*
|
||||
* @param callback the callback for which the reactives need to be cleared
|
||||
*/
|
||||
export function clearReactivesForCallback(callback: Callback): void {
|
||||
const targetsToClear = callbacksToTargets.get(callback);
|
||||
if (!targetsToClear) {
|
||||
return;
|
||||
}
|
||||
for (const target of targetsToClear) {
|
||||
const observedKeys = targetToKeysToCallbacks.get(target);
|
||||
if (!observedKeys) {
|
||||
continue;
|
||||
}
|
||||
for (const [key, callbacks] of observedKeys.entries()) {
|
||||
callbacks.delete(callback);
|
||||
if (!callbacks.size) {
|
||||
observedKeys.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
targetsToClear.clear();
|
||||
}
|
||||
|
||||
export function getSubscriptions(callback: Callback) {
|
||||
const targets = callbacksToTargets.get(callback) || [];
|
||||
return [...targets].map((target) => {
|
||||
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
||||
let keys = [];
|
||||
if (keysToCallbacks) {
|
||||
for (const [key, cbs] of keysToCallbacks) {
|
||||
if (cbs.has(callback)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { target, keys };
|
||||
});
|
||||
}
|
||||
// Maps reactive objects to the underlying target
|
||||
export const targets = new WeakMap<Reactive<Target>, Target>();
|
||||
const reactiveCache = new WeakMap<Target, Reactive<Target>>();
|
||||
const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive<Target>>>();
|
||||
/**
|
||||
* Creates a reactive proxy for an object. Reading data on the reactive object
|
||||
* subscribes to changes to the data. Writing data on the object will cause the
|
||||
@@ -235,7 +203,7 @@ const reactiveCache = new WeakMap<Target, Reactive<Target>>();
|
||||
* reactive has changed
|
||||
* @returns a proxy that tracks changes to it
|
||||
*/
|
||||
export function reactive<T extends Target>(target: T): T {
|
||||
export function reactive<T extends Target>(target: T, callback: Callback = NO_CALLBACK): T {
|
||||
if (!canBeMadeReactive(target)) {
|
||||
throw new OwlError(`Cannot make the given value reactive`);
|
||||
}
|
||||
@@ -244,130 +212,30 @@ export function reactive<T extends Target>(target: T): T {
|
||||
}
|
||||
if (targets.has(target)) {
|
||||
// target is reactive, create a reactive on the underlying object instead
|
||||
// return reactive(targets.get(target) as T);
|
||||
return target;
|
||||
return reactive(targets.get(target) as T, callback);
|
||||
}
|
||||
const reactive = reactiveCache.get(target)!;
|
||||
if (reactive) return reactive as T;
|
||||
|
||||
const targetRawType = rawType(target);
|
||||
const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
|
||||
? collectionsProxyHandler(target as Collection, targetRawType as CollectionRawType)
|
||||
: basicProxyHandler<T>();
|
||||
const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>;
|
||||
|
||||
reactiveCache.set(target, proxy);
|
||||
targets.set(proxy, target);
|
||||
|
||||
return proxy;
|
||||
if (!reactiveCache.has(target)) {
|
||||
reactiveCache.set(target, new WeakMap());
|
||||
}
|
||||
const reactivesForTarget = reactiveCache.get(target)!;
|
||||
if (!reactivesForTarget.has(callback)) {
|
||||
const targetRawType = rawType(target);
|
||||
const handler = COLLECTION_RAWTYPES.has(targetRawType)
|
||||
? collectionsProxyHandler(target as Collection, callback, targetRawType as CollectionRawType)
|
||||
: basicProxyHandler<T>(callback);
|
||||
const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>;
|
||||
reactivesForTarget.set(callback, proxy);
|
||||
targets.set(proxy, target);
|
||||
}
|
||||
return reactivesForTarget.get(callback) as Reactive<T>;
|
||||
}
|
||||
function removeAtomsFromContext(executionContext: ExecutionContext) {
|
||||
for (const sig of executionContext.atoms) {
|
||||
sig.executionContexts.delete(executionContext);
|
||||
}
|
||||
executionContext.atoms.clear();
|
||||
}
|
||||
/**
|
||||
* Unsubscribe an execution context and all its children from all atoms
|
||||
* they are subscribed to.
|
||||
*
|
||||
* @param parentExecutionContext the context to unsubscribe
|
||||
*/
|
||||
function unsubscribeChildEffect(
|
||||
parentExecutionContext: ExecutionContext,
|
||||
scheduledContexts: Set<ExecutionContext>
|
||||
) {
|
||||
// executionContext.update = () => {};
|
||||
|
||||
for (const children of parentExecutionContext.meta.children) {
|
||||
children.meta.parent = undefined;
|
||||
removeAtomsFromContext(children);
|
||||
scheduledContexts.delete(children);
|
||||
unsubscribeChildEffect(children, scheduledContexts);
|
||||
}
|
||||
parentExecutionContext.meta.children.length = 0;
|
||||
}
|
||||
export function withoutReactivity<T extends (...args: any[]) => any>(fn: T): ReturnType<T> {
|
||||
pushExecutionContext(undefined!);
|
||||
let r: ReturnType<T>;
|
||||
try {
|
||||
r = fn();
|
||||
} finally {
|
||||
popExecutionContext();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
export function effect(fn: Function) {
|
||||
let parent = getExecutionContext();
|
||||
// todo: is it useful?
|
||||
if (parent && !parent?.meta.children) {
|
||||
parent = undefined!;
|
||||
}
|
||||
const executionContext: ExecutionContext = {
|
||||
unsubcribe: (scheduledContexts: Set<ExecutionContext>) => {
|
||||
unsubscribeChildEffect(executionContext, scheduledContexts);
|
||||
},
|
||||
update: fn,
|
||||
onReadAtom: (atom: Atom) => addAtomToContext(atom, executionContext),
|
||||
atoms: new Set(),
|
||||
meta: {
|
||||
parent: parent,
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
if (parent) {
|
||||
// todo: is it useful?
|
||||
parent.meta.children?.push?.(executionContext);
|
||||
}
|
||||
pushExecutionContext(executionContext);
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
popExecutionContext();
|
||||
}
|
||||
}
|
||||
|
||||
export function derived(fn: Function) {
|
||||
let lastValue: any;
|
||||
|
||||
const derivedAtom: DerivedAtom = {
|
||||
executionContexts: new Set<ExecutionContext>(),
|
||||
dependents: new Set<Atom>(),
|
||||
dependencies: new Map<Atom, OldValue>(),
|
||||
getValue: () => lastValue,
|
||||
computed: false,
|
||||
};
|
||||
|
||||
return () => {
|
||||
const executionContext = getExecutionContext();
|
||||
executionContext?.onReadAtom(derivedAtom);
|
||||
if (derivedAtom.computed) return lastValue;
|
||||
|
||||
const derivedExecutionContext: ExecutionContext = {
|
||||
onReadAtom: (atom: Atom) => {
|
||||
atom.dependents.add(derivedAtom);
|
||||
// derivedAtom.executionContexts.add(executionContext);
|
||||
},
|
||||
};
|
||||
pushExecutionContext(derivedExecutionContext);
|
||||
try {
|
||||
lastValue = fn();
|
||||
} finally {
|
||||
popExecutionContext();
|
||||
}
|
||||
derivedAtom.computed = true;
|
||||
return lastValue;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a basic proxy handler for regular objects and arrays.
|
||||
*
|
||||
* @param callback @see reactive
|
||||
* @returns a proxy handler object
|
||||
*/
|
||||
function basicProxyHandler<T extends Target>(): ProxyHandler<T> {
|
||||
function basicProxyHandler<T extends Target>(callback: Callback): ProxyHandler<T> {
|
||||
return {
|
||||
get(target, key, receiver) {
|
||||
// non-writable non-configurable properties cannot be made reactive
|
||||
@@ -375,15 +243,15 @@ function basicProxyHandler<T extends Target>(): ProxyHandler<T> {
|
||||
if (desc && !desc.writable && !desc.configurable) {
|
||||
return Reflect.get(target, key, receiver);
|
||||
}
|
||||
onReadTargetKey(target, key);
|
||||
return possiblyReactive(Reflect.get(target, key, receiver));
|
||||
observeTargetKey(target, key, callback);
|
||||
return possiblyReactive(Reflect.get(target, key, receiver), callback);
|
||||
},
|
||||
set(target, key, value, receiver) {
|
||||
const hadKey = objectHasOwnProperty.call(target, key);
|
||||
const originalValue = Reflect.get(target, key, receiver);
|
||||
const ret = Reflect.set(target, key, toRaw(value), receiver);
|
||||
const ret = Reflect.set(target, key, value, receiver);
|
||||
if (!hadKey && objectHasOwnProperty.call(target, key)) {
|
||||
onWriteTargetKey(target, KEYCHANGES);
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
}
|
||||
// While Array length may trigger the set trap, it's not actually set by this
|
||||
// method but is updated behind the scenes, and the trap is not called with the
|
||||
@@ -392,26 +260,26 @@ function basicProxyHandler<T extends Target>(): ProxyHandler<T> {
|
||||
originalValue !== Reflect.get(target, key, receiver) ||
|
||||
(key === "length" && Array.isArray(target))
|
||||
) {
|
||||
onWriteTargetKey(target, key);
|
||||
notifyReactives(target, key);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
deleteProperty(target, key) {
|
||||
const ret = Reflect.deleteProperty(target, key);
|
||||
// TODO: only notify when something was actually deleted
|
||||
onWriteTargetKey(target, KEYCHANGES);
|
||||
onWriteTargetKey(target, key);
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
notifyReactives(target, key);
|
||||
return ret;
|
||||
},
|
||||
ownKeys(target) {
|
||||
onReadTargetKey(target, KEYCHANGES);
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return Reflect.ownKeys(target);
|
||||
},
|
||||
has(target, key) {
|
||||
// TODO: this observes all key changes instead of only the presence of the argument key
|
||||
// observing the key itself would observe value changes instead of presence changes
|
||||
// so we may need a finer grained system to distinguish observing value vs presence.
|
||||
onReadTargetKey(target, KEYCHANGES);
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return Reflect.has(target, key);
|
||||
},
|
||||
} as ProxyHandler<T>;
|
||||
@@ -424,11 +292,11 @@ function basicProxyHandler<T extends Target>(): ProxyHandler<T> {
|
||||
* @param target @see reactive
|
||||
* @param callback @see reactive
|
||||
*/
|
||||
function makeKeyObserver(methodName: "has" | "get", target: any) {
|
||||
function makeKeyObserver(methodName: "has" | "get", target: any, callback: Callback) {
|
||||
return (key: any) => {
|
||||
key = toRaw(key);
|
||||
onReadTargetKey(target, key);
|
||||
return possiblyReactive(target[methodName](key));
|
||||
observeTargetKey(target, key, callback);
|
||||
return possiblyReactive(target[methodName](key), callback);
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -441,15 +309,16 @@ function makeKeyObserver(methodName: "has" | "get", target: any) {
|
||||
*/
|
||||
function makeIteratorObserver(
|
||||
methodName: "keys" | "values" | "entries" | typeof Symbol.iterator,
|
||||
target: any
|
||||
target: any,
|
||||
callback: Callback
|
||||
) {
|
||||
return function* () {
|
||||
onReadTargetKey(target, KEYCHANGES);
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
const keys = target.keys();
|
||||
for (const item of target[methodName]()) {
|
||||
const key = keys.next().value;
|
||||
onReadTargetKey(target, key);
|
||||
yield possiblyReactive(item);
|
||||
observeTargetKey(target, key, callback);
|
||||
yield possiblyReactive(item, callback);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -461,16 +330,16 @@ function makeIteratorObserver(
|
||||
* @param target @see reactive
|
||||
* @param callback @see reactive
|
||||
*/
|
||||
function makeForEachObserver(target: any) {
|
||||
function makeForEachObserver(target: any, callback: Callback) {
|
||||
return function forEach(forEachCb: (val: any, key: any, target: any) => void, thisArg: any) {
|
||||
onReadTargetKey(target, KEYCHANGES);
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
target.forEach(function (val: any, key: any, targetObj: any) {
|
||||
onReadTargetKey(target, key);
|
||||
observeTargetKey(target, key, callback);
|
||||
forEachCb.call(
|
||||
thisArg,
|
||||
possiblyReactive(val),
|
||||
possiblyReactive(key),
|
||||
possiblyReactive(targetObj)
|
||||
possiblyReactive(val, callback),
|
||||
possiblyReactive(key, callback),
|
||||
possiblyReactive(targetObj, callback)
|
||||
);
|
||||
}, thisArg);
|
||||
};
|
||||
@@ -497,10 +366,10 @@ function delegateAndNotify(
|
||||
const ret = target[setterName](key, value);
|
||||
const hasKey = target.has(key);
|
||||
if (hadKey !== hasKey) {
|
||||
onWriteTargetKey(target, KEYCHANGES);
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
}
|
||||
if (originalValue !== target[getterName](key)) {
|
||||
onWriteTargetKey(target, key);
|
||||
if (originalValue !== value) {
|
||||
notifyReactives(target, key);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
@@ -515,9 +384,9 @@ function makeClearNotifier(target: Map<any, any> | Set<any>) {
|
||||
return () => {
|
||||
const allKeys = [...target.keys()];
|
||||
target.clear();
|
||||
onWriteTargetKey(target, KEYCHANGES);
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
for (const key of allKeys) {
|
||||
onWriteTargetKey(target, key);
|
||||
notifyReactives(target, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -529,40 +398,40 @@ function makeClearNotifier(target: Map<any, any> | Set<any>) {
|
||||
* reactives that the key which is being added or deleted has been modified.
|
||||
*/
|
||||
const rawTypeToFuncHandlers = {
|
||||
Set: (target: any) => ({
|
||||
has: makeKeyObserver("has", target),
|
||||
Set: (target: any, callback: Callback) => ({
|
||||
has: makeKeyObserver("has", target, callback),
|
||||
add: delegateAndNotify("add", "has", target),
|
||||
delete: delegateAndNotify("delete", "has", target),
|
||||
keys: makeIteratorObserver("keys", target),
|
||||
values: makeIteratorObserver("values", target),
|
||||
entries: makeIteratorObserver("entries", target),
|
||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
|
||||
forEach: makeForEachObserver(target),
|
||||
keys: makeIteratorObserver("keys", target, callback),
|
||||
values: makeIteratorObserver("values", target, callback),
|
||||
entries: makeIteratorObserver("entries", target, callback),
|
||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
||||
forEach: makeForEachObserver(target, callback),
|
||||
clear: makeClearNotifier(target),
|
||||
get size() {
|
||||
onReadTargetKey(target, KEYCHANGES);
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return target.size;
|
||||
},
|
||||
}),
|
||||
Map: (target: any) => ({
|
||||
has: makeKeyObserver("has", target),
|
||||
get: makeKeyObserver("get", target),
|
||||
Map: (target: any, callback: Callback) => ({
|
||||
has: makeKeyObserver("has", target, callback),
|
||||
get: makeKeyObserver("get", target, callback),
|
||||
set: delegateAndNotify("set", "get", target),
|
||||
delete: delegateAndNotify("delete", "has", target),
|
||||
keys: makeIteratorObserver("keys", target),
|
||||
values: makeIteratorObserver("values", target),
|
||||
entries: makeIteratorObserver("entries", target),
|
||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
|
||||
forEach: makeForEachObserver(target),
|
||||
keys: makeIteratorObserver("keys", target, callback),
|
||||
values: makeIteratorObserver("values", target, callback),
|
||||
entries: makeIteratorObserver("entries", target, callback),
|
||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
||||
forEach: makeForEachObserver(target, callback),
|
||||
clear: makeClearNotifier(target),
|
||||
get size() {
|
||||
onReadTargetKey(target, KEYCHANGES);
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return target.size;
|
||||
},
|
||||
}),
|
||||
WeakMap: (target: any) => ({
|
||||
has: makeKeyObserver("has", target),
|
||||
get: makeKeyObserver("get", target),
|
||||
WeakMap: (target: any, callback: Callback) => ({
|
||||
has: makeKeyObserver("has", target, callback),
|
||||
get: makeKeyObserver("get", target, callback),
|
||||
set: delegateAndNotify("set", "get", target),
|
||||
delete: delegateAndNotify("delete", "has", target),
|
||||
}),
|
||||
@@ -576,19 +445,20 @@ const rawTypeToFuncHandlers = {
|
||||
*/
|
||||
function collectionsProxyHandler<T extends Collection>(
|
||||
target: T,
|
||||
callback: Callback,
|
||||
targetRawType: CollectionRawType
|
||||
): ProxyHandler<T> {
|
||||
// TODO: if performance is an issue we can create the special handlers lazily when each
|
||||
// property is read.
|
||||
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target);
|
||||
return Object.assign(basicProxyHandler(), {
|
||||
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
||||
return Object.assign(basicProxyHandler(callback), {
|
||||
// FIXME: probably broken when part of prototype chain since we ignore the receiver
|
||||
get(target: any, key: PropertyKey) {
|
||||
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
||||
return (specialHandlers as any)[key];
|
||||
}
|
||||
onReadTargetKey(target, key);
|
||||
return possiblyReactive(target[key]);
|
||||
observeTargetKey(target, key, callback);
|
||||
return possiblyReactive(target[key], callback);
|
||||
},
|
||||
}) as ProxyHandler<T>;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ export class Scheduler {
|
||||
frame: number = 0;
|
||||
delayedRenders: Fiber[] = [];
|
||||
cancelledNodes: Set<ComponentNode> = new Set();
|
||||
processing = false;
|
||||
|
||||
constructor() {
|
||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||
@@ -54,10 +53,6 @@ export class Scheduler {
|
||||
}
|
||||
|
||||
processTasks() {
|
||||
if (this.processing) {
|
||||
return;
|
||||
}
|
||||
this.processing = true;
|
||||
this.frame = 0;
|
||||
for (let node of this.cancelledNodes) {
|
||||
node._destroy();
|
||||
@@ -71,7 +66,6 @@ export class Scheduler {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
this.processing = false;
|
||||
}
|
||||
|
||||
processFiber(fiber: RootFiber) {
|
||||
@@ -93,14 +87,7 @@ export class Scheduler {
|
||||
if (!hasError) {
|
||||
fiber.complete();
|
||||
}
|
||||
// at this point, the fiber should have been applied to the DOM, so we can
|
||||
// remove it from the task list. If it is not the case, it means that there
|
||||
// was an error and an error handler triggered a new rendering that recycled
|
||||
// the fiber, so in that case, we actually want to keep the fiber around,
|
||||
// otherwise it will just be ignored.
|
||||
if (fiber.appliedToDom) {
|
||||
this.tasks.delete(fiber);
|
||||
}
|
||||
this.tasks.delete(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { getTaskContext, TaskContext, useTaskContext } from "./cancellableContext";
|
||||
|
||||
export class Task<T = any> {
|
||||
_promise: Promise<T>;
|
||||
_ctx?: TaskContext = getTaskContext();
|
||||
|
||||
constructor(
|
||||
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason: any) => void) => void,
|
||||
public _onCancelled?: Function
|
||||
) {
|
||||
if (!this._ctx) {
|
||||
this._promise = new Promise(executor);
|
||||
return;
|
||||
}
|
||||
|
||||
this._promise = new Promise((resolve, reject) => {
|
||||
try {
|
||||
executor(
|
||||
(value: T | PromiseLike<T>) => {
|
||||
if (!this._ctx?.isCancelled) resolve(value);
|
||||
},
|
||||
(error: any) => {
|
||||
if (!this._ctx?.isCancelled) reject(error);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
if (!this._ctx?.isCancelled) reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
then(onFulfilled: (value: any) => any, onRejected: (error: any) => any) {
|
||||
if (!this._ctx) return this._promise.then(onFulfilled, onRejected);
|
||||
return this._promise.then((v) => {
|
||||
if (this._ctx!.isCancelled) return;
|
||||
let cleanup: Function;
|
||||
Promise.resolve().then(() => {
|
||||
const ctx = useTaskContext(this._ctx);
|
||||
cleanup = ctx.cleanup;
|
||||
});
|
||||
const result = onFulfilled(v);
|
||||
Promise.resolve().then(() => {
|
||||
cleanup();
|
||||
});
|
||||
return result;
|
||||
}, onRejected);
|
||||
}
|
||||
|
||||
catch(onRejected: (error: any) => any) {
|
||||
return this._promise.catch(onRejected);
|
||||
}
|
||||
|
||||
finally(onFinally: () => any) {
|
||||
return this._promise.finally(onFinally);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
if (this._onCancelled) {
|
||||
this._onCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return "Promise";
|
||||
}
|
||||
|
||||
// static all(tasks) {
|
||||
// return new Task((resolve, reject) => {
|
||||
// Promise.all(tasks.map((t) => (t instanceof Task ? t._promise : t))).then(resolve, reject);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { html } from "./blockdom/index";
|
||||
import { isOptional, validateSchema } from "./validation";
|
||||
import type { ComponentConstructor } from "./component";
|
||||
import { markRaw } from "./reactivity";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { OwlError } from "./error_handling";
|
||||
import type { ComponentNode } from "./component_node";
|
||||
|
||||
const ObjectCreate = Object.create;
|
||||
@@ -70,12 +70,14 @@ function prepareList(collection: unknown): [unknown[], unknown[], number, undefi
|
||||
} else if (collection instanceof Map) {
|
||||
keys = [...collection.keys()];
|
||||
values = [...collection.values()];
|
||||
} else if (Symbol.iterator in Object(collection)) {
|
||||
keys = [...(<Iterable<unknown>>collection)];
|
||||
values = keys;
|
||||
} else if (collection && typeof collection === "object") {
|
||||
values = Object.values(collection);
|
||||
keys = Object.keys(collection);
|
||||
if (Symbol.iterator in collection) {
|
||||
keys = [...(<Iterable<unknown>>collection)];
|
||||
values = keys;
|
||||
} else {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
}
|
||||
} else {
|
||||
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||
}
|
||||
|
||||
@@ -3,20 +3,45 @@ import { comment, createBlock, html, list, multi, text, toggler } from "./blockd
|
||||
import { getCurrent } from "./component_node";
|
||||
import { Portal, portalTemplate } from "./portal";
|
||||
import { helpers } from "./template_helpers";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { parseXML } from "../common/utils";
|
||||
import type { customDirectives } from "../common/types";
|
||||
import { OwlError } from "./error_handling";
|
||||
|
||||
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
||||
|
||||
function parseXML(xml: string): Document {
|
||||
const parser = new DOMParser();
|
||||
|
||||
const doc = parser.parseFromString(xml, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
let msg = "Invalid XML in template.";
|
||||
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
||||
if (parsererrorText) {
|
||||
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
||||
const re = /\d+/g;
|
||||
const firstMatch = re.exec(parsererrorText);
|
||||
if (firstMatch) {
|
||||
const lineNumber = Number(firstMatch[0]);
|
||||
const line = xml.split("\n")[lineNumber - 1];
|
||||
const secondMatch = re.exec(parsererrorText);
|
||||
if (line && secondMatch) {
|
||||
const columnIndex = Number(secondMatch[0]) - 1;
|
||||
if (line[columnIndex]) {
|
||||
msg +=
|
||||
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
||||
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new OwlError(msg);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
export interface TemplateSetConfig {
|
||||
dev?: boolean;
|
||||
translatableAttributes?: string[];
|
||||
translateFn?: (s: string, translationCtx: string) => string;
|
||||
templates?: string | Document | Record<string, string>;
|
||||
getTemplate?: (s: string) => Element | Function | string | void;
|
||||
customDirectives?: customDirectives;
|
||||
globalValues?: object;
|
||||
translateFn?: (s: string) => string;
|
||||
templates?: string | Document;
|
||||
}
|
||||
|
||||
export class TemplateSet {
|
||||
@@ -26,39 +51,21 @@ export class TemplateSet {
|
||||
dev: boolean;
|
||||
rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
|
||||
templates: { [name: string]: Template } = {};
|
||||
getRawTemplate?: (s: string) => Element | Function | string | void;
|
||||
translateFn?: (s: string, translationCtx: string) => string;
|
||||
translateFn?: (s: string) => string;
|
||||
translatableAttributes?: string[];
|
||||
Portal = Portal;
|
||||
customDirectives: customDirectives;
|
||||
runtimeUtils: object;
|
||||
hasGlobalValues: boolean;
|
||||
|
||||
constructor(config: TemplateSetConfig = {}) {
|
||||
this.dev = config.dev || false;
|
||||
this.translateFn = config.translateFn;
|
||||
this.translatableAttributes = config.translatableAttributes;
|
||||
if (config.templates) {
|
||||
if (config.templates instanceof Document || typeof config.templates === "string") {
|
||||
this.addTemplates(config.templates);
|
||||
} else {
|
||||
for (const name in config.templates) {
|
||||
this.addTemplate(name, config.templates[name]);
|
||||
}
|
||||
}
|
||||
this.addTemplates(config.templates);
|
||||
}
|
||||
this.getRawTemplate = config.getTemplate;
|
||||
this.customDirectives = config.customDirectives || {};
|
||||
this.runtimeUtils = { ...helpers, __globals__: config.globalValues || {} };
|
||||
this.hasGlobalValues = Boolean(config.globalValues && Object.keys(config.globalValues).length);
|
||||
}
|
||||
|
||||
addTemplate(name: string, template: string | Element) {
|
||||
if (name in this.rawTemplates) {
|
||||
// this check can be expensive, just silently ignore double definitions outside dev mode
|
||||
if (!this.dev) {
|
||||
return;
|
||||
}
|
||||
const rawTemplate = this.rawTemplates[name];
|
||||
const currentAsString =
|
||||
typeof rawTemplate === "string"
|
||||
@@ -89,7 +96,7 @@ export class TemplateSet {
|
||||
|
||||
getTemplate(name: string): Template {
|
||||
if (!(name in this.templates)) {
|
||||
const rawTemplate = this.getRawTemplate?.(name) || this.rawTemplates[name];
|
||||
const rawTemplate = this.rawTemplates[name];
|
||||
if (rawTemplate === undefined) {
|
||||
let extraInfo = "";
|
||||
try {
|
||||
@@ -106,7 +113,7 @@ export class TemplateSet {
|
||||
this.templates[name] = function (context, parent) {
|
||||
return templates[name].call(this, context, parent);
|
||||
};
|
||||
const template = templateFn(this, bdom, this.runtimeUtils);
|
||||
const template = templateFn(this, bdom, helpers);
|
||||
this.templates[name] = template;
|
||||
}
|
||||
return this.templates[name];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { OwlError } from "./error_handling";
|
||||
export type Callback = () => void;
|
||||
|
||||
/**
|
||||
@@ -35,43 +35,13 @@ export function inOwnerDocument(el?: HTMLElement) {
|
||||
return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given element is contained in a specific root documnet:
|
||||
* either directly or with a shadow root in between or in an iframe.
|
||||
*/
|
||||
function isAttachedToDocument(
|
||||
element: HTMLElement | ShadowRoot,
|
||||
documentElement: Document
|
||||
): boolean {
|
||||
let current: Node = element;
|
||||
const shadowRoot = documentElement.defaultView!.ShadowRoot;
|
||||
while (current) {
|
||||
if (current === documentElement) {
|
||||
return true;
|
||||
}
|
||||
if (current.parentNode) {
|
||||
current = current.parentNode;
|
||||
} else if (current instanceof shadowRoot && current.host) {
|
||||
current = current.host;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function validateTarget(target: HTMLElement | ShadowRoot) {
|
||||
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes
|
||||
const document = target && target.ownerDocument;
|
||||
if (document) {
|
||||
if (!document.defaultView) {
|
||||
throw new OwlError(
|
||||
"Cannot mount a component: the target document is not attached to a window (defaultView is missing)"
|
||||
);
|
||||
}
|
||||
const HTMLElement = document.defaultView.HTMLElement;
|
||||
const HTMLElement = document.defaultView!.HTMLElement;
|
||||
if (target instanceof HTMLElement || target instanceof ShadowRoot) {
|
||||
if (!isAttachedToDocument(target, document)) {
|
||||
if (!document.body.contains(target instanceof HTMLElement ? target : target.host)) {
|
||||
throw new OwlError("Cannot mount a component on a detached dom node");
|
||||
}
|
||||
return;
|
||||
@@ -111,50 +81,10 @@ export async function loadFile(url: string): Promise<string> {
|
||||
*/
|
||||
export class Markup extends String {}
|
||||
|
||||
export function htmlEscape(str: any): Markup {
|
||||
if (str instanceof Markup) {
|
||||
return str;
|
||||
}
|
||||
if (str === undefined) {
|
||||
return markup("");
|
||||
}
|
||||
if (typeof str === "number") {
|
||||
return markup(String(str));
|
||||
}
|
||||
[
|
||||
["&", "&"],
|
||||
["<", "<"],
|
||||
[">", ">"],
|
||||
["'", "'"],
|
||||
['"', """],
|
||||
["`", "`"],
|
||||
].forEach((pairs) => {
|
||||
str = String(str).replace(new RegExp(pairs[0], "g"), pairs[1]);
|
||||
});
|
||||
return markup(str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Marks a value as safe, that is, a value that can be injected as HTML directly.
|
||||
* It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
|
||||
*
|
||||
* If called as a tag function, the interpolated strings are escaped.
|
||||
*/
|
||||
export function markup(strings: TemplateStringsArray, ...placeholders: unknown[]): Markup;
|
||||
export function markup(value: string): Markup;
|
||||
export function markup(
|
||||
valueOrStrings: string | TemplateStringsArray,
|
||||
...placeholders: unknown[]
|
||||
): Markup {
|
||||
if (!Array.isArray(valueOrStrings)) {
|
||||
return new Markup(valueOrStrings);
|
||||
}
|
||||
const strings = valueOrStrings;
|
||||
let acc = "";
|
||||
let i = 0;
|
||||
for (; i < placeholders.length; ++i) {
|
||||
acc += strings[i] + htmlEscape(placeholders[i]);
|
||||
}
|
||||
acc += strings[i];
|
||||
return new Markup(acc);
|
||||
export function markup(value: any) {
|
||||
return new Markup(value);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { OwlError } from "./error_handling";
|
||||
import { toRaw } from "./reactivity";
|
||||
|
||||
type BaseType = { new (...args: any[]): any } | true | "*";
|
||||
type BaseType =
|
||||
| typeof String
|
||||
| typeof Boolean
|
||||
| typeof Number
|
||||
| typeof Date
|
||||
| typeof Object
|
||||
| typeof Array
|
||||
| true
|
||||
| "*";
|
||||
|
||||
interface TypeInfo {
|
||||
type?: TypeDescription;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
export const version = "2.8.1";
|
||||
export const version = "2.2.1";
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/><block-text-1/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['context'][ctx['props'].key].n;
|
||||
let d2 = ctx['state'].x;
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`ComponentC\`, {key: ctx['props'].key}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`ComponentB\`, {key: ctx['context'].key}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState destroyed component before being mounted is inactive 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -109,6 +155,69 @@ exports[`Reactivity: useState parent and children subscribed to same context 2`]
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState several nodes on different level use same context 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/> <block-text-1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['contextObj'].a;
|
||||
let d2 = ctx['contextObj'].b;
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState several nodes on different level use same context 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['contextObj'].b;
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState several nodes on different level use same context 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['contextObj'].a;
|
||||
let b2 = component(\`L3A\`, {}, key + \`__1\`, node, ctx);
|
||||
return block1([d1], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState several nodes on different level use same context 4`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`L2A\`, {}, key + \`__1\`, node, ctx);
|
||||
let b3 = component(\`L2B\`, {}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState two components are updated in parallel 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -244,7 +353,7 @@ exports[`Reactivity: useState useless atoms should be deleted 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(Object.keys(ctx['state']));;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`id\`] = k_block2[i1];
|
||||
ctx[\`id\`] = v_block2[i1];
|
||||
const key1 = ctx['id'];
|
||||
c_block2[i1] = withKey(comp1({id: ctx['id']}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
}
|
||||
|
||||
@@ -15,76 +15,6 @@ exports[`app App supports env with getters/setters 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`B\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
b2 = text(\`A\`);
|
||||
if (ctx['state'].value) {
|
||||
b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 3`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`B\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can add functions to the bdom 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { __globals__ } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"my-div\\" block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let hdlr1 = [()=>__globals__.plop('click'), ctx];
|
||||
return block1([hdlr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can call processTask twice in a row without crashing 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`parent\`);
|
||||
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can call processTask twice in a row without crashing 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can configure an app with props 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -99,19 +29,6 @@ exports[`app can configure an app with props 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can load templates from an object name-string 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"hello\\">hello</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can mount app in an iframe 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`destroy a subroot while another component is mounted in main app 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`ChildB\`, true, false, false, []);
|
||||
const comp2 = app.createComponent(\`ChildA\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
if (ctx['state'].flag) {
|
||||
b2 = comp1({}, key + \`__1\`, node, this, null);
|
||||
} else {
|
||||
b3 = comp2({}, key + \`__2\`, node, this, null);
|
||||
}
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`destroy a subroot while another component is mounted in main app 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block3 = createBlock(\`<div block-ref=\\"0\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`a\`);
|
||||
let ref1 = (el) => this.__owl__.setRef((\`elem\`), el);
|
||||
const b3 = block3([ref1]);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`destroy a subroot while another component is mounted in main app 3`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`c\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`destroy a subroot while another component is mounted in main app 4`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`b\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot by default, env is the same in sub root 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot by default, env is the same in sub root 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can create a root in a setup function, then use a hook 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`a\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can create a root in a setup function, then use a hook 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`c\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can mount subroot 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can mount subroot 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can mount subroot inside own dom 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can mount subroot inside own dom 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot env can be specified for sub roots 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot env can be specified for sub roots 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot subcomponents can be destroyed, and it properly cleanup the subroots 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot subcomponents can be destroyed, and it properly cleanup the subroots 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -1,15 +1,6 @@
|
||||
import { App, Component, mount, onWillPatch, onWillStart, useState, xml } from "../../src";
|
||||
import { App, Component, mount, xml } from "../../src";
|
||||
import { status } from "../../src/runtime/status";
|
||||
import {
|
||||
makeTestFixture,
|
||||
snapshotEverything,
|
||||
nextTick,
|
||||
elem,
|
||||
useLogLifecycle,
|
||||
makeDeferred,
|
||||
nextMicroTick,
|
||||
steps,
|
||||
} from "../helpers";
|
||||
import { makeTestFixture, snapshotEverything, nextTick, elem } from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
@@ -103,120 +94,4 @@ describe("app", () => {
|
||||
expect(iframeDoc.contains(div)).toBe(false);
|
||||
expect(status(comp)).toBe("destroyed");
|
||||
});
|
||||
|
||||
test("app: clear scheduler tasks and destroy cancelled nodes immediately on destroy", async () => {
|
||||
let def = makeDeferred();
|
||||
class B extends Component {
|
||||
static template = xml`B`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onWillStart(() => def);
|
||||
}
|
||||
}
|
||||
class A extends Component {
|
||||
static template = xml`A<t t-if="state.value"><B/></t>`;
|
||||
static components = { B };
|
||||
state = useState({ value: false });
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App(A);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:willRender",
|
||||
"A:rendered",
|
||||
"A:mounted",
|
||||
]
|
||||
`);
|
||||
|
||||
comp.state.value = true;
|
||||
await nextTick();
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"A:rendered",
|
||||
]
|
||||
`);
|
||||
|
||||
// rerender to force the instantiation of a new B component (and cancelling the first)
|
||||
comp.render();
|
||||
await nextMicroTick();
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"A:rendered",
|
||||
]
|
||||
`);
|
||||
|
||||
app.destroy();
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"A:willUnmount",
|
||||
"B:willDestroy",
|
||||
"A:willDestroy",
|
||||
"B:willDestroy",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("can load templates from an object name-string", async () => {
|
||||
const templates = {
|
||||
hello: `<div class="hello">hello</div>`,
|
||||
world: `<div>world</div>`,
|
||||
};
|
||||
class SomeComponent extends Component {
|
||||
static template = "hello";
|
||||
}
|
||||
|
||||
const app = new App(SomeComponent, { templates });
|
||||
await app.mount(fixture);
|
||||
expect(fixture.querySelector(".hello")).toBeDefined();
|
||||
// Only the "hello" template is used, so the "world" template is not yet loaded
|
||||
expect(Object.keys(app.templates)).toEqual(["hello"]);
|
||||
expect(Object.keys(app.rawTemplates)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
test("can call processTask twice in a row without crashing", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<div/>`;
|
||||
setup() {
|
||||
onWillPatch(() => app.scheduler.processTasks());
|
||||
}
|
||||
}
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`parent<Child/>`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
const app = new App(SomeComponent);
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("parent<div></div>");
|
||||
});
|
||||
|
||||
test("can add functions to the bdom", async () => {
|
||||
const steps: string[] = [];
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`<div t-on-click="() => __globals__.plop('click')" class="my-div"/>`;
|
||||
}
|
||||
const app = new App(SomeComponent, {
|
||||
globalValues: {
|
||||
plop: (string: any) => {
|
||||
steps.push(string);
|
||||
},
|
||||
},
|
||||
});
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div class="my-div"></div>`);
|
||||
fixture.querySelector("div")!.click();
|
||||
expect(steps).toEqual(["click"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
import { App, Component, onMounted, onWillDestroy, useRef, useState, xml } from "../../src";
|
||||
import { status } from "../../src/runtime/status";
|
||||
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`<div>main app</div>`;
|
||||
}
|
||||
|
||||
class SubComponent extends Component {
|
||||
static template = xml`<div>sub root</div>`;
|
||||
}
|
||||
|
||||
describe("subroot", () => {
|
||||
test("can mount subroot", async () => {
|
||||
const app = new App(SomeComponent);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div>");
|
||||
const subRoot = app.createRoot(SubComponent);
|
||||
const subcomp = await subRoot.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div><div>sub root</div>");
|
||||
|
||||
app.destroy();
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect(status(comp)).toBe("destroyed");
|
||||
expect(status(subcomp)).toBe("destroyed");
|
||||
});
|
||||
|
||||
test("can mount subroot inside own dom", async () => {
|
||||
const app = new App(SomeComponent);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div>");
|
||||
const subRoot = app.createRoot(SubComponent);
|
||||
const subcomp = await subRoot.mount(fixture.querySelector("div")!);
|
||||
expect(fixture.innerHTML).toBe("<div>main app<div>sub root</div></div>");
|
||||
|
||||
app.destroy();
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect(status(comp)).toBe("destroyed");
|
||||
expect(status(subcomp)).toBe("destroyed");
|
||||
});
|
||||
|
||||
test("by default, env is the same in sub root", async () => {
|
||||
let env, subenv;
|
||||
class SC extends SomeComponent {
|
||||
setup() {
|
||||
env = this.env;
|
||||
}
|
||||
}
|
||||
class Sub extends SubComponent {
|
||||
setup() {
|
||||
subenv = this.env;
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App(SC);
|
||||
await app.mount(fixture);
|
||||
const subRoot = app.createRoot(Sub);
|
||||
await subRoot.mount(fixture);
|
||||
|
||||
expect(env).toBeDefined();
|
||||
expect(subenv).toBeDefined();
|
||||
expect(env).toBe(subenv);
|
||||
});
|
||||
|
||||
test("env can be specified for sub roots", async () => {
|
||||
const env1 = { env1: true };
|
||||
const env2 = {};
|
||||
let someComponentEnv: any, subComponentEnv: any;
|
||||
class SC extends SomeComponent {
|
||||
setup() {
|
||||
someComponentEnv = this.env;
|
||||
}
|
||||
}
|
||||
class Sub extends SubComponent {
|
||||
setup() {
|
||||
subComponentEnv = this.env;
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App(SC, { env: env1 });
|
||||
await app.mount(fixture);
|
||||
const subRoot = app.createRoot(Sub, { env: env2 });
|
||||
await subRoot.mount(fixture);
|
||||
|
||||
// because env is different in app => it is given a sub object, frozen and all
|
||||
// not sure it is a good idea, but it's the way owl 2 works. maybe we should
|
||||
// avoid doing anything with the main env and let user code do it if they
|
||||
// want. in that case, we can change the test here to assert that they are equal
|
||||
expect(someComponentEnv).not.toBe(env1);
|
||||
expect(someComponentEnv!.env1).toBe(true);
|
||||
expect(subComponentEnv).toBe(env2);
|
||||
});
|
||||
|
||||
test("subcomponents can be destroyed, and it properly cleanup the subroots", async () => {
|
||||
const app = new App(SomeComponent);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div>");
|
||||
const root = app.createRoot(SubComponent);
|
||||
const subcomp = await root.mount(fixture.querySelector("div")!);
|
||||
expect(fixture.innerHTML).toBe("<div>main app<div>sub root</div></div>");
|
||||
|
||||
root.destroy();
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div>");
|
||||
expect(status(comp)).not.toBe("destroyed");
|
||||
expect(status(subcomp)).toBe("destroyed");
|
||||
});
|
||||
|
||||
test("can create a root in a setup function, then use a hook", async () => {
|
||||
class C extends Component {
|
||||
static template = xml`c`;
|
||||
}
|
||||
|
||||
class A extends Component {
|
||||
static template = xml`a`;
|
||||
state: any;
|
||||
setup() {
|
||||
app.createRoot(C);
|
||||
this.state = useState({ value: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App(A);
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("a");
|
||||
});
|
||||
});
|
||||
|
||||
test("destroy a subroot while another component is mounted in main app", async () => {
|
||||
class C extends Component {
|
||||
static template = xml`c`;
|
||||
}
|
||||
|
||||
class ChildA extends Component {
|
||||
static template = xml`a<div t-ref="elem"></div>`;
|
||||
ref: any;
|
||||
setup() {
|
||||
this.ref = useRef("elem");
|
||||
let root = app.createRoot(C);
|
||||
onMounted(() => {
|
||||
root.mount(this.ref.el);
|
||||
});
|
||||
onWillDestroy(() => {
|
||||
root.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
class ChildB extends Component {
|
||||
static template = xml`b`;
|
||||
}
|
||||
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`
|
||||
<t t-if="state.flag"><ChildB/></t>
|
||||
<t t-else=""><ChildA/></t>
|
||||
`;
|
||||
static components = { ChildA, ChildB };
|
||||
state = useState({ flag: false });
|
||||
}
|
||||
|
||||
const app = new App(SomeComponent);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("a<div></div>");
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("a<div>c</div>");
|
||||
comp.state.flag = true;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("b");
|
||||
});
|
||||
@@ -244,14 +244,12 @@ describe("misc", () => {
|
||||
});
|
||||
|
||||
test("namespace is not propagated to siblings", () => {
|
||||
const block = createBlock(`<div><svg xmlns="someNameSpace"><g/></svg><div></div></div>`);
|
||||
const block = createBlock(`<div><svg block-ns="someNameSpace"><g/></svg><div></div></div>`);
|
||||
|
||||
const fixture = makeTestFixture();
|
||||
mount(block(), fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<div><svg xmlns="someNameSpace"><g></g></svg><div></div></div>'
|
||||
);
|
||||
expect(fixture.innerHTML).toBe("<div><svg><g></g></svg><div></div></div>");
|
||||
expect(fixture.querySelector("svg")!.namespaceURI).toBe("someNameSpace");
|
||||
expect(fixture.querySelector("g")!.namespaceURI).toBe("someNameSpace");
|
||||
const allDivs = fixture.querySelectorAll("div");
|
||||
|
||||
@@ -30,22 +30,22 @@ describe("namespace", () => {
|
||||
expect(fixture.firstElementChild!.namespaceURI).toBe(XHTML_URI);
|
||||
});
|
||||
|
||||
test("namespace can be changed with xmlns", () => {
|
||||
const block = createBlock(`<tag xmlns="${SVG_URI}"/>`);
|
||||
test("namespace can be changed with block-ns", () => {
|
||||
const block = createBlock(`<tag block-ns="${SVG_URI}"/>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe(`<tag xmlns="${SVG_URI}"></tag>`);
|
||||
expect(fixture.innerHTML).toBe("<tag></tag>");
|
||||
expect(fixture.firstElementChild!.namespaceURI).toBe(SVG_URI);
|
||||
});
|
||||
|
||||
test("namespace is kept for children", () => {
|
||||
const block = createBlock(
|
||||
`<parent xmlns="${SVG_URI}"><child><subchild/></child><child/></parent>`
|
||||
`<parent block-ns="${SVG_URI}"><child><subchild/></child><child/></parent>`
|
||||
);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe(
|
||||
`<parent xmlns="${SVG_URI}"><child><subchild></subchild></child><child></child></parent>`
|
||||
"<parent><child><subchild></subchild></child><child></child></parent>"
|
||||
);
|
||||
const parent = fixture.firstElementChild!;
|
||||
const child1 = parent.firstElementChild!;
|
||||
@@ -58,10 +58,10 @@ describe("namespace", () => {
|
||||
});
|
||||
|
||||
test("various namespaces in same block", () => {
|
||||
const block = createBlock(`<none><one xmlns="one"/><two xmlns="two"/></none>`);
|
||||
const block = createBlock(`<none><one block-ns="one"/><two block-ns="two"/></none>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe('<none><one xmlns="one"></one><two xmlns="two"></two></none>');
|
||||
expect(fixture.innerHTML).toBe("<none><one></one><two></two></none>");
|
||||
const none = fixture.firstElementChild!;
|
||||
const one = none.firstElementChild!;
|
||||
const two = one.nextElementSibling!;
|
||||
|
||||
@@ -1,38 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`comments comment node with backslash at top level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return comment(\` \\\\\\\\ \`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`comments comment node with backtick at top-level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return comment(\` \\\\\` \`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`comments comment node with interpolation sigil at top level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return comment(\` \\\\\${very cool} \`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`comments only a comment 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -67,7 +34,7 @@ exports[`comments properly handle comments between t-if/t-else 1`] = `
|
||||
let block3 = createBlock(\`<span>owl</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (true) {
|
||||
b2 = block2();
|
||||
} else {
|
||||
|
||||
@@ -72,7 +72,7 @@ exports[`t-on can bind handlers with empty object (with non empty inner string)
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(['someval']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`action\`] = k_block2[i1];
|
||||
ctx[\`action\`] = v_block2[i1];
|
||||
ctx[\`action_index\`] = i1;
|
||||
const key1 = ctx['action_index'];
|
||||
const v1 = ctx['activate'];
|
||||
@@ -142,7 +142,7 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1]);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`value\`] = k_block1[i1];
|
||||
ctx[\`value\`] = v_block1[i1];
|
||||
const key1 = ctx['value'];
|
||||
let hdlr1 = [ctx['add'], ctx];
|
||||
c_block1[i1] = withKey(block2([hdlr1]), key1);
|
||||
@@ -189,11 +189,11 @@ exports[`t-on handler is bound to proper owner, part 4 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1]);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`value\`] = k_block1[i1];
|
||||
ctx[\`value\`] = v_block1[i1];
|
||||
ctx[\`value_first\`] = i1 === 0;
|
||||
ctx[\`value_last\`] = i1 === k_block1.length - 1;
|
||||
ctx[\`value_last\`] = i1 === v_block1.length - 1;
|
||||
ctx[\`value_index\`] = i1;
|
||||
ctx[\`value_value\`] = v_block1[i1];
|
||||
ctx[\`value_value\`] = k_block1[i1];
|
||||
const key1 = ctx['value'];
|
||||
c_block1[i1] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`), key1);
|
||||
}
|
||||
@@ -348,7 +348,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-f
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['projects']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`project\`] = k_block2[i1];
|
||||
ctx[\`project\`] = v_block2[i1];
|
||||
const key1 = ctx['project'];
|
||||
const v1 = ctx['onEdit'];
|
||||
const v2 = ctx['project'];
|
||||
|
||||
@@ -18,7 +18,7 @@ exports[`misc complex template 1`] = `
|
||||
let block13 = createBlock(\`<i class=\\"fa fa-fw fa-clock-o\\" title=\\"This commit is the head of a base branch\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3, b4, b6, b8;
|
||||
let b2,b3,b4,b6,b8;
|
||||
let attr1 = \`batch_tile \${ctx['options'].more?'more':'nomore'}\`;
|
||||
let attr2 = \`card bg-\${ctx['klass']}-light\`;
|
||||
let attr3 = \`/runbot/batch/\${ctx['batch'].id}\`;
|
||||
@@ -33,7 +33,7 @@ exports[`misc complex template 1`] = `
|
||||
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])));;
|
||||
for (let i1 = 0; i1 < l_block4; i1++) {
|
||||
ctx[\`slot\`] = k_block4[i1];
|
||||
ctx[\`slot\`] = v_block4[i1];
|
||||
const key1 = ctx['slot'].id;
|
||||
c_block4[i1] = withKey(comp1({class: ctx['slot_container'],slot: ctx['slot']}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ exports[`misc complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block6, v_block6, l_block6, c_block6] = prepareList([1,2,3,4]);;
|
||||
for (let i1 = 0; i1 < l_block6; i1++) {
|
||||
ctx[\`x\`] = k_block6[i1];
|
||||
ctx[\`x\`] = v_block6[i1];
|
||||
const key1 = ctx['x'];
|
||||
c_block6[i1] = withKey(block7(), key1);
|
||||
}
|
||||
@@ -51,9 +51,9 @@ exports[`misc complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block8, v_block8, l_block8, c_block8] = prepareList(ctx['commit_links']);;
|
||||
for (let i1 = 0; i1 < l_block8; i1++) {
|
||||
ctx[\`commit_link\`] = k_block8[i1];
|
||||
ctx[\`commit_link\`] = v_block8[i1];
|
||||
const key1 = ctx['commit_link'].id;
|
||||
let b10, b11, b12, b13;
|
||||
let b10,b11,b12,b13;
|
||||
let attr5 = \`/runbot/commit/\${ctx['commit_link'].commit_id}\`;
|
||||
let attr6 = \`badge badge-light batch_commit match_type_\${ctx['commit_link'].match_type}\`;
|
||||
if (ctx['commit_link'].match_type=='new') {
|
||||
@@ -99,11 +99,11 @@ exports[`misc global 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([4,5,6]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`value\`] = k_block2[i1];
|
||||
ctx[\`value\`] = v_block2[i1];
|
||||
ctx[\`value_first\`] = i1 === 0;
|
||||
ctx[\`value_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`value_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`value_index\`] = i1;
|
||||
ctx[\`value_value\`] = v_block2[i1];
|
||||
ctx[\`value_value\`] = k_block2[i1];
|
||||
const key1 = ctx['value'];
|
||||
let txt1 = ctx['value'];
|
||||
const b4 = block4([txt1]);
|
||||
@@ -112,16 +112,16 @@ exports[`misc global 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
setContextValue(ctx, \\"foo\\", 'aaa');
|
||||
const b7 = callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`);
|
||||
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`);
|
||||
ctx = ctx.__proto__;
|
||||
const b8 = callTemplate_2.call(this, ctx, node, key + \`__2__\${key1}\`);
|
||||
const b7 = callTemplate_2.call(this, ctx, node, key + \`__2__\${key1}\`);
|
||||
setContextValue(ctx, \\"foo\\", 'bbb');
|
||||
const b9 = callTemplate_3.call(this, ctx, node, key + \`__3__\${key1}\`);
|
||||
const b6 = multi([b7, b8, b9]);
|
||||
ctx[zero] = b6;
|
||||
const b5 = callTemplate_4.call(this, ctx, node, key + \`__4__\${key1}\`);
|
||||
const b8 = callTemplate_3.call(this, ctx, node, key + \`__3__\${key1}\`);
|
||||
const b5 = multi([b6, b7, b8]);
|
||||
ctx[zero] = b5;
|
||||
const b9 = callTemplate_4.call(this, ctx, node, key + \`__4__\${key1}\`);
|
||||
ctx = ctx.__proto__;
|
||||
c_block2[i1] = withKey(multi([b4, b5]), key1);
|
||||
c_block2[i1] = withKey(multi([b4, b9]), key1);
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
@@ -217,13 +217,13 @@ exports[`misc other complex template 1`] = `
|
||||
let block25 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b4, b14, b17, b22, b23, b24, b25;
|
||||
let b2,b4,b14,b17,b22,b23,b24,b25;
|
||||
let attr1 = \`/runbot/\${ctx['project'].slug}\`;
|
||||
let txt1 = ctx['project'].name;
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['projects']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`project\`] = k_block2[i1];
|
||||
ctx[\`project\`] = v_block2[i1];
|
||||
const key1 = ctx['project'].id;
|
||||
let hdlr1 = [ctx['selectProject'](ctx['project']), ctx];
|
||||
let txt2 = ctx['project'].name;
|
||||
@@ -232,12 +232,12 @@ exports[`misc other complex template 1`] = `
|
||||
ctx = ctx.__proto__;
|
||||
b2 = list(c_block2);
|
||||
if (ctx['user']) {
|
||||
let b5, b6;
|
||||
let b5,b6;
|
||||
if (ctx['user'].public) {
|
||||
let attr2 = \`/web/login?redirect=/\`;
|
||||
b5 = block5([attr2]);
|
||||
} else {
|
||||
let b7, b10, b13;
|
||||
let b7,b10,b13;
|
||||
if (ctx['nb_assigned_errors']&&ctx['nb_assigned_errors']>0) {
|
||||
let attr3 = \`You have \${ctx['nb_assigned_errors']} random bug assigned\`;
|
||||
let txt3 = ctx['nb_assigned_errors'];
|
||||
@@ -263,7 +263,7 @@ exports[`misc other complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']);;
|
||||
for (let i1 = 0; i1 < l_block15; i1++) {
|
||||
ctx[\`category\`] = k_block15[i1];
|
||||
ctx[\`category\`] = v_block15[i1];
|
||||
const key1 = ctx['category'].id;
|
||||
let attr6 = ctx['category'].id;
|
||||
let prop1 = new Boolean(ctx['category'].id==ctx['options'].active_category_id);
|
||||
@@ -284,7 +284,7 @@ exports[`misc other complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block18, v_block18, l_block18, c_block18] = prepareList(ctx['triggers']);;
|
||||
for (let i1 = 0; i1 < l_block18; i1++) {
|
||||
ctx[\`trigger\`] = k_block18[i1];
|
||||
ctx[\`trigger\`] = v_block18[i1];
|
||||
const key1 = ctx['trigger'].id;
|
||||
let b20;
|
||||
if (!ctx['trigger'].manual&&ctx['trigger'].project_id===ctx['project'].id&&ctx['trigger'].category_id===ctx['options'].active_category_id) {
|
||||
|
||||
@@ -12,7 +12,7 @@ exports[`memory t-foreach does not leak stuff in global scope 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
const key1 = ctx['item_index'];
|
||||
c_block2[i1] = withKey(text(ctx['item']), key1);
|
||||
|
||||
@@ -341,39 +341,6 @@ exports[`simple templates, mostly static template with t tag with multiple conte
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`simple templates, mostly static text node with backslash at top level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`\\\\\\\\\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`simple templates, mostly static text node with backtick at top-level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`\\\\\`\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`simple templates, mostly static text node with interpolation sigil at top level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`\\\\\${very cool}\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`simple templates, mostly static two t-escs next to each other 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -5,7 +5,7 @@ exports[`properly support svg add proper namespace to g tags 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<g xmlns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </g>\`);
|
||||
let block1 = createBlock(\`<g block-ns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </g>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -18,7 +18,7 @@ exports[`properly support svg add proper namespace to svg 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"100px\\" height=\\"90px\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </svg>\`);
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\" width=\\"100px\\" height=\\"90px\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -31,7 +31,7 @@ exports[`properly support svg namespace to g tags not added if already in svg na
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><g/></svg>\`);
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><g/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -44,7 +44,7 @@ exports[`properly support svg namespace to svg tags added even if already in svg
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><svg/></svg>\`);
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><svg/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -58,8 +58,8 @@ exports[`properly support svg svg creates new block if it is within html -- 2 1`
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block2 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/><block-child-0 xmlns=\\"\\"/></svg>\`);
|
||||
let block3 = createBlock(\`<path xmlns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
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;
|
||||
@@ -78,7 +78,7 @@ exports[`properly support svg svg creates new block if it is within html 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block2 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/></svg>\`);
|
||||
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 = \\"\\") {
|
||||
const b2 = block2();
|
||||
@@ -93,7 +93,7 @@ exports[`properly support svg svg namespace added to sub templates if root tag i
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const callTemplate_1 = app.getTemplate(\`path\`);
|
||||
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><block-child-0 xmlns=\\"\\"/></svg>\`);
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
@@ -107,7 +107,7 @@ exports[`properly support svg svg namespace added to sub templates if root tag i
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<path xmlns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
let block1 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -120,8 +120,8 @@ exports[`properly support svg svg namespace added to sub-blocks 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><block-child-0 xmlns=\\"\\"/></svg>\`);
|
||||
let block2 = createBlock(\`<path xmlns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||
let block2 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2;
|
||||
|
||||
@@ -61,19 +61,19 @@ exports[`t-call (template calling) call with several sub nodes on same line 1`]
|
||||
const callTemplate_1 = app.getTemplate(\`sub\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block4 = createBlock(\`<span>hey</span>\`);
|
||||
let block6 = createBlock(\`<span>yay</span>\`);
|
||||
let block3 = createBlock(\`<span>hey</span>\`);
|
||||
let block5 = createBlock(\`<span>yay</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b4 = block4();
|
||||
const b5 = text(\` \`);
|
||||
const b6 = block6();
|
||||
const b3 = multi([b4, b5, b6]);
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
const b3 = block3();
|
||||
const b4 = text(\` \`);
|
||||
const b5 = block5();
|
||||
const b2 = multi([b3, b4, b5]);
|
||||
ctx[zero] = b2;
|
||||
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b6]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -101,19 +101,19 @@ exports[`t-call (template calling) cascading t-call t-out='0' 1`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block4 = createBlock(\`<span>hey</span>\`);
|
||||
let block6 = createBlock(\`<span>yay</span>\`);
|
||||
let block3 = createBlock(\`<span>hey</span>\`);
|
||||
let block5 = createBlock(\`<span>yay</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b4 = block4();
|
||||
const b5 = text(\` \`);
|
||||
const b6 = block6();
|
||||
const b3 = multi([b4, b5, b6]);
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
const b3 = block3();
|
||||
const b4 = text(\` \`);
|
||||
const b5 = block5();
|
||||
const b2 = multi([b3, b4, b5]);
|
||||
ctx[zero] = b2;
|
||||
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b6]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -126,17 +126,17 @@ exports[`t-call (template calling) cascading t-call t-out='0' 2`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`subSubTemplate\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block4 = createBlock(\`<span>cascade 0</span>\`);
|
||||
let block3 = createBlock(\`<span>cascade 0</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b4 = block4();
|
||||
const b5 = ctx[zero];
|
||||
const b3 = multi([b4, b5]);
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
const b3 = block3();
|
||||
const b4 = ctx[zero];
|
||||
const b2 = multi([b3, b4]);
|
||||
ctx[zero] = b2;
|
||||
const b5 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -149,17 +149,17 @@ exports[`t-call (template calling) cascading t-call t-out='0' 3`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`finalTemplate\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block4 = createBlock(\`<span>cascade 1</span>\`);
|
||||
let block3 = createBlock(\`<span>cascade 1</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b4 = block4();
|
||||
const b5 = ctx[zero];
|
||||
const b3 = multi([b4, b5]);
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
const b3 = block3();
|
||||
const b4 = ctx[zero];
|
||||
const b2 = multi([b3, b4]);
|
||||
ctx[zero] = b2;
|
||||
const b5 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -186,17 +186,17 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
let block3 = createBlock(\`<span>hey</span>\`);
|
||||
let block5 = createBlock(\`<span>yay</span>\`);
|
||||
let block2 = createBlock(\`<span>hey</span>\`);
|
||||
let block4 = createBlock(\`<span>yay</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
const b4 = text(\` \`);
|
||||
const b5 = block5();
|
||||
const b2 = multi([b3, b4, b5]);
|
||||
ctx[zero] = b2;
|
||||
const b2 = block2();
|
||||
const b3 = text(\` \`);
|
||||
const b4 = block4();
|
||||
const b1 = multi([b2, b3, b4]);
|
||||
ctx[zero] = b1;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -209,15 +209,15 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subSubTemplate\`);
|
||||
|
||||
let block3 = createBlock(\`<span>cascade 0</span>\`);
|
||||
let block2 = createBlock(\`<span>cascade 0</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
const b4 = ctx[zero];
|
||||
const b2 = multi([b3, b4]);
|
||||
ctx[zero] = b2;
|
||||
const b2 = block2();
|
||||
const b3 = ctx[zero];
|
||||
const b1 = multi([b2, b3]);
|
||||
ctx[zero] = b1;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -230,15 +230,15 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`finalTemplate\`);
|
||||
|
||||
let block3 = createBlock(\`<span>cascade 1</span>\`);
|
||||
let block2 = createBlock(\`<span>cascade 1</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
const b4 = ctx[zero];
|
||||
const b2 = multi([b3, b4]);
|
||||
ctx[zero] = b2;
|
||||
const b2 = block2();
|
||||
const b3 = ctx[zero];
|
||||
const b1 = multi([b2, b3]);
|
||||
ctx[zero] = b1;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -342,15 +342,15 @@ exports[`t-call (template calling) nested t-calls with magic variable 0 1`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`grandchild\`);
|
||||
const callTemplate_2 = app.getTemplate(\`child\`);
|
||||
|
||||
let block3 = createBlock(\`<p>Some content...</p>\`);
|
||||
let block1 = createBlock(\`<p>Some content...</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
ctx[zero] = b3;
|
||||
const b1 = block1();
|
||||
ctx[zero] = b1;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
ctx = ctx.__proto__;
|
||||
ctx[zero] = b2;
|
||||
@@ -440,11 +440,11 @@ exports[`t-call (template calling) recursive template, part 2 2`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['node'].children||[]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`subtree\`] = k_block2[i1];
|
||||
ctx[\`subtree\`] = v_block2[i1];
|
||||
ctx[\`subtree_first\`] = i1 === 0;
|
||||
ctx[\`subtree_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`subtree_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`subtree_index\`] = i1;
|
||||
ctx[\`subtree_value\`] = v_block2[i1];
|
||||
ctx[\`subtree_value\`] = k_block2[i1];
|
||||
const key1 = ctx['subtree_index'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
@@ -495,11 +495,11 @@ exports[`t-call (template calling) recursive template, part 3 2`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['node'].children||[]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`subtree\`] = k_block2[i1];
|
||||
ctx[\`subtree\`] = v_block2[i1];
|
||||
ctx[\`subtree_first\`] = i1 === 0;
|
||||
ctx[\`subtree_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`subtree_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`subtree_index\`] = i1;
|
||||
ctx[\`subtree_value\`] = v_block2[i1];
|
||||
ctx[\`subtree_value\`] = k_block2[i1];
|
||||
const key1 = ctx['subtree_index'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
@@ -553,11 +553,11 @@ exports[`t-call (template calling) recursive template, part 4: with t-set recurs
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['node'].children||[]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`subtree\`] = k_block2[i1];
|
||||
ctx[\`subtree\`] = v_block2[i1];
|
||||
ctx[\`subtree_first\`] = i1 === 0;
|
||||
ctx[\`subtree_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`subtree_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`subtree_index\`] = i1;
|
||||
ctx[\`subtree_value\`] = v_block2[i1];
|
||||
ctx[\`subtree_value\`] = k_block2[i1];
|
||||
const key1 = ctx['subtree_index'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
@@ -571,134 +571,6 @@ exports[`t-call (template calling) recursive template, part 4: with t-set recurs
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-foreach 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, prepareList, withKey, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`i\`] = k_block2[i1];
|
||||
const key1 = ctx['i'];
|
||||
c_block2[i1] = withKey(text(\`1\`), key1);
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-foreach 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`sub\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-if false 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
let b3;
|
||||
if (false) {
|
||||
b3 = text(\`zero\`);
|
||||
}
|
||||
const b2 = multi([b3]);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-if false 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`sub\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-if true 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
let b3;
|
||||
if (true) {
|
||||
b3 = text(\`zero\`);
|
||||
}
|
||||
const b2 = multi([b3]);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-if true 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`sub\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-out with default 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, safeOutput, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = safeOutput(ctx['nothing']);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-out with default 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`sub\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) scoped parameters 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -833,13 +705,13 @@ exports[`t-call (template calling) t-call with body content as root of a templat
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`antony\`);
|
||||
|
||||
let block2 = createBlock(\`<p>antony</p>\`);
|
||||
let block1 = createBlock(\`<p>antony</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = block2();
|
||||
ctx[zero] = b2;
|
||||
const b1 = block1();
|
||||
ctx[zero] = b1;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -941,11 +813,11 @@ exports[`t-call (template calling) t-call with t-set inside and outside 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['list']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`v\`] = k_block2[i1];
|
||||
ctx[\`v\`] = v_block2[i1];
|
||||
ctx[\`v_first\`] = i1 === 0;
|
||||
ctx[\`v_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`v_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`v_index\`] = i1;
|
||||
ctx[\`v_value\`] = v_block2[i1];
|
||||
ctx[\`v_value\`] = k_block2[i1];
|
||||
const key1 = ctx['v_index'];
|
||||
setContextValue(ctx, \\"val\\", ctx['v'].val);
|
||||
ctx = Object.create(ctx);
|
||||
@@ -1008,11 +880,11 @@ exports[`t-call (template calling) t-call with t-set inside and outside. 2 2`] =
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['list']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`v\`] = k_block2[i1];
|
||||
ctx[\`v\`] = v_block2[i1];
|
||||
ctx[\`v_first\`] = i1 === 0;
|
||||
ctx[\`v_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`v_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`v_index\`] = i1;
|
||||
ctx[\`v_value\`] = v_block2[i1];
|
||||
ctx[\`v_value\`] = k_block2[i1];
|
||||
const key1 = ctx['v_index'];
|
||||
setContextValue(ctx, \\"val\\", ctx['v'].val);
|
||||
ctx = Object.create(ctx);
|
||||
@@ -1056,7 +928,7 @@ exports[`t-call (template calling) t-call, conditional and t-set in t-call body
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
setContextValue(ctx, \\"v1\\", 'elif');
|
||||
if (ctx['v1']==='if') {
|
||||
b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
@@ -1203,8 +1075,8 @@ exports[`t-call (template calling) with unused body 1`] = `
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = text(\`WHEEE\`);
|
||||
ctx[zero] = b2;
|
||||
const b1 = text(\`WHEEE\`);
|
||||
ctx[zero] = b1;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -1264,8 +1136,8 @@ exports[`t-call (template calling) with used body 1`] = `
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = text(\`ok\`);
|
||||
ctx[zero] = b2;
|
||||
const b1 = text(\`ok\`);
|
||||
ctx[zero] = b1;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`t-custom can use t-custom directive on a node 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"my-div\\" block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let hdlr1 = [ctx['click'], ctx];
|
||||
return block1([hdlr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-custom can use t-custom directive with modifiers on a node 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"my-div\\" block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let hdlr1 = [ctx['click'], ctx];
|
||||
return block1([hdlr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -49,27 +49,6 @@ exports[`debugging t-debug on sub template 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`debugging t-debug: interaction with t-set 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
debugger;
|
||||
setContextValue(ctx, \\"foo\\", 42);
|
||||
debugger;
|
||||
setContextValue(ctx, \\"bar\\", 49);
|
||||
let txt1 = ctx['foo']+ctx['bar'];
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`debugging t-log 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -87,24 +66,3 @@ exports[`debugging t-log 1`] = `
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`debugging t-log: interaction with t-set 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
console.log(ctx['foo']);
|
||||
setContextValue(ctx, \\"foo\\", 42);
|
||||
console.log(ctx['bar']);
|
||||
setContextValue(ctx, \\"bar\\", 49);
|
||||
let txt1 = ctx['foo']+ctx['bar'];
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -1,41 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`t-esc default with backslash at top level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withDefault } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(withDefault(undefined, \`\\\\\\\\\`));
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-esc default with backtick at top-level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withDefault } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(withDefault(undefined, \`\\\\\`\`));
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-esc default with interpolation sigil at top level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withDefault } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(withDefault(undefined, \`\\\\\${very cool}\`));
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-esc div with falsy values 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -216,15 +180,15 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`sub\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<p>escaped</p>\`);
|
||||
let block2 = createBlock(\`<p>escaped</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
const b2 = block2();
|
||||
ctx[zero] = b2;
|
||||
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -12,7 +12,7 @@ exports[`t-foreach does not pollute the rendering context 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
c_block2[i1] = withKey(text(ctx['item']), key1);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ exports[`t-foreach iterate on items (on a element node) 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([1,2]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
let txt1 = ctx['item'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
@@ -58,9 +58,9 @@ exports[`t-foreach iterate on items 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block2[i1];
|
||||
ctx[\`item_value\`] = k_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
const b4 = text(\` [\`);
|
||||
const b5 = text(ctx['item_index']);
|
||||
@@ -87,9 +87,9 @@ exports[`t-foreach iterate, Map param 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
@@ -115,9 +115,9 @@ exports[`t-foreach iterate, Set param 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
@@ -145,9 +145,9 @@ exports[`t-foreach iterate, dict param 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['value']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block2[i1];
|
||||
ctx[\`item_value\`] = k_block2[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b4 = text(\` [\`);
|
||||
const b5 = text(ctx['item_index']);
|
||||
@@ -174,9 +174,9 @@ exports[`t-foreach iterate, generator param 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['gen']());;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
@@ -202,9 +202,9 @@ exports[`t-foreach iterate, iterable param 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['map'].values());;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
@@ -232,12 +232,12 @@ exports[`t-foreach iterate, position 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(Array(5));;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`elem\`] = k_block2[i1];
|
||||
ctx[\`elem\`] = v_block2[i1];
|
||||
ctx[\`elem_first\`] = i1 === 0;
|
||||
ctx[\`elem_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`elem_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`elem_index\`] = i1;
|
||||
const key1 = ctx['elem'];
|
||||
let b4, b5, b6, b7, b8, b9;
|
||||
let b4,b5,b6,b7,b8,b9;
|
||||
b4 = text(\` -\`);
|
||||
if (ctx['elem_first']) {
|
||||
b5 = text(\` first\`);
|
||||
@@ -256,34 +256,6 @@ exports[`t-foreach iterate, position 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, string param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList('abc');;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach simple iteration (in a node) 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -296,7 +268,7 @@ exports[`t-foreach simple iteration (in a node) 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
c_block2[i1] = withKey(text(ctx['item']), key1);
|
||||
}
|
||||
@@ -316,7 +288,7 @@ exports[`t-foreach simple iteration 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
const key1 = ctx['item'];
|
||||
c_block1[i1] = withKey(text(ctx['item']), key1);
|
||||
}
|
||||
@@ -338,7 +310,7 @@ exports[`t-foreach simple iteration with two nodes inside 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
const key1 = ctx['item'];
|
||||
let txt1 = ctx['item'];
|
||||
const b3 = block3([txt1]);
|
||||
@@ -367,20 +339,20 @@ exports[`t-foreach t-call with body in t-foreach in t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['numbers']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`a\`] = k_block2[i1];
|
||||
ctx[\`a\`] = v_block2[i1];
|
||||
ctx[\`a_first\`] = i1 === 0;
|
||||
ctx[\`a_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`a_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`a_index\`] = i1;
|
||||
ctx[\`a_value\`] = v_block2[i1];
|
||||
ctx[\`a_value\`] = k_block2[i1];
|
||||
const key1 = ctx['a'];
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['letters']);;
|
||||
for (let i2 = 0; i2 < l_block4; i2++) {
|
||||
ctx[\`b\`] = k_block4[i2];
|
||||
ctx[\`b\`] = v_block4[i2];
|
||||
ctx[\`b_first\`] = i2 === 0;
|
||||
ctx[\`b_last\`] = i2 === k_block4.length - 1;
|
||||
ctx[\`b_last\`] = i2 === v_block4.length - 1;
|
||||
ctx[\`b_index\`] = i2;
|
||||
ctx[\`b_value\`] = v_block4[i2];
|
||||
ctx[\`b_value\`] = k_block4[i2];
|
||||
const key2 = ctx['b'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
@@ -436,20 +408,20 @@ exports[`t-foreach t-call without body in t-foreach in t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['numbers']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`a\`] = k_block2[i1];
|
||||
ctx[\`a\`] = v_block2[i1];
|
||||
ctx[\`a_first\`] = i1 === 0;
|
||||
ctx[\`a_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`a_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`a_index\`] = i1;
|
||||
ctx[\`a_value\`] = v_block2[i1];
|
||||
ctx[\`a_value\`] = k_block2[i1];
|
||||
const key1 = ctx['a'];
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['letters']);;
|
||||
for (let i2 = 0; i2 < l_block4; i2++) {
|
||||
ctx[\`b\`] = k_block4[i2];
|
||||
ctx[\`b\`] = v_block4[i2];
|
||||
ctx[\`b_first\`] = i2 === 0;
|
||||
ctx[\`b_last\`] = i2 === k_block4.length - 1;
|
||||
ctx[\`b_last\`] = i2 === v_block4.length - 1;
|
||||
ctx[\`b_index\`] = i2;
|
||||
ctx[\`b_value\`] = v_block4[i2];
|
||||
ctx[\`b_value\`] = k_block4[i2];
|
||||
const key2 = ctx['b'];
|
||||
c_block4[i2] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}__\${key2}\`), key2);
|
||||
}
|
||||
@@ -503,12 +475,12 @@ exports[`t-foreach t-foreach in t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['numbers']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`number\`] = k_block2[i1];
|
||||
ctx[\`number\`] = v_block2[i1];
|
||||
const key1 = ctx['number'];
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block3, v_block3, l_block3, c_block3] = prepareList(ctx['letters']);;
|
||||
for (let i2 = 0; i2 < l_block3; i2++) {
|
||||
ctx[\`letter\`] = k_block3[i2];
|
||||
ctx[\`letter\`] = v_block3[i2];
|
||||
const key2 = ctx['letter'];
|
||||
const b5 = text(\` [\`);
|
||||
const b6 = text(ctx['number']);
|
||||
@@ -537,7 +509,7 @@ exports[`t-foreach t-foreach with t-if inside (no external node) 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['elems']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`elem\`] = k_block1[i1];
|
||||
ctx[\`elem\`] = v_block1[i1];
|
||||
const key1 = ctx['elem'].id;
|
||||
let b3;
|
||||
if (ctx['elem'].id<3) {
|
||||
@@ -564,7 +536,7 @@ exports[`t-foreach t-foreach with t-if inside 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['elems']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`elem\`] = k_block2[i1];
|
||||
ctx[\`elem\`] = v_block2[i1];
|
||||
const key1 = ctx['elem'].id;
|
||||
let b4;
|
||||
if (ctx['elem'].id<3) {
|
||||
@@ -592,7 +564,7 @@ exports[`t-foreach t-key on t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['things']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`thing\`] = k_block2[i1];
|
||||
ctx[\`thing\`] = v_block2[i1];
|
||||
const key1 = ctx['thing'];
|
||||
c_block2[i1] = withKey(block3(), key1);
|
||||
}
|
||||
@@ -615,7 +587,7 @@ exports[`t-foreach throws error if invalid loop expression 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['abc']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
const key1 = ctx['item'];
|
||||
const tKey_1 = ctx['item_index'];
|
||||
@@ -642,7 +614,7 @@ exports[`t-foreach with t-memo 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['items']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
const key1 = ctx['item'].id;
|
||||
const memo1 = [ctx['item'].x];
|
||||
const vnode1 = cache[key1];;
|
||||
|
||||
@@ -8,7 +8,7 @@ exports[`t-if a t-if next to a div 1`] = `
|
||||
let block2 = createBlock(\`<div>foo</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
b2 = block2();
|
||||
if (ctx['cond']) {
|
||||
b3 = text(\`1\`);
|
||||
@@ -44,7 +44,7 @@ exports[`t-if boolean value condition elif (no outside node) 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3, b4, b5;
|
||||
let b2,b3,b4,b5;
|
||||
if (ctx['color']=='black') {
|
||||
b2 = text(\`black pearl\`);
|
||||
} else if (ctx['color']=='yellow') {
|
||||
@@ -67,7 +67,7 @@ exports[`t-if boolean value condition elif 1`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-2/><block-child-3/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3, b4, b5;
|
||||
let b2,b3,b4,b5;
|
||||
if (ctx['color']=='black') {
|
||||
b2 = text(\`black pearl\`);
|
||||
} else if (ctx['color']=='yellow') {
|
||||
@@ -90,7 +90,7 @@ exports[`t-if boolean value condition else 1`] = `
|
||||
let block1 = createBlock(\`<div><span>begin</span><block-child-0/><block-child-1/><span>end</span></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['condition']) {
|
||||
b2 = text(\`ok\`);
|
||||
} else {
|
||||
@@ -109,7 +109,7 @@ exports[`t-if boolean value condition false else 1`] = `
|
||||
let block1 = createBlock(\`<div><span>begin</span><block-child-0/><block-child-1/><span>end</span></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['condition']) {
|
||||
b2 = text(\`fail\`);
|
||||
} else {
|
||||
@@ -145,7 +145,7 @@ exports[`t-if can use some boolean operators in expressions 1`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-2/><block-child-3/><block-child-4/><block-child-5/><block-child-6/><block-child-7/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3, b4, b5, b6, b7, b8, b9;
|
||||
let b2,b3,b4,b5,b6,b7,b8,b9;
|
||||
if (ctx['cond1']&&ctx['cond2']) {
|
||||
b2 = text(\`and\`);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ exports[`t-if simple t-if/t-else 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['condition']) {
|
||||
b2 = text(\`1\`);
|
||||
} else {
|
||||
@@ -258,7 +258,7 @@ exports[`t-if simple t-if/t-else in a div 1`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['condition']) {
|
||||
b2 = text(\`1\`);
|
||||
} else {
|
||||
@@ -277,7 +277,7 @@ exports[`t-if t-esc with t-elif 1`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (false) {
|
||||
b2 = text(\`abc\`);
|
||||
} else {
|
||||
@@ -314,7 +314,7 @@ exports[`t-if t-if and t-else with two nodes 1`] = `
|
||||
let block5 = createBlock(\`<span>b</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['condition']) {
|
||||
b2 = text(\`1\`);
|
||||
} else {
|
||||
@@ -372,7 +372,7 @@ exports[`t-if t-if with empty content 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
b2 = text(\`hello\`);
|
||||
if (ctx['condition']) {
|
||||
b3 = text(\`\`);
|
||||
@@ -388,7 +388,7 @@ exports[`t-if t-if/t-else with more content 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['condition']) {
|
||||
if (ctx['condition']) {
|
||||
b2 = text(\`asf\`);
|
||||
@@ -458,7 +458,7 @@ exports[`t-if t-set, then t-if, part 3 1`] = `
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
setContextValue(ctx, \\"y\\", false);
|
||||
setContextValue(ctx, \\"x\\", ctx['y']);
|
||||
if (ctx['x']) {
|
||||
@@ -477,7 +477,7 @@ exports[`t-if two consecutive t-if 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['cond1']) {
|
||||
b2 = text(\`1\`);
|
||||
}
|
||||
@@ -497,7 +497,7 @@ exports[`t-if two consecutive t-if in a div 1`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['cond1']) {
|
||||
b2 = text(\`1\`);
|
||||
}
|
||||
@@ -520,7 +520,7 @@ exports[`t-if two t-ifs next to each other 1`] = `
|
||||
let block5 = createBlock(\`<p>2</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['condition']) {
|
||||
let txt1 = ctx['text'];
|
||||
b2 = block2([txt1]);
|
||||
|
||||
@@ -58,7 +58,7 @@ exports[`t-key t-key directive in a list 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['beers']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`beer\`] = k_block2[i1];
|
||||
ctx[\`beer\`] = v_block2[i1];
|
||||
const key1 = ctx['beer'].id;
|
||||
let txt1 = ctx['beer'].name;
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
@@ -79,7 +79,7 @@ exports[`t-key t-key on sub dom node pushes a child block in its parent 1`] = `
|
||||
let block3 = createBlock(\`<div><h1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['hasSpan']) {
|
||||
b2 = block2();
|
||||
}
|
||||
@@ -103,18 +103,3 @@ exports[`t-key t-key on sub dom node pushes a child block in its parent 2`] = `
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-key t-key: interaction with t-esc 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<p><block-text-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const tKey_1 = ctx['key'];
|
||||
let txt1 = ctx['text'];
|
||||
return toggler(tKey_1, block1([txt1]));
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -35,15 +35,15 @@ exports[`t-out multiple calls to t-out 1`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`sub\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<span>coucou</span>\`);
|
||||
let block2 = createBlock(\`<span>coucou</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
const b2 = block2();
|
||||
ctx[zero] = b2;
|
||||
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -102,15 +102,15 @@ exports[`t-out t-out 0 1`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`_basic-callee\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<div>zero</div>\`);
|
||||
let block2 = createBlock(\`<div>zero</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
const b2 = block2();
|
||||
ctx[zero] = b2;
|
||||
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -309,7 +309,7 @@ exports[`t-out t-out switch markup on bdom 1`] = `
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
let b3, b5;
|
||||
let b3,b5;
|
||||
ctx[\`bdom\`] = new LazyValue(value1, ctx, this, node, key);
|
||||
if (ctx['hasBdom']) {
|
||||
const b4 = safeOutput(ctx['bdom']);
|
||||
|
||||
@@ -105,7 +105,7 @@ exports[`t-ref refs in a loop 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['items']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
const tKey_1 = ctx['item'];
|
||||
const v1 = ctx['item'];
|
||||
|
||||
@@ -1,50 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`t-set body with backslash at top level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"value\\", \`\\\\\\\\\`);
|
||||
return text(ctx['value']);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set body with backtick at top-level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"value\\", \`\\\\\`\`);
|
||||
return text(ctx['value']);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set body with interpolation sigil at top level 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"value\\", \`\\\\\${very cool}\`);
|
||||
return text(ctx['value']);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set evaluate value expression 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -139,7 +94,7 @@ exports[`t-set set from body literal (with t-if/t-else 1`] = `
|
||||
let { isBoundary, withDefault, LazyValue } = helpers;
|
||||
|
||||
function value1(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
let b2,b3;
|
||||
if (ctx['condition']) {
|
||||
b2 = text(\`true\`);
|
||||
} else {
|
||||
@@ -396,7 +351,7 @@ exports[`t-set t-set outside modified in t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(['a','b']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`val\`] = k_block2[i1];
|
||||
ctx[\`val\`] = v_block2[i1];
|
||||
const key1 = ctx['val'];
|
||||
let txt1 = ctx['iter'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
@@ -426,7 +381,7 @@ exports[`t-set t-set outside modified in t-foreach increment-after operator 1`]
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(['a','b']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`val\`] = k_block2[i1];
|
||||
ctx[\`val\`] = v_block2[i1];
|
||||
const key1 = ctx['val'];
|
||||
let txt1 = ctx['iter'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
@@ -456,7 +411,7 @@ exports[`t-set t-set outside modified in t-foreach increment-before operator 1`]
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(['a','b']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`val\`] = k_block2[i1];
|
||||
ctx[\`val\`] = v_block2[i1];
|
||||
const key1 = ctx['val'];
|
||||
let txt1 = ctx['iter'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
@@ -486,7 +441,7 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['list']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`elem\`] = k_block2[i1];
|
||||
ctx[\`elem\`] = v_block2[i1];
|
||||
ctx[\`elem_index\`] = i1;
|
||||
const key1 = ctx['elem_index'];
|
||||
let txt1 = ctx['v'];
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`t-slot compile t-props correctly multiple time 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { callSlot } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return callSlot(ctx, node, key, 'default', false, Object.assign({}, {a:1}));
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -101,55 +101,3 @@ exports[`loading templates can load a few templates from an XMLDocument 2`] = `
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`loading templates getTemplate: element returned (2) 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>Hello World!</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`loading templates getTemplate: element returned 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>Hello World!</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`loading templates getTemplate: template string returned 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>Hello World!</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`loading templates getTemplate: undefined returned 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>Hello World!</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -1,144 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`translation context body of t-sets are translated in context 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"label\\", \`traduit\`);
|
||||
return text(ctx['label']);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation context default slot params and content translated in context 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { callSlot } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
function defaultContent1(ctx, node, key = \\"\\") {
|
||||
return text(\` foo \`);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b3 = callSlot(ctx, node, key, 'default', false, {param: \`param\`,title: \`título\`}, defaultContent1.bind(this));
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation context props with modifier .translate are translated in context 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`ChildComponent\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return comp1({text: \`jeu\`}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation context props with modifier .translate are translated in context 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['props'].text;
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation context slot attrs and text contents are translated in context 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { capture, markRaw } = helpers;
|
||||
const comp1 = app.createComponent(\`ChildComponent\`, true, true, false, []);
|
||||
|
||||
function slot1(ctx, node, key = \\"\\") {
|
||||
return text(\`jeu\`);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx1 = capture(ctx);
|
||||
return comp1({slots: markRaw({'a': {__render: slot1.bind(this), __ctx: ctx1, title: \`título\`}})}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation context slot attrs and text contents are translated in context 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { callSlot } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = callSlot(ctx, node, key, 'a', false, {});
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation context t-translation-context with several children 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><div/><div/><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2;
|
||||
if (true) {
|
||||
b2 = text(\`\`);
|
||||
}
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation context translation of attributes in context 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div title=\\"titre\\" label=\\"game\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation context translation of text in context 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block2 = createBlock(\`<div>word</div>\`);
|
||||
let block3 = createBlock(\`<div>mot</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = block2();
|
||||
const b3 = block3();
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation support body of t-sets are translated 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -169,21 +30,6 @@ exports[`translation support body of t-sets inside translation=off are not trans
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation support body of t-sets inside translation=off are not translated 2 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"label\\", \`untranslated\`);
|
||||
return text(ctx['label']);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation support body of t-sets with html content are translated 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -295,23 +141,6 @@ exports[`translation support t-set and falsy t-value: t-body are translated 1`]
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation support t-translation with several children 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><div/><div/><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2;
|
||||
if (true) {
|
||||
b2 = text(\`\`);
|
||||
}
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -26,19 +26,4 @@ describe("comments", () => {
|
||||
</div>`;
|
||||
expect(renderToString(template)).toBe("<div><span>true</span></div>");
|
||||
});
|
||||
|
||||
test("comment node with backslash at top level", () => {
|
||||
const template = "<!-- \\ -->";
|
||||
expect(renderToString(template)).toBe("<!-- \\ -->");
|
||||
});
|
||||
|
||||
test("comment node with backtick at top-level", () => {
|
||||
const template = "<!-- ` -->";
|
||||
expect(renderToString(template)).toBe("<!-- ` -->");
|
||||
});
|
||||
|
||||
test("comment node with interpolation sigil at top level", () => {
|
||||
const template = "<!-- ${very cool} -->";
|
||||
expect(renderToString(template)).toBe("<!-- ${very cool} -->");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -174,9 +174,6 @@ describe("expression evaluation", () => {
|
||||
expect(compileExpr("list.data.map((data) => data)")).toBe(
|
||||
"ctx['list'].data.map((_data)=>_data)"
|
||||
);
|
||||
expect(compileExpr("(ev) => { myFunc(v1, v2, ev.target.value); }")).toBe(
|
||||
"(_ev)=>{ctx['myFunc'](ctx['v1'],ctx['v2'],_ev.target.value);}"
|
||||
);
|
||||
});
|
||||
test.skip("arrow functions: not yet supported", () => {
|
||||
// e is added to localvars in inline_expression but not removed after the arrow func body
|
||||
|
||||
@@ -43,7 +43,6 @@ describe("qweb parser", () => {
|
||||
dynamicTag: null,
|
||||
content: [],
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -71,7 +70,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -86,7 +84,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -101,7 +98,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -113,7 +109,6 @@ describe("qweb parser", () => {
|
||||
tag: "span",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -133,7 +128,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -145,7 +139,6 @@ describe("qweb parser", () => {
|
||||
tag: "span",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -163,7 +156,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -189,7 +181,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -210,7 +201,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -233,7 +223,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -257,7 +246,6 @@ describe("qweb parser", () => {
|
||||
tag: "span",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -274,7 +262,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: { class: "abc" },
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -293,7 +280,6 @@ describe("qweb parser", () => {
|
||||
height: "90px",
|
||||
width: "100px",
|
||||
},
|
||||
attrsTranslationCtx: null,
|
||||
content: [
|
||||
{
|
||||
attrs: {
|
||||
@@ -304,7 +290,6 @@ describe("qweb parser", () => {
|
||||
stroke: "green",
|
||||
"stroke-width": "1",
|
||||
},
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
@@ -327,7 +312,6 @@ describe("qweb parser", () => {
|
||||
parse(`<g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/></g>`)
|
||||
).toEqual({
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [
|
||||
{
|
||||
attrs: {
|
||||
@@ -338,7 +322,6 @@ describe("qweb parser", () => {
|
||||
stroke: "green",
|
||||
"stroke-width": "1",
|
||||
},
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
@@ -365,7 +348,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
content: [
|
||||
@@ -374,7 +356,6 @@ describe("qweb parser", () => {
|
||||
tag: "pre",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
content: [],
|
||||
@@ -410,7 +391,6 @@ describe("qweb parser", () => {
|
||||
tag: "span",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -433,7 +413,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -476,7 +455,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -491,7 +469,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -512,7 +489,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -554,7 +530,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -632,7 +607,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -652,7 +626,6 @@ describe("qweb parser", () => {
|
||||
tag: "h1",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -666,7 +639,6 @@ describe("qweb parser", () => {
|
||||
tag: "h2",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -692,7 +664,6 @@ describe("qweb parser", () => {
|
||||
value: "value",
|
||||
defaultValue: null,
|
||||
body: null,
|
||||
hasNoRepresentation: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -703,7 +674,6 @@ describe("qweb parser", () => {
|
||||
defaultValue: "ok",
|
||||
value: null,
|
||||
body: null,
|
||||
hasNoRepresentation: true,
|
||||
});
|
||||
|
||||
expect(parse(`<t t-set="v"><div>ok</div></t>`)).toEqual({
|
||||
@@ -715,7 +685,6 @@ describe("qweb parser", () => {
|
||||
{
|
||||
type: ASTType.DomNode,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
@@ -725,7 +694,6 @@ describe("qweb parser", () => {
|
||||
content: [{ type: ASTType.Text, value: "ok" }],
|
||||
},
|
||||
],
|
||||
hasNoRepresentation: true,
|
||||
});
|
||||
|
||||
expect(parse(`<t t-set="v"><div>ok</div>abc</t>`)).toEqual({
|
||||
@@ -737,7 +705,6 @@ describe("qweb parser", () => {
|
||||
{
|
||||
type: ASTType.DomNode,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
@@ -748,7 +715,6 @@ describe("qweb parser", () => {
|
||||
},
|
||||
{ type: ASTType.Text, value: "abc" },
|
||||
],
|
||||
hasNoRepresentation: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -762,7 +728,6 @@ describe("qweb parser", () => {
|
||||
defaultValue: "ok",
|
||||
value: null,
|
||||
body: null,
|
||||
hasNoRepresentation: true,
|
||||
},
|
||||
tElif: null,
|
||||
tElse: null,
|
||||
@@ -777,7 +742,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -788,14 +752,7 @@ describe("qweb parser", () => {
|
||||
condition: "flag",
|
||||
content: { type: ASTType.Text, value: "1" },
|
||||
tElif: null,
|
||||
tElse: {
|
||||
type: ASTType.TSet,
|
||||
name: "ourvar",
|
||||
value: "0",
|
||||
defaultValue: null,
|
||||
body: null,
|
||||
hasNoRepresentation: true,
|
||||
},
|
||||
tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -854,7 +811,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -897,7 +853,6 @@ describe("qweb parser", () => {
|
||||
tag: "span",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -932,7 +887,6 @@ describe("qweb parser", () => {
|
||||
tag: "span",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -966,7 +920,6 @@ describe("qweb parser", () => {
|
||||
"t-att-selected": "category.id==options.active_category_id",
|
||||
"t-att-value": "category.id",
|
||||
},
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -987,7 +940,6 @@ describe("qweb parser", () => {
|
||||
).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -1035,7 +987,6 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
@@ -1059,7 +1010,6 @@ describe("qweb parser", () => {
|
||||
name: "Comp",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
slots: null,
|
||||
on: null,
|
||||
},
|
||||
@@ -1149,7 +1099,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -1190,7 +1139,6 @@ describe("qweb parser", () => {
|
||||
tag: "button",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: { click: "add" },
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -1227,7 +1175,6 @@ describe("qweb parser", () => {
|
||||
tag: "select",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
content: [
|
||||
@@ -1236,7 +1183,6 @@ describe("qweb parser", () => {
|
||||
tag: "option",
|
||||
dynamicTag: null,
|
||||
attrs: { value: "1" },
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
content: [],
|
||||
@@ -1266,7 +1212,6 @@ describe("qweb parser", () => {
|
||||
tag: "select",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
content: [
|
||||
@@ -1275,7 +1220,6 @@ describe("qweb parser", () => {
|
||||
tag: "option",
|
||||
dynamicTag: null,
|
||||
attrs: { "t-att-value": "valueVar" },
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
content: [],
|
||||
@@ -1307,7 +1251,6 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
on: null,
|
||||
slots: null,
|
||||
isDynamic: false,
|
||||
@@ -1320,7 +1263,6 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: { a: "1", b: "'b'" },
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: null,
|
||||
@@ -1333,7 +1275,6 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: "state",
|
||||
props: { a: "1" },
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: null,
|
||||
@@ -1346,7 +1287,6 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: { click: "someMethod" },
|
||||
slots: null,
|
||||
@@ -1389,14 +1329,12 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: {
|
||||
default: {
|
||||
content: { type: ASTType.Text, value: "foo" },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
@@ -1412,14 +1350,12 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: {
|
||||
default: {
|
||||
content: { type: ASTType.Text, value: "foo" },
|
||||
attrs: { param: "param" },
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
@@ -1434,7 +1370,6 @@ describe("qweb parser", () => {
|
||||
isDynamic: false,
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
on: null,
|
||||
slots: {
|
||||
default: {
|
||||
@@ -1446,7 +1381,6 @@ describe("qweb parser", () => {
|
||||
tag: "span",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -1458,7 +1392,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -1468,7 +1401,6 @@ describe("qweb parser", () => {
|
||||
],
|
||||
},
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
@@ -1483,11 +1415,9 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
on: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
slots: {
|
||||
mySlot: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
@@ -1504,16 +1434,9 @@ describe("qweb parser", () => {
|
||||
isDynamic: false,
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
on: null,
|
||||
slots: {
|
||||
name: {
|
||||
content: { type: ASTType.Text, value: "foo" },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
name: { content: { type: ASTType.Text, value: "foo" }, attrs: null, on: null, scope: null },
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1525,13 +1448,11 @@ describe("qweb parser", () => {
|
||||
isDynamic: false,
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
on: null,
|
||||
slots: {
|
||||
name: {
|
||||
content: { type: ASTType.Text, value: "foo" },
|
||||
attrs: { param: "param" },
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
@@ -1548,14 +1469,12 @@ describe("qweb parser", () => {
|
||||
isDynamic: false,
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
on: null,
|
||||
slots: {
|
||||
name: {
|
||||
content: { type: ASTType.Text, value: "foo" },
|
||||
on: { click: "doStuff" },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
scope: null,
|
||||
},
|
||||
},
|
||||
@@ -1574,24 +1493,16 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: {
|
||||
default: {
|
||||
content: { type: ASTType.Text, value: " " },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
name: {
|
||||
content: { type: ASTType.Text, value: "foo" },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
name: { content: { type: ASTType.Text, value: "foo" }, attrs: null, on: null, scope: null },
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1607,24 +1518,11 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: {
|
||||
a: {
|
||||
content: { type: ASTType.Text, value: "foo" },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
b: {
|
||||
content: { type: ASTType.Text, value: "bar" },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
a: { content: { type: ASTType.Text, value: "foo" }, attrs: null, on: null, scope: null },
|
||||
b: { content: { type: ASTType.Text, value: "bar" }, attrs: null, on: null, scope: null },
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1635,7 +1533,6 @@ describe("qweb parser", () => {
|
||||
name: "myComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: true,
|
||||
on: null,
|
||||
slots: null,
|
||||
@@ -1648,7 +1545,6 @@ describe("qweb parser", () => {
|
||||
name: "mycomponent",
|
||||
dynamicProps: null,
|
||||
props: { a: "1", b: "'b'" },
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: true,
|
||||
on: null,
|
||||
slots: null,
|
||||
@@ -1661,7 +1557,6 @@ describe("qweb parser", () => {
|
||||
name: "mycomponent",
|
||||
dynamicProps: "state",
|
||||
props: { a: "1" },
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: true,
|
||||
on: null,
|
||||
slots: null,
|
||||
@@ -1692,14 +1587,12 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: {
|
||||
default: {
|
||||
content: { body: null, name: "subTemplate", type: ASTType.TCall, context: null },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
scope: null,
|
||||
on: null,
|
||||
},
|
||||
@@ -1720,13 +1613,11 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: {
|
||||
default: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
content: {
|
||||
@@ -1735,13 +1626,11 @@ describe("qweb parser", () => {
|
||||
name: "Child",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
on: null,
|
||||
slots: {
|
||||
brol: {
|
||||
content: { type: ASTType.Text, value: "coucou" },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
scope: null,
|
||||
on: null,
|
||||
},
|
||||
@@ -1765,13 +1654,11 @@ describe("qweb parser", () => {
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
isDynamic: false,
|
||||
on: null,
|
||||
slots: {
|
||||
default: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
content: {
|
||||
@@ -1780,13 +1667,11 @@ describe("qweb parser", () => {
|
||||
name: "Child",
|
||||
dynamicProps: null,
|
||||
props: null,
|
||||
propsTranslationCtx: null,
|
||||
on: null,
|
||||
slots: {
|
||||
brol: {
|
||||
content: { type: ASTType.Text, value: "coucou" },
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
@@ -1806,7 +1691,6 @@ describe("qweb parser", () => {
|
||||
type: ASTType.TSlot,
|
||||
name: "default",
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
defaultContent: null,
|
||||
});
|
||||
@@ -1817,7 +1701,6 @@ describe("qweb parser", () => {
|
||||
type: ASTType.TSlot,
|
||||
name: "header",
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
defaultContent: { type: ASTType.Text, value: "default content" },
|
||||
});
|
||||
@@ -1828,7 +1711,6 @@ describe("qweb parser", () => {
|
||||
type: ASTType.TSlot,
|
||||
name: "default",
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: { "click.prevent": "doSomething" },
|
||||
defaultContent: null,
|
||||
});
|
||||
@@ -1846,7 +1728,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -1865,7 +1746,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
@@ -1885,7 +1765,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: "name",
|
||||
model: null,
|
||||
@@ -1900,7 +1779,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: "name",
|
||||
model: null,
|
||||
@@ -1917,7 +1795,6 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
on: null,
|
||||
ref: "name",
|
||||
model: null,
|
||||
@@ -1954,7 +1831,6 @@ describe("qweb parser", () => {
|
||||
body: {
|
||||
content: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.Text,
|
||||
@@ -1983,190 +1859,6 @@ describe("qweb parser", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('t-translation="off": interaction with t-esc', async () => {
|
||||
expect(parse(`<span t-esc="a" t-translation="off"/>`)).toEqual({
|
||||
type: ASTType.TTranslation,
|
||||
content: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [
|
||||
{
|
||||
defaultValue: "",
|
||||
expr: "a",
|
||||
type: ASTType.TEsc,
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
tag: "span",
|
||||
type: ASTType.DomNode,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('t-translation="off": interaction with t-out', async () => {
|
||||
expect(parse(`<span t-out="a" t-translation="off"/>`)).toEqual({
|
||||
type: ASTType.TTranslation,
|
||||
content: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [
|
||||
{
|
||||
body: null,
|
||||
expr: "a",
|
||||
type: ASTType.TOut,
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
tag: "span",
|
||||
type: ASTType.DomNode,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// t-translation-context
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('t-translation-context="fr"', async () => {
|
||||
expect(parse(`<t t-translation-context="fr">word</t>`)).toEqual({
|
||||
type: ASTType.TTranslationContext,
|
||||
content: {
|
||||
type: ASTType.Text,
|
||||
value: "word",
|
||||
},
|
||||
translationCtx: "fr",
|
||||
});
|
||||
|
||||
expect(parse(`<div t-translation-context="fr">word</div>`)).toEqual({
|
||||
content: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [
|
||||
{
|
||||
type: 0,
|
||||
value: "word",
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
tag: "div",
|
||||
type: ASTType.DomNode,
|
||||
},
|
||||
translationCtx: "fr",
|
||||
type: ASTType.TTranslationContext,
|
||||
});
|
||||
});
|
||||
|
||||
test("t-translation-context: interaction with t-esc", async () => {
|
||||
expect(parse(`<span t-esc="a" t-translation-context="fr"/>`)).toEqual({
|
||||
type: ASTType.TTranslationContext,
|
||||
content: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [
|
||||
{
|
||||
defaultValue: "",
|
||||
expr: "a",
|
||||
type: ASTType.TEsc,
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
tag: "span",
|
||||
type: ASTType.DomNode,
|
||||
},
|
||||
translationCtx: "fr",
|
||||
});
|
||||
});
|
||||
|
||||
test("t-translation-context: interaction with t-out", async () => {
|
||||
expect(parse(`<span t-out="a" t-translation-context="fr"/>`)).toEqual({
|
||||
type: ASTType.TTranslationContext,
|
||||
content: {
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [
|
||||
{
|
||||
body: null,
|
||||
expr: "a",
|
||||
type: ASTType.TOut,
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
tag: "span",
|
||||
type: ASTType.DomNode,
|
||||
},
|
||||
translationCtx: "fr",
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// t-translation-context-attr
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('t-translation-context="fr" and t-translation-context-title="pt" for a div attr title', async () => {
|
||||
expect(
|
||||
parse(
|
||||
`<div t-translation-context="fr" title="hello" t-translation-context-title="pt">word</div>`
|
||||
)
|
||||
).toEqual({
|
||||
content: {
|
||||
attrs: { title: "hello" },
|
||||
attrsTranslationCtx: { title: "pt" },
|
||||
content: [
|
||||
{
|
||||
type: 0,
|
||||
value: "word",
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: null,
|
||||
ref: null,
|
||||
tag: "div",
|
||||
type: ASTType.DomNode,
|
||||
},
|
||||
translationCtx: "fr",
|
||||
type: ASTType.TTranslationContext,
|
||||
});
|
||||
});
|
||||
|
||||
test('t-translation-context-title="fr" for component prop title', async () => {
|
||||
expect(parse(`<Comp title="hello" t-translation-context-title="fr" />`)).toEqual({
|
||||
dynamicProps: null,
|
||||
isDynamic: false,
|
||||
name: "Comp",
|
||||
on: null,
|
||||
props: {
|
||||
title: "hello",
|
||||
},
|
||||
propsTranslationCtx: {
|
||||
title: "fr",
|
||||
},
|
||||
slots: null,
|
||||
type: ASTType.TComponent,
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// t-model
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -2174,7 +1866,6 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<input t-model="state.stuff" />`)).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
on: null,
|
||||
ref: null,
|
||||
@@ -2195,7 +1886,6 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<input t-model="state['stuff']" />`)).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
on: null,
|
||||
ref: null,
|
||||
@@ -2216,7 +1906,6 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<input t-model.lazy.trim.number="state.stuff" />`)).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
on: null,
|
||||
ref: null,
|
||||
@@ -2238,7 +1927,6 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<textarea t-model="state.stuff" />`)).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
on: null,
|
||||
ref: null,
|
||||
@@ -2259,7 +1947,6 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<input type="checkbox" t-model="state.stuff" />`)).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: { type: "checkbox" },
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
on: null,
|
||||
ref: null,
|
||||
@@ -2280,7 +1967,6 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<input type="radio" t-model="state.stuff" />`)).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: { type: "radio" },
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
on: null,
|
||||
ref: null,
|
||||
@@ -2301,7 +1987,6 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<input type="radio" t-model.lazy.trim.number="state.stuff" />`)).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: { type: "radio" },
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
on: null,
|
||||
ref: null,
|
||||
@@ -2327,7 +2012,6 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<div t-tag="theTag" />`)).toEqual({
|
||||
type: ASTType.DomNode,
|
||||
attrs: null,
|
||||
attrsTranslationCtx: null,
|
||||
content: [],
|
||||
on: null,
|
||||
ref: null,
|
||||
|
||||
@@ -154,19 +154,4 @@ describe("simple templates, mostly static", () => {
|
||||
</div>`;
|
||||
expect(renderToString(template, { a: "a", b: "b", c: "c" })).toBe("<div>abLoadingc</div>");
|
||||
});
|
||||
|
||||
test("text node with backslash at top level", () => {
|
||||
const template = "\\";
|
||||
expect(renderToString(template)).toBe("\\");
|
||||
});
|
||||
|
||||
test("text node with backtick at top-level", () => {
|
||||
const template = "`";
|
||||
expect(renderToString(template)).toBe("`");
|
||||
});
|
||||
|
||||
test("text node with interpolation sigil at top level", () => {
|
||||
const template = "${very cool}";
|
||||
expect(renderToString(template)).toBe("${very cool}");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,20 +9,20 @@ describe("properly support svg", () => {
|
||||
test("add proper namespace to svg", () => {
|
||||
const template = `<svg width="100px" height="90px"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </svg>`;
|
||||
expect(renderToString(template)).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" width="100px" height="90px"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"></circle> </svg>`
|
||||
`<svg width=\"100px\" height=\"90px\"><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </svg>`
|
||||
);
|
||||
});
|
||||
|
||||
test("add proper namespace to g tags", () => {
|
||||
const template = `<g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </g>`;
|
||||
expect(renderToString(template)).toBe(
|
||||
`<g xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"></circle> </g>`
|
||||
`<g><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </g>`
|
||||
);
|
||||
});
|
||||
|
||||
test("namespace to g tags not added if already in svg namespace", () => {
|
||||
const template = `<svg><g/></svg>`;
|
||||
expect(renderToString(template)).toBe(`<svg xmlns="http://www.w3.org/2000/svg"><g></g></svg>`);
|
||||
expect(renderToString(template)).toBe(`<svg><g></g></svg>`);
|
||||
});
|
||||
|
||||
test("namespace to svg tags added even if already in svg namespace", () => {
|
||||
@@ -41,13 +41,8 @@ describe("properly support svg", () => {
|
||||
test("svg namespace added to sub-blocks", () => {
|
||||
const template = `<svg><path t-if="path"/></svg>`;
|
||||
|
||||
expect(renderToString(template, { path: false })).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg"></svg>`
|
||||
);
|
||||
// Because the path is its own block, it has its own xmlns attribute
|
||||
expect(renderToString(template, { path: true })).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg"><path xmlns="http://www.w3.org/2000/svg"></path></svg>`
|
||||
);
|
||||
expect(renderToString(template, { path: false })).toBe(`<svg></svg>`);
|
||||
expect(renderToString(template, { path: true })).toBe(`<svg><path></path></svg>`);
|
||||
|
||||
const bdom = renderToBdom(template, { path: true });
|
||||
const fixture = makeTestFixture();
|
||||
|
||||
@@ -430,48 +430,6 @@ describe("t-call (template calling)", () => {
|
||||
expect(context.renderToString("main")).toBe(expected);
|
||||
});
|
||||
|
||||
test("root t-call with body: t-if true", () => {
|
||||
const context = new TestContext();
|
||||
const subTemplate = `sub`;
|
||||
const main = `<t t-call="subTemplate"><t t-if="true">zero</t></t>`;
|
||||
context.addTemplate("subTemplate", subTemplate);
|
||||
context.addTemplate("main", main);
|
||||
const expected = "sub";
|
||||
expect(context.renderToString("main")).toBe(expected);
|
||||
});
|
||||
|
||||
test("root t-call with body: t-if false", () => {
|
||||
const context = new TestContext();
|
||||
const subTemplate = `sub`;
|
||||
const main = `<t t-call="subTemplate"><t t-if="false">zero</t></t>`;
|
||||
context.addTemplate("subTemplate", subTemplate);
|
||||
context.addTemplate("main", main);
|
||||
const expected = "sub";
|
||||
expect(context.renderToString("main")).toBe(expected);
|
||||
});
|
||||
|
||||
test("root t-call with body: t-out with default", () => {
|
||||
const context = new TestContext();
|
||||
const subTemplate = `sub`;
|
||||
const main = `<t t-call="subTemplate"><t t-out="nothing">default</t></t>`;
|
||||
context.addTemplate("subTemplate", subTemplate);
|
||||
context.addTemplate("main", main);
|
||||
const expected = "sub";
|
||||
expect(context.renderToString("main")).toBe(expected);
|
||||
});
|
||||
|
||||
test("root t-call with body: t-foreach", () => {
|
||||
const context = new TestContext();
|
||||
const subTemplate = `sub`;
|
||||
const main = `<t t-call="subTemplate">
|
||||
<t t-foreach="[1]" t-as="i" t-key="i">1</t>
|
||||
</t>`;
|
||||
context.addTemplate("subTemplate", subTemplate);
|
||||
context.addTemplate("main", main);
|
||||
const expected = "sub";
|
||||
expect(context.renderToString("main")).toBe(expected);
|
||||
});
|
||||
|
||||
test("dynamic t-call", () => {
|
||||
const context = new TestContext();
|
||||
const foo = `<foo><t t-esc="val"/></foo>`;
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { App, Component, xml } from "../../src";
|
||||
import { makeTestFixture, snapshotEverything } from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("t-custom", () => {
|
||||
test("can use t-custom directive on a node", async () => {
|
||||
const steps: string[] = [];
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`<div t-custom-plop="click" class="my-div"/>`;
|
||||
click() {
|
||||
steps.push("clicked");
|
||||
}
|
||||
}
|
||||
const app = new App(SomeComponent, {
|
||||
customDirectives: {
|
||||
plop: (node, value) => {
|
||||
node.setAttribute("t-on-click", value);
|
||||
},
|
||||
},
|
||||
});
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div class="my-div"></div>`);
|
||||
fixture.querySelector("div")!.click();
|
||||
expect(steps).toEqual(["clicked"]);
|
||||
});
|
||||
|
||||
test("can use t-custom directive with modifiers on a node", async () => {
|
||||
const steps: string[] = [];
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`<div t-custom-plop.mouse.stop="click" class="my-div"/>`;
|
||||
click() {
|
||||
steps.push("clicked");
|
||||
}
|
||||
}
|
||||
const app = new App(SomeComponent, {
|
||||
customDirectives: {
|
||||
plop: (node, value, modifiers) => {
|
||||
node.setAttribute("t-on-click", value);
|
||||
for (let mod of modifiers) {
|
||||
steps.push(mod);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div class="my-div"></div>`);
|
||||
fixture.querySelector("div")!.click();
|
||||
expect(steps).toEqual(["mouse", "stop", "clicked"]);
|
||||
});
|
||||
});
|
||||