mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aeed79c7e5 | |||
| 1c5b6f2573 | |||
| cd9b72158b | |||
| 7fc552e2f8 | |||
| e6768501cd | |||
| 6b2486473f | |||
| 7e687234bf | |||
| 968e96ad08 | |||
| 26c7856d5d |
@@ -66,6 +66,9 @@ The `config` object is an object with some of the following keys:
|
||||
needs a template. If undefined is returned, owl looks into the app templates.
|
||||
- **`warnIfNoStaticProps (boolean, default=false)`**: if true, Owl will log a warning
|
||||
whenever it encounters a component that does not provide a [static props description](props.md#props-validation).
|
||||
- **`customDirectives (object)`**: if given, the corresponding function on the object will be called
|
||||
on the template custom directives: `t-custom-*` (see [Custom Directives](templates.md#custom-directives)).
|
||||
- **`globalValues (object)`**: Global object of elements available at compilations.
|
||||
|
||||
## `mount` helper
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
- [Sub Templates](#sub-templates)
|
||||
- [Dynamic Sub Templates](#dynamic-sub-templates)
|
||||
- [Debugging](#debugging)
|
||||
- [Custom Directives](#custom-directives)
|
||||
- [Fragments](#fragments)
|
||||
- [Inline templates](#inline-templates)
|
||||
- [Rendering svg](#rendering-svg)
|
||||
@@ -80,6 +81,7 @@ needs. Here is a list of all Owl specific directives:
|
||||
| `t-slot`, `t-set-slot`, `t-slot-scope` | [Rendering a slot](slots.md) |
|
||||
| `t-model` | [Form input bindings](input_bindings.md) |
|
||||
| `t-tag` | [Rendering nodes with dynamic tag name](#dynamic-tag-names) |
|
||||
| `t-custom-*` | [Rendering nodes with custom directives](#custom-directives) |
|
||||
|
||||
## QWeb Template Reference
|
||||
|
||||
@@ -588,6 +590,35 @@ will stop execution if the browser dev tools are open.
|
||||
|
||||
will print 42 to the console.
|
||||
|
||||
### Custom Directives
|
||||
|
||||
Owl 2 supports the declaration of custom directives. To use them, an Object of functions needs to be configured on the owl APP:
|
||||
|
||||
```js
|
||||
new App(..., {
|
||||
customDirectives: {
|
||||
test_directive: function (el, value) {
|
||||
el.setAttribute("t-on-click", value);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The functions will be called when a custom directive with the name of the
|
||||
function is found. The original element will be replaced with the one
|
||||
modified by the function.
|
||||
This :
|
||||
|
||||
```xml
|
||||
<div t-custom-test_directive="click" />
|
||||
```
|
||||
|
||||
will be replaced by :
|
||||
|
||||
```xml
|
||||
<div t-on-click="value"/>
|
||||
```
|
||||
|
||||
## Fragments
|
||||
|
||||
Owl 2 supports templates with an arbitrary number of root elements, or even just
|
||||
|
||||
+95
-15
@@ -1625,6 +1625,13 @@ function makeRootFiber(node) {
|
||||
fibersInError.delete(current);
|
||||
fibersInError.delete(root);
|
||||
current.appliedToDom = false;
|
||||
if (current instanceof RootFiber) {
|
||||
// it is possible that this fiber is a fiber that crashed while being
|
||||
// mounted, so the mounted list is possibly corrupted. We restore it to
|
||||
// its normal initial state (which is empty list or a list with a mount
|
||||
// fiber.
|
||||
current.mounted = current instanceof MountFiber ? [current] : [];
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
@@ -1741,6 +1748,7 @@ class RootFiber extends Fiber {
|
||||
const node = this.node;
|
||||
this.locked = true;
|
||||
let current = undefined;
|
||||
let mountedFibers = this.mounted;
|
||||
try {
|
||||
// Step 1: calling all willPatch lifecycle hooks
|
||||
for (current of this.willPatch) {
|
||||
@@ -1760,7 +1768,6 @@ class RootFiber extends Fiber {
|
||||
node._patch();
|
||||
this.locked = false;
|
||||
// Step 4: calling all mounted lifecycle hooks
|
||||
let mountedFibers = this.mounted;
|
||||
while ((current = mountedFibers.pop())) {
|
||||
current = current;
|
||||
if (current.appliedToDom) {
|
||||
@@ -1781,6 +1788,15 @@ class RootFiber extends Fiber {
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// if mountedFibers is not empty, this means that a crash occured while
|
||||
// calling the mounted hooks of some component. So, there may still be
|
||||
// some component that have been mounted, but for which the mounted hooks
|
||||
// have not been called. Here, we remove the willUnmount hooks for these
|
||||
// specific component to prevent a worse situation (willUnmount being
|
||||
// called even though mounted has not been called)
|
||||
for (let fiber of mountedFibers) {
|
||||
fiber.node.willUnmount = [];
|
||||
}
|
||||
this.locked = false;
|
||||
node.app.handleError({ fiber: current || this, error: e });
|
||||
}
|
||||
@@ -2270,6 +2286,12 @@ function collectionsProxyHandler(target, callback, targetRawType) {
|
||||
}
|
||||
|
||||
let currentNode = null;
|
||||
function saveCurrent() {
|
||||
let n = currentNode;
|
||||
return () => {
|
||||
currentNode = n;
|
||||
};
|
||||
}
|
||||
function getCurrent() {
|
||||
if (!currentNode) {
|
||||
throw new OwlError("No active component (a hook function should only be called in 'setup')");
|
||||
@@ -3221,6 +3243,9 @@ class TemplateSet {
|
||||
}
|
||||
}
|
||||
this.getRawTemplate = config.getTemplate;
|
||||
this.customDirectives = config.customDirectives || {};
|
||||
this.runtimeUtils = { ...helpers, __globals__: config.globalValues || {} };
|
||||
this.hasGlobalValues = Boolean(config.globalValues && Object.keys(config.globalValues).length);
|
||||
}
|
||||
static registerTemplate(name, fn) {
|
||||
globalTemplates[name] = fn;
|
||||
@@ -3277,7 +3302,7 @@ class TemplateSet {
|
||||
this.templates[name] = function (context, parent) {
|
||||
return templates[name].call(this, context, parent);
|
||||
};
|
||||
const template = templateFn(this, bdom, helpers);
|
||||
const template = templateFn(this, bdom, this.runtimeUtils);
|
||||
this.templates[name] = template;
|
||||
}
|
||||
return this.templates[name];
|
||||
@@ -3328,7 +3353,7 @@ TemplateSet.registerTemplate("__portal__", portalTemplate);
|
||||
//------------------------------------------------------------------------------
|
||||
// Misc types, constants and helpers
|
||||
//------------------------------------------------------------------------------
|
||||
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(",");
|
||||
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__".split(",");
|
||||
const WORD_REPLACEMENT = Object.assign(Object.create(null), {
|
||||
and: "&&",
|
||||
or: "||",
|
||||
@@ -3800,6 +3825,9 @@ class CodeGenerator {
|
||||
this.dev = options.dev || false;
|
||||
this.ast = ast;
|
||||
this.templateName = options.name;
|
||||
if (options.hasGlobalValues) {
|
||||
this.helpers.add("__globals__");
|
||||
}
|
||||
}
|
||||
generateCode() {
|
||||
const ast = this.ast;
|
||||
@@ -4838,29 +4866,33 @@ class CodeGenerator {
|
||||
// Parser
|
||||
// -----------------------------------------------------------------------------
|
||||
const cache = new WeakMap();
|
||||
function parse(xml) {
|
||||
function parse(xml, customDir) {
|
||||
const ctx = {
|
||||
inPreTag: false,
|
||||
customDirectives: customDir,
|
||||
};
|
||||
if (typeof xml === "string") {
|
||||
const elem = parseXML(`<t>${xml}</t>`).firstChild;
|
||||
return _parse(elem);
|
||||
return _parse(elem, ctx);
|
||||
}
|
||||
let ast = cache.get(xml);
|
||||
if (!ast) {
|
||||
// we clone here the xml to prevent modifying it in place
|
||||
ast = _parse(xml.cloneNode(true));
|
||||
ast = _parse(xml.cloneNode(true), ctx);
|
||||
cache.set(xml, ast);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
function _parse(xml) {
|
||||
function _parse(xml, ctx) {
|
||||
normalizeXML(xml);
|
||||
const ctx = { inPreTag: false };
|
||||
return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" };
|
||||
}
|
||||
function parseNode(node, ctx) {
|
||||
if (!(node instanceof Element)) {
|
||||
return parseTextCommentNode(node, ctx);
|
||||
}
|
||||
return (parseTDebugLog(node, ctx) ||
|
||||
return (parseTCustom(node, ctx) ||
|
||||
parseTDebugLog(node, ctx) ||
|
||||
parseTForEach(node, ctx) ||
|
||||
parseTIf(node, ctx) ||
|
||||
parseTPortal(node, ctx) ||
|
||||
@@ -4902,6 +4934,35 @@ function parseTextCommentNode(node, ctx) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function parseTCustom(node, ctx) {
|
||||
if (!ctx.customDirectives) {
|
||||
return null;
|
||||
}
|
||||
const nodeAttrsNames = node.getAttributeNames();
|
||||
for (let attr of nodeAttrsNames) {
|
||||
if (attr === "t-custom" || attr === "t-custom-") {
|
||||
throw new OwlError("Missing custom directive name with t-custom directive");
|
||||
}
|
||||
if (attr.startsWith("t-custom-")) {
|
||||
const directiveName = attr.split(".")[0].slice(9);
|
||||
const customDirective = ctx.customDirectives[directiveName];
|
||||
if (!customDirective) {
|
||||
throw new OwlError(`Custom directive "${directiveName}" is not defined`);
|
||||
}
|
||||
const value = node.getAttribute(attr);
|
||||
const modifiers = attr.split(".").slice(1);
|
||||
node.removeAttribute(attr);
|
||||
try {
|
||||
customDirective(node, value, modifiers);
|
||||
}
|
||||
catch (error) {
|
||||
throw new OwlError(`Custom directive "${directiveName}" throw the following error: ${error}`);
|
||||
}
|
||||
return parseNode(node, ctx);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// -----------------------------------------------------------------------------
|
||||
// debugging
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -5533,9 +5594,11 @@ function normalizeXML(el) {
|
||||
normalizeTEscTOut(el);
|
||||
}
|
||||
|
||||
function compile(template, options = {}) {
|
||||
function compile(template, options = {
|
||||
hasGlobalValues: false,
|
||||
}) {
|
||||
// parsing
|
||||
const ast = parse(template);
|
||||
const ast = parse(template, options.customDirectives);
|
||||
// some work
|
||||
const hasSafeContext = template instanceof Node
|
||||
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
|
||||
@@ -5557,7 +5620,7 @@ function compile(template, options = {}) {
|
||||
}
|
||||
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
const version = "2.4.0";
|
||||
const version = "2.5.2";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Scheduler
|
||||
@@ -5568,6 +5631,7 @@ class Scheduler {
|
||||
this.frame = 0;
|
||||
this.delayedRenders = [];
|
||||
this.cancelledNodes = new Set();
|
||||
this.processing = false;
|
||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||
}
|
||||
addFiber(fiber) {
|
||||
@@ -5598,6 +5662,10 @@ class Scheduler {
|
||||
}
|
||||
}
|
||||
processTasks() {
|
||||
if (this.processing) {
|
||||
return;
|
||||
}
|
||||
this.processing = true;
|
||||
this.frame = 0;
|
||||
for (let node of this.cancelledNodes) {
|
||||
node._destroy();
|
||||
@@ -5611,6 +5679,7 @@ class Scheduler {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
this.processing = false;
|
||||
}
|
||||
processFiber(fiber) {
|
||||
if (fiber.root !== fiber) {
|
||||
@@ -5630,7 +5699,14 @@ class Scheduler {
|
||||
if (!hasError) {
|
||||
fiber.complete();
|
||||
}
|
||||
this.tasks.delete(fiber);
|
||||
// at this point, the fiber should have been applied to the DOM, so we can
|
||||
// remove it from the task list. If it is not the case, it means that there
|
||||
// was an error and an error handler triggered a new rendering that recycled
|
||||
// the fiber, so in that case, we actually want to keep the fiber around,
|
||||
// otherwise it will just be ignored.
|
||||
if (fiber.appliedToDom) {
|
||||
this.tasks.delete(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5686,7 +5762,9 @@ class App extends TemplateSet {
|
||||
if (config.env) {
|
||||
this.env = config.env;
|
||||
}
|
||||
const restore = saveCurrent();
|
||||
const node = this.makeNode(Root, props);
|
||||
restore();
|
||||
if (config.env) {
|
||||
this.env = env;
|
||||
}
|
||||
@@ -6012,12 +6090,14 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
|
||||
dev: this.dev,
|
||||
translateFn: this.translateFn,
|
||||
translatableAttributes: this.translatableAttributes,
|
||||
customDirectives: this.customDirectives,
|
||||
hasGlobalValues: this.hasGlobalValues,
|
||||
});
|
||||
};
|
||||
|
||||
export { App, Component, EventBus, OwlError, __info__, batched, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
|
||||
|
||||
|
||||
__info__.date = '2024-09-30T08:49:29.420Z';
|
||||
__info__.hash = 'eb2b32a';
|
||||
__info__.date = '2024-12-02T15:51:07.157Z';
|
||||
__info__.hash = '1c5b6f2';
|
||||
__info__.url = 'https://github.com/odoo/owl';
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.4.0",
|
||||
"version": "2.5.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.4.0",
|
||||
"version": "2.5.2",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"module": "dist/owl.es.js",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export type customDirectives = Record<
|
||||
string,
|
||||
(node: Element, value: string, modifier: string[]) => void
|
||||
>;
|
||||
@@ -43,6 +43,7 @@ export interface Config {
|
||||
export interface CodeGenOptions extends Config {
|
||||
hasSafeContext?: boolean;
|
||||
name?: string;
|
||||
hasGlobalValues: boolean;
|
||||
}
|
||||
|
||||
// using a non-html document so that <inner/outer>HTML serializes as XML instead
|
||||
@@ -286,6 +287,9 @@ export class CodeGenerator {
|
||||
this.dev = options.dev || false;
|
||||
this.ast = ast;
|
||||
this.templateName = options.name;
|
||||
if (options.hasGlobalValues) {
|
||||
this.helpers.add("__globals__");
|
||||
}
|
||||
}
|
||||
|
||||
generateCode(): string {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { customDirectives } from "../common/types";
|
||||
import type { TemplateSet } from "../runtime/template_set";
|
||||
import type { BDom } from "../runtime/blockdom";
|
||||
import { CodeGenerator, Config } from "./code_generator";
|
||||
@@ -10,13 +11,17 @@ export type TemplateFunction = (app: TemplateSet, bdom: any, helpers: any) => Te
|
||||
|
||||
interface CompileOptions extends Config {
|
||||
name?: string;
|
||||
customDirectives?: customDirectives;
|
||||
hasGlobalValues: boolean;
|
||||
}
|
||||
export function compile(
|
||||
template: string | Element,
|
||||
options: CompileOptions = {}
|
||||
options: CompileOptions = {
|
||||
hasGlobalValues: false,
|
||||
}
|
||||
): TemplateFunction {
|
||||
// parsing
|
||||
const ast = parse(template);
|
||||
const ast = parse(template, options.customDirectives);
|
||||
|
||||
// some work
|
||||
const hasSafeContext =
|
||||
|
||||
@@ -28,7 +28,7 @@ import { OwlError } from "../common/owl_error";
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const RESERVED_WORDS =
|
||||
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(
|
||||
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__".split(
|
||||
","
|
||||
);
|
||||
|
||||
|
||||
+42
-5
@@ -1,4 +1,5 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import type { customDirectives } from "../common/types";
|
||||
import { parseXML } from "../common/utils";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -198,23 +199,26 @@ export type AST =
|
||||
// -----------------------------------------------------------------------------
|
||||
const cache: WeakMap<Element, AST> = new WeakMap();
|
||||
|
||||
export function parse(xml: string | Element): AST {
|
||||
export function parse(xml: string | Element, customDir?: customDirectives): AST {
|
||||
const ctx = {
|
||||
inPreTag: false,
|
||||
customDirectives: customDir,
|
||||
};
|
||||
if (typeof xml === "string") {
|
||||
const elem = parseXML(`<t>${xml}</t>`).firstChild as Element;
|
||||
return _parse(elem);
|
||||
return _parse(elem, ctx);
|
||||
}
|
||||
let ast = cache.get(xml);
|
||||
if (!ast) {
|
||||
// we clone here the xml to prevent modifying it in place
|
||||
ast = _parse(xml.cloneNode(true) as Element);
|
||||
ast = _parse(xml.cloneNode(true) as Element, ctx);
|
||||
cache.set(xml, ast);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
function _parse(xml: Element): AST {
|
||||
function _parse(xml: Element, ctx: ParsingContext): AST {
|
||||
normalizeXML(xml);
|
||||
const ctx = { inPreTag: false };
|
||||
return parseNode(xml, ctx) || { type: ASTType.Text, value: "" };
|
||||
}
|
||||
|
||||
@@ -222,6 +226,7 @@ interface ParsingContext {
|
||||
tModelInfo?: TModelInfo | null;
|
||||
nameSpace?: string;
|
||||
inPreTag: boolean;
|
||||
customDirectives?: customDirectives;
|
||||
}
|
||||
|
||||
function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
@@ -229,6 +234,7 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
return parseTextCommentNode(node, ctx);
|
||||
}
|
||||
return (
|
||||
parseTCustom(node, ctx) ||
|
||||
parseTDebugLog(node, ctx) ||
|
||||
parseTForEach(node, ctx) ||
|
||||
parseTIf(node, ctx) ||
|
||||
@@ -277,6 +283,37 @@ function parseTextCommentNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseTCustom(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!ctx.customDirectives) {
|
||||
return null;
|
||||
}
|
||||
const nodeAttrsNames = node.getAttributeNames();
|
||||
for (let attr of nodeAttrsNames) {
|
||||
if (attr === "t-custom" || attr === "t-custom-") {
|
||||
throw new OwlError("Missing custom directive name with t-custom directive");
|
||||
}
|
||||
if (attr.startsWith("t-custom-")) {
|
||||
const directiveName = attr.split(".")[0].slice(9);
|
||||
const customDirective = ctx.customDirectives[directiveName];
|
||||
if (!customDirective) {
|
||||
throw new OwlError(`Custom directive "${directiveName}" is not defined`);
|
||||
}
|
||||
const value = node.getAttribute(attr)!;
|
||||
const modifiers = attr.split(".").slice(1);
|
||||
node.removeAttribute(attr);
|
||||
try {
|
||||
customDirective(node, value, modifiers);
|
||||
} catch (error) {
|
||||
throw new OwlError(
|
||||
`Custom directive "${directiveName}" throw the following error: ${error}`
|
||||
);
|
||||
}
|
||||
return parseNode(node, ctx);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// debugging
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -12,5 +12,7 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(
|
||||
dev: this.dev,
|
||||
translateFn: this.translateFn,
|
||||
translatableAttributes: this.translatableAttributes,
|
||||
customDirectives: this.customDirectives,
|
||||
hasGlobalValues: this.hasGlobalValues,
|
||||
});
|
||||
};
|
||||
|
||||
+17
-1
@@ -30,6 +30,13 @@ export function makeRootFiber(node: ComponentNode): Fiber {
|
||||
fibersInError.delete(current);
|
||||
fibersInError.delete(root);
|
||||
current.appliedToDom = false;
|
||||
if (current instanceof RootFiber) {
|
||||
// it is possible that this fiber is a fiber that crashed while being
|
||||
// mounted, so the mounted list is possibly corrupted. We restore it to
|
||||
// its normal initial state (which is empty list or a list with a mount
|
||||
// fiber.
|
||||
current.mounted = current instanceof MountFiber ? [current] : [];
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
@@ -152,6 +159,7 @@ export class RootFiber extends Fiber {
|
||||
const node = this.node;
|
||||
this.locked = true;
|
||||
let current: Fiber | undefined = undefined;
|
||||
let mountedFibers = this.mounted;
|
||||
try {
|
||||
// Step 1: calling all willPatch lifecycle hooks
|
||||
for (current of this.willPatch) {
|
||||
@@ -173,7 +181,6 @@ export class RootFiber extends Fiber {
|
||||
this.locked = false;
|
||||
|
||||
// Step 4: calling all mounted lifecycle hooks
|
||||
let mountedFibers = this.mounted;
|
||||
while ((current = mountedFibers.pop())) {
|
||||
current = current;
|
||||
if (current.appliedToDom) {
|
||||
@@ -194,6 +201,15 @@ export class RootFiber extends Fiber {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// if mountedFibers is not empty, this means that a crash occured while
|
||||
// calling the mounted hooks of some component. So, there may still be
|
||||
// some component that have been mounted, but for which the mounted hooks
|
||||
// have not been called. Here, we remove the willUnmount hooks for these
|
||||
// specific component to prevent a worse situation (willUnmount being
|
||||
// called even though mounted has not been called)
|
||||
for (let fiber of mountedFibers) {
|
||||
fiber.node.willUnmount = [];
|
||||
}
|
||||
this.locked = false;
|
||||
node.app.handleError({ fiber: current || this, error: e });
|
||||
}
|
||||
|
||||
@@ -93,7 +93,14 @@ export class Scheduler {
|
||||
if (!hasError) {
|
||||
fiber.complete();
|
||||
}
|
||||
this.tasks.delete(fiber);
|
||||
// at this point, the fiber should have been applied to the DOM, so we can
|
||||
// remove it from the task list. If it is not the case, it means that there
|
||||
// was an error and an error handler triggered a new rendering that recycled
|
||||
// the fiber, so in that case, we actually want to keep the fiber around,
|
||||
// otherwise it will just be ignored.
|
||||
if (fiber.appliedToDom) {
|
||||
this.tasks.delete(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Portal, portalTemplate } from "./portal";
|
||||
import { helpers } from "./template_helpers";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { parseXML } from "../common/utils";
|
||||
import type { customDirectives } from "../common/types";
|
||||
|
||||
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
||||
|
||||
@@ -14,6 +15,8 @@ export interface TemplateSetConfig {
|
||||
translateFn?: (s: string) => string;
|
||||
templates?: string | Document | Record<string, string>;
|
||||
getTemplate?: (s: string) => Element | Function | string | void;
|
||||
customDirectives?: customDirectives;
|
||||
globalValues?: object;
|
||||
}
|
||||
|
||||
export class TemplateSet {
|
||||
@@ -27,6 +30,9 @@ export class TemplateSet {
|
||||
translateFn?: (s: string) => string;
|
||||
translatableAttributes?: string[];
|
||||
Portal = Portal;
|
||||
customDirectives: customDirectives;
|
||||
runtimeUtils: object;
|
||||
hasGlobalValues: boolean;
|
||||
|
||||
constructor(config: TemplateSetConfig = {}) {
|
||||
this.dev = config.dev || false;
|
||||
@@ -42,6 +48,9 @@ export class TemplateSet {
|
||||
}
|
||||
}
|
||||
this.getRawTemplate = config.getTemplate;
|
||||
this.customDirectives = config.customDirectives || {};
|
||||
this.runtimeUtils = { ...helpers, __globals__: config.globalValues || {} };
|
||||
this.hasGlobalValues = Boolean(config.globalValues && Object.keys(config.globalValues).length);
|
||||
}
|
||||
|
||||
addTemplate(name: string, template: string | Element) {
|
||||
@@ -97,7 +106,7 @@ export class TemplateSet {
|
||||
this.templates[name] = function (context, parent) {
|
||||
return templates[name].call(this, context, parent);
|
||||
};
|
||||
const template = templateFn(this, bdom, helpers);
|
||||
const template = templateFn(this, bdom, this.runtimeUtils);
|
||||
this.templates[name] = template;
|
||||
}
|
||||
return this.templates[name];
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
export const version = "2.4.0";
|
||||
export const version = "2.5.2";
|
||||
|
||||
@@ -43,6 +43,21 @@ exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can add functions to the bdom 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { __globals__ } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"my-div\\" block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let hdlr1 = [()=>__globals__.plop('click'), ctx];
|
||||
return block1([hdlr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can call processTask twice in a row without crashing 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -201,4 +201,22 @@ describe("app", () => {
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("parent<div></div>");
|
||||
});
|
||||
|
||||
test("can add functions to the bdom", async () => {
|
||||
const steps: string[] = [];
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`<div t-on-click="() => __globals__.plop('click')" class="my-div"/>`;
|
||||
}
|
||||
const app = new App(SomeComponent, {
|
||||
globalValues: {
|
||||
plop: (string: any) => {
|
||||
steps.push(string);
|
||||
},
|
||||
},
|
||||
});
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div class="my-div"></div>`);
|
||||
fixture.querySelector("div")!.click();
|
||||
expect(steps).toEqual(["click"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`t-custom can use t-custom directive on a node 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"my-div\\" block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let hdlr1 = [ctx['click'], ctx];
|
||||
return block1([hdlr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-custom can use t-custom directive with modifiers on a node 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"my-div\\" block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let hdlr1 = [ctx['click'], ctx];
|
||||
return block1([hdlr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { App, Component, xml } from "../../src";
|
||||
import { makeTestFixture, snapshotEverything } from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("t-custom", () => {
|
||||
test("can use t-custom directive on a node", async () => {
|
||||
const steps: string[] = [];
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`<div t-custom-plop="click" class="my-div"/>`;
|
||||
click() {
|
||||
steps.push("clicked");
|
||||
}
|
||||
}
|
||||
const app = new App(SomeComponent, {
|
||||
customDirectives: {
|
||||
plop: (node, value) => {
|
||||
node.setAttribute("t-on-click", value);
|
||||
},
|
||||
},
|
||||
});
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div class="my-div"></div>`);
|
||||
fixture.querySelector("div")!.click();
|
||||
expect(steps).toEqual(["clicked"]);
|
||||
});
|
||||
|
||||
test("can use t-custom directive with modifiers on a node", async () => {
|
||||
const steps: string[] = [];
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`<div t-custom-plop.mouse.stop="click" class="my-div"/>`;
|
||||
click() {
|
||||
steps.push("clicked");
|
||||
}
|
||||
}
|
||||
const app = new App(SomeComponent, {
|
||||
customDirectives: {
|
||||
plop: (node, value, modifiers) => {
|
||||
node.setAttribute("t-on-click", value);
|
||||
for (let mod of modifiers) {
|
||||
steps.push(mod);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div class="my-div"></div>`);
|
||||
fixture.querySelector("div")!.click();
|
||||
expect(steps).toEqual(["mouse", "stop", "clicked"]);
|
||||
});
|
||||
});
|
||||
@@ -320,6 +320,50 @@ exports[`can catch errors can catch an error in a component render function 3`]
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors can catch an error in onmounted 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(null, false, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
b2 = text(\`Main\`);
|
||||
if (ctx['state'].ok) {
|
||||
const Comp1 = ctx['component'];
|
||||
b3 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
|
||||
}
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors can catch an error in onmounted 3`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>Error!!!</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors can catch an error in onmounted 4`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>perfect</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors can catch an error in the constructor call of a component render function 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -1147,6 +1191,135 @@ exports[`can catch errors error in mounted on a component with a sibling (proper
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(null, false, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const Comp1 = ctx['component'];
|
||||
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
const comp2 = app.createComponent(\`Boom\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`parent\`);
|
||||
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||
const b4 = comp2({}, key + \`__2\`, node, this, null);
|
||||
return multi([b2, b3, b4]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery 3`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`abc\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery 4`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`boom\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery 5`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`def\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery, variation 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(null, false, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
b2 = text(\`R\`);
|
||||
if (ctx['state'].gogogo) {
|
||||
const Comp1 = ctx['component'];
|
||||
b3 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
|
||||
}
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery, variation 3`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
const comp2 = app.createComponent(\`Boom\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`parent\`);
|
||||
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||
const b4 = comp2({}, key + \`__2\`, node, this, null);
|
||||
return multi([b2, b3, b4]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery, variation 4`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`abc\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery, variation 5`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`boom\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors error in onMounted, graceful recovery, variation 6`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`def\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can catch errors onError in class inheritance is called if rethrown 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -564,6 +564,82 @@ describe("can catch errors", () => {
|
||||
expect(mockConsoleWarn).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test("can catch an error in onmounted", async () => {
|
||||
class ErrorComponent extends Component {
|
||||
static template = xml`<div>Error!!!</div>`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onMounted(() => {
|
||||
throw new Error("error");
|
||||
});
|
||||
}
|
||||
}
|
||||
class PerfectComponent extends Component {
|
||||
static template = xml`<div>perfect</div>`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
class Main extends Component {
|
||||
static template = xml`Main<t t-if="state.ok" t-component="component"/>`;
|
||||
component: any;
|
||||
state: any;
|
||||
setup() {
|
||||
this.state = useState({ ok: false });
|
||||
useLogLifecycle();
|
||||
this.component = ErrorComponent;
|
||||
onError(() => {
|
||||
this.component = PerfectComponent;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const app = await mount(Main, fixture);
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Main:setup",
|
||||
"Main:willStart",
|
||||
"Main:willRender",
|
||||
"Main:rendered",
|
||||
"Main:mounted",
|
||||
]
|
||||
`);
|
||||
expect(fixture.innerHTML).toBe("Main");
|
||||
(app as any).state.ok = true;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("Main<div>Error!!!</div>");
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Main:willRender",
|
||||
"ErrorComponent:setup",
|
||||
"ErrorComponent:willStart",
|
||||
"Main:rendered",
|
||||
"ErrorComponent:willRender",
|
||||
"ErrorComponent:rendered",
|
||||
"Main:willPatch",
|
||||
"ErrorComponent:mounted",
|
||||
"Main:willRender",
|
||||
"PerfectComponent:setup",
|
||||
"PerfectComponent:willStart",
|
||||
"Main:rendered",
|
||||
]
|
||||
`);
|
||||
await nextTick();
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"PerfectComponent:willRender",
|
||||
"PerfectComponent:rendered",
|
||||
"Main:willPatch",
|
||||
"ErrorComponent:willUnmount",
|
||||
"ErrorComponent:willDestroy",
|
||||
"PerfectComponent:mounted",
|
||||
"Main:patched",
|
||||
]
|
||||
`);
|
||||
expect(fixture.innerHTML).toBe("Main<div>perfect</div>");
|
||||
});
|
||||
|
||||
test("calling a hook outside setup should crash", async () => {
|
||||
class Root extends Component {
|
||||
static template = xml`<t t-esc="state.value"/>`;
|
||||
@@ -1602,4 +1678,198 @@ describe("can catch errors", () => {
|
||||
`);
|
||||
expect(fixture.innerHTML).toBe("2");
|
||||
});
|
||||
|
||||
test("error in onMounted, graceful recovery", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`abc`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class OtherChild extends Component {
|
||||
static template = xml`def`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class Boom extends Component {
|
||||
static template = xml`boom`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onMounted(() => {
|
||||
throw new Error("boom");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`parent<Child/><Boom/>`;
|
||||
static components = { Child, Boom };
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class Root extends Component {
|
||||
static template = xml`<t t-component="component"/>`;
|
||||
|
||||
component: any = Parent;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onError(() => {
|
||||
logStep("error");
|
||||
this.component = OtherChild;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Root, fixture);
|
||||
expect(fixture.innerHTML).toBe("def");
|
||||
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Root:setup",
|
||||
"Root:willStart",
|
||||
"Root:willRender",
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Root:rendered",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Boom:setup",
|
||||
"Boom:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Boom:willRender",
|
||||
"Boom:rendered",
|
||||
"Boom:mounted",
|
||||
"error",
|
||||
"Root:willRender",
|
||||
"OtherChild:setup",
|
||||
"OtherChild:willStart",
|
||||
"Root:rendered",
|
||||
"OtherChild:willRender",
|
||||
"OtherChild:rendered",
|
||||
"OtherChild:mounted",
|
||||
"Root:mounted",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("error in onMounted, graceful recovery, variation", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`abc`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class OtherChild extends Component {
|
||||
static template = xml`def`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class Boom extends Component {
|
||||
static template = xml`boom`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onMounted(() => {
|
||||
throw new Error("boom");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`parent<Child/><Boom/>`;
|
||||
static components = { Child, Boom };
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class Root extends Component {
|
||||
static template = xml`R<t t-if="state.gogogo" t-component="component"/>`;
|
||||
|
||||
component: any = Parent;
|
||||
state = useState({ gogogo: false });
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onError(() => {
|
||||
logStep("error");
|
||||
this.component = OtherChild;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const root = await mount(Root, fixture);
|
||||
expect(fixture.innerHTML).toBe("R");
|
||||
|
||||
// standard mounting process
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Root:setup",
|
||||
"Root:willStart",
|
||||
"Root:willRender",
|
||||
"Root:rendered",
|
||||
"Root:mounted",
|
||||
]
|
||||
`);
|
||||
|
||||
root.state.gogogo = true;
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.innerHTML).toBe("Rparentabcboom");
|
||||
// rerender, root creates sub components, it crashes, tries to recover
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Root:willRender",
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Root:rendered",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Boom:setup",
|
||||
"Boom:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Boom:willRender",
|
||||
"Boom:rendered",
|
||||
"Root:willPatch",
|
||||
"Boom:mounted",
|
||||
"error",
|
||||
"Root:willRender",
|
||||
"OtherChild:setup",
|
||||
"OtherChild:willStart",
|
||||
"Root:rendered",
|
||||
]
|
||||
`);
|
||||
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("Rdef");
|
||||
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"OtherChild:willRender",
|
||||
"OtherChild:rendered",
|
||||
"Root:willPatch",
|
||||
"Child:willDestroy",
|
||||
"Boom:willUnmount",
|
||||
"Boom:willDestroy",
|
||||
"Parent:willDestroy",
|
||||
"OtherChild:mounted",
|
||||
"Root:patched",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,7 +140,6 @@ async function startRelease() {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
log(`Step ${step++}/${STEPS}: Creating the release...`);
|
||||
const relaseResult = await execCommand(`gh release create v${next} dist/*.js dist/*.zip ${draft} -F ${file}`);
|
||||
if (relaseResult !== 0) {
|
||||
|
||||
Reference in New Issue
Block a user