mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1e79677cb | |||
| 89d6b8b7dd | |||
| 9c2523e3ce | |||
| d8201b8955 | |||
| dfead2836e | |||
| 0e0ac5329a | |||
| efd934d2b1 | |||
| dabc24cee3 | |||
| 7f49796a07 | |||
| 757dffefac | |||
| bdfe058279 | |||
| 345c44b952 | |||
| ea74739d46 | |||
| 93b53d8017 |
+1
-1
@@ -14,7 +14,7 @@ removed after.
|
||||
|
||||
- components can now have empty content or multiple root nodes (htmlelement or text) ([details](#31-components-can-now-have-arbitrary-content))
|
||||
- new `useEffect` hook
|
||||
- new `onDestroyed` and `onRender` hooks
|
||||
- new `onDestroyed`, `onWillRender` and `onRendered` hooks
|
||||
- breaking: lifecycle methods are removed ([details](#1-component-lifecycle-methods-are-removed))
|
||||
- breaking: can no longer be mounted on detached DOM ([details](#2-components-can-no-longer-be-mounted-in-a-detached-dom-element))
|
||||
- breaking: standalone `mount` method API is simpler ([details](#4-mount-method-api-is-simpler))
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@
|
||||
"sass": "^1.16.1",
|
||||
"source-map-support": "^0.5.10",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "3.9.6",
|
||||
"typescript": "4.5.2",
|
||||
"uglify-es": "^3.3.9"
|
||||
},
|
||||
"jest": {
|
||||
|
||||
@@ -116,6 +116,7 @@ interface IntermediateTree {
|
||||
forceRef?: boolean;
|
||||
refIdx?: number;
|
||||
refN: number;
|
||||
currentNS: string | null;
|
||||
}
|
||||
|
||||
function buildTree(
|
||||
@@ -124,9 +125,10 @@ function buildTree(
|
||||
domParentTree: IntermediateTree | null = null
|
||||
): IntermediateTree {
|
||||
switch (node.nodeType) {
|
||||
case 1: {
|
||||
case Node.ELEMENT_NODE: {
|
||||
// HTMLElement
|
||||
let isActive = false;
|
||||
let currentNS = parent && parent.currentNS;
|
||||
const tagName = (node as Element).tagName;
|
||||
let el: Node | undefined = undefined;
|
||||
const info: DynamicInfo[] = [];
|
||||
@@ -143,11 +145,18 @@ function buildTree(
|
||||
el = document.createTextNode("");
|
||||
isActive = true;
|
||||
}
|
||||
if (!el) {
|
||||
el = document.createElement(tagName);
|
||||
const attrs = (node as Element).attributes;
|
||||
const ns = attrs.getNamedItem("block-ns");
|
||||
if (ns) {
|
||||
attrs.removeNamedItem("block-ns");
|
||||
currentNS = ns.value;
|
||||
}
|
||||
if (el instanceof HTMLElement) {
|
||||
const attrs = (node as Element).attributes;
|
||||
if (!el) {
|
||||
el = currentNS
|
||||
? document.createElementNS(currentNS, tagName)
|
||||
: document.createElement(tagName);
|
||||
}
|
||||
if (el instanceof Element) {
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attrName = attrs[i].name;
|
||||
const attrValue = attrs[i].value;
|
||||
@@ -193,13 +202,14 @@ function buildTree(
|
||||
el,
|
||||
info,
|
||||
refN: isActive ? 1 : 0,
|
||||
currentNS,
|
||||
};
|
||||
|
||||
if (node.firstChild) {
|
||||
const childNode = node.childNodes[0];
|
||||
if (
|
||||
node.childNodes.length === 1 &&
|
||||
childNode.nodeType === 1 &&
|
||||
childNode.nodeType === Node.ELEMENT_NODE &&
|
||||
(childNode as Element).tagName.startsWith("block-child-")
|
||||
) {
|
||||
const tagName = (childNode as Element).tagName;
|
||||
@@ -227,11 +237,11 @@ function buildTree(
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
case 3:
|
||||
case 8: {
|
||||
case Node.TEXT_NODE:
|
||||
case Node.COMMENT_NODE: {
|
||||
// text node or comment node
|
||||
const el =
|
||||
node.nodeType === 3
|
||||
node.nodeType === Node.TEXT_NODE
|
||||
? document.createTextNode(node.textContent!)
|
||||
: document.createComment(node.textContent!);
|
||||
return {
|
||||
@@ -241,6 +251,7 @@ function buildTree(
|
||||
el,
|
||||
info: [],
|
||||
refN: 0,
|
||||
currentNS: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,6 +497,10 @@ export class CodeGenerator {
|
||||
}
|
||||
// attributes
|
||||
const attrs: { [key: string]: string } = {};
|
||||
if (ast.ns) {
|
||||
// specific namespace uri
|
||||
attrs["block-ns"] = ast.ns;
|
||||
}
|
||||
for (let key in ast.attrs) {
|
||||
if (key.startsWith("t-attf")) {
|
||||
let expr = interpolate(ast.attrs[key]);
|
||||
@@ -944,7 +948,7 @@ export class CodeGenerator {
|
||||
let propString = propStr;
|
||||
if (ast.dynamicProps) {
|
||||
if (!props.length) {
|
||||
propString = `${compileExpr(ast.dynamicProps)}`;
|
||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
|
||||
} else {
|
||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
|
||||
}
|
||||
@@ -1008,7 +1012,7 @@ export class CodeGenerator {
|
||||
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
|
||||
}
|
||||
const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
|
||||
let blockExpr = `component(${blockArgs})`;
|
||||
let blockExpr = `component(${blockArgs}${hasSlot ? ", true" : ""})`;
|
||||
if (Object.keys(extraArgs).length) {
|
||||
this.shouldDefineAssign = true;
|
||||
const content = Object.keys(extraArgs).map((k) => `${k}: ${extraArgs[k]}`);
|
||||
|
||||
+12
-9
@@ -49,6 +49,7 @@ export interface ASTDomNode {
|
||||
shouldTrim: boolean;
|
||||
shouldNumberize: boolean;
|
||||
} | null;
|
||||
ns: string | null;
|
||||
}
|
||||
|
||||
export interface ASTMulti {
|
||||
@@ -171,11 +172,12 @@ export type AST =
|
||||
// -----------------------------------------------------------------------------
|
||||
interface ParsingContext {
|
||||
inPreTag: boolean;
|
||||
inSVG: boolean;
|
||||
}
|
||||
|
||||
export function parse(xml: string | Node): AST {
|
||||
const node = xml instanceof Element ? xml : parseXML(`<t>${xml}</t>`).firstChild!;
|
||||
const ctx = { inPreTag: false };
|
||||
const ctx = { inPreTag: false, inSVG: false };
|
||||
const ast = parseNode(node, ctx);
|
||||
if (!ast) {
|
||||
return { type: ASTType.Text, value: "" };
|
||||
@@ -240,7 +242,7 @@ const lineBreakRE = /[\r\n]/;
|
||||
const whitespaceRE = /\s+/g;
|
||||
|
||||
function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
||||
if (node.nodeType === 3) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
let value = node.textContent || "";
|
||||
if (!ctx.inPreTag) {
|
||||
if (lineBreakRE.test(value) && !value.trim()) {
|
||||
@@ -250,7 +252,7 @@ function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null
|
||||
}
|
||||
|
||||
return { type: ASTType.Text, value };
|
||||
} else if (node.nodeType === 8) {
|
||||
} else if (node.nodeType === Node.COMMENT_NODE) {
|
||||
return { type: ASTType.Comment, value: node.textContent || "" };
|
||||
}
|
||||
return null;
|
||||
@@ -289,18 +291,18 @@ const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
|
||||
|
||||
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
const { tagName } = node;
|
||||
let dynamicTag = null;
|
||||
if (node.hasAttribute("t-tag")) {
|
||||
dynamicTag = node.getAttribute("t-tag");
|
||||
node.removeAttribute("t-tag");
|
||||
}
|
||||
const dynamicTag = node.getAttribute("t-tag");
|
||||
node.removeAttribute("t-tag");
|
||||
if (tagName === "t" && !dynamicTag) {
|
||||
return null;
|
||||
}
|
||||
const children: AST[] = [];
|
||||
if (tagName === "pre") {
|
||||
ctx = { inPreTag: true };
|
||||
ctx.inPreTag = true;
|
||||
}
|
||||
const shouldAddSVGNS = tagName === "svg" || (tagName === "g" && !ctx.inSVG);
|
||||
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
|
||||
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
|
||||
const ref = node.getAttribute("t-ref");
|
||||
node.removeAttribute("t-ref");
|
||||
|
||||
@@ -381,6 +383,7 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
ref,
|
||||
content: children,
|
||||
model,
|
||||
ns,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Env } from "../app/app";
|
||||
import type { ComponentNode } from "./component_node";
|
||||
|
||||
export type Props = { [key: string]: any };
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Component Class
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -8,6 +10,7 @@ import type { ComponentNode } from "./component_node";
|
||||
export class Component {
|
||||
static template: string = "";
|
||||
static style: string = "";
|
||||
static props?: any;
|
||||
|
||||
props: any;
|
||||
env: Env;
|
||||
@@ -25,7 +28,7 @@ export class Component {
|
||||
|
||||
setup() {}
|
||||
|
||||
render(): Promise<void> {
|
||||
return this.__owl__.render();
|
||||
render(deep: boolean = false): Promise<void> {
|
||||
return this.__owl__.render(deep);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { App, Env } from "../app/app";
|
||||
import { BDom, VNode } from "../blockdom";
|
||||
import { Component } from "./component";
|
||||
import { clearReactivesForCallback, Reactive, reactive } from "../reactivity";
|
||||
import { batched, Callback } from "../utils";
|
||||
import { Component, Props } from "./component";
|
||||
import { fibersInError, handleError } from "./error_handling";
|
||||
import {
|
||||
Fiber,
|
||||
makeChildFiber,
|
||||
@@ -8,20 +11,68 @@ import {
|
||||
MountFiber,
|
||||
MountOptions,
|
||||
RootFiber,
|
||||
__internal__destroyed,
|
||||
__internal__destroyed
|
||||
} from "./fibers";
|
||||
import { handleError, fibersInError } from "./error_handling";
|
||||
import { applyDefaultProps } from "./props_validation";
|
||||
import { STATUS } from "./status";
|
||||
import { applyStyles } from "./style";
|
||||
|
||||
let currentNode: ComponentNode | null = null;
|
||||
|
||||
export function getCurrent(): ComponentNode | null {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Integration with reactivity system (useState)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
||||
/**
|
||||
* Creates a reactive object that will be observed by the current component.
|
||||
* Reading data from the returned object (eg during rendering) will cause the
|
||||
* component to subscribe to that data and be rerendered when it changes.
|
||||
*
|
||||
* @param state the state to observe
|
||||
* @returns a reactive object that will cause the component to re-render on
|
||||
* relevant changes
|
||||
* @see reactive
|
||||
*/
|
||||
export function useState<T extends object>(state: T): Reactive<T> {
|
||||
if (!batchedRenderFunctions.has(currentNode!)) {
|
||||
batchedRenderFunctions.set(
|
||||
currentNode!,
|
||||
batched(() => currentNode!.render())
|
||||
);
|
||||
}
|
||||
const render = batchedRenderFunctions.get(currentNode!)!;
|
||||
const reactiveState = reactive(state, render);
|
||||
|
||||
// manual implementation of onWillUnmount to break cyclic dependency
|
||||
currentNode!.willUnmount.unshift( clearReactivesForCallback.bind(null, render))
|
||||
return reactiveState;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// component function (used in compiled template code)
|
||||
// -----------------------------------------------------------------------------
|
||||
function arePropsDifferent(props1: Props, props2: Props): boolean {
|
||||
for (let k in props1) {
|
||||
if (props1[k] !== props2[k]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function component(
|
||||
name: string | typeof Component,
|
||||
props: any,
|
||||
key: string,
|
||||
ctx: ComponentNode,
|
||||
parent: any
|
||||
parent: any,
|
||||
hasSlots: boolean = false
|
||||
): ComponentNode {
|
||||
console.warn('asdf')
|
||||
let node: any = ctx.children[key];
|
||||
let isDynamic = typeof name !== "string";
|
||||
|
||||
@@ -39,7 +90,11 @@ export function component(
|
||||
|
||||
const parentFiber = ctx.fiber!;
|
||||
if (node) {
|
||||
node.updateAndRender(props, parentFiber);
|
||||
console.warn('coucou');
|
||||
if (hasSlots || parentFiber.deep || arePropsDifferent(node.component.props, props)) {
|
||||
console.warn('coucou3');
|
||||
node.updateAndRender(props, parentFiber);
|
||||
}
|
||||
} else {
|
||||
// new component
|
||||
let C;
|
||||
@@ -61,18 +116,14 @@ export function component(
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Component VNode
|
||||
// Component VNode class
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
let currentNode: ComponentNode | null = null;
|
||||
|
||||
export function getCurrent(): ComponentNode | null {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
type LifecycleHook = Function;
|
||||
|
||||
export class ComponentNode<T extends typeof Component = any> implements VNode<ComponentNode> {
|
||||
export class ComponentNode<T extends typeof Component = typeof Component>
|
||||
implements VNode<ComponentNode>
|
||||
{
|
||||
el?: HTMLElement | Text | undefined;
|
||||
app: App;
|
||||
fiber: Fiber | null = null;
|
||||
@@ -104,6 +155,9 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
applyDefaultProps(props, C);
|
||||
const env = (parent && parent.childEnv) || app.env;
|
||||
this.childEnv = env;
|
||||
// if (props) {
|
||||
// props = useState(props);
|
||||
// }
|
||||
this.component = new C(props, env, this) as any;
|
||||
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
||||
if (C.style) {
|
||||
@@ -127,7 +181,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
try {
|
||||
await Promise.all(this.willStart.map((f) => f.call(component)));
|
||||
} catch (e) {
|
||||
handleError(this, e as Error);
|
||||
handleError({ node: this, error: e });
|
||||
return;
|
||||
}
|
||||
if (this.status === STATUS.NEW && this.fiber === fiber) {
|
||||
@@ -135,7 +189,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
}
|
||||
}
|
||||
|
||||
async render() {
|
||||
async render(deep: boolean = false) {
|
||||
let fiber = this.fiber;
|
||||
if (fiber && !fiber.bdom && !fibersInError.has(fiber)) {
|
||||
return fiber.root.promise;
|
||||
@@ -145,6 +199,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
return;
|
||||
}
|
||||
fiber = makeRootFiber(this);
|
||||
fiber.deep = deep;
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
await Promise.resolve();
|
||||
if (this.status === STATUS.DESTROYED) {
|
||||
@@ -161,7 +216,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
fiber.bdom = this.renderFn();
|
||||
fiber.root.counter--;
|
||||
} catch (e) {
|
||||
handleError(this, e as Error);
|
||||
handleError({ node: this, error: e });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +294,10 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
}
|
||||
|
||||
patch() {
|
||||
if (!this.fiber) {
|
||||
// component was not rendered => no need to do anything
|
||||
return;
|
||||
}
|
||||
this.bdom!.patch(this!.fiber!.bdom!, false);
|
||||
this.fiber!.appliedToDom = true;
|
||||
this.fiber = null;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import type { Fiber } from "./fibers";
|
||||
|
||||
export const fibersInError: WeakMap<Fiber, Error> = new WeakMap();
|
||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: Error) => void)[]> = new WeakMap();
|
||||
// Maps fibers to thrown errors
|
||||
export const fibersInError: WeakMap<Fiber, any> = new WeakMap();
|
||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: any) => void)[]> = new WeakMap();
|
||||
|
||||
function _handleError(node: ComponentNode | null, error: Error, isFirstRound = false): boolean {
|
||||
function _handleError(node: ComponentNode | null, error: any, isFirstRound = false): boolean {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
@@ -25,7 +26,7 @@ function _handleError(node: ComponentNode | null, error: Error, isFirstRound = f
|
||||
h(error);
|
||||
propagate = false;
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,17 +39,12 @@ function _handleError(node: ComponentNode | null, error: Error, isFirstRound = f
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError(entity: ComponentNode | Fiber, error: Error) {
|
||||
let node: ComponentNode;
|
||||
let fiber: Fiber;
|
||||
// soft type check on Fiber
|
||||
if ("node" in entity) {
|
||||
fiber = entity;
|
||||
node = entity.node;
|
||||
} else {
|
||||
node = entity;
|
||||
fiber = entity.fiber!;
|
||||
}
|
||||
type ErrorParams = { error: any } & ({ node: ComponentNode } | { fiber: Fiber });
|
||||
export function handleError(params: ErrorParams) {
|
||||
const error = params.error;
|
||||
const node = "node" in params ? params.node : params.fiber.node;
|
||||
const fiber = "fiber" in params ? params.fiber : node.fiber!;
|
||||
|
||||
fibersInError.set(fiber.root, error);
|
||||
|
||||
const handled = _handleError(node, error, true);
|
||||
|
||||
@@ -67,6 +67,7 @@ export class Fiber {
|
||||
parent: Fiber | null;
|
||||
children: Fiber[] = [];
|
||||
appliedToDom = false;
|
||||
deep: boolean = false;
|
||||
|
||||
constructor(node: ComponentNode, parent: Fiber | null) {
|
||||
this.node = node;
|
||||
@@ -159,7 +160,7 @@ export class RootFiber extends Fiber {
|
||||
// unregistering the fiber
|
||||
node.fiber = null;
|
||||
} catch (e) {
|
||||
if (!handleError(current || this, e)) {
|
||||
if (!handleError({ fiber: current || this, error: e })) {
|
||||
this.reject(e);
|
||||
}
|
||||
}
|
||||
@@ -206,7 +207,7 @@ export class MountFiber extends RootFiber {
|
||||
}
|
||||
node.fiber = null;
|
||||
} catch (e) {
|
||||
if (!handleError(current as Fiber, e)) {
|
||||
if (!handleError({ fiber: current as Fiber, error: e })) {
|
||||
this.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function onDestroyed(fn: () => Promise<void> | void | any) {
|
||||
node.destroyed.push(fn);
|
||||
}
|
||||
|
||||
export function onRender(fn: () => void | any) {
|
||||
export function onWillRender(fn: () => void | any) {
|
||||
const node = getCurrent()!;
|
||||
const renderFn = node.renderFn;
|
||||
node.renderFn = () => {
|
||||
@@ -49,6 +49,16 @@ export function onRender(fn: () => void | any) {
|
||||
};
|
||||
}
|
||||
|
||||
export function onRendered(fn: () => void | any) {
|
||||
const node = getCurrent()!;
|
||||
const renderFn = node.renderFn;
|
||||
node.renderFn = () => {
|
||||
const result = renderFn();
|
||||
fn();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function onError(fn: (error: Error) => void | any) {
|
||||
const node = getCurrent()!;
|
||||
let handlers = nodeErrorHandlers.get(node);
|
||||
|
||||
@@ -26,26 +26,23 @@ export function applyDefaultProps(props: { [key: string]: any }, ComponentClass:
|
||||
* visit recursively the props and all the children to check if they are valid.
|
||||
* This is why it is only done in 'dev' mode.
|
||||
*/
|
||||
|
||||
// message: { type: String, default: "hello" };
|
||||
|
||||
export const validateProps = function (name: string | typeof Component, props: any, parent?: any) {
|
||||
const ComponentClass =
|
||||
typeof name !== "string" ? name : parent.constructor.components[name as string];
|
||||
const ComponentClass = (
|
||||
typeof name !== "string" ? name : parent.constructor.components[name]
|
||||
) as typeof Component;
|
||||
|
||||
applyDefaultProps(props, ComponentClass);
|
||||
|
||||
const propsDef = (<any>ComponentClass).props;
|
||||
const propsDef = ComponentClass.props;
|
||||
if (propsDef instanceof Array) {
|
||||
// list of strings (prop names)
|
||||
for (let i = 0, l = propsDef.length; i < l; i++) {
|
||||
const propName = propsDef[i];
|
||||
for (const propName of propsDef) {
|
||||
if (propName[propName.length - 1] === "?") {
|
||||
// optional prop
|
||||
break;
|
||||
}
|
||||
if (!(propName in props)) {
|
||||
throw new Error(`Missing props '${propsDef[i]}' (component '${ComponentClass.name}')`);
|
||||
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
||||
}
|
||||
}
|
||||
for (let key in props) {
|
||||
@@ -67,7 +64,9 @@ export const validateProps = function (name: string | typeof Component, props: a
|
||||
try {
|
||||
isValid = isValidProp(props[propName], propsDef[propName]);
|
||||
} catch (e) {
|
||||
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
||||
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
|
||||
(e as Error).message
|
||||
})`;
|
||||
throw e;
|
||||
}
|
||||
if (!isValid) {
|
||||
|
||||
+4
-2
@@ -34,6 +34,7 @@ import type { AppConfig } from "./app/app";
|
||||
import { App } from "./app/app";
|
||||
import { Component } from "./component/component";
|
||||
import { getCurrent } from "./component/component_node";
|
||||
export { useState } from "./component/component_node";
|
||||
|
||||
export { App, Component };
|
||||
|
||||
@@ -55,7 +56,7 @@ export { status } from "./component/status";
|
||||
export { Portal } from "./portal";
|
||||
export { Memo } from "./memo";
|
||||
export { css, xml } from "./tags";
|
||||
export { useState } from "./reactivity";
|
||||
export { reactive } from "./reactivity";
|
||||
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
|
||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
||||
|
||||
@@ -66,7 +67,8 @@ export {
|
||||
onWillUpdateProps,
|
||||
onWillPatch,
|
||||
onPatched,
|
||||
onRender,
|
||||
onWillRender,
|
||||
onRendered,
|
||||
onDestroyed,
|
||||
onError,
|
||||
} from "./component/lifecycle_hooks";
|
||||
|
||||
+172
-259
@@ -1,132 +1,26 @@
|
||||
import { ComponentNode, getCurrent } from "./component/component_node";
|
||||
import { onWillUnmount } from "./component/lifecycle_hooks";
|
||||
import { Callback } from "./utils";
|
||||
|
||||
type Observer = ComponentNode | Function;
|
||||
type Atom = any; // proxy linked to a unique observer and source
|
||||
type Source = any; // trackable that is not an atom
|
||||
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
||||
const TARGET = Symbol("Target");
|
||||
// Special key to subscribe to, to be notified of key creation/deletion
|
||||
const KEYCHANGES = Symbol("Key changes");
|
||||
|
||||
const sourceAtoms: WeakMap<Source, Map<any, ObserverSet>> = new WeakMap();
|
||||
const observerSourceAtom: WeakMap<Observer, Map<any, Atom>> = new WeakMap();
|
||||
type ObjectKey = string | number | symbol;
|
||||
type Target = object;
|
||||
export type Reactive<T extends Target = Target> = T & {
|
||||
[TARGET]: any;
|
||||
};
|
||||
|
||||
const SOURCE = Symbol("source");
|
||||
const OBSERVER = Symbol("observer");
|
||||
const KEYS = Symbol("keys");
|
||||
const ROOT = Symbol("root");
|
||||
const SEED = Symbol("Seed");
|
||||
|
||||
export function observe(source: any, observer: Observer): [Atom, Function] {
|
||||
if (!isTrackable(source)) {
|
||||
throw new Error("First argument is not trackable");
|
||||
}
|
||||
const unregisterObserver = registerObserver(observer);
|
||||
const newAtom = atom(source, observer);
|
||||
return [newAtom, unregisterObserver];
|
||||
}
|
||||
|
||||
function atom(source: any, observer: Observer) {
|
||||
return _atom(source, observer, true);
|
||||
}
|
||||
|
||||
function _atom(source: any, observer: Observer, seed = false) {
|
||||
if (isTrackable(source) && observerSourceAtom.has(observer)) {
|
||||
source = (source as any)[SOURCE] || source;
|
||||
const oldAtom = observerSourceAtom.get(observer)!.get(source);
|
||||
if (oldAtom) {
|
||||
if (seed) {
|
||||
oldAtom[SEED] = true;
|
||||
}
|
||||
return oldAtom;
|
||||
}
|
||||
if (!sourceAtoms.get(source)) {
|
||||
sourceAtoms.set(source, new Map([[ROOT, new ObserverSet()]]));
|
||||
}
|
||||
const newAtom = createAtom(source, observer);
|
||||
if (seed) {
|
||||
newAtom[SEED] = true;
|
||||
}
|
||||
observerSourceAtom.get(observer)!.set(source, newAtom);
|
||||
sourceAtoms.get(source)!.get(ROOT)!.add(newAtom);
|
||||
return newAtom;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
function createAtom(source: Source, observer: Observer): Atom {
|
||||
const keys: Set<any> = new Set();
|
||||
let seed: boolean = false;
|
||||
const newAtom: Atom = new Proxy(source as any, {
|
||||
set(target: any, key: any, value: any): boolean {
|
||||
if (key === SEED) {
|
||||
seed = value;
|
||||
return true;
|
||||
}
|
||||
if (!(key in target)) {
|
||||
target[key] = value;
|
||||
notify(sourceAtoms.get(source)!.get(ROOT)!);
|
||||
return true;
|
||||
}
|
||||
const current = target[key];
|
||||
if (current !== value) {
|
||||
target[key] = value;
|
||||
const observerSet = sourceAtoms.get(source)!.get(key);
|
||||
if (observerSet) {
|
||||
const clean = isTrackable(current);
|
||||
notify(observerSet, clean);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target: any, key: string): boolean {
|
||||
if (key in target) {
|
||||
const current = target[key];
|
||||
delete target[key];
|
||||
// notify source observers
|
||||
const clean = isTrackable(current);
|
||||
notify(sourceAtoms.get(source)!.get(ROOT)!, clean);
|
||||
const atoms = sourceAtoms.get(source)!;
|
||||
if (atoms.has(key)) {
|
||||
// clear source-key observers
|
||||
for (const atom of atoms.get(key)!) {
|
||||
atom[KEYS].delete(key);
|
||||
}
|
||||
atoms.delete(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
get(target: any, key: any, proxy: any): any {
|
||||
switch (key) {
|
||||
case OBSERVER:
|
||||
return observer;
|
||||
case SOURCE:
|
||||
return source;
|
||||
case KEYS:
|
||||
return keys;
|
||||
case SEED:
|
||||
return seed;
|
||||
default:
|
||||
const value = target[key];
|
||||
// register observer to source-key
|
||||
if (!keys.has(key) && observerSourceAtom.has(observer)) {
|
||||
const atoms = sourceAtoms.get(source)!;
|
||||
if (!atoms.has(key)) {
|
||||
atoms.set(key, new ObserverSet());
|
||||
}
|
||||
atoms.get(key)!.add(newAtom);
|
||||
keys.add(key);
|
||||
}
|
||||
//
|
||||
return _atom(value, observer);
|
||||
}
|
||||
},
|
||||
});
|
||||
return newAtom;
|
||||
}
|
||||
|
||||
function isTrackable(value: any): boolean {
|
||||
/**
|
||||
* Checks whether a given value can be made into a reactive object.
|
||||
*
|
||||
* @param value the value to check
|
||||
* @returns whether the value can be made reactive
|
||||
*/
|
||||
function canBeMadeReactive(value: any): boolean {
|
||||
return (
|
||||
value !== null &&
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!(value instanceof Date) &&
|
||||
!(value instanceof Promise) &&
|
||||
!(value instanceof String) &&
|
||||
@@ -134,144 +28,163 @@ function isTrackable(value: any): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function registerObserver(observer: Observer) {
|
||||
if (!observerSourceAtom.get(observer)) {
|
||||
observerSourceAtom.set(observer, new Map());
|
||||
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
|
||||
/**
|
||||
* Observes a given key on a target with an callback. The callback will be
|
||||
* called when the given key changes on the target.
|
||||
*
|
||||
* @param target the target whose key should be observed
|
||||
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
||||
* or deletion)
|
||||
* @param callback the function to call when the key changes
|
||||
*/
|
||||
function observeTargetKey(target: Target, key: ObjectKey, callback: Callback): void {
|
||||
if (!targetToKeysToCallbacks.get(target)) {
|
||||
targetToKeysToCallbacks.set(target, new Map());
|
||||
}
|
||||
return unregisterObserverAtoms.bind(null, observer, false);
|
||||
const keyToCallbacks = targetToKeysToCallbacks.get(target)!;
|
||||
if (!keyToCallbacks.get(key)) {
|
||||
keyToCallbacks.set(key, new Set());
|
||||
}
|
||||
keyToCallbacks.get(key)!.add(callback);
|
||||
}
|
||||
|
||||
function unregisterObserverAtoms(observer: Observer, keepSeeds: boolean) {
|
||||
const observerAtoms = observerSourceAtom.get(observer)!;
|
||||
for (const [source, atom] of observerAtoms) {
|
||||
if (keepSeeds && atom[SEED]) {
|
||||
continue;
|
||||
}
|
||||
observerAtoms.delete(source);
|
||||
const atoms = sourceAtoms.get(source)!;
|
||||
atoms.get(ROOT)!.delete(atom);
|
||||
for (const key of atom[KEYS]) {
|
||||
atoms.get(key)!.delete(atom);
|
||||
}
|
||||
}
|
||||
if (!keepSeeds) {
|
||||
observerSourceAtom.delete(observer);
|
||||
}
|
||||
}
|
||||
|
||||
export function useState(state: any): Atom {
|
||||
const node = getCurrent()!;
|
||||
const [newAtom, unregisterObserver] = observe(state, node);
|
||||
onWillUnmount(() => unregisterObserver());
|
||||
return newAtom;
|
||||
}
|
||||
|
||||
class ObserverSet {
|
||||
nodeAntichain: Antichain = new Antichain();
|
||||
callbackSet: Set<Atom> = new Set();
|
||||
add(atom: Atom) {
|
||||
if (atom[OBSERVER] instanceof ComponentNode) {
|
||||
this.nodeAntichain.add(atom);
|
||||
} else {
|
||||
this.callbackSet.add(atom);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
delete(atom: Atom) {
|
||||
if (atom[OBSERVER] instanceof ComponentNode) {
|
||||
return this.nodeAntichain.delete(atom);
|
||||
} else {
|
||||
return this.callbackSet.delete(atom);
|
||||
}
|
||||
}
|
||||
union(other: ObserverSet) {
|
||||
for (const atom of other.nodeAntichain) {
|
||||
this.nodeAntichain.add(atom);
|
||||
}
|
||||
for (const callback of other.callbackSet) {
|
||||
this.callbackSet.add(callback);
|
||||
}
|
||||
}
|
||||
notify() {
|
||||
for (const atom of this.nodeAntichain) {
|
||||
// an observer cannot be found twice here: <= in add is based on observers
|
||||
atom[OBSERVER].render();
|
||||
}
|
||||
const called = new Set();
|
||||
for (const atom of this.callbackSet) {
|
||||
const observer = atom[OBSERVER];
|
||||
if (!called.has(observer)) {
|
||||
observer();
|
||||
}
|
||||
called.add(observer);
|
||||
}
|
||||
}
|
||||
*[Symbol.iterator]() {
|
||||
yield* this.nodeAntichain;
|
||||
yield* this.callbackSet;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to optimize this!
|
||||
function isLessOrEqual(node1: ComponentNode, node2: ComponentNode) {
|
||||
let current: any = node1;
|
||||
if (current.level <= node2.level) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
if (current === node2) {
|
||||
return true;
|
||||
}
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
return false;
|
||||
}
|
||||
|
||||
// set of atoms linked to observers of type ComponentNode
|
||||
class Antichain extends Set<Atom> {
|
||||
level?: number;
|
||||
add(atom: Atom) {
|
||||
const node = atom[OBSERVER];
|
||||
if (this.level === node.level || this.level === undefined) {
|
||||
super.add(atom);
|
||||
this.level = node.level;
|
||||
return this;
|
||||
}
|
||||
let willAdd = false;
|
||||
for (const atom2 of this) {
|
||||
const node2 = atom2[OBSERVER];
|
||||
if (!willAdd && isLessOrEqual(node, node2)) {
|
||||
return this;
|
||||
} else if (isLessOrEqual(node2, node)) {
|
||||
super.delete(atom2);
|
||||
willAdd = true;
|
||||
}
|
||||
}
|
||||
super.add(atom);
|
||||
this.level = NaN;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
let toNotify: ObserverSet | null = null;
|
||||
let toClean: Set<Observer> = new Set();
|
||||
async function notify(observers: ObserverSet, clean = false) {
|
||||
if (clean) {
|
||||
for (const atom of observers) {
|
||||
toClean.add(atom[OBSERVER]);
|
||||
}
|
||||
}
|
||||
if (toNotify) {
|
||||
toNotify.union(observers);
|
||||
/**
|
||||
* Notify Reactives that are observing a given target that a key has changed on
|
||||
* the target.
|
||||
*
|
||||
* @param target target whose Reactives should be notified that the target was
|
||||
* changed.
|
||||
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
||||
* or deleted)
|
||||
*/
|
||||
function notifyReactives(target: Target, key: ObjectKey): void {
|
||||
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
||||
if (!keyToCallbacks) {
|
||||
return;
|
||||
}
|
||||
toNotify = new ObserverSet();
|
||||
toNotify.union(observers);
|
||||
await Promise.resolve();
|
||||
for (const observer of toClean) {
|
||||
unregisterObserverAtoms(observer, true);
|
||||
const callbacks = keyToCallbacks.get(key);
|
||||
if (!callbacks) {
|
||||
return;
|
||||
}
|
||||
// Loop on copy because clearReactivesForCallback will modify the set in place
|
||||
for (const callback of [...callbacks]) {
|
||||
clearReactivesForCallback(callback);
|
||||
callback();
|
||||
}
|
||||
toClean.clear();
|
||||
toNotify.notify();
|
||||
toNotify = null;
|
||||
}
|
||||
|
||||
const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
|
||||
/**
|
||||
* Clears all subscriptions of the Reactives associated with a given callback.
|
||||
*
|
||||
* @param callback the callback for which the reactives need to be cleared
|
||||
*/
|
||||
export function clearReactivesForCallback(callback: Callback): void {
|
||||
const targetsToClear = callbacksToTargets.get(callback);
|
||||
if (!targetsToClear) {
|
||||
return;
|
||||
}
|
||||
for (const target of targetsToClear) {
|
||||
const observedKeys = targetToKeysToCallbacks.get(target);
|
||||
if (!observedKeys) {
|
||||
continue;
|
||||
}
|
||||
for (const callbacks of observedKeys.values()) {
|
||||
callbacks.delete(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reactiveCache = new WeakMap<Target, Map<Callback, Reactive>>();
|
||||
/**
|
||||
* Creates a reactive proxy for an object. Reading data on the reactive object
|
||||
* subscribes to changes to the data. Writing data on the object will cause the
|
||||
* notify callback to be called if there are suscriptions to that data. Nested
|
||||
* objects and arrays are automatically made reactive as well.
|
||||
*
|
||||
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
||||
* you would like to be notified of any further changes, you should go read
|
||||
* the underlying data again. We assume that if you don't go read it again after
|
||||
* being notified, it means that you are no longer interested in that data.
|
||||
*
|
||||
* Subscriptions:
|
||||
* + Reading a property on an object will subscribe you to changes in the value
|
||||
* of that property.
|
||||
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
||||
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
||||
* key on the object with 'in' has the same effect.
|
||||
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
||||
* This is a choice that was made because changing a key's value will trigger
|
||||
* this trap and we do not want to subscribe by writes. This also means that
|
||||
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
||||
*
|
||||
* @param target the object for which to create a reactive proxy
|
||||
* @param callback the function to call when an observed property of the
|
||||
* reactive has changed
|
||||
* @returns a proxy that tracks changes to it
|
||||
*/
|
||||
export function reactive<T extends Target>(target: T, callback: Callback): Reactive<T> {
|
||||
if (!canBeMadeReactive(target)) {
|
||||
throw new Error(`Cannot make the given value reactive`);
|
||||
}
|
||||
const originalTarget = (target as Reactive)[TARGET];
|
||||
if (originalTarget) {
|
||||
return reactive(originalTarget, callback);
|
||||
}
|
||||
if (!reactiveCache.has(target)) {
|
||||
reactiveCache.set(target, new Map());
|
||||
}
|
||||
const reactivesForTarget = reactiveCache.get(target)!;
|
||||
if (!reactivesForTarget.has(callback)) {
|
||||
const proxy = new Proxy(target, {
|
||||
get(target: any, key: ObjectKey, proxy: Reactive<T>) {
|
||||
if (key === TARGET) {
|
||||
return target;
|
||||
}
|
||||
observeTargetKey(target, key, callback);
|
||||
const value = Reflect.get(target, key, proxy);
|
||||
if (!canBeMadeReactive(value)) {
|
||||
return value;
|
||||
}
|
||||
return reactive(value, callback);
|
||||
},
|
||||
set(target, key, value, proxy) {
|
||||
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
||||
const originalValue = Reflect.get(target, key, proxy);
|
||||
const ret = Reflect.set(target, key, value, proxy);
|
||||
if (isNewKey) {
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
}
|
||||
// While Array length may trigger the set trap, it's not actually set by this
|
||||
// method but is updated behind the scenes, and the trap is not called with the
|
||||
// new value. We disable the "same-value-optimization" for it because of that.
|
||||
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
||||
notifyReactives(target, key);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
deleteProperty(target, key) {
|
||||
const ret = Reflect.deleteProperty(target, key);
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
notifyReactives(target, key);
|
||||
return ret;
|
||||
},
|
||||
ownKeys(target) {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return Reflect.ownKeys(target);
|
||||
},
|
||||
has(target, key) {
|
||||
// TODO: this observes all key changes instead of only the presence of the argument key
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return Reflect.has(target, key);
|
||||
},
|
||||
});
|
||||
reactivesForTarget.set(callback, proxy);
|
||||
if (!callbacksToTargets.has(callback)) {
|
||||
callbacksToTargets.set(callback, new Set());
|
||||
}
|
||||
callbacksToTargets.get(callback)!.add(target);
|
||||
}
|
||||
return reactivesForTarget.get(callback) as Reactive<T>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
export type Callback = () => void;
|
||||
|
||||
/**
|
||||
* Creates a batched version of a callback so that all calls to it in the same
|
||||
* microtick will only call the original callback once.
|
||||
*
|
||||
* @param callback the callback to batch
|
||||
* @returns a batched version of the original callback
|
||||
*/
|
||||
export function batched(callback: Callback): Callback {
|
||||
let called = false;
|
||||
return async () => {
|
||||
// This await blocks all calls to the callback here, then releases them sequentially
|
||||
// in the next microtick. This line decides the granularity of the batch.
|
||||
await Promise.resolve();
|
||||
if (!called) {
|
||||
called = true;
|
||||
callback();
|
||||
// wait for all calls in this microtick to fall through before resetting "called"
|
||||
// so that only the first call to the batched function calls the original callback
|
||||
await Promise.resolve();
|
||||
called = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export class EventBus extends EventTarget {
|
||||
trigger(name: string, payload?: any) {
|
||||
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/><block-text-1/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['context'][ctx['props'].key].n;
|
||||
let d2 = ctx['state'].x;
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`ComponentC\`, {key: ctx['props'].key}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`ComponentB\`, {key: ctx['context'].key}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState destroyed component before being mounted is inactive 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { createBlock, mount } from "../../src/blockdom";
|
||||
import { makeTestFixture } from "./helpers";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Setup and helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const XHTML_URI = "http://www.w3.org/1999/xhtml";
|
||||
const SVG_URI = "http://www.w3.org/2000/svg";
|
||||
let fixture: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.remove();
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tests
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
describe("namespace", () => {
|
||||
test("default namespace is xhtml", () => {
|
||||
const block = createBlock(`<tag/>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe("<tag></tag>");
|
||||
expect(fixture.firstElementChild!.namespaceURI).toBe(XHTML_URI);
|
||||
});
|
||||
|
||||
test("namespace can be changed with block-ns", () => {
|
||||
const block = createBlock(`<tag block-ns="${SVG_URI}"/>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe("<tag></tag>");
|
||||
expect(fixture.firstElementChild!.namespaceURI).toBe(SVG_URI);
|
||||
});
|
||||
|
||||
test("namespace is kept for children", () => {
|
||||
const block = createBlock(
|
||||
`<parent block-ns="${SVG_URI}"><child><subchild/></child><child/></parent>`
|
||||
);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<parent><child><subchild></subchild></child><child></child></parent>"
|
||||
);
|
||||
const parent = fixture.firstElementChild!;
|
||||
const child1 = parent.firstElementChild!;
|
||||
const subchild = child1.firstElementChild!;
|
||||
const child2 = child1.nextElementSibling!;
|
||||
expect(parent.namespaceURI).toBe(SVG_URI);
|
||||
expect(child1.namespaceURI).toBe(SVG_URI);
|
||||
expect(child2.namespaceURI).toBe(SVG_URI);
|
||||
expect(subchild.namespaceURI).toBe(SVG_URI);
|
||||
});
|
||||
|
||||
test("various namespaces in same block", () => {
|
||||
const block = createBlock(`<none><one block-ns="one"/><two block-ns="two"/></none>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe("<none><one></one><two></two></none>");
|
||||
const none = fixture.firstElementChild!;
|
||||
const one = none.firstElementChild!;
|
||||
const two = one.nextElementSibling!;
|
||||
expect(none.namespaceURI).toBe(XHTML_URI);
|
||||
expect(one.namespaceURI).toBe("one");
|
||||
expect(two.namespaceURI).toBe("two");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`properly support svg add proper namespace to g tags 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<g block-ns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </g>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg add proper namespace to svg 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\" width=\\"100px\\" height=\\"90px\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg namespace to g tags not added if already in svg namespace 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><g/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg namespace to svg tags added even if already in svg namespace 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><svg block-ns=\\"http://www.w3.org/2000/svg\\"/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -46,6 +46,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,6 +74,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
content: [],
|
||||
ns: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,6 +88,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
content: [{ type: ASTType.Text, value: "some text" }],
|
||||
ns: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,6 +101,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{ type: ASTType.Text, value: "some text" },
|
||||
{
|
||||
@@ -108,6 +112,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "inside" }],
|
||||
},
|
||||
],
|
||||
@@ -126,6 +131,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [],
|
||||
},
|
||||
{
|
||||
@@ -136,6 +142,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [],
|
||||
},
|
||||
],
|
||||
@@ -155,6 +162,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [],
|
||||
},
|
||||
],
|
||||
@@ -170,10 +178,83 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "foo" }],
|
||||
});
|
||||
});
|
||||
|
||||
test("svg dom node", async () => {
|
||||
expect(
|
||||
parse(
|
||||
`<svg width="100px" height="90px"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/></svg>`
|
||||
)
|
||||
).toEqual({
|
||||
attrs: {
|
||||
height: "90px",
|
||||
width: "100px",
|
||||
},
|
||||
content: [
|
||||
{
|
||||
attrs: {
|
||||
cx: "50",
|
||||
cy: "50",
|
||||
fill: "yellow",
|
||||
r: "4",
|
||||
stroke: "green",
|
||||
"stroke-width": "1",
|
||||
},
|
||||
content: [],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: {},
|
||||
ref: null,
|
||||
tag: "circle",
|
||||
type: 2,
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: "http://www.w3.org/2000/svg",
|
||||
on: {},
|
||||
ref: null,
|
||||
tag: "svg",
|
||||
type: 2,
|
||||
});
|
||||
expect(
|
||||
parse(`<g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/></g>`)
|
||||
).toEqual({
|
||||
attrs: {},
|
||||
content: [
|
||||
{
|
||||
attrs: {
|
||||
cx: "50",
|
||||
cy: "50",
|
||||
fill: "yellow",
|
||||
r: "4",
|
||||
stroke: "green",
|
||||
"stroke-width": "1",
|
||||
},
|
||||
content: [],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: {},
|
||||
ref: null,
|
||||
tag: "circle",
|
||||
type: 2,
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: "http://www.w3.org/2000/svg",
|
||||
on: {},
|
||||
ref: null,
|
||||
tag: "g",
|
||||
type: 2,
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// t-esc
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -200,6 +281,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "" }],
|
||||
});
|
||||
});
|
||||
@@ -221,6 +303,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "hey" }],
|
||||
});
|
||||
});
|
||||
@@ -262,6 +345,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TOut, expr: "text", body: null }],
|
||||
});
|
||||
});
|
||||
@@ -275,6 +359,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{ type: ASTType.TOut, expr: "text", body: [{ type: ASTType.Text, value: "body" }] },
|
||||
],
|
||||
@@ -294,6 +379,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.TIf,
|
||||
@@ -321,6 +407,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "hey" }],
|
||||
},
|
||||
tElif: null,
|
||||
@@ -397,6 +484,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.Text,
|
||||
@@ -415,6 +503,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "elif" }],
|
||||
},
|
||||
},
|
||||
@@ -427,6 +516,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.Text,
|
||||
@@ -474,6 +564,7 @@ describe("qweb parser", () => {
|
||||
dynamicTag: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "ok" }],
|
||||
},
|
||||
],
|
||||
@@ -493,6 +584,7 @@ describe("qweb parser", () => {
|
||||
dynamicTag: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "ok" }],
|
||||
},
|
||||
{ type: ASTType.Text, value: "abc" },
|
||||
@@ -527,6 +619,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.TIf,
|
||||
@@ -601,6 +694,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
memo: "",
|
||||
@@ -646,6 +740,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
memo: "",
|
||||
@@ -681,6 +776,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
},
|
||||
@@ -715,6 +811,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "category.name", defaultValue: "" }],
|
||||
},
|
||||
memo: "",
|
||||
@@ -736,6 +833,7 @@ describe("qweb parser", () => {
|
||||
model: null,
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.TForEach,
|
||||
@@ -781,6 +879,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
attrs: {},
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
memo: "",
|
||||
@@ -888,6 +987,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.TCall,
|
||||
@@ -925,6 +1025,7 @@ describe("qweb parser", () => {
|
||||
on: { click: "add" },
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "Click" }],
|
||||
});
|
||||
});
|
||||
@@ -1003,6 +1104,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
on: {},
|
||||
ns: null,
|
||||
},
|
||||
{
|
||||
type: ASTType.DomNode,
|
||||
@@ -1013,6 +1115,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
on: {},
|
||||
ns: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1203,6 +1306,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "hey" }],
|
||||
},
|
||||
});
|
||||
@@ -1220,6 +1324,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "hey" }],
|
||||
},
|
||||
});
|
||||
@@ -1238,6 +1343,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: "name",
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "hey" }],
|
||||
});
|
||||
});
|
||||
@@ -1251,6 +1357,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: "name",
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{ type: ASTType.TOut, expr: "text", body: [{ type: ASTType.Text, value: "body" }] },
|
||||
],
|
||||
@@ -1266,6 +1373,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: "name",
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "body" }],
|
||||
});
|
||||
});
|
||||
@@ -1309,7 +1417,8 @@ describe("qweb parser", () => {
|
||||
model: null,
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
type: 2,
|
||||
type: ASTType.DomNode,
|
||||
ns: null,
|
||||
},
|
||||
type: 16,
|
||||
},
|
||||
@@ -1339,6 +1448,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1357,6 +1467,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1375,6 +1486,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1394,6 +1506,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "textarea",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1412,6 +1525,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1430,6 +1544,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1448,6 +1563,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1472,6 +1588,7 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: "theTag",
|
||||
model: null,
|
||||
ns: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+25
-17
@@ -1,22 +1,30 @@
|
||||
import { renderToString, snapshotEverything } from "../helpers";
|
||||
|
||||
// NB: check the snapshots to see where the SVG namespaces are added
|
||||
snapshotEverything();
|
||||
|
||||
describe("properly support svg", () => {
|
||||
test.skip("add proper namespace to svg", () => {
|
||||
// qweb.addTemplate(
|
||||
// "test",
|
||||
// `<svg width="100px" height="90px"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </svg>`
|
||||
// );
|
||||
// expect(renderToString(qweb, "test")).toBe(
|
||||
// `<svg width=\"100px\" height=\"90px\"><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </svg>`
|
||||
// );
|
||||
test("add proper namespace to svg", () => {
|
||||
const template = `<svg width="100px" height="90px"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </svg>`;
|
||||
expect(renderToString(template)).toBe(
|
||||
`<svg width=\"100px\" height=\"90px\"><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </svg>`
|
||||
);
|
||||
});
|
||||
|
||||
test.skip("add proper namespace to g tags", () => {
|
||||
// this is necessary if one wants to use components in a svg
|
||||
// qweb.addTemplate(
|
||||
// "test",
|
||||
// `<g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </g>`
|
||||
// );
|
||||
// expect(renderToString(qweb, "test")).toBe(
|
||||
// `<g><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </g>`
|
||||
// );
|
||||
test("add proper namespace to g tags", () => {
|
||||
const template = `<g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </g>`;
|
||||
expect(renderToString(template)).toBe(
|
||||
`<g><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </g>`
|
||||
);
|
||||
});
|
||||
|
||||
test("namespace to g tags not added if already in svg namespace", () => {
|
||||
const template = `<svg><g/></svg>`;
|
||||
expect(renderToString(template)).toBe(`<svg><g></g></svg>`);
|
||||
});
|
||||
|
||||
test("namespace to svg tags added even if already in svg namespace", () => {
|
||||
const template = `<svg><svg/></svg>`;
|
||||
expect(renderToString(template)).toBe(`<svg><svg></svg></svg>`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1020,7 +1020,7 @@ exports[`basics update props of component without concrete own node 3`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const tKey_1 = ctx['childProps'].key;
|
||||
let b2 = toggler(tKey_1, component(\`Child\`, ctx['childProps'], tKey_1 + key + \`__2\`, node, ctx));
|
||||
let b2 = toggler(tKey_1, component(\`Child\`, Object.assign({}, ctx['childProps']), tKey_1 + key + \`__2\`, node, ctx));
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
@@ -1192,7 +1192,7 @@ exports[`support svg components add proper namespace to svg 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<g><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/></g>\`);
|
||||
let block1 = createBlock(\`<g block-ns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/></g>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -1206,7 +1206,7 @@ exports[`support svg components add proper namespace to svg 2`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<svg><block-child-0/></svg>\`);
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`GComp\`, {}, key + \`__1\`, node, ctx);
|
||||
|
||||
@@ -1067,7 +1067,7 @@ exports[`properly behave when destroyed/unmounted while rendering 2`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`SubChild\`, {}, key + \`__1\`, node, ctx);
|
||||
let b2 = component(\`SubChild\`, {val: ctx['props'].val}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -126,7 +126,7 @@ exports[`can catch errors can catch an error in a component render function 3`]
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -230,7 +230,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -250,7 +250,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -305,7 +305,7 @@ exports[`can catch errors can catch an error in the initial call of a component
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -362,7 +362,7 @@ exports[`can catch errors can catch an error in the initial call of a component
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3;
|
||||
if (ctx['state'].flag) {
|
||||
b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
return block1([], [b3]);
|
||||
}
|
||||
@@ -417,7 +417,7 @@ exports[`can catch errors can catch an error in the mounted call 3`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -473,7 +473,7 @@ exports[`can catch errors can catch an error in the willPatch call 3`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['state'].message;
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([d1], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -527,7 +527,7 @@ exports[`can catch errors can catch an error in the willStart call 3`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -597,7 +597,7 @@ exports[`can catch errors can catch an error origination from a child's willStar
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -531,7 +531,7 @@ exports[`lifecycle hooks mounted hook is called on subsubcomponents, in proper o
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks onRender 1`] = `
|
||||
exports[`lifecycle hooks onWillRender 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
@@ -548,14 +548,14 @@ exports[`lifecycle hooks onRender 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks onRender 2`] = `
|
||||
exports[`lifecycle hooks onWillRender 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||
return component(\`Child\`, {someValue: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -53,7 +53,7 @@ exports[`refs refs are properly bound in slots 2`] = `
|
||||
const refs = ctx.__owl__.refs;
|
||||
let d1 = ctx['state'].val;
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'footer': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'footer': slot3(ctx2)}});
|
||||
return block1([d1], [b3]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rendering semantics blabla 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(ctx['props'].a.b);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics blabla 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {a: ctx['state']}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics can force a render to update sub tree 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`child\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics can force a render to update sub tree 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = text(ctx['state'].value);
|
||||
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics can render a parent without rendering child 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`child\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics can render a parent without rendering child 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = text(ctx['state'].value);
|
||||
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -37,7 +37,7 @@ exports[`slots can define and call slots 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b4 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'header': slot3(ctx2), 'footer': slot4(ctx2)}});
|
||||
let b4 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'header': slot3(ctx2), 'footer': slot4(ctx2)}});
|
||||
return block1([], [b4]);
|
||||
}
|
||||
}"
|
||||
@@ -74,7 +74,7 @@ exports[`slots can render node with t-ref and Component in same slot 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const refs = ctx.__owl__.refs;
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -117,7 +117,7 @@ exports[`slots content is the default slot (variation) 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -152,7 +152,7 @@ exports[`slots content is the default slot 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -226,7 +226,7 @@ exports[`slots default content is not rendered if named slot is provided 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'header': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'header': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -265,7 +265,7 @@ exports[`slots default content is not rendered if slot is provided 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -310,7 +310,7 @@ exports[`slots default slot next to named slot, with default content 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'footer': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'footer': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -340,7 +340,7 @@ exports[`slots default slot work with text nodes (variation) 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -374,7 +374,7 @@ exports[`slots default slot work with text nodes 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -422,7 +422,7 @@ exports[`slots dynamic t-slot call 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b6 = assign(component(\`Toggler\`, {}, key + \`__1\`, node, ctx), {slots: {'slot1': slot3(ctx2), 'slot2': slot4(ctx2)}});
|
||||
let b6 = assign(component(\`Toggler\`, {}, key + \`__1\`, node, ctx, true), {slots: {'slot1': slot3(ctx2), 'slot2': slot4(ctx2)}});
|
||||
return block1([], [b6]);
|
||||
}
|
||||
}"
|
||||
@@ -473,7 +473,7 @@ exports[`slots dynamic t-slot call with default 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b6 = assign(component(\`Toggler\`, {}, key + \`__1\`, node, ctx), {slots: {'slot1': slot3(ctx2), 'slot2': slot4(ctx2)}});
|
||||
let b6 = assign(component(\`Toggler\`, {}, key + \`__1\`, node, ctx, true), {slots: {'slot1': slot3(ctx2), 'slot2': slot4(ctx2)}});
|
||||
return block1([], [b6]);
|
||||
}
|
||||
}"
|
||||
@@ -505,7 +505,7 @@ exports[`slots fun: two calls to the same slot 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -574,7 +574,7 @@ exports[`slots multiple roots are allowed in a default slot 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -614,7 +614,7 @@ exports[`slots multiple roots are allowed in a named slot 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b5 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'content': slot3(ctx2)}});
|
||||
let b5 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'content': slot3(ctx2)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -668,7 +668,7 @@ exports[`slots multiple slots containing components 3`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
return assign(component(\`B\`, {}, key + \`__1\`, node, ctx), {slots: {'s1': slot3(ctx2), 's2': slot5(ctx2)}});
|
||||
return assign(component(\`B\`, {}, key + \`__1\`, node, ctx, true), {slots: {'s1': slot3(ctx2), 's2': slot5(ctx2)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -707,7 +707,7 @@ exports[`slots named slot inside slot 2`] = `
|
||||
|
||||
const slot4 = ctx => (node, key) => {
|
||||
const ctx6 = capture(ctx);
|
||||
return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx), {slots: {'brol': slot7(ctx6)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx, true), {slots: {'brol': slot7(ctx6)}});
|
||||
}
|
||||
|
||||
const slot7 = ctx => (node, key) => {
|
||||
@@ -717,7 +717,7 @@ exports[`slots named slot inside slot 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'brol': slot3(ctx2), 'default': slot4(ctx2)}});
|
||||
let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'brol': slot3(ctx2), 'default': slot4(ctx2)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -757,7 +757,7 @@ exports[`slots named slot inside slot, part 3 2`] = `
|
||||
|
||||
const slot4 = ctx => (node, key) => {
|
||||
const ctx6 = capture(ctx);
|
||||
return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx), {slots: {'brol': slot7(ctx6)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx, true), {slots: {'brol': slot7(ctx6)}});
|
||||
}
|
||||
|
||||
const slot7 = ctx => (node, key) => {
|
||||
@@ -767,7 +767,7 @@ exports[`slots named slot inside slot, part 3 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'brol': slot3(ctx2), 'default': slot4(ctx2)}});
|
||||
let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'brol': slot3(ctx2), 'default': slot4(ctx2)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -850,7 +850,7 @@ exports[`slots named slots inside slot, again 2`] = `
|
||||
|
||||
const slot4 = ctx => (node, key) => {
|
||||
const ctx6 = capture(ctx);
|
||||
return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx), {slots: {'brol2': slot7(ctx6)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__5\`, node, ctx, true), {slots: {'brol2': slot7(ctx6)}});
|
||||
}
|
||||
|
||||
const slot7 = ctx => (node, key) => {
|
||||
@@ -860,7 +860,7 @@ exports[`slots named slots inside slot, again 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'brol1': slot3(ctx2), 'default': slot4(ctx2)}});
|
||||
let b5 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'brol1': slot3(ctx2), 'default': slot4(ctx2)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -920,7 +920,7 @@ exports[`slots nested slots in same template 4`] = `
|
||||
let block1 = createBlock(\`<span id=\\"parent\\"><block-child-0/></span>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return assign(component(\`Child2\`, {}, key + \`__3\`, node, ctx), {slots: {'default': slot4(ctx)}});
|
||||
return assign(component(\`Child2\`, {}, key + \`__3\`, node, ctx, true), {slots: {'default': slot4(ctx)}});
|
||||
}
|
||||
|
||||
const slot4 = ctx => (node, key) => {
|
||||
@@ -928,7 +928,7 @@ exports[`slots nested slots in same template 4`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b4 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b4 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b4]);
|
||||
}
|
||||
}"
|
||||
@@ -976,7 +976,7 @@ exports[`slots nested slots: evaluation context and parented relationship 3`] =
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`GrandChild\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`GrandChild\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -993,7 +993,7 @@ exports[`slots nested slots: evaluation context and parented relationship 4`] =
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -1052,7 +1052,7 @@ exports[`slots simple default slot 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -1081,7 +1081,7 @@ exports[`slots simple default slot, variation 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -1131,7 +1131,7 @@ exports[`slots slot and (inline) t-call 3`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1182,7 +1182,7 @@ exports[`slots slot and t-call 3`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1217,7 +1217,7 @@ exports[`slots slot and t-esc 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1270,7 +1270,7 @@ exports[`slots slot are properly rendered if inner props are changed 3`] = `
|
||||
const v1 = ctx['inc'];
|
||||
let d1 = [v1, ctx];
|
||||
let d2 = ctx['state'].val;
|
||||
let b3 = assign(component(\`GenericComponent\`, {}, key + \`__2\`, node, ctx), {slots: {'default': slot3(ctx)}});
|
||||
let b3 = assign(component(\`GenericComponent\`, {}, key + \`__2\`, node, ctx, true), {slots: {'default': slot3(ctx)}});
|
||||
return block1([d1, d2], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1307,7 +1307,7 @@ exports[`slots slot content is bound to caller 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -1350,7 +1350,7 @@ exports[`slots slot preserves properly parented relationship 3`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1408,7 +1408,7 @@ exports[`slots slot preserves properly parented relationship, even through t-cal
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1441,7 +1441,7 @@ exports[`slots slots and wrapper components 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Link\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Link\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -1480,7 +1480,7 @@ exports[`slots slots are rendered with proper context 2`] = `
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['state'].val;
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'footer': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'footer': slot3(ctx2)}});
|
||||
return block1([d1], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1525,7 +1525,7 @@ exports[`slots slots are rendered with proper context, part 2 2`] = `
|
||||
ctx[\`user\`] = v_block2[i1];
|
||||
let key1 = ctx['user'].id;
|
||||
const ctx2 = capture(ctx);
|
||||
let b7 = assign(component(\`Link\`, {to: '/user/'+ctx['user'].id}, key + \`__1__\${key1}\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b7 = assign(component(\`Link\`, {to: '/user/'+ctx['user'].id}, key + \`__1__\${key1}\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
c_block2[i1] = withKey(block3([], [b7]), key1);
|
||||
}
|
||||
let b2 = list(c_block2);
|
||||
@@ -1574,7 +1574,7 @@ exports[`slots slots are rendered with proper context, part 3 2`] = `
|
||||
let key1 = ctx['user'].id;
|
||||
setContextValue(ctx, \\"userdescr\\", 'User '+ctx['user'].name);
|
||||
const ctx2 = capture(ctx);
|
||||
let b5 = assign(component(\`Link\`, {to: '/user/'+ctx['user'].id}, key + \`__1__\${key1}\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b5 = assign(component(\`Link\`, {to: '/user/'+ctx['user'].id}, key + \`__1__\${key1}\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
c_block2[i1] = withKey(block3([], [b5]), key1);
|
||||
}
|
||||
let b2 = list(c_block2);
|
||||
@@ -1617,7 +1617,7 @@ exports[`slots slots are rendered with proper context, part 4 2`] = `
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"userdescr\\", 'User '+ctx['state'].user.name);
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Link\`, {to: '/user/'+ctx['state'].user.id}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Link\`, {to: '/user/'+ctx['state'].user.id}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1652,7 +1652,7 @@ exports[`slots slots in slots, with vars 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`B\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`B\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1678,7 +1678,7 @@ exports[`slots slots in slots, with vars 3`] = `
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"test\\", ctx['state'].name);
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`A\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`A\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1721,7 +1721,7 @@ exports[`slots slots in t-foreach and re-rendering 2`] = `
|
||||
ctx[\`n_index\`] = i1;
|
||||
let key1 = ctx['n_index'];
|
||||
const ctx2 = capture(ctx);
|
||||
c_block2[i1] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx), {slots: {'default': slot3(ctx2)}}), key1);
|
||||
c_block2[i1] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx, true), {slots: {'default': slot3(ctx2)}}), key1);
|
||||
}
|
||||
let b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
@@ -1775,7 +1775,7 @@ exports[`slots slots in t-foreach in t-foreach 2`] = `
|
||||
ctx[\`node2\`] = v_block6[i2];
|
||||
let key2 = ctx['node2'].key;
|
||||
const ctx2 = capture(ctx);
|
||||
c_block6[i2] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}__\${key2}\`, node, ctx), {slots: {'default': slot3(ctx2)}}), key2);
|
||||
c_block6[i2] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}__\${key2}\`, node, ctx, true), {slots: {'default': slot3(ctx2)}}), key2);
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
let b6 = list(c_block6);
|
||||
@@ -1828,7 +1828,7 @@ exports[`slots slots in t-foreach with t-set and re-rendering 2`] = `
|
||||
let key1 = ctx['n_index'];
|
||||
setContextValue(ctx, \\"dummy\\", ctx['n_index']);
|
||||
const ctx2 = capture(ctx);
|
||||
c_block2[i1] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx), {slots: {'default': slot3(ctx2)}}), key1);
|
||||
c_block2[i1] = withKey(assign(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx, true), {slots: {'default': slot3(ctx2)}}), key1);
|
||||
}
|
||||
let b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
@@ -1867,7 +1867,7 @@ exports[`slots t-debug on a t-set-slot (defining a slot) 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'content': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'content': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1906,7 +1906,7 @@ exports[`slots t-set t-value in a slot 2`] = `
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -1968,7 +1968,7 @@ exports[`slots t-slot in recursive templates 2`] = `
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
const ctx2 = capture(ctx);
|
||||
return assign(component(\`Wrapper\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
return assign(component(\`Wrapper\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -2027,7 +2027,7 @@ exports[`slots t-slot nested within another slot 4`] = `
|
||||
let block1 = createBlock(\`<span id=\\"c2\\"><block-child-0/></span>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return assign(component(\`Portal\`, {}, key + \`__3\`, node, ctx), {slots: {'default': slot4(ctx)}});
|
||||
return assign(component(\`Portal\`, {}, key + \`__3\`, node, ctx, true), {slots: {'default': slot4(ctx)}});
|
||||
}
|
||||
|
||||
const slot4 = ctx => (node, key) => {
|
||||
@@ -2035,7 +2035,7 @@ exports[`slots t-slot nested within another slot 4`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b4 = assign(component(\`Modal\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b4 = assign(component(\`Modal\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b4]);
|
||||
}
|
||||
}"
|
||||
@@ -2055,7 +2055,7 @@ exports[`slots t-slot nested within another slot 5`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -2090,7 +2090,7 @@ exports[`slots t-slot scope context 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Wrapper\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Wrapper\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -2109,7 +2109,7 @@ exports[`slots t-slot scope context 3`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -2174,7 +2174,7 @@ exports[`slots t-slot within dynamic t-call 4`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Slotted\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b3 = assign(component(\`Slotted\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -2221,7 +2221,7 @@ exports[`slots template can just return a slot 3`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`SlotComponent\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`SlotComponent\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -24,7 +24,7 @@ exports[`t-props basic use 2`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`Child\`, ctx['some'].obj, key + \`__1\`, node, ctx);
|
||||
let b2 = component(\`Child\`, Object.assign({}, ctx['some'].obj), key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
@@ -85,7 +85,7 @@ exports[`t-props t-props only 2`] = `
|
||||
let block1 = createBlock(\`<div><div><block-child-0/></div></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`Comp\`, ctx['state'], key + \`__1\`, node, ctx);
|
||||
let b2 = component(\`Comp\`, Object.assign({}, ctx['state']), key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -38,7 +38,7 @@ exports[`t-set slot setted value (with t-set) not accessible with t-esc 2`] = `
|
||||
setContextValue(ctx, \\"iter\\", 'source');
|
||||
let d1 = ctx['iter'];
|
||||
const ctx2 = capture(ctx);
|
||||
let b2 = assign(component(\`Childcomp\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
let b2 = assign(component(\`Childcomp\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
|
||||
let d2 = ctx['iter'];
|
||||
return block1([d1, d2], [b2]);
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@ describe("basics", () => {
|
||||
class SomeWidget extends Component {
|
||||
static template = xml`<div>content</div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(SomeWidget, document.createDocumentFragment() as any);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot mount component: the target is not a valid DOM element");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Cannot mount component: the target is not a valid DOM element");
|
||||
});
|
||||
|
||||
test("can mount a simple component with props", async () => {
|
||||
@@ -160,28 +160,28 @@ describe("basics", () => {
|
||||
static template = xml`<span>simple vnode</span>`;
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Test, null as any);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot mount component: the target is not a valid DOM element");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Cannot mount component: the target is not a valid DOM element");
|
||||
});
|
||||
|
||||
test("a component cannot be mounted in a detached node", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`<div/>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Test, document.createElement("div"));
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot mount a component on a detached dom node");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
||||
});
|
||||
|
||||
test("crashes if it cannot find a template", async () => {
|
||||
@@ -189,14 +189,14 @@ describe("basics", () => {
|
||||
static template = "wrongtemplate";
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Test, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe('Missing template: "wrongtemplate"');
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe('Missing template: "wrongtemplate"');
|
||||
});
|
||||
|
||||
test("class component with dynamic text", async () => {
|
||||
@@ -419,7 +419,7 @@ describe("basics", () => {
|
||||
expect(fixture.innerHTML).toBe("<div><span></span></div>");
|
||||
});
|
||||
|
||||
test("child can be updated", async () => {
|
||||
test.only("child can be updated", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.value"/>`;
|
||||
}
|
||||
|
||||
@@ -112,16 +112,20 @@ test("destroying/recreating a subwidget with different props (if start is not ov
|
||||
expect(steps).toEqual([
|
||||
"W:setup",
|
||||
"W:willStart",
|
||||
"W:render",
|
||||
"W:willRender",
|
||||
"W:rendered",
|
||||
"W:mounted",
|
||||
"W:render",
|
||||
"W:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"W:render",
|
||||
"W:rendered",
|
||||
"W:willRender",
|
||||
"Child:destroyed",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"W:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"W:willPatch",
|
||||
"Child:mounted",
|
||||
"W:patched",
|
||||
@@ -189,19 +193,24 @@ test("creating two async components, scenario 1", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"Parent:render",
|
||||
"Parent:rendered",
|
||||
"Parent:willRender",
|
||||
"ChildA:destroyed",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"ChildB:setup",
|
||||
"ChildB:willStart",
|
||||
"ChildB:render",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildB:willRender",
|
||||
"ChildB:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"Parent:willPatch",
|
||||
"ChildB:mounted",
|
||||
"ChildA:mounted",
|
||||
@@ -263,20 +272,26 @@ test("creating two async components, scenario 2", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"ChildA:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:willUpdateProps",
|
||||
"Parent:render",
|
||||
"Parent:rendered",
|
||||
"Parent:willRender",
|
||||
"ChildA:willUpdateProps",
|
||||
"ChildB:setup",
|
||||
"ChildB:willStart",
|
||||
"ChildB:render",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildB:willRender",
|
||||
"ChildB:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"Parent:willPatch",
|
||||
"ChildA:willPatch",
|
||||
"ChildB:mounted",
|
||||
@@ -336,20 +351,26 @@ test("creating two async components, scenario 3 (patching in the same frame)", a
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"ChildA:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:willUpdateProps",
|
||||
"Parent:render",
|
||||
"Parent:rendered",
|
||||
"Parent:willRender",
|
||||
"ChildA:willUpdateProps",
|
||||
"ChildB:setup",
|
||||
"ChildB:willStart",
|
||||
"ChildB:render",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildB:willRender",
|
||||
"ChildB:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"Parent:willPatch",
|
||||
"ChildA:willPatch",
|
||||
"ChildB:mounted",
|
||||
@@ -396,17 +417,22 @@ test("update a sub-component twice in the same frame", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"ChildA:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:willUpdateProps",
|
||||
"Parent:render",
|
||||
"Parent:rendered",
|
||||
"Parent:willRender",
|
||||
"ChildA:willUpdateProps",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"Parent:willPatch",
|
||||
"ChildA:willPatch",
|
||||
"ChildA:patched",
|
||||
@@ -447,15 +473,18 @@ test("update a sub-component twice in the same frame, 2", async () => {
|
||||
expect(steps.splice(0)).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildA:willRender",
|
||||
"render",
|
||||
"ChildA:rendered",
|
||||
"ChildA:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:willUpdateProps",
|
||||
"Parent:rendered",
|
||||
]);
|
||||
await nextMicroTick();
|
||||
// For an unknown reason, this test fails on windows without the next microtick. It works
|
||||
@@ -463,22 +492,27 @@ test("update a sub-component twice in the same frame, 2", async () => {
|
||||
// I do not see anything harmful in waiting an extra tick. But it is annoying to not
|
||||
// know what is different.
|
||||
await nextMicroTick();
|
||||
expect(steps.splice(0)).toEqual(["ChildA:render", "render"]);
|
||||
expect(steps.splice(0)).toEqual(["ChildA:willRender", "render", "ChildA:rendered"]);
|
||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
||||
parent.state.valA = 3;
|
||||
await nextMicroTick();
|
||||
await nextMicroTick();
|
||||
expect(steps.splice(0)).toEqual(["Parent:render", "ChildA:willUpdateProps"]);
|
||||
expect(steps.splice(0)).toEqual([
|
||||
"Parent:willRender",
|
||||
"ChildA:willUpdateProps",
|
||||
"Parent:rendered",
|
||||
]);
|
||||
await nextMicroTick();
|
||||
// same as above
|
||||
await nextMicroTick();
|
||||
expect(steps).toEqual(["ChildA:render", "render"]);
|
||||
expect(steps).toEqual(["ChildA:willRender", "render", "ChildA:rendered"]);
|
||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
|
||||
expect(steps).toEqual([
|
||||
"ChildA:render",
|
||||
"ChildA:willRender",
|
||||
"render",
|
||||
"ChildA:rendered",
|
||||
"Parent:willPatch",
|
||||
"ChildA:willPatch",
|
||||
"ChildA:patched",
|
||||
@@ -509,7 +543,7 @@ test("properly behave when destroyed/unmounted while rendering ", async () => {
|
||||
}
|
||||
|
||||
class Child extends Component {
|
||||
static template = xml`<div><SubChild /></div>`;
|
||||
static template = xml`<div><SubChild val="props.val"/></div>`;
|
||||
static components = { SubChild };
|
||||
setup() {
|
||||
useLogLifecycle(steps);
|
||||
@@ -548,21 +582,27 @@ test("properly behave when destroyed/unmounted while rendering ", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"SubChild:setup",
|
||||
"SubChild:willStart",
|
||||
"SubChild:render",
|
||||
"Child:rendered",
|
||||
"SubChild:willRender",
|
||||
"SubChild:rendered",
|
||||
"SubChild:mounted",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"SubChild:willUpdateProps",
|
||||
"Parent:render",
|
||||
"Child:rendered",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willUnmount",
|
||||
"SubChild:willUnmount",
|
||||
@@ -612,16 +652,20 @@ test("rendering component again in next microtick", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:render",
|
||||
"Parent:rendered",
|
||||
"Parent:willRender",
|
||||
"Child:destroyed",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:mounted",
|
||||
"Parent:patched",
|
||||
@@ -689,23 +733,30 @@ test("concurrent renderings scenario 1", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:setup",
|
||||
"ComponentC:willStart",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentC:rendered",
|
||||
"ComponentC:mounted",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentB:render",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentA:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentC:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentC:willPatch",
|
||||
@@ -774,23 +825,30 @@ test("concurrent renderings scenario 2", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:setup",
|
||||
"ComponentC:willStart",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentC:rendered",
|
||||
"ComponentC:mounted",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentC:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentC:willPatch",
|
||||
@@ -858,23 +916,30 @@ test("concurrent renderings scenario 2bis", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:setup",
|
||||
"ComponentC:willStart",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentC:rendered",
|
||||
"ComponentC:mounted",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentC:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentC:willPatch",
|
||||
@@ -964,29 +1029,38 @@ test("concurrent renderings scenario 3", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:setup",
|
||||
"ComponentC:willStart",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:setup",
|
||||
"ComponentD:willStart",
|
||||
"ComponentD:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentD:willRender",
|
||||
"ComponentD:rendered",
|
||||
"ComponentD:mounted",
|
||||
"ComponentC:mounted",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentC:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:willUpdateProps",
|
||||
"ComponentD:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentD:willRender",
|
||||
"ComponentD:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentC:willPatch",
|
||||
@@ -1078,29 +1152,38 @@ test("concurrent renderings scenario 4", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:setup",
|
||||
"ComponentC:willStart",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:setup",
|
||||
"ComponentD:willStart",
|
||||
"ComponentD:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentD:willRender",
|
||||
"ComponentD:rendered",
|
||||
"ComponentD:mounted",
|
||||
"ComponentC:mounted",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentC:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:willUpdateProps",
|
||||
"ComponentD:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentD:willRender",
|
||||
"ComponentD:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentC:willPatch",
|
||||
@@ -1164,17 +1247,22 @@ test("concurrent renderings scenario 5", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentA:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentB:patched",
|
||||
@@ -1235,17 +1323,22 @@ test("concurrent renderings scenario 6", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentA:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentB:patched",
|
||||
@@ -1294,15 +1387,19 @@ test("concurrent renderings scenario 7", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentB:patched",
|
||||
@@ -1354,15 +1451,19 @@ test("concurrent renderings scenario 8", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentB:patched",
|
||||
@@ -1446,30 +1547,40 @@ test("concurrent renderings scenario 9", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentC:setup",
|
||||
"ComponentC:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentC:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:setup",
|
||||
"ComponentD:willStart",
|
||||
"ComponentD:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentD:willRender",
|
||||
"ComponentD:rendered",
|
||||
"ComponentD:mounted",
|
||||
"ComponentC:mounted",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentC:willUpdateProps",
|
||||
"ComponentC:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:willUpdateProps",
|
||||
"ComponentD:render",
|
||||
"ComponentC:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentD:willRender",
|
||||
"ComponentD:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentD:willUpdateProps",
|
||||
"ComponentD:render",
|
||||
"ComponentB:render",
|
||||
"ComponentC:rendered",
|
||||
"ComponentD:willRender",
|
||||
"ComponentD:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentC:willPatch",
|
||||
@@ -1554,22 +1665,28 @@ test("concurrent renderings scenario 10", async () => {
|
||||
expect(steps).toEqual([
|
||||
"ComponentA:setup",
|
||||
"ComponentA:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:setup",
|
||||
"ComponentB:willStart",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentB:rendered",
|
||||
"ComponentB:mounted",
|
||||
"ComponentA:mounted",
|
||||
"ComponentB:render",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:setup",
|
||||
"ComponentC:willStart",
|
||||
"ComponentA:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentA:willRender",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentB:render",
|
||||
"ComponentA:rendered",
|
||||
"ComponentB:willRender",
|
||||
"ComponentC:destroyed",
|
||||
"ComponentC:setup",
|
||||
"ComponentC:willStart",
|
||||
"ComponentC:render",
|
||||
"ComponentB:rendered",
|
||||
"ComponentC:willRender",
|
||||
"ComponentC:rendered",
|
||||
"ComponentA:willPatch",
|
||||
"ComponentB:willPatch",
|
||||
"ComponentC:mounted",
|
||||
@@ -1624,15 +1741,19 @@ test("concurrent renderings scenario 11", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
@@ -1697,17 +1818,22 @@ test("concurrent renderings scenario 12", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Parent:render",
|
||||
"Parent:rendered",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
@@ -1761,28 +1887,31 @@ test("concurrent renderings scenario 13", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Child:render",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
"Parent:render",
|
||||
"Child:willUpdateProps",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:mounted",
|
||||
"Child:patched",
|
||||
"Parent:patched",
|
||||
"Child:render",
|
||||
"Child:render",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
"Child:willPatch",
|
||||
@@ -1856,24 +1985,32 @@ test("concurrent renderings scenario 14", async () => {
|
||||
expect(steps).toEqual([
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:render",
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"B:render",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"C:setup",
|
||||
"C:willStart",
|
||||
"C:render",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"C:mounted",
|
||||
"B:mounted",
|
||||
"A:mounted",
|
||||
"A:render",
|
||||
"A:willRender",
|
||||
"B:willUpdateProps",
|
||||
"B:render",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"C:willUpdateProps",
|
||||
"C:render",
|
||||
"B:render",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"B:willRender",
|
||||
"C:willUpdateProps",
|
||||
"C:render",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"A:willPatch",
|
||||
"B:willPatch",
|
||||
"C:willPatch",
|
||||
@@ -1955,24 +2092,32 @@ test("concurrent renderings scenario 15", async () => {
|
||||
expect(steps).toEqual([
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:render",
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"B:render",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"C:setup",
|
||||
"C:willStart",
|
||||
"C:render",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"C:mounted",
|
||||
"B:mounted",
|
||||
"A:mounted",
|
||||
"A:render",
|
||||
"A:willRender",
|
||||
"B:willUpdateProps",
|
||||
"B:render",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"C:willUpdateProps",
|
||||
"C:render",
|
||||
"B:render",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"B:willRender",
|
||||
"C:willUpdateProps",
|
||||
"C:render",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"A:willPatch",
|
||||
"B:willPatch",
|
||||
"C:willPatch",
|
||||
@@ -2063,30 +2208,39 @@ test.skip("concurrent renderings scenario 16", async () => {
|
||||
expect(steps).toEqual([
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:render",
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"B:render",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"C:setup",
|
||||
"C:willStart",
|
||||
"C:render",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"C:mounted",
|
||||
"B:mounted",
|
||||
"A:mounted",
|
||||
"A:render",
|
||||
"A:willRender",
|
||||
"B:willUpdateProps",
|
||||
"B:render",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"B:rendered",
|
||||
"C:willUpdateProps",
|
||||
"C:render",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"D:setup",
|
||||
"D:willStart",
|
||||
"B:render",
|
||||
"B:willRender",
|
||||
"B:rendered",
|
||||
"C:willUpdateProps",
|
||||
"C:render",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"D:destroyed",
|
||||
"D:setup",
|
||||
"D:willStart",
|
||||
"D:render",
|
||||
"D:willRender",
|
||||
"D:rendered",
|
||||
"A:willPatch",
|
||||
"B:willPatch",
|
||||
"C:willPatch",
|
||||
@@ -2209,9 +2363,11 @@ test("change state and call manually render: no unnecessary rendering", async ()
|
||||
expect(steps).toEqual([
|
||||
"Test:setup",
|
||||
"Test:willStart",
|
||||
"Test:render",
|
||||
"Test:willRender",
|
||||
"Test:rendered",
|
||||
"Test:mounted",
|
||||
"Test:render",
|
||||
"Test:willRender",
|
||||
"Test:rendered",
|
||||
"Test:willPatch",
|
||||
"Test:patched",
|
||||
]);
|
||||
@@ -2242,7 +2398,13 @@ test("changing state before first render does not trigger a render", async () =>
|
||||
await nextTick();
|
||||
expect(renders).toBe(1);
|
||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||
expect(steps).toEqual(["TestW:setup", "TestW:willStart", "TestW:render", "TestW:mounted"]);
|
||||
expect(steps).toEqual([
|
||||
"TestW:setup",
|
||||
"TestW:willStart",
|
||||
"TestW:willRender",
|
||||
"TestW:rendered",
|
||||
"TestW:mounted",
|
||||
]);
|
||||
Object.freeze(steps);
|
||||
});
|
||||
|
||||
@@ -2278,17 +2440,25 @@ test("changing state before first render does not trigger a render (with parent)
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div></div>");
|
||||
expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]);
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
steps.splice(0);
|
||||
parent.state.flag = true;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><div>3</div></div>");
|
||||
expect(renders).toBe(1);
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"TestW:setup",
|
||||
"TestW:willStart",
|
||||
"TestW:render",
|
||||
"Parent:rendered",
|
||||
"TestW:willRender",
|
||||
"TestW:rendered",
|
||||
"Parent:willPatch",
|
||||
"TestW:mounted",
|
||||
"Parent:patched",
|
||||
@@ -2342,10 +2512,12 @@ test("two renderings initiated between willPatch and patched", async () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Panel:setup",
|
||||
"Panel:willStart",
|
||||
"Panel:render",
|
||||
"Parent:rendered",
|
||||
"Panel:willRender",
|
||||
"Panel:rendered",
|
||||
"Panel:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -2356,10 +2528,12 @@ test("two renderings initiated between willPatch and patched", async () => {
|
||||
expect(fixture.innerHTML).toBe("<div><abc>Panel2</abc></div>");
|
||||
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Panel:setup",
|
||||
"Panel:willStart",
|
||||
"Panel:render",
|
||||
"Parent:rendered",
|
||||
"Panel:willRender",
|
||||
"Panel:rendered",
|
||||
"Parent:willPatch",
|
||||
"Panel:willUnmount",
|
||||
"Panel:destroyed",
|
||||
@@ -2373,7 +2547,8 @@ test("two renderings initiated between willPatch and patched", async () => {
|
||||
expect(fixture.innerHTML).toBe("<div></div>");
|
||||
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Panel:willUnmount",
|
||||
"Panel:destroyed",
|
||||
|
||||
@@ -35,18 +35,18 @@ describe("basics", () => {
|
||||
expect(fixture.innerHTML).toBe("<div><div>heyfalse</div></div>");
|
||||
parent.state.flag = true;
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await parent.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect(status(parent)).toBe("destroyed");
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("display a nice error if it cannot find component", async () => {
|
||||
@@ -58,14 +58,14 @@ describe("basics", () => {
|
||||
static template = xml`<SomeMispelledComponent />`;
|
||||
static components = { SomeComponent };
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
});
|
||||
@@ -108,16 +108,16 @@ describe("errors and promises", () => {
|
||||
static template = xml`<div><t t-esc="this.will.crash"/></div>`;
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -136,14 +136,14 @@ describe("errors and promises", () => {
|
||||
}
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("boom");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("boom");
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -166,14 +166,14 @@ describe("errors and promises", () => {
|
||||
|
||||
const app = await mount(App, fixture);
|
||||
app.val = 4;
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await app.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("boom");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("boom");
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -196,14 +196,14 @@ describe("errors and promises", () => {
|
||||
|
||||
const app = await mount(App, fixture);
|
||||
app.val = 4;
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await app.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("boom");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("boom");
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -222,16 +222,16 @@ describe("errors and promises", () => {
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -249,16 +249,16 @@ describe("errors and promises", () => {
|
||||
const app = await mount(App, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div></div>");
|
||||
app.flag = true;
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await app.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -273,20 +273,20 @@ describe("errors and promises", () => {
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("errors in mounted and in willUnmount", async () => {
|
||||
expect.assertions(2); // apparently this expect count in assertions...
|
||||
expect.assertions(2);
|
||||
class Example extends Component {
|
||||
static template = xml`<div/>`;
|
||||
val: any;
|
||||
@@ -305,7 +305,7 @@ describe("errors and promises", () => {
|
||||
try {
|
||||
await mount(Example, fixture);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Error in mounted");
|
||||
expect((e as Error).message).toBe("Error in mounted");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
onWillUnmount,
|
||||
onPatched,
|
||||
onWillUpdateProps,
|
||||
onRender,
|
||||
onWillRender,
|
||||
} from "../../src/component/lifecycle_hooks";
|
||||
import { status } from "../../src/component/status";
|
||||
import { xml } from "../../src/tags";
|
||||
@@ -385,20 +385,25 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
"Parent:patched",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willUnmount",
|
||||
"Child:destroyed",
|
||||
@@ -561,10 +566,12 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -609,20 +616,29 @@ describe("lifecycle hooks", () => {
|
||||
const app = new App(Parent);
|
||||
const parent = await app.mount(fixture);
|
||||
|
||||
expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]);
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
|
||||
steps.splice(0);
|
||||
|
||||
parent.state.hasChild = true;
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"GrandChild:setup",
|
||||
"GrandChild:willStart",
|
||||
"GrandChild:render",
|
||||
"Child:rendered",
|
||||
"GrandChild:willRender",
|
||||
"GrandChild:rendered",
|
||||
"Parent:willPatch",
|
||||
"GrandChild:mounted",
|
||||
"Child:mounted",
|
||||
@@ -672,7 +688,13 @@ describe("lifecycle hooks", () => {
|
||||
const app = new App(Parent);
|
||||
const parent = await app.mount(fixture);
|
||||
|
||||
expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]);
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
|
||||
steps.splice(0);
|
||||
|
||||
@@ -717,7 +739,13 @@ describe("lifecycle hooks", () => {
|
||||
const app = new App(Parent);
|
||||
const parent = await app.mount(fixture);
|
||||
|
||||
expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]);
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
|
||||
steps.splice(0);
|
||||
|
||||
@@ -725,12 +753,14 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"GrandChild:setup",
|
||||
"GrandChild:willStart",
|
||||
"Child:rendered",
|
||||
]);
|
||||
|
||||
steps.splice(0);
|
||||
@@ -769,10 +799,12 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -783,7 +815,8 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willUnmount",
|
||||
"Child:destroyed",
|
||||
@@ -816,10 +849,12 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -830,9 +865,11 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
@@ -841,7 +878,7 @@ describe("lifecycle hooks", () => {
|
||||
Object.freeze(steps);
|
||||
});
|
||||
|
||||
test("onRender", async () => {
|
||||
test("onWillRender", async () => {
|
||||
let steps: string[] = [];
|
||||
const def = makeDeferred();
|
||||
|
||||
@@ -852,7 +889,7 @@ describe("lifecycle hooks", () => {
|
||||
setup() {
|
||||
useLogLifecycle(steps);
|
||||
onWillUpdateProps(() => def);
|
||||
onRender(() => (this.visibleState = this.state.value));
|
||||
onWillRender(() => (this.visibleState = this.state.value));
|
||||
}
|
||||
increment() {
|
||||
this.state.value++;
|
||||
@@ -862,8 +899,9 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<Child />`;
|
||||
<Child someValue="state.value" />`;
|
||||
static components = { Child };
|
||||
state = useState({ value: 1 });
|
||||
setup() {
|
||||
useLogLifecycle(steps);
|
||||
}
|
||||
@@ -873,7 +911,7 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
expect(fixture.innerHTML).toBe("<button>1</button>");
|
||||
|
||||
parent.render(); // to block child render
|
||||
parent.state.value++; // to block child render
|
||||
await nextTick();
|
||||
|
||||
fixture.querySelector("button")!.click();
|
||||
@@ -891,17 +929,21 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Parent:rendered",
|
||||
"inc:1",
|
||||
"inc:1",
|
||||
"Child:render",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
@@ -993,19 +1035,24 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:render",
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"C:setup",
|
||||
"C:willStart",
|
||||
"B:render",
|
||||
"C:render",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"D:setup",
|
||||
"D:willStart",
|
||||
"E:setup",
|
||||
"E:willStart",
|
||||
"D:render",
|
||||
"E:render",
|
||||
"C:rendered",
|
||||
"D:willRender",
|
||||
"D:rendered",
|
||||
"E:willRender",
|
||||
"E:rendered",
|
||||
"E:mounted",
|
||||
"D:mounted",
|
||||
"C:mounted",
|
||||
@@ -1017,19 +1064,18 @@ describe("lifecycle hooks", () => {
|
||||
steps.splice(0);
|
||||
c!.state.flag = false;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe(`<div>A<div>B</div><div>C<div>D</div><div>F</div></div></div>`);
|
||||
expect(steps).toEqual([
|
||||
"C:render",
|
||||
"D:willUpdateProps",
|
||||
"C:willRender",
|
||||
"F:setup",
|
||||
"F:willStart",
|
||||
"D:render",
|
||||
"F:render",
|
||||
"C:rendered",
|
||||
"F:willRender",
|
||||
"F:rendered",
|
||||
"C:willPatch",
|
||||
"D:willPatch",
|
||||
"E:willUnmount",
|
||||
"E:destroyed",
|
||||
"F:mounted",
|
||||
"D:patched",
|
||||
"C:patched",
|
||||
]);
|
||||
});
|
||||
@@ -1062,21 +1108,26 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willUnmount",
|
||||
"Child:destroyed",
|
||||
"Parent:patched",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:mounted",
|
||||
"Parent:patched",
|
||||
|
||||
@@ -46,23 +46,23 @@ describe("props validation", () => {
|
||||
static components = { SubComp };
|
||||
static template = xml`<div><SubComp /></div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'message' (component 'SubComp')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'message' (component 'SubComp')`);
|
||||
error = undefined;
|
||||
|
||||
try {
|
||||
await mountApp(Parent, false);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
});
|
||||
|
||||
test("props: list of strings", async () => {
|
||||
@@ -75,14 +75,14 @@ describe("props validation", () => {
|
||||
static template = xml`<div><SubComp /></div>`;
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'message' (component 'SubComp')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'message' (component 'SubComp')`);
|
||||
});
|
||||
|
||||
test("validate simple types", async () => {
|
||||
@@ -108,32 +108,32 @@ describe("props validation", () => {
|
||||
};
|
||||
(Parent as any).components = { SubComp };
|
||||
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
props = {};
|
||||
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'p' (component '_a')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'p' (component '_a')`);
|
||||
error = undefined;
|
||||
props = { p: test.ok };
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
props = { p: test.ko };
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -159,31 +159,31 @@ describe("props validation", () => {
|
||||
static template = xml`<div>hey</div>`;
|
||||
};
|
||||
(Parent as any).components = { SubComp };
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
props = {};
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'p' (component '_a')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'p' (component '_a')`);
|
||||
error = undefined;
|
||||
props = { p: test.ok };
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
props = { p: test.ko };
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -199,30 +199,30 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: "string" };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: true };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: 1 };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate an optional props", async () => {
|
||||
@@ -237,30 +237,30 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: "key" };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = {};
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: 1 };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate an array with given primitive type", async () => {
|
||||
@@ -275,35 +275,35 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: [] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: ["string"] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: [1] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
error = undefined;
|
||||
try {
|
||||
props = { p: ["string", 1] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -319,37 +319,37 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: [] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: ["string"] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: [false, true, "string"] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: [true, 1] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate an object with simple shape", async () => {
|
||||
@@ -366,40 +366,40 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: { id: 1, url: "url" } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: { id: 1, url: "url", extra: true } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid prop 'p' in component SubComp (unknown prop 'extra')");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid prop 'p' in component SubComp (unknown prop 'extra')");
|
||||
try {
|
||||
props = { p: { id: "1", url: "url" } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
error = undefined;
|
||||
try {
|
||||
props = { p: { id: 1 } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate recursively complicated prop def", async () => {
|
||||
@@ -422,30 +422,30 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: { id: 1, url: true } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: { id: 1, url: [12] } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: { id: 1, url: [12, true] } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate optional attributes in nested sub props", () => {
|
||||
@@ -462,20 +462,20 @@ describe("props validation", () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
validateProps(TestComponent as any, { myprop: [{}] });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
validateProps(TestComponent as any, { myprop: [{ a: 1 }] });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(
|
||||
"Invalid prop 'myprop' in component TestComponent (unknown prop 'a')"
|
||||
);
|
||||
});
|
||||
@@ -488,20 +488,20 @@ describe("props validation", () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
validateProps(TestComponent as any, { size: "small" });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
validateProps(TestComponent as any, { size: "abcdef" });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'size' in component 'TestComponent'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'size' in component 'TestComponent'");
|
||||
});
|
||||
|
||||
test("can validate with a custom validator, and a type", () => {
|
||||
@@ -514,30 +514,30 @@ describe("props validation", () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
try {
|
||||
validateProps(TestComponent as any, { n: 3 });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
expect(validator).toBeCalledTimes(1);
|
||||
try {
|
||||
validateProps(TestComponent as any, { n: "str" });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(validator).toBeCalledTimes(1);
|
||||
error = null;
|
||||
error = undefined;
|
||||
try {
|
||||
validateProps(TestComponent as any, { n: 100 });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(validator).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -647,18 +647,18 @@ describe("props validation", () => {
|
||||
static template = xml`<div><SubComp/></div>`;
|
||||
static components = { SubComp };
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Missing props 'p' (component 'SubComp')");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Missing props 'p' (component 'SubComp')");
|
||||
});
|
||||
|
||||
test("props are validated whenever component is updated", async () => {
|
||||
let error;
|
||||
let error: Error;
|
||||
class SubComp extends Component {
|
||||
static props = { p: { type: Number } };
|
||||
static template = xml`<div><t t-esc="props.p"/></div>`;
|
||||
@@ -674,10 +674,10 @@ describe("props validation", () => {
|
||||
(app as any).root.component.state.p = undefined;
|
||||
await (app as any).root.component.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Missing props 'p' (component 'SubComp')");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Missing props 'p' (component 'SubComp')");
|
||||
});
|
||||
|
||||
test("default values are applied before validating props at update", async () => {
|
||||
@@ -712,14 +712,14 @@ describe("props validation", () => {
|
||||
static components = { Child };
|
||||
static template = xml`<div><Child/></div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Missing props 'mandatory' (component 'Child')");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Missing props 'mandatory' (component 'Child')");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
Component,
|
||||
mount,
|
||||
onPatched,
|
||||
onRender,
|
||||
onWillRender,
|
||||
onWillPatch,
|
||||
onWillUnmount,
|
||||
useState,
|
||||
@@ -37,7 +37,7 @@ describe("reactivity in lifecycle", () => {
|
||||
static template = xml`<div><t t-esc="state.a"/></div>`;
|
||||
state = useState({ a: 5, b: 7 });
|
||||
setup() {
|
||||
onRender(() => n++);
|
||||
onWillRender(() => n++);
|
||||
}
|
||||
}
|
||||
const comp = await mount(Comp, fixture);
|
||||
@@ -61,7 +61,7 @@ describe("reactivity in lifecycle", () => {
|
||||
`;
|
||||
state = useState({ n: 2 });
|
||||
setup() {
|
||||
onRender(() => {
|
||||
onWillRender(() => {
|
||||
steps.push("render");
|
||||
});
|
||||
onWillPatch(() => {
|
||||
@@ -132,7 +132,7 @@ describe("reactivity in lifecycle", () => {
|
||||
state = useState({ val: 1 });
|
||||
setup() {
|
||||
STATE = this.state;
|
||||
onRender(() => {
|
||||
onWillRender(() => {
|
||||
steps.push(this.state.val);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Component, mount, onRendered, useState } from "../../src";
|
||||
import { xml } from "../../src/tags";
|
||||
import { makeTestFixture, snapshotEverything, nextTick } from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("rendering semantics", () => {
|
||||
test("can render a parent without rendering child", async () => {
|
||||
let childN = 0;
|
||||
let parentN = 0;
|
||||
class Child extends Component {
|
||||
static template = xml`child`;
|
||||
setup() {
|
||||
onRendered(() => childN++);
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<t t-esc="state.value"/>
|
||||
<Child/>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
state = useState({ value: "A" });
|
||||
setup() {
|
||||
onRendered(() => parentN++);
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("Achild");
|
||||
expect(parentN).toBe(1);
|
||||
expect(childN).toBe(1);
|
||||
|
||||
parent.state.value = "B";
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("Bchild");
|
||||
expect(parentN).toBe(2);
|
||||
expect(childN).toBe(1);
|
||||
});
|
||||
|
||||
test("can force a render to update sub tree", async () => {
|
||||
let childN = 0;
|
||||
let parentN = 0;
|
||||
class Child extends Component {
|
||||
static template = xml`child`;
|
||||
setup() {
|
||||
onRendered(() => childN++);
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<t t-esc="state.value"/>
|
||||
<Child/>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
state = { value: "A" };
|
||||
setup() {
|
||||
onRendered(() => parentN++);
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("Achild");
|
||||
expect(parentN).toBe(1);
|
||||
expect(childN).toBe(1);
|
||||
|
||||
parent.state.value = "B";
|
||||
parent.render(true);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("Bchild");
|
||||
expect(parentN).toBe(2);
|
||||
expect(childN).toBe(2);
|
||||
});
|
||||
|
||||
test("blabla", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.a.b"/>`;
|
||||
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<Child a="state"/>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
state = useState({ b: 1 });
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("1");
|
||||
|
||||
parent.state.b = 3;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("3");
|
||||
});
|
||||
});
|
||||
@@ -338,16 +338,16 @@ describe("style and class handling", () => {
|
||||
static template = xml`<Child class="'a'"/>`;
|
||||
static components = { Child };
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(ParentWidget, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,14 +87,14 @@ describe("styles and component", () => {
|
||||
static template = xml`<div class="app">text</div>`;
|
||||
static style = `.app {color: red;}`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(
|
||||
"Invalid css stylesheet for component 'App'. Did you forget to use the 'css' tag helper?"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -35,10 +35,12 @@ describe("t-component", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -80,16 +82,20 @@ describe("t-component", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"ChildA:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildB:setup",
|
||||
"ChildB:willStart",
|
||||
"ChildB:render",
|
||||
"Parent:rendered",
|
||||
"ChildB:willRender",
|
||||
"ChildB:rendered",
|
||||
"Parent:willPatch",
|
||||
"ChildA:willUnmount",
|
||||
"ChildA:destroyed",
|
||||
@@ -199,14 +205,14 @@ describe("t-component", () => {
|
||||
static components = { Child };
|
||||
static template = xml`<div><div t-component="Child"/></div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(
|
||||
`Directive 't-component' can only be used on <t> nodes (used on a <div>)`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -81,13 +81,16 @@ describe("list of components", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
|
||||
@@ -57,14 +57,14 @@ describe("t-model directive", () => {
|
||||
</div>`;
|
||||
state = useState({ text: "" });
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(SomeComponent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Invalid t-model expression: "state" (it should be assignable)`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Invalid t-model expression: "state" (it should be assignable)`);
|
||||
});
|
||||
|
||||
test("basic use, on another key in component", async () => {
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("t-props", () => {
|
||||
a: "first",
|
||||
b: "second",
|
||||
});
|
||||
a = "third";
|
||||
a? = "third";
|
||||
}
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.textContent).toBe("thirdsecond");
|
||||
@@ -65,7 +65,7 @@ describe("t-props", () => {
|
||||
`;
|
||||
setup() {
|
||||
expect(this.props).toEqual({ a: 1, b: 2 });
|
||||
expect(this.props).toBe(props);
|
||||
expect(this.props).not.toBe(props);
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
|
||||
+8
-3
@@ -4,13 +4,14 @@ import {
|
||||
onDestroyed,
|
||||
onMounted,
|
||||
onPatched,
|
||||
onRender,
|
||||
onWillRender,
|
||||
onWillPatch,
|
||||
onWillStart,
|
||||
onWillUnmount,
|
||||
onWillUpdateProps,
|
||||
status,
|
||||
useComponent,
|
||||
onRendered,
|
||||
} from "../src";
|
||||
import { BDom } from "../src/blockdom";
|
||||
import { blockDom } from "../src";
|
||||
@@ -181,8 +182,12 @@ export function useLogLifecycle(steps: string[]) {
|
||||
steps.push(`${name}:willUpdateProps`);
|
||||
});
|
||||
|
||||
onRender(() => {
|
||||
steps.push(`${name}:render`);
|
||||
onWillRender(() => {
|
||||
steps.push(`${name}:willRender`);
|
||||
});
|
||||
|
||||
onRendered(() => {
|
||||
steps.push(`${name}:rendered`);
|
||||
});
|
||||
|
||||
onWillPatch(() => {
|
||||
|
||||
@@ -30,7 +30,7 @@ exports[`Memo if no prop change, prevent renderings from above 2`] = `
|
||||
let b2 = text(ctx['state'].a);
|
||||
let b3 = text(ctx['state'].b);
|
||||
let b4 = text(ctx['state'].c);
|
||||
let b9 = assign(component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b9 = assign(component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return multi([b2, b3, b4, b9]);
|
||||
}
|
||||
}"
|
||||
@@ -61,7 +61,7 @@ exports[`Memo if no props, prevent renderings from above 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||
let b4 = assign(component(\`Memo\`, {}, key + \`__2\`, node, ctx), {slots: {'default': slot3(ctx)}});
|
||||
let b4 = assign(component(\`Memo\`, {}, key + \`__2\`, node, ctx, true), {slots: {'default': slot3(ctx)}});
|
||||
return multi([b2, b4]);
|
||||
}
|
||||
}"
|
||||
@@ -80,7 +80,7 @@ exports[`Memo if no props, prevent renderings from above (work with simple html)
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = text(ctx['state'].value);
|
||||
let b4 = assign(component(\`Memo\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b4 = assign(component(\`Memo\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return multi([b2, b4]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -28,7 +28,7 @@ exports[`Portal Portal composed with t-slot 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -47,7 +47,7 @@ exports[`Portal Portal composed with t-slot 3`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -68,7 +68,7 @@ exports[`Portal basic use of portal 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -106,7 +106,7 @@ exports[`Portal conditional use of Portal (with sub Component) 2`] = `
|
||||
let b2,b4;
|
||||
b2 = block2();
|
||||
if (ctx['state'].hasPortal) {
|
||||
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
return multi([b2, b4]);
|
||||
}
|
||||
@@ -131,7 +131,7 @@ exports[`Portal conditional use of Portal 1`] = `
|
||||
let b2,b4;
|
||||
b2 = block2();
|
||||
if (ctx['state'].hasPortal) {
|
||||
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
return multi([b2, b4]);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ exports[`Portal lifecycle hooks of portal sub component are properly called 2`]
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3;
|
||||
if (ctx['state'].hasChild) {
|
||||
b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
}
|
||||
return block1([], [b3]);
|
||||
}
|
||||
@@ -196,7 +196,7 @@ exports[`Portal portal could have dynamically no content 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b4]);
|
||||
}
|
||||
}"
|
||||
@@ -231,7 +231,7 @@ exports[`Portal portal destroys on crash 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -266,7 +266,7 @@ exports[`Portal portal with child and props 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -295,7 +295,7 @@ exports[`Portal portal with dynamic body 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -319,7 +319,7 @@ exports[`Portal portal with many children 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -343,7 +343,7 @@ exports[`Portal portal with no content 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b4]);
|
||||
}
|
||||
}"
|
||||
@@ -363,7 +363,7 @@ exports[`Portal portal with only text as content 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -384,7 +384,7 @@ exports[`Portal portal with target not in dom 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#does-not-exist'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#does-not-exist'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -418,7 +418,7 @@ exports[`Portal portal's parent's env is not polluted 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -439,7 +439,7 @@ exports[`Portal with target in template (after portal) 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -460,7 +460,7 @@ exports[`Portal with target in template (before portal) 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -481,7 +481,7 @@ exports[`Portal: Props validation target is mandatory 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -502,7 +502,7 @@ exports[`Portal: Props validation target is not list 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: ['body']}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: ['body']}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -537,7 +537,7 @@ exports[`Portal: UI/UX focus is kept across re-renders 2`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
|
||||
+16
-16
@@ -160,15 +160,15 @@ describe("Portal", () => {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("invalid portal target");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("invalid portal target");
|
||||
expect(fixture.innerHTML).toBe(`<div></div>`);
|
||||
});
|
||||
|
||||
@@ -396,16 +396,16 @@ describe("Portal", () => {
|
||||
const parent = await mount(Parent, fixture);
|
||||
parent.state.error = true;
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await parent.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("portal's parent's env is not polluted", async () => {
|
||||
@@ -519,16 +519,16 @@ describe("Portal: Props validation", () => {
|
||||
</Portal>
|
||||
</div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let app = new App(Parent);
|
||||
app.configure({ dev: true });
|
||||
try {
|
||||
await app.mount(fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'target' (component 'Portal')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'target' (component 'Portal')`);
|
||||
console.info = consoleInfo;
|
||||
});
|
||||
|
||||
@@ -544,16 +544,16 @@ describe("Portal: Props validation", () => {
|
||||
</Portal>
|
||||
</div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let app = new App(Parent);
|
||||
app.configure({ dev: true });
|
||||
try {
|
||||
await app.mount(fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
||||
console.info = consoleInfo;
|
||||
});
|
||||
});
|
||||
|
||||
+498
-460
File diff suppressed because it is too large
Load Diff
+19
-1
@@ -1,4 +1,5 @@
|
||||
import { EventBus } from "../src/utils";
|
||||
import { batched, EventBus } from "../src/utils";
|
||||
import { nextMicroTick } from "./helpers";
|
||||
|
||||
describe("event bus behaviour", () => {
|
||||
test("can subscribe and be notified", () => {
|
||||
@@ -33,3 +34,20 @@ describe("event bus behaviour", () => {
|
||||
bus.trigger("event", "hello world");
|
||||
});
|
||||
});
|
||||
|
||||
describe("batched", () => {
|
||||
test("callback is called only once after operations", async () => {
|
||||
let n = 0;
|
||||
let fn = batched(() => n++);
|
||||
|
||||
expect(n).toBe(0);
|
||||
fn();
|
||||
fn();
|
||||
expect(n).toBe(0);
|
||||
|
||||
await nextMicroTick();
|
||||
expect(n).toBe(1);
|
||||
await nextMicroTick();
|
||||
expect(n).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
+76
-169
@@ -1,19 +1,11 @@
|
||||
import { SAMPLES } from "./samples.js";
|
||||
const { mount, hooks } = owl;
|
||||
const { useState, useRef, onMounted, onWillUnmount } = hooks;
|
||||
import { debounce, loadJS } from "./utils.js";
|
||||
import { exportStandaloneApp } from "./export_snippet.js";
|
||||
const { useState, useRef, onMounted, onWillUnmount, onPatched, onWillUpdateProps } = owl;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants, helpers, utils
|
||||
//------------------------------------------------------------------------------
|
||||
let owlJS;
|
||||
|
||||
async function owlSourceCode() {
|
||||
if (owlJS) {
|
||||
return owlJS;
|
||||
}
|
||||
const result = await fetch("../owl.js");
|
||||
owlJS = await result.text();
|
||||
return owlJS;
|
||||
}
|
||||
|
||||
const MODES = {
|
||||
js: "ace/mode/javascript",
|
||||
@@ -24,77 +16,41 @@ const MODES = {
|
||||
const DEFAULT_XML = `<templates>
|
||||
</templates>`;
|
||||
|
||||
const DEFAULT_HTML = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OWL App</title>
|
||||
<link rel="icon" href="data:,">
|
||||
|
||||
<script src="owl.js"></script>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const APP_PY = `#!/usr/bin/env python3
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
||||
|
||||
def start_server():
|
||||
SimpleHTTPRequestHandler.extensions_map['.js'] = 'application/javascript'
|
||||
httpd = HTTPServer(('0.0.0.0', 3600), SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
||||
|
||||
url = 'http://127.0.0.1:3600'
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Owl Application")
|
||||
print("---------------")
|
||||
print("Server running on: {}".format(url))
|
||||
threading.Thread(target=start_server, daemon=True).start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
httpd.server_close()
|
||||
quit(0)
|
||||
`;
|
||||
|
||||
/**
|
||||
* Make an iframe, with all the js, css and xml properly injected.
|
||||
*/
|
||||
function makeCodeIframe(js, css, xml) {
|
||||
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "").replace(/`/g, '\\\`');
|
||||
|
||||
|
||||
// create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
|
||||
iframe.onload = () => {
|
||||
const doc = iframe.contentDocument;
|
||||
const utilsMod = doc.createElement("script");
|
||||
utilsMod.setAttribute("type", "module");
|
||||
utilsMod.setAttribute("src", "utils.js");
|
||||
doc.head.appendChild(utilsMod);
|
||||
// inject js
|
||||
const owlScript = doc.createElement("script");
|
||||
owlScript.type = "text/javascript";
|
||||
owlScript.src = "../owl.js";
|
||||
owlScript.addEventListener("load", () => {
|
||||
const script = doc.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.type = "module";
|
||||
const content = `
|
||||
{
|
||||
owl.config.mode = 'dev';
|
||||
let templates = \`${sanitizedXML}\`;
|
||||
const qweb = new owl.QWeb({ templates });
|
||||
owl.Component.env = { qweb };
|
||||
}
|
||||
${js}`;
|
||||
import * as utils from "./utils.js";
|
||||
(function (owl) {
|
||||
const _configure = owl.App.prototype.configure;
|
||||
owl.App.prototype.configure = function configureOverriden(config) {
|
||||
config = Object.assign({ dev: true }, config);
|
||||
this.addTemplates(\`${sanitizedXML}\`);
|
||||
return _configure.call(this, config);
|
||||
}
|
||||
})(owl);
|
||||
(async function() {
|
||||
${js}
|
||||
})()`;
|
||||
script.innerHTML = content;
|
||||
doc.body.appendChild(script);
|
||||
});
|
||||
@@ -108,58 +64,6 @@ function makeCodeIframe(js, css, xml) {
|
||||
return iframe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a zip file containing a functioning application
|
||||
*/
|
||||
async function makeApp(js, css, xml) {
|
||||
await owl.utils.loadJS("libs/jszip.min.js");
|
||||
|
||||
const zip = new JSZip();
|
||||
const processedJS = js
|
||||
.split("\n")
|
||||
.map(l => (l === "" ? "" : " " + l))
|
||||
.join("\n");
|
||||
|
||||
const JS = `
|
||||
/**
|
||||
* This is the javascript code defined in the playground.
|
||||
* In a larger application, this code should probably be moved in different
|
||||
* sub files.
|
||||
*/
|
||||
function app() {
|
||||
${processedJS}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization code
|
||||
* This code load templates, and make sure everything is properly connected.
|
||||
*/
|
||||
async function start() {
|
||||
let templates;
|
||||
try {
|
||||
templates = await owl.utils.loadFile('app.xml');
|
||||
} catch(e) {
|
||||
console.error(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
|
||||
return;
|
||||
}
|
||||
const env = { qweb: new owl.QWeb({templates})};
|
||||
owl.Component.env = env;
|
||||
await owl.utils.whenReady();
|
||||
app();
|
||||
}
|
||||
|
||||
start();
|
||||
`;
|
||||
|
||||
zip.file("app.js", JS);
|
||||
zip.file("app.css", css);
|
||||
zip.file("app.py", APP_PY);
|
||||
zip.file("app.xml", xml);
|
||||
zip.file("index.html", DEFAULT_HTML);
|
||||
zip.file("owl.js", owlSourceCode());
|
||||
return zip.generateAsync({ type: "blob" });
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// SAMPLES
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -189,7 +93,7 @@ function deleteLocalSample() {
|
||||
|
||||
function useSamples() {
|
||||
const samples = loadSamples();
|
||||
const component = owl.Component.current;
|
||||
const component = owl.useComponent();
|
||||
let interval;
|
||||
|
||||
onMounted(() => {
|
||||
@@ -205,57 +109,57 @@ function useSamples() {
|
||||
});
|
||||
return samples;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tabbed editor
|
||||
//------------------------------------------------------------------------------
|
||||
class TabbedEditor extends owl.Component {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
setup() {
|
||||
const props = this.props;
|
||||
this.state = useState({
|
||||
currentTab: props.js !== false ? "js" : props.xml ? "xml" : "css"
|
||||
});
|
||||
this.setTab = owl.utils.debounce(this.setTab, 250, true);
|
||||
this.setTab = debounce(this.setTab.bind(this), 250, true);
|
||||
|
||||
this.sessions = {};
|
||||
this._setupSessions(props);
|
||||
this.editorNode = useRef("editor");
|
||||
this._updateCode = this._updateCode.bind(this);
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.editor = this.editor || ace.edit(this.editorNode.el);
|
||||
onMounted(() => {
|
||||
this.editor = this.editor || ace.edit(this.editorNode.el);
|
||||
|
||||
this.editor.setValue(this.props[this.state.currentTab], -1);
|
||||
this.editor.setFontSize("12px");
|
||||
this.editor.setTheme("ace/theme/monokai");
|
||||
this.editor.setSession(this.sessions[this.state.currentTab]);
|
||||
const tabSize = this.state.currentTab === "xml" ? 2 : 4;
|
||||
this.editor.session.setOption("tabSize", tabSize);
|
||||
this.editor.on("blur", this._updateCode);
|
||||
this.interval = setInterval(this._updateCode, 3000);
|
||||
}
|
||||
this.editor.setValue(this.props[this.state.currentTab], -1);
|
||||
this.editor.setFontSize("12px");
|
||||
this.editor.setTheme("ace/theme/monokai");
|
||||
this.editor.setSession(this.sessions[this.state.currentTab]);
|
||||
const tabSize = this.state.currentTab === "xml" ? 2 : 4;
|
||||
this.editor.session.setOption("tabSize", tabSize);
|
||||
this.editor.on("blur", this._updateCode);
|
||||
this.interval = setInterval(this._updateCode, 3000);
|
||||
});
|
||||
|
||||
willUnmount() {
|
||||
clearInterval(this.interval);
|
||||
this.editor.off("blur", this._updateCode);
|
||||
}
|
||||
willUpdateProps(nextProps) {
|
||||
this._setupSessions(nextProps);
|
||||
}
|
||||
onPatched(() => {
|
||||
const session = this.sessions[this.state.currentTab];
|
||||
let content = this.props[this.state.currentTab];
|
||||
if (content === false) {
|
||||
const tab = this.props.js !== false ? "js" : this.props.xml ? "xml" : "css";
|
||||
content = this.props[tab];
|
||||
this.state.currentTab = tab;
|
||||
}
|
||||
if (this.editor.getValue() !== content) {
|
||||
session.setValue(content, -1);
|
||||
this.editor.setSession(session);
|
||||
this.editor.resize();
|
||||
}
|
||||
});
|
||||
|
||||
patched() {
|
||||
const session = this.sessions[this.state.currentTab];
|
||||
let content = this.props[this.state.currentTab];
|
||||
if (content === false) {
|
||||
const tab = this.props.js !== false ? "js" : this.props.xml ? "xml" : "css";
|
||||
content = this.props[tab];
|
||||
this.state.currentTab = tab;
|
||||
}
|
||||
if (this.editor.getValue() !== content) {
|
||||
session.setValue(content, -1);
|
||||
this.editor.setSession(session);
|
||||
this.editor.resize();
|
||||
}
|
||||
onWillUpdateProps((nextProps) => this._setupSessions(nextProps));
|
||||
|
||||
onWillUnmount(() => {
|
||||
clearInterval(this.interval);
|
||||
this.editor.off("blur", this._updateCode);
|
||||
});
|
||||
}
|
||||
|
||||
setTab(tab) {
|
||||
@@ -298,20 +202,20 @@ class TabbedEditor extends owl.Component {
|
||||
const editorValue = this.editor.getValue();
|
||||
const propsValue = this.props[this.state.currentTab];
|
||||
if (editorValue !== propsValue) {
|
||||
this.trigger("updateCode", {
|
||||
this.props.updateCode({
|
||||
type: this.state.currentTab,
|
||||
value: editorValue
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
TabbedEditor.template = "TabbedEditor";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// MAIN APP
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
setup() {
|
||||
this.version = owl.__info__.version;
|
||||
this.SAMPLES = useSamples();
|
||||
this.isDirty = false;
|
||||
@@ -326,10 +230,11 @@ class App extends owl.Component {
|
||||
topPanelHeight: null
|
||||
});
|
||||
|
||||
this.toggleLayout = owl.utils.debounce(this.toggleLayout, 250, true);
|
||||
this.runCode = owl.utils.debounce(this.runCode, 250, true);
|
||||
this.downloadCode = owl.utils.debounce(this.downloadCode, 250, true);
|
||||
this.toggleLayout = debounce(this.toggleLayout, 250, true);
|
||||
this.runCode = debounce(this.runCode, 250, true);
|
||||
this.downloadCode = debounce(this.downloadCode, 250, true);
|
||||
this.content = useRef("content");
|
||||
this.updateCode = this.updateCode.bind(this);
|
||||
}
|
||||
|
||||
runCode() {
|
||||
@@ -379,9 +284,9 @@ class App extends owl.Component {
|
||||
}
|
||||
});
|
||||
}
|
||||
updateCode(ev) {
|
||||
if (this.state[ev.detail.type] !== ev.detail.value) {
|
||||
this.state[ev.detail.type] = ev.detail.value;
|
||||
updateCode({type, value}) {
|
||||
if (this.state[type] !== value) {
|
||||
this.state[type] = value;
|
||||
this.isDirty = true;
|
||||
}
|
||||
}
|
||||
@@ -401,12 +306,13 @@ class App extends owl.Component {
|
||||
|
||||
async downloadCode() {
|
||||
const { js, css, xml } = this.state;
|
||||
const content = await makeApp(js, css, xml);
|
||||
await owl.utils.loadJS("libs/FileSaver.min.js");
|
||||
const content = await exportStandaloneApp(js, css, xml);
|
||||
await loadJS("libs/FileSaver.min.js");
|
||||
saveAs(content, "app.zip");
|
||||
}
|
||||
}
|
||||
App.components = { TabbedEditor };
|
||||
App.template = "App";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
@@ -416,12 +322,13 @@ async function start() {
|
||||
const commit = `https://github.com/odoo/owl/commit/${owl.__info__.hash}`;
|
||||
console.info(`This application is using Owl built with the following commit:`, commit);
|
||||
const [templates] = await Promise.all([
|
||||
owl.utils.loadFile("templates.xml"),
|
||||
owl.utils.whenReady()
|
||||
owl.loadFile("templates.xml"),
|
||||
owl.whenReady()
|
||||
]);
|
||||
const qweb = new owl.QWeb({ templates });
|
||||
const env = { qweb };
|
||||
await mount(App, {target: document.body, env});
|
||||
const rootApp = new owl.App(App);
|
||||
rootApp.addTemplates(templates);
|
||||
|
||||
await rootApp.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { loadJS } from "./utils.js";
|
||||
|
||||
const sourceCache = new Map();
|
||||
async function getSourceCode(filePath) {
|
||||
if (sourceCache.has(filePath)) {
|
||||
return sourceCache.get(filePath);
|
||||
}
|
||||
let source = await fetch(`${filePath}.js`);
|
||||
source = await source.text();
|
||||
sourceCache.set(filePath, source);
|
||||
return source;
|
||||
}
|
||||
|
||||
const DEFAULT_HTML = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OWL App</title>
|
||||
<link rel="icon" href="data:,">
|
||||
|
||||
<script src="owl.js"></script>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
<script type="module" src="app.js"></script>
|
||||
<script type="module" src="utils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const APP_PY = `#!/usr/bin/env python3
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
||||
|
||||
def start_server():
|
||||
SimpleHTTPRequestHandler.extensions_map['.js'] = 'application/javascript'
|
||||
httpd = HTTPServer(('0.0.0.0', 3600), SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
||||
|
||||
url = 'http://127.0.0.1:3600'
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Owl Application")
|
||||
print("---------------")
|
||||
print("Server running on: {}".format(url))
|
||||
threading.Thread(target=start_server, daemon=True).start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
httpd.server_close()
|
||||
quit(0)
|
||||
`;
|
||||
|
||||
/**
|
||||
* Make a zip file containing a functioning application
|
||||
*/
|
||||
export async function exportStandaloneApp(js, css, xml) {
|
||||
await loadJS("libs/jszip.min.js");
|
||||
|
||||
const zip = new JSZip();
|
||||
const processedJS = js
|
||||
.split("\n")
|
||||
.map(l => (l === "" ? "" : " " + l))
|
||||
.join("\n");
|
||||
|
||||
const JS = `
|
||||
/**
|
||||
* This is the javascript code defined in the playground.
|
||||
* In a larger application, this code should probably be moved in different
|
||||
* sub files.
|
||||
*/
|
||||
import * as utils from "./utils.js";
|
||||
async function app() {
|
||||
${processedJS}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization code
|
||||
* This code load templates, and make sure everything is properly connected.
|
||||
*/
|
||||
function prepareOWLApp(templates) {
|
||||
const _configure = owl.App.prototype.configure;
|
||||
owl.App.prototype.configure = function configureOverriden(config) {
|
||||
config = Object.assign({ dev: true }, config);
|
||||
this.addTemplates(templates);
|
||||
return _configure.call(this, config);
|
||||
}
|
||||
}
|
||||
|
||||
async function start() {
|
||||
let templates;
|
||||
try {
|
||||
templates = await owl.loadFile('app.xml');
|
||||
} catch(e) {
|
||||
console.error(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
|
||||
return;
|
||||
}
|
||||
prepareOWLApp(templates);
|
||||
app();
|
||||
}
|
||||
|
||||
start();
|
||||
`;
|
||||
|
||||
const [owlSrc, utilsSrc] = await Promise.all([
|
||||
getSourceCode("../owl"),
|
||||
getSourceCode("./utils"),
|
||||
]);
|
||||
|
||||
zip.file("app.js", JS);
|
||||
zip.file("utils.js", utilsSrc);
|
||||
zip.file("app.css", css);
|
||||
zip.file("app.py", APP_PY);
|
||||
zip.file("app.xml", xml);
|
||||
zip.file("index.html", DEFAULT_HTML);
|
||||
zip.file("owl.js", owlSrc);
|
||||
return zip.generateAsync({ type: "blob" });
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
<!-- Application JS/CSS -->
|
||||
<link rel="stylesheet" href="playground.css">
|
||||
<script type="module" src="export_snippet.js"></script>
|
||||
<script type="module" src="utils.js"></script>
|
||||
<script type="module" src="app.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
+316
-531
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
<templates>
|
||||
<div t-name="TabbedEditor" class="tabbed-editor">
|
||||
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
|
||||
<t t-foreach="['js', 'xml', 'css']" t-as="tab">
|
||||
<a t-ref="{{tab}}" t-if="props[tab] !== false" t-key="tab" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="setTab(tab)">
|
||||
<t t-foreach="['js', 'xml', 'css']" t-as="tab" t-key="tab">
|
||||
<a t-ref="{{tab}}" t-if="props[tab] !== false" t-key="tab" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="() => setTab(tab)">
|
||||
<t t-esc="tab"/>
|
||||
</a>
|
||||
</t>
|
||||
@@ -12,8 +12,7 @@
|
||||
|
||||
<div t-name="App" class="playground">
|
||||
<div class="left-bar" t-att-class="{split: state.splitLayout}"
|
||||
t-att-style="leftPaneStyle"
|
||||
t-on-updateCode="updateCode">
|
||||
t-att-style="leftPaneStyle">
|
||||
<div class="menubar">
|
||||
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
|
||||
<select t-on-change="setSample">
|
||||
@@ -28,7 +27,8 @@
|
||||
js="state.js"
|
||||
css="!state.splitLayout and state.css"
|
||||
xml="!state.splitLayout and state.xml"
|
||||
t-att-style="topEditorStyle"/>
|
||||
style="topEditorStyle"
|
||||
updateCode="updateCode"/>
|
||||
<t t-if="state.splitLayout">
|
||||
<div class="separator horizontal"/>
|
||||
<TabbedEditor
|
||||
@@ -36,7 +36,8 @@
|
||||
css="state.css"
|
||||
xml="state.xml"
|
||||
resizeable="true"
|
||||
t-on-updatePanelHeight="updatePanelHeight"/>
|
||||
onUpdatePanelHeight="updatePanelHeight"
|
||||
updateCode="updateCode"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
export function debounce(func, wait, immediate) {
|
||||
let timeout;
|
||||
return function () {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
function later() {
|
||||
timeout = null;
|
||||
if (!immediate) {
|
||||
func.apply(context, args);
|
||||
}
|
||||
}
|
||||
const callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) {
|
||||
func.apply(context, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function useBus(bus, evName, cb) {
|
||||
bus.addEventListener(evName, cb);
|
||||
owl.onWillUnmount(() => {
|
||||
bus.removeEventListener(evName, cb);
|
||||
});
|
||||
}
|
||||
|
||||
const loadedScripts = {};
|
||||
|
||||
export function loadJS(url) {
|
||||
if (url in loadedScripts) {
|
||||
return loadedScripts[url];
|
||||
}
|
||||
const promise = new Promise(function (resolve, reject) {
|
||||
const script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.src = url;
|
||||
script.onload = function () {
|
||||
resolve();
|
||||
};
|
||||
script.onerror = function () {
|
||||
reject(`Error loading file '${url}'`);
|
||||
};
|
||||
const head = document.head || document.getElementsByTagName("head")[0];
|
||||
head.appendChild(script);
|
||||
});
|
||||
loadedScripts[url] = promise;
|
||||
return promise;
|
||||
}
|
||||
Reference in New Issue
Block a user