Compare commits

...

17 Commits

Author SHA1 Message Date
Géry Debongnie 9fad164c79 [REL] bump to v0.20.0 2019-09-05 14:41:12 +02:00
Géry Debongnie 53a8841914 [TEST] component: snapshots slot functions 2019-09-05 14:33:43 +02:00
Géry Debongnie 19f0863aa3 [IMP] qweb: allow recursive templates t-calls
closes #267
2019-09-05 14:33:43 +02:00
Géry Debongnie 28f7b44f9e [IMP] component: make t-model works with sub state keys
closes #273
2019-09-05 08:47:37 +02:00
Géry Debongnie fe46a6ccee [CLEANUP] run prettier on some code 2019-09-04 22:48:37 +02:00
Géry Debongnie 3c23a7b246 [FIX] component: t-model properly works with select
The initial value was not set on the select tag.

closes #271
2019-09-04 22:45:21 +02:00
Géry Debongnie c52799de2c [FIX] ConnectedComponent: keep props uptodate
This is a tricky bug. The problem is that a ConnectedComponent can
trigger a rendering before it is aware that there is a state change in
the store, and just before the store update event comes in.

What we do in this commit is to update the storeProps whenever a
rendering is scheduled.  However, we need to keep the rendering
information as a promise to give it in some case to the __checkUpdate
method (see the note in the render method)

closes #268
2019-09-04 15:36:10 +02:00
Géry Debongnie 4edb90f44b [FIX] router: properly handle popstate
It was not correct, the url was changed multiple times and back
navigation was broken.

Also, note that there are no tests because the history api is not
available in jsdom.
2019-09-04 13:59:54 +02:00
Géry Debongnie 0c1a7aabae [FIX] component: better templateId for multiple subcomponents
Of course, the templateId computation is kind of tricky.  Here, an issue
occurred because the templateId did not take the componentId into
account when we were in a loop, but with no keyed parent.

This meant that multiple sub components shared the same templateId,
confusing the vdom algorithm.
2019-09-03 16:38:24 +02:00
Géry Debongnie cfdce29ce7 [FIX] component: avoid key collisions in sub components
This is a difficult part of owl: we need to be able to reconcile the
vdom generated (this is done with our virtual dom algorithm), but also
to be able to reconcile the proper components.

The issue here is that when we are in a list, with a t-key attribute on all
list nodes, and those list nodes contains sub component, then the
component system used the index of the list, and did not take into
account the key from the parent.  The fix is to keep track of the
current key in the context, and uses that as a part of the templateId.
2019-09-03 12:13:57 +02:00
Géry Debongnie b344d73e07 [FIX] component: prevent collision between template ids and keys 2019-09-02 15:27:37 +02:00
Géry Debongnie 4cd08d25c8 [FIX] component: solve tricky concurrency issue
In some specific situation, a component could crash, because it was
destroyed before being mounted, but reused anyway for another rendering.
2019-09-01 21:24:54 +02:00
Géry Debongnie f9861040c8 [FIX] router: properly react on hashchange
Before this commit, hashchanges were not taken into account by the
router, if used in history mode.

Also, it's stupid, but i ran prettier on the codebase
2019-09-01 11:45:13 +02:00
Géry Debongnie 40d8090232 [FIX] router: update app if parameterized route changes 2019-08-31 15:03:17 +02:00
Géry Debongnie 55be6437f1 [FIX] router: better support for hash mode 2019-08-31 09:55:45 +02:00
Géry Debongnie 6ff4dd74bf [DOC] readme: improve main example 2019-08-28 12:22:32 +02:00
Géry Debongnie 3628b49c37 [DOC] update readme (swap some sections around) 2019-08-28 12:05:53 +02:00
29 changed files with 1209 additions and 256 deletions
+48 -40
View File
@@ -14,6 +14,44 @@ related projects. OWL's main features are:
**Try it online!** An online playground is available at [https://odoo.github.io/owl/playground](https://odoo.github.io/owl/playground) to let you experiment with the OWL framework.
## Example
Here is a short example to illustrate interactive components:
```xml
<templates>
<button t-name="Counter" t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>
<div t-name="App">
<span>Hello Owl</span>
<Counter />
</div>
</templates>
```
```javascript
class Counter extends owl.Component {
state = { value: 0 };
increment() {
this.state.value++;
}
}
class App extends owl.Component {
components = { Counter };
}
const qweb = new owl.QWeb(TEMPLATES);
const app = new App({ qweb });
app.mount(document.body);
```
Note that we assume here that the xml templates are available in the `TEMPLATES`
string. More interesting examples can be found on the
[playground](https://odoo.github.io/owl/playground) application.
## OWL's Design Principles
OWL is designed to be used in highly dynamic applications where changing
@@ -37,42 +75,23 @@ one way to define components (with classes).
If you are interested in a comparison with React or Vue, you will
find some more information [here](doc/comparison.md).
## Example
## Documentation
Here is a short example to illustrate interactive components:
The complete documentation can be found [here](doc/readme.md). The most important sections are:
```xml
<templates>
<button t-name="Counter" t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>
</templates>
```
- [Quick Start](doc/quick_start.md)
- [Component](doc/component.md)
- [QWeb](doc/qweb.md)
```javascript
class Counter extends owl.Component {
state = { value: 0 };
increment() {
this.state.value++;
}
}
const qweb = new owl.QWeb(TEMPLATES);
const counter = new Counter({ qweb });
counter.mount(document.body);
```
Note that we assume here that the xml templates are available in the `TEMPLATES`
string. More interesting examples can be found on the
[playground](https://odoo.github.io/owl/playground) application.
Found an issue in the documentation? A broken link? Some outdated information?
Submit a PR!
## Installing/Building
If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-0.19.0.js](https://github.com/odoo/owl/releases/download/v0.19.0/owl.js)
- [owl-0.19.0.min.js](https://github.com/odoo/owl/releases/download/v0.19.0/owl.min.js)
- [owl-0.20.0.js](https://github.com/odoo/owl/releases/download/v0.20.0/owl.js)
- [owl-0.20.0.min.js](https://github.com/odoo/owl/releases/download/v0.20.0/owl.min.js)
Some npm scripts are available:
@@ -86,17 +105,6 @@ Some npm scripts are available:
| `npm run tools` | build tools applications, start a static server (see [here](doc/tooling.md)) |
| `npm run tools:watch` | same as `tools`, but with a watcher to rebuild owl |
## Documentation
The complete documentation can be found [here](doc/readme.md). The most important sections are:
- [Quick Start](doc/quick_start.md)
- [Component](doc/component.md)
- [QWeb](doc/qweb.md)
Found an issue in the documentation? A broken link? Some outdated information?
Submit a PR!
## License
OWL is [GPL licensed](./LICENSE).
OWL is [LGPL licensed](./LICENSE).
+1 -1
View File
@@ -103,7 +103,7 @@ const actions = {
try {
const loginInfo = await doSomeRPC("/login/", info);
state.loginState = loginInfo;
} catch {
} catch (e) {
state.loginState = "error";
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "owl",
"version": "0.19.0",
"version": "0.20.0",
"description": "Odoo Web Library (OWL)",
"main": "src/index.ts",
"scripts": {
+18 -18
View File
@@ -238,23 +238,23 @@ QWeb.addDirective({
if (key) {
// we bind a variable to the key (could be a complex expression, so we
// want to evaluate it only once)
ctx.addLine(`let key${keyID} = ${key};`);
ctx.addLine(`let key${keyID} = 'key' + ${key};`);
}
ctx.addLine(`let def${defID};`);
let templateID = key
? `key${keyID}`
: ctx.inLoop
? `String(-${componentID} - i)`
? ctx.currentKey
? `String(${ctx.currentKey} + '_k_' + i + '_c_' + ${componentID} )`
: `String(-${componentID} - i)`
: String(componentID);
if (ctx.allowMultipleRoots) {
// necessary to prevent collisions
if (!key && ctx.inLoop) {
let id = ctx.generateID();
ctx.addLine(`let template${id} = "_slot_" + String(-${componentID} - i)`);
templateID = `template${id}`;
} else {
templateID = `"_slot_${templateID}"`;
}
templateID = `"_slot_${templateID}"`;
}
if (key || ctx.inLoop) {
let id = ctx.generateID();
ctx.addLine(`let templateId${id} = ${templateID};`);
templateID = `templateId${id}`;
}
let ref = node.getAttribute("t-ref");
@@ -422,7 +422,7 @@ QWeb.addDirective({
const key = slotNode.getAttribute("t-set")!;
slotNode.removeAttribute("t-set");
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
qweb.slots[`${slotId}_${key}`] = slotFn;
}
}
if (clone.childNodes.length) {
@@ -431,7 +431,7 @@ QWeb.addDirective({
t.appendChild(child);
}
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
qweb.slots[`${slotId}_default`] = slotFn;
}
}
@@ -449,18 +449,18 @@ QWeb.addDirective({
registerCode = `utils.defineProxy(vn${ctx.rootNode}, pvnode);`;
}
ctx.addLine(
`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${componentID}.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});${registerCode}w${componentID}.__owl__.pvnode = pvnode;});`
`def${defID} = def${defID}.then(vnode=>{if (w${componentID}.__owl__.isDestroyed){return}${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${componentID}.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});${registerCode}w${componentID}.__owl__.pvnode = pvnode;});`
);
ctx.addElse();
// need to update component
let patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue";
if (keepAlive) {
// if we have t-keepalive="1", the component could be unmounted, but then
// we __updateProps is called. This is ok, but we do not want to call
// the willPatch/patched hooks of the component in this case, so we
// disable the patch queue
patchQueueCode = `w${componentID}.__owl__.isMounted ? ${patchQueueCode} : []`;
// if we have t-keepalive="1", the component could be unmounted, but then
// we __updateProps is called. This is ok, but we do not want to call
// the willPatch/patched hooks of the component in this case, so we
// disable the patch queue
patchQueueCode = `w${componentID}.__owl__.isMounted ? ${patchQueueCode} : []`;
}
if (QWeb.dev) {
ctx.addLine(`utils.validateProps(w${componentID}.constructor, props${componentID})`);
-1
View File
@@ -25,7 +25,6 @@ export class Observer {
notifyCB() {}
async notifyChange() {
this.dirty = true;
await Promise.resolve();
if (this.dirty) {
+45 -7
View File
@@ -82,6 +82,7 @@ QWeb.addDirective({
if (node.nodeName !== "t") {
let nodeID = qweb._compileGenericNode(node, ctx);
ctx = ctx.withParent(nodeID);
ctx = ctx.subContext("currentKey", ctx.lastNodeKey);
}
let value = ctx.getValue(node.getAttribute("t-esc")!);
compileValueNode(value, node, qweb, ctx.subContext("escaping", true));
@@ -96,6 +97,7 @@ QWeb.addDirective({
if (node.nodeName !== "t") {
let nodeID = qweb._compileGenericNode(node, ctx);
ctx = ctx.withParent(nodeID);
ctx = ctx.subContext("currentKey", ctx.lastNodeKey);
}
let value = ctx.getValue(node.getAttribute("t-raw")!);
compileValueNode(value, node, qweb, ctx);
@@ -202,8 +204,50 @@ QWeb.addDirective({
const vars = Object.assign({}, ctx.variables, tempCtx.variables);
ctx.rootContext.nextID = tempCtx.nextID;
const templateMap = Object.create(ctx.templates);
// open new scope, if necessary
const hasNewVariables = Object.keys(tempCtx.variables).length > 0;
// compile sub template
let subCtx = ctx.subContext("caller", nodeCopy).subContext("variables", Object.create(vars));
subCtx = subCtx.subContext("templates", templateMap);
if (templateMap[subTemplate]) {
// OUCH, IT IS A RECURSIVE TEMPLATE SITUATION...
// This is a tricky situation... We obviously cannot inline the compiled
// template. So, what we need to do is to compile it, and make sure we
// properly transfer everything from the current scope to the sub template.
ctx.rootContext.shouldTrackScope = true;
ctx.rootContext.shouldDefineOwner = true;
let subTemplateName;
if (ctx.hasParentWidget) {
subTemplateName = ctx.templateName;
} else {
subTemplateName = `__${ctx.generateID()}`;
subCtx.variables = {};
let id = 0;
for (let v in vars) {
subCtx.variables[v] = vars[v];
(vars[v] as any).id = `_v${id++}`;
}
const subTemplateFn = qweb._compile(subTemplateName, nodeTemplate.elem, subCtx);
qweb.recursiveFns[subTemplateName] = subTemplateFn;
}
let varCode = `{}`;
if (Object.keys(vars).length) {
let id = 0;
const content = Object.values(vars)
.map((v: any) => `_v${id++}: ${v.expr}`)
.join(",");
varCode = `{${content}}`;
}
ctx.addLine(
`this.recursiveFns['${subTemplateName}'].call(this, context, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: ${varCode}, scope}));`
);
return true;
}
templateMap[subTemplate] = true;
if (hasNewVariables) {
ctx.addLine("{");
ctx.indent();
@@ -216,10 +260,6 @@ QWeb.addDirective({
// todo: handle XML variables...
}
}
// compile sub template
const subCtx = ctx.subContext("caller", nodeCopy).subContext("variables", Object.create(vars));
qweb._compileNode(nodeTemplate.elem, subCtx);
// close new scope
@@ -283,9 +323,7 @@ QWeb.addDirective({
}
if (shouldWarn) {
console.warn(
`Directive t-foreach should always be used with a t-key! (in template: '${
ctx.templateName
}')`
`Directive t-foreach should always be used with a t-key! (in template: '${ctx.templateName}')`
);
}
nodeCopy.removeAttribute("t-foreach");
+4
View File
@@ -28,10 +28,14 @@ export class Context {
allowMultipleRoots: boolean = false;
hasParentWidget: boolean = false;
scopeVars: any[] = [];
currentKey: string = "";
lastNodeKey: string = ""; // temp variable to communicate to previous caller
templates: { [key: string]: boolean } = {};
constructor(name?: string) {
this.rootContext = this;
this.templateName = name || "noname";
this.templates[this.templateName] = true;
this.addLine("var h = this.h;");
}
+12 -12
View File
@@ -230,9 +230,7 @@ QWeb.addDirective({
ctx.addLine(`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`);
ctx.addIf(`slot${slotKey}`);
ctx.addLine(
`slot${slotKey}(context.__owl__.parent, Object.assign({}, extra, {parentNode: c${
ctx.parentNode
}, vars: extra.vars, parent: owner}));`
`slot${slotKey}.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: extra.vars, parent: owner}));`
);
ctx.closeIf();
return true;
@@ -250,31 +248,33 @@ QWeb.utils.toNumber = function(val: string): number | string {
QWeb.addDirective({
name: "model",
priority: 42,
atNodeCreation({ ctx, nodeID, value, node, fullName }) {
atNodeCreation({ ctx, nodeID, value, node, fullName, addNodeHook }) {
const type = node.getAttribute("type");
let handler;
let event = fullName.includes(".lazy") ? "change" : "input";
const expr = ctx.formatExpression(`state.${value}`);
if (node.tagName === "select") {
ctx.addLine(`p${nodeID}.props = {value: context.state['${value}']};`);
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
addNodeHook("create", `n.elm.value=${expr};`);
event = "change";
handler = `(ev) => {context.state['${value}'] = ev.target.value}`;
handler = `(ev) => {${expr} = ev.target.value}`;
} else if (type === "checkbox") {
ctx.addLine(`p${nodeID}.props = {checked: context.state['${value}']};`);
handler = `(ev) => {context.state['${value}'] = ev.target.checked}`;
ctx.addLine(`p${nodeID}.props = {checked: ${expr}};`);
handler = `(ev) => {${expr} = ev.target.checked}`;
} else if (type === "radio") {
const nodeValue = node.getAttribute("value")!;
ctx.addLine(`p${nodeID}.props = {checked:context.state['${value}'] === '${nodeValue}'};`);
handler = `(ev) => {context.state['${value}'] = ev.target.value}`;
ctx.addLine(`p${nodeID}.props = {checked:${expr} === '${nodeValue}'};`);
handler = `(ev) => {${expr} = ev.target.value}`;
event = "click";
} else {
ctx.addLine(`p${nodeID}.props = {value: context.state['${value}']};`);
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
const trimCode = fullName.includes(".trim") ? ".trim()" : "";
let valueCode = `ev.target.value${trimCode}`;
if (fullName.includes(".number")) {
ctx.rootContext.shouldDefineUtils = true;
valueCode = `utils.toNumber(${valueCode})`;
}
handler = `(ev) => {context.state['${value}'] = ${valueCode}}`;
handler = `(ev) => {${expr} = ${valueCode}}`;
}
ctx.addLine(
`extra.handlers['${event}' + ${nodeID}] = extra.handlers['${event}' + ${nodeID}] || (${handler});`
+13 -10
View File
@@ -1,6 +1,7 @@
import { EventBus } from "../core/event_bus";
import { h, patch, VNode } from "../vdom/index";
import { Context } from "./context";
import { shallowEqual } from "../utils";
/**
* Owl QWeb Engine
@@ -92,14 +93,7 @@ const UTILS: Utils = {
}
return expr;
},
shallowEqual(p1, p2) {
for (let k in p1) {
if (p1[k] !== p2[k]) {
return false;
}
}
return true;
}
shallowEqual
};
function parseXML(xml: string): Document {
@@ -163,6 +157,11 @@ export class QWeb extends EventBus {
slots = {};
nextSlotId = 1;
// recursiveTemplates contains sub templates called with t-call, but which
// ends up in recursive situations. This is very similar to the slot situation,
// as in we need to propagate the scope.
recursiveFns = {};
isUpdating: boolean = false;
constructor(data?: string) {
@@ -212,7 +211,7 @@ export class QWeb extends EventBus {
* template, with the name given by the t-name attribute.
*/
addTemplates(xmlstr: string | Document) {
const doc = typeof xmlstr === 'string' ? parseXML(xmlstr) : xmlstr;
const doc = typeof xmlstr === "string" ? parseXML(xmlstr) : xmlstr;
const templates = doc.getElementsByTagName("templates")[0];
if (!templates) {
return;
@@ -328,6 +327,7 @@ export class QWeb extends EventBus {
const isDebug = elem.attributes.hasOwnProperty("t-debug");
const ctx = new Context(name);
if (parentContext) {
ctx.templates = Object.create(parentContext.templates);
ctx.variables = Object.create(parentContext.variables);
ctx.nextID = parentContext.parentNode! + 1;
ctx.parentNode = parentContext.parentNode!;
@@ -479,6 +479,7 @@ export class QWeb extends EventBus {
if (node.nodeName !== "t") {
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
ctx = ctx.withParent(nodeID);
ctx = ctx.subContext("currentKey", ctx.lastNodeKey);
let nodeHooks = {};
let addNodeHook = function(hook, handler) {
nodeHooks[hook] = nodeHooks[hook] || [];
@@ -647,7 +648,9 @@ export class QWeb extends EventBus {
let nodeID = ctx.generateID();
let nodeKey: any = (<Element>node).getAttribute("t-key");
if (nodeKey) {
nodeKey = ctx.formatExpression(nodeKey);
ctx.addLine(`const nodeKey${nodeID} = ${ctx.formatExpression(nodeKey)}`);
nodeKey = `nodeKey${nodeID}`;
ctx.lastNodeKey = nodeKey;
} else {
nodeKey = nodeID;
}
+27 -8
View File
@@ -2,6 +2,7 @@ import { Env } from "../component/component";
import { QWeb } from "../qweb/index";
import { makeDirective } from "./directive";
import { LINK_TEMPLATE, LINK_TEMPLATE_NAME } from "./Link";
import { shallowEqual } from "../utils";
type NavigationGuard = (info: {
env: Env;
@@ -84,9 +85,6 @@ export class Router {
this.routeIds.push(partialRoute.name);
}
(this as any)._listener = () => this.matchAndApplyRules(this.currentPath());
window.addEventListener("popstate", (this as any)._listener);
// setup link and directive
env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE);
QWeb.addDirective(makeDirective(<RouterEnv>env));
@@ -97,6 +95,11 @@ export class Router {
//--------------------------------------------------------------------------
async start() {
(this as any)._listener = ev => this._navigate(this.currentPath(), ev);
window.addEventListener("popstate", (this as any)._listener);
if (this.mode === "hash") {
window.addEventListener("hashchange", (this as any)._listener);
}
const result = await this.matchAndApplyRules(this.currentPath());
if (result.type === "match") {
this.currentRoute = result.route;
@@ -110,18 +113,27 @@ export class Router {
async navigate(to: Destination): Promise<boolean> {
const path = this.destToPath(to);
return this._navigate(path);
}
async _navigate(path: string, ev?: any): Promise<boolean> {
const initialName = this.currentRouteName;
const initialParams = this.currentParams;
const result = await this.matchAndApplyRules(path);
if (result.type === "match") {
const finalPath = this.routeToPath(result.route, result.params);
this.setUrlFromPath(finalPath);
const isPopStateEvent = ev && ev instanceof PopStateEvent;
if (!isPopStateEvent) {
this.setUrlFromPath(finalPath);
}
this.currentRoute = result.route;
this.currentParams = result.params;
} else if (result.type === "nomatch") {
this.currentRoute = null;
this.currentParams = null;
}
if (this.currentRouteName !== initialName) {
const didChange =
this.currentRouteName !== initialName || !shallowEqual(this.currentParams, initialParams);
if (didChange) {
this.env.qweb.forceUpdate();
return true;
}
@@ -142,8 +154,11 @@ export class Router {
//--------------------------------------------------------------------------
private setUrlFromPath(path: string) {
const url = location.origin + path;
window.history.pushState({}, path, url);
const separator = this.mode === "hash" ? "/" : "";
const url = location.origin + separator + path;
if (url !== window.location.href) {
window.history.pushState({}, path, url);
}
}
private validateDestination(dest: Destination) {
@@ -164,7 +179,8 @@ export class Router {
parts[i] = <string>params[key];
}
}
return parts.join("/");
const prefix = this.mode === "hash" ? "#" : "";
return prefix + parts.join("/");
}
private currentPath(): string {
@@ -223,6 +239,9 @@ export class Router {
if (route.path === "*") {
return {};
}
if (path.startsWith("#")) {
path = path.slice(1);
}
const descrParts = route.path.split("/");
const targetParts = path.split("/");
const l = descrParts.length;
+4 -1
View File
@@ -13,7 +13,10 @@ export function makeDirective(env: RouterEnv) {
// make new t t-component element
const comp = node.ownerDocument.createElement("t");
comp.setAttribute("t-component", "__component__" + route.name);
comp.setAttribute(first ? "t-if" : "t-elif", `env.router.currentRouteName === '${route.name}'`);
comp.setAttribute(
first ? "t-if" : "t-elif",
`env.router.currentRouteName === '${route.name}'`
);
first = false;
for (let param of route.params) {
comp.setAttribute(param, `env.router.currentParams.${param}`);
+39 -9
View File
@@ -56,6 +56,7 @@ export class ConnectedComponent<T extends Env, P, S> extends Component<T, P, S>
const observer = store.observer;
const revFn = this.deep ? observer.deepRevNumber : observer.revNumber;
(this.__owl__ as any).store = store;
(this.__owl__ as any).ownProps = this.props;
(this.__owl__ as any).revFn = revFn.bind(observer);
(this.__owl__ as any).storeHash = this.hashFunction(this.storeProps, {
prevStoreProps: this.storeProps
@@ -81,13 +82,44 @@ export class ConnectedComponent<T extends Env, P, S> extends Component<T, P, S>
super.__destroy(parent);
}
async render(force: boolean = false) {
this.__updateStoreProps(this.props);
// this is quite technical, so this deserves some explanation.
// When we have a connected component, it can be updated for 3 reasons:
// - some internal state changes (this will go through this method)
// - some props changes (if a parent is changed and need to rerender itself)
// - a store update
//
// It is possible (with connected component and parent) to have the following
// situation: the parent component is rendered first (from its state change),
// then immediately after, it is rendered (from store update). Then, if the
// __checkUpdate method is immediately over, the children component will
// be rendered again by the store update, even though it is supposed to be
// destroyed by the first rendering.
//
// So, the solution is to keep the information that there is a current
// rendering occuring with the same store state, the same props, and return
// that in the __checkUpdate method. To do this, we use the renderPromise
// deferred, which is not used by the component system once the
// component is ready, so we can use it for our own purpose.
(this.__owl__ as any).renderPromise = super.render(force);
return (this.__owl__ as any).renderPromise;
}
async __updateProps(nextProps: P, f, p, s, v) {
this.__updateStoreProps(nextProps);
return super.__updateProps(nextProps, f, p, s, v);
}
__updateStoreProps(nextProps): boolean {
const store = (this.__owl__ as any).store;
const __owl__ = this.__owl__ as any;
const store = __owl__.store;
const observer = store.observer;
if (observer.rev === __owl__.rev && nextProps === __owl__.ownProps) {
return false;
}
const storeProps = (<any>this.constructor).mapStoreToProps(
store.state,
nextProps,
@@ -97,23 +129,21 @@ export class ConnectedComponent<T extends Env, P, S> extends Component<T, P, S>
const storeHash = this.hashFunction(storeProps, options);
this.storeProps = storeProps;
let didChange = options.didChange;
if (storeHash !== (this.__owl__ as any).storeHash) {
(this.__owl__ as any).storeHash = storeHash;
if (storeHash !== __owl__.storeHash) {
__owl__.storeHash = storeHash;
didChange = true;
}
(this.__owl__ as any).rev = store.observer.rev;
__owl__.rev = store.observer.rev;
__owl__.ownProps = nextProps;
return didChange;
}
async __checkUpdate() {
const observer = (this.__owl__ as any).store.observer;
if (observer.rev === (this.__owl__ as any).rev) {
// update was already done by updateProps, from parent
return;
}
const didChange = this.__updateStoreProps(this.props);
if (didChange) {
return this.render();
}
// see note in render method
return (this.__owl__ as any).renderPromise;
}
}
+8 -8
View File
@@ -42,7 +42,7 @@ export class Store extends EventBus {
debug: boolean;
env: any;
observer: Observer;
getters: { [name: string]: (payload?) => any };
getters: { [name: string]: (payload?) => any };
constructor(config: StoreConfig, options: StoreOption = {}) {
super();
@@ -54,13 +54,13 @@ export class Store extends EventBus {
this.state = this.observer.observe(config.state || {});
this.getters = {};
if (config.getters) {
const firstArg = {
state: this.state,
getters: this.getters,
};
for (let g in config.getters) {
this.getters[g] = config.getters[g].bind(this, firstArg);
}
const firstArg = {
state: this.state,
getters: this.getters
};
for (let g in config.getters) {
this.getters[g] = config.getters[g].bind(this, firstArg);
}
}
}
+9
View File
@@ -95,3 +95,12 @@ export function debounce(func: Function, wait: number, immediate?: boolean): Fun
}
};
}
export function shallowEqual(p1, p2): boolean {
for (let k in p1) {
if (p1[k] !== p2[k]) {
return false;
}
}
return true;
}
+1 -1
View File
@@ -526,7 +526,7 @@ const htmlDomApi = {
parentNode,
nextSibling,
tagName,
setTextContent,
setTextContent
} as DOMAPI;
//------------------------------------------------------------------------------
+2 -2
View File
@@ -31,7 +31,7 @@ exports[`animations t-transition combined with component 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
w4.destroy();
};
utils.transitionRemove(vn, 'chimay', finalize);}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
@@ -76,7 +76,7 @@ exports[`animations t-transition combined with t-component and t-if 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
w4.destroy();
};
utils.transitionRemove(vn, 'chimay', finalize);}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
@@ -44,7 +44,7 @@ exports[`async rendering delayed component with t-asyncroot directive 1`] = `
w8 = new W8(parent, props8);
parent.__owl__.cmap[8] = w8.__owl__.id;
def7 = w8.__prepare();
def7 = def7.then(vnode=>{let pvnode=h(vnode.sel, {key: 8, hook: {insert(vn) {let nvn=w8.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});c5[_6_index]=pvnode;w8.__owl__.pvnode = pvnode;});
def7 = def7.then(vnode=>{if (w8.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 8, hook: {insert(vn) {let nvn=w8.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});c5[_6_index]=pvnode;w8.__owl__.pvnode = pvnode;});
} else {
def7 = def7 || w8.__updateProps(props8, extra.forceUpdate, extra.patchQueue);
def7 = def7.then(()=>{if (w8.__owl__.isDestroyed) {return};let pvnode=w8.__owl__.pvnode;c5[_6_index]=pvnode;});
@@ -72,7 +72,7 @@ exports[`async rendering delayed component with t-asyncroot directive 1`] = `
w11 = new W11(parent, props11);
parent.__owl__.cmap[11] = w11.__owl__.id;
def10 = w11.__prepare();
def10 = def10.then(vnode=>{let pvnode=h(vnode.sel, {key: 11, hook: {insert(vn) {let nvn=w11.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w11.destroy();}}});c5[_9_index]=pvnode;w11.__owl__.pvnode = pvnode;});
def10 = def10.then(vnode=>{if (w11.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 11, hook: {insert(vn) {let nvn=w11.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w11.destroy();}}});c5[_9_index]=pvnode;w11.__owl__.pvnode = pvnode;});
} else {
def10 = def10 || w11.__updateProps(props11, extra.forceUpdate, patchQueue11);
def10 = def10.then(()=>{if (w11.__owl__.isDestroyed) {return};let pvnode=w11.__owl__.pvnode;c5[_9_index]=pvnode;});
@@ -127,7 +127,7 @@ exports[`async rendering fast component with t-asyncroot directive 1`] = `
w8 = new W8(parent, props8);
parent.__owl__.cmap[8] = w8.__owl__.id;
def7 = w8.__prepare();
def7 = def7.then(vnode=>{let pvnode=h(vnode.sel, {key: 8, hook: {insert(vn) {let nvn=w8.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});c5[_6_index]=pvnode;w8.__owl__.pvnode = pvnode;});
def7 = def7.then(vnode=>{if (w8.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 8, hook: {insert(vn) {let nvn=w8.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});c5[_6_index]=pvnode;w8.__owl__.pvnode = pvnode;});
} else {
def7 = def7 || w8.__updateProps(props8, extra.forceUpdate, patchQueue8);
def7 = def7.then(()=>{if (w8.__owl__.isDestroyed) {return};let pvnode=w8.__owl__.pvnode;c5[_6_index]=pvnode;});
@@ -154,7 +154,7 @@ exports[`async rendering fast component with t-asyncroot directive 1`] = `
w11 = new W11(parent, props11);
parent.__owl__.cmap[11] = w11.__owl__.id;
def10 = w11.__prepare();
def10 = def10.then(vnode=>{let pvnode=h(vnode.sel, {key: 11, hook: {insert(vn) {let nvn=w11.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w11.destroy();}}});c5[_9_index]=pvnode;w11.__owl__.pvnode = pvnode;});
def10 = def10.then(vnode=>{if (w11.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 11, hook: {insert(vn) {let nvn=w11.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w11.destroy();}}});c5[_9_index]=pvnode;w11.__owl__.pvnode = pvnode;});
} else {
def10 = def10 || w11.__updateProps(props11, extra.forceUpdate, extra.patchQueue);
def10 = def10.then(()=>{if (w11.__owl__.isDestroyed) {return};let pvnode=w11.__owl__.pvnode;c5[_9_index]=pvnode;});
@@ -208,7 +208,7 @@ exports[`async rendering t-component with t-asyncroot directive: mixed re-render
w8 = new W8(parent, props8);
parent.__owl__.cmap[8] = w8.__owl__.id;
def7 = w8.__prepare();
def7 = def7.then(vnode=>{let pvnode=h(vnode.sel, {key: 8, hook: {insert(vn) {let nvn=w8.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});c5[_6_index]=pvnode;w8.__owl__.pvnode = pvnode;});
def7 = def7.then(vnode=>{if (w8.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 8, hook: {insert(vn) {let nvn=w8.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});c5[_6_index]=pvnode;w8.__owl__.pvnode = pvnode;});
} else {
def7 = def7 || w8.__updateProps(props8, extra.forceUpdate, extra.patchQueue);
def7 = def7.then(()=>{if (w8.__owl__.isDestroyed) {return};let pvnode=w8.__owl__.pvnode;c5[_6_index]=pvnode;});
@@ -236,7 +236,7 @@ exports[`async rendering t-component with t-asyncroot directive: mixed re-render
w11 = new W11(parent, props11);
parent.__owl__.cmap[11] = w11.__owl__.id;
def10 = w11.__prepare();
def10 = def10.then(vnode=>{let pvnode=h(vnode.sel, {key: 11, hook: {insert(vn) {let nvn=w11.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w11.destroy();}}});c5[_9_index]=pvnode;w11.__owl__.pvnode = pvnode;});
def10 = def10.then(vnode=>{if (w11.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 11, hook: {insert(vn) {let nvn=w11.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w11.destroy();}}});c5[_9_index]=pvnode;w11.__owl__.pvnode = pvnode;});
} else {
def10 = def10 || w11.__updateProps(props11, extra.forceUpdate, patchQueue11);
def10 = def10.then(()=>{if (w11.__owl__.isDestroyed) {return};let pvnode=w11.__owl__.pvnode;c5[_9_index]=pvnode;});
@@ -278,7 +278,7 @@ exports[`class and style attributes with t-component dynamic t-att-style is prop
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.style = _5;}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.style = _5;}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};w4.el.style=_5;let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -322,7 +322,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;context.refs[ref5] = w4;},remove() {},destroy(vn) {w4.destroy();delete context.refs[ref5];}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;context.refs[ref5] = w4;},remove() {},destroy(vn) {w4.destroy();delete context.refs[ref5];}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -380,7 +380,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;context.refs[ref5] = w4;},remove() {},destroy(vn) {w4.destroy();delete context.refs[ref5];}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;context.refs[ref5] = w4;},remove() {},destroy(vn) {w4.destroy();delete context.refs[ref5];}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -436,7 +436,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -479,7 +479,7 @@ exports[`composition sub components dom state with t-keepalive is preserved 1`]
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.unmount();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.unmount();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, w4.__owl__.isMounted ? extra.patchQueue : []);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;pvnode.data.hook.insert = vn => {vn.elm.parentNode.replaceChild(w4.el,vn.elm);vn.elm=w4.el;w4.__remount();};c1[_2_index]=pvnode;});
@@ -516,9 +516,10 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
context.number = _3[i];
context.number_value = _4[i];
//COMPONENT
let key8 = context['number'];
let key8 = 'key' + context['number'];
let def6;
let w7 = key8 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[key8]] : false;
let templateId9 = key8;
let w7 = templateId9 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[templateId9]] : false;
let _5_index = c1.length;
c1.push(null);
let props7 = {};
@@ -535,9 +536,9 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
let W7 = context.components && context.components[componentKey7] || QWeb.components[componentKey7];
if (!W7) {throw new Error('Cannot find the definition of component \\"' + componentKey7 + '\\"')}
w7 = new W7(parent, props7);
parent.__owl__.cmap[key8] = w7.__owl__.id;
parent.__owl__.cmap[templateId9] = w7.__owl__.id;
def6 = w7.__prepare();
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: key8, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
def6 = def6.then(vnode=>{if (w7.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: templateId9, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
} else {
def6 = def6 || w7.__updateProps(props7, extra.forceUpdate, extra.patchQueue);
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c1[_5_index]=pvnode;});
@@ -579,7 +580,7 @@ exports[`composition t-component with dynamic value 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -620,7 +621,7 @@ exports[`composition t-component with dynamic value 2 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -662,7 +663,7 @@ exports[`lifecycle hooks willPatch/patched hook with t-keepalive 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.unmount();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.unmount();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, w4.__owl__.isMounted ? extra.patchQueue : []);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;pvnode.data.hook.insert = vn => {vn.elm.parentNode.replaceChild(w4.el,vn.elm);vn.elm=w4.el;w4.__remount();};c1[_2_index]=pvnode;});
@@ -704,7 +705,7 @@ exports[`other directives with t-component t-on with handler bound to argument 1
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, 3));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, 3));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -745,7 +746,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -786,7 +787,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -827,7 +828,7 @@ exports[`other directives with t-component t-on with handler bound to object 1`]
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {val:3}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {val:3}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -868,7 +869,7 @@ exports[`other directives with t-component t-on with prevent and self modifiers
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {e.preventDefault();if (e.target !== vn.elm) {return}owner['onEv'].call(owner, e);});}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {e.preventDefault();if (e.target !== vn.elm) {return}owner['onEv'].call(owner, e);});}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -909,7 +910,7 @@ exports[`other directives with t-component t-on with self and prevent modifiers
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (e.target !== vn.elm) {return}e.preventDefault();owner['onEv'].call(owner, e);});}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (e.target !== vn.elm) {return}e.preventDefault();owner['onEv'].call(owner, e);});}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -950,7 +951,7 @@ exports[`other directives with t-component t-on with self modifier 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', owner['onEv1'].bind(owner));vn.elm.addEventListener('ev-2', function (e) {if (e.target !== vn.elm) {return}owner['onEv2'].call(owner, e);});}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', owner['onEv1'].bind(owner));vn.elm.addEventListener('ev-2', function (e) {if (e.target !== vn.elm) {return}owner['onEv2'].call(owner, e);});}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -991,7 +992,7 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {e.stopPropagation();owner['onEv1'].call(owner, e);});vn.elm.addEventListener('ev-2', function (e) {e.preventDefault();owner['onEv2'].call(owner, e);});vn.elm.addEventListener('ev-3', function (e) {e.stopPropagation();e.preventDefault();owner['onEv3'].call(owner, e);});}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {e.stopPropagation();owner['onEv1'].call(owner, e);});vn.elm.addEventListener('ev-2', function (e) {e.preventDefault();owner['onEv2'].call(owner, e);});vn.elm.addEventListener('ev-3', function (e) {e.stopPropagation();e.preventDefault();owner['onEv3'].call(owner, e);});}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -1012,9 +1013,10 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
//COMPONENT
let key5 = 'somestring';
let key5 = 'key' + 'somestring';
let def3;
let w4 = key5 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[key5]] : false;
let templateId6 = key5;
let w4 = templateId6 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[templateId6]] : false;
let _2_index = c1.length;
c1.push(null);
let props4 = {flag:context['state'].flag};
@@ -1031,9 +1033,9 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
let W4 = context.components && context.components[componentKey4] || QWeb.components[componentKey4];
if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')}
w4 = new W4(parent, props4);
parent.__owl__.cmap[key5] = w4.__owl__.id;
parent.__owl__.cmap[templateId6] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: key5, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: templateId6, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -1069,10 +1071,11 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
context.item = _3[i];
context.item_value = _4[i];
//COMPONENT
let key8 = context['item'];
let key8 = 'key' + context['item'];
let def6;
let arg9 = context['item'];
let w7 = key8 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[key8]] : false;
let templateId9 = key8;
let arg10 = context['item'];
let w7 = templateId9 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[templateId9]] : false;
let _5_index = c1.length;
c1.push(null);
let props7 = {};
@@ -1089,9 +1092,9 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
let W7 = context.components && context.components[componentKey7] || QWeb.components[componentKey7];
if (!W7) {throw new Error('Cannot find the definition of component \\"' + componentKey7 + '\\"')}
w7 = new W7(parent, props7);
parent.__owl__.cmap[key8] = w7.__owl__.id;
parent.__owl__.cmap[templateId9] = w7.__owl__.id;
def6 = w7.__prepare();
def6 = def6.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, arg9));}};let pvnode=h(vnode.sel, {key: key8, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
def6 = def6.then(vnode=>{if (w7.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, arg10));}};let pvnode=h(vnode.sel, {key: templateId9, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
} else {
def6 = def6 || w7.__updateProps(props7, extra.forceUpdate, extra.patchQueue);
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c1[_5_index]=pvnode;});
@@ -1111,8 +1114,8 @@ exports[`t-model directive .lazy modifier 1`] = `
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('input', p2, c2);
c1.push(vn2);
p2.props = {value: context.state['text']};
extra.handlers['change' + 2] = extra.handlers['change' + 2] || ((ev) => {context.state['text'] = ev.target.value});
p2.props = {value: context['state'].text};
extra.handlers['change' + 2] = extra.handlers['change' + 2] || ((ev) => {context['state'].text = ev.target.value});
p2.on['change'] = extra.handlers['change' + 2];
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
@@ -1134,8 +1137,8 @@ exports[`t-model directive basic use, on an input 1`] = `
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('input', p2, c2);
c1.push(vn2);
p2.props = {value: context.state['text']};
extra.handlers['input' + 2] = extra.handlers['input' + 2] || ((ev) => {context.state['text'] = ev.target.value});
p2.props = {value: context['state'].text};
extra.handlers['input' + 2] = extra.handlers['input' + 2] || ((ev) => {context['state'].text = ev.target.value});
p2.on['input'] = extra.handlers['input' + 2];
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
@@ -1157,9 +1160,14 @@ exports[`t-model directive on a select 1`] = `
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('select', p2, c2);
c1.push(vn2);
p2.props = {value: context.state['color']};
extra.handlers['change' + 2] = extra.handlers['change' + 2] || ((ev) => {context.state['color'] = ev.target.value});
p2.props = {value: context['state'].color};
extra.handlers['change' + 2] = extra.handlers['change' + 2] || ((ev) => {context['state'].color = ev.target.value});
p2.on['change'] = extra.handlers['change' + 2];
p2.hook = {
create: (_, n) => {
n.elm.value=context['state'].color;
},
};
var _3 = '';
let c4 = [], p4 = {key:4,attrs:{value: _3}};
var vn4 = h('option', p4, c4);
@@ -1187,6 +1195,29 @@ exports[`t-model directive on a select 1`] = `
}"
`;
exports[`t-model directive on a sub state key 1`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('input', p2, c2);
c1.push(vn2);
p2.props = {value: context['state'].something.text};
extra.handlers['input' + 2] = extra.handlers['input' + 2] || ((ev) => {context['state'].something.text = ev.target.value});
p2.on['input'] = extra.handlers['input' + 2];
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
c1.push(vn3);
var _4 = context['state'].something.text;
if (_4 || _4 === 0) {
c3.push({text: _4});
}
return vn1;
}"
`;
exports[`t-model directive on an input type=radio 1`] = `
"function anonymous(context,extra
) {
@@ -1199,8 +1230,8 @@ exports[`t-model directive on an input type=radio 1`] = `
let c5 = [], p5 = {key:5,attrs:{type: _2,id: _3,value: _4},on:{}};
var vn5 = h('input', p5, c5);
c1.push(vn5);
p5.props = {checked:context.state['choice'] === 'One'};
extra.handlers['click' + 5] = extra.handlers['click' + 5] || ((ev) => {context.state['choice'] = ev.target.value});
p5.props = {checked:context['state'].choice === 'One'};
extra.handlers['click' + 5] = extra.handlers['click' + 5] || ((ev) => {context['state'].choice = ev.target.value});
p5.on['click'] = extra.handlers['click' + 5];
var _6 = 'radio';
var _7 = 'two';
@@ -1208,8 +1239,8 @@ exports[`t-model directive on an input type=radio 1`] = `
let c9 = [], p9 = {key:9,attrs:{type: _6,id: _7,value: _8},on:{}};
var vn9 = h('input', p9, c9);
c1.push(vn9);
p9.props = {checked:context.state['choice'] === 'Two'};
extra.handlers['click' + 9] = extra.handlers['click' + 9] || ((ev) => {context.state['choice'] = ev.target.value});
p9.props = {checked:context['state'].choice === 'Two'};
extra.handlers['click' + 9] = extra.handlers['click' + 9] || ((ev) => {context['state'].choice = ev.target.value});
p9.on['click'] = extra.handlers['click' + 9];
let c10 = [], p10 = {key:10};
var vn10 = h('span', p10, c10);
@@ -1233,8 +1264,8 @@ exports[`t-model directive on an input, type=checkbox 1`] = `
let c3 = [], p3 = {key:3,attrs:{type: _2},on:{}};
var vn3 = h('input', p3, c3);
c1.push(vn3);
p3.props = {checked: context.state['flag']};
extra.handlers['input' + 3] = extra.handlers['input' + 3] || ((ev) => {context.state['flag'] = ev.target.checked});
p3.props = {checked: context['state'].flag};
extra.handlers['input' + 3] = extra.handlers['input' + 3] || ((ev) => {context['state'].flag = ev.target.checked});
p3.on['input'] = extra.handlers['input' + 3];
let c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
@@ -1281,7 +1312,7 @@ exports[`t-slot directive can define and call slots 1`] = `
parent.__owl__.cmap[4] = w4.__owl__.id;
w4.__owl__.slotId = 1;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -1303,19 +1334,146 @@ exports[`t-slot directive can define and call slots 2`] = `
c1.push(vn2);
const slot3 = this.slots[context.__owl__.slotId + '_' + 'header'];
if (slot3) {
slot3(context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
slot3.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
}
let c4 = [], p4 = {key:4};
var vn4 = h('div', p4, c4);
c1.push(vn4);
const slot5 = this.slots[context.__owl__.slotId + '_' + 'footer'];
if (slot5) {
slot5(context.__owl__.parent, Object.assign({}, extra, {parentNode: c4, vars: extra.vars, parent: owner}));
slot5.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c4, vars: extra.vars, parent: owner}));
}
return vn1;
}"
`;
exports[`t-slot directive can define and call slots 3`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
let c2 = [], p2 = {key:2};
var vn2 = h('span', p2, c2);
c1.push(vn2);
c2.push({text: \`header\`});
}"
`;
exports[`t-slot directive can define and call slots 4`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
let c2 = [], p2 = {key:2};
var vn2 = h('span', p2, c2);
c1.push(vn2);
c2.push({text: \`footer\`});
}"
`;
exports[`t-slot directive content is the default slot 1`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
let c2 = [], p2 = {key:2};
var vn2 = h('span', p2, c2);
c1.push(vn2);
c2.push({text: \`sts rocks\`});
}"
`;
exports[`t-slot directive default slot work with text nodes 1`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
c1.push({text: \`sts rocks\`});
}"
`;
exports[`t-slot directive multiple roots are allowed in a default slot 1`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
let c2 = [], p2 = {key:2};
var vn2 = h('span', p2, c2);
c1.push(vn2);
c2.push({text: \`sts\`});
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
c1.push(vn3);
c3.push({text: \`rocks\`});
}"
`;
exports[`t-slot directive multiple roots are allowed in a named slot 1`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
let c2 = [], p2 = {key:2};
var vn2 = h('span', p2, c2);
c1.push(vn2);
c2.push({text: \`sts\`});
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
c1.push(vn3);
c3.push({text: \`rocks\`});
}"
`;
exports[`t-slot directive refs are properly bound in slots 1`] = `
"function anonymous(context,extra
) {
let owner = context;
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('button', p2, c2);
c1.push(vn2);
if (!context['doSomething']) {
throw new Error('Missing handler \\\\'' + 'doSomething' + \`\\\\' when evaluating template 'slot_footer_template'\`)
}
extra.handlers['click' + 2] = extra.handlers['click' + 2] || context['doSomething'].bind(owner);
p2.on['click'] = extra.handlers['click' + 2];
const ref3 = \`myButton\`;
p2.hook = {
create: (_, n) => {
context.refs[ref3] = n.elm;
},
};
c2.push({text: \`do something\`});
}"
`;
exports[`t-slot directive slots are rendered with proper context 1`] = `
"function anonymous(context,extra
) {
let owner = context;
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('button', p2, c2);
c1.push(vn2);
if (!context['doSomething']) {
throw new Error('Missing handler \\\\'' + 'doSomething' + \`\\\\' when evaluating template 'slot_footer_template'\`)
}
extra.handlers['click' + 2] = extra.handlers['click' + 2] || context['doSomething'].bind(owner);
p2.on['click'] = extra.handlers['click' + 2];
c2.push({text: \`do something\`});
}"
`;
exports[`t-slot directive slots are rendered with proper context, part 2 1`] = `
"function anonymous(context,extra
) {
@@ -1326,7 +1484,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 1`] = `
var vn2 = h('a', p2, c2);
const slot3 = this.slots[context.__owl__.slotId + '_' + 'default'];
if (slot3) {
slot3(context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
slot3.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
}
return vn2;
}"
@@ -1366,12 +1524,14 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
scope.user = context.user;
context.user_value = _5[i];
scope.user_value = context.user_value;
let c6 = [], p6 = {key:context['user'].id};
const nodeKey6 = context['user'].id
let c6 = [], p6 = {key:nodeKey6};
var vn6 = h('li', p6, c6);
c2.push(vn6);
//COMPONENT
let def8;
let w9 = String(-9 - i) in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[String(-9 - i)]] : false;
let templateId10 = String(nodeKey6 + '_k_' + i + '_c_' + 9 );
let w9 = templateId10 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[templateId10]] : false;
let _7_index = c6.length;
c6.push(null);
let props9 = {to:'/user/'+context['user'].id};
@@ -1388,10 +1548,10 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
let W9 = context.components && context.components[componentKey9] || QWeb.components[componentKey9];
if (!W9) {throw new Error('Cannot find the definition of component \\"' + componentKey9 + '\\"')}
w9 = new W9(parent, props9);
parent.__owl__.cmap[String(-9 - i)] = w9.__owl__.id;
parent.__owl__.cmap[templateId10] = w9.__owl__.id;
w9.__owl__.slotId = 1;
def8 = w9.__prepare(Object.assign({}, scope));
def8 = def8.then(vnode=>{let pvnode=h(vnode.sel, {key: String(-9 - i), hook: {insert(vn) {let nvn=w9.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}});c6[_7_index]=pvnode;w9.__owl__.pvnode = pvnode;});
def8 = def8.then(vnode=>{if (w9.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: templateId10, hook: {insert(vn) {let nvn=w9.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}});c6[_7_index]=pvnode;w9.__owl__.pvnode = pvnode;});
} else {
def8 = def8 || w9.__updateProps(props9, extra.forceUpdate, extra.patchQueue, Object.assign({}, scope));
def8 = def8.then(()=>{if (w9.__owl__.isDestroyed) {return};let pvnode=w9.__owl__.pvnode;c6[_7_index]=pvnode;});
@@ -1402,6 +1562,20 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
}"
`;
exports[`t-slot directive slots are rendered with proper context, part 2 3`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c6 = extra.parentNode;
Object.assign(context, extra.scope);
c6.push({text: \`User \`});
var _7 = context['user'].name;
if (_7 || _7 === 0) {
c6.push({text: _7});
}
}"
`;
exports[`t-slot directive slots are rendered with proper context, part 3 1`] = `
"function anonymous(context,extra
) {
@@ -1412,7 +1586,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 1`] = `
var vn2 = h('a', p2, c2);
const slot3 = this.slots[context.__owl__.slotId + '_' + 'default'];
if (slot3) {
slot3(context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
slot3.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
}
return vn2;
}"
@@ -1452,13 +1626,15 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
scope.user = context.user;
context.user_value = _5[i];
scope.user_value = context.user_value;
let c6 = [], p6 = {key:context['user'].id};
const nodeKey6 = context['user'].id
let c6 = [], p6 = {key:nodeKey6};
var vn6 = h('li', p6, c6);
c2.push(vn6);
var _7 = 'User '+context['user'].name;
//COMPONENT
let def9;
let w10 = String(-10 - i) in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[String(-10 - i)]] : false;
let templateId11 = String(nodeKey6 + '_k_' + i + '_c_' + 10 );
let w10 = templateId11 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[templateId11]] : false;
let _8_index = c6.length;
c6.push(null);
let props10 = {to:'/user/'+context['user'].id};
@@ -1475,10 +1651,10 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
let W10 = context.components && context.components[componentKey10] || QWeb.components[componentKey10];
if (!W10) {throw new Error('Cannot find the definition of component \\"' + componentKey10 + '\\"')}
w10 = new W10(parent, props10);
parent.__owl__.cmap[String(-10 - i)] = w10.__owl__.id;
parent.__owl__.cmap[templateId11] = w10.__owl__.id;
w10.__owl__.slotId = 1;
def9 = w10.__prepare(Object.assign({}, scope), {_7});
def9 = def9.then(vnode=>{let pvnode=h(vnode.sel, {key: String(-10 - i), hook: {insert(vn) {let nvn=w10.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});c6[_8_index]=pvnode;w10.__owl__.pvnode = pvnode;});
def9 = def9.then(vnode=>{if (w10.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: templateId11, hook: {insert(vn) {let nvn=w10.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});c6[_8_index]=pvnode;w10.__owl__.pvnode = pvnode;});
} else {
def9 = def9 || w10.__updateProps(props10, extra.forceUpdate, extra.patchQueue, Object.assign({}, scope), {_7});
def9 = def9.then(()=>{if (w10.__owl__.isDestroyed) {return};let pvnode=w10.__owl__.pvnode;c6[_8_index]=pvnode;});
@@ -1489,6 +1665,19 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
}"
`;
exports[`t-slot directive slots are rendered with proper context, part 3 3`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c6 = extra.parentNode;
let _7 = extra.vars._7
Object.assign(context, extra.scope);
if (_7 || _7 === 0) {
c6.push({text: _7});
}
}"
`;
exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
"function anonymous(context,extra
) {
@@ -1522,7 +1711,7 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
parent.__owl__.cmap[5] = w5.__owl__.id;
w5.__owl__.slotId = 1;
def4 = w5.__prepare({}, {_2});
def4 = def4.then(vnode=>{let pvnode=h(vnode.sel, {key: 5, hook: {insert(vn) {let nvn=w5.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w5.destroy();}}});c1[_3_index]=pvnode;w5.__owl__.pvnode = pvnode;});
def4 = def4.then(vnode=>{if (w5.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 5, hook: {insert(vn) {let nvn=w5.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w5.destroy();}}});c1[_3_index]=pvnode;w5.__owl__.pvnode = pvnode;});
} else {
def4 = def4 || w5.__updateProps(props5, extra.forceUpdate, extra.patchQueue, {}, {_2});
def4 = def4.then(()=>{if (w5.__owl__.isDestroyed) {return};let pvnode=w5.__owl__.pvnode;c1[_3_index]=pvnode;});
@@ -1532,6 +1721,19 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
}"
`;
exports[`t-slot directive slots are rendered with proper context, part 4 2`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = extra.parentNode;
let _2 = extra.vars._2
Object.assign(context, extra.scope);
if (_2 || _2 === 0) {
c1.push({text: _2});
}
}"
`;
exports[`top level sub widgets basic use 1`] = `
"function anonymous(context,extra
) {
@@ -1562,7 +1764,7 @@ exports[`top level sub widgets basic use 1`] = `
w3 = new W3(parent, props3);
parent.__owl__.cmap[3] = w3.__owl__.id;
def2 = w3.__prepare();
def2 = def2.then(vnode=>{let pvnode=h(vnode.sel, {key: 3, hook: {insert(vn) {let nvn=w3.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});utils.defineProxy(vn4, pvnode);w3.__owl__.pvnode = pvnode;});
def2 = def2.then(vnode=>{if (w3.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 3, hook: {insert(vn) {let nvn=w3.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});utils.defineProxy(vn4, pvnode);w3.__owl__.pvnode = pvnode;});
} else {
def2 = def2 || w3.__updateProps(props3, extra.forceUpdate, extra.patchQueue);
def2 = def2.then(()=>{if (w3.__owl__.isDestroyed) {return};let pvnode=w3.__owl__.pvnode;utils.defineProxy(vn4, pvnode);});
@@ -1603,7 +1805,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
w3 = new W3(parent, props3);
parent.__owl__.cmap[3] = w3.__owl__.id;
def2 = w3.__prepare();
def2 = def2.then(vnode=>{let pvnode=h(vnode.sel, {key: 3, hook: {insert(vn) {let nvn=w3.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});utils.defineProxy(vn4, pvnode);w3.__owl__.pvnode = pvnode;});
def2 = def2.then(vnode=>{if (w3.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 3, hook: {insert(vn) {let nvn=w3.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});utils.defineProxy(vn4, pvnode);w3.__owl__.pvnode = pvnode;});
} else {
def2 = def2 || w3.__updateProps(props3, extra.forceUpdate, extra.patchQueue);
def2 = def2.then(()=>{if (w3.__owl__.isDestroyed) {return};let pvnode=w3.__owl__.pvnode;utils.defineProxy(vn4, pvnode);});
@@ -1632,7 +1834,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
w7 = new W7(parent, props7);
parent.__owl__.cmap[7] = w7.__owl__.id;
def6 = w7.__prepare();
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});utils.defineProxy(vn8, pvnode);w7.__owl__.pvnode = pvnode;});
def6 = def6.then(vnode=>{if (w7.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});utils.defineProxy(vn8, pvnode);w7.__owl__.pvnode = pvnode;});
} else {
def6 = def6 || w7.__updateProps(props7, extra.forceUpdate, extra.patchQueue);
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;utils.defineProxy(vn8, pvnode);});
@@ -36,7 +36,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
utils.validateProps(w4.constructor, props4)
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
+179
View File
@@ -160,6 +160,30 @@ describe("basic widget properties", () => {
widget.render();
expect(fixture.innerHTML).toBe(`<div><span></span></div>`);
});
test("reconciliation alg is not confused in some specific situation", async () => {
// in this test, we set t-key to 4 because it was in conflict with the
// template id corresponding to the first child.
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<Child />
<Child t-key="4"/>
</div>
<span t-name="Child">child</span>
</templates>
`);
class Child extends Component<any, any, any> {}
class Parent extends Component<any, any, any> {
components = { Child };
}
const widget = new Parent(env);
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>child</span><span>child</span></div>");
});
});
describe("lifecycle hooks", () => {
@@ -833,6 +857,50 @@ describe("destroy method", () => {
expect(fixture.innerHTML).toBe("");
expect(isRendered).toBe(false);
});
test("destroying a widget before being mounted", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="Parent" t-on-some-event="doStuff">
<Child />
</div>
<span t-name="Child">
<GrandChild t-if="state.flag" val="something"/>
<button t-on-click="doSomething">click</button>
</span>
<span t-name="GrandChild">
<t t-esc="props.val.val"/>
</span>
</templates>`);
class Parent extends Component<any, any, any> {
components = { Child };
state = { p: 1 };
doStuff() {
this.state.p = 2;
}
}
class Child extends Component<any, any, any> {
components = { GrandChild };
state = { val: 33, flag: false };
doSomething() {
this.state.val = 12;
this.state.flag = true;
this.trigger("some-event");
}
get something() {
return { val: this.state.val };
}
}
class GrandChild extends Component<any, any, any> {}
const parent = new Parent(env);
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span><button>click</button></span></div>");
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><span><span>12</span><button>click</button></span></div>");
});
});
describe("composition", () => {
@@ -1274,6 +1342,62 @@ describe("composition", () => {
);
});
test("list of sub components inside other nodes", async () => {
// this confuses the patching algorithm...
env.qweb.addTemplate("ChildWidget", `<span>child</span>`);
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<div t-foreach="state.blips" t-as="blip" t-key="blip.id">
<SubWidget />
</div>
</div>
<span t-name="SubWidget">asdf</span>
</templates>`);
class SubWidget extends Widget {}
class Parent extends Widget {
components = { SubWidget };
state = { blips: [{ a: "a", id: 1 }, { b: "b", id: 2 }, { c: "c", id: 4 }] };
}
const parent = new Parent(env);
await parent.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div><span>asdf</span></div><div><span>asdf</span></div><div><span>asdf</span></div></div>"
);
parent.state.blips.splice(0, 1);
await nextTick();
expect(fixture.innerHTML).toBe(
"<div><div><span>asdf</span></div><div><span>asdf</span></div></div>"
);
});
test("list of two sub components inside other nodes", async () => {
// this confuses the patching algorithm...
env.qweb.addTemplate("ChildWidget", `<span>child</span>`);
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<div t-foreach="state.blips" t-as="blip" t-key="blip.id">
<SubWidget />
<SubWidget />
</div>
</div>
<span t-name="SubWidget">asdf</span>
</templates>`);
class SubWidget extends Widget {}
class Parent extends Widget {
components = { SubWidget };
state = { blips: [{ a: "a", id: 1 }] };
}
const parent = new Parent(env);
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span>asdf</span><span>asdf</span></div></div>");
});
test("t-component with dynamic value", async () => {
env.qweb.addTemplate("ParentWidget", `<div><t t-component="{{state.widget}}"/></div>`);
class ParentWidget extends Widget {
@@ -2927,6 +3051,8 @@ describe("t-slot directive", () => {
);
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
expect(env.qweb.templates.Dialog.fn.toString()).toMatchSnapshot();
expect(env.qweb.slots['1_header'].toString()).toMatchSnapshot();
expect(env.qweb.slots['1_footer'].toString()).toMatchSnapshot();
});
test("slots are rendered with proper context", async () => {
@@ -2962,6 +3088,7 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe(
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
);
expect(env.qweb.slots['1_footer'].toString()).toMatchSnapshot();
});
test("slots are rendered with proper context, part 2", async () => {
@@ -2999,6 +3126,7 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe(
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
);
expect(env.qweb.slots['1_default'].toString()).toMatchSnapshot();
});
test("slots are rendered with proper context, part 3", async () => {
@@ -3037,6 +3165,7 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe(
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
);
expect(env.qweb.slots['1_default'].toString()).toMatchSnapshot();
});
test("slots are rendered with proper context, part 4", async () => {
@@ -3069,6 +3198,7 @@ describe("t-slot directive", () => {
app.state.user.name = "David";
await nextTick();
expect(fixture.innerHTML).toBe('<div><a href="/user/1">User David</a></div>');
expect(env.qweb.slots['1_default'].toString()).toMatchSnapshot();
});
test("refs are properly bound in slots", async () => {
@@ -3104,6 +3234,7 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe(
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
);
expect(env.qweb.slots['1_footer'].toString()).toMatchSnapshot();
});
test("content is the default slot", async () => {
@@ -3125,6 +3256,7 @@ describe("t-slot directive", () => {
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span>sts rocks</span></div></div>");
expect(env.qweb.slots['1_default'].toString()).toMatchSnapshot();
});
test("default slot work with text nodes", async () => {
@@ -3144,6 +3276,7 @@ describe("t-slot directive", () => {
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div>sts rocks</div></div>");
expect(env.qweb.slots['1_default'].toString()).toMatchSnapshot();
});
test("multiple roots are allowed in a named slot", async () => {
@@ -3168,6 +3301,7 @@ describe("t-slot directive", () => {
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
expect(env.qweb.slots['1_content'].toString()).toMatchSnapshot();
});
test("multiple roots are allowed in a default slot", async () => {
@@ -3190,6 +3324,7 @@ describe("t-slot directive", () => {
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
expect(env.qweb.slots['1_default'].toString()).toMatchSnapshot();
});
test("missing slots are ignored", async () => {
@@ -3459,6 +3594,50 @@ describe("t-model directive", () => {
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
});
test("on a select, initial state", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="SomeComponent">
<select t-model="color">
<option value="">Please select one</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
</div>
</templates>`);
class SomeComponent extends Widget {
state = { color: "red" };
}
const comp = new SomeComponent(env);
await comp.mount(fixture);
const select = fixture.querySelector("select")!;
expect(select.value).toBe("red");
});
test("on a sub state key", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="SomeComponent">
<input t-model="something.text"/>
<span><t t-esc="state.something.text"/></span>
</div>
</templates>`);
class SomeComponent extends Widget {
state = { something: {text: "" }};
}
const comp = new SomeComponent(env);
await comp.mount(fixture);
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
const input = fixture.querySelector("input")!;
await editInput(input, "test");
expect(comp.state.something.text).toBe("test");
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
});
test(".lazy modifier", async () => {
env.qweb.addTemplates(`
<templates>
+5 -6
View File
@@ -280,7 +280,7 @@ describe("props validation", () => {
test("props: extra props cause an error, part 2", async () => {
class TestWidget extends Widget {
static props = {message: true};
static props = { message: true };
}
expect(() => {
@@ -294,23 +294,22 @@ describe("props validation", () => {
}
expect(() => {
QWeb.utils.validateProps(TestWidget, { message: 1});
QWeb.utils.validateProps(TestWidget, { message: 1 });
}).not.toThrow();
});
test("optional prop do not cause an error if value is undefined", async () => {
class TestWidget extends Widget {
static props = {message: {type: String, optional: true}};
static props = { message: { type: String, optional: true } };
}
expect(() => {
QWeb.utils.validateProps(TestWidget, { message: undefined});
QWeb.utils.validateProps(TestWidget, { message: undefined });
}).not.toThrow();
expect(() => {
QWeb.utils.validateProps(TestWidget, { message: null});
QWeb.utils.validateProps(TestWidget, { message: null });
}).toThrow();
});
});
describe("default props", () => {
+1 -1
View File
@@ -65,7 +65,7 @@ describe("observer", () => {
expect(observer.revNumber(obj)).toBe(1);
expect(observer.deepRevNumber(obj)).toBe(1);
expect(observer.rev).toBe(1);
expect(typeof obj.date.getFullYear()).toBe('number');
expect(typeof obj.date.getFullYear()).toBe("number");
expect(obj.date).toBe(date);
obj.date = new Date();
+232 -6
View File
@@ -361,7 +361,8 @@ exports[`foreach iterate on items (on a element node) 1`] = `
context.item_index = i;
context.item = _3[i];
context.item_value = _4[i];
let c5 = [], p5 = {key:context['item']};
const nodeKey5 = context['item']
let c5 = [], p5 = {key:nodeKey5};
var vn5 = h('span', p5, c5);
c1.push(vn5);
var _6 = context['item'];
@@ -788,6 +789,226 @@ exports[`t-call (template calling inherit context 1`] = `
}"
`;
exports[`t-call (template calling recursive template, part 1 1`] = `
"function anonymous(context,extra
) {
let owner = context;
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
let c2 = [], p2 = {key:2};
var vn2 = h('span', p2, c2);
c1.push(vn2);
c2.push({text: \`hey\`});
if (false) {
this.recursiveFns['__3'].call(this, context, Object.assign({}, extra, {parentNode: c1, vars: {}, scope}));
}
return vn1;
}"
`;
exports[`t-call (template calling recursive template, part 1 2`] = `
"function anonymous(context,extra
) {
let owner = context;
var h = this.h;
let c1 = extra.parentNode;
Object.assign(context, extra.scope);
let c2 = [], p2 = {key:2};
var vn2 = h('div', p2, c2);
c1.push(vn2);
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
c2.push(vn3);
c3.push({text: \`hey\`});
if (false) {
this.recursiveFns['__3'].call(this, context, Object.assign({}, extra, {parentNode: c2, vars: {}, scope}));
}
}"
`;
exports[`t-call (template calling recursive template, part 2 1`] = `
"function anonymous(context,extra
) {
let owner = context;
context = Object.create(context);
const scope = Object.create(null);
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
{
let _2 = context['root'];
let c3 = [], p3 = {key:3};
var vn3 = h('div', p3, c3);
c1.push(vn3);
let c4 = [], p4 = {key:4};
var vn4 = h('p', p4, c4);
c3.push(vn4);
var _5 = _2.val;
if (_5 || _5 === 0) {
c4.push({text: _5});
}
var _6 = _2.children||[];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
var _7 = _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
}
var _length7 = _7.length;
for (let i = 0; i < _length7; i++) {
context.subtree_first = i === 0;
scope.subtree_first = context.subtree_first;
context.subtree_last = i === _length7 - 1;
scope.subtree_last = context.subtree_last;
context.subtree_index = i;
scope.subtree_index = context.subtree_index;
context.subtree = _7[i];
scope.subtree = context.subtree;
context.subtree_value = _8[i];
scope.subtree_value = context.subtree_value;
this.recursiveFns['__10'].call(this, context, Object.assign({}, extra, {parentNode: c3, vars: {_v0: context['subtree']}, scope}));
}
}
return vn1;
}"
`;
exports[`t-call (template calling recursive template, part 2 2`] = `
"function anonymous(context,extra
) {
let owner = context;
context = Object.create(context);
const scope = Object.create(null);
var h = this.h;
let c3 = extra.parentNode;
let _v0 = extra.vars._v0
Object.assign(context, extra.scope);
let c4 = [], p4 = {key:4};
var vn4 = h('div', p4, c4);
c3.push(vn4);
let c5 = [], p5 = {key:5};
var vn5 = h('p', p5, c5);
c4.push(vn5);
var _6 = _v0.val;
if (_6 || _6 === 0) {
c5.push({text: _6});
}
var _7 = _v0.children||[];
if (!_7) { throw new Error('QWeb error: Invalid loop expression')}
var _8 = _9 = _7;
if (!(_7 instanceof Array)) {
_8 = Object.keys(_7);
_9 = Object.values(_7);
}
var _length8 = _8.length;
for (let i = 0; i < _length8; i++) {
context.subtree_first = i === 0;
scope.subtree_first = context.subtree_first;
context.subtree_last = i === _length8 - 1;
scope.subtree_last = context.subtree_last;
context.subtree_index = i;
scope.subtree_index = context.subtree_index;
context.subtree = _8[i];
scope.subtree = context.subtree;
context.subtree_value = _9[i];
scope.subtree_value = context.subtree_value;
this.recursiveFns['__10'].call(this, context, Object.assign({}, extra, {parentNode: c4, vars: {_v0: context['subtree']}, scope}));
}
}"
`;
exports[`t-call (template calling recursive template, part 3 1`] = `
"function anonymous(context,extra
) {
let owner = context;
context = Object.create(context);
const scope = Object.create(null);
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
{
let _2 = context['root'];
let c3 = [], p3 = {key:3};
var vn3 = h('div', p3, c3);
c1.push(vn3);
let c4 = [], p4 = {key:4};
var vn4 = h('p', p4, c4);
c3.push(vn4);
var _5 = _2.val;
if (_5 || _5 === 0) {
c4.push({text: _5});
}
var _6 = _2.children||[];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
var _7 = _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
}
var _length7 = _7.length;
for (let i = 0; i < _length7; i++) {
context.subtree_first = i === 0;
scope.subtree_first = context.subtree_first;
context.subtree_last = i === _length7 - 1;
scope.subtree_last = context.subtree_last;
context.subtree_index = i;
scope.subtree_index = context.subtree_index;
context.subtree = _7[i];
scope.subtree = context.subtree;
context.subtree_value = _8[i];
scope.subtree_value = context.subtree_value;
this.recursiveFns['__10'].call(this, context, Object.assign({}, extra, {parentNode: c3, vars: {_v0: context['subtree']}, scope}));
}
}
return vn1;
}"
`;
exports[`t-call (template calling recursive template, part 3 2`] = `
"function anonymous(context,extra
) {
let owner = context;
context = Object.create(context);
const scope = Object.create(null);
var h = this.h;
let c3 = extra.parentNode;
let _v0 = extra.vars._v0
Object.assign(context, extra.scope);
let c4 = [], p4 = {key:4};
var vn4 = h('div', p4, c4);
c3.push(vn4);
let c5 = [], p5 = {key:5};
var vn5 = h('p', p5, c5);
c4.push(vn5);
var _6 = _v0.val;
if (_6 || _6 === 0) {
c5.push({text: _6});
}
var _7 = _v0.children||[];
if (!_7) { throw new Error('QWeb error: Invalid loop expression')}
var _8 = _9 = _7;
if (!(_7 instanceof Array)) {
_8 = Object.keys(_7);
_9 = Object.values(_7);
}
var _length8 = _8.length;
for (let i = 0; i < _length8; i++) {
context.subtree_first = i === 0;
scope.subtree_first = context.subtree_first;
context.subtree_last = i === _length8 - 1;
scope.subtree_last = context.subtree_last;
context.subtree_index = i;
scope.subtree_index = context.subtree_index;
context.subtree = _8[i];
scope.subtree = context.subtree;
context.subtree_value = _9[i];
scope.subtree_value = context.subtree_value;
this.recursiveFns['__10'].call(this, context, Object.assign({}, extra, {parentNode: c4, vars: {_v0: context['subtree']}, scope}));
}
}"
`;
exports[`t-call (template calling scoped parameters 1`] = `
"function anonymous(context,extra
) {
@@ -1095,7 +1316,8 @@ exports[`t-key can use t-key directive on a node 1`] = `
"function anonymous(context,extra
) {
var h = this.h;
let c1 = [], p1 = {key:context['beer'].id};
const nodeKey1 = context['beer'].id
let c1 = [], p1 = {key:nodeKey1};
var vn1 = h('div', p1, c1);
var _2 = context['beer'].name;
if (_2 || _2 === 0) {
@@ -1126,7 +1348,8 @@ exports[`t-key t-key directive in a list 1`] = `
context.beer_index = i;
context.beer = _3[i];
context.beer_value = _4[i];
let c5 = [], p5 = {key:context['beer'].id};
const nodeKey5 = context['beer'].id
let c5 = [], p5 = {key:nodeKey5};
var vn5 = h('li', p5, c5);
c1.push(vn5);
var _6 = context['beer'].name;
@@ -1227,7 +1450,8 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
context.action_index = i;
context.action = _3[i];
context.action_value = _4[i];
let c5 = [], p5 = {key:context['action_index']};
const nodeKey5 = context['action_index']
let c5 = [], p5 = {key:nodeKey5};
var vn5 = h('li', p5, c5);
c1.push(vn5);
let c6 = [], p6 = {key:6,on:{}};
@@ -1560,7 +1784,8 @@ exports[`t-ref refs in a loop 1`] = `
context.item_index = i;
context.item = _3[i];
context.item_value = _4[i];
let c5 = [], p5 = {key:context['item']};
const nodeKey5 = context['item']
let c5 = [], p5 = {key:nodeKey5};
var vn5 = h('div', p5, c5);
c1.push(vn5);
const ref6 = (context['item']);
@@ -1720,7 +1945,8 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
context.elem_index = i;
context.elem = _4[i];
context.elem_value = _5[i];
let c6 = [], p6 = {key:context['elem_index']};
const nodeKey6 = context['elem_index']
let c6 = [], p6 = {key:nodeKey6};
var vn6 = h('div', p6, c6);
c1.push(vn6);
let c7 = [], p7 = {key:7};
+70
View File
@@ -573,6 +573,76 @@ describe("t-call (template calling", () => {
const expected = "<div>ok</div>";
expect(trim(renderToString(qweb, "caller"))).toBe(expected);
});
test("recursive template, part 1", () => {
qweb.addTemplates(`
<templates>
<div t-name="recursive">
<span>hey</span>
<t t-if="false">
<t t-call="recursive"/>
</t>
</div>
</templates>
`);
const expected = "<div><span>hey</span></div>";
expect(renderToString(qweb, "recursive")).toBe(expected);
const recursiveFn = Object.values(qweb.recursiveFns)[0];
expect(recursiveFn.toString()).toMatchSnapshot();
});
test("recursive template, part 2", () => {
qweb.addTemplates(`
<templates>
<div t-name="Parent">
<t t-call="nodeTemplate">
<t t-set="node" t-value="root"/>
</t>
</div>
<div t-name="nodeTemplate">
<p><t t-esc="node.val"/></p>
<t t-foreach="node.children or []" t-as="subtree">
<t t-call="nodeTemplate">
<t t-set="node" t-value="subtree"/>
</t>
</t>
</div>
</templates>
`);
const root = { val: "a", children: [{val: "b"}, {val: "c"}]};
const expected = "<div><div><p>a</p><div><p>b</p></div><div><p>c</p></div></div></div>";
expect(renderToString(qweb, "Parent", {root })).toBe(expected);
const recursiveFn = Object.values(qweb.recursiveFns)[0];
expect(recursiveFn.toString()).toMatchSnapshot();
});
test("recursive template, part 3", () => {
qweb.addTemplates(`
<templates>
<div t-name="Parent">
<t t-call="nodeTemplate">
<t t-set="node" t-value="root"/>
</t>
</div>
<div t-name="nodeTemplate">
<p><t t-esc="node.val"/></p>
<t t-foreach="node.children or []" t-as="subtree">
<t t-call="nodeTemplate">
<t t-set="node" t-value="subtree"/>
</t>
</t>
</div>
</templates>
`);
const root = { val: "a", children: [{val: "b", children: [{val: "d"}]}, {val: "c"}]};
const expected = "<div><div><p>a</p><div><p>b</p><div><p>d</p></div></div><div><p>c</p></div></div></div>";
expect(renderToString(qweb, "Parent", {root })).toBe(expected);
const recursiveFn = Object.values(qweb.recursiveFns)[0];
expect(recursiveFn.toString()).toMatchSnapshot();
});
});
describe("foreach", () => {
+1 -1
View File
@@ -17,7 +17,7 @@ exports[`Link component can render simple cases 1`] = `
p3.on['click'] = extra.handlers['click' + 3];
const slot4 = this.slots[context.__owl__.slotId + '_' + 'default'];
if (slot4) {
slot4(context.__owl__.parent, Object.assign({}, extra, {parentNode: c3, vars: extra.vars, parent: owner}));
slot4.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c3, vars: extra.vars, parent: owner}));
}
return vn3;
}"
@@ -32,7 +32,7 @@ exports[`router directive t-routecomponent can render parameterized route 1`] =
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -75,7 +75,7 @@ exports[`router directive t-routecomponent can render parameterized route with s
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -118,7 +118,7 @@ exports[`router directive t-routecomponent can render simple cases 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
@@ -147,7 +147,7 @@ exports[`router directive t-routecomponent can render simple cases 1`] = `
w7 = new W7(parent, props7);
parent.__owl__.cmap[7] = w7.__owl__.id;
def6 = w7.__prepare();
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
def6 = def6.then(vnode=>{if (w7.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
} else {
def6 = def6 || w7.__updateProps(props7, extra.forceUpdate, extra.patchQueue);
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c1[_5_index]=pvnode;});
+103 -25
View File
@@ -1,5 +1,5 @@
import { Destination, Router, RouterEnv, Route } from "../../src/router/Router";
import { makeTestEnv } from "../helpers";
import { Destination, RouterEnv, Route } from "../../src/router/Router";
import { makeTestEnv, nextTick } from "../helpers";
import { TestRouter } from "./TestRouter";
let env: RouterEnv;
@@ -25,18 +25,58 @@ describe("router miscellaneous", () => {
]);
}).toThrow(`Invalid destination: {"abc":"hey"}`);
});
test("navigate to same route but with different params should trigger update", async () => {
router = new TestRouter(env, [{ name: "users", path: "/users/{{id}}" }]);
env.qweb.forceUpdate = jest.fn();
await router.navigate({ to: "users", params: { id: 3 } });
expect(window.location.pathname).toBe("/users/3");
expect(env.qweb.forceUpdate).toHaveBeenCalledTimes(1);
await router.navigate({ to: "users", params: { id: 5 } });
expect(window.location.pathname).toBe("/users/5");
expect(env.qweb.forceUpdate).toHaveBeenCalledTimes(2);
});
test("changing url to same route but with different params should trigger update (hash mode)", async () => {
env.qweb.forceUpdate = jest.fn();
router = new TestRouter(env, [{ name: "users", path: "/users/{{id}}" }], { mode: "hash" });
await router.start();
await router.navigate({ to: "users", params: { id: 3 } });
expect(window.location.hash).toBe("#/users/3");
expect(env.qweb.forceUpdate).toHaveBeenCalledTimes(1);
window.location.hash = "/users/5";
window.dispatchEvent(new Event("hashchange"));
await nextTick();
expect(window.location.hash).toBe("#/users/5");
expect(env.qweb.forceUpdate).toHaveBeenCalledTimes(2);
});
});
describe("routeToPath", () => {
const routeToPath = Router.prototype["routeToPath"];
test("simple non parameterized path", () => {
expect(routeToPath({path: "/abc"} as Route, {})).toBe("/abc");
expect(routeToPath({path: "/abc/def"} as Route, {})).toBe("/abc/def");
expect(routeToPath({path: "/abc"} as Route, { val: 12 })).toBe("/abc");
router = new TestRouter(env, []);
expect(router["routeToPath"]({ path: "/abc" } as Route, {})).toBe("/abc");
expect(router["routeToPath"]({ path: "/abc/def" } as Route, {})).toBe("/abc/def");
expect(router["routeToPath"]({ path: "/abc" } as Route, { val: 12 })).toBe("/abc");
});
test("simple parameterized path", () => {
expect(routeToPath({path: "/abc/{{def}}"} as Route, { def: 34 })).toBe("/abc/34");
router = new TestRouter(env, []);
expect(router["routeToPath"]({ path: "/abc/{{def}}" } as Route, { def: 34 })).toBe("/abc/34");
});
test("simple non parameterized path, mode = hash", () => {
router = new TestRouter(env, [], { mode: "hash" });
expect(router["routeToPath"]({ path: "/abc" } as Route, {})).toBe("#/abc");
expect(router["routeToPath"]({ path: "/abc/def" } as Route, {})).toBe("#/abc/def");
expect(router["routeToPath"]({ path: "/abc" } as Route, { val: 12 })).toBe("#/abc");
});
test("simple parameterized path, mode=hash", () => {
router = new TestRouter(env, [], { mode: "hash" });
expect(router["routeToPath"]({ path: "/abc/{{def}}" } as Route, { def: 34 })).toBe("#/abc/34");
});
});
@@ -58,26 +98,60 @@ describe("destToPath", () => {
});
describe("getRouteParams", () => {
const getRouteParams = Router.prototype["getRouteParams"];
test("properly match simple routes", () => {
router = new TestRouter(env, []);
// simple route
expect(getRouteParams({path: "/home"} as Route, "/home")).toEqual({});
expect(router["getRouteParams"]({ path: "/home" } as Route, "/home")).toEqual({});
// no match
expect(getRouteParams({path: "/home"} as Route, "/otherpath")).toEqual(false);
expect(router["getRouteParams"]({ path: "/home" } as Route, "/otherpath")).toEqual(false);
// fallback route
expect(getRouteParams({path: "*"} as Route, "somepath")).toEqual({});
expect(router["getRouteParams"]({ path: "*" } as Route, "somepath")).toEqual({});
});
test("properly match simple routes, mode hash", () => {
router = new TestRouter(env, [], { mode: "hash" });
// simple route
expect(router["getRouteParams"]({ path: "/home" } as Route, "#/home")).toEqual({});
// no match
expect(router["getRouteParams"]({ path: "/home" } as Route, "#/otherpath")).toEqual(false);
// fallback route
expect(router["getRouteParams"]({ path: "*" } as Route, "#/somepath")).toEqual({});
});
test("match some parameterized routes", () => {
expect(getRouteParams({path: "/invoices/{{id}}"} as Route, "/invoices/3")).toEqual({
router = new TestRouter(env, []);
expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "/invoices/3")).toEqual({
id: "3"
});
});
test("match some parameterized routes, mode hash", () => {
router = new TestRouter(env, [], { mode: "hash" });
expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "#/invoices/3")).toEqual(
{
id: "3"
}
);
});
test("can convert to number if needed", () => {
expect(getRouteParams({path: "/invoices/{{id.number}}"} as Route, "/invoices/3")).toEqual({
router = new TestRouter(env, []);
expect(
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "/invoices/3")
).toEqual({
id: 3
});
});
test("can convert to number if needed, mode: hash", () => {
router = new TestRouter(env, [], { mode: "hash" });
expect(
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "#/invoices/3")
).toEqual({
id: 3
});
});
@@ -131,9 +205,9 @@ describe("beforeRouteEnter", () => {
expect(window.location.pathname).toBe("/");
const guard = jest.fn(() => false);
router = new TestRouter(env, [
{ name: "routea", path: "/some/patha"},
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard }
]);
{ name: "routea", path: "/some/patha" },
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard }
]);
await router.start();
await router.navigate({ to: "routea" });
@@ -147,12 +221,14 @@ describe("beforeRouteEnter", () => {
test("navigation is redirected if guard decides so", async () => {
expect(window.location.pathname).toBe("/");
const guard = jest.fn(() => {return {to: "routec"}});
const guard = jest.fn(() => {
return { to: "routec" };
});
router = new TestRouter(env, [
{ name: "routea", path: "/some/patha"},
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard },
{ name: "routec", path: "/some/pathc"},
]);
{ name: "routea", path: "/some/patha" },
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard },
{ name: "routec", path: "/some/pathc" }
]);
await router.start();
const result = await router.navigate({ to: "routea" });
@@ -166,11 +242,13 @@ describe("beforeRouteEnter", () => {
test("navigation is initially redirected if guard decides so", async () => {
expect(window.location.pathname).toBe("/");
const guard = jest.fn(() => {return {to: "otherroute"}});
const guard = jest.fn(() => {
return { to: "otherroute" };
});
router = new TestRouter(env, [
{ name: "landing", path: "/", beforeRouteEnter: guard},
{ name: "otherroute", path: "/some/other/route"}
]);
{ name: "landing", path: "/", beforeRouteEnter: guard },
{ name: "otherroute", path: "/some/other/route" }
]);
expect(window.location.pathname).toBe("/");
@@ -15,3 +15,7 @@ exports[`connecting a component to store deep and shallow connecting a component
exports[`connecting a component to store deep and shallow connecting a component 3`] = `"<div><span>Bertinchamps</span></div>"`;
exports[`connecting a component to store deep and shallow connecting a component 4`] = `"<div><span>Kasteel</span></div>"`;
exports[`various scenarios scenarios with async store updates and some components events 1`] = `"<div><button>Do stuff</button><div><span>Attachment 100</span><span>Name: text.txt</span></div></div>"`;
exports[`various scenarios scenarios with async store updates and some components events 2`] = `"<div><button>Do stuff</button></div>"`;
+87 -5
View File
@@ -616,8 +616,8 @@ describe("connecting a component to store", () => {
return { flag: s.flag, someId: s.someId };
}
async render(force) {
await def;
return super.render(force);
await def;
return super.render(force);
}
}
@@ -628,7 +628,7 @@ describe("connecting a component to store", () => {
}
}
const state = { someId: 1, flag: true, messages: {1: "abc"}};
const state = { someId: 1, flag: true, messages: { 1: "abc" } };
const actions = {
setFlagToFalse({ state }) {
state.flag = false;
@@ -1040,7 +1040,7 @@ describe("connected components and default values", () => {
super.off(eventType, owner);
}
}
const store = new TestStore({ state: {val: 1} });
const store = new TestStore({ state: { val: 1 } });
(<any>env).store = store;
const parent = new Parent(env);
@@ -1088,9 +1088,91 @@ describe("connected components and default values", () => {
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div>0</div>");
const res = app.dispatch('inc');
const res = app.dispatch("inc");
expect(res).toBe(1);
await nextTick();
expect(fixture.innerHTML).toBe("<div>1</div>");
});
});
describe("various scenarios", () => {
let fixture: HTMLElement;
let env: Env;
beforeEach(() => {
fixture = makeTestFixture();
env = makeTestEnv();
});
afterEach(() => {
fixture.remove();
});
test("scenarios with async store updates and some components events", async () => {
const actions = {
async deleteAttachment({ state }) {
await Promise.resolve();
delete state.attachments[100];
state.messages[10].attachmentIds = [];
}
};
const state = {
attachments: {
100: {
id: 100,
name: "text.txt"
}
},
messages: {
10: {
attachmentIds: [100],
id: 10
}
}
};
const store = new Store({ actions, state });
env.qweb.addTemplates(`
<templates>
<div t-name="Message">
<button t-on-click="doStuff">Do stuff</button>
<Attachment t-foreach="storeProps.attachmentIds" t-key="attachmentId" t-as="attachmentId" id="attachmentId"/>
</div>
<div t-name="Attachment">
<span>Attachment <t t-esc="props.id"/></span>
<span>Name: <t t-esc="storeProps.name"/></span>
</div>
</templates>
`);
class Attachment extends ConnectedComponent<any, any, any> {
static mapStoreToProps(state, ownProps) {
return {
name: state.attachments[ownProps.id].name
};
}
}
class Message extends ConnectedComponent<any, any, any> {
static mapStoreToProps(state) {
return {
attachmentIds: state.messages[10].attachmentIds
};
}
components = { Attachment };
state = { isAttachmentDeleted: false };
doStuff() {
this.dispatch("deleteAttachment", 100);
this.state.isAttachmentDeleted = true;
}
}
(<any>env).store = store;
const message = new Message(env);
await message.mount(fixture);
expect(fixture.innerHTML).toMatchSnapshot();
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toMatchSnapshot();
});
});
+24 -24
View File
@@ -249,32 +249,32 @@ describe("advanced state properties", () => {
expect(store.state.a).toEqual([1, 2, 3, 53]);
});
test("can use object assign in store", async () => {
const actions = {
dosomething({ state }) {
Object.assign(state.westmalle, { a: 3, b: 4 });
}
};
const store = new Store({
state: { westmalle: { a: 1, b: 2 } },
actions
});
store.dispatch("dosomething");
expect(store.state.westmalle).toEqual({ a: 3, b: 4 });
test("can use object assign in store", async () => {
const actions = {
dosomething({ state }) {
Object.assign(state.westmalle, { a: 3, b: 4 });
}
};
const store = new Store({
state: { westmalle: { a: 1, b: 2 } },
actions
});
store.dispatch("dosomething");
expect(store.state.westmalle).toEqual({ a: 3, b: 4 });
});
test("aku reactive store state 1", async () => {
const actions = {
inc({ state }) {
state.counter++;
}
};
const state = { counter: 0 };
const store = new Store({ state, actions });
expect(store.state.counter).toBe(0);
store.dispatch("inc", {});
expect(store.state.counter).toBe(1);
});
test("aku reactive store state 1", async () => {
const actions = {
inc({ state }) {
state.counter++;
}
};
const state = { counter: 0 };
const store = new Store({ state, actions });
expect(store.state.counter).toBe(0);
store.dispatch("inc", {});
expect(store.state.counter).toBe(1);
});
});
describe("updates triggered by the store", () => {