mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9edd31f755 |
+26
-72
@@ -4539,7 +4539,7 @@ class CodeGenerator {
|
|||||||
const isNewBlock = !block || forceNewBlock;
|
const isNewBlock = !block || forceNewBlock;
|
||||||
let codeIdx = this.target.code.length;
|
let codeIdx = this.target.code.length;
|
||||||
if (isNewBlock) {
|
if (isNewBlock) {
|
||||||
const n = ast.content.filter((c) => !c.hasNoRepresentation).length;
|
const n = ast.content.filter((c) => c.type !== 6 /* TSet */).length;
|
||||||
let result = null;
|
let result = null;
|
||||||
if (n <= 1) {
|
if (n <= 1) {
|
||||||
for (let child of ast.content) {
|
for (let child of ast.content) {
|
||||||
@@ -4553,15 +4553,15 @@ class CodeGenerator {
|
|||||||
let index = 0;
|
let index = 0;
|
||||||
for (let i = 0, l = ast.content.length; i < l; i++) {
|
for (let i = 0, l = ast.content.length; i < l; i++) {
|
||||||
const child = ast.content[i];
|
const child = ast.content[i];
|
||||||
const forceNewBlock = !child.hasNoRepresentation;
|
const isTSet = child.type === 6 /* TSet */;
|
||||||
const subCtx = createContext(ctx, {
|
const subCtx = createContext(ctx, {
|
||||||
block,
|
block,
|
||||||
index,
|
index,
|
||||||
forceNewBlock,
|
forceNewBlock: !isTSet,
|
||||||
isLast: ctx.isLast && i === l - 1,
|
isLast: ctx.isLast && i === l - 1,
|
||||||
});
|
});
|
||||||
this.compileAST(child, subCtx);
|
this.compileAST(child, subCtx);
|
||||||
if (forceNewBlock) {
|
if (!isTSet) {
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4979,9 +4979,9 @@ function parseNode(node, ctx) {
|
|||||||
parseTCallBlock(node) ||
|
parseTCallBlock(node) ||
|
||||||
parseTTranslation(node, ctx) ||
|
parseTTranslation(node, ctx) ||
|
||||||
parseTTranslationContext(node, ctx) ||
|
parseTTranslationContext(node, ctx) ||
|
||||||
parseTKey(node, ctx) ||
|
|
||||||
parseTEscNode(node, ctx) ||
|
parseTEscNode(node, ctx) ||
|
||||||
parseTOutNode(node, ctx) ||
|
parseTOutNode(node, ctx) ||
|
||||||
|
parseTKey(node, ctx) ||
|
||||||
parseTSlot(node, ctx) ||
|
parseTSlot(node, ctx) ||
|
||||||
parseComponent(node, ctx) ||
|
parseComponent(node, ctx) ||
|
||||||
parseDOMNode(node, ctx) ||
|
parseDOMNode(node, ctx) ||
|
||||||
@@ -5049,29 +5049,19 @@ function parseTCustom(node, ctx) {
|
|||||||
function parseTDebugLog(node, ctx) {
|
function parseTDebugLog(node, ctx) {
|
||||||
if (node.hasAttribute("t-debug")) {
|
if (node.hasAttribute("t-debug")) {
|
||||||
node.removeAttribute("t-debug");
|
node.removeAttribute("t-debug");
|
||||||
const content = parseNode(node, ctx);
|
return {
|
||||||
const ast = {
|
|
||||||
type: 12 /* TDebug */,
|
type: 12 /* TDebug */,
|
||||||
content,
|
content: parseNode(node, ctx),
|
||||||
};
|
};
|
||||||
if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
}
|
||||||
if (node.hasAttribute("t-log")) {
|
if (node.hasAttribute("t-log")) {
|
||||||
const expr = node.getAttribute("t-log");
|
const expr = node.getAttribute("t-log");
|
||||||
node.removeAttribute("t-log");
|
node.removeAttribute("t-log");
|
||||||
const content = parseNode(node, ctx);
|
return {
|
||||||
const ast = {
|
|
||||||
type: 13 /* TLog */,
|
type: 13 /* TLog */,
|
||||||
expr,
|
expr,
|
||||||
content,
|
content: parseNode(node, ctx),
|
||||||
};
|
};
|
||||||
if (content === null || content === void 0 ? void 0 : content.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -5301,19 +5291,11 @@ function parseTKey(node, ctx) {
|
|||||||
}
|
}
|
||||||
const key = node.getAttribute("t-key");
|
const key = node.getAttribute("t-key");
|
||||||
node.removeAttribute("t-key");
|
node.removeAttribute("t-key");
|
||||||
const content = parseNode(node, ctx);
|
const body = parseNode(node, ctx);
|
||||||
if (!content) {
|
if (!body) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const ast = {
|
return { type: 10 /* TKey */, expr: key, content: body };
|
||||||
type: 10 /* TKey */,
|
|
||||||
expr: key,
|
|
||||||
content,
|
|
||||||
};
|
|
||||||
if (content.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// t-call
|
// t-call
|
||||||
@@ -5422,7 +5404,7 @@ function parseTSetNode(node, ctx) {
|
|||||||
if (node.textContent !== node.innerHTML) {
|
if (node.textContent !== node.innerHTML) {
|
||||||
body = parseChildren(node, ctx);
|
body = parseChildren(node, ctx);
|
||||||
}
|
}
|
||||||
return { type: 6 /* TSet */, name, value, defaultValue, body, hasNoRepresentation: true };
|
return { type: 6 /* TSet */, name, value, defaultValue, body };
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Components
|
// Components
|
||||||
@@ -5601,51 +5583,30 @@ function parseTSlot(node, ctx) {
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Translation
|
// Translation
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
function wrapInTTranslationAST(r) {
|
|
||||||
const ast = { type: 16 /* TTranslation */, content: r };
|
|
||||||
if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
function parseTTranslation(node, ctx) {
|
function parseTTranslation(node, ctx) {
|
||||||
if (node.getAttribute("t-translation") !== "off") {
|
if (node.getAttribute("t-translation") !== "off") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
node.removeAttribute("t-translation");
|
node.removeAttribute("t-translation");
|
||||||
const result = parseNode(node, ctx);
|
return {
|
||||||
if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* Multi */) {
|
type: 16 /* TTranslation */,
|
||||||
const children = result.content.map(wrapInTTranslationAST);
|
content: parseNode(node, ctx),
|
||||||
return makeASTMulti(children);
|
};
|
||||||
}
|
|
||||||
return wrapInTTranslationAST(result);
|
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Translation Context
|
// Translation Context
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
function wrapInTTranslationContextAST(r, translationCtx) {
|
|
||||||
const ast = {
|
|
||||||
type: 17 /* TTranslationContext */,
|
|
||||||
content: r,
|
|
||||||
translationCtx,
|
|
||||||
};
|
|
||||||
if (r === null || r === void 0 ? void 0 : r.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
function parseTTranslationContext(node, ctx) {
|
function parseTTranslationContext(node, ctx) {
|
||||||
const translationCtx = node.getAttribute("t-translation-context");
|
const translationCtx = node.getAttribute("t-translation-context");
|
||||||
if (!translationCtx) {
|
if (!translationCtx) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
node.removeAttribute("t-translation-context");
|
node.removeAttribute("t-translation-context");
|
||||||
const result = parseNode(node, ctx);
|
return {
|
||||||
if ((result === null || result === void 0 ? void 0 : result.type) === 3 /* Multi */) {
|
type: 17 /* TTranslationContext */,
|
||||||
const children = result.content.map((c) => wrapInTTranslationContextAST(c, translationCtx));
|
content: parseNode(node, ctx),
|
||||||
return makeASTMulti(children);
|
translationCtx,
|
||||||
}
|
};
|
||||||
return wrapInTTranslationContextAST(result, translationCtx);
|
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Portal
|
// Portal
|
||||||
@@ -5690,13 +5651,6 @@ function parseChildren(node, ctx) {
|
|||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
function makeASTMulti(children) {
|
|
||||||
const ast = { type: 3 /* Multi */, content: children };
|
|
||||||
if (children.every((c) => c.hasNoRepresentation)) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Parse all the child nodes of a given node and return an ast if possible.
|
* Parse all the child nodes of a given node and return an ast if possible.
|
||||||
* In the case there are multiple children, they are wrapped in a astmulti.
|
* In the case there are multiple children, they are wrapped in a astmulti.
|
||||||
@@ -5709,7 +5663,7 @@ function parseChildNodes(node, ctx) {
|
|||||||
case 1:
|
case 1:
|
||||||
return children[0];
|
return children[0];
|
||||||
default:
|
default:
|
||||||
return makeASTMulti(children);
|
return { type: 3 /* Multi */, content: children };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -5813,7 +5767,7 @@ function compile(template, options = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do not modify manually. This file is generated by the release script.
|
// do not modify manually. This file is generated by the release script.
|
||||||
const version = "2.8.1";
|
const version = "2.8.0";
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Scheduler
|
// Scheduler
|
||||||
@@ -6284,6 +6238,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
|
|||||||
export { App, Component, EventBus, OwlError, __info__, batched, blockDom, htmlEscape, 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 };
|
export { App, Component, EventBus, OwlError, __info__, batched, blockDom, htmlEscape, 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 = '2025-09-23T07:17:45.055Z';
|
__info__.date = '2025-06-30T12:46:06.424Z';
|
||||||
__info__.hash = '5211116';
|
__info__.hash = 'b620502';
|
||||||
__info__.url = 'https://github.com/odoo/owl';
|
__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|||||||
Generated
+3233
-6113
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.8.1",
|
"version": "2.8.0",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"module": "dist/owl.es.js",
|
"module": "dist/owl.es.js",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"build:devtools-chrome": "npm run dev:devtools-chrome -- --config-env=production",
|
"build:devtools-chrome": "npm run dev:devtools-chrome -- --config-env=production",
|
||||||
"build:devtools-firefox": "npm run dev:devtools-firefox -- --config-env=production",
|
"build:devtools-firefox": "npm run dev:devtools-firefox -- --config-env=production",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:debug": "node node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
|
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"playground:serve": "python3 tools/playground_server.py || python tools/playground_server.py",
|
"playground:serve": "python3 tools/playground_server.py || python tools/playground_server.py",
|
||||||
"playground": "npm run build && npm run playground:serve",
|
"playground": "npm run build && npm run playground:serve",
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# encountered issues
|
|
||||||
## dropdown issue
|
|
||||||
- there was a problem that writing in a state while the effect was updated.
|
|
||||||
- the tracking of signal being written were dropped because we cleared it
|
|
||||||
after re-running the effect that made a write.
|
|
||||||
- solution: clear the tracked signal before re-executing the effects
|
|
||||||
- reading signal A while also writing signal A makes an infinite loop
|
|
||||||
- current solution: use toRaw in order to not track the read
|
|
||||||
- possible better solution to explore: do not track read if there is a write in a effect.
|
|
||||||
## website issue
|
|
||||||
- a rpc request was made on onWillStart, onWillStart was tracking reads. (see WebsiteBuilderClientAction)
|
|
||||||
- The read subsequently made a write, that re-triggered the onWillStart.
|
|
||||||
- A similar situation happened with onWillUpdateProps (see Transition)
|
|
||||||
- solution: prevent tracking reads in onWillStart and onWillUpdateProps
|
|
||||||
|
|
||||||
# future
|
|
||||||
- worker for computation?
|
|
||||||
- cap'n web
|
|
||||||
@@ -1,28 +1,4 @@
|
|||||||
export type ExecutionContext = {
|
|
||||||
onReadAtom: (atom: Atom) => void;
|
|
||||||
unsubcribe?: (scheduledContexts: Set<ExecutionContext>) => void;
|
|
||||||
update?: Function;
|
|
||||||
atoms?: Set<Atom>;
|
|
||||||
meta?: any;
|
|
||||||
// getParent: () => ExecutionContext | undefined;
|
|
||||||
// getChildren: () => ExecutionContext[];
|
|
||||||
// schedule: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type customDirectives = Record<
|
export type customDirectives = Record<
|
||||||
string,
|
string,
|
||||||
(node: Element, value: string, modifier: string[]) => void
|
(node: Element, value: string, modifier: string[]) => void
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type Atom = {
|
|
||||||
executionContexts: Set<ExecutionContext>;
|
|
||||||
dependents: Set<DerivedAtom>;
|
|
||||||
getValue: () => any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OldValue = any;
|
|
||||||
|
|
||||||
export type DerivedAtom = Atom & {
|
|
||||||
dependencies: Map<Atom, OldValue>;
|
|
||||||
computed: boolean;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -995,7 +995,7 @@ export class CodeGenerator {
|
|||||||
const isNewBlock = !block || forceNewBlock;
|
const isNewBlock = !block || forceNewBlock;
|
||||||
let codeIdx = this.target.code.length;
|
let codeIdx = this.target.code.length;
|
||||||
if (isNewBlock) {
|
if (isNewBlock) {
|
||||||
const n = ast.content.filter((c) => !c.hasNoRepresentation).length;
|
const n = ast.content.filter((c) => c.type !== ASTType.TSet).length;
|
||||||
let result: string | null = null;
|
let result: string | null = null;
|
||||||
if (n <= 1) {
|
if (n <= 1) {
|
||||||
for (let child of ast.content) {
|
for (let child of ast.content) {
|
||||||
@@ -1009,15 +1009,15 @@ export class CodeGenerator {
|
|||||||
let index = 0;
|
let index = 0;
|
||||||
for (let i = 0, l = ast.content.length; i < l; i++) {
|
for (let i = 0, l = ast.content.length; i < l; i++) {
|
||||||
const child = ast.content[i];
|
const child = ast.content[i];
|
||||||
const forceNewBlock = !child.hasNoRepresentation;
|
const isTSet = child.type === ASTType.TSet;
|
||||||
const subCtx = createContext(ctx, {
|
const subCtx = createContext(ctx, {
|
||||||
block,
|
block,
|
||||||
index,
|
index,
|
||||||
forceNewBlock,
|
forceNewBlock: !isTSet,
|
||||||
isLast: ctx.isLast && i === l - 1,
|
isLast: ctx.isLast && i === l - 1,
|
||||||
});
|
});
|
||||||
this.compileAST(child, subCtx);
|
this.compileAST(child, subCtx);
|
||||||
if (forceNewBlock) {
|
if (!isTSet) {
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-93
@@ -31,17 +31,12 @@ export const enum ASTType {
|
|||||||
TPortal,
|
TPortal,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseAST {
|
export interface ASTText {
|
||||||
type: ASTType;
|
|
||||||
hasNoRepresentation?: true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ASTText extends BaseAST {
|
|
||||||
type: ASTType.Text;
|
type: ASTType.Text;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTComment extends BaseAST {
|
export interface ASTComment {
|
||||||
type: ASTType.Comment;
|
type: ASTType.Comment;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
@@ -57,7 +52,7 @@ interface TModelInfo {
|
|||||||
specialInitTargetAttr: string | null;
|
specialInitTargetAttr: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTDomNode extends BaseAST {
|
export interface ASTDomNode {
|
||||||
type: ASTType.DomNode;
|
type: ASTType.DomNode;
|
||||||
tag: string;
|
tag: string;
|
||||||
content: AST[];
|
content: AST[];
|
||||||
@@ -70,24 +65,24 @@ export interface ASTDomNode extends BaseAST {
|
|||||||
ns: string | null;
|
ns: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTMulti extends BaseAST {
|
export interface ASTMulti {
|
||||||
type: ASTType.Multi;
|
type: ASTType.Multi;
|
||||||
content: AST[];
|
content: AST[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTEsc extends BaseAST {
|
export interface ASTTEsc {
|
||||||
type: ASTType.TEsc;
|
type: ASTType.TEsc;
|
||||||
expr: string;
|
expr: string;
|
||||||
defaultValue: string;
|
defaultValue: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTOut extends BaseAST {
|
export interface ASTTOut {
|
||||||
type: ASTType.TOut;
|
type: ASTType.TOut;
|
||||||
expr: string;
|
expr: string;
|
||||||
body: AST[] | null;
|
body: AST[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTif extends BaseAST {
|
export interface ASTTif {
|
||||||
type: ASTType.TIf;
|
type: ASTType.TIf;
|
||||||
condition: string;
|
condition: string;
|
||||||
content: AST;
|
content: AST;
|
||||||
@@ -95,16 +90,15 @@ export interface ASTTif extends BaseAST {
|
|||||||
tElse: AST | null;
|
tElse: AST | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTSet extends BaseAST {
|
export interface ASTTSet {
|
||||||
type: ASTType.TSet;
|
type: ASTType.TSet;
|
||||||
name: string;
|
name: string;
|
||||||
value: string | null; // value defined in attribute
|
value: string | null; // value defined in attribute
|
||||||
defaultValue: string | null; // value defined in body, if text
|
defaultValue: string | null; // value defined in body, if text
|
||||||
body: AST[] | null; // content of body if not text
|
body: AST[] | null; // content of body if not text
|
||||||
hasNoRepresentation: true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTForEach extends BaseAST {
|
export interface ASTTForEach {
|
||||||
type: ASTType.TForEach;
|
type: ASTType.TForEach;
|
||||||
collection: string;
|
collection: string;
|
||||||
elem: string;
|
elem: string;
|
||||||
@@ -117,13 +111,13 @@ export interface ASTTForEach extends BaseAST {
|
|||||||
key: string | null;
|
key: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTKey extends BaseAST {
|
export interface ASTTKey {
|
||||||
type: ASTType.TKey;
|
type: ASTType.TKey;
|
||||||
expr: string;
|
expr: string;
|
||||||
content: AST;
|
content: AST;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTCall extends BaseAST {
|
export interface ASTTCall {
|
||||||
type: ASTType.TCall;
|
type: ASTType.TCall;
|
||||||
name: string;
|
name: string;
|
||||||
body: AST[] | null;
|
body: AST[] | null;
|
||||||
@@ -138,7 +132,7 @@ interface SlotDefinition {
|
|||||||
attrsTranslationCtx: Attrs | null;
|
attrsTranslationCtx: Attrs | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTComponent extends BaseAST {
|
export interface ASTComponent {
|
||||||
type: ASTType.TComponent;
|
type: ASTType.TComponent;
|
||||||
name: string;
|
name: string;
|
||||||
isDynamic: boolean;
|
isDynamic: boolean;
|
||||||
@@ -149,7 +143,7 @@ export interface ASTComponent extends BaseAST {
|
|||||||
slots: { [name: string]: SlotDefinition } | null;
|
slots: { [name: string]: SlotDefinition } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTSlot extends BaseAST {
|
export interface ASTSlot {
|
||||||
type: ASTType.TSlot;
|
type: ASTType.TSlot;
|
||||||
name: string;
|
name: string;
|
||||||
attrs: Attrs | null;
|
attrs: Attrs | null;
|
||||||
@@ -158,34 +152,34 @@ export interface ASTSlot extends BaseAST {
|
|||||||
defaultContent: AST | null;
|
defaultContent: AST | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTCallBlock extends BaseAST {
|
export interface ASTTCallBlock {
|
||||||
type: ASTType.TCallBlock;
|
type: ASTType.TCallBlock;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTDebug extends BaseAST {
|
export interface ASTDebug {
|
||||||
type: ASTType.TDebug;
|
type: ASTType.TDebug;
|
||||||
content: AST | null;
|
content: AST | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTLog extends BaseAST {
|
export interface ASTLog {
|
||||||
type: ASTType.TLog;
|
type: ASTType.TLog;
|
||||||
expr: string;
|
expr: string;
|
||||||
content: AST | null;
|
content: AST | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTranslation extends BaseAST {
|
export interface ASTTranslation {
|
||||||
type: ASTType.TTranslation;
|
type: ASTType.TTranslation;
|
||||||
content: AST | null;
|
content: AST | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTranslationContext extends BaseAST {
|
export interface ASTTranslationContext {
|
||||||
type: ASTType.TTranslationContext;
|
type: ASTType.TTranslationContext;
|
||||||
content: AST | null;
|
content: AST | null;
|
||||||
translationCtx: string;
|
translationCtx: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTPortal extends BaseAST {
|
export interface ASTTPortal {
|
||||||
type: ASTType.TPortal;
|
type: ASTType.TPortal;
|
||||||
target: string;
|
target: string;
|
||||||
content: AST;
|
content: AST;
|
||||||
@@ -261,9 +255,9 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
|||||||
parseTCallBlock(node, ctx) ||
|
parseTCallBlock(node, ctx) ||
|
||||||
parseTTranslation(node, ctx) ||
|
parseTTranslation(node, ctx) ||
|
||||||
parseTTranslationContext(node, ctx) ||
|
parseTTranslationContext(node, ctx) ||
|
||||||
parseTKey(node, ctx) ||
|
|
||||||
parseTEscNode(node, ctx) ||
|
parseTEscNode(node, ctx) ||
|
||||||
parseTOutNode(node, ctx) ||
|
parseTOutNode(node, ctx) ||
|
||||||
|
parseTKey(node, ctx) ||
|
||||||
parseTSlot(node, ctx) ||
|
parseTSlot(node, ctx) ||
|
||||||
parseComponent(node, ctx) ||
|
parseComponent(node, ctx) ||
|
||||||
parseDOMNode(node, ctx) ||
|
parseDOMNode(node, ctx) ||
|
||||||
@@ -340,30 +334,20 @@ function parseTCustom(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null {
|
function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null {
|
||||||
if (node.hasAttribute("t-debug")) {
|
if (node.hasAttribute("t-debug")) {
|
||||||
node.removeAttribute("t-debug");
|
node.removeAttribute("t-debug");
|
||||||
const content = parseNode(node, ctx);
|
return {
|
||||||
const ast: ASTDebug = {
|
|
||||||
type: ASTType.TDebug,
|
type: ASTType.TDebug,
|
||||||
content,
|
content: parseNode(node, ctx),
|
||||||
};
|
};
|
||||||
if (content?.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.hasAttribute("t-log")) {
|
if (node.hasAttribute("t-log")) {
|
||||||
const expr = node.getAttribute("t-log")!;
|
const expr = node.getAttribute("t-log")!;
|
||||||
node.removeAttribute("t-log");
|
node.removeAttribute("t-log");
|
||||||
const content = parseNode(node, ctx);
|
return {
|
||||||
const ast: ASTLog = {
|
|
||||||
type: ASTType.TLog,
|
type: ASTType.TLog,
|
||||||
expr,
|
expr,
|
||||||
content,
|
content: parseNode(node, ctx),
|
||||||
};
|
};
|
||||||
if (content?.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -614,19 +598,11 @@ function parseTKey(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
}
|
}
|
||||||
const key = node.getAttribute("t-key")!;
|
const key = node.getAttribute("t-key")!;
|
||||||
node.removeAttribute("t-key");
|
node.removeAttribute("t-key");
|
||||||
const content = parseNode(node, ctx);
|
const body = parseNode(node, ctx);
|
||||||
if (!content) {
|
if (!body) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const ast: ASTTKey = {
|
return { type: ASTType.TKey, expr: key, content: body };
|
||||||
type: ASTType.TKey,
|
|
||||||
expr: key,
|
|
||||||
content,
|
|
||||||
};
|
|
||||||
if (content.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -748,7 +724,7 @@ function parseTSetNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
if (node.textContent !== node.innerHTML) {
|
if (node.textContent !== node.innerHTML) {
|
||||||
body = parseChildren(node, ctx);
|
body = parseChildren(node, ctx);
|
||||||
}
|
}
|
||||||
return { type: ASTType.TSet, name, value, defaultValue, body, hasNoRepresentation: true };
|
return { type: ASTType.TSet, name, value, defaultValue, body };
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -940,55 +916,32 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
// Translation
|
// Translation
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
function wrapInTTranslationAST(r: AST | null) {
|
|
||||||
const ast: ASTTranslation = { type: ASTType.TTranslation, content: r };
|
|
||||||
if (r?.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
|
function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
|
||||||
if (node.getAttribute("t-translation") !== "off") {
|
if (node.getAttribute("t-translation") !== "off") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
node.removeAttribute("t-translation");
|
node.removeAttribute("t-translation");
|
||||||
const result = parseNode(node, ctx);
|
return {
|
||||||
if (result?.type === ASTType.Multi) {
|
type: ASTType.TTranslation,
|
||||||
const children = result.content.map(wrapInTTranslationAST);
|
content: parseNode(node, ctx),
|
||||||
return makeASTMulti(children);
|
};
|
||||||
}
|
|
||||||
return wrapInTTranslationAST(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Translation Context
|
// Translation Context
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
function wrapInTTranslationContextAST(r: AST | null, translationCtx: string) {
|
|
||||||
const ast: ASTTranslationContext = {
|
|
||||||
type: ASTType.TTranslationContext,
|
|
||||||
content: r,
|
|
||||||
translationCtx,
|
|
||||||
};
|
|
||||||
if (r?.hasNoRepresentation) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseTTranslationContext(node: Element, ctx: ParsingContext): AST | null {
|
function parseTTranslationContext(node: Element, ctx: ParsingContext): AST | null {
|
||||||
const translationCtx = node.getAttribute("t-translation-context");
|
const translationCtx = node.getAttribute("t-translation-context");
|
||||||
if (!translationCtx) {
|
if (!translationCtx) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
node.removeAttribute("t-translation-context");
|
node.removeAttribute("t-translation-context");
|
||||||
const result = parseNode(node, ctx);
|
return {
|
||||||
if (result?.type === ASTType.Multi) {
|
type: ASTType.TTranslationContext,
|
||||||
const children = result.content.map((c) => wrapInTTranslationContextAST(c, translationCtx));
|
content: parseNode(node, ctx),
|
||||||
return makeASTMulti(children);
|
translationCtx,
|
||||||
}
|
};
|
||||||
return wrapInTTranslationContextAST(result, translationCtx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -1037,14 +990,6 @@ function parseChildren(node: Element, ctx: ParsingContext): AST[] {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeASTMulti(children: AST[]) {
|
|
||||||
const ast: ASTMulti = { type: ASTType.Multi, content: children };
|
|
||||||
if (children.every((c) => c.hasNoRepresentation)) {
|
|
||||||
ast.hasNoRepresentation = true;
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse all the child nodes of a given node and return an ast if possible.
|
* Parse all the child nodes of a given node and return an ast if possible.
|
||||||
* In the case there are multiple children, they are wrapped in a astmulti.
|
* In the case there are multiple children, they are wrapped in a astmulti.
|
||||||
@@ -1057,7 +1002,7 @@ function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
case 1:
|
case 1:
|
||||||
return children[0];
|
return children[0];
|
||||||
default:
|
default:
|
||||||
return makeASTMulti(children);
|
return { type: ASTType.Multi, content: children };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
export type TaskContext = { isCancelled: boolean; cancel: () => void; meta: Record<string, any> };
|
|
||||||
|
|
||||||
export const taskContextStack: TaskContext[] = [];
|
|
||||||
|
|
||||||
export function getTaskContext() {
|
|
||||||
return taskContextStack[taskContextStack.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeTaskContext(): TaskContext {
|
|
||||||
let isCancelled = false;
|
|
||||||
return {
|
|
||||||
get isCancelled() {
|
|
||||||
return isCancelled;
|
|
||||||
},
|
|
||||||
cancel() {
|
|
||||||
isCancelled = true;
|
|
||||||
},
|
|
||||||
meta: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTaskContext(ctx?: TaskContext) {
|
|
||||||
ctx ??= makeTaskContext();
|
|
||||||
taskContextStack.push(ctx);
|
|
||||||
return {
|
|
||||||
ctx,
|
|
||||||
cleanup: () => {
|
|
||||||
taskContextStack.pop();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pushTaskContext(context: TaskContext) {
|
|
||||||
taskContextStack.push(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function popTaskContext() {
|
|
||||||
taskContextStack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function taskEffect(fn: Function) {
|
|
||||||
const { ctx, cleanup } = useTaskContext();
|
|
||||||
fn();
|
|
||||||
cleanup();
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import { OwlError } from "../common/owl_error";
|
|
||||||
import { Atom, ExecutionContext } from "../common/types";
|
|
||||||
import type { App, Env } from "./app";
|
import type { App, Env } from "./app";
|
||||||
import { BDom, VNode } from "./blockdom";
|
import { BDom, VNode } from "./blockdom";
|
||||||
import { makeTaskContext, TaskContext } from "./cancellableContext";
|
|
||||||
import { Component, ComponentConstructor, Props } from "./component";
|
import { Component, ComponentConstructor, Props } from "./component";
|
||||||
import { fibersInError } from "./error_handling";
|
import { fibersInError } from "./error_handling";
|
||||||
|
import { OwlError } from "../common/owl_error";
|
||||||
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
|
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
|
||||||
import { addAtomToContext, reactive, targets, withoutReactivity } from "./reactivity";
|
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
|
import { batched, Callback } from "./utils";
|
||||||
|
|
||||||
let currentNode: ComponentNode | null = null;
|
let currentNode: ComponentNode | null = null;
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ function applyDefaultProps<P extends object>(props: P, defaultProps: Partial<P>)
|
|||||||
// Integration with reactivity system (useState)
|
// Integration with reactivity system (useState)
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
||||||
/**
|
/**
|
||||||
* Creates a reactive object that will be observed by the current component.
|
* Creates a reactive object that will be observed by the current component.
|
||||||
* Reading data from the returned object (eg during rendering) will cause the
|
* Reading data from the returned object (eg during rendering) will cause the
|
||||||
@@ -55,7 +54,15 @@ function applyDefaultProps<P extends object>(props: P, defaultProps: Partial<P>)
|
|||||||
* @see reactive
|
* @see reactive
|
||||||
*/
|
*/
|
||||||
export function useState<T extends object>(state: T): T {
|
export function useState<T extends object>(state: T): T {
|
||||||
return reactive(state);
|
const node = getCurrent();
|
||||||
|
let render = batchedRenderFunctions.get(node)!;
|
||||||
|
if (!render) {
|
||||||
|
render = batched(node.render.bind(node, false));
|
||||||
|
batchedRenderFunctions.set(node, render);
|
||||||
|
// manual implementation of onWillDestroy to break cyclic dependency
|
||||||
|
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
||||||
|
}
|
||||||
|
return reactive(state, render);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -89,8 +96,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
willPatch: LifecycleHook[] = [];
|
willPatch: LifecycleHook[] = [];
|
||||||
patched: LifecycleHook[] = [];
|
patched: LifecycleHook[] = [];
|
||||||
willDestroy: LifecycleHook[] = [];
|
willDestroy: LifecycleHook[] = [];
|
||||||
taskContext: TaskContext;
|
|
||||||
executionContext: ExecutionContext;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
C: ComponentConstructor<P, E>,
|
C: ComponentConstructor<P, E>,
|
||||||
@@ -104,15 +109,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.props = props;
|
this.props = props;
|
||||||
this.parentKey = parentKey;
|
this.parentKey = parentKey;
|
||||||
this.taskContext = makeTaskContext();
|
|
||||||
this.executionContext = {
|
|
||||||
meta: this,
|
|
||||||
update: () => {
|
|
||||||
this.render(false);
|
|
||||||
},
|
|
||||||
onReadAtom: (atom: Atom) => addAtomToContext(atom, this.executionContext),
|
|
||||||
atoms: new Set<Atom>(),
|
|
||||||
};
|
|
||||||
const defaultProps = C.defaultProps;
|
const defaultProps = C.defaultProps;
|
||||||
props = Object.assign({}, props);
|
props = Object.assign({}, props);
|
||||||
if (defaultProps) {
|
if (defaultProps) {
|
||||||
@@ -120,18 +116,16 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
}
|
}
|
||||||
const env = (parent && parent.childEnv) || app.env;
|
const env = (parent && parent.childEnv) || app.env;
|
||||||
this.childEnv = env;
|
this.childEnv = env;
|
||||||
// for (const key in props) {
|
for (const key in props) {
|
||||||
// const prop = props[key];
|
const prop = props[key];
|
||||||
// if (prop && typeof prop === "object" && targets.has(prop)) {
|
if (prop && typeof prop === "object" && targets.has(prop)) {
|
||||||
// props[key] = useState(prop);
|
props[key] = useState(prop);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
this.component = new C(props, env, this);
|
this.component = new C(props, env, this);
|
||||||
const ctx = Object.assign(Object.create(this.component), { this: this.component });
|
const ctx = Object.assign(Object.create(this.component), { this: this.component });
|
||||||
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
|
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
|
||||||
withoutReactivity(() => {
|
|
||||||
this.component.setup();
|
this.component.setup();
|
||||||
});
|
|
||||||
currentNode = null;
|
currentNode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,11 +142,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
}
|
}
|
||||||
const component = this.component;
|
const component = this.component;
|
||||||
try {
|
try {
|
||||||
let prom: Promise<any[]>;
|
await Promise.all(this.willStart.map((f) => f.call(component)));
|
||||||
withoutReactivity(() => {
|
|
||||||
prom = Promise.all(this.willStart.map((f) => f.call(component)));
|
|
||||||
});
|
|
||||||
await prom!;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.app.handleError({ node: this, error: e });
|
this.app.handleError({ node: this, error: e });
|
||||||
return;
|
return;
|
||||||
@@ -268,18 +258,15 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentNode = this;
|
currentNode = this;
|
||||||
// for (const key in props) {
|
for (const key in props) {
|
||||||
// const prop = props[key];
|
const prop = props[key];
|
||||||
// if (prop && typeof prop === "object" && targets.has(prop)) {
|
if (prop && typeof prop === "object" && targets.has(prop)) {
|
||||||
// props[key] = useState(prop);
|
props[key] = useState(prop);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
currentNode = null;
|
currentNode = null;
|
||||||
let prom: Promise<any[]>;
|
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||||
withoutReactivity(() => {
|
await prom;
|
||||||
prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
|
||||||
});
|
|
||||||
await prom!;
|
|
||||||
if (fiber !== this.fiber) {
|
if (fiber !== this.fiber) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -397,8 +384,8 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
return this.component.constructor.name;
|
return this.component.constructor.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get subscriptions(): ReturnType<typeof getSubscriptions> {
|
get subscriptions(): ReturnType<typeof getSubscriptions> {
|
||||||
// const render = batchedRenderFunctions.get(this);
|
const render = batchedRenderFunctions.get(this);
|
||||||
// return render ? getSubscriptions(render) : [];
|
return render ? getSubscriptions(render) : [];
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { ExecutionContext } from "../common/types";
|
|
||||||
|
|
||||||
export const executionContexts: ExecutionContext[] = [];
|
|
||||||
(window as any).executionContexts = executionContexts;
|
|
||||||
// export const scheduledContexts: Set<ExecutionContext> = new Set();
|
|
||||||
|
|
||||||
export function getExecutionContext() {
|
|
||||||
return executionContexts[executionContexts.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pushExecutionContext(context: ExecutionContext) {
|
|
||||||
executionContexts.push(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function popExecutionContext() {
|
|
||||||
executionContexts.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// export function makeExecutionContext({ update, meta }: { update: () => void; meta?: any }) {
|
|
||||||
// const executionContext: ExecutionContext = {
|
|
||||||
// update,
|
|
||||||
// atoms: new Set(),
|
|
||||||
// meta: meta || {},
|
|
||||||
// };
|
|
||||||
// return executionContext;
|
|
||||||
// }
|
|
||||||
@@ -3,8 +3,6 @@ import type { ComponentNode } from "./component_node";
|
|||||||
import { fibersInError } from "./error_handling";
|
import { fibersInError } from "./error_handling";
|
||||||
import { OwlError } from "../common/owl_error";
|
import { OwlError } from "../common/owl_error";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
import { popTaskContext, pushTaskContext } from "./cancellableContext";
|
|
||||||
import { popExecutionContext, pushExecutionContext } from "./executionContext";
|
|
||||||
|
|
||||||
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
||||||
let current = node.fiber;
|
let current = node.fiber;
|
||||||
@@ -135,16 +133,12 @@ export class Fiber {
|
|||||||
const node = this.node;
|
const node = this.node;
|
||||||
const root = this.root;
|
const root = this.root;
|
||||||
if (root) {
|
if (root) {
|
||||||
pushTaskContext(node.taskContext);
|
|
||||||
pushExecutionContext(node.executionContext);
|
|
||||||
try {
|
try {
|
||||||
(this.bdom as any) = true;
|
(this.bdom as any) = true;
|
||||||
this.bdom = node.renderFn();
|
this.bdom = node.renderFn();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
node.app.handleError({ node, error: e });
|
node.app.handleError({ node, error: e });
|
||||||
}
|
}
|
||||||
popExecutionContext();
|
|
||||||
popTaskContext();
|
|
||||||
root.setCounter(root.counter - 1);
|
root.setCounter(root.counter - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-27
@@ -1,6 +1,5 @@
|
|||||||
import type { Env } from "./app";
|
import type { Env } from "./app";
|
||||||
import { getCurrent } from "./component_node";
|
import { getCurrent } from "./component_node";
|
||||||
import { popExecutionContext, pushExecutionContext } from "./executionContext";
|
|
||||||
import { onMounted, onPatched, onWillUnmount } from "./lifecycle_hooks";
|
import { onMounted, onPatched, onWillUnmount } from "./lifecycle_hooks";
|
||||||
import { inOwnerDocument } from "./utils";
|
import { inOwnerDocument } from "./utils";
|
||||||
|
|
||||||
@@ -87,43 +86,22 @@ export function useEffect<T extends unknown[]>(
|
|||||||
effect: Effect<T>,
|
effect: Effect<T>,
|
||||||
computeDependencies: () => [...T] = () => [NaN] as never
|
computeDependencies: () => [...T] = () => [NaN] as never
|
||||||
) {
|
) {
|
||||||
const context = getCurrent().component.__owl__.executionContext;
|
|
||||||
let cleanup: (() => void) | void;
|
let cleanup: (() => void) | void;
|
||||||
let dependencies: T;
|
let dependencies: T;
|
||||||
|
|
||||||
const runEffect = () => {
|
|
||||||
pushExecutionContext(context);
|
|
||||||
try {
|
|
||||||
cleanup = effect(...dependencies);
|
|
||||||
} finally {
|
|
||||||
popExecutionContext();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const computeDependenciesWithContext = () => {
|
|
||||||
pushExecutionContext(context);
|
|
||||||
let r: any;
|
|
||||||
try {
|
|
||||||
r = computeDependencies();
|
|
||||||
} finally {
|
|
||||||
popExecutionContext();
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
dependencies = computeDependenciesWithContext();
|
dependencies = computeDependencies();
|
||||||
runEffect();
|
cleanup = effect(...dependencies);
|
||||||
});
|
});
|
||||||
|
|
||||||
onPatched(() => {
|
onPatched(() => {
|
||||||
const newDeps = computeDependenciesWithContext();
|
const newDeps = computeDependencies();
|
||||||
const shouldReapply = newDeps.some((val: any, i: number) => val !== dependencies[i]);
|
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
||||||
if (shouldReapply) {
|
if (shouldReapply) {
|
||||||
dependencies = newDeps;
|
dependencies = newDeps;
|
||||||
if (cleanup) {
|
if (cleanup) {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
runEffect();
|
cleanup = effect(...dependencies);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export { Component } from "./component";
|
|||||||
export type { ComponentConstructor } from "./component";
|
export type { ComponentConstructor } from "./component";
|
||||||
export { useComponent, useState } from "./component_node";
|
export { useComponent, useState } from "./component_node";
|
||||||
export { status } from "./status";
|
export { status } from "./status";
|
||||||
export { reactive, markRaw, toRaw, effect, withoutReactivity } from "./reactivity";
|
export { reactive, markRaw, toRaw } from "./reactivity";
|
||||||
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
|
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
|
||||||
export { batched, EventBus, htmlEscape, whenReady, loadFile, markup } from "./utils";
|
export { batched, EventBus, htmlEscape, whenReady, loadFile, markup } from "./utils";
|
||||||
export {
|
export {
|
||||||
|
|||||||
+141
-270
@@ -1,9 +1,13 @@
|
|||||||
|
import type { Callback } from "./utils";
|
||||||
import { OwlError } from "../common/owl_error";
|
import { OwlError } from "../common/owl_error";
|
||||||
import { ExecutionContext, Atom, DerivedAtom, OldValue } from "../common/types";
|
|
||||||
import { getExecutionContext, popExecutionContext, pushExecutionContext } from "./executionContext";
|
|
||||||
|
|
||||||
// Special key to subscribe to, to be notified of key creation/deletion
|
// Special key to subscribe to, to be notified of key creation/deletion
|
||||||
const KEYCHANGES = Symbol("Key changes");
|
const KEYCHANGES = Symbol("Key changes");
|
||||||
|
// Used to specify the absence of a callback, can be used as WeakMap key but
|
||||||
|
// should only be used as a sentinel value and never called.
|
||||||
|
const NO_CALLBACK = () => {
|
||||||
|
throw new Error("Called NO_CALLBACK. Owl is broken, please report this to the maintainers.");
|
||||||
|
};
|
||||||
|
|
||||||
// The following types only exist to signify places where objects are expected
|
// The following types only exist to signify places where objects are expected
|
||||||
// to be reactive or not, they provide no type checking benefit over "object"
|
// to be reactive or not, they provide no type checking benefit over "object"
|
||||||
@@ -51,8 +55,8 @@ function canBeMadeReactive(value: any): boolean {
|
|||||||
* @param value the value make reactive
|
* @param value the value make reactive
|
||||||
* @returns a reactive for the given object when possible, the original otherwise
|
* @returns a reactive for the given object when possible, the original otherwise
|
||||||
*/
|
*/
|
||||||
function possiblyReactive(val: any) {
|
function possiblyReactive(val: any, cb: Callback) {
|
||||||
return canBeMadeReactive(val) ? reactive(val) : val;
|
return canBeMadeReactive(val) ? reactive(val, cb) : val;
|
||||||
}
|
}
|
||||||
|
|
||||||
const skipped = new WeakSet<Target>();
|
const skipped = new WeakSet<Target>();
|
||||||
@@ -77,37 +81,7 @@ export function toRaw<T extends Target, U extends Reactive<T>>(value: U | T): T
|
|||||||
return targets.has(value) ? (targets.get(value) as T) : value;
|
return targets.has(value) ? (targets.get(value) as T) : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetToKeysToAtomItem = new WeakMap<Target, Map<PropertyKey, Atom>>();
|
const targetToKeysToCallbacks = new WeakMap<Target, Map<PropertyKey, Set<Callback>>>();
|
||||||
const scheduledAtoms = new Set<Atom>();
|
|
||||||
|
|
||||||
function makeAtom(getValue: () => any): Atom {
|
|
||||||
const atom: Atom = {
|
|
||||||
executionContexts: new Set<ExecutionContext>(),
|
|
||||||
dependents: new Set<Atom>(),
|
|
||||||
// getValue,
|
|
||||||
};
|
|
||||||
return atom;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTargetKeyAtom(target: Target, key: PropertyKey): Atom {
|
|
||||||
let keyToAtomItem: Map<PropertyKey, Atom> = targetToKeysToAtomItem.get(target)!;
|
|
||||||
if (!keyToAtomItem) {
|
|
||||||
keyToAtomItem = new Map();
|
|
||||||
targetToKeysToAtomItem.set(target, keyToAtomItem);
|
|
||||||
}
|
|
||||||
let atom = keyToAtomItem.get(key)!;
|
|
||||||
if (!atom) {
|
|
||||||
atom = makeAtom(() => Reflect.get(target, key));
|
|
||||||
keyToAtomItem.set(key, atom);
|
|
||||||
}
|
|
||||||
return atom;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addAtomToContext(atom: Atom, executionContext: ExecutionContext) {
|
|
||||||
executionContext.atoms.add(atom);
|
|
||||||
atom.executionContexts.add(executionContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observes a given key on a target with an callback. The callback will be
|
* Observes a given key on a target with an callback. The callback will be
|
||||||
* called when the given key changes on the target.
|
* called when the given key changes on the target.
|
||||||
@@ -117,73 +91,23 @@ export function addAtomToContext(atom: Atom, executionContext: ExecutionContext)
|
|||||||
* or deletion)
|
* or deletion)
|
||||||
* @param callback the function to call when the key changes
|
* @param callback the function to call when the key changes
|
||||||
*/
|
*/
|
||||||
function onReadTargetKey(target: Target, key: PropertyKey, receiver: any): void {
|
function observeTargetKey(target: Target, key: PropertyKey, callback: Callback): void {
|
||||||
const executionContext = getExecutionContext();
|
if (callback === NO_CALLBACK) {
|
||||||
executionContext?.onReadAtom(getTargetKeyAtom(target, key));
|
return;
|
||||||
|
}
|
||||||
|
if (!targetToKeysToCallbacks.get(target)) {
|
||||||
|
targetToKeysToCallbacks.set(target, new Map());
|
||||||
|
}
|
||||||
|
const keyToCallbacks = targetToKeysToCallbacks.get(target)!;
|
||||||
|
if (!keyToCallbacks.get(key)) {
|
||||||
|
keyToCallbacks.set(key, new Set());
|
||||||
|
}
|
||||||
|
keyToCallbacks.get(key)!.add(callback);
|
||||||
|
if (!callbacksToTargets.has(callback)) {
|
||||||
|
callbacksToTargets.set(callback, new Set());
|
||||||
|
}
|
||||||
|
callbacksToTargets.get(callback)!.add(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
let scheduled = false;
|
|
||||||
function scheduleAtom(atom: Atom) {
|
|
||||||
scheduledAtoms.add(atom);
|
|
||||||
// batched(processAtoms)();
|
|
||||||
if (scheduled) return;
|
|
||||||
scheduled = true;
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
scheduled = false;
|
|
||||||
processAtoms();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function processDerivedAtoms() {
|
|
||||||
const processedAtoms = new Set<Atom>();
|
|
||||||
for (const atom of scheduledAtoms) {
|
|
||||||
for (const dep of atom.dependents) {
|
|
||||||
if (processedAtoms.has(dep)) continue;
|
|
||||||
dep.computed = false;
|
|
||||||
processedAtoms.add(dep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processAtoms() {
|
|
||||||
processDerivedAtoms();
|
|
||||||
|
|
||||||
const scheduledContexts = new Set(
|
|
||||||
[...scheduledAtoms.values()].map((s) => [...s.executionContexts]).flat()
|
|
||||||
);
|
|
||||||
|
|
||||||
// schedule before context.update in case there is write operations during update
|
|
||||||
// todo: add a test in case there is write operations during update the test
|
|
||||||
// will break is scheduledAtoms.clear(); is called after context.update();
|
|
||||||
// that writes
|
|
||||||
scheduledAtoms.clear();
|
|
||||||
for (const ctx of [...scheduledContexts]) {
|
|
||||||
removeAtomsFromContext(ctx);
|
|
||||||
// custom unsubscribe depending on the context.
|
|
||||||
// scheduledContexts might be updated while we're iterating over it.
|
|
||||||
ctx.unsubcribe?.(scheduledContexts);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const context of scheduledContexts) {
|
|
||||||
pushExecutionContext(context);
|
|
||||||
try {
|
|
||||||
context.update?.();
|
|
||||||
} finally {
|
|
||||||
popExecutionContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify Reactives that are observing a given target that a key has changed on
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
for (const context of executionContexts) {
|
|
||||||
context.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify Reactives that are observing a given target that a key has changed on
|
* Notify Reactives that are observing a given target that a key has changed on
|
||||||
* the target.
|
* the target.
|
||||||
@@ -193,21 +117,66 @@ function processAtoms() {
|
|||||||
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
||||||
* or deleted)
|
* or deleted)
|
||||||
*/
|
*/
|
||||||
function onWriteTargetKey(target: Target, key: PropertyKey): void {
|
function notifyReactives(target: Target, key: PropertyKey): void {
|
||||||
const keyToAtomItem = targetToKeysToAtomItem.get(target)!;
|
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
||||||
if (!keyToAtomItem) {
|
if (!keyToCallbacks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const atom = keyToAtomItem.get(key);
|
const callbacks = keyToCallbacks.get(key);
|
||||||
if (!atom) {
|
if (!callbacks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scheduleAtom(atom);
|
// Loop on copy because clearReactivesForCallback will modify the set in place
|
||||||
|
for (const callback of [...callbacks]) {
|
||||||
|
clearReactivesForCallback(callback);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 [key, callbacks] of observedKeys.entries()) {
|
||||||
|
callbacks.delete(callback);
|
||||||
|
if (!callbacks.size) {
|
||||||
|
observedKeys.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetsToClear.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSubscriptions(callback: Callback) {
|
||||||
|
const targets = callbacksToTargets.get(callback) || [];
|
||||||
|
return [...targets].map((target) => {
|
||||||
|
const keysToCallbacks = targetToKeysToCallbacks.get(target);
|
||||||
|
let keys = [];
|
||||||
|
if (keysToCallbacks) {
|
||||||
|
for (const [key, cbs] of keysToCallbacks) {
|
||||||
|
if (cbs.has(callback)) {
|
||||||
|
keys.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { target, keys };
|
||||||
|
});
|
||||||
|
}
|
||||||
// Maps reactive objects to the underlying target
|
// Maps reactive objects to the underlying target
|
||||||
export const targets = new WeakMap<Reactive<Target>, Target>();
|
export const targets = new WeakMap<Reactive<Target>, Target>();
|
||||||
const reactiveCache = new WeakMap<Target, Reactive<Target>>();
|
const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive<Target>>>();
|
||||||
/**
|
/**
|
||||||
* Creates a reactive proxy for an object. Reading data on the reactive object
|
* 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
|
* subscribes to changes to the data. Writing data on the object will cause the
|
||||||
@@ -235,7 +204,7 @@ const reactiveCache = new WeakMap<Target, Reactive<Target>>();
|
|||||||
* reactive has changed
|
* reactive has changed
|
||||||
* @returns a proxy that tracks changes to it
|
* @returns a proxy that tracks changes to it
|
||||||
*/
|
*/
|
||||||
export function reactive<T extends Target>(target: T): T {
|
export function reactive<T extends Target>(target: T, callback: Callback = NO_CALLBACK): T {
|
||||||
if (!canBeMadeReactive(target)) {
|
if (!canBeMadeReactive(target)) {
|
||||||
throw new OwlError(`Cannot make the given value reactive`);
|
throw new OwlError(`Cannot make the given value reactive`);
|
||||||
}
|
}
|
||||||
@@ -244,130 +213,30 @@ export function reactive<T extends Target>(target: T): T {
|
|||||||
}
|
}
|
||||||
if (targets.has(target)) {
|
if (targets.has(target)) {
|
||||||
// target is reactive, create a reactive on the underlying object instead
|
// target is reactive, create a reactive on the underlying object instead
|
||||||
// return reactive(targets.get(target) as T);
|
return reactive(targets.get(target) as T, callback);
|
||||||
return target;
|
|
||||||
}
|
}
|
||||||
const reactive = reactiveCache.get(target)!;
|
if (!reactiveCache.has(target)) {
|
||||||
if (reactive) return reactive as T;
|
reactiveCache.set(target, new WeakMap());
|
||||||
|
}
|
||||||
|
const reactivesForTarget = reactiveCache.get(target)!;
|
||||||
|
if (!reactivesForTarget.has(callback)) {
|
||||||
const targetRawType = rawType(target);
|
const targetRawType = rawType(target);
|
||||||
const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
|
const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
|
||||||
? collectionsProxyHandler(target as Collection, targetRawType as CollectionRawType)
|
? collectionsProxyHandler(target as Collection, callback, targetRawType as CollectionRawType)
|
||||||
: basicProxyHandler<T>();
|
: basicProxyHandler<T>(callback);
|
||||||
const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>;
|
const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>;
|
||||||
|
reactivesForTarget.set(callback, proxy);
|
||||||
reactiveCache.set(target, proxy);
|
|
||||||
targets.set(proxy, target);
|
targets.set(proxy, target);
|
||||||
|
}
|
||||||
return proxy;
|
return reactivesForTarget.get(callback) as Reactive<T>;
|
||||||
}
|
}
|
||||||
function removeAtomsFromContext(executionContext: ExecutionContext) {
|
|
||||||
for (const sig of executionContext.atoms) {
|
|
||||||
sig.executionContexts.delete(executionContext);
|
|
||||||
}
|
|
||||||
executionContext.atoms.clear();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Unsubscribe an execution context and all its children from all atoms
|
|
||||||
* they are subscribed to.
|
|
||||||
*
|
|
||||||
* @param parentExecutionContext the context to unsubscribe
|
|
||||||
*/
|
|
||||||
function unsubscribeChildEffect(
|
|
||||||
parentExecutionContext: ExecutionContext,
|
|
||||||
scheduledContexts: Set<ExecutionContext>
|
|
||||||
) {
|
|
||||||
// executionContext.update = () => {};
|
|
||||||
|
|
||||||
for (const children of parentExecutionContext.meta.children) {
|
|
||||||
children.meta.parent = undefined;
|
|
||||||
removeAtomsFromContext(children);
|
|
||||||
scheduledContexts.delete(children);
|
|
||||||
unsubscribeChildEffect(children, scheduledContexts);
|
|
||||||
}
|
|
||||||
parentExecutionContext.meta.children.length = 0;
|
|
||||||
}
|
|
||||||
export function withoutReactivity<T extends (...args: any[]) => any>(fn: T): ReturnType<T> {
|
|
||||||
pushExecutionContext(undefined!);
|
|
||||||
let r: ReturnType<T>;
|
|
||||||
try {
|
|
||||||
r = fn();
|
|
||||||
} finally {
|
|
||||||
popExecutionContext();
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function effect(fn: Function) {
|
|
||||||
let parent = getExecutionContext();
|
|
||||||
// todo: is it useful?
|
|
||||||
if (parent && !parent?.meta.children) {
|
|
||||||
parent = undefined!;
|
|
||||||
}
|
|
||||||
const executionContext: ExecutionContext = {
|
|
||||||
unsubcribe: (scheduledContexts: Set<ExecutionContext>) => {
|
|
||||||
unsubscribeChildEffect(executionContext, scheduledContexts);
|
|
||||||
},
|
|
||||||
update: fn,
|
|
||||||
onReadAtom: (atom: Atom) => addAtomToContext(atom, executionContext),
|
|
||||||
atoms: new Set(),
|
|
||||||
meta: {
|
|
||||||
parent: parent,
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (parent) {
|
|
||||||
// todo: is it useful?
|
|
||||||
parent.meta.children?.push?.(executionContext);
|
|
||||||
}
|
|
||||||
pushExecutionContext(executionContext);
|
|
||||||
try {
|
|
||||||
fn();
|
|
||||||
} finally {
|
|
||||||
popExecutionContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function derived(fn: Function) {
|
|
||||||
let lastValue: any;
|
|
||||||
|
|
||||||
const derivedAtom: DerivedAtom = {
|
|
||||||
executionContexts: new Set<ExecutionContext>(),
|
|
||||||
dependents: new Set<Atom>(),
|
|
||||||
dependencies: new Map<Atom, OldValue>(),
|
|
||||||
getValue: () => lastValue,
|
|
||||||
computed: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const executionContext = getExecutionContext();
|
|
||||||
executionContext?.onReadAtom(derivedAtom);
|
|
||||||
if (derivedAtom.computed) return lastValue;
|
|
||||||
|
|
||||||
const derivedExecutionContext: ExecutionContext = {
|
|
||||||
onReadAtom: (atom: Atom) => {
|
|
||||||
atom.dependents.add(derivedAtom);
|
|
||||||
// derivedAtom.executionContexts.add(executionContext);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
pushExecutionContext(derivedExecutionContext);
|
|
||||||
try {
|
|
||||||
lastValue = fn();
|
|
||||||
} finally {
|
|
||||||
popExecutionContext();
|
|
||||||
}
|
|
||||||
derivedAtom.computed = true;
|
|
||||||
return lastValue;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a basic proxy handler for regular objects and arrays.
|
* Creates a basic proxy handler for regular objects and arrays.
|
||||||
*
|
*
|
||||||
* @param callback @see reactive
|
* @param callback @see reactive
|
||||||
* @returns a proxy handler object
|
* @returns a proxy handler object
|
||||||
*/
|
*/
|
||||||
function basicProxyHandler<T extends Target>(): ProxyHandler<T> {
|
function basicProxyHandler<T extends Target>(callback: Callback): ProxyHandler<T> {
|
||||||
return {
|
return {
|
||||||
get(target, key, receiver) {
|
get(target, key, receiver) {
|
||||||
// non-writable non-configurable properties cannot be made reactive
|
// non-writable non-configurable properties cannot be made reactive
|
||||||
@@ -375,15 +244,15 @@ function basicProxyHandler<T extends Target>(): ProxyHandler<T> {
|
|||||||
if (desc && !desc.writable && !desc.configurable) {
|
if (desc && !desc.writable && !desc.configurable) {
|
||||||
return Reflect.get(target, key, receiver);
|
return Reflect.get(target, key, receiver);
|
||||||
}
|
}
|
||||||
onReadTargetKey(target, key);
|
observeTargetKey(target, key, callback);
|
||||||
return possiblyReactive(Reflect.get(target, key, receiver));
|
return possiblyReactive(Reflect.get(target, key, receiver), callback);
|
||||||
},
|
},
|
||||||
set(target, key, value, receiver) {
|
set(target, key, value, receiver) {
|
||||||
const hadKey = objectHasOwnProperty.call(target, key);
|
const hadKey = objectHasOwnProperty.call(target, key);
|
||||||
const originalValue = Reflect.get(target, key, receiver);
|
const originalValue = Reflect.get(target, key, receiver);
|
||||||
const ret = Reflect.set(target, key, toRaw(value), receiver);
|
const ret = Reflect.set(target, key, toRaw(value), receiver);
|
||||||
if (!hadKey && objectHasOwnProperty.call(target, key)) {
|
if (!hadKey && objectHasOwnProperty.call(target, key)) {
|
||||||
onWriteTargetKey(target, KEYCHANGES);
|
notifyReactives(target, KEYCHANGES);
|
||||||
}
|
}
|
||||||
// While Array length may trigger the set trap, it's not actually set by this
|
// 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
|
// method but is updated behind the scenes, and the trap is not called with the
|
||||||
@@ -392,26 +261,26 @@ function basicProxyHandler<T extends Target>(): ProxyHandler<T> {
|
|||||||
originalValue !== Reflect.get(target, key, receiver) ||
|
originalValue !== Reflect.get(target, key, receiver) ||
|
||||||
(key === "length" && Array.isArray(target))
|
(key === "length" && Array.isArray(target))
|
||||||
) {
|
) {
|
||||||
onWriteTargetKey(target, key);
|
notifyReactives(target, key);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
deleteProperty(target, key) {
|
deleteProperty(target, key) {
|
||||||
const ret = Reflect.deleteProperty(target, key);
|
const ret = Reflect.deleteProperty(target, key);
|
||||||
// TODO: only notify when something was actually deleted
|
// TODO: only notify when something was actually deleted
|
||||||
onWriteTargetKey(target, KEYCHANGES);
|
notifyReactives(target, KEYCHANGES);
|
||||||
onWriteTargetKey(target, key);
|
notifyReactives(target, key);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
ownKeys(target) {
|
ownKeys(target) {
|
||||||
onReadTargetKey(target, KEYCHANGES);
|
observeTargetKey(target, KEYCHANGES, callback);
|
||||||
return Reflect.ownKeys(target);
|
return Reflect.ownKeys(target);
|
||||||
},
|
},
|
||||||
has(target, key) {
|
has(target, key) {
|
||||||
// TODO: this observes all key changes instead of only the presence of the argument key
|
// TODO: this observes all key changes instead of only the presence of the argument key
|
||||||
// observing the key itself would observe value changes instead of presence changes
|
// observing the key itself would observe value changes instead of presence changes
|
||||||
// so we may need a finer grained system to distinguish observing value vs presence.
|
// so we may need a finer grained system to distinguish observing value vs presence.
|
||||||
onReadTargetKey(target, KEYCHANGES);
|
observeTargetKey(target, KEYCHANGES, callback);
|
||||||
return Reflect.has(target, key);
|
return Reflect.has(target, key);
|
||||||
},
|
},
|
||||||
} as ProxyHandler<T>;
|
} as ProxyHandler<T>;
|
||||||
@@ -424,11 +293,11 @@ function basicProxyHandler<T extends Target>(): ProxyHandler<T> {
|
|||||||
* @param target @see reactive
|
* @param target @see reactive
|
||||||
* @param callback @see reactive
|
* @param callback @see reactive
|
||||||
*/
|
*/
|
||||||
function makeKeyObserver(methodName: "has" | "get", target: any) {
|
function makeKeyObserver(methodName: "has" | "get", target: any, callback: Callback) {
|
||||||
return (key: any) => {
|
return (key: any) => {
|
||||||
key = toRaw(key);
|
key = toRaw(key);
|
||||||
onReadTargetKey(target, key);
|
observeTargetKey(target, key, callback);
|
||||||
return possiblyReactive(target[methodName](key));
|
return possiblyReactive(target[methodName](key), callback);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -441,15 +310,16 @@ function makeKeyObserver(methodName: "has" | "get", target: any) {
|
|||||||
*/
|
*/
|
||||||
function makeIteratorObserver(
|
function makeIteratorObserver(
|
||||||
methodName: "keys" | "values" | "entries" | typeof Symbol.iterator,
|
methodName: "keys" | "values" | "entries" | typeof Symbol.iterator,
|
||||||
target: any
|
target: any,
|
||||||
|
callback: Callback
|
||||||
) {
|
) {
|
||||||
return function* () {
|
return function* () {
|
||||||
onReadTargetKey(target, KEYCHANGES);
|
observeTargetKey(target, KEYCHANGES, callback);
|
||||||
const keys = target.keys();
|
const keys = target.keys();
|
||||||
for (const item of target[methodName]()) {
|
for (const item of target[methodName]()) {
|
||||||
const key = keys.next().value;
|
const key = keys.next().value;
|
||||||
onReadTargetKey(target, key);
|
observeTargetKey(target, key, callback);
|
||||||
yield possiblyReactive(item);
|
yield possiblyReactive(item, callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -461,16 +331,16 @@ function makeIteratorObserver(
|
|||||||
* @param target @see reactive
|
* @param target @see reactive
|
||||||
* @param callback @see reactive
|
* @param callback @see reactive
|
||||||
*/
|
*/
|
||||||
function makeForEachObserver(target: any) {
|
function makeForEachObserver(target: any, callback: Callback) {
|
||||||
return function forEach(forEachCb: (val: any, key: any, target: any) => void, thisArg: any) {
|
return function forEach(forEachCb: (val: any, key: any, target: any) => void, thisArg: any) {
|
||||||
onReadTargetKey(target, KEYCHANGES);
|
observeTargetKey(target, KEYCHANGES, callback);
|
||||||
target.forEach(function (val: any, key: any, targetObj: any) {
|
target.forEach(function (val: any, key: any, targetObj: any) {
|
||||||
onReadTargetKey(target, key);
|
observeTargetKey(target, key, callback);
|
||||||
forEachCb.call(
|
forEachCb.call(
|
||||||
thisArg,
|
thisArg,
|
||||||
possiblyReactive(val),
|
possiblyReactive(val, callback),
|
||||||
possiblyReactive(key),
|
possiblyReactive(key, callback),
|
||||||
possiblyReactive(targetObj)
|
possiblyReactive(targetObj, callback)
|
||||||
);
|
);
|
||||||
}, thisArg);
|
}, thisArg);
|
||||||
};
|
};
|
||||||
@@ -497,10 +367,10 @@ function delegateAndNotify(
|
|||||||
const ret = target[setterName](key, value);
|
const ret = target[setterName](key, value);
|
||||||
const hasKey = target.has(key);
|
const hasKey = target.has(key);
|
||||||
if (hadKey !== hasKey) {
|
if (hadKey !== hasKey) {
|
||||||
onWriteTargetKey(target, KEYCHANGES);
|
notifyReactives(target, KEYCHANGES);
|
||||||
}
|
}
|
||||||
if (originalValue !== target[getterName](key)) {
|
if (originalValue !== target[getterName](key)) {
|
||||||
onWriteTargetKey(target, key);
|
notifyReactives(target, key);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
@@ -515,9 +385,9 @@ function makeClearNotifier(target: Map<any, any> | Set<any>) {
|
|||||||
return () => {
|
return () => {
|
||||||
const allKeys = [...target.keys()];
|
const allKeys = [...target.keys()];
|
||||||
target.clear();
|
target.clear();
|
||||||
onWriteTargetKey(target, KEYCHANGES);
|
notifyReactives(target, KEYCHANGES);
|
||||||
for (const key of allKeys) {
|
for (const key of allKeys) {
|
||||||
onWriteTargetKey(target, key);
|
notifyReactives(target, key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -529,40 +399,40 @@ function makeClearNotifier(target: Map<any, any> | Set<any>) {
|
|||||||
* reactives that the key which is being added or deleted has been modified.
|
* reactives that the key which is being added or deleted has been modified.
|
||||||
*/
|
*/
|
||||||
const rawTypeToFuncHandlers = {
|
const rawTypeToFuncHandlers = {
|
||||||
Set: (target: any) => ({
|
Set: (target: any, callback: Callback) => ({
|
||||||
has: makeKeyObserver("has", target),
|
has: makeKeyObserver("has", target, callback),
|
||||||
add: delegateAndNotify("add", "has", target),
|
add: delegateAndNotify("add", "has", target),
|
||||||
delete: delegateAndNotify("delete", "has", target),
|
delete: delegateAndNotify("delete", "has", target),
|
||||||
keys: makeIteratorObserver("keys", target),
|
keys: makeIteratorObserver("keys", target, callback),
|
||||||
values: makeIteratorObserver("values", target),
|
values: makeIteratorObserver("values", target, callback),
|
||||||
entries: makeIteratorObserver("entries", target),
|
entries: makeIteratorObserver("entries", target, callback),
|
||||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
|
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
||||||
forEach: makeForEachObserver(target),
|
forEach: makeForEachObserver(target, callback),
|
||||||
clear: makeClearNotifier(target),
|
clear: makeClearNotifier(target),
|
||||||
get size() {
|
get size() {
|
||||||
onReadTargetKey(target, KEYCHANGES);
|
observeTargetKey(target, KEYCHANGES, callback);
|
||||||
return target.size;
|
return target.size;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Map: (target: any) => ({
|
Map: (target: any, callback: Callback) => ({
|
||||||
has: makeKeyObserver("has", target),
|
has: makeKeyObserver("has", target, callback),
|
||||||
get: makeKeyObserver("get", target),
|
get: makeKeyObserver("get", target, callback),
|
||||||
set: delegateAndNotify("set", "get", target),
|
set: delegateAndNotify("set", "get", target),
|
||||||
delete: delegateAndNotify("delete", "has", target),
|
delete: delegateAndNotify("delete", "has", target),
|
||||||
keys: makeIteratorObserver("keys", target),
|
keys: makeIteratorObserver("keys", target, callback),
|
||||||
values: makeIteratorObserver("values", target),
|
values: makeIteratorObserver("values", target, callback),
|
||||||
entries: makeIteratorObserver("entries", target),
|
entries: makeIteratorObserver("entries", target, callback),
|
||||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
|
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
||||||
forEach: makeForEachObserver(target),
|
forEach: makeForEachObserver(target, callback),
|
||||||
clear: makeClearNotifier(target),
|
clear: makeClearNotifier(target),
|
||||||
get size() {
|
get size() {
|
||||||
onReadTargetKey(target, KEYCHANGES);
|
observeTargetKey(target, KEYCHANGES, callback);
|
||||||
return target.size;
|
return target.size;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
WeakMap: (target: any) => ({
|
WeakMap: (target: any, callback: Callback) => ({
|
||||||
has: makeKeyObserver("has", target),
|
has: makeKeyObserver("has", target, callback),
|
||||||
get: makeKeyObserver("get", target),
|
get: makeKeyObserver("get", target, callback),
|
||||||
set: delegateAndNotify("set", "get", target),
|
set: delegateAndNotify("set", "get", target),
|
||||||
delete: delegateAndNotify("delete", "has", target),
|
delete: delegateAndNotify("delete", "has", target),
|
||||||
}),
|
}),
|
||||||
@@ -576,19 +446,20 @@ const rawTypeToFuncHandlers = {
|
|||||||
*/
|
*/
|
||||||
function collectionsProxyHandler<T extends Collection>(
|
function collectionsProxyHandler<T extends Collection>(
|
||||||
target: T,
|
target: T,
|
||||||
|
callback: Callback,
|
||||||
targetRawType: CollectionRawType
|
targetRawType: CollectionRawType
|
||||||
): ProxyHandler<T> {
|
): ProxyHandler<T> {
|
||||||
// TODO: if performance is an issue we can create the special handlers lazily when each
|
// TODO: if performance is an issue we can create the special handlers lazily when each
|
||||||
// property is read.
|
// property is read.
|
||||||
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target);
|
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
|
||||||
return Object.assign(basicProxyHandler(), {
|
return Object.assign(basicProxyHandler(callback), {
|
||||||
// FIXME: probably broken when part of prototype chain since we ignore the receiver
|
// FIXME: probably broken when part of prototype chain since we ignore the receiver
|
||||||
get(target: any, key: PropertyKey) {
|
get(target: any, key: PropertyKey) {
|
||||||
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
if (objectHasOwnProperty.call(specialHandlers, key)) {
|
||||||
return (specialHandlers as any)[key];
|
return (specialHandlers as any)[key];
|
||||||
}
|
}
|
||||||
onReadTargetKey(target, key);
|
observeTargetKey(target, key, callback);
|
||||||
return possiblyReactive(target[key]);
|
return possiblyReactive(target[key], callback);
|
||||||
},
|
},
|
||||||
}) as ProxyHandler<T>;
|
}) as ProxyHandler<T>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import { getTaskContext, TaskContext, useTaskContext } from "./cancellableContext";
|
|
||||||
|
|
||||||
export class Task<T = any> {
|
|
||||||
_promise: Promise<T>;
|
|
||||||
_ctx?: TaskContext = getTaskContext();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason: any) => void) => void,
|
|
||||||
public _onCancelled?: Function
|
|
||||||
) {
|
|
||||||
if (!this._ctx) {
|
|
||||||
this._promise = new Promise(executor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._promise = new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
executor(
|
|
||||||
(value: T | PromiseLike<T>) => {
|
|
||||||
if (!this._ctx?.isCancelled) resolve(value);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
if (!this._ctx?.isCancelled) reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
if (!this._ctx?.isCancelled) reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
then(onFulfilled: (value: any) => any, onRejected: (error: any) => any) {
|
|
||||||
if (!this._ctx) return this._promise.then(onFulfilled, onRejected);
|
|
||||||
return this._promise.then((v) => {
|
|
||||||
if (this._ctx!.isCancelled) return;
|
|
||||||
let cleanup: Function;
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
const ctx = useTaskContext(this._ctx);
|
|
||||||
cleanup = ctx.cleanup;
|
|
||||||
});
|
|
||||||
const result = onFulfilled(v);
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}, onRejected);
|
|
||||||
}
|
|
||||||
|
|
||||||
catch(onRejected: (error: any) => any) {
|
|
||||||
return this._promise.catch(onRejected);
|
|
||||||
}
|
|
||||||
|
|
||||||
finally(onFinally: () => any) {
|
|
||||||
return this._promise.finally(onFinally);
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
|
||||||
if (this._onCancelled) {
|
|
||||||
this._onCancelled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get [Symbol.toStringTag]() {
|
|
||||||
return "Promise";
|
|
||||||
}
|
|
||||||
|
|
||||||
// static all(tasks) {
|
|
||||||
// return new Task((resolve, reject) => {
|
|
||||||
// Promise.all(tasks.map((t) => (t instanceof Task ? t._promise : t))).then(resolve, reject);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { OwlError } from "../common/owl_error";
|
import { OwlError } from "../common/owl_error";
|
||||||
|
import { ComponentNode, getCurrent } from "./component_node";
|
||||||
export type Callback = () => void;
|
export type Callback = () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,10 +82,52 @@ export function validateTarget(target: HTMLElement | ShadowRoot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class EventBus extends EventTarget {
|
export class EventBus extends EventTarget {
|
||||||
|
constructor(events?: string[]) {
|
||||||
|
if (events) {
|
||||||
|
let node: ComponentNode | null = null;
|
||||||
|
try {
|
||||||
|
node = getCurrent();
|
||||||
|
} catch {}
|
||||||
|
if (node?.app?.dev) {
|
||||||
|
return new DebugEventBus(events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super();
|
||||||
|
}
|
||||||
trigger(name: string, payload?: any) {
|
trigger(name: string, payload?: any) {
|
||||||
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class DebugEventBus extends EventBus {
|
||||||
|
private events: Set<string>;
|
||||||
|
constructor(events: string[]) {
|
||||||
|
super();
|
||||||
|
this.events = new Set(events);
|
||||||
|
}
|
||||||
|
addEventListener(
|
||||||
|
type: string,
|
||||||
|
listener: EventListenerOrEventListenerObject | null,
|
||||||
|
options?: boolean | AddEventListenerOptions
|
||||||
|
): void {
|
||||||
|
if (!this.events.has(type)) {
|
||||||
|
throw new OwlError(`EventBus: subscribing to unknown event '${type}'`);
|
||||||
|
}
|
||||||
|
super.addEventListener(type, listener, options);
|
||||||
|
}
|
||||||
|
trigger(name: string, payload?: any) {
|
||||||
|
if (!this.events.has(name)) {
|
||||||
|
throw new OwlError(`EventBus: triggering unknown event '${name}'`);
|
||||||
|
}
|
||||||
|
super.trigger(name, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent(event: Event): boolean {
|
||||||
|
if (!this.events.has(event.type)) {
|
||||||
|
throw new OwlError(`EventBus: dispatching unknown event '${event.type}'`);
|
||||||
|
}
|
||||||
|
return super.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function whenReady(fn?: any): Promise<void> {
|
export function whenReady(fn?: any): Promise<void> {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
// do not modify manually. This file is generated by the release script.
|
// do not modify manually. This file is generated by the release script.
|
||||||
export const version = "2.8.1";
|
export const version = "2.8.0";
|
||||||
|
|||||||
@@ -1,5 +1,51 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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`] = `
|
exports[`Reactivity: useState destroyed component before being mounted is inactive 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -109,6 +155,69 @@ exports[`Reactivity: useState parent and children subscribed to same context 2`]
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Reactivity: useState several nodes on different level use same context 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(\`<div><block-text-0/> <block-text-1/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['contextObj'].a;
|
||||||
|
let d2 = ctx['contextObj'].b;
|
||||||
|
return block1([d1, d2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Reactivity: useState several nodes on different level use same context 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(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['contextObj'].b;
|
||||||
|
return block1([d1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Reactivity: useState several nodes on different level use same context 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-text-0/><block-child-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['contextObj'].a;
|
||||||
|
let b2 = component(\`L3A\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
return block1([d1], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Reactivity: useState several nodes on different level use same context 4`] = `
|
||||||
|
"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/><block-child-1/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = component(\`L2A\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
let b3 = component(\`L2B\`, {}, key + \`__2\`, node, ctx);
|
||||||
|
return block1([], [b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Reactivity: useState two components are updated in parallel 1`] = `
|
exports[`Reactivity: useState two components are updated in parallel 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -49,27 +49,6 @@ exports[`debugging t-debug on sub template 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`debugging t-debug: interaction with t-set 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
ctx = Object.create(ctx);
|
|
||||||
ctx[isBoundary] = 1
|
|
||||||
debugger;
|
|
||||||
setContextValue(ctx, \\"foo\\", 42);
|
|
||||||
debugger;
|
|
||||||
setContextValue(ctx, \\"bar\\", 49);
|
|
||||||
let txt1 = ctx['foo']+ctx['bar'];
|
|
||||||
return block1([txt1]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`debugging t-log 1`] = `
|
exports[`debugging t-log 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -87,24 +66,3 @@ exports[`debugging t-log 1`] = `
|
|||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`debugging t-log: interaction with t-set 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
ctx = Object.create(ctx);
|
|
||||||
ctx[isBoundary] = 1
|
|
||||||
console.log(ctx['foo']);
|
|
||||||
setContextValue(ctx, \\"foo\\", 42);
|
|
||||||
console.log(ctx['bar']);
|
|
||||||
setContextValue(ctx, \\"bar\\", 49);
|
|
||||||
let txt1 = ctx['foo']+ctx['bar'];
|
|
||||||
return block1([txt1]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -103,18 +103,3 @@ exports[`t-key t-key on sub dom node pushes a child block in its parent 2`] = `
|
|||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`t-key t-key: interaction with t-esc 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<p><block-text-0/></p>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const tKey_1 = ctx['key'];
|
|
||||||
let txt1 = ctx['text'];
|
|
||||||
return toggler(tKey_1, block1([txt1]));
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ exports[`translation context body of t-sets are translated in context 1`] = `
|
|||||||
ctx = Object.create(ctx);
|
ctx = Object.create(ctx);
|
||||||
ctx[isBoundary] = 1
|
ctx[isBoundary] = 1
|
||||||
setContextValue(ctx, \\"label\\", \`traduit\`);
|
setContextValue(ctx, \\"label\\", \`traduit\`);
|
||||||
return text(ctx['label']);
|
const b2 = text(ctx['label']);
|
||||||
|
return multi([b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -93,23 +94,6 @@ exports[`translation context slot attrs and text contents are translated in cont
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`translation context t-translation-context with several children 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><div/><div/><block-child-0/></div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2;
|
|
||||||
if (true) {
|
|
||||||
b2 = text(\`\`);
|
|
||||||
}
|
|
||||||
return block1([], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`translation context translation of attributes in context 1`] = `
|
exports[`translation context translation of attributes in context 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -169,21 +153,6 @@ exports[`translation support body of t-sets inside translation=off are not trans
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`translation support body of t-sets inside translation=off are not translated 2 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
ctx = Object.create(ctx);
|
|
||||||
ctx[isBoundary] = 1
|
|
||||||
setContextValue(ctx, \\"label\\", \`untranslated\`);
|
|
||||||
return text(ctx['label']);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`translation support body of t-sets with html content are translated 1`] = `
|
exports[`translation support body of t-sets with html content are translated 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -295,23 +264,6 @@ exports[`translation support t-set and falsy t-value: t-body are translated 1`]
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`translation support t-translation with several children 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><div/><div/><block-child-0/></div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2;
|
|
||||||
if (true) {
|
|
||||||
b2 = text(\`\`);
|
|
||||||
}
|
|
||||||
return block1([], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
|
exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -692,7 +692,6 @@ describe("qweb parser", () => {
|
|||||||
value: "value",
|
value: "value",
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
body: null,
|
body: null,
|
||||||
hasNoRepresentation: true,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -703,7 +702,6 @@ describe("qweb parser", () => {
|
|||||||
defaultValue: "ok",
|
defaultValue: "ok",
|
||||||
value: null,
|
value: null,
|
||||||
body: null,
|
body: null,
|
||||||
hasNoRepresentation: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(parse(`<t t-set="v"><div>ok</div></t>`)).toEqual({
|
expect(parse(`<t t-set="v"><div>ok</div></t>`)).toEqual({
|
||||||
@@ -725,7 +723,6 @@ describe("qweb parser", () => {
|
|||||||
content: [{ type: ASTType.Text, value: "ok" }],
|
content: [{ type: ASTType.Text, value: "ok" }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
hasNoRepresentation: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(parse(`<t t-set="v"><div>ok</div>abc</t>`)).toEqual({
|
expect(parse(`<t t-set="v"><div>ok</div>abc</t>`)).toEqual({
|
||||||
@@ -748,7 +745,6 @@ describe("qweb parser", () => {
|
|||||||
},
|
},
|
||||||
{ type: ASTType.Text, value: "abc" },
|
{ type: ASTType.Text, value: "abc" },
|
||||||
],
|
],
|
||||||
hasNoRepresentation: true,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -762,7 +758,6 @@ describe("qweb parser", () => {
|
|||||||
defaultValue: "ok",
|
defaultValue: "ok",
|
||||||
value: null,
|
value: null,
|
||||||
body: null,
|
body: null,
|
||||||
hasNoRepresentation: true,
|
|
||||||
},
|
},
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: null,
|
tElse: null,
|
||||||
@@ -788,14 +783,7 @@ describe("qweb parser", () => {
|
|||||||
condition: "flag",
|
condition: "flag",
|
||||||
content: { type: ASTType.Text, value: "1" },
|
content: { type: ASTType.Text, value: "1" },
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: {
|
tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null },
|
||||||
type: ASTType.TSet,
|
|
||||||
name: "ourvar",
|
|
||||||
value: "0",
|
|
||||||
defaultValue: null,
|
|
||||||
body: null,
|
|
||||||
hasNoRepresentation: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,34 +38,4 @@ describe("debugging", () => {
|
|||||||
expect(console.log).toHaveBeenCalledWith(45);
|
expect(console.log).toHaveBeenCalledWith(45);
|
||||||
console.log = consoleLog;
|
console.log = consoleLog;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-log: interaction with t-set", () => {
|
|
||||||
const consoleLog = console.log;
|
|
||||||
console.log = jest.fn();
|
|
||||||
|
|
||||||
const template = `
|
|
||||||
<t>
|
|
||||||
<t t-log="foo" t-set="foo" t-value="42"/>
|
|
||||||
<t t-log="bar" t-set="bar" t-value="49"/>
|
|
||||||
<span t-esc="foo + bar"/>
|
|
||||||
</t>
|
|
||||||
`;
|
|
||||||
snapshotTemplate(template);
|
|
||||||
renderToString(template);
|
|
||||||
expect(console.log).toHaveBeenCalledWith(undefined);
|
|
||||||
expect(console.log).toHaveBeenCalledWith(undefined);
|
|
||||||
console.log = consoleLog;
|
|
||||||
});
|
|
||||||
|
|
||||||
test("t-debug: interaction with t-set", () => {
|
|
||||||
const template = `
|
|
||||||
<t>
|
|
||||||
<t t-debug="" t-set="foo" t-value="42"/>
|
|
||||||
<t t-debug="" t-set="bar" t-value="49"/>
|
|
||||||
<span t-esc="foo + bar"/>
|
|
||||||
</t>
|
|
||||||
`;
|
|
||||||
snapshotTemplate(template);
|
|
||||||
renderToString(template);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,10 +63,4 @@ describe("t-key", () => {
|
|||||||
|
|
||||||
expect(renderToString(template2, { key: "1" })).toBe("<div><h1></h1></div>");
|
expect(renderToString(template2, { key: "1" })).toBe("<div><h1></h1></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-key: interaction with t-esc", async () => {
|
|
||||||
const template = `<p t-key="key" t-esc="text"/>`;
|
|
||||||
|
|
||||||
expect(renderToString(template, { key: "1", text: "abc" })).toBe("<p>abc</p>");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -129,21 +129,6 @@ describe("translation support", () => {
|
|||||||
expect(fixture.innerHTML).toBe("untranslated");
|
expect(fixture.innerHTML).toBe("untranslated");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("body of t-sets inside translation=off are not translated 2", async () => {
|
|
||||||
class SomeComponent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<t>
|
|
||||||
<t t-translation="off" t-set="label">untranslated</t>
|
|
||||||
<t t-esc="label"/>
|
|
||||||
</t>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const translateFn = () => "translated";
|
|
||||||
|
|
||||||
await mount(SomeComponent, fixture, { translateFn });
|
|
||||||
expect(fixture.innerHTML).toBe("untranslated");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("body of t-sets with html content are translated", async () => {
|
test("body of t-sets with html content are translated", async () => {
|
||||||
class SomeComponent extends Component {
|
class SomeComponent extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
@@ -185,22 +170,6 @@ describe("translation support", () => {
|
|||||||
await mount(SomeComponent, fixture, { translateFn });
|
await mount(SomeComponent, fixture, { translateFn });
|
||||||
expect(fixture.innerHTML).toBe("translated");
|
expect(fixture.innerHTML).toBe("translated");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-translation with several children", async () => {
|
|
||||||
class SomeComponent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<div>
|
|
||||||
<t t-translation="off">
|
|
||||||
<div/>
|
|
||||||
<div/>
|
|
||||||
</t>
|
|
||||||
<t t-if="true"/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
await mount(SomeComponent, fixture);
|
|
||||||
expect(fixture.outerHTML).toBe("<div><div><div></div><div></div></div></div>");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("translation context", () => {
|
describe("translation context", () => {
|
||||||
@@ -324,20 +293,4 @@ describe("translation context", () => {
|
|||||||
expect(translateFn).toHaveBeenCalledWith("param", "fr");
|
expect(translateFn).toHaveBeenCalledWith("param", "fr");
|
||||||
expect(translateFn).toHaveBeenCalledWith("title", "pt");
|
expect(translateFn).toHaveBeenCalledWith("title", "pt");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-translation-context with several children", async () => {
|
|
||||||
class SomeComponent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<div>
|
|
||||||
<t t-translation-context="ctx">
|
|
||||||
<div/>
|
|
||||||
<div/>
|
|
||||||
</t>
|
|
||||||
<t t-if="true"/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
await mount(SomeComponent, fixture);
|
|
||||||
expect(fixture.outerHTML).toBe("<div><div><div></div><div></div></div></div>");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,36 +52,6 @@ exports[`reactivity in lifecycle Component is automatically subscribed to reacti
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`reactivity in lifecycle an external reactive object should be tracked 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
const comp1 = app.createComponent(\`TestSubComponent\`, true, false, false, []);
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-text-0/><block-child-0/></div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let txt1 = ctx['obj1'].value;
|
|
||||||
const b2 = comp1({}, key + \`__1\`, node, this, null);
|
|
||||||
return block1([txt1], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`reactivity in lifecycle an external reactive object should be tracked 2`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let txt1 = ctx['obj2'].value;
|
|
||||||
return block1([txt1]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`reactivity in lifecycle can use a state hook 1`] = `
|
exports[`reactivity in lifecycle can use a state hook 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -170,3 +140,39 @@ exports[`reactivity in lifecycle state changes in willUnmount do not trigger rer
|
|||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`subscriptions subscriptions returns the keys and targets observed by the component 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['state'].a);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`subscriptions subscriptions returns the keys observed by the component 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"state\\"]);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(ctx['state'].a);
|
||||||
|
const b3 = comp1({state: ctx['state']}, key + \`__1\`, node, this, null);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`subscriptions subscriptions returns the keys observed by the component 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].state.b);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
import { App, Component, mount, onWillDestroy } from "../../src";
|
import { App, Component, mount, onWillDestroy } from "../../src";
|
||||||
import { OwlError } from "../../src/common/owl_error";
|
|
||||||
import {
|
import {
|
||||||
onError,
|
onError,
|
||||||
onMounted,
|
onMounted,
|
||||||
onPatched,
|
onPatched,
|
||||||
onRendered,
|
|
||||||
onWillPatch,
|
onWillPatch,
|
||||||
onWillRender,
|
|
||||||
onWillStart,
|
onWillStart,
|
||||||
|
onWillRender,
|
||||||
|
onRendered,
|
||||||
onWillUnmount,
|
onWillUnmount,
|
||||||
useState,
|
useState,
|
||||||
xml,
|
xml,
|
||||||
} from "../../src/index";
|
} from "../../src/index";
|
||||||
import { getCurrent } from "../../src/runtime/component_node";
|
|
||||||
import {
|
import {
|
||||||
logStep,
|
logStep,
|
||||||
makeTestFixture,
|
makeTestFixture,
|
||||||
nextAppError,
|
|
||||||
nextMicroTick,
|
|
||||||
nextTick,
|
nextTick,
|
||||||
|
nextMicroTick,
|
||||||
snapshotEverything,
|
snapshotEverything,
|
||||||
steps,
|
|
||||||
useLogLifecycle,
|
useLogLifecycle,
|
||||||
|
nextAppError,
|
||||||
|
steps,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
|
import { OwlError } from "../../src/common/owl_error";
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
let fixture: HTMLElement;
|
||||||
|
|
||||||
@@ -648,7 +647,7 @@ describe("can catch errors", () => {
|
|||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
onWillStart(() => {
|
onWillStart(() => {
|
||||||
getCurrent();
|
this.state = useState({ value: 2 });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,10 +450,14 @@ test(".alike suffix in a list", async () => {
|
|||||||
expect(fixture.innerHTML).toBe("<button>1V</button><button>2V</button>");
|
expect(fixture.innerHTML).toBe("<button>1V</button><button>2V</button>");
|
||||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
"Todo:willRender",
|
"Todo:willRender",
|
||||||
"Todo:rendered",
|
"Todo:rendered",
|
||||||
"Todo:willPatch",
|
"Todo:willPatch",
|
||||||
"Todo:patched",
|
"Todo:patched",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Parent:patched",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -702,7 +702,7 @@ describe("props validation", () => {
|
|||||||
const app = new App(Parent, { test: true });
|
const app = new App(Parent, { test: true });
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("12");
|
expect(fixture.innerHTML).toBe("12");
|
||||||
// expect(app.root!.subscriptions).toEqual([{ keys: ["otherValue"], target: obj }]);
|
expect(app.root!.subscriptions).toEqual([{ keys: ["otherValue"], target: obj }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("props are validated whenever component is updated", async () => {
|
test("props are validated whenever component is updated", async () => {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
mount,
|
mount,
|
||||||
onPatched,
|
onPatched,
|
||||||
onWillPatch,
|
|
||||||
onWillRender,
|
onWillRender,
|
||||||
|
onWillPatch,
|
||||||
onWillUnmount,
|
onWillUnmount,
|
||||||
reactive,
|
useState,
|
||||||
xml,
|
xml,
|
||||||
|
toRaw,
|
||||||
} from "../../src";
|
} from "../../src";
|
||||||
import { makeTestFixture, nextTick, snapshotEverything, steps, useLogLifecycle } from "../helpers";
|
import { makeTestFixture, nextTick, snapshotEverything, steps, useLogLifecycle } from "../helpers";
|
||||||
|
|
||||||
@@ -19,36 +20,10 @@ beforeEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("reactivity in lifecycle", () => {
|
describe("reactivity in lifecycle", () => {
|
||||||
test("an external reactive object should be tracked", async () => {
|
|
||||||
const obj1 = reactive({ value: 1 });
|
|
||||||
const obj2 = reactive({ value: 100 });
|
|
||||||
class TestSubComponent extends Component {
|
|
||||||
obj2 = obj2;
|
|
||||||
|
|
||||||
static template = xml`<div>
|
|
||||||
<t t-esc="obj2.value"/>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
class TestComponent extends Component {
|
|
||||||
obj1 = obj1;
|
|
||||||
static template = xml`<div>
|
|
||||||
<t t-esc="obj1.value"/>
|
|
||||||
<TestSubComponent/>
|
|
||||||
</div>`;
|
|
||||||
static components = { TestSubComponent };
|
|
||||||
}
|
|
||||||
await mount(TestComponent, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>1<div>100</div></div>");
|
|
||||||
obj1.value = 2;
|
|
||||||
obj2.value = 200;
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>2<div>200</div></div>");
|
|
||||||
});
|
|
||||||
test("can use a state hook", async () => {
|
test("can use a state hook", async () => {
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
static template = xml`<div><t t-esc="counter.value"/></div>`;
|
static template = xml`<div><t t-esc="counter.value"/></div>`;
|
||||||
counter = reactive({ value: 42 });
|
counter = useState({ value: 42 });
|
||||||
}
|
}
|
||||||
const counter = await mount(Counter, fixture);
|
const counter = await mount(Counter, fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div>42</div>");
|
expect(fixture.innerHTML).toBe("<div>42</div>");
|
||||||
@@ -61,7 +36,7 @@ describe("reactivity in lifecycle", () => {
|
|||||||
let n = 0;
|
let n = 0;
|
||||||
class Comp extends Component {
|
class Comp extends Component {
|
||||||
static template = xml`<div><t t-esc="state.a"/></div>`;
|
static template = xml`<div><t t-esc="state.a"/></div>`;
|
||||||
state = reactive({ a: 5, b: 7 });
|
state = useState({ a: 5, b: 7 });
|
||||||
setup() {
|
setup() {
|
||||||
onWillRender(() => n++);
|
onWillRender(() => n++);
|
||||||
}
|
}
|
||||||
@@ -82,7 +57,7 @@ describe("reactivity in lifecycle", () => {
|
|||||||
test("can use a state hook on Map", async () => {
|
test("can use a state hook on Map", async () => {
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
static template = xml`<div><t t-esc="counter.get('value')"/></div>`;
|
static template = xml`<div><t t-esc="counter.get('value')"/></div>`;
|
||||||
counter = reactive(new Map([["value", 42]]));
|
counter = useState(new Map([["value", 42]]));
|
||||||
}
|
}
|
||||||
const counter = await mount(Counter, fixture);
|
const counter = await mount(Counter, fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div>42</div>");
|
expect(fixture.innerHTML).toBe("<div>42</div>");
|
||||||
@@ -97,7 +72,7 @@ describe("reactivity in lifecycle", () => {
|
|||||||
static template = xml`
|
static template = xml`
|
||||||
<span><t t-esc="props.val"/><t t-esc="state.n"/></span>
|
<span><t t-esc="props.val"/><t t-esc="state.n"/></span>
|
||||||
`;
|
`;
|
||||||
state = reactive({ n: 2 });
|
state = useState({ n: 2 });
|
||||||
setup() {
|
setup() {
|
||||||
onWillRender(() => {
|
onWillRender(() => {
|
||||||
steps.push("render");
|
steps.push("render");
|
||||||
@@ -121,7 +96,7 @@ describe("reactivity in lifecycle", () => {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
state = reactive({ val: 1, flag: true });
|
state = useState({ val: 1, flag: true });
|
||||||
}
|
}
|
||||||
const parent = await mount(Parent, fixture);
|
const parent = await mount(Parent, fixture);
|
||||||
expect(steps).toEqual(["render"]);
|
expect(steps).toEqual(["render"]);
|
||||||
@@ -167,7 +142,7 @@ describe("reactivity in lifecycle", () => {
|
|||||||
static template = xml`
|
static template = xml`
|
||||||
<div><t t-esc="state.val"/></div>
|
<div><t t-esc="state.val"/></div>
|
||||||
`;
|
`;
|
||||||
state = reactive({ val: 1 });
|
state = useState({ val: 1 });
|
||||||
setup() {
|
setup() {
|
||||||
STATE = this.state;
|
STATE = this.state;
|
||||||
onWillRender(() => {
|
onWillRender(() => {
|
||||||
@@ -192,7 +167,7 @@ describe("reactivity in lifecycle", () => {
|
|||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<Child t-if="state.renderChild" state="state"/>`;
|
static template = xml`<Child t-if="state.renderChild" state="state"/>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
state: any = reactive({ renderChild: true, content: { a: 2 } });
|
state: any = useState({ renderChild: true, content: { a: 2 } });
|
||||||
setup() {
|
setup() {
|
||||||
useLogLifecycle();
|
useLogLifecycle();
|
||||||
}
|
}
|
||||||
@@ -230,8 +205,7 @@ describe("reactivity in lifecycle", () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// todo: unskip it
|
test("Component is automatically subscribed to reactive object received as prop", async () => {
|
||||||
test.skip("Component is automatically subscribed to reactive object received as prop", async () => {
|
|
||||||
let childRenderCount = 0;
|
let childRenderCount = 0;
|
||||||
let parentRenderCount = 0;
|
let parentRenderCount = 0;
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
@@ -244,7 +218,7 @@ describe("reactivity in lifecycle", () => {
|
|||||||
static template = xml`<Child obj="obj" reactiveObj="reactiveObj"/>`;
|
static template = xml`<Child obj="obj" reactiveObj="reactiveObj"/>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
obj = { a: 1 };
|
obj = { a: 1 };
|
||||||
reactiveObj = reactive({ b: 2 });
|
reactiveObj = useState({ b: 2 });
|
||||||
setup() {
|
setup() {
|
||||||
onWillRender(() => parentRenderCount++);
|
onWillRender(() => parentRenderCount++);
|
||||||
}
|
}
|
||||||
@@ -263,3 +237,34 @@ describe("reactivity in lifecycle", () => {
|
|||||||
expect(fixture.innerHTML).toBe("34");
|
expect(fixture.innerHTML).toBe("34");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("subscriptions", () => {
|
||||||
|
test("subscriptions returns the keys and targets observed by the component", async () => {
|
||||||
|
class Comp extends Component {
|
||||||
|
static template = xml`<t t-esc="state.a"/>`;
|
||||||
|
state = useState({ a: 1, b: 2 });
|
||||||
|
}
|
||||||
|
const comp = await mount(Comp, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("1");
|
||||||
|
expect(comp.__owl__.subscriptions).toEqual([{ keys: ["a"], target: toRaw(comp.state) }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("subscriptions returns the keys observed by the component", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<t t-esc="props.state.b"/>`;
|
||||||
|
setup() {
|
||||||
|
child = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let child: Child;
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<t t-esc="state.a"/><Child state="state"/>`;
|
||||||
|
static components = { Child };
|
||||||
|
state = useState({ a: 1, b: 2 });
|
||||||
|
}
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("12");
|
||||||
|
expect(parent.__owl__.subscriptions).toEqual([{ keys: ["a"], target: toRaw(parent.state) }]);
|
||||||
|
expect(child!.__owl__.subscriptions).toEqual([{ keys: ["b"], target: toRaw(parent.state) }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -330,8 +330,12 @@ describe("rendering semantics", () => {
|
|||||||
expect(fixture.innerHTML).toBe("444");
|
expect(fixture.innerHTML).toBe("444");
|
||||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
"Child:willRender",
|
"Child:willRender",
|
||||||
"Child:rendered",
|
"Child:rendered",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Parent:patched",
|
||||||
"Child:willPatch",
|
"Child:willPatch",
|
||||||
"Child:patched",
|
"Child:patched",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import { taskEffect } from "../../src/runtime/cancellableContext";
|
|
||||||
import { Task } from "../../src/runtime/task";
|
|
||||||
|
|
||||||
export type Deffered = Promise<any> & {
|
|
||||||
resolve: (value: any) => void;
|
|
||||||
reject: (reason: any) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface TaskWithResolvers<T> {
|
|
||||||
promise: Promise<T>;
|
|
||||||
resolve: (value: T | PromiseLike<T>) => void;
|
|
||||||
reject: (reason?: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let resolvers: Record<string, TaskWithResolvers<string>> = {};
|
|
||||||
function getTask(id: string) {
|
|
||||||
const resolver: {
|
|
||||||
task?: Task<string>;
|
|
||||||
resolve?: (value: string | PromiseLike<string>) => void;
|
|
||||||
reject?: (reason?: any) => void;
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
const promise = new Task<string>((res, rej) => {
|
|
||||||
resolver.resolve = res;
|
|
||||||
resolver.reject = rej;
|
|
||||||
});
|
|
||||||
resolver.task = promise;
|
|
||||||
|
|
||||||
resolvers[id] = resolver as TaskWithResolvers<string>;
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
function tick() {
|
|
||||||
return new Promise((r) => setTimeout(r, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// const timeoutTask = (ms: number) => new Task((resolve) => setTimeout(() => resolve(ms), ms));
|
|
||||||
|
|
||||||
const steps: string[] = [];
|
|
||||||
function step(msg: string) {
|
|
||||||
steps.push(msg);
|
|
||||||
}
|
|
||||||
function verifySteps(expected: string[]) {
|
|
||||||
expect(steps).toEqual(expected);
|
|
||||||
steps.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
resolvers = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("task", () => {
|
|
||||||
test("should run a task properly", async () => {
|
|
||||||
taskEffect(async () => {
|
|
||||||
let result;
|
|
||||||
step(`a:begin`);
|
|
||||||
result = await getTask("a");
|
|
||||||
step(`a:${result}`);
|
|
||||||
result = await getTask("b");
|
|
||||||
step(`b:${result}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
verifySteps(["a:begin"]);
|
|
||||||
resolvers["a"].resolve("a");
|
|
||||||
await tick();
|
|
||||||
verifySteps(["a:a"]);
|
|
||||||
resolvers["b"].resolve("b");
|
|
||||||
await tick();
|
|
||||||
verifySteps(["b:b"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test.only("should cancel a task properly", async () => {
|
|
||||||
const ctx = taskEffect(async () => {
|
|
||||||
let result;
|
|
||||||
step(`a:begin`);
|
|
||||||
result = await getTask("a");
|
|
||||||
step(`a:${result}`);
|
|
||||||
result = await getTask("b");
|
|
||||||
step(`b:${result}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
verifySteps(["a:begin"]);
|
|
||||||
resolvers["a"].resolve("a");
|
|
||||||
await tick();
|
|
||||||
verifySteps(["a:a"]);
|
|
||||||
ctx.cancel();
|
|
||||||
resolvers["b"].resolve("b");
|
|
||||||
await tick();
|
|
||||||
verifySteps([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should run a task with subtasks properly", async () => {
|
|
||||||
taskEffect(async () => {
|
|
||||||
let result;
|
|
||||||
step(`a:begin`);
|
|
||||||
result = await getTask("a");
|
|
||||||
step(`a:${result}`);
|
|
||||||
result = await getTask("b");
|
|
||||||
step(`b:${result}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
verifySteps(["a:begin"]);
|
|
||||||
resolvers["a"].resolve("a");
|
|
||||||
await tick();
|
|
||||||
verifySteps(["a:a"]);
|
|
||||||
resolvers["b"].resolve("b");
|
|
||||||
await tick();
|
|
||||||
verifySteps(["b:b"]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -458,8 +458,10 @@ describe("Portal", () => {
|
|||||||
"parent:willPatch",
|
"parent:willPatch",
|
||||||
"child:mounted",
|
"child:mounted",
|
||||||
"parent:patched",
|
"parent:patched",
|
||||||
|
"parent:willPatch",
|
||||||
"child:willPatch",
|
"child:willPatch",
|
||||||
"child:patched",
|
"child:patched",
|
||||||
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
expect(fixture.innerHTML).toBe('<div id="outside"><span>2</span></div><div></div>');
|
expect(fixture.innerHTML).toBe('<div id="outside"><span>2</span></div><div></div>');
|
||||||
|
|
||||||
@@ -470,8 +472,10 @@ describe("Portal", () => {
|
|||||||
"parent:willPatch",
|
"parent:willPatch",
|
||||||
"child:mounted",
|
"child:mounted",
|
||||||
"parent:patched",
|
"parent:patched",
|
||||||
|
"parent:willPatch",
|
||||||
"child:willPatch",
|
"child:willPatch",
|
||||||
"child:patched",
|
"child:patched",
|
||||||
|
"parent:patched",
|
||||||
"parent:willPatch",
|
"parent:willPatch",
|
||||||
"child:willUnmount",
|
"child:willUnmount",
|
||||||
"parent:patched",
|
"parent:patched",
|
||||||
|
|||||||
+809
-759
File diff suppressed because it is too large
Load Diff
+63
-1
@@ -1,5 +1,7 @@
|
|||||||
import { batched, EventBus, htmlEscape, markup } from "../src/runtime/utils";
|
import { batched, EventBus, htmlEscape, markup } from "../src/runtime/utils";
|
||||||
import { nextMicroTick } from "./helpers";
|
import { makeTestFixture, nextMicroTick } from "./helpers";
|
||||||
|
import { getCurrent } from "../src/runtime/component_node";
|
||||||
|
import { Component, mount, xml } from "../src";
|
||||||
|
|
||||||
describe("event bus behaviour", () => {
|
describe("event bus behaviour", () => {
|
||||||
test("can subscribe and be notified", () => {
|
test("can subscribe and be notified", () => {
|
||||||
@@ -33,6 +35,66 @@ describe("event bus behaviour", () => {
|
|||||||
bus.addEventListener("event", (ev: any) => expect(ev.detail).toBe("hello world"));
|
bus.addEventListener("event", (ev: any) => expect(ev.detail).toBe("hello world"));
|
||||||
bus.trigger("event", "hello world");
|
bus.trigger("event", "hello world");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("events are not validated if the bus is created outside of dev mode", async () => {
|
||||||
|
let bus_empty: EventBus | null = null;
|
||||||
|
class Root extends Component {
|
||||||
|
static template = xml`<div/>`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
getCurrent(); // checks that we're in a component context
|
||||||
|
|
||||||
|
bus_empty = new EventBus([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await mount(Root, makeTestFixture());
|
||||||
|
|
||||||
|
bus_empty!.addEventListener("a", () => {});
|
||||||
|
bus_empty!.trigger("a");
|
||||||
|
bus_empty!.dispatchEvent(new CustomEvent("a"));
|
||||||
|
});
|
||||||
|
test("events are validated if the bus is created in dev mode & events are provided", async () => {
|
||||||
|
let bus: EventBus | null = null;
|
||||||
|
let bus_empty: EventBus | null = null;
|
||||||
|
let bbus_no_validation: EventBus | null = null;
|
||||||
|
class Root extends Component {
|
||||||
|
static template = xml`<div/>`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
getCurrent(); // checks that we're in a component context
|
||||||
|
|
||||||
|
bus = new EventBus(["a", "b"]);
|
||||||
|
bus_empty = new EventBus([]);
|
||||||
|
bbus_no_validation = new EventBus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Root, makeTestFixture(), { test: true });
|
||||||
|
|
||||||
|
bbus_no_validation!.addEventListener("c", () => {});
|
||||||
|
bbus_no_validation!.trigger("c");
|
||||||
|
bbus_no_validation!.dispatchEvent(new CustomEvent("c"));
|
||||||
|
|
||||||
|
bus!.addEventListener("a", () => {});
|
||||||
|
bus!.trigger("a");
|
||||||
|
bus!.dispatchEvent(new CustomEvent("a"));
|
||||||
|
|
||||||
|
expect(() => bus!.addEventListener("c", () => {})).toThrow(
|
||||||
|
"EventBus: subscribing to unknown event 'c'"
|
||||||
|
);
|
||||||
|
expect(() => bus!.trigger("c")).toThrow("EventBus: triggering unknown event 'c'");
|
||||||
|
expect(() => bus!.dispatchEvent(new CustomEvent("c"))).toThrow(
|
||||||
|
"EventBus: dispatching unknown event 'c'"
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => bus_empty!.addEventListener("a", () => {})).toThrow(
|
||||||
|
"EventBus: subscribing to unknown event 'a'"
|
||||||
|
);
|
||||||
|
expect(() => bus_empty!.trigger("a")).toThrow("EventBus: triggering unknown event 'a'");
|
||||||
|
expect(() => bus_empty!.dispatchEvent(new CustomEvent("a"))).toThrow(
|
||||||
|
"EventBus: dispatching unknown event 'a'"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("batched", () => {
|
describe("batched", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user