make widget generic on State

This commit is contained in:
Géry Debongnie
2019-02-04 21:09:17 +01:00
parent 70b25fc7b8
commit 05e9797f7a
16 changed files with 161 additions and 107 deletions
@@ -23,8 +23,8 @@ interface Meta<T extends WEnv> {
isStarted: boolean; isStarted: boolean;
isMounted: boolean; isMounted: boolean;
isDestroyed: boolean; isDestroyed: boolean;
parent: Widget<T, {}> | null; parent: BaseWidget<T, any, any> | null;
children: { [key: number]: Widget<T, {}> }; children: { [key: number]: BaseWidget<T, any, any> };
// children mapping: from templateID to widgetID // children mapping: from templateID to widgetID
// should it be a map number => Widget? // should it be a map number => Widget?
cmap: { [key: number]: number }; cmap: { [key: number]: number };
@@ -40,7 +40,7 @@ export interface Type<T> extends Function {
// Widget // Widget
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
export class Widget<T extends WEnv, Props> extends EventBus { export class BaseWidget<T extends WEnv, Props, State> extends EventBus {
__widget__: Meta<WEnv>; __widget__: Meta<WEnv>;
template: string = "default"; template: string = "default";
inlineTemplate: string | null = null; inlineTemplate: string | null = null;
@@ -52,13 +52,15 @@ export class Widget<T extends WEnv, Props> extends EventBus {
env: T; env: T;
state: Object = {}; state: Object = {};
props: Props; props: Props;
refs: { [key: string]: Widget<T, {}> | HTMLElement | undefined } = {}; refs: {
[key: string]: BaseWidget<T, any, any> | HTMLElement | undefined;
} = {};
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Lifecycle // Lifecycle
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
constructor(parent: Widget<T, any> | T, props?: Props) { constructor(parent: BaseWidget<T, any, any> | T, props?: Props) {
super(); super();
wl.push(this); wl.push(this);
@@ -68,8 +70,8 @@ export class Widget<T extends WEnv, Props> extends EventBus {
// Pro: but creating widget (by a template) is always unsafe anyway // Pro: but creating widget (by a template) is always unsafe anyway
this.props = <Props>props; this.props = <Props>props;
let id: number; let id: number;
let p: Widget<T, any> | null = null; let p: BaseWidget<T, any, any> | null = null;
if (parent instanceof Widget) { if (parent instanceof BaseWidget) {
p = parent; p = parent;
this.env = parent.env; this.env = parent.env;
id = this.env.getID(); id = this.env.getID();
@@ -173,17 +175,11 @@ export class Widget<T extends WEnv, Props> extends EventBus {
* - it is ok to call updateState before the widget is started. In that * - it is ok to call updateState before the widget is started. In that
* case, it will simply update the state and will not rerender * case, it will simply update the state and will not rerender
*/ */
async updateState(nextState: Object) { async updateState(nextState: Partial<State>) {
if (Object.keys(nextState).length === 0) { if (Object.keys(nextState).length === 0) {
return; return;
} }
for (let key in nextState) { Object.assign(this.state, nextState);
if (key in this.state) {
this.state[key] = nextState[key];
} else {
throw new Error(`Invalid key: '${key}' does not exist in widget state`);
}
}
if (this.__widget__.isStarted) { if (this.__widget__.isStarted) {
return this.render(); return this.render();
} }
@@ -259,7 +255,7 @@ export class Widget<T extends WEnv, Props> extends EventBus {
} }
} }
private visitSubTree(callback: (w: Widget<T, any>) => boolean) { private visitSubTree(callback: (w: BaseWidget<T, any, any>) => boolean) {
const shouldVisitChildren = callback(this); const shouldVisitChildren = callback(this);
if (shouldVisitChildren) { if (shouldVisitChildren) {
const children = this.__widget__.children; const children = this.__widget__.children;
+11 -7
View File
@@ -1,18 +1,22 @@
import { QWeb } from "./core/qweb_vdom";
import { idGenerator, memoize } from "./core/utils";
import { WEnv } from "./core/widget";
import { ActionManager, IActionManager } from "./services/action_manager";
import { Ajax, IAjax } from "./core/ajax"; import { Ajax, IAjax } from "./core/ajax";
import { WEnv } from "./core/base_widget";
import { import {
INotificationManager, INotificationManager,
NotificationManager NotificationManager
} from "./core/notifications"; } from "./core/notifications";
import { actionRegistry } from "./registries"; import { QWeb } from "./core/qweb_vdom";
import { Registry } from "./core/registry"; import { Registry } from "./core/registry";
import { IRouter, Router } from "./core/router"; import { IRouter, Router } from "./core/router";
import { idGenerator, memoize } from "./core/utils";
import { actionRegistry } from "./registries";
import {
ActionManager,
ActionWidget,
IActionManager
} from "./services/action_manager";
import { CRM } from "./widgets/crm"; import { CRM } from "./widgets/crm";
import { Discuss } from "./widgets/discuss"; import { Discuss } from "./widgets/discuss";
import { Widget, Type } from "./core/widget";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Types // Types
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -30,7 +34,7 @@ export interface Env extends WEnv {
router: IRouter; router: IRouter;
// registries // registries
actionRegistry: Registry<Type<Widget<Env, any>>>; actionRegistry: Registry<ActionWidget>;
// data // data
menus: Menu[]; menus: Menu[];
+2 -3
View File
@@ -1,5 +1,4 @@
import { Registry } from "./core/registry"; import { Registry } from "./core/registry";
import { Widget, Type } from "./core/widget"; import { ActionWidget } from "./services/action_manager";
import { Env } from "./env";
export const actionRegistry: Registry<Type<Widget<Env, any>>> = new Registry(); export const actionRegistry: Registry<ActionWidget> = new Registry();
+7 -5
View File
@@ -1,7 +1,7 @@
import { Type } from "../core/base_widget";
import { EventBus } from "../core/event_bus"; import { EventBus } from "../core/event_bus";
import { Registry } from "../core/registry"; import { Registry } from "../core/registry";
import { Type, Widget } from "../core/widget"; import { Widget } from "../widgets/widget";
import { Env } from "../env";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Types // Types
@@ -18,10 +18,12 @@ export interface CommonActionInfo {
target: "current" | "new"; target: "current" | "new";
} }
export type ActionWidget = Type<Widget<{}, {}>>;
export interface ClientActionInfo extends CommonActionInfo { export interface ClientActionInfo extends CommonActionInfo {
type: "client"; type: "client";
name: string; name: string;
Widget: Type<Widget<Env, {}>>; Widget: ActionWidget;
} }
export interface ActWindowInfo extends CommonActionInfo { export interface ActWindowInfo extends CommonActionInfo {
@@ -47,10 +49,10 @@ export interface IActionManager {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
export class ActionManager extends EventBus implements IActionManager { export class ActionManager extends EventBus implements IActionManager {
registry: Registry<Type<Widget<Env, any>>>; registry: Registry<ActionWidget>;
stack: ActionStack; stack: ActionStack;
constructor(registry: Registry<Type<Widget<Env, any>>>) { constructor(registry: Registry<ActionWidget>) {
super(); super();
this.registry = registry; this.registry = registry;
this.stack = []; this.stack = [];
+2 -3
View File
@@ -1,6 +1,5 @@
import { Widget } from "../core/widget"; import { Widget } from "./widget";
import { Env } from "../env";
export class CRM extends Widget<Env, {}> { export class CRM extends Widget<{}, {}> {
template = "web.crm"; template = "web.crm";
} }
+16 -4
View File
@@ -1,22 +1,34 @@
import { Widget } from "../core/widget"; import { Widget } from "./widget";
import { Env } from "../env";
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
interface Props { interface Props {
initialState?: number; initialState?: number;
} }
export class Counter extends Widget<Env, Props> { interface State {
counter: number;
}
//------------------------------------------------------------------------------
// Counter
//------------------------------------------------------------------------------
export class Counter extends Widget<Props, State> {
inlineTemplate = ` inlineTemplate = `
<div t-name="counter"> <div t-name="counter">
<button t-on-click="increment(-1)">-</button> <button t-on-click="increment(-1)">-</button>
<span style="font-weight:bold">Value: <t t-esc="state.counter"/></span> <span style="font-weight:bold">Value: <t t-esc="state.counter"/></span>
<button t-on-click="increment(1)">+</button> <button t-on-click="increment(1)">+</button>
</div>`; </div>`;
state = { state = {
counter: 0 counter: 0
}; };
constructor(parent: Widget<Env, {}>, props: Props) { constructor(parent: Widget<any, any>, props: Props) {
super(parent, props); super(parent, props);
this.state.counter = props.initialState || 0; this.state.counter = props.initialState || 0;
} }
+16 -4
View File
@@ -1,9 +1,21 @@
import { Widget } from "../core/widget";
import { Env } from "../env";
import { Clock } from "./clock"; import { Clock } from "./clock";
import { Counter } from "./counter"; import { Counter } from "./counter";
import { Widget } from "./widget";
export class Discuss extends Widget<Env, {}> { //------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
interface State {
validcounter: boolean;
color: "red" | "blue";
}
//------------------------------------------------------------------------------
// Discuss
//------------------------------------------------------------------------------
export class Discuss extends Widget<{}, State> {
template = "web.discuss"; template = "web.discuss";
widgets = { Clock, Counter, ColorWidget }; widgets = { Clock, Counter, ColorWidget };
state = { validcounter: true, color: "red" }; state = { validcounter: true, color: "red" };
@@ -38,7 +50,7 @@ export class Discuss extends Widget<Env, {}> {
} }
} }
class ColorWidget extends Widget<Env, { color: "red" | "blue" }> { class ColorWidget extends Widget<{ color: "red" | "blue" }, {}> {
inlineTemplate = ` inlineTemplate = `
<div t-name="colorwidget"> <div t-name="colorwidget">
<span>Current Color: </span> <span>Current Color: </span>
+11 -3
View File
@@ -1,13 +1,21 @@
import { Widget } from "../core/widget"; import { Menu } from "../env";
import { Env, Menu } from "../env";
import { MenuItem } from "../misc/menu_helpers"; import { MenuItem } from "../misc/menu_helpers";
import { Widget } from "./widget";
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
export interface Props { export interface Props {
inHome: boolean; inHome: boolean;
app: MenuItem | null; app: MenuItem | null;
} }
export class Navbar extends Widget<Env, Props> { //------------------------------------------------------------------------------
// Navbar
//------------------------------------------------------------------------------
export class Navbar extends Widget<Props, {}> {
template = "web.navbar"; template = "web.navbar";
getUrl(menu: Menu) { getUrl(menu: Menu) {
+10 -3
View File
@@ -1,12 +1,19 @@
import { Widget } from "../core/widget";
import { Env } from "../env";
import { ActionStack } from "../services/action_manager"; import { ActionStack } from "../services/action_manager";
import { Widget } from "./widget";
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
export interface Props { export interface Props {
stack: ActionStack; stack: ActionStack;
} }
export class ActionContainer extends Widget<Env, Props> { //------------------------------------------------------------------------------
// Action Container
//------------------------------------------------------------------------------
export class ActionContainer extends Widget<Props, {}> {
template = "web.action_container"; template = "web.action_container";
currentWidget: any; currentWidget: any;
+14 -3
View File
@@ -1,7 +1,18 @@
import { Widget } from "../core/widget"; import { Widget } from "./widget";
import { Env } from "../env";
export class Clock extends Widget<Env, {}> { //------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
interface State {
currentTime: string;
}
//------------------------------------------------------------------------------
// Clock
//------------------------------------------------------------------------------
export class Clock extends Widget<{}, State> {
inlineTemplate = `<div class="o_clock"><t t-esc="state.currentTime"/></div>`; inlineTemplate = `<div class="o_clock"><t t-esc="state.currentTime"/></div>`;
timeout: any | undefined; timeout: any | undefined;
+2 -3
View File
@@ -1,6 +1,5 @@
import { Widget } from "../core/widget";
import { Env } from "../env";
import { MenuInfo, MenuItem } from "../misc/menu_helpers"; import { MenuInfo, MenuItem } from "../misc/menu_helpers";
import { Widget } from "./widget";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Types // Types
@@ -14,7 +13,7 @@ export interface Props {
// Home Menu // Home Menu
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
export class HomeMenu extends Widget<Env, Props> { export class HomeMenu extends Widget<Props, {}> {
template = "web.home_menu"; template = "web.home_menu";
get apps(): MenuItem[] { get apps(): MenuItem[] {
+2 -3
View File
@@ -1,8 +1,7 @@
import { Widget } from "../core/widget";
import { Env } from "../env";
import { INotification } from "../core/notifications"; import { INotification } from "../core/notifications";
import { Widget } from "./widget";
export class Notification extends Widget<Env, INotification> { export class Notification extends Widget<INotification, {}> {
template = "web.notification"; template = "web.notification";
close() { close() {
+2 -2
View File
@@ -1,5 +1,5 @@
import { INotification } from "../core/notifications"; import { INotification } from "../core/notifications";
import { Widget } from "../core/widget"; import { Widget } from "./widget";
import { Env } from "../env"; import { Env } from "../env";
import { MenuInfo, MenuItem, getAppAndAction } from "../misc/menu_helpers"; import { MenuInfo, MenuItem, getAppAndAction } from "../misc/menu_helpers";
import { ActionStack } from "../services/action_manager"; import { ActionStack } from "../services/action_manager";
@@ -28,7 +28,7 @@ interface Props {
// Root Widget // Root Widget
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
export class Root extends Widget<Env, Props> { export class Root extends Widget<Props, State> {
template = "web.web_client"; template = "web.web_client";
widgets = { Navbar, Notification, HomeMenu, ActionContainer }; widgets = { Navbar, Notification, HomeMenu, ActionContainer };
+4
View File
@@ -0,0 +1,4 @@
import { BaseWidget } from "../core/base_widget";
import { Env } from "../env";
export class Widget<Props, State> extends BaseWidget<Env, Props, State> {}
@@ -1,6 +1,5 @@
import { WEnv, Widget } from "../../src/ts/core/widget"; import { BaseWidget, WEnv } from "../../src/ts/core/base_widget";
import { makeTestWEnv, makeTestFixture } from "../helpers"; import { makeTestFixture, makeTestWEnv, normalize } from "../helpers";
import { normalize } from "../helpers";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Setup and helpers // Setup and helpers
@@ -34,13 +33,16 @@ function nextTick(): Promise<void> {
return Promise.resolve(); return Promise.resolve();
} }
function children(w: Widget<WEnv, {}>): Widget<WEnv, {}>[] { class Widget extends BaseWidget<WEnv, {}, {}> {}
function children(w: Widget): Widget[] {
const childrenMap = w.__widget__.children; const childrenMap = w.__widget__.children;
return Object.keys(childrenMap).map(id => childrenMap[id]); return Object.keys(childrenMap).map(id => childrenMap[id]);
} }
// Test widgets // Test widgets
class Counter extends Widget<WEnv, {}> { class Counter extends Widget {
// class Counter extends Widget<WEnv, {}, {counter: number}> {
template = "counter"; template = "counter";
state = { state = {
counter: 0 counter: 0
@@ -51,12 +53,12 @@ class Counter extends Widget<WEnv, {}> {
} }
} }
class WidgetA extends Widget<WEnv, {}> { class WidgetA extends Widget {
template = "widgetA"; template = "widgetA";
widgets = { b: WidgetB }; widgets = { b: WidgetB };
} }
class WidgetB extends Widget<WEnv, {}> { class WidgetB extends Widget {
template = "widgetB"; template = "widgetB";
} }
@@ -88,7 +90,7 @@ describe("basic widget properties", () => {
}); });
test("widget style and classname", async () => { test("widget style and classname", async () => {
class StyledWidget extends Widget<WEnv, {}> { class StyledWidget extends Widget {
inlineTemplate = `<div style="font-weight:bold;" class="some-class">world</div>`; inlineTemplate = `<div style="font-weight:bold;" class="some-class">world</div>`;
} }
const widget = new StyledWidget(env); const widget = new StyledWidget(env);
@@ -100,7 +102,7 @@ describe("basic widget properties", () => {
test("updateState before first render does not trigger a render", async () => { test("updateState before first render does not trigger a render", async () => {
let renderCalls = 0; let renderCalls = 0;
class TestW extends Widget<WEnv, {}> { class TestW extends Widget {
async willStart() { async willStart() {
this.updateState({}); this.updateState({});
} }
@@ -142,7 +144,7 @@ describe("basic widget properties", () => {
describe("lifecycle hooks", () => { describe("lifecycle hooks", () => {
test("willStart hook is called", async () => { test("willStart hook is called", async () => {
let willstart = false; let willstart = false;
class HookWidget extends Widget<WEnv, {}> { class HookWidget extends Widget {
async willStart() { async willStart() {
willstart = true; willstart = true;
} }
@@ -154,7 +156,7 @@ describe("lifecycle hooks", () => {
test("mounted hook is not called if not in DOM", async () => { test("mounted hook is not called if not in DOM", async () => {
let mounted = false; let mounted = false;
class HookWidget extends Widget<WEnv, {}> { class HookWidget extends Widget {
async mounted() { async mounted() {
mounted = true; mounted = true;
} }
@@ -167,7 +169,7 @@ describe("lifecycle hooks", () => {
test("mounted hook is called if mounted in DOM", async () => { test("mounted hook is called if mounted in DOM", async () => {
let mounted = false; let mounted = false;
class HookWidget extends Widget<WEnv, {}> { class HookWidget extends Widget {
async mounted() { async mounted() {
mounted = true; mounted = true;
} }
@@ -179,11 +181,11 @@ describe("lifecycle hooks", () => {
test("willStart hook is called on subwidget", async () => { test("willStart hook is called on subwidget", async () => {
let ok = false; let ok = false;
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="child"/></div>`; inlineTemplate = `<div><t t-widget="child"/></div>`;
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
} }
class ChildWidget extends Widget<WEnv, {}> { class ChildWidget extends Widget {
async willStart() { async willStart() {
ok = true; ok = true;
} }
@@ -197,7 +199,7 @@ describe("lifecycle hooks", () => {
expect.assertions(4); expect.assertions(4);
let parentMounted = false; let parentMounted = false;
let childMounted = false; let childMounted = false;
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="child"/></div>`; inlineTemplate = `<div><t t-widget="child"/></div>`;
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
mounted() { mounted() {
@@ -205,7 +207,7 @@ describe("lifecycle hooks", () => {
parentMounted = true; parentMounted = true;
} }
} }
class ChildWidget extends Widget<WEnv, {}> { class ChildWidget extends Widget {
mounted() { mounted() {
expect(document.body.contains(this.el)).toBe(true); expect(document.body.contains(this.el)).toBe(true);
expect(parentMounted).toBe(true); expect(parentMounted).toBe(true);
@@ -223,7 +225,7 @@ describe("lifecycle hooks", () => {
// the t-else part in the template is important. This is // the t-else part in the template is important. This is
// necessary to have a situation that could confuse the vdom // necessary to have a situation that could confuse the vdom
// patching algorithm // patching algorithm
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
inlineTemplate = ` inlineTemplate = `
<div> <div>
<t t-if="state.ok"> <t t-if="state.ok">
@@ -236,7 +238,7 @@ describe("lifecycle hooks", () => {
state = { ok: false }; state = { ok: false };
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
} }
class ChildWidget extends Widget<WEnv, {}> { class ChildWidget extends Widget {
async willStart() { async willStart() {
hookCounter++; hookCounter++;
} }
@@ -261,13 +263,13 @@ describe("lifecycle hooks", () => {
expect.assertions(1); expect.assertions(1);
const target = document.createElement("div"); const target = document.createElement("div");
document.body.appendChild(target); document.body.appendChild(target);
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
mounted() { mounted() {
const child = new ChildWidget(this); const child = new ChildWidget(this);
child.mount(this.el!); child.mount(this.el!);
} }
} }
class ChildWidget extends Widget<WEnv, {}> { class ChildWidget extends Widget {
mounted() { mounted() {
expect(this.el).toBeTruthy(); expect(this.el).toBeTruthy();
done(); done();
@@ -279,7 +281,7 @@ describe("lifecycle hooks", () => {
test("widgets are unmounted and destroyed if no longer in DOM", async () => { test("widgets are unmounted and destroyed if no longer in DOM", async () => {
let steps: string[] = []; let steps: string[] = [];
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
state = { ok: true }; state = { ok: true };
inlineTemplate = `<div> inlineTemplate = `<div>
<t t-if="state.ok"><t t-widget="child"/></t> <t t-if="state.ok"><t t-widget="child"/></t>
@@ -287,7 +289,7 @@ describe("lifecycle hooks", () => {
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
} }
class ChildWidget extends Widget<WEnv, {}> { class ChildWidget extends Widget {
constructor(parent) { constructor(parent) {
super(parent); super(parent);
steps.push("init"); steps.push("init");
@@ -320,7 +322,7 @@ describe("lifecycle hooks", () => {
test("hooks are called in proper order in widget creation/destruction", async () => { test("hooks are called in proper order in widget creation/destruction", async () => {
let steps: string[] = []; let steps: string[] = [];
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="child"/></div>`; inlineTemplate = `<div><t t-widget="child"/></div>`;
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
constructor(parent) { constructor(parent) {
@@ -341,7 +343,7 @@ describe("lifecycle hooks", () => {
} }
} }
class ChildWidget extends Widget<WEnv, {}> { class ChildWidget extends Widget {
constructor(parent) { constructor(parent) {
super(parent); super(parent);
steps.push("c init"); steps.push("c init");
@@ -378,7 +380,7 @@ describe("lifecycle hooks", () => {
test("shouldUpdate hook prevent rerendering", async () => { test("shouldUpdate hook prevent rerendering", async () => {
let shouldUpdate = false; let shouldUpdate = false;
class TestWidget extends Widget<WEnv, {}> { class TestWidget extends Widget {
inlineTemplate = `<div><t t-esc="props.val"/></div>`; inlineTemplate = `<div><t t-esc="props.val"/></div>`;
shouldUpdate() { shouldUpdate() {
return shouldUpdate; return shouldUpdate;
@@ -408,7 +410,7 @@ describe("destroy method", () => {
test("destroying a widget twice only call destroyed once", async () => { test("destroying a widget twice only call destroyed once", async () => {
let count = 0; let count = 0;
class TestWidget extends Widget<WEnv, {}> { class TestWidget extends Widget {
destroyed() { destroyed() {
count++; count++;
} }
@@ -450,7 +452,7 @@ describe("destroy method", () => {
let p: Promise<void> = new Promise(function(r) { let p: Promise<void> = new Promise(function(r) {
resolve = r; resolve = r;
}); });
class DelayedWidget extends Widget<WEnv, {}> { class DelayedWidget extends Widget {
willStart() { willStart() {
return p; return p;
} }
@@ -490,7 +492,7 @@ describe("composition", () => {
}); });
test("t-refs on widget are widgets", async () => { test("t-refs on widget are widgets", async () => {
class WidgetC extends Widget<WEnv, {}> { class WidgetC extends Widget {
inlineTemplate = `<div>Hello<t t-ref="mywidgetb" t-widget="b"/></div>`; inlineTemplate = `<div>Hello<t t-ref="mywidgetb" t-widget="b"/></div>`;
widgets = { b: WidgetB }; widgets = { b: WidgetB };
} }
@@ -500,7 +502,7 @@ describe("composition", () => {
}); });
test("modifying a sub widget", async () => { test("modifying a sub widget", async () => {
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="Counter"/></div>`; inlineTemplate = `<div><t t-widget="Counter"/></div>`;
widgets = { Counter }; widgets = { Counter };
} }
@@ -538,7 +540,7 @@ describe("composition", () => {
}); });
test("rerendering a widget with a sub widget", async () => { test("rerendering a widget with a sub widget", async () => {
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="Counter"/></div>`; inlineTemplate = `<div><t t-widget="Counter"/></div>`;
widgets = { Counter }; widgets = { Counter };
} }
@@ -557,7 +559,7 @@ describe("composition", () => {
}); });
test("sub widgets are destroyed if no longer in dom, then recreated", async () => { test("sub widgets are destroyed if no longer in dom, then recreated", async () => {
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
state = { ok: true }; state = { ok: true };
inlineTemplate = `<div><t t-if="state.ok"><t t-widget="counter"/></t></div>`; inlineTemplate = `<div><t t-if="state.ok"><t t-widget="counter"/></t></div>`;
widgets = { counter: Counter }; widgets = { counter: Counter };
@@ -579,7 +581,7 @@ describe("composition", () => {
}); });
test("sub widgets with t-keep-alive are not destroyed if no longer in dom", async () => { test("sub widgets with t-keep-alive are not destroyed if no longer in dom", async () => {
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
state = { ok: true }; state = { ok: true };
inlineTemplate = `<div><t t-if="state.ok"><t t-widget="counter" t-keep-alive="1"/></t></div>`; inlineTemplate = `<div><t t-if="state.ok"><t t-widget="counter" t-keep-alive="1"/></t></div>`;
widgets = { counter: Counter }; widgets = { counter: Counter };
@@ -605,12 +607,12 @@ describe("composition", () => {
}); });
test("sub widgets dom state with t-keep-alive is preserved", async () => { test("sub widgets dom state with t-keep-alive is preserved", async () => {
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
state = { ok: true }; state = { ok: true };
inlineTemplate = `<div><t t-if="state.ok"><t t-widget="InputWidget" t-keep-alive="1"/></t></div>`; inlineTemplate = `<div><t t-if="state.ok"><t t-widget="InputWidget" t-keep-alive="1"/></t></div>`;
widgets = { InputWidget }; widgets = { InputWidget };
} }
class InputWidget extends Widget<WEnv, {}> { class InputWidget extends Widget {
inlineTemplate = "<input/>"; inlineTemplate = "<input/>";
} }
const widget = new ParentWidget(env); const widget = new ParentWidget(env);
@@ -627,11 +629,11 @@ describe("composition", () => {
}); });
test("sub widgets rendered in a loop", async () => { test("sub widgets rendered in a loop", async () => {
class ChildWidget extends Widget<WEnv, { n: number }> { class ChildWidget extends Widget {
inlineTemplate = `<span><t t-esc="props.n"/></span>`; inlineTemplate = `<span><t t-esc="props.n"/></span>`;
} }
class Parent extends Widget<WEnv, {}> { class Parent extends Widget {
inlineTemplate = ` inlineTemplate = `
<div> <div>
<t t-foreach="state.numbers" t-as="number"> <t t-foreach="state.numbers" t-as="number">
@@ -658,7 +660,7 @@ describe("composition", () => {
test("sub widgets with some state rendered in a loop", async () => { test("sub widgets with some state rendered in a loop", async () => {
let n = 1; let n = 1;
class ChildWidget extends Widget<WEnv, never> { class ChildWidget extends Widget {
inlineTemplate = `<span><t t-esc="state.n"/></span>`; inlineTemplate = `<span><t t-esc="state.n"/></span>`;
constructor(parent) { constructor(parent) {
super(parent); super(parent);
@@ -667,7 +669,7 @@ describe("composition", () => {
} }
} }
class Parent extends Widget<WEnv, {}> { class Parent extends Widget {
inlineTemplate = `<div> inlineTemplate = `<div>
<t t-foreach="state.numbers" t-as="number"> <t t-foreach="state.numbers" t-as="number">
<t t-widget="ChildWidget" t-key="number"/> <t t-widget="ChildWidget" t-key="number"/>
@@ -694,13 +696,13 @@ describe("composition", () => {
describe("props evaluation (with t-props directive)", () => { describe("props evaluation (with t-props directive)", () => {
test("explicit object prop", async () => { test("explicit object prop", async () => {
class Parent extends Widget<WEnv, {}> { class Parent extends Widget {
inlineTemplate = `<div><t t-widget="child" t-props="{value: state.val}"/></div>`; inlineTemplate = `<div><t t-widget="child" t-props="{value: state.val}"/></div>`;
widgets = { child: Child }; widgets = { child: Child };
state = { val: 42 }; state = { val: 42 };
} }
class Child extends Widget<WEnv, {}> { class Child extends Widget {
inlineTemplate = `<span><t t-esc="state.someval"/></span>`; inlineTemplate = `<span><t t-esc="state.someval"/></span>`;
state: { someval: number }; state: { someval: number };
constructor(parent: Parent, props: { value: number }) { constructor(parent: Parent, props: { value: number }) {
@@ -715,13 +717,13 @@ describe("props evaluation (with t-props directive)", () => {
}); });
test("object prop value", async () => { test("object prop value", async () => {
class Parent extends Widget<WEnv, {}> { class Parent extends Widget {
inlineTemplate = `<div><t t-widget="child" t-props="state"/></div>`; inlineTemplate = `<div><t t-widget="child" t-props="state"/></div>`;
widgets = { child: Child }; widgets = { child: Child };
state = { val: 42 }; state = { val: 42 };
} }
class Child extends Widget<WEnv, {}> { class Child extends Widget {
inlineTemplate = `<span><t t-esc="state.someval"/></span>`; inlineTemplate = `<span><t t-esc="state.someval"/></span>`;
state: { someval: number }; state: { someval: number };
constructor(parent: Parent, props: { val: number }) { constructor(parent: Parent, props: { val: number }) {
@@ -739,7 +741,7 @@ describe("props evaluation (with t-props directive)", () => {
describe("t-on directive on widgets", () => { describe("t-on directive on widgets", () => {
test("t-on works as expected", async () => { test("t-on works as expected", async () => {
let n = 0; let n = 0;
class ParentWidget extends Widget<WEnv, {}> { class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="child" t-on-customevent="someMethod"/></div>`; inlineTemplate = `<div><t t-widget="child" t-on-customevent="someMethod"/></div>`;
widgets = { child: Child }; widgets = { child: Child };
someMethod(arg) { someMethod(arg) {
@@ -747,7 +749,7 @@ describe("t-on directive on widgets", () => {
n++; n++;
} }
} }
class Child extends Widget<WEnv, {}> {} class Child extends Widget {}
const widget = new ParentWidget(env); const widget = new ParentWidget(env);
await widget.mount(fixture); await widget.mount(fixture);
let child = children(widget)[0]; let child = children(widget)[0];
@@ -765,7 +767,7 @@ describe("random stuff/miscellaneous", () => {
// this test makes sure that the foreach directive does not pollute sub // this test makes sure that the foreach directive does not pollute sub
// context with the inLoop variable, which is then used in the t-widget // context with the inLoop variable, which is then used in the t-widget
// directive as a key // directive as a key
class Test extends Widget<WEnv, {}> { class Test extends Widget {
inlineTemplate = `<div><t t-foreach="2">txt</t><t t-widget="widget"/></div>`; inlineTemplate = `<div><t t-foreach="2">txt</t><t t-widget="widget"/></div>`;
widgets = { widget: Widget }; widgets = { widget: Widget };
} }
@@ -778,13 +780,13 @@ describe("random stuff/miscellaneous", () => {
// in this situation, we protect against a bug that occurred: because of the // in this situation, we protect against a bug that occurred: because of the
// interplay between widgets and vnodes, a sub widget vnode was patched // interplay between widgets and vnodes, a sub widget vnode was patched
// twice. // twice.
class Parent extends Widget<WEnv, {}> { class Parent extends Widget {
inlineTemplate = `<div><t t-widget="child" t-props="state"/></div>`; inlineTemplate = `<div><t t-widget="child" t-props="state"/></div>`;
widgets = { child: Child }; widgets = { child: Child };
state = { flag: false }; state = { flag: false };
} }
class Child extends Widget<WEnv, {}> { class Child extends Widget {
inlineTemplate = `<span>abc<t t-if="props.flag">def</t></span>`; inlineTemplate = `<span>abc<t t-if="props.flag">def</t></span>`;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
import { QWeb } from "../src/ts/core/qweb_vdom"; import { QWeb } from "../src/ts/core/qweb_vdom";
import { idGenerator } from "../src/ts/core/utils"; import { idGenerator } from "../src/ts/core/utils";
import { WEnv } from "../src/ts/core/widget"; import { WEnv } from "../src/ts/core/base_widget";
import { Env } from "../src/ts/env"; import { Env } from "../src/ts/env";
import { IAjax, RPCQuery } from "../src/ts/core/ajax"; import { IAjax, RPCQuery } from "../src/ts/core/ajax";
import { Registry } from "../src/ts/core/registry"; import { Registry } from "../src/ts/core/registry";