mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 211f6ebdd6 | |||
| 1707bd240d | |||
| 142b69823f | |||
| 94c8bce810 | |||
| ae172d42e7 | |||
| ddf30a8a97 | |||
| 8d0d8538ad | |||
| 2b0315c03f | |||
| c469cac315 | |||
| b4ad14edc0 | |||
| 718e5264ae | |||
| f5d019bb69 | |||
| afa36f52a0 |
@@ -121,8 +121,8 @@ npm install @odoo/owl
|
||||
|
||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||
|
||||
- [owl-1.0.5.js](https://github.com/odoo/owl/releases/download/v1.0.5/owl.js)
|
||||
- [owl-1.0.5.min.js](https://github.com/odoo/owl/releases/download/v1.0.5/owl.min.js)
|
||||
- [owl-1.0.6.js](https://github.com/odoo/owl/releases/download/v1.0.6/owl.js)
|
||||
- [owl-1.0.6.min.js](https://github.com/odoo/owl/releases/download/v1.0.6/owl.min.js)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
- [Setting an Environment](#setting-an-environment)
|
||||
- [Using a sub environment](#using-a-sub-environment)
|
||||
- [Content of an Environment](#content-of-an-environment)
|
||||
- [Special keys](#special-keys)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -93,7 +94,7 @@ Some good use cases for additional keys in the environment are:
|
||||
|
||||
- some configuration keys,
|
||||
- session information,
|
||||
- generic services (such as doing rpcs, or accessing local storage).
|
||||
- generic services (such as doing rpcs).
|
||||
|
||||
Doing it this way means that components are easily testable: we can simply
|
||||
create a test environment with mock services.
|
||||
@@ -125,3 +126,26 @@ async function start() {
|
||||
await app.mount(document.body);
|
||||
}
|
||||
```
|
||||
|
||||
## Special Keys
|
||||
|
||||
There are two special key/value added by Owl if not provided in the environment:
|
||||
the `QWeb` instance and a `browser` object:
|
||||
|
||||
- `qweb` will be set to an empty `QWeb` instance. This is absolutely necessary
|
||||
for Owl to be able to render anything
|
||||
- `browser`: this is an object that contains some common access points to the
|
||||
browser methods with a side effect. This is particularly useful when one want
|
||||
to test more advanced components, and be able to mock those methods.
|
||||
|
||||
More specifically, the `browser` object contains the following methods and objects:
|
||||
|
||||
- `setTimeout`
|
||||
- `clearTimeout`
|
||||
- `setInterval`
|
||||
- `clearInterval`
|
||||
- `requestAnimationFrame`
|
||||
- `random`
|
||||
- `Date`
|
||||
- `fetch`
|
||||
- `localStorage`
|
||||
|
||||
@@ -532,7 +532,7 @@ The javascript QWeb implementation provides two useful debugging directives:
|
||||
|
||||
```xml
|
||||
<t t-if="a_test">
|
||||
<t t-debug="">
|
||||
<t t-debug=""/>
|
||||
</t>
|
||||
```
|
||||
|
||||
|
||||
@@ -37,16 +37,16 @@ rendering context.
|
||||
</div>
|
||||
```
|
||||
|
||||
Slots are defined by the caller, with the `t-set` directive:
|
||||
Slots are defined by the caller, with the `t-set-slot` directive:
|
||||
|
||||
```xml
|
||||
<div t-name="SomeComponent">
|
||||
<div>some component</div>
|
||||
<Dialog title="Some Dialog">
|
||||
<t t-set="content">
|
||||
<t t-set-slot="content">
|
||||
<div>hey</div>
|
||||
</t>
|
||||
<t t-set="footer">
|
||||
<t t-set-slot="footer">
|
||||
<button t-on-click="doSomething">ok</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
@@ -57,6 +57,9 @@ In this example, the component `Dialog` will render the slots `content` and `foo
|
||||
with its parent as rendering context. This means that clicking on the button
|
||||
will execute the `doSomething` method on the parent, not on the dialog.
|
||||
|
||||
Note: Owl previously used the `t-set` directive to define the content of a slot.
|
||||
This is deprecated and should no longer be used in new code.
|
||||
|
||||
## Reference
|
||||
|
||||
Default slot: the first element inside the component which is not a named slot will
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.js",
|
||||
"types": "dist/types/index.d.ts",
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
# 🦉 OWL Roadmap 🦉
|
||||
|
||||
- Current version: 1.0.5
|
||||
- Current version: 1.0.6
|
||||
- Status: stable
|
||||
|
||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||
@@ -16,6 +16,8 @@ change!
|
||||
|
||||
### 2.x (2020? 2021? 2022?)
|
||||
|
||||
- stop support for `t-set` directive to define the content of a slot
|
||||
|
||||
Maybe:
|
||||
|
||||
- reimplement vdom to use *block* system, like Vue 3, which should make Owl
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface Browser {
|
||||
setTimeout: Window["setTimeout"];
|
||||
clearTimeout: Window["clearTimeout"];
|
||||
setInterval: Window["setInterval"];
|
||||
clearInterval: Window["clearInterval"];
|
||||
requestAnimationFrame: Window["requestAnimationFrame"];
|
||||
random: Math["random"];
|
||||
Date: typeof Date;
|
||||
fetch: Window["fetch"];
|
||||
localStorage: Window["localStorage"];
|
||||
}
|
||||
|
||||
export const browser: Browser = {
|
||||
setTimeout: window.setTimeout.bind(window),
|
||||
clearTimeout: window.clearTimeout.bind(window),
|
||||
setInterval: window.setInterval.bind(window),
|
||||
clearInterval: window.clearInterval.bind(window),
|
||||
requestAnimationFrame: window.requestAnimationFrame.bind(window),
|
||||
random: Math.random,
|
||||
Date: window.Date,
|
||||
fetch: (window.fetch || (() => {})).bind(window),
|
||||
localStorage: window.localStorage
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import { Fiber } from "./fiber";
|
||||
import "./props_validation";
|
||||
import { Scheduler, scheduler } from "./scheduler";
|
||||
import { activateSheet } from "./styles";
|
||||
import { Browser, browser } from "../browser";
|
||||
|
||||
/**
|
||||
* Owl Component System
|
||||
@@ -34,7 +35,7 @@ import { activateSheet } from "./styles";
|
||||
*/
|
||||
export interface Env {
|
||||
qweb: QWeb;
|
||||
[key: string]: any;
|
||||
browser: Browser;
|
||||
}
|
||||
|
||||
export type MountPosition = "first-child" | "last-child" | "self";
|
||||
@@ -158,6 +159,9 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
if (!this.env.qweb) {
|
||||
this.env.qweb = new QWeb();
|
||||
}
|
||||
if (!this.env.browser) {
|
||||
this.env.browser = browser;
|
||||
}
|
||||
this.env.qweb.on("update", this, () => {
|
||||
if (this.__owl__.isMounted) {
|
||||
this.render(true);
|
||||
@@ -357,14 +361,15 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
*/
|
||||
async render(force: boolean = false): Promise<void> {
|
||||
const __owl__ = this.__owl__;
|
||||
if (!__owl__.isMounted && !__owl__.currentFiber) {
|
||||
const currentFiber = __owl__.currentFiber;
|
||||
if (!__owl__.isMounted && !currentFiber) {
|
||||
// if we get here, this means that the component was either never mounted,
|
||||
// or was unmounted and some state change triggered a render. Either way,
|
||||
// we do not want to actually render anything in this case.
|
||||
return;
|
||||
}
|
||||
if (__owl__.currentFiber && !__owl__.currentFiber.isRendered) {
|
||||
return scheduler.addFiber(__owl__.currentFiber.root);
|
||||
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
|
||||
return scheduler.addFiber(currentFiber.root);
|
||||
}
|
||||
// if we aren't mounted at this point, it implies that there is a
|
||||
// currentFiber that is already rendered (isRendered is true), so we are
|
||||
@@ -603,9 +608,10 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
// key. So we fall back on looking for a template matching its name (or
|
||||
// one of its subclass).
|
||||
|
||||
let template: string;
|
||||
while ((template = p.name) && !(template in qweb.templates) && p !== Component) {
|
||||
let template: string = p.name;
|
||||
while (!(template in qweb.templates) && p !== Component) {
|
||||
p = p.__proto__;
|
||||
template = p.name;
|
||||
}
|
||||
if (p === Component) {
|
||||
throw new Error(`Could not find template for component "${this.constructor.name}"`);
|
||||
|
||||
@@ -400,15 +400,36 @@ QWeb.addDirective({
|
||||
|
||||
if (hasSlots) {
|
||||
const clone = <Element>node.cloneNode(true);
|
||||
const slotNodes = clone.querySelectorAll("[t-set]");
|
||||
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
|
||||
|
||||
// The next code is a fallback for compatibility reason. It accepts t-set
|
||||
// elements that are direct children with a non empty body as nodes defining
|
||||
// the content of a slot.
|
||||
//
|
||||
// This is wrong, but is necessary to prevent breaking all existing Owl
|
||||
// code using slots. This will be removed in v2.0 someday. Meanwhile,
|
||||
// please use t-set-slot everywhere you need to set the content of a
|
||||
// slot.
|
||||
for (let el of clone.children) {
|
||||
if (el.getAttribute("t-set") && el.hasChildNodes()) {
|
||||
slotNodes.push(el);
|
||||
}
|
||||
}
|
||||
const slotId = QWeb.nextSlotId++;
|
||||
ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`);
|
||||
if (slotNodes.length) {
|
||||
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
||||
const slotNode = slotNodes[i];
|
||||
slotNode.parentElement!.removeChild(slotNode);
|
||||
const key = slotNode.getAttribute("t-set")!;
|
||||
slotNode.removeAttribute("t-set");
|
||||
let key = slotNode.getAttribute("t-set-slot")!;
|
||||
slotNode.removeAttribute("t-set-slot");
|
||||
|
||||
// here again, this code should be removed when we stop supporting
|
||||
// using t-set to define the content of named slots.
|
||||
if (!key) {
|
||||
key = slotNode.getAttribute("t-set")!;
|
||||
slotNode.removeAttribute("t-set");
|
||||
}
|
||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
||||
QWeb.slots[`${slotId}_${key}`] = slotFn;
|
||||
}
|
||||
|
||||
@@ -225,6 +225,12 @@ export class Fiber {
|
||||
`Cannot attach '${component.constructor.name}' to target node (not same tag name)`
|
||||
);
|
||||
}
|
||||
// In self mode, we *know* we are to take possession of the target
|
||||
// Hence we manually create the corresponding VNode and copy the "key" in data
|
||||
const selfVnodeData = fiber.vnode!.data ? { key: fiber.vnode!.data.key } : {};
|
||||
const selfVnode = h(fiber.vnode!.sel, selfVnodeData);
|
||||
selfVnode.elm = target;
|
||||
target = selfVnode;
|
||||
} else {
|
||||
target = component.__owl__.vnode || document.createElement(fiber.vnode!.sel!);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Fiber } from "./fiber";
|
||||
import { browser } from "../browser";
|
||||
|
||||
/**
|
||||
* Owl Scheduler Class
|
||||
@@ -19,9 +20,9 @@ interface Task {
|
||||
export class Scheduler {
|
||||
tasks: Task[] = [];
|
||||
isRunning: boolean = false;
|
||||
requestAnimationFrame: typeof window.requestAnimationFrame;
|
||||
requestAnimationFrame: Window["requestAnimationFrame"];
|
||||
|
||||
constructor(requestAnimationFrame) {
|
||||
constructor(requestAnimationFrame: Window["requestAnimationFrame"]) {
|
||||
this.requestAnimationFrame = requestAnimationFrame;
|
||||
}
|
||||
|
||||
@@ -109,5 +110,4 @@ export class Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
const raf = window.requestAnimationFrame.bind(window);
|
||||
export const scheduler = new Scheduler(raf);
|
||||
export const scheduler = new Scheduler(browser.requestAnimationFrame);
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ function makeAsyncHook(method: string) {
|
||||
if (component.__owl__[method]) {
|
||||
const current = component.__owl__[method];
|
||||
component.__owl__[method] = function(...args) {
|
||||
return Promise.all[(current.call(component, ...args), cb.call(component, ...args))];
|
||||
return Promise.all([current.call(component, ...args), cb.call(component, ...args)]);
|
||||
};
|
||||
} else {
|
||||
component.__owl__[method] = cb;
|
||||
|
||||
@@ -196,11 +196,12 @@ export class CompilationContext {
|
||||
const protectID = this.generateID();
|
||||
this.rootContext.protectedScopeNumber++;
|
||||
this.rootContext.shouldDefineScope = true;
|
||||
const scopeExpr = codeBlock
|
||||
? `Object.create(scope);`
|
||||
: `Object.assign(Object.create(context), scope);`;
|
||||
const scopeExpr = `Object.create(scope);`;
|
||||
this.addLine(`let _origScope${protectID} = scope;`);
|
||||
this.addLine(`scope = ${scopeExpr}`);
|
||||
if (!codeBlock) {
|
||||
this.addLine(`scope.__access_mode__ = 'ro';`);
|
||||
}
|
||||
return protectID;
|
||||
}
|
||||
stopProtectScope(protectID: number) {
|
||||
|
||||
+5
-1
@@ -133,7 +133,11 @@ const UTILS: Utils = {
|
||||
},
|
||||
getScope(obj, property: string) {
|
||||
const obj0 = obj;
|
||||
while (obj && !obj.hasOwnProperty(property)) {
|
||||
while (
|
||||
obj &&
|
||||
!obj.hasOwnProperty(property) &&
|
||||
!(obj.hasOwnProperty("__access_mode__") && obj.__access_mode__ === "ro")
|
||||
) {
|
||||
const newObj = obj.__proto__;
|
||||
if (!newObj || isComponent(newObj)) {
|
||||
return obj0;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component } from "../component/component";
|
||||
import { xml } from "../tags";
|
||||
import { EnvWithRouter } from "./router";
|
||||
|
||||
export class RouteComponent extends Component {
|
||||
export class RouteComponent extends Component<{}, EnvWithRouter> {
|
||||
static template = xml`
|
||||
<t>
|
||||
<t
|
||||
|
||||
@@ -49,6 +49,10 @@ interface Options {
|
||||
mode: Router["mode"];
|
||||
}
|
||||
|
||||
export interface EnvWithRouter extends Env {
|
||||
router: Router;
|
||||
}
|
||||
|
||||
const paramRegexp = /\{\{(.*?)\}\}/;
|
||||
|
||||
export class Router {
|
||||
@@ -60,7 +64,11 @@ export class Router {
|
||||
routeIds: string[];
|
||||
env: RouterEnv;
|
||||
|
||||
constructor(env: Env, routes: Partial<Route>[], options: Options = { mode: "history" }) {
|
||||
constructor(
|
||||
env: Partial<EnvWithRouter>,
|
||||
routes: Partial<Route>[],
|
||||
options: Options = { mode: "history" }
|
||||
) {
|
||||
env.router = this;
|
||||
this.mode = options.mode;
|
||||
this.env = env as RouterEnv;
|
||||
|
||||
+7
-3
@@ -24,6 +24,10 @@ import { onWillUpdateProps } from "./hooks";
|
||||
// Store Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
export interface EnvWithStore extends Env {
|
||||
store: Store;
|
||||
}
|
||||
|
||||
export type Action = ({ state, dispatch, env, getters }, ...payload: any) => any;
|
||||
export type Getter = ({ state: any, getters }, payload?) => any;
|
||||
|
||||
@@ -83,7 +87,7 @@ interface SelectorOptions {
|
||||
const isStrictEqual = (a, b) => a === b;
|
||||
|
||||
export function useStore(selector, options: SelectorOptions = {}): any {
|
||||
const component: Component = Component.current!;
|
||||
const component = Component.current as Component<any, EnvWithStore>;
|
||||
const componentId = component.__owl__.id;
|
||||
const store = options.store || (component.env.store as Store);
|
||||
if (!(store instanceof Store)) {
|
||||
@@ -149,11 +153,11 @@ export function useStore(selector, options: SelectorOptions = {}): any {
|
||||
}
|
||||
|
||||
export function useDispatch(store?: Store): Store["dispatch"] {
|
||||
store = store || (Component.current!.env.store as Store);
|
||||
store = store || (Component.current!.env as EnvWithStore).store;
|
||||
return store.dispatch.bind(store);
|
||||
}
|
||||
|
||||
export function useGetters(store?: Store): Store["getters"] {
|
||||
store = store || (Component.current!.env.store as Store);
|
||||
store = store || (Component.current!.env as EnvWithStore).store;
|
||||
return store.getters;
|
||||
}
|
||||
|
||||
+5
-3
@@ -10,6 +10,8 @@
|
||||
* - debounce
|
||||
*/
|
||||
|
||||
import { browser } from "./browser";
|
||||
|
||||
export function whenReady(fn?: any) {
|
||||
return new Promise(function(resolve) {
|
||||
if (document.readyState !== "loading") {
|
||||
@@ -44,7 +46,7 @@ export function loadJS(url: string): Promise<void> {
|
||||
}
|
||||
|
||||
export async function loadFile(url: string): Promise<string> {
|
||||
const result = await fetch(url);
|
||||
const result = await browser.fetch(url);
|
||||
if (!result.ok) {
|
||||
throw new Error("Error while fetching xml templates");
|
||||
}
|
||||
@@ -83,8 +85,8 @@ export function debounce(func: Function, wait: number, immediate?: boolean): Fun
|
||||
}
|
||||
}
|
||||
const callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
browser.clearTimeout(timeout);
|
||||
timeout = browser.setTimeout(later, wait);
|
||||
if (callNow) {
|
||||
func.apply(context, args);
|
||||
}
|
||||
|
||||
@@ -1282,7 +1282,8 @@ exports[`other directives with t-component t-set can't alter in t-call body 1`]
|
||||
}
|
||||
{
|
||||
let _origScope4 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'iter').iter = 'inCall';
|
||||
|
||||
@@ -90,6 +90,96 @@ exports[`t-slot directive can define and call slots 4`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-slot directive can define and call slots using old t-set keyword 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"__template__2\\"
|
||||
let utils = this.constructor.utils;
|
||||
let QWeb = this.constructor;
|
||||
let parent = context;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
// Component 'Dialog'
|
||||
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
||||
let props2 = {};
|
||||
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||
w2.destroy();
|
||||
w2 = false;
|
||||
}
|
||||
if (w2) {
|
||||
w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope));
|
||||
let pvnode = w2.__owl__.pvnode;
|
||||
c1.push(pvnode);
|
||||
} else {
|
||||
let componentKey2 = \`Dialog\`;
|
||||
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Dialog'];
|
||||
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||
w2 = new W2(parent, props2);
|
||||
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
||||
w2.__owl__.slotId = 1;
|
||||
let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||
c1.push(pvnode);
|
||||
w2.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-slot directive can define and call slots using old t-set keyword 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"__template__1\\"
|
||||
let h = this.h;
|
||||
let c6 = [], p6 = {key:6};
|
||||
let vn6 = h('div', p6, c6);
|
||||
let c7 = [], p7 = {key:7};
|
||||
let vn7 = h('div', p7, c7);
|
||||
c6.push(vn7);
|
||||
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
||||
if (slot8) {
|
||||
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c7, parent: extra.parent || context}));
|
||||
}
|
||||
let c9 = [], p9 = {key:9};
|
||||
let vn9 = h('div', p9, c9);
|
||||
c6.push(vn9);
|
||||
const slot10 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||
if (slot10) {
|
||||
slot10.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
|
||||
}
|
||||
return vn6;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-slot directive can define and call slots using old t-set keyword 3`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"slot_header_template\\"
|
||||
let h = this.h;
|
||||
let c1 = extra.parentNode;
|
||||
let c4 = [], p4 = {key:4};
|
||||
let vn4 = h('span', p4, c4);
|
||||
c1.push(vn4);
|
||||
c4.push({text: \`header\`});
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-slot directive can define and call slots using old t-set keyword 4`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"slot_footer_template\\"
|
||||
let h = this.h;
|
||||
let c1 = extra.parentNode;
|
||||
let c5 = [], p5 = {key:5};
|
||||
let vn5 = h('span', p5, c5);
|
||||
c1.push(vn5);
|
||||
c5.push({text: \`footer\`});
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-slot directive content is the default slot 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
@@ -469,6 +559,21 @@ exports[`t-slot directive slots are rendered with proper context, part 4 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-slot directive t-set t-value in a slot 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"__template__1\\"
|
||||
let h = this.h;
|
||||
let c4 = [], p4 = {key:4};
|
||||
let vn4 = h('span', p4, c4);
|
||||
const slot5 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
if (slot5) {
|
||||
slot5.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c4, parent: extra.parent || context}));
|
||||
}
|
||||
return vn4;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-slot directive template can just return a slot 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
|
||||
@@ -1366,6 +1366,28 @@ describe("async rendering", () => {
|
||||
await prom;
|
||||
});
|
||||
|
||||
test("concurrent renderings scenario 17", async () => {
|
||||
class Parent extends Component {
|
||||
static template = xml`<span><t t-esc="state.value"/></span>`;
|
||||
state = useState({ value: 1 });
|
||||
}
|
||||
|
||||
const parent = new Parent();
|
||||
await parent.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<span>1</span>");
|
||||
|
||||
parent.state.value = 2;
|
||||
parent.__owl__.currentFiber!.cancel();
|
||||
|
||||
parent.state.value = 3; // update value directly
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<span>3</span>");
|
||||
|
||||
parent.state.value = 4; // update value after a tick
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<span>4</span>");
|
||||
});
|
||||
|
||||
test("change state and call manually render: no unnecessary rendering", async () => {
|
||||
class Widget extends Component {
|
||||
static template = xml`<div><t t-esc="state.val"/></div>`;
|
||||
|
||||
@@ -3080,6 +3080,15 @@ describe("can deduce template from name", () => {
|
||||
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
||||
});
|
||||
|
||||
test("can find template of anonymous component", async () => {
|
||||
class ABC extends Component {}
|
||||
const Anonymous = class extends ABC {};
|
||||
env.qweb.addTemplate("ABC", "<span>Orval</span>");
|
||||
const def = new Anonymous();
|
||||
await def.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
||||
});
|
||||
|
||||
test("can find template of parent component, defined by template key", async () => {
|
||||
class ABC extends Component {
|
||||
static template = "Achel";
|
||||
@@ -3475,11 +3484,14 @@ describe("environment and plugins", () => {
|
||||
});
|
||||
|
||||
test("can define specific env for root components", async () => {
|
||||
class App1 extends Component {
|
||||
interface AppEnv extends Env {
|
||||
test: number;
|
||||
}
|
||||
class App1 extends Component<{}, AppEnv> {
|
||||
static template = xml`<span></span>`;
|
||||
}
|
||||
App1.env = { test: 1 };
|
||||
class App2 extends Component {
|
||||
class App2 extends Component<{}, AppEnv> {
|
||||
static template = xml`<span></span>`;
|
||||
}
|
||||
App2.env = { test: 2 };
|
||||
|
||||
@@ -41,8 +41,8 @@ describe("t-slot directive", () => {
|
||||
<templates>
|
||||
<div t-name="Parent">
|
||||
<Dialog>
|
||||
<t t-set="header"><span>header</span></t>
|
||||
<t t-set="footer"><span>footer</span></t>
|
||||
<t t-set-slot="header"><span>header</span></t>
|
||||
<t t-set-slot="footer"><span>footer</span></t>
|
||||
</Dialog>
|
||||
</div>
|
||||
<div t-name="Dialog">
|
||||
@@ -67,6 +67,38 @@ describe("t-slot directive", () => {
|
||||
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("can define and call slots using old t-set keyword", async () => {
|
||||
// NOTE: this test should be removed once we stop supporting the t-set directive
|
||||
// for defining slot content.
|
||||
class Dialog extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<div><t t-slot="header"/></div>
|
||||
<div><t t-slot="footer"/></div>
|
||||
</div>`;
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Dialog>
|
||||
<t t-set="header"><span>header</span></t>
|
||||
<t t-set="footer"><span>footer</span></t>
|
||||
</Dialog>
|
||||
</div>`;
|
||||
static components = { Dialog };
|
||||
}
|
||||
const parent = new Parent();
|
||||
await parent.mount(fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><div><div><span>header</span></div><div><span>footer</span></div></div></div>"
|
||||
);
|
||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_header"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("named slots can define a default content", async () => {
|
||||
class Dialog extends Component {
|
||||
static template = xml`
|
||||
@@ -128,7 +160,7 @@ describe("t-slot directive", () => {
|
||||
</span>`;
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`<div><Dialog><t t-set="header">hey</t></Dialog></div>`;
|
||||
static template = xml`<div><Dialog><t t-set-slot="header">hey</t></Dialog></div>`;
|
||||
static components = { Dialog };
|
||||
}
|
||||
const parent = new Parent();
|
||||
@@ -143,7 +175,7 @@ describe("t-slot directive", () => {
|
||||
<div t-name="Parent">
|
||||
<span class="counter"><t t-esc="state.val"/></span>
|
||||
<Dialog>
|
||||
<t t-set="footer"><button t-on-click="doSomething">do something</button></t>
|
||||
<t t-set-slot="footer"><button t-on-click="doSomething">do something</button></t>
|
||||
</Dialog>
|
||||
</div>
|
||||
<span t-name="Dialog"><t t-slot="footer"/></span>
|
||||
@@ -302,7 +334,7 @@ describe("t-slot directive", () => {
|
||||
<div>
|
||||
<span class="counter"><t t-esc="state.val"/></span>
|
||||
<Dialog>
|
||||
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
|
||||
<t t-set-slot="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
|
||||
</Dialog>
|
||||
</div>
|
||||
`;
|
||||
@@ -376,7 +408,7 @@ describe("t-slot directive", () => {
|
||||
<templates>
|
||||
<div t-name="Parent">
|
||||
<Dialog>
|
||||
<t t-set="content">
|
||||
<t t-set-slot="content">
|
||||
<span>sts</span>
|
||||
<span>rocks</span>
|
||||
</t>
|
||||
@@ -442,14 +474,14 @@ describe("t-slot directive", () => {
|
||||
expect(fixture.innerHTML).toBe("<div><span><span>some content</span></span></div>");
|
||||
});
|
||||
|
||||
test("t-debug on a t-set (defining a slot)", async () => {
|
||||
test("t-debug on a t-set-slot (defining a slot)", async () => {
|
||||
const consoleLog = console.log;
|
||||
console.log = jest.fn();
|
||||
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="Parent">
|
||||
<Dialog><t t-set="content" t-debug="">abc</t></Dialog>
|
||||
<Dialog><t t-set-slot="content" t-debug="">abc</t></Dialog>
|
||||
</div>
|
||||
<span t-name="Dialog">
|
||||
<t t-slot="content"/>
|
||||
@@ -623,8 +655,8 @@ describe("t-slot directive", () => {
|
||||
class A extends Component {
|
||||
static template = xml`
|
||||
<B>
|
||||
<t t-set="s1"><C val="1"/></t>
|
||||
<t t-set="s2"><C val="2"/></t>
|
||||
<t t-set-slot="s1"><C val="1"/></t>
|
||||
<t t-set-slot="s2"><C val="2"/></t>
|
||||
</B>`;
|
||||
static components = { B, C };
|
||||
}
|
||||
@@ -873,4 +905,28 @@ describe("t-slot directive", () => {
|
||||
await parent.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><div><span><p>heyaaron</p></span></div></div>");
|
||||
});
|
||||
|
||||
test("t-set t-value in a slot", async () => {
|
||||
class Dialog extends Component {
|
||||
static template = xml`
|
||||
<span>
|
||||
<t t-slot="default"/>
|
||||
</span>`;
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Dialog>
|
||||
<t t-set="rainbow" t-value="'dash'"/>
|
||||
<t t-esc="rainbow"/>
|
||||
</Dialog>
|
||||
</div>`;
|
||||
static components = { Dialog };
|
||||
}
|
||||
const parent = new Parent();
|
||||
await parent.mount(fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><span>dash</span></div>");
|
||||
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,14 +28,34 @@ afterEach(() => {
|
||||
describe("mount targets", () => {
|
||||
test("can attach a component to an existing node (if same tagname)", async () => {
|
||||
class App extends Component {
|
||||
static template = xml`<div>app</div>`;
|
||||
static template = xml`<div t-att-class="state.customClass">app<p>another tag</p></div>`;
|
||||
state = useState({ customClass: "custom" });
|
||||
}
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("arbitrary");
|
||||
div.innerHTML = `<p>pre-existing</p>`;
|
||||
fixture.appendChild(div);
|
||||
|
||||
const app = new App();
|
||||
await app.mount(div, { position: "self" });
|
||||
expect(fixture.innerHTML).toBe("<div>app</div>");
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
`<div class="arbitrary custom"><p>pre-existing</p>app<p>another tag</p></div>`
|
||||
);
|
||||
expect(div).toBe(app.el);
|
||||
|
||||
app.state.customClass = "custom2";
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe(
|
||||
`<div class="arbitrary custom2"><p>pre-existing</p>app<p>another tag</p></div>`
|
||||
);
|
||||
expect(div).toBe(app.el);
|
||||
|
||||
app.unmount();
|
||||
// This assert is a best guess
|
||||
// The use case it covers was not really thought through
|
||||
// and may change in the future
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
});
|
||||
|
||||
test("cannot attach a component to an existing node (if not same tagname)", async () => {
|
||||
|
||||
+3
-1
@@ -6,6 +6,7 @@ import { patch } from "../src/vdom";
|
||||
import "../src/qweb/base_directives";
|
||||
import "../src/qweb/extensions";
|
||||
import "../src/component/directive";
|
||||
import { browser } from "../src/browser";
|
||||
|
||||
// modifies scheduler to make it faster to test components
|
||||
scheduler.requestAnimationFrame = function(callback: FrameRequestCallback) {
|
||||
@@ -75,7 +76,8 @@ export function makeDeferred(): Deferred {
|
||||
|
||||
export function makeTestEnv(): Env {
|
||||
return {
|
||||
qweb: new QWeb()
|
||||
qweb: new QWeb(),
|
||||
browser: browser
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+37
-4
@@ -559,13 +559,31 @@ describe("hooks", () => {
|
||||
|
||||
test("can use onWillStart, onWillUpdateProps", async () => {
|
||||
const steps: string[] = [];
|
||||
async function slow(): Promise<string> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve("slow");
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
function useMyHook() {
|
||||
onWillStart(() => {
|
||||
onWillStart(async () => {
|
||||
steps.push(await slow());
|
||||
steps.push("onWillStart");
|
||||
});
|
||||
onWillUpdateProps(async nextProps => {
|
||||
expect(nextProps).toEqual({ value: 2 });
|
||||
steps.push(await slow());
|
||||
steps.push("onWillUpdateProps");
|
||||
});
|
||||
}
|
||||
function use2ndHook() {
|
||||
onWillStart(() => {
|
||||
steps.push("on2ndStart");
|
||||
});
|
||||
onWillUpdateProps(nextProps => {
|
||||
expect(nextProps).toEqual({ value: 2 });
|
||||
steps.push("onWillUpdateProps");
|
||||
steps.push("on2ndUpdate");
|
||||
});
|
||||
}
|
||||
class MyComponent extends Component {
|
||||
@@ -573,6 +591,7 @@ describe("hooks", () => {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
useMyHook();
|
||||
use2ndHook();
|
||||
}
|
||||
}
|
||||
class App extends Component {
|
||||
@@ -586,12 +605,26 @@ describe("hooks", () => {
|
||||
expect(app).not.toHaveProperty("willStart");
|
||||
expect(app).not.toHaveProperty("willUpdateProps");
|
||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
||||
expect(steps).toEqual(["onWillStart"]);
|
||||
|
||||
// NOTE: 'on2ndStart' appears first in the list even though
|
||||
// the 'use2ndHook' is declared after 'useMyHook'. This is
|
||||
// because Promise.all is used to call the callbacks specified
|
||||
// in the hooks, which runs them simultaneously.
|
||||
// Additionally, 'slow' should be listed before 'onWillStart'
|
||||
// because call to `slow` is awaited.
|
||||
expect(steps).toEqual(["on2ndStart", "slow", "onWillStart"]);
|
||||
|
||||
app.state.value = 2;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>2</span></div>");
|
||||
expect(steps).toEqual(["onWillStart", "onWillUpdateProps"]);
|
||||
expect(steps).toEqual([
|
||||
"on2ndStart",
|
||||
"slow",
|
||||
"onWillStart",
|
||||
"on2ndUpdate",
|
||||
"slow",
|
||||
"onWillUpdateProps"
|
||||
]);
|
||||
});
|
||||
|
||||
test("useExternalListener", async () => {
|
||||
|
||||
@@ -774,12 +774,14 @@ exports[`misc global 1`] = `
|
||||
}
|
||||
{
|
||||
let _origScope10 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
{
|
||||
let _origScope13 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'foo').foo = 'aaa';
|
||||
@@ -1150,7 +1152,8 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] =
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
let c4 = [], p4 = {key:4};
|
||||
@@ -1198,7 +1201,8 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope12 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
let c13 = [], p13 = {key:13};
|
||||
@@ -1288,7 +1292,8 @@ exports[`t-call (template calling recursive template, part 2 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope11 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['root'];
|
||||
@@ -1339,7 +1344,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
|
||||
let key1 = i1;
|
||||
{
|
||||
let _origScope9 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['subtree'];
|
||||
@@ -1365,7 +1371,8 @@ exports[`t-call (template calling recursive template, part 3 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope11 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['root'];
|
||||
@@ -1416,7 +1423,93 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
|
||||
let key1 = i1;
|
||||
{
|
||||
let _origScope9 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['subtree'];
|
||||
scope[utils.zero] = c__0;
|
||||
}
|
||||
let k10 = \`__10__\${key0}__\${key1}__\`;
|
||||
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
|
||||
scope = _origScope9;
|
||||
}
|
||||
}
|
||||
scope = _origScope8;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling recursive template, part 4: with t-set recursive index 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Parent\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope11 = scope;
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'recursive_idx').recursive_idx = 1;
|
||||
utils.getScope(scope, 'node').node = scope['root'];
|
||||
scope[utils.zero] = c__0;
|
||||
}
|
||||
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
|
||||
scope = _origScope11;
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling recursive template, part 4: with t-set recursive index 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"nodeTemplate\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = extra.parentNode;
|
||||
let key0 = extra.key || \\"\\";
|
||||
let c2 = [], p2 = {key:\`\${key0}_2\`};
|
||||
let vn2 = h('div', p2, c2);
|
||||
c1.push(vn2);
|
||||
scope.recursive_idx = scope.recursive_idx+1;
|
||||
let c3 = [], p3 = {key:\`\${key0}_3\`};
|
||||
let vn3 = h('p', p3, c3);
|
||||
c2.push(vn3);
|
||||
let _4 = scope['node'].val;
|
||||
if (_4 != null) {
|
||||
c3.push({text: _4});
|
||||
}
|
||||
c3.push({text: \` \`});
|
||||
if (scope.recursive_idx != null) {
|
||||
c3.push({text: scope.recursive_idx});
|
||||
}
|
||||
let _5 = scope['node'].children||[];
|
||||
if (!_5) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _6 = _7 = _5;
|
||||
if (!(_5 instanceof Array)) {
|
||||
_6 = Object.keys(_5);
|
||||
_7 = Object.values(_5);
|
||||
}
|
||||
let _length6 = _6.length;
|
||||
let _origScope8 = scope;
|
||||
scope = Object.create(scope);
|
||||
for (let i1 = 0; i1 < _length6; i1++) {
|
||||
scope.subtree_first = i1 === 0
|
||||
scope.subtree_last = i1 === _length6 - 1
|
||||
scope.subtree_index = i1
|
||||
scope.subtree = _6[i1]
|
||||
scope.subtree_value = _7[i1]
|
||||
let key1 = i1;
|
||||
{
|
||||
let _origScope9 = scope;
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['subtree'];
|
||||
@@ -1442,7 +1535,8 @@ exports[`t-call (template calling scoped parameters 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope2 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'foo').foo = 42;
|
||||
@@ -1517,7 +1611,8 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
|
||||
utils.getScope(scope, 'val').val = scope['v'].val;
|
||||
{
|
||||
let _origScope8 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'val3').val3 = scope.val*3;
|
||||
@@ -1564,7 +1659,8 @@ exports[`t-call (template calling t-call, conditional and t-set in t-call body 1
|
||||
else if (scope.v1==='elif') {
|
||||
{
|
||||
let _origScope6 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'v').v = 'success';
|
||||
@@ -1602,7 +1698,8 @@ exports[`t-call (template calling with unused body 1`] = `
|
||||
let h = this.h;
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
c__0.push({text: \`WHEEE\`});
|
||||
@@ -1627,7 +1724,8 @@ exports[`t-call (template calling with unused setbody 1`] = `
|
||||
let h = this.h;
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'qux').qux = 3;
|
||||
@@ -1652,7 +1750,8 @@ exports[`t-call (template calling with used body 1`] = `
|
||||
let h = this.h;
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
c__0.push({text: \`ok\`});
|
||||
@@ -1678,7 +1777,8 @@ exports[`t-call (template calling with used set body 1`] = `
|
||||
let vn1 = h('span', p1, c1);
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'foo').foo = 'ok';
|
||||
@@ -1839,7 +1939,8 @@ exports[`t-esc t-esc is escaped 1`] = `
|
||||
scope.var = c2
|
||||
if (scope.var != null) {
|
||||
let _origScope4 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.var = scope.var instanceof utils.VDomArray ? utils.vDomToString(scope.var) : scope.var;
|
||||
c1.push({text: scope.var});
|
||||
scope = _origScope4;
|
||||
@@ -1875,7 +1976,8 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
let c4 = [], p4 = {key:4};
|
||||
@@ -2948,7 +3050,8 @@ exports[`t-set set from body literal 1`] = `
|
||||
scope.value = c1
|
||||
if (scope.value != null) {
|
||||
let _origScope2 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value;
|
||||
let vn3 = {text: scope.value};
|
||||
result = vn3
|
||||
@@ -2975,7 +3078,8 @@ exports[`t-set set from body lookup 1`] = `
|
||||
scope.stuff = c2
|
||||
if (scope.stuff != null) {
|
||||
let _origScope4 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.stuff = scope.stuff instanceof utils.VDomArray ? utils.vDomToString(scope.stuff) : scope.stuff;
|
||||
c1.push({text: scope.stuff});
|
||||
scope = _origScope4;
|
||||
@@ -3123,7 +3227,8 @@ exports[`t-set t-set with content and sub t-esc 1`] = `
|
||||
scope.setvar = c2
|
||||
if (scope.setvar != null) {
|
||||
let _origScope4 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.setvar = scope.setvar instanceof utils.VDomArray ? utils.vDomToString(scope.setvar) : scope.setvar;
|
||||
c1.push({text: scope.setvar});
|
||||
scope = _origScope4;
|
||||
@@ -3215,7 +3320,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = `
|
||||
}
|
||||
if (scope.ourvar != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
|
||||
c1.push({text: scope.ourvar});
|
||||
scope = _origScope3;
|
||||
@@ -3243,7 +3349,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 2`] = `
|
||||
}
|
||||
if (scope.ourvar != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
|
||||
c1.push({text: scope.ourvar});
|
||||
scope = _origScope3;
|
||||
@@ -3271,7 +3378,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = `
|
||||
}
|
||||
if (scope.ourvar != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
|
||||
c1.push({text: scope.ourvar});
|
||||
scope = _origScope3;
|
||||
@@ -3299,7 +3407,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 2`] = `
|
||||
}
|
||||
if (scope.ourvar != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
|
||||
c1.push({text: scope.ourvar});
|
||||
scope = _origScope3;
|
||||
@@ -3325,7 +3434,8 @@ exports[`t-set value priority 1`] = `
|
||||
}
|
||||
if (scope.value != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value;
|
||||
c1.push({text: scope.value});
|
||||
scope = _origScope3;
|
||||
|
||||
@@ -944,6 +944,38 @@ describe("t-call (template calling", () => {
|
||||
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("recursive template, part 4: with t-set recursive index", () => {
|
||||
qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="Parent">
|
||||
<t t-call="nodeTemplate">
|
||||
<t t-set="recursive_idx" t-value="1"/>
|
||||
<t t-set="node" t-value="root"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-name="nodeTemplate">
|
||||
<t t-set="recursive_idx" t-value="recursive_idx + 1"/>
|
||||
<p><t t-esc="node.val"/> <t t-esc="recursive_idx"/></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: "c", children: [{ val: "d" }] }] }]
|
||||
};
|
||||
const expected =
|
||||
"<div><div><p>a 2</p><div><p>b 3</p><div><p>c 4</p><div><p>d 5</p></div></div></div></div></div>";
|
||||
expect(renderToString(qweb, "Parent", { root })).toBe(expected);
|
||||
const recursiveFn = Object.values(qweb.subTemplates)[0] as any;
|
||||
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("t-call, global templates", () => {
|
||||
QWeb.registerTemplate("abcd", '<div><t t-call="john"/></div>');
|
||||
qweb.addTemplate("john", `<span>desk</span>`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, Env } from "../src/component/component";
|
||||
import { Store, useStore, useDispatch, useGetters } from "../src/store";
|
||||
import { Store, useStore, useDispatch, useGetters, EnvWithStore } from "../src/store";
|
||||
import { useState } from "../src/hooks";
|
||||
import { xml } from "../src/tags";
|
||||
import { shallowEqual } from "../src/utils";
|
||||
@@ -885,7 +885,7 @@ describe("connecting a component to store", () => {
|
||||
actions
|
||||
});
|
||||
|
||||
class TodoItem extends Component {
|
||||
class TodoItem extends Component<{}, EnvWithStore> {
|
||||
static template = xml`
|
||||
<div class="todo">
|
||||
<t t-esc="state.todo.title"/>
|
||||
@@ -958,7 +958,7 @@ describe("connecting a component to store", () => {
|
||||
actions
|
||||
});
|
||||
|
||||
class TodoItem extends Component {
|
||||
class TodoItem extends Component<{}, EnvWithStore> {
|
||||
static template = xml`
|
||||
<div class="todo">
|
||||
<t t-esc="state.todo.title"/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { escape, debounce } from "../src/utils";
|
||||
import { browser } from "../src/browser";
|
||||
|
||||
describe("escape", () => {
|
||||
test("normal strings", () => {
|
||||
@@ -15,6 +16,10 @@ describe("escape", () => {
|
||||
describe("debounce", () => {
|
||||
test("works as expected", () => {
|
||||
jest.useFakeTimers();
|
||||
// need to reset them on browser because they were mocked in window, but not
|
||||
// on browser object
|
||||
browser.setTimeout = window.setTimeout.bind(window);
|
||||
browser.clearTimeout = window.clearTimeout.bind(window);
|
||||
let n = 0;
|
||||
let f = debounce(() => n++, 100);
|
||||
expect(n).toBe(0);
|
||||
|
||||
@@ -1169,7 +1169,7 @@ const RESPONSIVE_CSS = `body {
|
||||
const SLOTS = `// We show here how slots can be used to create generic components.
|
||||
// In this example, the Card component is basically only a container. It is not
|
||||
// aware of its content. It just knows where it should be (with t-slot).
|
||||
// The parent component define the content with t-set.
|
||||
// The parent component define the content with t-set-slot.
|
||||
//
|
||||
// Note that the t-on-click event, defined in the App template, is executed in
|
||||
// the context of the App component, even though it is inside the Card component
|
||||
@@ -1235,15 +1235,15 @@ const SLOTS_XML = `<templates>
|
||||
|
||||
<div t-name="App" class="main">
|
||||
<Card title="'Title card A'">
|
||||
<t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t>
|
||||
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
|
||||
<t t-set-slot="content">Content of card 1... [<t t-esc="state.a"/>]</t>
|
||||
<t t-set-slot="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
|
||||
</Card>
|
||||
<Card title="'Title card B'">
|
||||
<t t-set="content">
|
||||
<t t-set-slot="content">
|
||||
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
||||
<Counter />
|
||||
</t>
|
||||
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
|
||||
<t t-set-slot="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
|
||||
</Card>
|
||||
</div>
|
||||
</templates>`;
|
||||
|
||||
@@ -106,7 +106,6 @@ async function startRelease() {
|
||||
draft: true // todo: remove this someday
|
||||
};
|
||||
const result = await createRelease(token, options);
|
||||
console.log(result);
|
||||
await ask("Ready for next step...");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user