Compare commits

..

19 Commits

Author SHA1 Message Date
Géry Debongnie 3d0f046d9a [REL] v1.0.0
Finally!!
2020-01-09 14:37:36 +01:00
Géry Debongnie 4c75bae0e8 [IMP] owl: improve npm packaging
closes #611
2020-01-09 14:35:13 +01:00
Géry Debongnie 54f5819ef9 [ADD] hooks: add useExternalListener hook
It is very useful.

Also, this commit prettifies the code.

closes #608
2020-01-09 14:06:30 +01:00
Joseph Caburnay 0c9c5b877b [FIX] playground: not all completed is deleted
Before this fix, not all completed tasks are deleted when clicking clear
completed button because we are looping to a mutated array. Looping thru
a copy of the mutated array fixes this problem.
2020-01-09 09:40:13 +01:00
Joseph Caburnay b7b0e38fca [FIX] docs: some spelling corrections 2020-01-09 09:40:13 +01:00
mcm-odoo 74bd119bf5 [IMP] styles: handle & selector
This commit adds the support of the & selector.
This selector is useful to join a subrule with a parent selector.

example:

button {
  &:hover {
    background-color: red;
  }
}

will give

button:hover {
  background-color: red;
}
2020-01-09 09:37:27 +01:00
Géry Debongnie 4a0d04e4ae [FIX] debug tools: do not crash on non json-stringifiable props
closes #595
2020-01-09 09:24:50 +01:00
Géry Debongnie 49931104a1 [CLEANUP] update snapshots 2020-01-08 16:05:22 +01:00
Lucas Perais (lpe) 21d1306ec2 [FIX] qweb: t-foreach needs to hold on scope
Have a t-on within a t-foreach
the t-on has an expression

Before this commit, when triggering the t-on, the expression was falsely evaluated
In details, the expression took scope from outside the foreach loop
as we protect it to have similar results than an actual for loop

But, at triggering time, the scope protection had already been terminated
meaning the info of the iteration of the loop the handler has been built in
had already disappeared

This commit fixes that

closes #594
2020-01-08 15:45:44 +01:00
Lucas Perais (lpe) 21737d33fa [FIX] qweb: variables set outside foreach must be altered by inloop t-tset
In a template, have t-set t-value outside a t-foreach
in the t-foreach, alter that variable by resetting it (as for a incrementation variable)

Before this commit, when printing the variable when the loop had finished
its value was the one set in the first place

After this commit, the value becomes the one altered by the loop iterations

closes #598
2020-01-08 15:45:44 +01:00
Géry Debongnie 23012f3e7c [FIX] component: multiple t-calls
Before this commit, owl could crash in some specific situations:
multiple t-calls with sub components, outside a loop.  The reason is
that the t-call did not generate a key, so from the point of view of the
children component, it had the same key, even though it was at a
different place in a template.

Also, with this fix, we can fix the playground responsive example.

closes #602
2020-01-08 15:24:43 +01:00
Géry Debongnie 55889febd5 [FIX] qweb: add support for "in " operator in expressions
closes #603
2020-01-08 15:20:01 +01:00
mcm-odoo 0af46bc123 [FIX] styles: fix selector generation
This commit fixes the css selector generation for nested rules

Before this fix, a selector like:

.parent {
	.child-a, .child-b {
		color: red;
	}
}

was generated as:

.parent .child-a, .child-b {
	color: red;
}

Now it generates:

.parent .child-a, .parent .child-b {
	color: red;
}
2019-12-27 12:31:17 +01:00
Géry Debongnie b63cd4c9d9 [REL] v1.0.0-beta5 2019-12-20 11:26:53 +01:00
Géry Debongnie 953778dc50 [IMP] component/tags: add inline css tag
This add an important feature: defining completely standalone owl
components, with the template/style and javascript code together.

closes #284
2019-12-20 11:20:10 +01:00
Géry Debongnie 4f61d9f1e0 [FIX] component: no crash if empty props
The expression parser properly format an empty string into an empty
string. But then, this empty string was injected in the props object of
a component, and the compiled code looked like this:

let props5 = { val: };

So, in this case, we simply put undefined, since there are no props
value.

closes #587
2019-12-19 14:32:10 +01:00
Sébastien Theys bc2c7edff4 [FIX] store: prevent crash when selector returns null
Because `typeof null === 'object'`.
2019-12-19 14:24:36 +01:00
Géry Debongnie c8d9c0b50e [DOC] add npm install instruction 2019-12-19 14:12:00 +01:00
Géry Debongnie 15b25fd838 [IMP] owl: prepare owl to be released on npm
closes #196
2019-12-19 14:09:41 +01:00
34 changed files with 1645 additions and 137 deletions
+8 -2
View File
@@ -102,10 +102,16 @@ Submit a PR!
## Installing/Building
Owl can be installed with the following command:
```
npm install @odoo/owl
```
If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.0.0-beta4.js](https://github.com/odoo/owl/releases/download/v1.0.0-beta4/owl.js)
- [owl-1.0.0-beta4.min.js](https://github.com/odoo/owl/releases/download/v1.0.0-beta4/owl.min.js)
- [owl-1.0.0.js](https://github.com/odoo/owl/releases/download/v1.0.0/owl.js)
- [owl-1.0.0.min.js](https://github.com/odoo/owl/releases/download/v1.0.0/owl.min.js)
Some npm scripts are available:
+9 -9
View File
@@ -56,15 +56,15 @@ useState Link
config RouteComponent
mode Router
core tags
EventBus xml
Observer utils
hooks debounce
onWillStart escape
onMounted loadJS
onWillUpdateProps loadFile
onWillPatch shallowEqual
onPatched whenReady
onWillUnmount
EventBus css
Observer xml
hooks utils
onWillStart debounce
onMounted escape
onWillUpdateProps loadJS
onWillPatch loadFile
onPatched shallowEqual
onWillUnmount whenReady
useContext
useState
useRef
+5 -1
View File
@@ -225,6 +225,10 @@ to be called in the constructor.
}
```
- **`style`** (string, optional): it should be the return value of the [`css` tag](tags.md#css-tag),
which is used to inject stylesheet whenever the component is visible on the
screen.
There is another static property defined on the `Component` class: `current`.
This property is set to the currently being defined component (in the constructor).
This is the way [hooks](hooks.md) are able to get a reference to the target
@@ -421,7 +425,7 @@ likely via a change in its state/props or environment).
This method is not called on the initial render. It is useful to interact
with the DOM (for example, through an external library) whenever the
component was patched. Note that this hook will not be called if the compoent is
component was patched. Note that this hook will not be called if the component is
not in the DOM.
Updating the component state in this hook is possible, but not encouraged.
+2 -2
View File
@@ -23,7 +23,7 @@ a rendering that is no longer relevant, restart it, reuse it in some cases.
But even though using concurrency is quite simple (and is the default behaviour),
asynchrony is difficult, because it introduces an additional dimension that
vastly increase the complexity of an application. This section will explain
how Owl manages this complexity, how concuurent rendering works in a general way.
how Owl manages this complexity, how concurrent rendering works in a general way.
## Rendering Components
@@ -120,7 +120,7 @@ Here is what Owl will do:
1. hook `willUpdateProps` is called on `D` (async)
2. template `D` is rerendered
- component `F` is created:
1. hook `willStart` is called on `E` (async)
1. hook `willStart` is called on `F` (async)
2. template `F` is rendered
3. `willPatch` hooks are called recursively on components `C`, `D` (not on `F`,
+2 -2
View File
@@ -40,8 +40,8 @@ all templates.
Whenever a root component `App` is mounted, Owl will setup a valid environment by
following the next steps:
- take the `env` object defined on `App.env` (if no `env` was explicitely setup,
this will be return the empty `env` object defined on `Component`)
- take the `env` object defined on `App.env` (if no `env` was explicitly setup,
this will return the empty `env` object defined on `Component`)
- if `env.qweb` is not set, then Owl will create a `QWeb` instance.
The correct way to customize an environment is to simply set it up on the root
+12
View File
@@ -17,6 +17,7 @@
- [`useContext`](#usecontext)
- [`useRef`](#useref)
- [`useSubEnv`](#usesubenv)
- [`useExternalListener`](#useexternallistener)
- [`useStore`](#usestore)
- [`useDispatch`](#usedispatch)
- [`useGetters`](#usegetters)
@@ -354,6 +355,17 @@ will be added to the parent environment. Note that it will extend, not replace
the parent environment. And of course, the parent environment will not be
affected.
### `useExternalListener`
The `useExternalListener` hook helps solve a very common problem: adding and removing
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);
```
### `useStore`
The `useStore` hook is the entry point for a component to connect to the store.
+134 -4
View File
@@ -4,16 +4,19 @@
- [Overview](#overview)
- [`xml` tag](#xml-tag)
- [`css` tag](#css-tag)
## Overview
Tags are very small helpers to make it easy to write inline templates. There is
only one currently available tag: `xml`, but we plan to add other tags later,
such as a `css` tag, which will be used to write [single file components](../tooling.md#single-file-component).
Tags are very small helpers intended to make it easy to write inline templates
or styles. There are currently two tags: `css` and `xml`. With these functions,
it is possible to write [single file components](../tooling.md#single-file-component).
## XML tag
Without tags, creating a standalone component would look like this:
The `xml` tag is certainly the most useful tag. It is used to define an inline
QWeb template for a component. Without tags, creating a standalone component
would look like this:
```js
import { Component } from 'owl'
@@ -52,3 +55,130 @@ class MyComponent extends Component {
...
}
```
## CSS tag
The CSS tag is useful to define a css stylesheet in the javascript file:
```js
class MyComponent extends Component {
static template = xml`
<div class="my-component">some template</div>
`;
static css`
.my-component {
color: red;
}
`;
}
```
The `css` tag registers internally the css information. Then, whenever the first
instance of the component is created, will add a `<style>` tag to the document
`<head>`.
Note that to make it more useful, like other css preprocessors, the `css` tag
accepts a small extension of the css specification: css scopes can be nested,
and the rules will then be expanded by the `css` helper:
```scss
.my-component {
display: block;
.sub-component h {
color: red;
}
}
```
will be formatted as:
```css
.my-component {
display: block;
}
.my-component .sub-component h {
color: red;
}
```
This extension brings another useful feature: the `&` selector which refers to
the parent selector. For example, we want our component to be red when hovered.
We would like to write something like:
```scss
.my-component {
display: block;
:hover {
color: red;
}
}
```
but it will be formatted as:
```css
.my-component {
display: block;
}
.my-component :hover {
color: red;
}
```
The `&` selector can be used to solve this problem:
```scss
.my-component {
display: block;
&:hover {
color: red;
}
}
```
will be formatted as:
```css
.my-component {
display: block;
}
.my-component:hover {
color: red;
}
```
Now, there is no additional processing done by the `css` tag. However, since it
is done in javascript at runtime, we actually have more power. For example:
1. sharing values between javascript and css:
```js
import { theme } from "./theme";
class MyComponent extends Component {
static template = xml`<div class="my-component">...</div>`;
static style = css`
.my-component {
color: ${theme.MAIN_COLOR};
background-color: ${theme.SECONDARY_color};
}
`;
}
```
2. scoping rules to the current component:
```js
import { generateUUID } from "./utils";
const uuid = generateUUID();
class MyComponent extends Component {
static template = xml`<div data-o-${uuid}="">...</div>`;
static style = css`
[data-o-${uuid}] {
color: red;
}
`;
}
```
+21 -10
View File
@@ -43,32 +43,43 @@ useful to compare various performance metrics on some tasks.
It is very useful to group code by feature instead of by type of file. It makes
it easier to scale application to larger size.
To do so, Owl currently has a small helper that makes it easy to define a
template inside a javascript (or typescript) file: the [`xml`](reference/tags.md#xml-tag)
helper. With this, a template is automatically registered to [QWeb](reference/qweb_engine.md).
To do so, Owl has two small helpers that make it easy to define a
template or a stylesheet inside a javascript (or typescript) file: the
[`xml`](reference/tags.md#xml-tag) and [`css`](reference/tags.md#css-tag)
helper.
This means that the template and the javascript code can be defined in the same
file. It is not currently possible to add css to the same file, but Owl may
get a `css` tag helper later.
This means that the template, the style and the javascript code can be defined in
the same file. For example:
```js
const { Component } = owl;
const { xml } = owl.tags;
const { xml, css } = owl.tags;
// -----------------------------------------------------------------------------
// TEMPLATE
// -----------------------------------------------------------------------------
const TEMPLATE = xml/* xml */ `
<div class="main two-columns">
<div class="main">
<Sidebar/>
<Content />
</div>`;
// -----------------------------------------------------------------------------
// STYLE
// -----------------------------------------------------------------------------
const STYLE = css/* css */ `
.main {
display: grid;
grid-template-columns: 200px auto;
}
`;
// -----------------------------------------------------------------------------
// CODE
// -----------------------------------------------------------------------------
class MyComponent extends Component {
class Main extends Component {
static template = TEMPLATE;
static style = STYLE;
static components = { Sidebar, Content };
// rest of component...
@@ -91,7 +102,7 @@ logging useful information is extremely valuable. There is a [javascript file](.
Once it is executed, it will log a lot of information on each component main hooks. The following code is a minified version to make it easier to copy/paste:
```
function debugOwl(t,n){let e,o="[OWL_DEBUG]";function s(t){let n=JSON.stringify(t||{});return n.length>200&&(n=n.slice(0,200)+"..."),n}if(Object.defineProperty(t.Component,"current",{get:()=>e,set(i){e=i;const r=i.constructor.name;if(n.componentBlackList&&n.componentBlackList.test(r))return;if(n.componentWhiteList&&!n.componentWhiteList.test(r))return;let l;Object.defineProperty(e,"__owl__",{get:()=>l,set(e){!function(e,i,r){let l=`${i}<id=${r}>`,c=t=>console.log(`${o} ${l} ${t}`),u=t=>(!n.methodBlackList||!n.methodBlackList.includes(t))&&!(n.methodWhiteList&&!n.methodWhiteList.includes(t));u("constructor")&&c(`constructor, props=${s(e.props)}`);u("willStart")&&t.hooks.onWillStart(()=>{c("willStart")});u("mounted")&&t.hooks.onMounted(()=>{c("mounted")});u("willUpdateProps")&&t.hooks.onWillUpdateProps(t=>{c(`willUpdateProps, nextprops=${s(t)}`)});u("willPatch")&&t.hooks.onWillPatch(()=>{c("willPatch")});u("patched")&&t.hooks.onPatched(()=>{c("patched")});u("willUnmount")&&t.hooks.onWillUnmount(()=>{c("willUnmount")});const d=e.__render.bind(e);e.__render=function(...t){c("rendering template"),d(...t)};const h=e.render.bind(e);e.render=function(...t){const n=e.__owl__;let o="render";return n.isMounted||n.currentFiber||(o+=" (warning: component is not mounted, this render has no effect)"),c(o),h(...t)};const p=e.mount.bind(e);e.mount=function(...t){return c("mount"),p(...t)}}(i,r,(l=e).id)}})}}),n.logScheduler){let n=t.Component.scheduler.start,e=t.Component.scheduler.stop;t.Component.scheduler.start=function(){this.isRunning||console.log(`${o} scheduler: start running tasks queue`),n.call(this)},t.Component.scheduler.stop=function(){this.isRunning&&console.log(`${o} scheduler: stop running tasks queue`),e.call(this)}}if(n.logStore){let n=t.Store.prototype.dispatch;t.Store.prototype.dispatch=function(t,...e){return console.log(`${o} store: action '${t}' dispatched. Payload: '${s(e)}'`),n.call(this,t,...e)}}}
function debugOwl(t,e){let n,o="[OWL_DEBUG]";function r(t){let e;try{e=JSON.stringify(t||{})}catch(t){e="<JSON error>"}return e.length>200&&(e=e.slice(0,200)+"..."),e}if(Object.defineProperty(t.Component,"current",{get:()=>n,set(s){n=s;const i=s.constructor.name;if(e.componentBlackList&&e.componentBlackList.test(i))return;if(e.componentWhiteList&&!e.componentWhiteList.test(i))return;let l;Object.defineProperty(n,"__owl__",{get:()=>l,set(n){!function(n,s,i){let l=`${s}<id=${i}>`,c=t=>console.log(`${o} ${l} ${t}`),u=t=>(!e.methodBlackList||!e.methodBlackList.includes(t))&&!(e.methodWhiteList&&!e.methodWhiteList.includes(t));u("constructor")&&c(`constructor, props=${r(n.props)}`);u("willStart")&&t.hooks.onWillStart(()=>{c("willStart")});u("mounted")&&t.hooks.onMounted(()=>{c("mounted")});u("willUpdateProps")&&t.hooks.onWillUpdateProps(t=>{c(`willUpdateProps, nextprops=${r(t)}`)});u("willPatch")&&t.hooks.onWillPatch(()=>{c("willPatch")});u("patched")&&t.hooks.onPatched(()=>{c("patched")});u("willUnmount")&&t.hooks.onWillUnmount(()=>{c("willUnmount")});const d=n.__render.bind(n);n.__render=function(...t){c("rendering template"),d(...t)};const h=n.render.bind(n);n.render=function(...t){const e=n.__owl__;let o="render";return e.isMounted||e.currentFiber||(o+=" (warning: component is not mounted, this render has no effect)"),c(o),h(...t)};const p=n.mount.bind(n);n.mount=function(...t){return c("mount"),p(...t)}}(s,i,(l=n).id)}})}}),e.logScheduler){let e=t.Component.scheduler.start,n=t.Component.scheduler.stop;t.Component.scheduler.start=function(){this.isRunning||console.log(`${o} scheduler: start running tasks queue`),e.call(this)},t.Component.scheduler.stop=function(){this.isRunning&&console.log(`${o} scheduler: stop running tasks queue`),n.call(this)}}if(e.logStore){let e=t.Store.prototype.dispatch;t.Store.prototype.dispatch=function(t,...n){return console.log(`${o} store: action '${t}' dispatched. Payload: '${r(n)}'`),e.call(this,t,...n)}}}
debugOwl(owl, {
// componentBlackList: /App/, // regexp
// componentWhiteList: /SomeComponent/, // regexp
+6 -3
View File
@@ -1,8 +1,11 @@
{
"name": "owl-framework",
"version": "1.0.0-beta4",
"name": "@odoo/owl",
"version": "1.0.0",
"description": "Odoo Web Library (OWL)",
"main": "src/index.ts",
"main": "dist/owl.js",
"types": "dist/types/index.d.ts",
"prepublish": "npm run buildcommonjs",
"files": ["dist/types/", "dist/owl.js"],
"engines": {
"node": ">=10.15.3"
},
+2 -2
View File
@@ -1,7 +1,7 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.0.0-beta4
- Status: mostly stable
- Current version: 1.0.0
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
change!
+18
View File
@@ -6,6 +6,7 @@ import "./directive";
import { Fiber } from "./fiber";
import "./props_validation";
import { Scheduler, scheduler } from "./scheduler";
import { activateSheet } from "./styles";
/**
* Owl Component System
@@ -199,6 +200,9 @@ export class Component<T extends Env, Props extends {}> {
refs: null,
scope: null
};
if (constr.style) {
this.__applyStyles(constr);
}
}
/**
@@ -580,6 +584,20 @@ export class Component<T extends Env, Props extends {}> {
return fiber;
}
/**
* Apply the stylesheets defined by the component. Note that we need to make
* sure all inherited stylesheets are applied as well. We then delete the
* `style` key from the constructor to make sure we do not apply it again.
*/
private __applyStyles(constr) {
while (constr && constr.style) {
if (constr.hasOwnProperty("style")) {
activateSheet(constr.style, constr.name);
delete constr.style;
}
constr = constr.__proto__;
}
}
__getTemplate(qweb: QWeb): string {
let p = (<any>this).constructor;
if (!p.hasOwnProperty("_template")) {
+1 -1
View File
@@ -211,7 +211,7 @@ QWeb.addDirective({
} else if (!name.startsWith("t-")) {
if (name !== "class" && name !== "style") {
// this is a prop!
props[name] = ctx.formatExpression(value);
props[name] = ctx.formatExpression(value) || "undefined";
}
}
}
+70
View File
@@ -0,0 +1,70 @@
/**
* Owl Style System
*
* This files contains the Owl code related to processing (extended) css strings
* and creating/adding <style> tags to the document head.
*/
export const STYLESHEETS: { [id: string]: HTMLStyleElement } = {};
export function processSheet(str: string): string {
const tokens = str.split(/(\{|\}|;)/).map(s => s.trim());
const selectorStack: string[][] = [];
const parts: string[] = [];
let rules: string[] = [];
function generateSelector(stackIndex: number, parentSelector?: string) {
const parts: string[] = [];
for (const selector of selectorStack[stackIndex]) {
let part = (parentSelector && parentSelector + " " + selector) || selector;
if (part.includes("&")) {
part = selector.replace(/&/g, parentSelector || "");
}
if (stackIndex < selectorStack.length - 1) {
part = generateSelector(stackIndex + 1, part);
}
parts.push(part);
}
return parts.join(", ");
}
function generateRules() {
if (rules.length) {
parts.push(generateSelector(0) + " {");
parts.push(...rules);
parts.push("}");
rules = [];
}
}
while (tokens.length) {
let token = tokens.shift()!;
if (token === "}") {
generateRules();
selectorStack.pop();
} else {
if (tokens[0] === "{") {
generateRules();
selectorStack.push(token.split(/\s*,\s*/));
tokens.shift();
}
if (tokens[0] === ";") {
rules.push(" " + token + ";");
}
}
}
return parts.join("\n");
}
export function registerSheet(id: string, css: string) {
const sheet = document.createElement("style");
sheet.innerHTML = processSheet(css);
STYLESHEETS[id] = sheet;
}
export function activateSheet(id, name) {
const sheet = STYLESHEETS[id];
if (!sheet) {
throw new Error(
`Invalid css stylesheet for component '${name}'. Did you forget to use the 'css' tag helper?`
);
}
sheet.setAttribute("component", name);
document.head.appendChild(sheet);
}
+24
View File
@@ -131,3 +131,27 @@ export function useSubEnv(nextEnv) {
const component = Component.current!;
component.env = Object.assign(Object.create(component.env), nextEnv);
}
// -----------------------------------------------------------------------------
// useExternalListener
// -----------------------------------------------------------------------------
/**
* When a component needs to listen to DOM Events on element(s) that are not
* part of his hierarchy, we can use the `useExternalListener` hook.
* It will correctly add and remove the event listener, whenever the
* component is mounted and unmounted.
*
* Example:
* a menu needs to listen to the click on window to be closed automatically
*
* Usage:
* in the constructor of the OWL component that needs to be notified,
* `useExternalListener(window, 'click', this._doSomething);`
* */
export function useExternalListener(target: HTMLElement, eventName: string, handler, eventParams?) {
const boundHandler = handler.bind(Component.current);
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
}
+9 -4
View File
@@ -128,7 +128,12 @@ QWeb.addDirective({
qwebvar.expr = `scope.${variable}`;
if (value) {
const formattedValue = ctx.formatExpression(value);
ctx.addLine(`${qwebvar.expr} = ${formattedValue};`);
let scopeExpr = `scope`;
if (ctx.protectedScopeNumber) {
ctx.rootContext.shouldDefineUtils = true;
scopeExpr = `utils.getScope(scope, '${variable}')`;
}
ctx.addLine(`${scopeExpr}.${variable} = ${formattedValue};`);
qwebvar.value = formattedValue;
}
@@ -265,9 +270,9 @@ QWeb.addDirective({
// ------------------------------------------------
const callingScope = hasBody ? "scope" : "Object.assign(Object.create(context), scope)";
const parentComponent = `utils.getComponent(context)`;
const keyCode = ctx.loopNumber || ctx.hasKey0 ? `, key: ${ctx.generateTemplateKey()}` : "";
const key = ctx.generateTemplateKey();
const parentNode = ctx.parentNode ? `c${ctx.parentNode}` : "result";
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}${keyCode}})`;
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}, key: ${key}})`;
if (ctx.parentNode) {
ctx.addLine(`this.subTemplates['${subTemplate}'].call(this, ${callingScope}, ${extra});`);
} else {
@@ -313,7 +318,7 @@ QWeb.addDirective({
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
ctx.closeIf();
ctx.addLine(`let _length${keysID} = _${keysID}.length;`);
let varsID = ctx.startProtectScope();
let varsID = ctx.startProtectScope(true);
const loopVar = `i${ctx.loopNumber}`;
ctx.addLine(`for (let ${loopVar} = 0; ${loopVar} < _length${keysID}; ${loopVar}++) {`);
ctx.indent();
+27 -3
View File
@@ -1,4 +1,4 @@
import { compileExpr, QWebVar } from "./expression_parser";
import { compileExpr, compileExprToArray, QWebVar } from "./expression_parser";
export const INTERP_REGEXP = /\{\{.*?\}\}/g;
//------------------------------------------------------------------------------
@@ -17,6 +17,7 @@ export class CompilationContext {
rootContext: CompilationContext;
shouldDefineParent: boolean = false;
shouldDefineScope: boolean = false;
protectedScopeNumber: number = 0;
shouldDefineQWeb: boolean = false;
shouldDefineUtils: boolean = false;
shouldDefineRefs: boolean = false;
@@ -155,6 +156,24 @@ export class CompilationContext {
this.rootContext.shouldDefineScope = true;
return compileExpr(expr, this.variables);
}
captureExpression(expr: string): string {
this.rootContext.shouldDefineScope = true;
const argId = this.generateID();
const tokens = compileExprToArray(expr, this.variables);
const done = new Set();
return tokens
.map(tok => {
if (tok.varName) {
if (!done.has(tok.varName)) {
done.add(tok.varName);
this.addLine(`const ${tok.varName}_${argId} = ${tok.value};`);
}
tok.value = `${tok.varName}_${argId}`;
}
return tok.value;
})
.join("");
}
/**
* Perform string interpolation on the given string. Note that if the whole
@@ -173,14 +192,19 @@ export class CompilationContext {
let r = s.replace(/\{\{.*?\}\}/g, s => "${" + this.formatExpression(s.slice(2, -2)) + "}");
return "`" + r + "`";
}
startProtectScope(): number {
startProtectScope(codeBlock?: boolean): number {
const protectID = this.generateID();
this.rootContext.protectedScopeNumber++;
this.rootContext.shouldDefineScope = true;
const scopeExpr = codeBlock
? `Object.create(scope);`
: `Object.assign(Object.create(context), scope);`;
this.addLine(`let _origScope${protectID} = scope;`);
this.addLine(`scope = Object.assign(Object.create(context), scope);`);
this.addLine(`scope = ${scopeExpr}`);
return protectID;
}
stopProtectScope(protectID: number) {
this.rootContext.protectedScopeNumber--;
this.addLine(`scope = _origScope${protectID};`);
}
}
+11 -3
View File
@@ -66,6 +66,7 @@ interface Token {
value: string;
originalValue?: string;
size?: number;
varName?: string;
}
const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
@@ -81,7 +82,7 @@ const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
// note that the space after typeof is relevant. It makes sure that the formatted
// expression has a space after typeof
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;".split(",");
const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ".split(",");
type Tokenizer = (expr: string) => Token | false;
@@ -234,7 +235,7 @@ export function tokenize(expr: string): Token[] {
* the arrow operator, then we add the current (or some previous tokens) token to
* the list of variables so it does not get replaced by a lookup in the context
*/
export function compileExpr(expr: string, scope: { [key: string]: QWebVar }): string {
export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar }): Token[] {
scope = Object.create(scope);
const tokens = tokenize(expr);
for (let i = 0; i < tokens.length; i++) {
@@ -269,6 +270,7 @@ export function compileExpr(expr: string, scope: { [key: string]: QWebVar }): st
}
if (isVar) {
token.varName = token.value;
if (token.value in scope && "id" in scope[token.value]) {
token.value = scope[token.value].expr!;
} else {
@@ -277,5 +279,11 @@ export function compileExpr(expr: string, scope: { [key: string]: QWebVar }): st
}
}
}
return tokens.map(t => t.value).join("");
return tokens;
}
export function compileExpr(expr: string, scope: { [key: string]: QWebVar }): string {
return compileExprToArray(expr, scope)
.map(t => t.value)
.join("");
}
+3 -2
View File
@@ -58,7 +58,7 @@ export function makeHandlerCode(
ctx.rootContext.shouldDefineUtils = true;
const comp = `utils.getComponent(context)`;
if (args) {
let argId = ctx.generateID();
const argId = ctx.generateID();
ctx.addLine(`let args${argId} = [${ctx.formatExpression(args)}];`);
code = `${comp}['${name}'](...args${argId}, e);`;
putInCache = false;
@@ -67,8 +67,9 @@ export function makeHandlerCode(
}
} else {
// if we get here, then it is an expression
// we need to capture every variable in it
putInCache = false;
code = ctx.formatExpression(value);
code = ctx.captureExpression(value);
}
const modCode = mods.map(mod => modcodes[mod]).join("");
let handler = `function (e) {if (!context.__owl__.isMounted){return}${modCode}${code}}`;
+16 -1
View File
@@ -86,6 +86,10 @@ interface Utils {
[key: string]: any;
}
function isComponent(obj) {
return obj && obj.hasOwnProperty("__owl__");
}
const UTILS: Utils = {
zero: Symbol("zero"),
toObj(expr) {
@@ -122,10 +126,21 @@ const UTILS: Utils = {
.join("");
},
getComponent(obj) {
while (obj && !obj.hasOwnProperty("__owl__")) {
while (obj && !isComponent(obj)) {
obj = obj.__proto__;
}
return obj;
},
getScope(obj, property: string) {
const obj0 = obj;
while (obj && !obj.hasOwnProperty(property)) {
const newObj = obj.__proto__;
if (!newObj || isComponent(newObj)) {
return obj0;
}
obj = newObj;
}
return obj;
}
};
+1 -1
View File
@@ -132,7 +132,7 @@ export function useStore(selector, options: SelectorOptions = {}): any {
__destroy.call(component, parent);
};
if (typeof result !== "object") {
if (typeof result !== "object" || result === null) {
return result;
}
return new Proxy(result, {
+17
View File
@@ -1,4 +1,5 @@
import { QWeb } from "./qweb/index";
import { registerSheet } from "./component/styles";
/**
* Owl Tags
@@ -25,3 +26,19 @@ export function xml(strings, ...args) {
QWeb.registerTemplate(name, value);
return name;
}
/**
* CSS tag helper for defining inline stylesheets. With this, one can simply define
* an inline stylesheet with just the following code:
* ```js
* class A extends Component {
* static style = css`.component-a { color: red; }`;
* }
* ```
*/
export function css(strings, ...args) {
const name = `__sheet__${QWeb.nextId++}`;
const value = String.raw(strings, ...args);
registerSheet(name, value);
return name;
}
@@ -1,5 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`basic widget properties can handle empty props 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__2\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
// Component 'Child'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
let props2 = {val:undefined};
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
w2.destroy();
w2 = false;
}
if (w2) {
w2.__updateProps(props2, extra.fiber, undefined);
let pvnode = w2.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey2 = \`Child\`;
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
}
w2.__owl__.parentLastFiberId = extra.fiber.id;
return vn1;
}"
`;
exports[`basic widget properties reconciliation alg works for t-foreach in t-foreach 1`] = `
"function anonymous(context, extra
) {
@@ -20,7 +58,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.section_first = i1 === 0
scope.section_last = i1 === _length3 - 1
@@ -37,7 +75,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
}
let _length7 = _7.length;
let _origScope9 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i2 = 0; i2 < _length7; i2++) {
scope.blip_first = i2 === 0
scope.blip_last = i2 === _length7 - 1
@@ -394,7 +432,7 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.number_first = i1 === 0
scope.number_last = i1 === _length3 - 1
@@ -549,6 +587,256 @@ exports[`dynamic t-props basic use 1`] = `
}"
`;
exports[`other directives with t-component slot setted value (with t-set) not accessible with t-esc 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__2\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.iter = 'source';
let c2 = [], p2 = {key:2};
let vn2 = h('p', p2, c2);
c1.push(vn2);
if (scope.iter != null) {
c2.push({text: scope.iter});
}
// Component 'ChildWidget'
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
let props3 = {};
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
w3.destroy();
w3 = false;
}
if (w3) {
w3.__updateProps(props3, extra.fiber, Object.assign(Object.create(context), scope));
let pvnode = w3.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey3 = \`ChildWidget\`;
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['ChildWidget'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
w3.__owl__.slotId = 1;
let fiber = w3.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
}
w3.__owl__.parentLastFiberId = extra.fiber.id;
let c5 = [], p5 = {key:5};
let vn5 = h('p', p5, c5);
c1.push(vn5);
if (scope.iter != null) {
c5.push({text: scope.iter});
}
return vn1;
}"
`;
exports[`other directives with t-component t-on expression captured in t-foreach 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.iter = 0;
let _2 = scope['arr'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.val_first = i1 === 0
scope.val_last = i1 === _length3 - 1
scope.val_index = i1
scope.val = _3[i1]
scope.val_value = _4[i1]
let key1 = scope['val'];
let c6 = [], p6 = {key:\`\${key1}_6\`};
let vn6 = h('div', p6, c6);
c1.push(vn6);
let c7 = [], p7 = {key:\`\${key1}_7\`,on:{}};
let vn7 = h('button', p7, c7);
c6.push(vn7);
const otherState_8 = scope['otherState'];
const iter_8 = scope.iter;
p7.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_8.vals.push(iter_8+'_'+iter_8)};
c7.push({text: \`expr\`});
utils.getScope(scope, 'iter').iter = scope.iter+1;
}
scope = _origScope5;
return vn1;
}"
`;
exports[`other directives with t-component t-on expression in t-foreach 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _2 = scope['state'].values;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.val_first = i1 === 0
scope.val_last = i1 === _length3 - 1
scope.val_index = i1
scope.val = _3[i1]
scope.val_value = _4[i1]
let key1 = scope['val'];
let c6 = [], p6 = {key:\`\${key1}_6\`};
let vn6 = h('div', p6, c6);
c1.push(vn6);
let _7 = scope['val_index'];
if (_7 != null) {
c6.push({text: _7});
}
c6.push({text: \`: \`});
let _8 = scope['val']+'';
if (_8 != null) {
c6.push({text: _8});
}
let c9 = [], p9 = {key:\`\${key1}_9\`,on:{}};
let vn9 = h('button', p9, c9);
c6.push(vn9);
const otherState_10 = scope['otherState'];
const val_10 = scope['val'];
p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_10.vals.push(val_10)};
c9.push({text: \`Expr\`});
}
scope = _origScope5;
return vn1;
}"
`;
exports[`other directives with t-component t-on expression in t-foreach with t-set 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.bossa = 'nova';
let _2 = scope['state'].values;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.val_first = i1 === 0
scope.val_last = i1 === _length3 - 1
scope.val_index = i1
scope.val = _3[i1]
scope.val_value = _4[i1]
let key1 = scope['val'];
let c6 = [], p6 = {key:\`\${key1}_6\`};
let vn6 = h('div', p6, c6);
c1.push(vn6);
utils.getScope(scope, 'bossa').bossa = scope.bossa+'_'+scope['val_index'];
let _7 = scope['val_index'];
if (_7 != null) {
c6.push({text: _7});
}
c6.push({text: \`: \`});
let _8 = scope['val']+'';
if (_8 != null) {
c6.push({text: _8});
}
let c9 = [], p9 = {key:\`\${key1}_9\`,on:{}};
let vn9 = h('button', p9, c9);
c6.push(vn9);
const otherState_10 = scope['otherState'];
const val_10 = scope['val'];
const bossa_10 = scope.bossa;
p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_10.vals.push(val_10+'_'+bossa_10)};
c9.push({text: \`Expr\`});
}
scope = _origScope5;
return vn1;
}"
`;
exports[`other directives with t-component t-on method call in t-foreach 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _2 = scope['state'].values;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.val_first = i1 === 0
scope.val_last = i1 === _length3 - 1
scope.val_index = i1
scope.val = _3[i1]
scope.val_value = _4[i1]
let key1 = scope['val'];
let c6 = [], p6 = {key:\`\${key1}_6\`};
let vn6 = h('div', p6, c6);
c1.push(vn6);
let _7 = scope['val_index'];
if (_7 != null) {
c6.push({text: _7});
}
c6.push({text: \`: \`});
let _8 = scope['val']+'';
if (_8 != null) {
c6.push({text: _8});
}
let c9 = [], p9 = {key:\`\${key1}_9\`,on:{}};
let vn9 = h('button', p9, c9);
c6.push(vn9);
let args10 = [scope['val']];
p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['addVal'](...args10, e);};
c9.push({text: \`meth call\`});
}
scope = _origScope5;
return vn1;
}"
`;
exports[`other directives with t-component t-on with getter as handler 1`] = `
"function anonymous(context, extra
) {
@@ -763,6 +1051,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = `
c1.push({text: _2});
}
// Component 'Child'
const state_5 = scope['state'];
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
let props3 = {};
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
@@ -779,7 +1068,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = `
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}scope['state'].counter++});}};});
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}state_5.counter++});}};});
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
@@ -979,6 +1268,195 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie
}"
`;
exports[`other directives with t-component t-set can't alter component 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let c2 = [], p2 = {key:2};
let vn2 = h('p', p2, c2);
c1.push(vn2);
let _3 = scope['iter'];
if (_3 != null) {
c2.push({text: _3});
}
scope.iter = 5;
let c4 = [], p4 = {key:4};
let vn4 = h('p', p4, c4);
c1.push(vn4);
if (scope.iter != null) {
c4.push({text: scope.iter});
}
return vn1;
}"
`;
exports[`other directives with t-component t-set can't alter from within callee 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.iter = 'source';
let c2 = [], p2 = {key:2};
let vn2 = h('p', p2, c2);
c1.push(vn2);
if (scope.iter != null) {
c2.push({text: scope.iter});
}
this.subTemplates['ChildWidget'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
let c5 = [], p5 = {key:5};
let vn5 = h('p', p5, c5);
c1.push(vn5);
if (scope.iter != null) {
c5.push({text: scope.iter});
}
return vn1;
}"
`;
exports[`other directives with t-component t-set can't alter in t-call body 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.iter = 'source';
let c2 = [], p2 = {key:2};
let vn2 = h('p', p2, c2);
c1.push(vn2);
if (scope.iter != null) {
c2.push({text: scope.iter});
}
{
let _origScope4 = scope;
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
utils.getScope(scope, 'iter').iter = 'inCall';
scope[utils.zero] = c__0;
}
this.subTemplates['ChildWidget'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope4;
}
let c6 = [], p6 = {key:6};
let vn6 = h('p', p6, c6);
c1.push(vn6);
if (scope.iter != null) {
c6.push({text: scope.iter});
}
return vn1;
}"
`;
exports[`other directives with t-component t-set not altered by child widget 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__2\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.iter = 'source';
let c2 = [], p2 = {key:2};
let vn2 = h('p', p2, c2);
c1.push(vn2);
if (scope.iter != null) {
c2.push({text: scope.iter});
}
// Component 'ChildWidget'
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
let props3 = {};
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
w3.destroy();
w3 = false;
}
if (w3) {
w3.__updateProps(props3, extra.fiber, undefined);
let pvnode = w3.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey3 = \`ChildWidget\`;
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['ChildWidget'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
}
w3.__owl__.parentLastFiberId = extra.fiber.id;
let c5 = [], p5 = {key:5};
let vn5 = h('p', p5, c5);
c1.push(vn5);
if (scope.iter != null) {
c5.push({text: scope.iter});
}
return vn1;
}"
`;
exports[`other directives with t-component t-set outside modified in t-foreach 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.iter = 0;
let _2 = scope['state'].values;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.val_first = i1 === 0
scope.val_last = i1 === _length3 - 1
scope.val_index = i1
scope.val = _3[i1]
scope.val_value = _4[i1]
let key1 = scope['val'];
let c6 = [], p6 = {key:\`\${key1}_6\`};
let vn6 = h('p', p6, c6);
c1.push(vn6);
c6.push({text: \`InLoop: \`});
if (scope.iter != null) {
c6.push({text: scope.iter});
}
utils.getScope(scope, 'iter').iter = scope.iter+1;
}
scope = _origScope5;
let c7 = [], p7 = {key:7};
let vn7 = h('p', p7, c7);
c1.push(vn7);
c7.push({text: \`EndLoop: \`});
if (scope.iter != null) {
c7.push({text: scope.iter});
}
return vn1;
}"
`;
exports[`random stuff/miscellaneous can inject values in tagged templates 1`] = `
"function anonymous(context, extra
) {
@@ -988,7 +1466,7 @@ exports[`random stuff/miscellaneous can inject values in tagged templates 1`] =
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['__template__1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['__template__1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -1055,7 +1533,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.item_first = i1 === 0
scope.item_last = i1 === _length3 - 1
@@ -1304,7 +1782,7 @@ exports[`t-model directive in a t-foreach 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.thing_first = i1 === 0
scope.thing_last = i1 === _length3 - 1
@@ -263,7 +263,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
}
let _length4 = _4.length;
let _origScope6 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length4; i1++) {
scope.user_first = i1 === 0
scope.user_last = i1 === _length4 - 1
@@ -360,7 +360,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
}
let _length4 = _4.length;
let _origScope6 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length4; i1++) {
scope.user_first = i1 === 0
scope.user_last = i1 === _length4 - 1
@@ -371,7 +371,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
let c7 = [], p7 = {key:\`\${key1}_7\`};
let vn7 = h('li', p7, c7);
c2.push(vn7);
scope.userdescr = 'User '+scope['user'].name;
utils.getScope(scope, 'userdescr').userdescr = 'User '+scope['user'].name;
// Component 'Link'
let k9 = \`__9__\${key1}__\`;
let w8 = k9 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k9]] : false;
+393
View File
@@ -147,6 +147,21 @@ describe("basic widget properties", () => {
expect(fixture.innerHTML).toBe("<div>1<button>Inc</button></div>");
});
test("can handle empty props", async () => {
class Child extends Component<any, any> {
static template = xml`<span><t t-esc="props.val"/></span>`;
}
class Parent extends Component<any, any> {
static template = xml`<div><Child val=""/></div>`;
static components = { Child };
}
const parent = new Parent();
await parent.mount(fixture);
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
expect(fixture.innerHTML).toBe("<div><span></span></div>");
});
test("cannot be clicked on and updated if not in DOM", async () => {
class Counter extends Component<any, any> {
static template = xml`
@@ -2655,6 +2670,359 @@ describe("other directives with t-component", () => {
await nextTick(); // wait for changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
test("t-set outside modified in t-foreach", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="iter" t-value="0"/>
<t t-foreach="state.values" t-as="val" t-key="val">
<p>InLoop: <t t-esc="iter"/></p>
<t t-set="iter" t-value="iter + 1"/>
</t>
<p>EndLoop: <t t-esc="iter"/></p>
</div>`;
state = useState({ values: ["a", "b"] });
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>InLoop: 0</p><p>InLoop: 1</p><p>EndLoop: 2</p></div>");
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("t-set outside modified in t-if", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="iter" t-value="0"/>
<t t-set="flag" t-value="state.flag" />
<t t-if="flag === 'if'">
<t t-set="iter" t-value="2"/>
</t>
<t t-elif="flag === 'elif'">
<t t-set="iter" t-value="3"/>
</t>
<t t-else="">
<t t-set="iter" t-value="4"/>
</t>
<p><t t-esc="iter"/></p>
</div>`;
state = { flag: "if" };
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>2</p></div>");
widget.state.flag = "elif";
await widget.render();
expect(fixture.innerHTML).toBe("<div><p>3</p></div>");
widget.state.flag = "false";
await widget.render();
expect(fixture.innerHTML).toBe("<div><p>4</p></div>");
});
test("t-set in t-if", async () => {
// Weird that code block within 'if' leaks outside of it
// Python does the same
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="flag" t-value="state.flag" />
<t t-if="flag === 'if'">
<t t-set="iter" t-value="2"/>
</t>
<t t-elif="flag === 'elif'">
<t t-set="iter" t-value="3"/>
</t>
<t t-else="">
<t t-set="iter" t-value="4"/>
</t>
<p><t t-esc="iter"/></p>
</div>`;
state = { flag: "if" };
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>2</p></div>");
widget.state.flag = "elif";
await widget.render();
expect(fixture.innerHTML).toBe("<div><p>3</p></div>");
widget.state.flag = "false";
await widget.render();
expect(fixture.innerHTML).toBe("<div><p>4</p></div>");
});
test("t-set can't alter component", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<p><t t-esc="iter"/></p>
<t t-set="iter" t-value="5"/>
<p><t t-esc="iter"/></p>
</div>`;
iter = 1;
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>1</p><p>5</p></div>");
expect(widget.iter).toBe(1);
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("t-set can't alter from within callee", async () => {
env.qweb.addTemplate(
"ChildWidget",
`<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`
);
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="iter" t-value="'source'"/>
<p><t t-esc="iter"/></p>
<t t-call="ChildWidget"/>
<p><t t-esc="iter"/></p>
</div>`;
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>source</p><div>sourcecalled</div><p>source</p></div>");
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("t-set can't alter in t-call body", async () => {
env.qweb.addTemplate(
"ChildWidget",
`<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`
);
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="iter" t-value="'source'"/>
<p><t t-esc="iter"/></p>
<t t-call="ChildWidget">
<t t-set="iter" t-value="'inCall'"/>
</t>
<p><t t-esc="iter"/></p>
</div>`;
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>source</p><div>inCallcalled</div><p>source</p></div>");
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("slot setted value (with t-set) not accessible with t-esc", async () => {
class ChildWidget extends Component<any, any> {
static template = xml`<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`;
}
class SomeWidget extends Component<any, any> {
static components = { ChildWidget };
static template = xml`
<div>
<t t-set="iter" t-value="'source'"/>
<p><t t-esc="iter"/></p>
<ChildWidget>
<t t-set="iter" t-value="'inCall'"/>
</ChildWidget>
<p><t t-esc="iter"/></p>
</div>`;
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>source</p><div>called</div><p>source</p></div>");
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("t-set not altered by child widget", async () => {
let child;
class ChildWidget extends Component<any, any> {
static template = xml`<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`;
iter = "child";
constructor() {
super(...arguments);
child = this;
}
}
class SomeWidget extends Component<any, any> {
static components = { ChildWidget };
static template = xml`
<div>
<t t-set="iter" t-value="'source'"/>
<p><t t-esc="iter"/></p>
<ChildWidget/>
<p><t t-esc="iter"/></p>
</div>`;
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>source</p><div>childcalled</div><p>source</p></div>");
expect(child.iter).toBe("child");
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("t-set outside modified in t-foreach increment-after operator", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="iter" t-value="0"/>
<t t-foreach="state.values" t-as="val" t-key="val">
<p>InLoop: <t t-esc="iter"/></p>
<t t-set="iter" t-value="iter++"/>
</t>
<p>EndLoop: <t t-esc="iter"/></p>
</div>`;
state = useState({ values: ["a", "b"] });
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>InLoop: 0</p><p>InLoop: 1</p><p>EndLoop: 0</p></div>");
});
test("t-set outside modified in t-foreach increment-before operator", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="iter" t-value="0"/>
<t t-foreach="state.values" t-as="val" t-key="val">
<p>InLoop: <t t-esc="iter"/></p>
<t t-set="iter" t-value="++iter"/>
</t>
<p>EndLoop: <t t-esc="iter"/></p>
</div>`;
state = useState({ values: ["a", "b"] });
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p>InLoop: 0</p><p>InLoop: 1</p><p>EndLoop: 1</p></div>");
});
test("t-on expression in t-foreach", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<div t-foreach="state.values" t-as="val" t-key="val">
<t t-esc="val_index"/>: <t t-esc="val + ''"/>
<button t-on-click="otherState.vals.push(val)">Expr</button>
</div>
</div>`;
state = useState({ values: ["a", "b"] });
otherState = { vals: [] };
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div>0: a<button>Expr</button></div><div>1: b<button>Expr</button></div></div>"
);
expect(widget.otherState.vals).toStrictEqual([]);
const buttons = fixture.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
expect(widget.otherState.vals).toStrictEqual(["a", "b"]);
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("t-on expression in t-foreach with t-set", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="bossa" t-value="'nova'"/>
<div t-foreach="state.values" t-as="val" t-key="val">
<t t-set="bossa" t-value="bossa + '_' + val_index" />
<t t-esc="val_index"/>: <t t-esc="val + ''"/>
<button t-on-click="otherState.vals.push(val + '_' + bossa)">Expr</button>
</div>
</div>`;
state = useState({ values: ["a", "b"] });
otherState = { vals: [] };
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div>0: a<button>Expr</button></div><div>1: b<button>Expr</button></div></div>"
);
expect(widget.otherState.vals).toStrictEqual([]);
const buttons = fixture.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
expect(widget.otherState.vals).toStrictEqual(["a_nova_0", "b_nova_0_1"]);
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("t-on method call in t-foreach", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<div t-foreach="state.values" t-as="val" t-key="val">
<t t-esc="val_index"/>: <t t-esc="val + ''"/>
<button t-on-click="addVal(val)">meth call</button>
</div>
</div>`;
state = useState({ values: ["a", "b"] });
otherState = { vals: new Array<string>() };
addVal(val: string) {
this.otherState.vals.push(val);
}
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div>0: a<button>meth call</button></div><div>1: b<button>meth call</button></div></div>"
);
expect(widget.otherState.vals).toStrictEqual([]);
const buttons = fixture.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
expect(widget.otherState.vals).toStrictEqual(["a", "b"]);
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
test("t-on expression captured in t-foreach", async () => {
class SomeWidget extends Component<any, any> {
static template = xml`
<div>
<t t-set="iter" t-value="0" />
<div t-foreach="arr" t-as="val" t-key="val">
<button t-on-click="otherState.vals.push(iter + '_' + iter)">expr</button>
<t t-set="iter" t-value="iter + 1" />
</div>
</div>`;
arr = ["a", "b"];
otherState = { vals: new Array<string>() };
}
const widget = new SomeWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div><button>expr</button></div><div><button>expr</button></div></div>"
);
expect(widget.otherState.vals).toStrictEqual([]);
const buttons = fixture.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
expect(widget.otherState.vals).toStrictEqual(["0_0", "1_1"]);
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
});
});
describe("random stuff/miscellaneous", () => {
@@ -5711,4 +6079,29 @@ describe("t-call", () => {
fixture.querySelector("p")!.click();
});
test("sub components in two t-calls", async () => {
class Child extends Component<any, any> {
static template = xml`<span><t t-esc="props.val"/></span>`;
}
env.qweb.addTemplate("sub", `<Child val="state.val"/>`);
class Parent extends Component<any, any> {
static template = xml`
<div>
<t t-if="state.val===1">
<t t-call="sub"/>
</t>
<div t-else=""><t t-call="sub"/></div>
</div>`;
static components = { Child };
state = useState({ val: 1 });
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
parent.state.val = 2;
await nextTick();
expect(fixture.innerHTML).toBe("<div><div><span>2</span></div></div>");
});
});
-7
View File
@@ -19,13 +19,6 @@ let env: Env;
beforeEach(() => {
fixture = makeTestFixture();
env = makeTestEnv();
env.qweb.addTemplate("Component<any,any>", "<div></div>");
env.qweb.addTemplate(
"Counter",
`<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>`
);
env.qweb.addTemplate("WidgetA", `<div>Hello<t t-component="b"/></div>`);
env.qweb.addTemplate("WidgetB", `<div>world</div>`);
Component.env = env;
});
+170
View File
@@ -0,0 +1,170 @@
import { Component, Env } from "../../src/component/component";
import { processSheet } from "../../src/component/styles";
import { xml, css } from "../../src/tags";
import { makeTestFixture, makeTestEnv } from "../helpers";
//------------------------------------------------------------------------------
// Setup and helpers
//------------------------------------------------------------------------------
// We create before each test:
// - fixture: a div, appended to the DOM, intended to be the target of dom
// manipulations. Note that it is removed after each test.
// - env: an Env, necessary to create new components
let fixture: HTMLElement;
let env: Env;
beforeEach(() => {
fixture = makeTestFixture();
env = makeTestEnv();
Component.env = env;
document.head.innerHTML = "";
});
afterEach(() => {
fixture.remove();
});
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
describe("styles and component", () => {
test("can define an inline stylesheet", async () => {
class App extends Component<any, any> {
static template = xml`<div class="app">text</div>`;
static style = css`
.app {
color: red;
}
`;
}
expect(document.head.innerHTML).toBe("");
const app = new App();
expect(document.head.innerHTML).toBe(`<style component=\"App\">.app {
color: red;
}</style>`);
await app.mount(fixture);
const style = getComputedStyle(app.el!);
expect(style.color).toBe("red");
expect(fixture.innerHTML).toBe('<div class="app">text</div>');
});
test("inherited components properly apply css", async () => {
class App extends Component<any, any> {
static template = xml`<div class="app">text</div>`;
static style = css`
.app {
color: red;
}
`;
}
class SubApp extends App {
static style = css`
.app {
font-weight: bold;
}
`;
}
expect(document.head.innerHTML).toBe("");
const app = new SubApp();
expect(document.head.innerHTML).toBe(`<style component=\"SubApp\">.app {
font-weight: bold;
}</style><style component=\"App\">.app {
color: red;
}</style>`);
await app.mount(fixture);
const style = getComputedStyle(app.el!);
expect(style.color).toBe("red");
expect(style.fontWeight).toBe("bold");
expect(fixture.innerHTML).toBe('<div class="app">text</div>');
});
test("get a meaningful error message if css helper is missing", async () => {
class App extends Component<any, any> {
static template = xml`<div class="app">text</div>`;
static style = `.app {color: red;}`;
}
let error;
try {
new App();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe(
"Invalid css stylesheet for component 'App'. Did you forget to use the 'css' tag helper?"
);
});
test("inline stylesheets are processed", async () => {
class App extends Component<any, any> {
static template = xml`<div class="app">text</div>`;
static style = css`
.app {
color: red;
.some-class {
font-weight: bold;
width: 40px;
}
display: block;
}
`;
}
new App();
expect(document.head.querySelector("style")!.innerHTML).toBe(`.app {
color: red;
}
.app .some-class {
font-weight: bold;
width: 40px;
}
.app {
display: block;
}`);
});
test("properly handle rules with commas", async () => {
const sheet = processSheet(`.parent-a, .parent-b {
.child-a, .child-b {
color: red;
}
}`);
expect(sheet)
.toBe(`.parent-a .child-a, .parent-a .child-b, .parent-b .child-a, .parent-b .child-b {
color: red;
}`);
});
test("handle & selector", async () => {
let sheet = processSheet(`.btn {
&.danger {
color: red;
}
}`);
expect(sheet).toBe(`.btn.danger {
color: red;
}`);
sheet = processSheet(`.some-class {
&.btn {
.other-class ~ & {
color: red;
}
}
}`);
expect(sheet).toBe(`.other-class ~ .some-class.btn {
color: red;
}`);
});
});
+37 -1
View File
@@ -9,7 +9,8 @@ import {
onWillPatch,
onWillStart,
onWillUpdateProps,
useSubEnv
useSubEnv,
useExternalListener
} from "../src/hooks";
import { xml } from "../src/tags";
@@ -592,4 +593,39 @@ describe("hooks", () => {
expect(fixture.innerHTML).toBe("<div><span>2</span></div>");
expect(steps).toEqual(["onWillStart", "onWillUpdateProps"]);
});
test("useExternalListener", async () => {
let n = 0;
class MyComponent extends Component<any, any> {
static template = xml`<span><t t-esc="props.value"/></span>`;
constructor(parent, props) {
super(parent, props);
useExternalListener(window as any, "click", this.increment);
}
increment() {
n++;
}
}
class App extends Component<any, any> {
static template = xml`<div><MyComponent t-if="state.flag"/></div>`;
static components = { MyComponent };
state = useState({ flag: false });
}
const app = new App();
await app.mount(fixture);
expect(n).toBe(0);
window.dispatchEvent(new Event("click"));
expect(n).toBe(0);
app.state.flag = true;
await nextTick();
window.dispatchEvent(new Event("click"));
expect(n).toBe(1);
app.state.flag = false;
await nextTick();
window.dispatchEvent(new Event("click"));
expect(n).toBe(1);
});
});
+65 -59
View File
@@ -421,7 +421,7 @@ exports[`foreach does not pollute the rendering context 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.item_first = i1 === 0
scope.item_last = i1 === _length3 - 1
@@ -456,7 +456,7 @@ exports[`foreach iterate on items (on a element node) 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.item_first = i1 === 0
scope.item_last = i1 === _length3 - 1
@@ -494,7 +494,7 @@ exports[`foreach iterate on items 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.item_first = i1 === 0
scope.item_last = i1 === _length3 - 1
@@ -541,7 +541,7 @@ exports[`foreach iterate, dict param 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.item_first = i1 === 0
scope.item_last = i1 === _length3 - 1
@@ -588,7 +588,7 @@ exports[`foreach iterate, position 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.elem_first = i1 === 0
scope.elem_last = i1 === _length3 - 1
@@ -632,7 +632,7 @@ exports[`foreach t-foreach in t-forach 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.number_first = i1 === 0
scope.number_last = i1 === _length3 - 1
@@ -649,7 +649,7 @@ exports[`foreach t-foreach in t-forach 1`] = `
}
let _length7 = _7.length;
let _origScope9 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i2 = 0; i2 < _length7; i2++) {
scope.letter_first = i2 === 0
scope.letter_last = i2 === _length7 - 1
@@ -692,7 +692,7 @@ exports[`foreach warn if no key in some case 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.item_first = i1 === 0
scope.item_last = i1 === _length3 - 1
@@ -734,7 +734,7 @@ exports[`loading templates can load a few templates from a xml string 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('ul', p1, c1);
this.subTemplates['items'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['items'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -757,7 +757,7 @@ exports[`misc global 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.value_first = i1 === 0
scope.value_last = i1 === _length3 - 1
@@ -782,7 +782,7 @@ exports[`misc global 1`] = `
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.foo = 'aaa';
utils.getScope(scope, 'foo').foo = 'aaa';
scope[utils.zero] = c__0;
}
let k14 = \`__14__\${key1}__\`;
@@ -791,7 +791,7 @@ exports[`misc global 1`] = `
}
let k15 = \`__15__\${key1}__\`;
this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k15}));
scope.foo = 'bbb';
utils.getScope(scope, 'foo').foo = 'bbb';
let k16 = \`__16__\${key1}__\`;
this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k16}));
scope[utils.zero] = c__0;
@@ -802,7 +802,7 @@ exports[`misc global 1`] = `
}
}
scope = _origScope5;
this.subTemplates['_callee-asc-toto'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['_callee-asc-toto'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__20__'}));
return vn1;
}"
`;
@@ -1041,7 +1041,7 @@ exports[`t-call (template calling basic caller 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
return vn1;
}"
`;
@@ -1069,7 +1069,7 @@ exports[`t-call (template calling basic caller, no parent node 1`] = `
let result;
let h = this.h;
result = []
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__3__'}));
result = result[0]
return result;
}"
@@ -1114,7 +1114,7 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] =
c5.push({text: \`yay\`});
scope[utils.zero] = c__0;
}
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
scope = _origScope3;
}
return vn1;
@@ -1162,7 +1162,7 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
c14.push({text: \`yay\`});
scope[utils.zero] = c__0;
}
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__15__'}));
scope = _origScope12;
}
return vn1;
@@ -1179,7 +1179,7 @@ exports[`t-call (template calling inherit context 1`] = `
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.foo = 1;
this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__2__'}));
return vn1;
}"
`;
@@ -1198,7 +1198,7 @@ exports[`t-call (template calling recursive template, part 1 1`] = `
c1.push(vn2);
c2.push({text: \`hey\`});
if (false) {
this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
}
return vn1;
}"
@@ -1241,10 +1241,10 @@ exports[`t-call (template calling recursive template, part 2 1`] = `
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.node = scope['root'];
utils.getScope(scope, 'node').node = scope['root'];
scope[utils.zero] = c__0;
}
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
return vn1;
@@ -1279,7 +1279,7 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
}
let _length6 = _6.length;
let _origScope8 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length6; i1++) {
scope.subtree_first = i1 === 0
scope.subtree_last = i1 === _length6 - 1
@@ -1292,7 +1292,7 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.node = scope['subtree'];
utils.getScope(scope, 'node').node = scope['subtree'];
scope[utils.zero] = c__0;
}
let k10 = \`__10__\${key0}__\${key1}__\`;
@@ -1318,10 +1318,10 @@ exports[`t-call (template calling recursive template, part 3 1`] = `
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.node = scope['root'];
utils.getScope(scope, 'node').node = scope['root'];
scope[utils.zero] = c__0;
}
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
return vn1;
@@ -1356,7 +1356,7 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
}
let _length6 = _6.length;
let _origScope8 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length6; i1++) {
scope.subtree_first = i1 === 0
scope.subtree_last = i1 === _length6 - 1
@@ -1369,7 +1369,7 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.node = scope['subtree'];
utils.getScope(scope, 'node').node = scope['subtree'];
scope[utils.zero] = c__0;
}
let k10 = \`__10__\${key0}__\${key1}__\`;
@@ -1395,10 +1395,10 @@ exports[`t-call (template calling scoped parameters 1`] = `
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.foo = 42;
utils.getScope(scope, 'foo').foo = 42;
scope[utils.zero] = c__0;
}
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
scope = _origScope2;
}
if (scope.foo != null) {
@@ -1418,7 +1418,7 @@ exports[`t-call (template calling t-call with t-if 1`] = `
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
if (scope['flag']) {
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
}
return vn1;
}"
@@ -1456,7 +1456,7 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.v_first = i1 === 0
scope.v_last = i1 === _length3 - 1
@@ -1464,13 +1464,13 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
scope.v = _3[i1]
scope.v_value = _4[i1]
let key1 = i1;
scope.val = scope['v'].val;
utils.getScope(scope, 'val').val = scope['v'].val;
{
let _origScope8 = scope;
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.val3 = scope.val*3;
utils.getScope(scope, 'val3').val3 = scope.val*3;
scope[utils.zero] = c__0;
}
let k9 = \`__9__\${key1}__\`;
@@ -1493,7 +1493,7 @@ exports[`t-call (template calling t-call with t-set inside and outside. 2 1`] =
let c1 = [], p1 = {key:1};
let vn1 = h('p', p1, c1);
scope.w = 'fromwrapper';
this.subTemplates['main'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['main'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__11__'}));
return vn1;
}"
`;
@@ -1509,19 +1509,19 @@ exports[`t-call (template calling t-call, conditional and t-set in t-call body 1
let vn1 = h('div', p1, c1);
scope.v1 = 'elif';
if (scope.v1==='if') {
this.subTemplates['callee1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['callee1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
}
else if (scope.v1==='elif') {
{
let _origScope5 = scope;
let _origScope6 = scope;
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.v = 'success';
utils.getScope(scope, 'v').v = 'success';
scope[utils.zero] = c__0;
}
this.subTemplates['callee2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
scope = _origScope5;
this.subTemplates['callee2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__7__'}));
scope = _origScope6;
}
}
return vn1;
@@ -1537,7 +1537,7 @@ exports[`t-call (template calling t-call, global templates 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['john'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['john'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
return vn1;
}"
`;
@@ -1559,7 +1559,7 @@ exports[`t-call (template calling with unused body 1`] = `
scope[utils.zero] = c__0;
}
result = []
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1580,11 +1580,11 @@ exports[`t-call (template calling with unused setbody 1`] = `
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.qux = 3;
utils.getScope(scope, 'qux').qux = 3;
scope[utils.zero] = c__0;
}
result = []
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1609,7 +1609,7 @@ exports[`t-call (template calling with used body 1`] = `
scope[utils.zero] = c__0;
}
result = []
this.subTemplates['_callee-printsbody'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)}));
this.subTemplates['_callee-printsbody'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1631,10 +1631,10 @@ exports[`t-call (template calling with used set body 1`] = `
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.foo = 'ok';
utils.getScope(scope, 'foo').foo = 'ok';
scope[utils.zero] = c__0;
}
this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
}
return vn1;
@@ -1834,7 +1834,7 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
c4.push({text: \`escaped\`});
scope[utils.zero] = c__0;
}
this.subTemplates['test'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['test'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope3;
}
return vn1;
@@ -2156,7 +2156,7 @@ exports[`t-key t-key directive in a list 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.beer_first = i1 === 0
scope.beer_last = i1 === _length3 - 1
@@ -2260,7 +2260,7 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.action_first = i1 === 0
scope.action_last = i1 === _length3 - 1
@@ -2400,7 +2400,8 @@ exports[`t-on t-on with inline statement (function call) 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}scope['state'].incrementCounter(2)};
const state_2 = scope['state'];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.incrementCounter(2)};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2414,7 +2415,8 @@ exports[`t-on t-on with inline statement 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}scope['state'].counter++};
const state_2 = scope['state'];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.counter++};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2428,7 +2430,8 @@ exports[`t-on t-on with inline statement, part 2 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}scope['state'].flag=!scope['state'].flag};
const state_2 = scope['state'];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.flag=!state_2.flag};
c1.push({text: \`Toggle\`});
return vn1;
}"
@@ -2442,7 +2445,9 @@ exports[`t-on t-on with inline statement, part 3 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}scope['state'].n=scope['someFunction'](3)};
const state_2 = scope['state'];
const someFunction_2 = scope['someFunction'];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.n=someFunction_2(3)};
c1.push({text: \`Toggle\`});
return vn1;
}"
@@ -2517,7 +2522,7 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.project_first = i1 === 0
scope.project_last = i1 === _length3 - 1
@@ -2605,7 +2610,7 @@ exports[`t-on t-on with t-call 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -2619,7 +2624,7 @@ exports[`t-on t-on, with arguments and t-call 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -2763,7 +2768,7 @@ exports[`t-ref refs in a loop 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.item_first = i1 === 0
scope.item_last = i1 === _length3 - 1
@@ -2990,6 +2995,7 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
@@ -3004,7 +3010,7 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.elem_first = i1 === 0
scope.elem_last = i1 === _length3 - 1
@@ -3022,7 +3028,7 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
if (scope.v != null) {
c7.push({text: scope.v});
}
scope.v = scope['elem'];
utils.getScope(scope, 'v').v = scope['elem'];
}
scope = _origScope5;
return vn1;
+7
View File
@@ -56,6 +56,12 @@ describe("tokenizer", () => {
{ type: "OPERATOR", value: "..." },
{ type: "VALUE", value: "1" }
]);
expect(tokenize("a in b")).toEqual([
{ type: "SYMBOL", value: "a" },
{ type: "OPERATOR", value: "in " },
{ type: "SYMBOL", value: "b" }
]);
});
test("strings", () => {
@@ -134,6 +140,7 @@ describe("expression evaluation", () => {
expect(compileExpr("color === 'black'", {})).toBe("scope['color']==='black'");
expect(compileExpr("'li_'+item", {})).toBe("'li_'+scope['item']");
expect(compileExpr("state.val > 1", {})).toBe("scope['state'].val>1");
expect(compileExpr("a in b", {})).toBe("scope['a']in scope['b']");
});
test("boolean operations", () => {
+15 -1
View File
@@ -49,10 +49,13 @@ describe("connecting a component to store", () => {
});
test("useStore can observe primitive types and call onUpdate", async () => {
const state = { isBoolean: false };
const state = { isBoolean: false, nullValue: null };
const actions = {
setTrue({ state }) {
state.isBoolean = true;
},
setNotNull({ state }) {
state.nullValue = "ok";
}
};
const store = new Store({ state, actions });
@@ -61,8 +64,10 @@ describe("connecting a component to store", () => {
static template = xml`
<div>
<span t-if="isBoolean">ok</span>
<span t-if="nullValue !== null">not null</span>
</div>`;
isBoolean: boolean;
nullValue: string;
constructor() {
super();
this.isBoolean = useStore(state => state.isBoolean, {
@@ -70,6 +75,11 @@ describe("connecting a component to store", () => {
this.isBoolean = isBoolean;
}
});
this.nullValue = useStore(state => state.nullValue, {
onUpdate: nullValue => {
this.nullValue = nullValue;
}
});
}
}
@@ -82,6 +92,10 @@ describe("connecting a component to store", () => {
store.dispatch("setTrue");
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>ok</span></div>");
store.dispatch("setNotNull");
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>ok</span><span>not null</span></div>");
});
test("map works on the result of useStore when the resulting array changes for a bigger one", async () => {
+57
View File
@@ -0,0 +1,57 @@
/**
* We can only make one test per file, since the debug tool modify in place
* the owl object in a way that is difficult to undo.
*/
import { debugOwl } from "../../tools/debug";
import * as owl from "../../src/index";
import { Component, Env } from "../../src/component/component";
import { xml } from "../../src/tags";
import { makeTestFixture, makeTestEnv } from "../helpers";
let fixture: HTMLElement = makeTestFixture();
let env: Env = makeTestEnv();
Component.env = env;
debugOwl(owl, { logScheduler: true });
test("log a sub component with non stringifiable props", async () => {
const steps: string[] = [];
const log = console.log;
console.log = arg => steps.push(arg);
class Child extends Component<any, any> {
static template = xml`<span><t t-esc="props.obj.val"/></span>`;
}
const circularObject: any = { val: 1 };
circularObject.circularObject = circularObject;
class Parent extends Component<any, any> {
static template = xml`<div><Child obj="obj"/></div>`;
static components = { Child };
obj = circularObject;
}
const parent = new Parent(null, {});
await parent.mount(fixture);
parent.unmount();
expect(steps).toEqual([
"[OWL_DEBUG] Parent<id=1> constructor, props={}",
"[OWL_DEBUG] Parent<id=1> mount",
"[OWL_DEBUG] Parent<id=1> willStart",
"[OWL_DEBUG] scheduler: start running tasks queue",
"[OWL_DEBUG] Parent<id=1> rendering template",
"[OWL_DEBUG] Child<id=2> constructor, props=<JSON error>",
"[OWL_DEBUG] Child<id=2> willStart",
"[OWL_DEBUG] Child<id=2> rendering template",
"[OWL_DEBUG] Child<id=2> mounted",
"[OWL_DEBUG] Parent<id=1> mounted",
"[OWL_DEBUG] scheduler: stop running tasks queue",
"[OWL_DEBUG] Parent<id=1> willUnmount",
"[OWL_DEBUG] Child<id=2> willUnmount"
]);
console.log = log;
});
+6 -1
View File
@@ -35,7 +35,12 @@
});
function toStr(obj) {
let str = JSON.stringify(obj || {});
let str;
try {
str = JSON.stringify(obj || {});
} catch (e) {
str = "<JSON error>";
}
if (str.length > 200) {
str = str.slice(0, 200) + "...";
}
+7 -7
View File
@@ -455,7 +455,7 @@ const actions = {
todo.completed = !todo.completed;
},
clearCompleted({ state, dispatch }) {
for (let todo of state.todos) {
for (let todo of state.todos.slice()) {
if (todo.completed) {
dispatch("removeTodo", todo.id);
}
@@ -1089,20 +1089,20 @@ const RESPONSIVE_XML = `<templates>
<button>Button!</button>
</div>
<t t-name="maincontent">
<FormView />
<Chatter />
</t>
<div t-name="App" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
<t t-set="maincontent">
<FormView />
<Chatter />
</t>
<Navbar/>
<ControlPanel/>
<div class="content-wrapper" t-if="!env.isMobile">
<div class="content">
<t t-raw="maincontent"/>
<t t-call="maincontent"/>
</div>
</div>
<t t-else="">
<t t-raw="maincontent"/>
<t t-call="maincontent"/>
</t>
</div>
</templates>
+2 -1
View File
@@ -13,7 +13,8 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strictPropertyInitialization": true,
"strictNullChecks": true
"strictNullChecks": true,
"declarationDir": "dist/types"
},
"include": ["src/**/*.ts","src/*.ts"]
}