2019-07-14 22:18:00 +02:00
import { Component , Env } from "../../src/component/component" ;
import { QWeb } from "../../src/qweb/qweb" ;
2019-09-12 09:45:32 +02:00
import { xml } from "../../src/tags" ;
2019-09-26 21:56:01 +02:00
import { useState , useRef } from "../../src/hooks" ;
2019-07-14 22:18:00 +02:00
import { EventBus } from "../../src/core/event_bus" ;
2019-02-06 13:26:21 +01:00
import {
2019-02-07 12:10:44 +01:00
makeDeferred ,
2019-02-06 13:26:21 +01:00
makeTestFixture ,
2019-05-17 15:52:16 +02:00
makeTestEnv ,
2019-02-06 13:26:21 +01:00
nextMicroTick ,
2019-02-07 12:10:44 +01:00
nextTick ,
2019-06-13 16:27:12 +02:00
normalize ,
editInput
2019-07-14 22:18:00 +02:00
} from "../helpers" ;
2019-01-16 13:19:58 +01:00
2019-01-25 15:28:21 +01:00
//------------------------------------------------------------------------------
// Setup and helpers
//------------------------------------------------------------------------------
// We create before each test:
// - fixture: a div, appended to the DOM, intended to be the target of dom
// manipulations. Note that it is removed after each test.
2019-06-20 09:42:33 +02:00
// - env: a WEnv, necessary to create new components
2019-01-23 14:28:41 +01:00
2019-01-25 15:28:21 +01:00
let fixture : HTMLElement ;
2019-03-14 15:53:31 +01:00
let env : Env ;
2019-01-23 11:12:54 +01:00
2019-01-25 15:28:21 +01:00
beforeEach ( ( ) = > {
2019-01-29 10:46:01 +01:00
fixture = makeTestFixture ( ) ;
2019-05-17 15:52:16 +02:00
env = makeTestEnv ( ) ;
2019-06-14 23:33:45 +02:00
env . qweb . addTemplate ( "Widget" , "<div></div>" ) ;
2019-02-02 11:41:19 +01:00
env . qweb . addTemplate (
2019-05-15 11:15:08 +02:00
"Counter" ,
2019-02-02 11:41:19 +01:00
` <div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div> `
) ;
2019-06-20 09:42:33 +02:00
env . qweb . addTemplate ( "WidgetA" , ` <div>Hello<t t-component="b"/></div> ` ) ;
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "WidgetB" , ` <div>world</div> ` ) ;
2019-10-31 13:31:10 +01:00
Component . env = env ;
2019-01-25 15:28:21 +01:00
} ) ;
afterEach ( ( ) = > {
fixture . remove ( ) ;
} ) ;
2019-01-16 13:19:58 +01:00
2019-09-24 14:06:53 +02:00
class Widget extends Component < any , any > { }
2019-02-04 21:09:17 +01:00
function children ( w : Widget ) : Widget [ ] {
2019-04-13 14:14:34 +02:00
const childrenMap = w . __owl__ . children ;
2019-01-27 13:50:53 +01:00
return Object . keys ( childrenMap ) . map ( id = > childrenMap [ id ] ) ;
}
2019-06-20 09:42:33 +02:00
// Test components
2019-02-04 21:09:17 +01:00
class Counter extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( {
2019-01-25 15:44:32 +01:00
counter : 0
2019-09-24 14:06:53 +02:00
} ) ;
2019-01-25 15:44:32 +01:00
inc() {
2019-04-17 10:07:31 +02:00
this . state . counter ++ ;
2019-01-25 15:44:32 +01:00
}
}
2019-09-10 22:37:08 +02:00
class WidgetB extends Widget { }
2019-02-04 21:09:17 +01:00
class WidgetA extends Widget {
2019-09-10 22:37:08 +02:00
static components = { b : WidgetB } ;
2019-01-27 13:50:53 +01:00
}
2019-01-25 15:28:21 +01:00
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
2019-01-16 13:19:58 +01:00
describe ( "basic widget properties" , ( ) = > {
2019-11-01 07:58:25 +01:00
test ( "props is set on root components" , async ( ) = > {
const widget = new Widget ( null , { } ) ;
expect ( widget . props ) . toEqual ( { } ) ;
2019-03-22 09:28:56 +01:00
} ) ;
2019-01-16 13:19:58 +01:00
test ( "has no el after creation" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const widget = new Widget ( ) ;
2019-01-16 13:19:58 +01:00
expect ( widget . el ) . toBe ( null ) ;
} ) ;
test ( "can be mounted" , async ( ) = > {
2019-10-25 15:45:56 +02:00
class SomeWidget extends Component < any , any > {
static template = xml ` <div>content</div> ` ;
}
2019-10-14 15:09:17 +02:00
const widget = new SomeWidget ( ) ;
2019-10-25 15:45:56 +02:00
widget . mount ( fixture ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>content</div>" ) ;
2019-01-16 13:19:58 +01:00
} ) ;
2019-11-02 19:45:28 +01:00
test ( "display a nice message if mounted on a non existing node" , async ( ) = > {
class SomeWidget extends Component < any , any > {
static template = xml ` <div>content</div> ` ;
}
const widget = new SomeWidget ( ) ;
let error ;
try {
await widget . mount ( null as any ) ;
} catch ( e ) {
error = e ;
}
expect ( error ) . toBeDefined ( ) ;
expect ( error . message ) . toBe (
"Component 'SomeWidget' cannot be mounted: the target is not a valid DOM node.\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)"
) ;
} ) ;
2019-11-14 15:33:45 +01:00
test ( "display an error message if result of rendering is empty" , async ( ) = > {
class SomeWidget extends Component < any , any > {
static template = xml ` <t/> ` ;
}
const widget = new SomeWidget ( ) ;
let error ;
try {
await widget . mount ( fixture ) ;
} catch ( e ) {
error = e ;
}
expect ( error ) . toBeDefined ( ) ;
expect ( error . message ) . toBe ( "Rendering 'SomeWidget' did not return anything" ) ;
} ) ;
2019-06-14 23:33:45 +02:00
test ( "crashes if it cannot find a template" , async ( ) = > {
expect . assertions ( 1 ) ;
2019-09-24 14:06:53 +02:00
class SomeWidget extends Component < any , any > { }
2019-06-14 23:33:45 +02:00
try {
2019-10-14 15:09:17 +02:00
new SomeWidget ( ) ;
2019-06-14 23:33:45 +02:00
} catch ( e ) {
2019-06-28 10:32:03 +02:00
expect ( e . message ) . toBe ( 'Could not find template for component "SomeWidget"' ) ;
2019-06-14 23:33:45 +02:00
}
} ) ;
2019-01-16 13:19:58 +01:00
test ( "can be clicked on and updated" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const counter = new Counter ( ) ;
2019-10-25 15:45:56 +02:00
counter . mount ( fixture ) ;
await nextTick ( ) ;
2019-05-06 13:45:50 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div>0<button>Inc</button></div>" ) ;
const button = ( < HTMLElement > counter . el ) . getElementsByTagName ( "button" ) [ 0 ] ;
2019-10-25 15:45:56 +02:00
button . click ( ) ;
2019-05-06 13:45:50 +02:00
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>1<button>Inc</button></div>" ) ;
} ) ;
test ( "cannot be clicked on and updated if not in DOM" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const counter = new Counter ( ) ;
2019-01-16 13:19:58 +01:00
const target = document . createElement ( "div" ) ;
await counter . mount ( target ) ;
expect ( target . innerHTML ) . toBe ( "<div>0<button>Inc</button></div>" ) ;
2019-01-25 15:28:21 +01:00
const button = ( < HTMLElement > counter . el ) . getElementsByTagName ( "button" ) [ 0 ] ;
2019-10-25 15:45:56 +02:00
button . click ( ) ;
2019-04-17 10:07:31 +02:00
await nextTick ( ) ;
2019-05-06 13:45:50 +02:00
expect ( target . innerHTML ) . toBe ( "<div>0<button>Inc</button></div>" ) ;
expect ( counter . state . counter ) . toBe ( 1 ) ;
2019-01-16 13:19:58 +01:00
} ) ;
2019-01-21 14:38:44 +01:00
test ( "widget style and classname" , async ( ) = > {
2019-09-12 09:45:32 +02:00
class StyledWidget extends Widget {
static template = xml `
<div style="font-weight:bold;" class="some-class">world</div>
` ;
}
2019-10-14 15:09:17 +02:00
const widget = new StyledWidget ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( ` <div style="font-weight:bold;" class="some-class">world</div> ` ) ;
2019-01-21 14:38:44 +01:00
} ) ;
2019-01-24 13:48:31 +01:00
2019-04-17 10:07:31 +02:00
test ( "changing state before first render does not trigger a render" , async ( ) = > {
2019-10-25 15:45:56 +02:00
const steps : string [ ] = [ ] ;
2019-02-04 21:09:17 +01:00
class TestW extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { drinks : 1 } ) ;
2019-01-24 13:48:31 +01:00
async willStart() {
2019-04-17 10:07:31 +02:00
this . state . drinks ++ ;
2019-01-24 13:48:31 +01:00
}
2019-09-19 11:42:00 +02:00
async __render ( f ) {
2019-10-25 15:45:56 +02:00
steps . push ( "__render" ) ;
2019-09-19 11:42:00 +02:00
return super . __render ( f ) ;
2019-01-24 13:48:31 +01:00
}
2019-10-25 15:45:56 +02:00
mounted() {
steps . push ( "mounted" ) ;
}
patched() {
steps . push ( "patched" ) ;
}
2019-01-24 13:48:31 +01:00
}
2019-10-14 15:09:17 +02:00
const widget = new TestW ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
2019-10-25 15:45:56 +02:00
expect ( steps ) . toEqual ( [ "__render" , "mounted" ] ) ;
} ) ;
test ( "changing state before first render does not trigger a render (with parent)" , async ( ) = > {
const steps : string [ ] = [ ] ;
class TestW extends Component < any , any > {
static template = xml ` <span>W</span> ` ;
state = useState ( { drinks : 1 } ) ;
async willStart() {
this . state . drinks ++ ;
}
async __render ( f ) {
steps . push ( "__render" ) ;
return super . __render ( f ) ;
}
mounted() {
steps . push ( "mounted" ) ;
}
patched() {
steps . push ( "patched" ) ;
}
}
class Parent extends Component < any , any > {
state = useState ( { flag : false } ) ;
static components = { TestW } ;
static template = xml ` <div><TestW t-if="state.flag"/></div> ` ;
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-25 15:45:56 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
expect ( steps ) . toEqual ( [ ] ) ;
parent . state . flag = true ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>W</span></div>" ) ;
expect ( steps ) . toEqual ( [ "__render" , "mounted" ] ) ;
2019-01-24 13:48:31 +01:00
} ) ;
2019-01-26 20:33:23 +01:00
2019-10-24 21:20:52 +02:00
test ( "render method wait until rendering is done" , async ( ) = > {
class TestW extends Component < any , any > {
static template = xml ` <div><t t-esc="state.drinks"/></div> ` ;
state = { drinks : 1 } ;
}
2019-10-14 15:09:17 +02:00
const widget = new TestW ( ) ;
2019-10-24 21:20:52 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>1</div>" ) ;
widget . state . drinks = 2 ;
const renderPromise = widget . render ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>1</div>" ) ;
await renderPromise ;
expect ( fixture . innerHTML ) . toBe ( "<div>2</div>" ) ;
} ) ;
2019-01-27 15:12:35 +01:00
test ( "keeps a reference to env" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const widget = new Widget ( ) ;
2019-01-26 20:33:23 +01:00
expect ( widget . env ) . toBe ( env ) ;
} ) ;
2019-01-27 15:12:35 +01:00
test ( "do not remove previously rendered dom if not necessary" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const widget = new Widget ( ) ;
2019-01-27 15:12:35 +01:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( ` <div></div> ` ) ;
widget . el ! . appendChild ( document . createElement ( "span" ) ) ;
expect ( fixture . innerHTML ) . toBe ( ` <div><span></span></div> ` ) ;
2019-10-25 15:45:56 +02:00
widget . render ( ) ; // FIXME?
2019-01-27 15:12:35 +01:00
expect ( fixture . innerHTML ) . toBe ( ` <div><span></span></div> ` ) ;
} ) ;
2019-09-02 15:27:37 +02:00
test ( "reconciliation alg is not confused in some specific situation" , async ( ) = > {
// in this test, we set t-key to 4 because it was in conflict with the
// template id corresponding to the first child.
2019-09-24 14:06:53 +02:00
class Child extends Component < any , any > {
2019-09-12 09:45:32 +02:00
static template = xml ` <span>child</span> ` ;
}
2019-09-02 15:27:37 +02:00
2019-09-24 14:06:53 +02:00
class Parent extends Component < any , any > {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
<Child />
<Child t-key="4"/>
</div>
` ;
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-09-02 15:27:37 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-09-02 15:27:37 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>child</span><span>child</span></div>" ) ;
} ) ;
2019-10-16 14:51:53 +02:00
test ( "reconciliation alg works for t-foreach in t-foreach" , async ( ) = > {
const warn = console . warn ;
console . warn = ( ) = > { } ;
class Child extends Component < any , any > {
static template = xml ` <div><t t-esc="props.blip"/></div> ` ;
}
class Parent extends Component < any , any > {
static template = xml `
<div>
<t t-foreach="state.s" t-as="section">
<t t-foreach="section.blips" t-as="blip">
<Child blip="blip"/>
</t>
</t>
</div> ` ;
static components = { Child } ;
state = { s : [ { blips : [ "a1" , "a2" ] } , { blips : [ "b1" ] } ] } ;
}
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-10-16 14:51:53 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>a1</div><div>a2</div><div>b1</div></div>" ) ;
expect ( env . qweb . templates [ Parent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
console . warn = warn ;
} ) ;
test ( "same t-keys in two different places" , async ( ) = > {
class Child extends Component < any , any > {
static template = xml ` <span><t t-esc="props.blip"/></span> ` ;
}
class Parent extends Component < any , any > {
static template = xml `
<div>
<div><Child t-key="1" blip="'1'"/></div>
<div><Child t-key="1" blip="'2'"/></div>
</div> ` ;
static components = { Child } ;
}
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-10-16 14:51:53 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div><span>1</span></div><div><span>2</span></div></div>" ) ;
} ) ;
2019-01-16 13:19:58 +01:00
} ) ;
2019-01-16 23:03:51 +01:00
describe ( "lifecycle hooks" , ( ) = > {
test ( "willStart hook is called" , async ( ) = > {
let willstart = false ;
2019-02-04 21:09:17 +01:00
class HookWidget extends Widget {
2019-01-16 23:03:51 +01:00
async willStart() {
willstart = true ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new HookWidget ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
2019-01-16 23:03:51 +01:00
expect ( willstart ) . toBe ( true ) ;
} ) ;
test ( "mounted hook is not called if not in DOM" , async ( ) = > {
let mounted = false ;
2019-02-04 21:09:17 +01:00
class HookWidget extends Widget {
2019-01-16 23:03:51 +01:00
async mounted() {
mounted = true ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new HookWidget ( ) ;
2019-01-16 23:03:51 +01:00
const target = document . createElement ( "div" ) ;
await widget . mount ( target ) ;
expect ( mounted ) . toBe ( false ) ;
} ) ;
2019-01-21 14:54:13 +01:00
test ( "mounted hook is called if mounted in DOM" , async ( ) = > {
let mounted = false ;
2019-02-04 21:09:17 +01:00
class HookWidget extends Widget {
2019-01-21 14:54:13 +01:00
async mounted() {
mounted = true ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new HookWidget ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
2019-01-21 14:54:13 +01:00
expect ( mounted ) . toBe ( true ) ;
} ) ;
2019-01-21 16:26:51 +01:00
2019-01-25 11:12:20 +01:00
test ( "willStart hook is called on subwidget" , async ( ) = > {
let ok = false ;
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
2019-01-25 11:12:20 +01:00
async willStart() {
ok = true ;
}
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div><t t-component="child"/></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { child : ChildWidget } ;
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
2019-01-25 11:12:20 +01:00
expect ( ok ) . toBe ( true ) ;
} ) ;
2019-06-20 09:42:33 +02:00
test ( "mounted hook is called on subcomponents, in proper order" , async ( ) = > {
2019-04-26 16:20:50 +02:00
const steps : any [ ] = [ ] ;
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
2019-01-25 11:01:43 +01:00
mounted() {
expect ( document . body . contains ( this . el ) ) . toBe ( true ) ;
2019-04-26 16:20:50 +02:00
steps . push ( "child:mounted" ) ;
2019-01-21 16:26:51 +01:00
}
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
2019-09-19 11:42:00 +02:00
static template = xml ` <div><ChildWidget /></div> ` ;
2019-09-12 09:45:32 +02:00
static components = { ChildWidget } ;
2019-09-10 22:37:08 +02:00
mounted() {
steps . push ( "parent:mounted" ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
2019-04-26 16:20:50 +02:00
expect ( steps ) . toEqual ( [ "child:mounted" , "parent:mounted" ] ) ;
2019-01-21 16:26:51 +01:00
} ) ;
2019-01-25 11:12:20 +01:00
2019-06-20 09:42:33 +02:00
test ( "mounted hook is called on subsubcomponents, in proper order" , async ( ) = > {
2019-04-26 17:32:02 +02:00
const steps : any [ ] = [ ] ;
2019-09-10 22:37:08 +02:00
class ChildChildWidget extends Widget {
2019-04-26 17:32:02 +02:00
mounted() {
2019-09-10 22:37:08 +02:00
steps . push ( "childchild:mounted" ) ;
2019-04-26 17:32:02 +02:00
}
willUnmount() {
2019-09-10 22:37:08 +02:00
steps . push ( "childchild:willUnmount" ) ;
2019-04-26 17:32:02 +02:00
}
}
2019-09-10 22:37:08 +02:00
2019-04-26 17:32:02 +02:00
class ChildWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div><t t-component="childchild"/></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { childchild : ChildChildWidget } ;
2019-04-26 17:32:02 +02:00
mounted() {
steps . push ( "child:mounted" ) ;
}
willUnmount() {
steps . push ( "child:willUnmount" ) ;
}
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div><t t-if="state.flag"><t t-component="child"/></t></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { child : ChildWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : false } ) ;
2019-04-26 17:32:02 +02:00
mounted() {
2019-09-10 22:37:08 +02:00
steps . push ( "parent:mounted" ) ;
2019-04-26 17:32:02 +02:00
}
willUnmount() {
2019-09-10 22:37:08 +02:00
steps . push ( "parent:willUnmount" ) ;
2019-04-26 17:32:02 +02:00
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-04-26 17:32:02 +02:00
await widget . mount ( fixture ) ;
expect ( steps ) . toEqual ( [ "parent:mounted" ] ) ;
widget . state . flag = true ;
await nextTick ( ) ;
widget . destroy ( ) ;
expect ( steps ) . toEqual ( [
"parent:mounted" ,
"childchild:mounted" ,
"child:mounted" ,
"parent:willUnmount" ,
"child:willUnmount" ,
"childchild:willUnmount"
] ) ;
} ) ;
2019-06-20 09:42:33 +02:00
test ( "willPatch, patched hook are called on subsubcomponents, in proper order" , async ( ) = > {
2019-04-27 19:20:29 +02:00
const steps : any [ ] = [ ] ;
2019-09-10 22:37:08 +02:00
env . qweb . addTemplate ( "ChildWidget" , ` <div><t t-component="childchild" n="props.n"/></div> ` ) ;
env . qweb . addTemplate ( "ChildChildWidget" , ` <div><t t-esc="props.n"/></div> ` ) ;
class ChildChildWidget extends Widget {
2019-04-27 19:20:29 +02:00
willPatch() {
2019-09-10 22:37:08 +02:00
steps . push ( "childchild:willPatch" ) ;
2019-04-27 19:20:29 +02:00
}
patched() {
2019-09-10 22:37:08 +02:00
steps . push ( "childchild:patched" ) ;
2019-04-27 19:20:29 +02:00
}
}
class ChildWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { childchild : ChildChildWidget } ;
2019-04-27 19:20:29 +02:00
willPatch() {
steps . push ( "child:willPatch" ) ;
}
patched() {
steps . push ( "child:patched" ) ;
}
}
2019-05-15 11:15:08 +02:00
2019-09-10 22:37:08 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-component="child" n="state.n"/></div> ` ) ;
class ParentWidget extends Widget {
static components = { child : ChildWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { n : 1 } ) ;
2019-04-27 19:20:29 +02:00
willPatch() {
2019-09-10 22:37:08 +02:00
steps . push ( "parent:willPatch" ) ;
2019-04-27 19:20:29 +02:00
}
patched() {
2019-09-10 22:37:08 +02:00
steps . push ( "parent:patched" ) ;
2019-04-27 19:20:29 +02:00
}
}
2019-09-10 22:37:08 +02:00
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-04-27 19:20:29 +02:00
await widget . mount ( fixture ) ;
expect ( steps ) . toEqual ( [ ] ) ;
widget . state . n = 2 ;
await nextTick ( ) ;
widget . destroy ( ) ;
expect ( steps ) . toEqual ( [
"parent:willPatch" ,
"child:willPatch" ,
"childchild:willPatch" ,
"childchild:patched" ,
"child:patched" ,
"parent:patched"
] ) ;
} ) ;
2019-01-25 11:12:20 +01:00
test ( "willStart, mounted on subwidget rendered after main is mounted in some other position" , async ( ) = > {
2019-04-26 16:20:50 +02:00
const steps : string [ ] = [ ] ;
2019-02-03 09:42:35 +01:00
// the t-else part in the template is important. This is
// necessary to have a situation that could confuse the vdom
// patching algorithm
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"ParentWidget" ,
`
2019-01-25 15:28:21 +01:00
<div>
<t t-if="state.ok">
2019-06-20 09:42:33 +02:00
<t t-component="child"/>
2019-01-25 15:28:21 +01:00
</t>
<t t-else="1">
<div/>
</t>
2019-05-15 11:15:08 +02:00
</div> `
) ;
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
2019-01-25 11:12:20 +01:00
async willStart() {
2019-04-26 16:20:50 +02:00
steps . push ( "child:willStart" ) ;
2019-01-25 11:12:20 +01:00
}
mounted() {
2019-04-26 16:20:50 +02:00
steps . push ( "child:mounted" ) ;
2019-01-25 11:12:20 +01:00
}
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { ok : false } ) ;
2019-09-10 22:37:08 +02:00
static components = { child : ChildWidget } ;
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
2019-04-26 16:20:50 +02:00
expect ( steps ) . toEqual ( [ ] ) ;
2019-04-16 22:59:54 +02:00
widget . state . ok = true ;
await nextTick ( ) ;
2019-04-26 16:20:50 +02:00
expect ( steps ) . toEqual ( [ "child:willStart" , "child:mounted" ] ) ;
2019-01-25 11:12:20 +01:00
} ) ;
2019-01-24 22:17:06 +01:00
2019-10-25 15:45:56 +02:00
test ( "mounted hook is correctly called on subcomponents created in mounted hook" , async ( ) = > {
2019-01-24 22:17:06 +01:00
// the issue here is that the parent widget creates in the
// mounted hook a new widget, which means that it modifies
// in place its list of children. But this list of children is currently
// being visited, so the mount action of the parent could cause a mount
// action of the new child widget, even though it is not ready yet.
expect . assertions ( 1 ) ;
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-01-24 22:17:06 +01:00
mounted() {
const child = new ChildWidget ( this ) ;
child . mount ( this . el ! ) ;
}
}
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
2019-01-24 22:17:06 +01:00
mounted() {
expect ( this . el ) . toBeTruthy ( ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-10-25 15:45:56 +02:00
await widget . mount ( fixture ) ; // wait for ParentWidget
await nextTick ( ) ; // wait for ChildWidget
2019-01-24 22:17:06 +01:00
} ) ;
2019-01-27 15:40:10 +01:00
2019-06-20 09:42:33 +02:00
test ( "components are unmounted and destroyed if no longer in DOM" , async ( ) = > {
2019-01-27 15:40:10 +01:00
let steps : string [ ] = [ ] ;
2019-09-12 09:45:32 +02:00
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
2019-01-27 15:40:10 +01:00
constructor ( parent ) {
super ( parent ) ;
steps . push ( "init" ) ;
}
async willStart() {
steps . push ( "willstart" ) ;
}
mounted() {
steps . push ( "mounted" ) ;
}
willUnmount() {
steps . push ( "willunmount" ) ;
}
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
<t t-if="state.ok"><ChildWidget /></t>
</div>
` ;
static components = { ChildWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { ok : true } ) ;
2019-09-10 22:37:08 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-01-27 15:40:10 +01:00
await widget . mount ( fixture ) ;
expect ( steps ) . toEqual ( [ "init" , "willstart" , "mounted" ] ) ;
2019-04-17 10:07:31 +02:00
widget . state . ok = false ;
await nextTick ( ) ;
2019-04-10 09:35:20 +02:00
expect ( steps ) . toEqual ( [ "init" , "willstart" , "mounted" , "willunmount" ] ) ;
2019-01-27 15:40:10 +01:00
} ) ;
2019-01-27 21:03:13 +01:00
2019-06-20 09:42:33 +02:00
test ( "components are unmounted and destroyed if no longer in DOM, even after updateprops" , async ( ) = > {
2019-04-09 15:24:15 +02:00
let childUnmounted = false ;
2019-09-12 09:45:32 +02:00
2019-04-09 15:24:15 +02:00
class ChildWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <span><t t-esc="props.n"/></span> ` ;
2019-04-09 15:24:15 +02:00
willUnmount() {
childUnmounted = true ;
}
}
2019-05-15 11:15:08 +02:00
class ParentWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
<div t-if="state.flag">
<ChildWidget n="state.n"/>
</div>
</div>
` ;
2019-09-10 22:37:08 +02:00
static components = { ChildWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { n : 0 , flag : true } ) ;
2019-04-09 15:24:15 +02:00
increment() {
2019-04-17 10:07:31 +02:00
this . state . n += 1 ;
2019-04-09 15:24:15 +02:00
}
toggleSubWidget() {
2019-04-17 10:07:31 +02:00
this . state . flag = ! this . state . flag ;
2019-04-09 15:24:15 +02:00
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-04-09 15:24:15 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div><span>0</span></div></div>" ) ;
widget . increment ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div><span>1</span></div></div>" ) ;
widget . toggleSubWidget ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
expect ( childUnmounted ) . toBe ( true ) ;
} ) ;
2019-01-27 21:03:13 +01:00
test ( "hooks are called in proper order in widget creation/destruction" , async ( ) = > {
let steps : string [ ] = [ ] ;
2019-09-12 09:45:32 +02:00
2019-09-10 22:37:08 +02:00
class ChildWidget extends Widget {
2019-01-27 21:03:13 +01:00
constructor ( parent ) {
super ( parent ) ;
2019-09-10 22:37:08 +02:00
steps . push ( "c init" ) ;
2019-01-27 21:03:13 +01:00
}
async willStart() {
2019-09-10 22:37:08 +02:00
steps . push ( "c willstart" ) ;
2019-01-27 21:03:13 +01:00
}
mounted() {
2019-09-10 22:37:08 +02:00
steps . push ( "c mounted" ) ;
2019-01-27 21:03:13 +01:00
}
willUnmount() {
2019-09-10 22:37:08 +02:00
steps . push ( "c willunmount" ) ;
2019-01-27 21:03:13 +01:00
}
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div><t t-component="child"/></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { child : ChildWidget } ;
2019-10-14 15:09:17 +02:00
constructor ( parent ? ) {
2019-01-27 21:03:13 +01:00
super ( parent ) ;
2019-09-10 22:37:08 +02:00
steps . push ( "p init" ) ;
2019-01-27 21:03:13 +01:00
}
async willStart() {
2019-09-10 22:37:08 +02:00
steps . push ( "p willstart" ) ;
2019-01-27 21:03:13 +01:00
}
mounted() {
2019-09-10 22:37:08 +02:00
steps . push ( "p mounted" ) ;
2019-01-27 21:03:13 +01:00
}
willUnmount() {
2019-09-10 22:37:08 +02:00
steps . push ( "p willunmount" ) ;
2019-01-27 21:03:13 +01:00
}
}
2019-09-10 22:37:08 +02:00
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-01-27 21:03:13 +01:00
await widget . mount ( fixture ) ;
widget . destroy ( ) ;
expect ( steps ) . toEqual ( [
"p init" ,
"p willstart" ,
"c init" ,
"c willstart" ,
"c mounted" ,
2019-04-26 16:20:50 +02:00
"p mounted" ,
"p willunmount" ,
"c willunmount"
2019-01-27 21:03:13 +01:00
] ) ;
} ) ;
2019-01-28 16:16:22 +01:00
2019-04-09 13:51:04 +02:00
test ( "willUpdateProps hook is called" , async ( ) = > {
let def = makeDeferred ( ) ;
2019-06-22 14:44:09 +02:00
env . qweb . addTemplate ( "Parent" , '<span><Child n="state.n"/></span>' ) ;
2019-04-09 13:51:04 +02:00
class HookWidget extends Widget {
willUpdateProps ( nextProps ) {
expect ( nextProps . n ) . toBe ( 2 ) ;
return def ;
}
}
2019-09-10 22:37:08 +02:00
class Parent extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { n : 1 } ) ;
2019-09-10 22:37:08 +02:00
static components = { Child : HookWidget } ;
}
env . qweb . addTemplate ( "HookWidget" , '<span><t t-esc="props.n"/></span>' ) ;
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-04-09 13:51:04 +02:00
await widget . mount ( fixture ) ;
2019-04-27 19:20:29 +02:00
expect ( fixture . innerHTML ) . toBe ( "<span><span>1</span></span>" ) ;
widget . state . n = 2 ;
2019-04-09 13:51:04 +02:00
await nextTick ( ) ;
2019-04-27 19:20:29 +02:00
expect ( fixture . innerHTML ) . toBe ( "<span><span>1</span></span>" ) ;
2019-04-09 13:51:04 +02:00
def . resolve ( ) ;
await nextTick ( ) ;
2019-04-27 19:20:29 +02:00
expect ( fixture . innerHTML ) . toBe ( "<span><span>2</span></span>" ) ;
2019-04-09 13:51:04 +02:00
} ) ;
2019-04-17 10:07:31 +02:00
test ( "patched hook is called after updating State" , async ( ) = > {
2019-04-01 15:42:24 +02:00
let n = 0 ;
class TestWidget extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { a : 1 } ) ;
2019-04-01 15:42:24 +02:00
2019-04-09 10:37:53 +02:00
patched() {
2019-04-01 15:42:24 +02:00
n ++ ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new TestWidget ( ) ;
2019-04-01 15:42:24 +02:00
await widget . mount ( fixture ) ;
expect ( n ) . toBe ( 0 ) ;
2019-04-17 10:07:31 +02:00
widget . state . a = 1 ; // empty update, should do nothing
await nextTick ( ) ;
2019-04-01 15:42:24 +02:00
expect ( n ) . toBe ( 0 ) ;
2019-04-17 10:07:31 +02:00
widget . state . a = 3 ;
await nextTick ( ) ;
2019-04-01 15:42:24 +02:00
expect ( n ) . toBe ( 1 ) ;
} ) ;
2019-04-09 10:37:53 +02:00
test ( "patched hook is called after updateProps" , async ( ) = > {
2019-04-01 15:42:24 +02:00
let n = 0 ;
class TestWidget extends Widget {
2019-04-09 10:37:53 +02:00
patched() {
2019-04-01 15:42:24 +02:00
n ++ ;
}
}
2019-09-10 22:37:08 +02:00
class Parent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div><Child a="state.a"/></div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { a : 1 } ) ;
2019-09-10 22:37:08 +02:00
static components = { Child : TestWidget } ;
}
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-04-01 15:42:24 +02:00
await widget . mount ( fixture ) ;
expect ( n ) . toBe ( 0 ) ;
2019-04-27 19:20:29 +02:00
widget . state . a = 2 ;
await nextTick ( ) ;
2019-04-01 15:42:24 +02:00
expect ( n ) . toBe ( 1 ) ;
} ) ;
2019-01-28 16:16:22 +01:00
test ( "shouldUpdate hook prevent rerendering" , async ( ) = > {
let shouldUpdate = false ;
2019-02-04 21:09:17 +01:00
class TestWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div><t t-esc="props.val"/></div> ` ;
2019-01-28 16:16:22 +01:00
shouldUpdate() {
return shouldUpdate ;
}
}
2019-09-10 22:37:08 +02:00
class Parent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div><Child val="state.val"/></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { Child : TestWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 42 } ) ;
2019-09-10 22:37:08 +02:00
}
2019-09-12 09:45:32 +02:00
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-01-28 16:16:22 +01:00
await widget . mount ( fixture ) ;
2019-04-27 19:20:29 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div>42</div></div>" ) ;
widget . state . val = 123 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>42</div></div>" ) ;
2019-01-28 16:16:22 +01:00
shouldUpdate = true ;
2019-04-27 19:20:29 +02:00
widget . state . val = 666 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>666</div></div>" ) ;
2019-01-28 16:16:22 +01:00
} ) ;
2019-03-29 12:50:45 +01:00
test ( "sub widget (inside sub node): hooks are correctly called" , async ( ) = > {
let created = false ;
let mounted = false ;
class ChildWidget extends Widget {
constructor ( parent , props ) {
super ( parent , props ) ;
created = true ;
}
mounted() {
mounted = true ;
}
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
<t t-if="state.flag">
<div><t t-component="child"/></div>
</t>
</div>
` ;
2019-09-10 22:37:08 +02:00
static components = { child : ChildWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : false } ) ;
2019-09-10 22:37:08 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-03-29 12:50:45 +01:00
await widget . mount ( fixture ) ;
expect ( created ) . toBe ( false ) ;
expect ( mounted ) . toBe ( false ) ;
2019-04-17 10:07:31 +02:00
widget . state . flag = true ;
await nextTick ( ) ;
2019-03-29 12:50:45 +01:00
expect ( mounted ) . toBe ( true ) ;
expect ( created ) . toBe ( true ) ;
} ) ;
2019-04-05 16:30:35 +02:00
2019-04-09 10:37:53 +02:00
test ( "willPatch/patched hook" , async ( ) = > {
2019-04-05 16:30:35 +02:00
const steps : string [ ] = [ ] ;
2019-09-12 09:45:32 +02:00
2019-09-10 22:37:08 +02:00
class ChildWidget extends Widget {
willPatch() {
steps . push ( "child:willPatch" ) ;
}
patched() {
steps . push ( "child:patched" ) ;
}
}
2019-05-15 11:15:08 +02:00
class ParentWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
<t t-component="child" v="state.n"/>
</div>
` ;
2019-09-10 22:37:08 +02:00
static components = { child : ChildWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { n : 1 } ) ;
2019-04-05 16:30:35 +02:00
willPatch() {
steps . push ( "parent:willPatch" ) ;
}
2019-09-29 16:47:06 +02:00
patched() {
2019-04-09 10:37:53 +02:00
steps . push ( "parent:patched" ) ;
2019-04-05 16:30:35 +02:00
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-04-05 16:30:35 +02:00
await widget . mount ( fixture ) ;
expect ( steps ) . toEqual ( [ ] ) ;
2019-04-16 22:59:54 +02:00
widget . state . n = 2 ;
await nextTick ( ) ;
2019-04-05 16:30:35 +02:00
// Not sure about this order. If you disagree, feel free to open an issue...
expect ( steps ) . toEqual ( [
2019-04-27 19:20:29 +02:00
"parent:willPatch" ,
2019-04-05 16:30:35 +02:00
"child:willPatch" ,
2019-04-09 10:37:53 +02:00
"child:patched" ,
"parent:patched"
2019-04-05 16:30:35 +02:00
] ) ;
} ) ;
2019-01-16 23:03:51 +01:00
} ) ;
describe ( "destroy method" , ( ) = > {
test ( "destroy remove the widget from the DOM" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const widget = new Widget ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
2019-01-16 23:03:51 +01:00
expect ( document . contains ( widget . el ) ) . toBe ( true ) ;
widget . destroy ( ) ;
expect ( document . contains ( widget . el ) ) . toBe ( false ) ;
2019-04-13 14:14:34 +02:00
expect ( widget . __owl__ . isMounted ) . toBe ( false ) ;
expect ( widget . __owl__ . isDestroyed ) . toBe ( true ) ;
2019-01-16 23:03:51 +01:00
} ) ;
2019-01-26 18:20:49 +01:00
2019-01-27 21:03:13 +01:00
test ( "destroying a parent also destroys its children" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const parent = new WidgetA ( ) ;
2019-01-27 21:03:13 +01:00
await parent . mount ( fixture ) ;
const child = children ( parent ) [ 0 ] ;
2019-04-13 14:14:34 +02:00
expect ( child . __owl__ . isDestroyed ) . toBe ( false ) ;
2019-01-27 21:03:13 +01:00
parent . destroy ( ) ;
2019-04-13 14:14:34 +02:00
expect ( child . __owl__ . isDestroyed ) . toBe ( true ) ;
2019-01-27 21:03:13 +01:00
} ) ;
2019-01-27 13:50:53 +01:00
test ( "destroy remove the parent/children link" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const parent = new WidgetA ( ) ;
2019-01-27 13:50:53 +01:00
await parent . mount ( fixture ) ;
const child = children ( parent ) [ 0 ] ;
2019-04-13 14:14:34 +02:00
expect ( child . __owl__ . parent ) . toBe ( parent ) ;
2019-01-27 13:50:53 +01:00
expect ( children ( parent ) . length ) . toBe ( 1 ) ;
child . destroy ( ) ;
2019-04-13 14:14:34 +02:00
expect ( child . __owl__ . parent ) . toBe ( null ) ;
2019-01-27 13:50:53 +01:00
expect ( children ( parent ) . length ) . toBe ( 0 ) ;
} ) ;
2019-01-27 21:50:22 +01:00
test ( "destroying a widget before willStart is done" , async ( ) = > {
2019-02-07 12:10:44 +01:00
let def = makeDeferred ( ) ;
2019-01-27 21:50:22 +01:00
let isRendered = false ;
2019-02-04 21:09:17 +01:00
class DelayedWidget extends Widget {
2019-01-27 21:50:22 +01:00
willStart() {
2019-02-07 12:10:44 +01:00
return def ;
2019-01-27 21:50:22 +01:00
}
}
expect ( fixture . innerHTML ) . toBe ( "" ) ;
2019-10-14 15:09:17 +02:00
const widget = new DelayedWidget ( ) ;
2019-01-27 21:50:22 +01:00
widget . mount ( fixture ) ;
2019-04-13 14:14:34 +02:00
expect ( widget . __owl__ . isMounted ) . toBe ( false ) ;
expect ( widget . __owl__ . isDestroyed ) . toBe ( false ) ;
2019-01-27 21:50:22 +01:00
widget . destroy ( ) ;
2019-04-13 14:14:34 +02:00
expect ( widget . __owl__ . isMounted ) . toBe ( false ) ;
expect ( widget . __owl__ . isDestroyed ) . toBe ( true ) ;
2019-02-07 12:10:44 +01:00
def . resolve ( ) ;
await nextTick ( ) ;
2019-01-27 21:50:22 +01:00
2019-04-13 14:14:34 +02:00
expect ( widget . __owl__ . isMounted ) . toBe ( false ) ;
expect ( widget . __owl__ . isDestroyed ) . toBe ( true ) ;
2019-04-26 16:20:50 +02:00
expect ( widget . __owl__ . vnode ) . toBe ( undefined ) ;
2019-01-27 21:50:22 +01:00
expect ( fixture . innerHTML ) . toBe ( "" ) ;
expect ( isRendered ) . toBe ( false ) ;
} ) ;
2019-09-01 21:24:54 +02:00
test ( "destroying a widget before being mounted" , async ( ) = > {
2019-09-24 14:06:53 +02:00
class GrandChild extends Component < any , any > {
2019-09-12 09:45:32 +02:00
static template = xml `
<span>
<t t-esc="props.val.val"/>
</span>
` ;
}
2019-09-24 14:06:53 +02:00
class Child extends Component < any , any > {
2019-09-12 09:45:32 +02:00
static template = xml `
<span>
<GrandChild t-if="state.flag" val="something"/>
<button t-on-click="doSomething">click</button>
</span>
` ;
2019-09-10 22:37:08 +02:00
static components = { GrandChild } ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 33 , flag : false } ) ;
2019-09-01 21:24:54 +02:00
doSomething() {
this . state . val = 12 ;
this . state . flag = true ;
this . trigger ( "some-event" ) ;
}
get something() {
return { val : this.state.val } ;
}
}
2019-09-24 14:06:53 +02:00
class Parent extends Component < any , any > {
2019-09-12 09:45:32 +02:00
static template = xml `
<div t-on-some-event="doStuff">
<Child />
</div>
` ;
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { p : 1 } ) ;
2019-09-10 22:37:08 +02:00
doStuff() {
this . state . p = 2 ;
}
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-09-01 21:24:54 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span><button>click</button></span></div>" ) ;
fixture . querySelector ( "button" ) ! . click ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span><span>12</span><button>click</button></span></div>" ) ;
} ) ;
2019-01-16 23:03:51 +01:00
} ) ;
2019-01-20 14:46:36 +01:00
describe ( "composition" , ( ) = > {
2019-01-21 15:46:29 +01:00
test ( "a widget with a sub widget" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const widget = new WidgetA ( ) ;
2019-01-25 15:28:21 +01:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>Hello<div>world</div></div>" ) ;
2019-04-13 14:14:34 +02:00
expect ( children ( widget ) [ 0 ] . __owl__ . parent ) . toBe ( widget ) ;
2019-01-20 14:46:36 +01:00
} ) ;
2019-01-21 15:46:29 +01:00
2019-06-20 09:42:33 +02:00
test ( "can use components from the global registry" , async ( ) = > {
2019-09-12 10:45:40 +02:00
QWeb . registerComponent ( "WidgetB" , WidgetB ) ;
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-component="WidgetB"/></div> ` ) ;
2019-05-23 16:14:52 +02:00
class ParentWidget extends Widget { }
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-23 16:14:52 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>world</div></div>" ) ;
2019-06-20 09:42:33 +02:00
delete QWeb . components [ "WidgetB" ] ;
2019-05-23 16:14:52 +02:00
} ) ;
2019-09-21 09:28:56 +02:00
test ( "can use dynamic components (the class) if given" , async ( ) = > {
2019-09-24 14:06:53 +02:00
class A extends Component < any , any > {
2019-09-21 09:28:56 +02:00
static template = xml ` <span>child a</span> ` ;
}
2019-09-24 14:06:53 +02:00
class B extends Component < any , any > {
2019-09-21 09:28:56 +02:00
static template = xml ` <span>child b</span> ` ;
}
2019-09-24 14:06:53 +02:00
class App extends Component < any , any > {
2019-09-21 09:28:56 +02:00
static template = xml ` <t t-component="myComponent" t-key="state.child"/> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( {
2019-09-21 09:28:56 +02:00
child : "a"
2019-09-24 14:06:53 +02:00
} ) ;
2019-09-21 09:28:56 +02:00
get myComponent() {
return this . state . child === "a" ? A : B ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new App ( ) ;
2019-09-21 09:28:56 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>child a</span>" ) ;
widget . state . child = "b" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>child b</span>" ) ;
} ) ;
2019-10-25 15:45:56 +02:00
test ( "can use dynamic components (the class) if given (with different root tagname)" , async ( ) = > {
class A extends Component < any , any > {
static template = xml ` <span>child a</span> ` ;
}
class B extends Component < any , any > {
static template = xml ` <div>child b</div> ` ;
}
class App extends Component < any , any > {
static template = xml ` <t t-component="myComponent" t-key="state.child"/> ` ;
state = useState ( {
child : "a"
} ) ;
get myComponent() {
return this . state . child === "a" ? A : B ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new App ( ) ;
2019-10-25 15:45:56 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>child a</span>" ) ;
widget . state . child = "b" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>child b</div>" ) ;
} ) ;
2019-05-23 16:14:52 +02:00
test ( "don't fallback to global registry if widget defined locally" , async ( ) = > {
2019-09-12 10:45:40 +02:00
QWeb . registerComponent ( "WidgetB" , WidgetB ) ; // should not use this widget
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-component="WidgetB"/></div> ` ) ;
2019-05-23 16:14:52 +02:00
env . qweb . addTemplate ( "AnotherWidgetB" , ` <span>Belgium</span> ` ) ;
class AnotherWidgetB extends Widget { }
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { WidgetB : AnotherWidgetB } ;
2019-05-23 16:14:52 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-23 16:14:52 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>Belgium</span></div>" ) ;
2019-06-20 09:42:33 +02:00
delete QWeb . components [ "WidgetB" ] ;
2019-05-23 16:14:52 +02:00
} ) ;
2019-06-20 09:42:33 +02:00
test ( "can define components in template without t-component" , async ( ) = > {
2019-06-18 09:24:52 +02:00
env . qweb . addTemplates ( `
<templates>
<div t-name="P"><C a="1"/></div>
<span t-name="C"><t t-esc="props.a"/></span>
</templates> ` ) ;
class C extends Widget { }
class P extends Widget {
2019-09-10 22:37:08 +02:00
static components = { C } ;
2019-06-18 09:24:52 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new P ( ) ;
2019-06-18 09:24:52 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>1</span></div>" ) ;
} ) ;
2019-07-09 14:33:56 +02:00
test ( "display a nice error if it cannot find component" , async ( ) = > {
const consoleError = console . error ;
console . error = jest . fn ( ) ;
2019-06-22 14:44:09 +02:00
env . qweb . addTemplate ( "Parent" , ` <div><SomeMispelledWidget /></div> ` ) ;
2019-05-15 17:17:39 +02:00
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { SomeWidget : Widget } ;
2019-05-15 17:17:39 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-26 16:44:12 +02:00
let error ;
try {
await parent . mount ( fixture ) ;
} catch ( e ) {
error = e ;
}
expect ( error ) . toBeDefined ( ) ;
expect ( error . message ) . toBe ( 'Cannot find the definition of component "SomeMispelledWidget"' ) ;
expect ( console . error ) . toBeCalledTimes ( 0 ) ;
2019-07-09 14:33:56 +02:00
console . error = consoleError ;
2019-05-15 17:17:39 +02:00
} ) ;
2019-01-25 15:44:32 +01:00
test ( "modifying a sub widget" , async ( ) = > {
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-component="Counter"/></div> ` ) ;
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Counter } ;
2019-01-25 15:44:32 +01:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-01-25 15:44:32 +01:00
await widget . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div>0<button>Inc</button></div></div>" ) ;
2019-01-25 15:44:32 +01:00
const button = fixture . getElementsByTagName ( "button" ) [ 0 ] ;
await button . click ( ) ;
2019-04-17 10:07:31 +02:00
await nextTick ( ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div>1<button>Inc</button></div></div>" ) ;
2019-01-25 15:44:32 +01:00
} ) ;
2019-01-26 08:40:09 +01:00
2019-05-04 22:46:58 +02:00
test ( "refs in a loop" , async ( ) = > {
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"ParentWidget" ,
` <div>
2019-05-04 22:46:58 +02:00
<t t-foreach="state.items" t-as="item">
2019-06-20 09:42:33 +02:00
<t t-component="Child" t-ref="{{item}}" t-key="item"/>
2019-05-04 22:46:58 +02:00
</t>
2019-05-15 11:15:08 +02:00
</div> `
) ;
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Child : Widget } ;
2019-09-26 21:56:01 +02:00
elem1 = useRef ( "1" ) ;
elem2 = useRef ( "2" ) ;
elem3 = useRef ( "3" ) ;
elem4 = useRef ( "4" ) ;
state = useState ( { items : [ 1 , 2 , 3 ] } ) ;
2019-05-04 22:46:58 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new ParentWidget ( ) ;
2019-05-04 22:46:58 +02:00
await parent . mount ( fixture ) ;
2019-09-26 21:56:01 +02:00
expect ( parent . elem1 . comp ) . toBeDefined ( ) ;
expect ( parent . elem2 . comp ) . toBeDefined ( ) ;
expect ( parent . elem3 . comp ) . toBeDefined ( ) ;
expect ( parent . elem4 . comp ) . toBeNull ( ) ;
2019-05-04 22:46:58 +02:00
} ) ;
2019-01-26 08:40:09 +01:00
test ( "parent's elm for a children === children's elm, even after rerender" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const widget = new WidgetA ( ) ;
2019-01-26 08:40:09 +01:00
await widget . mount ( fixture ) ;
2019-04-13 14:14:34 +02:00
expect ( ( < any > widget . __owl__ . vnode ! . children ! [ 1 ] ) . elm ) . toBe (
( < any > children ( widget ) [ 0 ] . __owl__ . vnode ) . elm
2019-01-26 08:40:09 +01:00
) ;
2019-01-27 13:50:53 +01:00
await children ( widget ) [ 0 ] . render ( ) ;
2019-04-13 14:14:34 +02:00
expect ( ( < any > widget . __owl__ . vnode ! . children ! [ 1 ] ) . elm ) . toBe (
( < any > children ( widget ) [ 0 ] . __owl__ . vnode ) . elm
2019-01-26 08:40:09 +01:00
) ;
} ) ;
2019-01-26 20:33:23 +01:00
2019-06-20 09:42:33 +02:00
test ( "parent env is propagated to child components" , async ( ) = > {
2019-10-14 15:09:17 +02:00
const widget = new WidgetA ( ) ;
2019-01-26 20:33:23 +01:00
await widget . mount ( fixture ) ;
2019-01-27 13:50:53 +01:00
expect ( children ( widget ) [ 0 ] . env ) . toBe ( env ) ;
2019-01-26 20:33:23 +01:00
} ) ;
2019-01-27 22:16:00 +01:00
test ( "rerendering a widget with a sub widget" , async ( ) = > {
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-component="Counter"/></div> ` ) ;
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Counter } ;
2019-01-27 22:16:00 +01:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-01-27 22:16:00 +01:00
await widget . mount ( fixture ) ;
const button = fixture . getElementsByTagName ( "button" ) [ 0 ] ;
await button . click ( ) ;
2019-04-17 10:07:31 +02:00
await nextTick ( ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div>1<button>Inc</button></div></div>" ) ;
2019-01-27 22:16:00 +01:00
await widget . render ( ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div>1<button>Inc</button></div></div>" ) ;
2019-01-27 22:16:00 +01:00
} ) ;
2019-01-28 15:51:20 +01:00
2019-06-20 09:42:33 +02:00
test ( "sub components are destroyed if no longer in dom, then recreated" , async ( ) = > {
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-if="state.ok"><Counter /></t></div> ` ) ;
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { ok : true } ) ;
2019-09-10 22:37:08 +02:00
static components = { Counter } ;
2019-01-28 15:51:20 +01:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-01-28 15:51:20 +01:00
await widget . mount ( fixture ) ;
const button = fixture . getElementsByTagName ( "button" ) [ 0 ] ;
await button . click ( ) ;
2019-04-17 10:07:31 +02:00
await nextTick ( ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div>1<button>Inc</button></div></div>" ) ;
2019-04-17 10:07:31 +02:00
widget . state . ok = false ;
await nextTick ( ) ;
2019-01-28 15:51:20 +01:00
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
2019-04-17 10:07:31 +02:00
widget . state . ok = true ;
await nextTick ( ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div>0<button>Inc</button></div></div>" ) ;
2019-01-28 15:51:20 +01:00
} ) ;
2019-01-29 15:19:02 +01:00
2019-06-20 09:42:33 +02:00
test ( "sub components rendered in a loop" , async ( ) = > {
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "ChildWidget" , ` <span><t t-esc="props.n"/></span> ` ) ;
class ChildWidget extends Widget { }
2019-02-03 09:42:35 +01:00
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"Parent" ,
`
2019-01-29 15:19:02 +01:00
<div>
<t t-foreach="state.numbers" t-as="number">
2019-06-20 09:42:33 +02:00
<t t-component="ChildWidget" t-key="number" n="number"/>
2019-01-29 15:19:02 +01:00
</t>
2019-05-15 11:15:08 +02:00
</div> `
) ;
class Parent extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( {
2019-01-29 15:19:02 +01:00
numbers : [ 1 , 2 , 3 ]
2019-09-24 14:06:53 +02:00
} ) ;
2019-09-10 22:37:08 +02:00
static components = { ChildWidget } ;
2019-01-29 15:19:02 +01:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-01-29 15:19:02 +01:00
await parent . mount ( fixture ) ;
expect ( normalize ( fixture . innerHTML ) ) . toBe (
normalize ( `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
</div>
` )
) ;
} ) ;
2019-01-30 11:29:54 +01:00
2019-06-20 09:42:33 +02:00
test ( "sub components with some state rendered in a loop" , async ( ) = > {
2019-01-30 11:29:54 +01:00
let n = 1 ;
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "ChildWidget" , ` <span><t t-esc="state.n"/></span> ` ) ;
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
2019-09-24 14:06:53 +02:00
state : any ;
2019-01-30 11:29:54 +01:00
constructor ( parent ) {
super ( parent ) ;
2019-09-24 14:06:53 +02:00
this . state = useState ( { n } ) ;
2019-01-30 11:29:54 +01:00
n ++ ;
}
}
2019-02-02 11:41:19 +01:00
2019-03-13 14:10:15 +01:00
env . qweb . addTemplate (
"parent" ,
` <div>
2019-01-30 11:29:54 +01:00
<t t-foreach="state.numbers" t-as="number">
2019-06-20 09:42:33 +02:00
<t t-component="ChildWidget" t-key="number"/>
2019-01-30 11:29:54 +01:00
</t>
2019-03-13 14:10:15 +01:00
</div> `
) ;
class Parent extends Widget {
2019-09-12 09:48:17 +02:00
static template = "parent" ;
2019-09-24 14:06:53 +02:00
state = useState ( {
2019-01-30 11:29:54 +01:00
numbers : [ 1 , 2 , 3 ]
2019-09-24 14:06:53 +02:00
} ) ;
2019-09-10 22:37:08 +02:00
static components = { ChildWidget } ;
2019-01-30 11:29:54 +01:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-01-30 11:29:54 +01:00
await parent . mount ( fixture ) ;
2019-04-17 10:07:31 +02:00
parent . state . numbers = [ 1 , 3 ] ;
await nextTick ( ) ;
2019-01-30 11:29:54 +01:00
expect ( normalize ( fixture . innerHTML ) ) . toBe (
normalize ( `
<div>
<span>1</span>
<span>3</span>
</div>
` )
) ;
2019-05-02 09:47:33 +02:00
expect ( env . qweb . templates . parent . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-01-30 11:29:54 +01:00
} ) ;
2019-02-15 14:35:46 +01:00
2019-06-20 09:42:33 +02:00
test ( "sub components between t-ifs" , async ( ) = > {
2019-02-15 14:35:46 +01:00
// this confuses the patching algorithm...
2019-10-25 15:45:56 +02:00
class ChildWidget extends Widget {
static template = xml ` <span>child</span> ` ;
}
2019-05-15 11:15:08 +02:00
class Parent extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml `
<div>
<h1 t-if="state.flag">hey</h1>
<h2 t-else="1">noo</h2>
<span><ChildWidget/></span>
<t t-if="state.flag"><span>test</span></t>
</div> ` ;
2019-09-10 22:37:08 +02:00
static components = { ChildWidget } ;
2019-10-25 15:45:56 +02:00
state = useState ( { flag : false } ) ;
2019-02-15 14:35:46 +01:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-02-15 14:35:46 +01:00
await parent . mount ( fixture ) ;
const child = children ( parent ) [ 0 ] ;
2019-10-25 15:45:56 +02:00
expect ( normalize ( fixture . innerHTML ) ) . toBe (
normalize ( `
<div>
<h2>noo</h2>
<span><span>child</span></span>
</div>
` )
) ;
2019-04-17 10:07:31 +02:00
parent . state . flag = true ;
await nextTick ( ) ;
2019-02-15 14:35:46 +01:00
expect ( children ( parent ) [ 0 ] ) . toBe ( child ) ;
2019-04-13 14:14:34 +02:00
expect ( child . __owl__ . isDestroyed ) . toBe ( false ) ;
2019-02-15 14:35:46 +01:00
expect ( normalize ( fixture . innerHTML ) ) . toBe (
normalize ( `
<div>
<h1>hey</h1>
<span><span>child</span></span>
<span>test</span>
</div>
` )
) ;
} ) ;
2019-05-28 14:30:08 +02:00
2019-09-03 09:32:38 +02:00
test ( "list of sub components inside other nodes" , async ( ) = > {
// this confuses the patching algorithm...
env . qweb . addTemplate ( "ChildWidget" , ` <span>child</span> ` ) ;
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<div t-foreach="state.blips" t-as="blip" t-key="blip.id">
<SubWidget />
</div>
</div>
<span t-name="SubWidget">asdf</span>
</templates> ` ) ;
class SubWidget extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { SubWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { blips : [ { a : "a" , id : 1 } , { b : "b" , id : 2 } , { c : "c" , id : 4 } ] } ) ;
2019-09-03 09:32:38 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-09-03 09:32:38 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
"<div><div><span>asdf</span></div><div><span>asdf</span></div><div><span>asdf</span></div></div>"
) ;
parent . state . blips . splice ( 0 , 1 ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe (
"<div><div><span>asdf</span></div><div><span>asdf</span></div></div>"
) ;
} ) ;
2019-09-03 16:38:24 +02:00
test ( "list of two sub components inside other nodes" , async ( ) = > {
// this confuses the patching algorithm...
env . qweb . addTemplate ( "ChildWidget" , ` <span>child</span> ` ) ;
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<div t-foreach="state.blips" t-as="blip" t-key="blip.id">
<SubWidget />
<SubWidget />
</div>
</div>
<span t-name="SubWidget">asdf</span>
</templates> ` ) ;
class SubWidget extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { SubWidget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { blips : [ { a : "a" , id : 1 } ] } ) ;
2019-09-03 16:38:24 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-09-03 16:38:24 +02:00
await parent . mount ( fixture ) ;
2019-09-04 22:48:37 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div><span>asdf</span><span>asdf</span></div></div>" ) ;
2019-09-03 16:38:24 +02:00
} ) ;
2019-06-20 09:42:33 +02:00
test ( "t-component with dynamic value" , async ( ) = > {
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-component="{{state.widget}}"/></div> ` ) ;
2019-05-28 14:30:08 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { WidgetB } ;
2019-09-24 14:06:53 +02:00
state = useState ( { widget : "WidgetB" } ) ;
2019-05-28 14:30:08 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-28 14:30:08 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>world</div></div>" ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
2019-06-20 09:42:33 +02:00
test ( "t-component with dynamic value 2" , async ( ) = > {
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-component="Widget{{state.widget}}"/></div> ` ) ;
2019-05-28 14:30:08 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { WidgetB } ;
2019-09-24 14:06:53 +02:00
state = useState ( { widget : "B" } ) ;
2019-05-28 14:30:08 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-28 14:30:08 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>world</div></div>" ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
2019-10-24 21:20:52 +02:00
test ( "sub components, loops, and shouldUpdate" , async ( ) = > {
class ChildWidget extends Component < any , any > {
static template = xml ` <span><t t-esc="props.val"/></span> ` ;
shouldUpdate ( nextProps ) {
if ( nextProps . val === 12 ) {
return false ;
}
return true ;
}
}
class Parent extends Component < any , any > {
static template = xml `
<div>
<t t-foreach="state.records" t-as="record">
<ChildWidget t-key="record.id" val="record.val"/>
</t>
</div> ` ;
state = useState ( {
records : [ { id : 1 , val : 1 } , { id : 2 , val : 2 } , { id : 3 , val : 3 } ]
} ) ;
static components = { ChildWidget } ;
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-24 21:20:52 +02:00
await parent . mount ( fixture ) ;
expect ( normalize ( fixture . innerHTML ) ) . toBe (
"<div><span>1</span><span>2</span><span>3</span></div>"
) ;
parent . state . records [ 0 ] . val = 11 ;
parent . state . records [ 1 ] . val = 12 ;
parent . state . records [ 2 ] . val = 13 ;
await nextTick ( ) ;
expect ( normalize ( fixture . innerHTML ) ) . toBe (
"<div><span>11</span><span>2</span><span>13</span></div>"
) ;
} ) ;
2019-01-20 14:46:36 +01:00
} ) ;
2019-01-28 12:03:59 +01:00
2019-06-03 10:10:41 +02:00
describe ( "props evaluation " , ( ) = > {
2019-01-28 12:03:59 +01:00
test ( "explicit object prop" , async ( ) = > {
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "Parent" , ` <div><t t-component="child" value="state.val"/></div> ` ) ;
2019-02-04 21:09:17 +01:00
class Child extends Widget {
2019-01-28 12:03:59 +01:00
state : { someval : number } ;
constructor ( parent : Parent , props : { value : number } ) {
super ( parent ) ;
2019-09-24 14:06:53 +02:00
this . state = useState ( { someval : props.value } ) ;
2019-01-28 12:03:59 +01:00
}
}
2019-09-10 22:37:08 +02:00
class Parent extends Widget {
static components = { child : Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 42 } ) ;
2019-09-10 22:37:08 +02:00
}
env . qweb . addTemplate ( "Child" , ` <span><t t-esc="state.someval"/></span> ` ) ;
2019-01-28 12:03:59 +01:00
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-01-28 12:03:59 +01:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>42</span></div>" ) ;
} ) ;
2019-03-14 15:53:31 +01:00
test ( "accept ES6-like syntax for props (with getters)" , async ( ) = > {
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "Child" , ` <span><t t-esc="props.greetings"/></span> ` ) ;
class Child extends Widget { }
2019-03-14 15:53:31 +01:00
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "Parent" , ` <div><t t-component="child" greetings="greetings"/></div> ` ) ;
2019-03-14 15:53:31 +01:00
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-03-14 15:53:31 +01:00
get greetings() {
2019-10-30 14:57:24 +01:00
const name = "aaron" ;
return ` hello ${ name } ` ;
2019-03-14 15:53:31 +01:00
}
}
2019-10-30 14:57:24 +01:00
const widget = new Parent ( ) ;
2019-03-14 15:53:31 +01:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>hello aaron</span></div>" ) ;
} ) ;
2019-03-15 10:11:50 +01:00
2019-06-03 10:10:41 +02:00
test ( "t-set works " , async ( ) = > {
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"Parent" ,
`
2019-03-15 10:11:50 +01:00
<div>
<t t-set="val" t-value="42"/>
2019-06-20 09:42:33 +02:00
<t t-component="child" val="val"/>
2019-05-15 11:15:08 +02:00
</div> `
) ;
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-03-15 10:11:50 +01:00
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"Child" ,
`
2019-03-15 10:11:50 +01:00
<span>
<t t-esc="props.val"/>
2019-05-15 11:15:08 +02:00
</span> `
) ;
2019-03-15 10:11:50 +01:00
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-03-15 10:11:50 +01:00
await widget . mount ( fixture ) ;
expect ( normalize ( fixture . innerHTML ) ) . toBe ( "<div><span>42</span></div>" ) ;
} ) ;
2019-01-28 12:03:59 +01:00
} ) ;
2019-01-30 13:33:37 +01:00
2019-06-20 09:42:33 +02:00
describe ( "class and style attributes with t-component" , ( ) = > {
2019-05-04 22:26:51 +02:00
test ( "class is properly added on widget root el" , async ( ) = > {
2019-10-25 15:45:56 +02:00
class Child extends Widget {
static template = xml ` <div class="c"/> ` ;
}
2019-05-15 11:15:08 +02:00
class ParentWidget extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <div><Child class="a b"/></div> ` ;
static components = { Child } ;
2019-05-04 22:26:51 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-04 22:26:51 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( ` <div><div class="c a b"></div></div> ` ) ;
} ) ;
2019-05-06 10:36:40 +02:00
test ( "t-att-class is properly added/removed on widget root el" , async ( ) = > {
2019-10-25 15:45:56 +02:00
class Child extends Widget {
static template = xml ` <div class="c"/> ` ;
}
2019-05-06 10:36:40 +02:00
class ParentWidget extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <div><Child t-att-class="{a:state.a, b:state.b}"/></div> ` ;
static components = { Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { a : true , b : false } ) ;
2019-05-06 10:36:40 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-06 10:36:40 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( ` <div><div class="c a"></div></div> ` ) ;
2019-10-25 15:45:56 +02:00
expect ( QWeb . TEMPLATES [ ParentWidget . template ] . fn . toString ( ) ) ;
2019-05-06 10:36:40 +02:00
widget . state . a = false ;
widget . state . b = true ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( ` <div><div class="c b"></div></div> ` ) ;
} ) ;
2019-06-25 10:14:16 +02:00
test ( "class with extra whitespaces" , async ( ) = > {
env . qweb . addTemplate (
"ParentWidget" ,
` <div>
<Child class="a b c d"/>
</div> `
) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-06-25 10:14:16 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-06-25 10:14:16 +02:00
}
env . qweb . addTemplate ( "Child" , ` <div/> ` ) ;
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-25 10:14:16 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( ` <div><div class="a b c d"></div></div> ` ) ;
} ) ;
2019-06-25 14:24:03 +02:00
test ( "t-att-class is properly added/removed on widget root el (v2)" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="ParentWidget">
<Child class="a" t-att-class="{ b: state.b }" t-ref="child"/>
</div>
<span t-name="Child" class="c" t-att-class="{ d: state.d }"/>
</templates> ` ) ;
class Child extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { d : true } ) ;
2019-06-25 14:24:03 +02:00
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
static components = { Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { b : true } ) ;
2019-09-26 21:56:01 +02:00
child = useRef ( "child" ) ;
2019-09-10 22:37:08 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-25 14:24:03 +02:00
await widget . mount ( fixture ) ;
const span = fixture . querySelector ( "span" ) ! ;
expect ( span . className ) . toBe ( "c d a b" ) ;
widget . state . b = false ;
await nextTick ( ) ;
expect ( span . className ) . toBe ( "c d a" ) ;
2019-09-26 21:56:01 +02:00
( widget . child . comp as Child ) . state . d = false ;
2019-06-25 14:24:03 +02:00
await nextTick ( ) ;
expect ( span . className ) . toBe ( "c a" ) ;
widget . state . b = true ;
await nextTick ( ) ;
expect ( span . className ) . toBe ( "c a b" ) ;
2019-09-26 21:56:01 +02:00
( widget . child . comp as Child ) . state . d = true ;
2019-06-25 14:24:03 +02:00
await nextTick ( ) ;
expect ( span . className ) . toBe ( "c a b d" ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
expect ( env . qweb . templates . Child . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "t-att-class is properly added/removed on widget root el (v3)" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="ParentWidget">
<Child class="a" t-att-class="state.b ? 'b' : ''" t-ref="child"/>
</div>
<span t-name="Child" class="c" t-att-class="state.d ? 'd' : ''"/>
</templates> ` ) ;
class Child extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { d : true } ) ;
2019-06-25 14:24:03 +02:00
}
2019-09-10 22:37:08 +02:00
class ParentWidget extends Widget {
static components = { Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { b : true } ) ;
2019-09-26 21:56:01 +02:00
child = useRef ( "child" ) ;
2019-09-10 22:37:08 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-25 14:24:03 +02:00
await widget . mount ( fixture ) ;
const span = fixture . querySelector ( "span" ) ! ;
expect ( span . className ) . toBe ( "c d a b" ) ;
widget . state . b = false ;
await nextTick ( ) ;
expect ( span . className ) . toBe ( "c d a" ) ;
2019-09-26 21:56:01 +02:00
( widget . child . comp as Child ) . state . d = false ;
2019-06-25 14:24:03 +02:00
await nextTick ( ) ;
expect ( span . className ) . toBe ( "c a" ) ;
widget . state . b = true ;
await nextTick ( ) ;
expect ( span . className ) . toBe ( "c a b" ) ;
2019-09-26 21:56:01 +02:00
( widget . child . comp as Child ) . state . d = true ;
2019-06-25 14:24:03 +02:00
await nextTick ( ) ;
expect ( span . className ) . toBe ( "c a b d" ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
expect ( env . qweb . templates . Child . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "class on components do not interfere with user defined classes" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="App" t-att-class="{ c: state.c }" />
</templates> ` ) ;
class App extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { c : true } ) ;
2019-06-25 14:24:03 +02:00
mounted() {
this . el ! . classList . add ( "user" ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new App ( ) ;
2019-06-25 14:24:03 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( '<div class="c user"></div>' ) ;
widget . state . c = false ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( '<div class="user"></div>' ) ;
} ) ;
2019-05-04 22:26:51 +02:00
test ( "style is properly added on widget root el" , async ( ) = > {
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"ParentWidget" ,
`
2019-05-04 22:26:51 +02:00
<div>
2019-06-20 09:42:33 +02:00
<t t-component="child" style="font-weight: bold;"/>
2019-05-15 11:15:08 +02:00
</div> `
) ;
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Widget } ;
2019-05-04 22:26:51 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-04 22:26:51 +02:00
await widget . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( ` <div><div style="font-weight: bold;"></div></div> ` ) ;
2019-05-06 10:36:40 +02:00
} ) ;
test ( "dynamic t-att-style is properly added and updated on widget root el" , async ( ) = > {
env . qweb . addTemplate (
2019-05-15 11:15:08 +02:00
"ParentWidget" ,
2019-05-06 10:36:40 +02:00
`
<div>
2019-06-20 09:42:33 +02:00
<t t-component="child" t-att-style="state.style"/>
2019-05-06 10:36:40 +02:00
</div> `
) ;
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Widget } ;
2019-09-24 14:06:53 +02:00
state = useState ( { style : "font-size: 20px" } ) ;
2019-05-06 10:36:40 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-06 10:36:40 +02:00
await widget . mount ( fixture ) ;
2019-05-15 11:15:08 +02:00
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-05-06 10:36:40 +02:00
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( ` <div><div style="font-size: 20px;"></div></div> ` ) ;
2019-05-06 10:36:40 +02:00
widget . state . style = "font-size: 30px" ;
await nextTick ( ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( ` <div><div style="font-size: 30px;"></div></div> ` ) ;
2019-05-04 22:26:51 +02:00
} ) ;
} ) ;
2019-06-20 09:42:33 +02:00
describe ( "other directives with t-component" , ( ) = > {
2019-02-02 18:17:21 +01:00
test ( "t-on works as expected" , async ( ) = > {
2019-05-31 14:03:34 +02:00
expect . assertions ( 4 ) ;
env . qweb . addTemplates ( `
<templates>
2019-06-20 09:42:33 +02:00
<div t-name="ParentWidget"><t t-component="child" t-on-custom-event="someMethod"/></div>
2019-05-31 14:03:34 +02:00
</templates>
` ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-05-31 14:03:34 +02:00
n = 0 ;
someMethod ( ev ) {
expect ( ev . detail ) . toBe ( 43 ) ;
this . n ++ ;
2019-02-02 18:17:21 +01:00
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-02-02 18:17:21 +01:00
await widget . mount ( fixture ) ;
let child = children ( widget ) [ 0 ] ;
2019-05-31 14:03:34 +02:00
expect ( widget . n ) . toBe ( 0 ) ;
child . trigger ( "custom-event" , 43 ) ;
expect ( widget . n ) . toBe ( 1 ) ;
2019-02-02 18:17:21 +01:00
child . destroy ( ) ;
2019-05-31 14:03:34 +02:00
child . trigger ( "custom-event" , 43 ) ;
expect ( widget . n ) . toBe ( 1 ) ;
2019-02-02 18:17:21 +01:00
} ) ;
2019-03-04 09:29:27 +01:00
2019-06-03 15:12:21 +02:00
test ( "t-on with handler bound to argument" , async ( ) = > {
expect . assertions ( 3 ) ;
env . qweb . addTemplates ( `
<templates>
2019-06-20 09:42:33 +02:00
<div t-name="ParentWidget"><t t-component="child" t-on-ev="onEv(3)"/></div>
2019-06-03 15:12:21 +02:00
</templates>
` ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-06-03 15:12:21 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-06-03 15:12:21 +02:00
onEv ( n , ev ) {
expect ( n ) . toBe ( 3 ) ;
expect ( ev . detail ) . toBe ( 43 ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-03 15:12:21 +02:00
await widget . mount ( fixture ) ;
let child = children ( widget ) [ 0 ] ;
child . trigger ( "ev" , 43 ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "t-on with handler bound to object" , async ( ) = > {
expect . assertions ( 3 ) ;
env . qweb . addTemplates ( `
<templates>
2019-06-20 09:42:33 +02:00
<div t-name="ParentWidget"><t t-component="child" t-on-ev="onEv({val: 3})"/></div>
2019-06-03 15:12:21 +02:00
</templates>
` ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-06-03 15:12:21 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-06-03 15:12:21 +02:00
onEv ( o , ev ) {
expect ( o ) . toEqual ( { val : 3 } ) ;
expect ( ev . detail ) . toBe ( 43 ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-03 15:12:21 +02:00
await widget . mount ( fixture ) ;
let child = children ( widget ) [ 0 ] ;
child . trigger ( "ev" , 43 ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "t-on with handler bound to empty object" , async ( ) = > {
expect . assertions ( 3 ) ;
env . qweb . addTemplates ( `
<templates>
2019-06-20 09:42:33 +02:00
<div t-name="ParentWidget"><t t-component="child" t-on-ev="onEv({})"/></div>
2019-06-03 15:12:21 +02:00
</templates>
` ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-06-03 15:12:21 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-06-03 15:12:21 +02:00
onEv ( o , ev ) {
expect ( o ) . toEqual ( { } ) ;
expect ( ev . detail ) . toBe ( 43 ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-03 15:12:21 +02:00
await widget . mount ( fixture ) ;
let child = children ( widget ) [ 0 ] ;
child . trigger ( "ev" , 43 ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "t-on with handler bound to empty object (with non empty inner string)" , async ( ) = > {
expect . assertions ( 3 ) ;
env . qweb . addTemplates ( `
<templates>
2019-06-20 09:42:33 +02:00
<div t-name="ParentWidget"><t t-component="child" t-on-ev="onEv({ })"/></div>
2019-06-03 15:12:21 +02:00
</templates>
` ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-06-03 15:12:21 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-06-03 15:12:21 +02:00
onEv ( o , ev ) {
expect ( o ) . toEqual ( { } ) ;
expect ( ev . detail ) . toBe ( 43 ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-03 15:12:21 +02:00
await widget . mount ( fixture ) ;
let child = children ( widget ) [ 0 ] ;
child . trigger ( "ev" , 43 ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
2019-06-03 14:28:10 +02:00
test ( "t-on with stop and/or prevent modifiers" , async ( ) = > {
expect . assertions ( 7 ) ;
env . qweb . addTemplates ( `
<templates>
<div t-name="ParentWidget">
2019-06-20 09:42:33 +02:00
<t t-component="child"
2019-06-03 14:28:10 +02:00
t-on-ev-1.stop="onEv1"
t-on-ev-2.prevent="onEv2"
t-on-ev-3.stop.prevent="onEv3"/>
</div>
</templates>
` ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-06-03 14:28:10 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-06-03 14:28:10 +02:00
onEv1 ( ev ) {
expect ( ev . defaultPrevented ) . toBe ( false ) ;
expect ( ev . cancelBubble ) . toBe ( true ) ;
}
onEv2 ( ev ) {
expect ( ev . defaultPrevented ) . toBe ( true ) ;
expect ( ev . cancelBubble ) . toBe ( false ) ;
}
onEv3 ( ev ) {
expect ( ev . defaultPrevented ) . toBe ( true ) ;
expect ( ev . cancelBubble ) . toBe ( true ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-03 14:28:10 +02:00
await widget . mount ( fixture ) ;
const child = children ( widget ) [ 0 ] ;
child . trigger ( "ev-1" ) ;
child . trigger ( "ev-2" ) ;
child . trigger ( "ev-3" ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "t-on with self modifier" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="ParentWidget">
2019-06-20 09:42:33 +02:00
<t t-component="child" t-on-ev-1="onEv1" t-on-ev-2.self="onEv2"/>
2019-06-03 14:28:10 +02:00
</div>
2019-06-20 09:42:33 +02:00
<div t-name="Child"><t t-component="child"/></div>
2019-06-03 14:28:10 +02:00
</templates>
` ) ;
const steps : string [ ] = [ ] ;
2019-09-10 22:37:08 +02:00
class GrandChild extends Widget { }
class Child extends Widget {
static components = { child : GrandChild } ;
}
2019-06-03 14:28:10 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-06-03 14:28:10 +02:00
onEv1 ( ev ) {
steps . push ( "onEv1" ) ;
}
onEv2 ( ev ) {
steps . push ( "onEv2" ) ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-03 14:28:10 +02:00
await widget . mount ( fixture ) ;
const child = children ( widget ) [ 0 ] ;
const grandChild = children ( child ) [ 0 ] ;
child . trigger ( "ev-1" ) ;
child . trigger ( "ev-2" ) ;
expect ( steps ) . toEqual ( [ "onEv1" , "onEv2" ] ) ;
grandChild . trigger ( "ev-1" ) ;
grandChild . trigger ( "ev-2" ) ;
expect ( steps ) . toEqual ( [ "onEv1" , "onEv2" , "onEv1" ] ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "t-on with self and prevent modifiers (order matters)" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="ParentWidget">
2019-06-20 09:42:33 +02:00
<t t-component="child" t-on-ev.self.prevent="onEv"/>
2019-06-03 14:28:10 +02:00
</div>
2019-06-20 09:42:33 +02:00
<div t-name="Child"><t t-component="child"/></div>
2019-06-03 14:28:10 +02:00
</templates>
` ) ;
const steps : boolean [ ] = [ ] ;
2019-09-10 22:37:08 +02:00
class GrandChild extends Widget { }
class Child extends Widget {
static components = { child : GrandChild } ;
}
2019-06-03 14:28:10 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-06-03 14:28:10 +02:00
onEv() { }
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-03 14:28:10 +02:00
await widget . mount ( fixture ) ;
( < HTMLElement > fixture ) . addEventListener ( "ev" , function ( e ) {
steps . push ( e . defaultPrevented ) ;
} ) ;
const child = children ( widget ) [ 0 ] ;
const grandChild = children ( child ) [ 0 ] ;
child . trigger ( "ev" ) ;
grandChild . trigger ( "ev" ) ;
expect ( steps ) . toEqual ( [ true , false ] ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "t-on with prevent and self modifiers (order matters)" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="ParentWidget">
2019-06-18 09:24:52 +02:00
<Child t-on-ev.prevent.self="onEv"/>
2019-06-03 14:28:10 +02:00
</div>
2019-06-18 09:24:52 +02:00
<div t-name="Child"><GrandChild/></div>
2019-06-03 14:28:10 +02:00
</templates>
` ) ;
const steps : boolean [ ] = [ ] ;
2019-09-10 22:37:08 +02:00
class GrandChild extends Widget { }
class Child extends Widget {
static components = { GrandChild } ;
}
2019-06-03 14:28:10 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-06-03 14:28:10 +02:00
onEv() { }
}
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-06-03 14:28:10 +02:00
await widget . mount ( fixture ) ;
( < HTMLElement > fixture ) . addEventListener ( "ev" , function ( e ) {
steps . push ( e . defaultPrevented ) ;
} ) ;
const child = children ( widget ) [ 0 ] ;
const grandChild = children ( child ) [ 0 ] ;
child . trigger ( "ev" ) ;
grandChild . trigger ( "ev" ) ;
expect ( steps ) . toEqual ( [ true , true ] ) ;
expect ( env . qweb . templates . ParentWidget . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
2019-10-25 10:57:08 +02:00
test ( "t-on with getter as handler" , async ( ) = > {
class Child extends Component < any , any > {
static template = xml ` <span></span> ` ;
}
class Parent extends Component < any , any > {
static template = xml `
<div>
<t t-esc="state.counter"/>
<Child t-on-ev="handler"/>
</div> ` ;
static components = { Child } ;
2019-10-26 08:55:38 +02:00
state = useState ( { counter : 0 } ) ;
2019-10-25 10:57:08 +02:00
get handler() {
this . state . counter ++ ;
return ( ) = > { } ;
}
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-25 10:57:08 +02:00
await parent . mount ( fixture ) ;
expect ( env . qweb . templates [ Parent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-10-26 08:55:38 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div>0<span></span></div>" ) ;
2019-10-25 10:57:08 +02:00
let child = children ( parent ) [ 0 ] ;
child . trigger ( "ev" ) ;
await nextTick ( ) ;
2019-10-26 08:55:38 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div>1<span></span></div>" ) ;
2019-10-25 10:57:08 +02:00
} ) ;
test ( "t-on with inline statement" , async ( ) = > {
class Child extends Component < any , any > {
static template = xml ` <span></span> ` ;
}
class Parent extends Component < any , any > {
static template = xml `
<div>
<t t-esc="state.counter"/>
<Child t-on-ev="state.counter++"/>
</div> ` ;
static components = { Child } ;
2019-10-26 08:55:38 +02:00
state = useState ( { counter : 0 } ) ;
2019-10-25 10:57:08 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-25 10:57:08 +02:00
await parent . mount ( fixture ) ;
expect ( env . qweb . templates [ Parent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-10-26 08:55:38 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div>0<span></span></div>" ) ;
2019-10-25 10:57:08 +02:00
let child = children ( parent ) [ 0 ] ;
child . trigger ( "ev" ) ;
await nextTick ( ) ;
2019-10-26 08:55:38 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div>1<span></span></div>" ) ;
2019-10-25 10:57:08 +02:00
} ) ;
2019-10-25 10:58:18 +02:00
test ( "t-on with no handler (only modifiers)" , async ( ) = > {
expect . assertions ( 2 ) ;
class ComponentB extends Component < any , any > {
static template = xml ` <span></span> ` ;
}
class ComponentA extends Component < any , any > {
static template = xml ` <p><ComponentB t-on-ev.prevent=""/></p> ` ;
static components = { ComponentB } ;
}
class Parent extends Component < any , any > {
static template = xml ` <div><ComponentA t-on-ev="onEv"/></div> ` ;
static components = { ComponentA } ;
onEv ( ev ) {
expect ( ev . defaultPrevented ) . toBe ( true ) ;
}
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-25 10:58:18 +02:00
await parent . mount ( fixture ) ;
let componentB = children ( children ( parent ) [ 0 ] ) [ 0 ] ;
componentB . trigger ( "ev" ) ;
expect ( env . qweb . templates [ Parent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
2019-06-20 09:42:33 +02:00
test ( "t-if works with t-component" , async ( ) = > {
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "ParentWidget" , ` <div><t t-component="child" t-if="state.flag"/></div> ` ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-03-04 09:29:27 +01:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true } ) ;
2019-03-04 09:29:27 +01:00
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "Child" , "<span>hey</span>" ) ;
2019-03-04 09:29:27 +01:00
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-03-04 09:29:27 +01:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>hey</span></div>" ) ;
2019-04-17 10:07:31 +02:00
widget . state . flag = false ;
await nextTick ( ) ;
2019-03-04 09:29:27 +01:00
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
2019-04-17 10:07:31 +02:00
widget . state . flag = true ;
await nextTick ( ) ;
2019-03-04 09:29:27 +01:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>hey</span></div>" ) ;
} ) ;
2019-03-13 14:29:57 +01:00
2019-06-20 09:42:33 +02:00
test ( "t-else works with t-component" , async ( ) = > {
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"ParentWidget" ,
`
2019-03-13 14:29:57 +01:00
<div>
<div t-if="state.flag">somediv</div>
2019-06-20 09:42:33 +02:00
<t t-else="1" t-component="child"/>
2019-05-15 11:15:08 +02:00
</div> `
) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-05-15 11:15:08 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true } ) ;
2019-03-13 14:29:57 +01:00
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "Child" , "<span>hey</span>" ) ;
2019-03-13 14:29:57 +01:00
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-03-13 14:29:57 +01:00
await widget . mount ( fixture ) ;
expect ( normalize ( fixture . innerHTML ) ) . toBe ( "<div><div>somediv</div></div>" ) ;
2019-04-17 10:07:31 +02:00
widget . state . flag = false ;
await nextTick ( ) ;
2019-03-13 14:29:57 +01:00
expect ( normalize ( fixture . innerHTML ) ) . toBe ( "<div><span>hey</span></div>" ) ;
} ) ;
2019-04-16 14:17:02 +02:00
2019-06-20 09:42:33 +02:00
test ( "t-elif works with t-component" , async ( ) = > {
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"ParentWidget" ,
`
2019-05-10 19:35:58 +02:00
<div>
<div t-if="state.flag">somediv</div>
2019-06-20 09:42:33 +02:00
<t t-elif="!state.flag" t-component="child"/>
2019-05-15 11:15:08 +02:00
</div> `
) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-05-15 11:15:08 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true } ) ;
2019-05-10 19:35:58 +02:00
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "Child" , "<span>hey</span>" ) ;
2019-05-10 19:35:58 +02:00
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-05-10 19:35:58 +02:00
await widget . mount ( fixture ) ;
expect ( normalize ( fixture . innerHTML ) ) . toBe ( "<div><div>somediv</div></div>" ) ;
widget . state . flag = false ;
await nextTick ( ) ;
expect ( normalize ( fixture . innerHTML ) ) . toBe ( "<div><span>hey</span></div>" ) ;
} ) ;
2019-06-20 09:42:33 +02:00
test ( "t-else with empty string works with t-component" , async ( ) = > {
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"ParentWidget" ,
`
2019-04-16 14:17:02 +02:00
<div>
<div t-if="state.flag">somediv</div>
2019-06-20 09:42:33 +02:00
<t t-else="" t-component="child"/>
2019-05-15 11:15:08 +02:00
</div> `
) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-05-15 11:15:08 +02:00
class ParentWidget extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true } ) ;
2019-04-16 14:17:02 +02:00
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "Child" , "<span>hey</span>" ) ;
2019-04-16 14:17:02 +02:00
2019-10-14 15:09:17 +02:00
const widget = new ParentWidget ( ) ;
2019-04-16 14:17:02 +02:00
await widget . mount ( fixture ) ;
expect ( normalize ( fixture . innerHTML ) ) . toBe ( "<div><div>somediv</div></div>" ) ;
2019-04-17 10:07:31 +02:00
widget . state . flag = false ;
await nextTick ( ) ;
2019-04-16 14:17:02 +02:00
expect ( normalize ( fixture . innerHTML ) ) . toBe ( "<div><span>hey</span></div>" ) ;
} ) ;
2019-02-02 18:17:21 +01:00
} ) ;
describe ( "random stuff/miscellaneous" , ( ) = > {
2019-01-30 13:33:37 +01:00
test ( "widget after a t-foreach" , async ( ) = > {
// this test makes sure that the foreach directive does not pollute sub
2019-06-20 09:42:33 +02:00
// context with the inLoop variable, which is then used in the t-component
2019-01-30 13:33:37 +01:00
// directive as a key
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate (
"Test" ,
2019-06-20 09:42:33 +02:00
` <div><t t-foreach="Array(2)">txt</t><t t-component="widget"/></div> `
2019-05-15 11:15:08 +02:00
) ;
2019-02-04 21:09:17 +01:00
class Test extends Widget {
2019-09-10 22:37:08 +02:00
static components = { widget : Widget } ;
2019-01-30 13:33:37 +01:00
}
2019-10-14 15:09:17 +02:00
const widget = new Test ( ) ;
2019-01-30 13:33:37 +01:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>txttxt<div></div></div>" ) ;
} ) ;
2019-01-30 14:56:47 +01:00
2019-06-21 16:23:42 +02:00
test ( "t-on with handler bound to dynamic argument on a t-foreach" , async ( ) = > {
expect . assertions ( 3 ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-06-21 16:23:42 +02:00
class ParentWidget extends Widget {
2019-10-30 14:57:24 +01:00
static template = xml `
<div>
<t t-foreach="items" t-as="item">
<Child t-key="item" t-on-ev="onEv(item)"/>
</t>
</div> ` ;
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-10-30 14:57:24 +01:00
items = [ 1 , 2 , 3 , 4 ] ;
2019-06-21 16:23:42 +02:00
onEv ( n , ev ) {
expect ( n ) . toBe ( 1 ) ;
expect ( ev . detail ) . toBe ( 43 ) ;
}
}
2019-10-30 14:57:24 +01:00
const widget = new ParentWidget ( ) ;
2019-06-21 16:23:42 +02:00
await widget . mount ( fixture ) ;
children ( widget ) [ 0 ] . trigger ( "ev" , 43 ) ;
2019-10-30 14:57:24 +01:00
expect ( env . qweb . templates [ ParentWidget . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-21 16:23:42 +02:00
} ) ;
2019-01-30 14:56:47 +01:00
test ( "updating widget immediately" , async ( ) = > {
// in this situation, we protect against a bug that occurred: because of the
2019-06-20 09:42:33 +02:00
// interplay between components and vnodes, a sub widget vnode was patched
2019-01-30 14:56:47 +01:00
// twice.
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "Parent" , ` <div><t t-component="child" flag="state.flag"/></div> ` ) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-02-04 21:09:17 +01:00
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : false } ) ;
2019-01-30 14:56:47 +01:00
}
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "Child" , ` <span>abc<t t-if="props.flag">def</t></span> ` ) ;
2019-01-30 14:56:47 +01:00
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-01-30 14:56:47 +01:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>abc</span></div>" ) ;
2019-04-17 10:07:31 +02:00
widget . state . flag = true ;
await nextTick ( ) ;
2019-01-30 14:56:47 +01:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>abcdef</span></div>" ) ;
} ) ;
2019-02-14 12:37:07 +01:00
test ( "snapshotting compiled code" , async ( ) = > {
env . qweb . addTemplate (
2019-05-15 11:15:08 +02:00
"Parent" ,
2019-06-20 09:42:33 +02:00
` <div><t t-component="child" t-key="'somestring'" flag="state.flag"/></div> `
2019-02-14 12:37:07 +01:00
) ;
2019-09-10 22:37:08 +02:00
class Child extends Widget { }
2019-02-14 12:37:07 +01:00
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { child : Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : false } ) ;
2019-02-14 12:37:07 +01:00
}
2019-06-28 10:32:03 +02:00
env . qweb . addTemplate ( "Child" , ` <span>abc<t t-if="props.flag">def</t></span> ` ) ;
2019-02-14 12:37:07 +01:00
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-03-28 17:35:08 +01:00
await widget . mount ( fixture ) ;
2019-05-15 11:15:08 +02:00
expect ( env . qweb . templates . Parent . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-03-28 17:35:08 +01:00
} ) ;
2019-05-08 15:21:33 +02:00
test ( "component semantics" , async ( ) = > {
let steps : string [ ] = [ ] ;
let c : C ;
class TestWidget extends Widget {
name : string = "test" ;
async willStart() {
steps . push ( ` ${ this . name } :willStart ` ) ;
}
2019-09-19 11:42:00 +02:00
__render ( f ) {
2019-05-08 15:21:33 +02:00
steps . push ( ` ${ this . name } :render ` ) ;
2019-09-19 11:42:00 +02:00
return super . __render ( f ) ;
2019-05-08 15:21:33 +02:00
}
2019-06-24 13:02:12 +02:00
__patch ( vnode ) {
steps . push ( ` ${ this . name } :__patch ` ) ;
super . __patch ( vnode ) ;
2019-05-08 15:21:33 +02:00
}
2019-06-24 13:02:12 +02:00
__mount ( vnode , elm ) {
steps . push ( ` ${ this . name } :__patch(from __mount) ` ) ;
return super . __mount ( vnode , elm ) ;
2019-05-08 15:21:33 +02:00
}
mounted() {
steps . push ( ` ${ this . name } :mounted ` ) ;
}
async willUpdateProps() {
steps . push ( ` ${ this . name } :willUpdateProps ` ) ;
}
willPatch() {
steps . push ( ` ${ this . name } :willPatch ` ) ;
}
patched() {
steps . push ( ` ${ this . name } :patched ` ) ;
}
willUnmount() {
steps . push ( ` ${ this . name } :willUnmount ` ) ;
}
destroy() {
super . destroy ( ) ;
steps . push ( ` ${ this . name } :destroy ` ) ;
}
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "B" , ` <div>B</div> ` ) ;
2019-05-08 15:21:33 +02:00
class B extends TestWidget {
name = "B" ;
constructor ( parent , props ) {
super ( parent , props ) ;
steps . push ( "B:constructor" ) ;
}
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "D" , ` <div>D</div> ` ) ;
2019-05-08 15:21:33 +02:00
class D extends TestWidget {
name = "D" ;
constructor ( parent , props ) {
super ( parent , props ) ;
steps . push ( "D:constructor" ) ;
}
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "E" , ` <div>E</div> ` ) ;
2019-05-08 15:21:33 +02:00
class E extends TestWidget {
name = "E" ;
constructor ( parent , props ) {
super ( parent , props ) ;
steps . push ( "E:constructor" ) ;
}
}
2019-05-15 11:15:08 +02:00
env . qweb . addTemplate ( "F" , ` <div>F</div> ` ) ;
2019-05-08 15:21:33 +02:00
class F extends TestWidget {
name = "F" ;
constructor ( parent , props ) {
super ( parent , props ) ;
steps . push ( "F:constructor" ) ;
}
}
2019-09-10 22:37:08 +02:00
env . qweb . addTemplate (
"C" ,
`
<div>C<D />
<E t-if="state.flag" />
<F t-else="!state.flag" />
</div> `
) ;
class C extends TestWidget {
static components = { D , E , F } ;
name = "C" ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true } ) ;
2019-09-10 22:37:08 +02:00
constructor ( parent , props ) {
super ( parent , props ) ;
c = this ;
steps . push ( "C:constructor" ) ;
}
}
env . qweb . addTemplate ( "A" , ` <div>A<B /><C /></div> ` ) ;
class A extends TestWidget {
static components = { B , C } ;
name = "A" ;
}
2019-10-14 15:09:17 +02:00
const a = new A ( ) ;
2019-05-08 15:21:33 +02:00
await a . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( ` <div>A<div>B</div><div>C<div>D</div><div>E</div></div></div> ` ) ;
2019-05-08 15:21:33 +02:00
expect ( steps ) . toEqual ( [
"A:willStart" ,
"A:render" ,
"B:constructor" ,
"B:willStart" ,
"C:constructor" ,
"C:willStart" ,
"B:render" ,
"C:render" ,
"D:constructor" ,
"D:willStart" ,
"E:constructor" ,
"E:willStart" ,
"D:render" ,
"E:render" ,
2019-06-24 13:02:12 +02:00
"A:__patch" ,
"B:__patch(from __mount)" ,
"C:__patch(from __mount)" ,
"D:__patch(from __mount)" ,
"E:__patch(from __mount)" ,
2019-05-08 15:21:33 +02:00
"B:mounted" ,
"D:mounted" ,
"E:mounted" ,
"C:mounted" ,
2019-05-10 15:03:41 +02:00
"A:mounted"
2019-05-08 15:21:33 +02:00
] ) ;
// update
steps = [ ] ;
c ! . state . flag = false ;
await nextTick ( ) ;
expect ( steps ) . toEqual ( [
"C:render" ,
"D:willUpdateProps" ,
"F:constructor" ,
"F:willStart" ,
"D:render" ,
"F:render" ,
"C:willPatch" ,
"D:willPatch" ,
2019-06-24 13:02:12 +02:00
"C:__patch" ,
2019-05-08 15:21:33 +02:00
"E:willUnmount" ,
"E:destroy" ,
2019-06-24 13:02:12 +02:00
"F:__patch(from __mount)" ,
2019-05-08 15:21:33 +02:00
"F:mounted" ,
2019-06-24 13:02:12 +02:00
"D:__patch" ,
2019-05-08 15:21:33 +02:00
"D:patched" ,
"C:patched"
] ) ;
} ) ;
2019-09-21 23:05:44 +02:00
test ( "can inject values in tagged templates" , async ( ) = > {
2019-09-22 22:24:24 +02:00
const SUBTEMPLATE = xml ` <span><t t-esc="state.n"/></span> ` ;
class Parent extends Widget {
static template = xml ` <div><t t-call=" ${ SUBTEMPLATE } "/></div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { n : 42 } ) ;
2019-09-22 22:24:24 +02:00
}
2019-09-21 23:05:44 +02:00
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-09-21 23:05:44 +02:00
await widget . mount ( fixture ) ;
expect ( env . qweb . templates [ Parent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-09-22 22:24:24 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>42</span></div>" ) ;
2019-09-21 23:05:44 +02:00
} ) ;
2019-01-30 14:56:47 +01:00
} ) ;
2019-02-07 12:46:30 +01:00
describe ( "async rendering" , ( ) = > {
2019-02-08 09:17:04 +01:00
test ( "destroying a widget before start is over" , async ( ) = > {
let def = makeDeferred ( ) ;
class W extends Widget {
willStart ( ) : Promise < void > {
return def ;
}
}
2019-10-14 15:09:17 +02:00
const w = new W ( ) ;
2019-02-08 09:17:04 +01:00
w . mount ( fixture ) ;
2019-04-13 14:14:34 +02:00
expect ( w . __owl__ . isDestroyed ) . toBe ( false ) ;
expect ( w . __owl__ . isMounted ) . toBe ( false ) ;
2019-02-08 09:17:04 +01:00
w . destroy ( ) ;
def . resolve ( ) ;
await nextTick ( ) ;
2019-04-13 14:14:34 +02:00
expect ( w . __owl__ . isDestroyed ) . toBe ( true ) ;
expect ( w . __owl__ . isMounted ) . toBe ( false ) ;
2019-02-08 09:17:04 +01:00
} ) ;
test ( "destroying/recreating a subwidget with different props (if start is not over)" , async ( ) = > {
let def = makeDeferred ( ) ;
let n = 0 ;
class Child extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <span>child:<t t-esc="props.val"/></span> ` ;
2019-02-08 09:17:04 +01:00
constructor ( parent , props ) {
super ( parent , props ) ;
n ++ ;
}
willStart ( ) : Promise < void > {
return def ;
}
}
2019-09-10 22:37:08 +02:00
class W extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <div><t t-if="state.val > 1"><Child val="state.val"/></t></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 1 } ) ;
2019-09-10 22:37:08 +02:00
}
2019-10-14 15:09:17 +02:00
const w = new W ( ) ;
2019-02-08 09:17:04 +01:00
await w . mount ( fixture ) ;
expect ( n ) . toBe ( 0 ) ;
2019-04-16 22:59:54 +02:00
w . state . val = 2 ;
await nextMicroTick ( ) ;
2019-02-08 09:17:04 +01:00
expect ( n ) . toBe ( 1 ) ;
2019-04-16 22:59:54 +02:00
w . state . val = 3 ;
await nextMicroTick ( ) ;
2019-02-08 09:17:04 +01:00
expect ( n ) . toBe ( 2 ) ;
def . resolve ( ) ;
await nextTick ( ) ;
expect ( children ( w ) . length ) . toBe ( 1 ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>child:3</span></div>" ) ;
} ) ;
2019-06-20 09:42:33 +02:00
test ( "creating two async components, scenario 1" , async ( ) = > {
2019-02-07 20:24:01 +01:00
let defA = makeDeferred ( ) ;
let defB = makeDeferred ( ) ;
2019-10-25 15:45:56 +02:00
let nbRenderings : number = 0 ;
2019-02-07 12:46:30 +01:00
2019-10-25 15:45:56 +02:00
class ChildA extends Component < any , any > {
static template = xml ` <span><t t-esc="getValue()"/></span> ` ;
2019-02-07 20:24:01 +01:00
willStart ( ) : Promise < void > {
return defA ;
}
2019-10-25 15:45:56 +02:00
getValue() {
nbRenderings ++ ;
return "a" ;
}
2019-02-07 12:46:30 +01:00
}
2019-10-25 15:45:56 +02:00
class ChildB extends Component < any , any > {
static template = xml ` <span>b</span> ` ;
2019-02-07 20:24:01 +01:00
willStart ( ) : Promise < void > {
return defB ;
}
2019-02-07 12:46:30 +01:00
}
2019-10-25 15:45:56 +02:00
class Parent extends Component < any , any > {
static template = xml `
2019-02-07 12:46:30 +01:00
<div>
2019-06-18 09:24:52 +02:00
<t t-if="state.flagA"><ChildA /></t>
<t t-if="state.flagB"><ChildB /></t>
2019-10-25 15:45:56 +02:00
</div> ` ;
2019-09-10 22:37:08 +02:00
static components = { ChildA , ChildB } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flagA : false , flagB : false } ) ;
2019-02-07 12:46:30 +01:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-02-07 12:46:30 +01:00
await parent . mount ( fixture ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
2019-04-17 10:07:31 +02:00
parent . state . flagA = true ;
2019-02-07 12:46:30 +01:00
await nextTick ( ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
2019-04-17 10:07:31 +02:00
parent . state . flagB = true ;
2019-02-07 12:46:30 +01:00
await nextTick ( ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
2019-02-07 20:24:01 +01:00
defB . resolve ( ) ;
2019-10-25 15:45:56 +02:00
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
expect ( nbRenderings ) . toBe ( 0 ) ;
2019-02-07 20:24:01 +01:00
defA . resolve ( ) ;
2019-02-07 12:46:30 +01:00
await nextTick ( ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>a</span><span>b</span></div>" ) ;
expect ( nbRenderings ) . toBe ( 1 ) ;
2019-02-07 12:46:30 +01:00
} ) ;
2019-02-07 20:24:01 +01:00
2019-06-20 09:42:33 +02:00
test ( "creating two async components, scenario 2" , async ( ) = > {
2019-02-07 20:24:01 +01:00
let defA = makeDeferred ( ) ;
let defB = makeDeferred ( ) ;
2019-10-25 15:45:56 +02:00
class ChildA extends Component < any , any > {
static template = xml ` <span>a<t t-esc="props.val"/></span> ` ;
willUpdateProps ( ) : Promise < void > {
return defA ;
2019-02-07 20:24:01 +01:00
}
}
2019-10-25 15:45:56 +02:00
class ChildB extends Component < any , any > {
static template = xml ` <span>b<t t-esc="props.val"/></span> ` ;
2019-02-07 20:24:01 +01:00
willStart ( ) : Promise < void > {
return defB ;
}
}
2019-10-25 15:45:56 +02:00
class Parent extends Component < any , any > {
static template = xml `
2019-02-07 20:24:01 +01:00
<div>
2019-06-18 09:24:52 +02:00
<ChildA val="state.valA"/>
<t t-if="state.flagB"><ChildB val="state.valB"/></t>
2019-10-25 15:45:56 +02:00
</div> ` ;
2019-09-10 22:37:08 +02:00
static components = { ChildA , ChildB } ;
2019-09-24 14:06:53 +02:00
state = useState ( { valA : 1 , valB : 2 , flagB : false } ) ;
2019-02-07 20:24:01 +01:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-02-07 20:24:01 +01:00
await parent . mount ( fixture ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>a1</span></div>" ) ;
2019-04-17 10:07:31 +02:00
parent . state . valA = 2 ;
2019-02-07 20:24:01 +01:00
await nextTick ( ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>a1</span></div>" ) ;
2019-04-17 10:07:31 +02:00
parent . state . flagB = true ;
2019-02-07 20:24:01 +01:00
await nextTick ( ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>a1</span></div>" ) ;
2019-02-07 20:24:01 +01:00
defB . resolve ( ) ;
2019-10-25 15:45:56 +02:00
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>a1</span></div>" ) ;
2019-02-07 20:24:01 +01:00
defA . resolve ( ) ;
await nextTick ( ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>a2</span><span>b2</span></div>" ) ;
} ) ;
test ( "creating two async components, scenario 3 (patching in the same frame)" , async ( ) = > {
let defA = makeDeferred ( ) ;
let defB = makeDeferred ( ) ;
class ChildA extends Component < any , any > {
static template = xml ` <span>a<t t-esc="props.val"/></span> ` ;
willUpdateProps ( ) : Promise < void > {
return defA ;
}
}
class ChildB extends Component < any , any > {
static template = xml ` <span>b<t t-esc="props.val"/></span> ` ;
willStart ( ) : Promise < void > {
return defB ;
}
}
class Parent extends Component < any , any > {
static template = xml `
<div>
<ChildA val="state.valA"/>
<t t-if="state.flagB"><ChildB val="state.valB"/></t>
</div> ` ;
static components = { ChildA , ChildB } ;
state = useState ( { valA : 1 , valB : 2 , flagB : false } ) ;
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-25 15:45:56 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>a1</span></div>" ) ;
parent . state . valA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>a1</span></div>" ) ;
parent . state . flagB = true ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>a1</span></div>" ) ;
defB . resolve ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>a1</span></div>" ) ;
defA . resolve ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>a2</span><span>b2</span></div>" ) ;
} ) ;
test ( "update a sub-component twice in the same frame" , async ( ) = > {
const steps : string [ ] = [ ] ;
const defs = [ makeDeferred ( ) , makeDeferred ( ) ] ;
let index = 0 ;
class ChildA extends Component < any , any > {
static template = xml ` <span><t t-esc="props.val"/></span> ` ;
willUpdateProps ( ) : Promise < void > {
return defs [ index ++ ] ;
}
patched() {
2019-10-24 21:20:52 +02:00
steps . push ( "patched" ) ;
2019-10-25 15:45:56 +02:00
}
}
class Parent extends Component < any , any > {
static template = xml ` <div><ChildA val="state.valA"/></div> ` ;
static components = { ChildA } ;
state = useState ( { valA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-25 15:45:56 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>1</span></div>" ) ;
parent . state . valA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>1</span></div>" ) ;
parent . state . valA = 3 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>1</span></div>" ) ;
defs [ 0 ] . resolve ( ) ;
await Promise . resolve ( ) ;
defs [ 1 ] . resolve ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>3</span></div>" ) ;
2019-10-24 21:20:52 +02:00
expect ( steps ) . toEqual ( [ "patched" ] ) ;
} ) ;
test ( "update a sub-component twice in the same frame, 2" , async ( ) = > {
const steps : string [ ] = [ ] ;
class ChildA extends Component < any , any > {
static template = xml ` <span><t t-esc="val()"/></span> ` ;
patched() {
steps . push ( "patched" ) ;
}
val() {
steps . push ( "render" ) ;
return this . props . val ;
}
}
class Parent extends Component < any , any > {
static template = xml ` <div><ChildA val="state.valA"/></div> ` ;
static components = { ChildA } ;
state = useState ( { valA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-24 21:20:52 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>1</span></div>" ) ;
parent . state . valA = 2 ;
await nextMicroTick ( ) ;
expect ( steps ) . toEqual ( [ "render" ] ) ;
await nextMicroTick ( ) ;
2019-10-31 11:32:27 +01:00
// For an unknown reason, this test fails on windows without the next microtick. It works
// in linux and osx, but fails on at least this machine.
// I do not see anything harmful in waiting an extra tick. But it is annoying to not
// know what is different.
await nextMicroTick ( ) ;
2019-10-24 21:20:52 +02:00
expect ( steps ) . toEqual ( [ "render" , "render" ] ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>1</span></div>" ) ;
parent . state . valA = 3 ;
await nextMicroTick ( ) ;
expect ( steps ) . toEqual ( [ "render" , "render" ] ) ;
await nextMicroTick ( ) ;
2019-10-31 11:32:27 +01:00
// same as above
await nextMicroTick ( ) ;
2019-10-24 21:20:52 +02:00
expect ( steps ) . toEqual ( [ "render" , "render" , "render" ] ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>1</span></div>" ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>3</span></div>" ) ;
expect ( steps ) . toEqual ( [ "render" , "render" , "render" , "patched" ] ) ;
2019-02-07 20:24:01 +01:00
} ) ;
2019-05-06 14:23:29 +02:00
2019-06-20 09:42:33 +02:00
test ( "components in a node in a t-foreach " , async ( ) = > {
2019-05-15 15:18:27 +02:00
class Child extends Widget { }
class App extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-05-15 15:18:27 +02:00
get items() {
return [ 1 , 2 ] ;
}
}
env . qweb . addTemplates ( `
<templates>
<div t-name="Child"><t t-esc="props.item"/></div>
<div t-name="App">
<ul>
<t t-foreach="items" t-as="item">
<li t-key="'li_'+item">
2019-06-18 09:24:52 +02:00
<Child item="item"/>
2019-05-15 15:18:27 +02:00
</li>
</t>
</ul>
</div>
</templates> ` ) ;
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-05-15 15:18:27 +02:00
await app . mount ( fixture ) ;
2019-05-15 17:17:39 +02:00
expect ( fixture . innerHTML ) . toBe (
"<div><ul><li><div>1</div></li><li><div>2</div></li></ul></div>"
) ;
2019-05-15 15:18:27 +02:00
} ) ;
2019-05-07 11:36:03 +02:00
test ( "properly behave when destroyed/unmounted while rendering " , async ( ) = > {
2019-10-25 15:45:56 +02:00
const def = makeDeferred ( ) ;
2019-05-06 14:23:29 +02:00
2019-09-10 22:37:08 +02:00
class SubChild extends Widget {
willPatch() {
throw new Error ( "Should not happen!" ) ;
}
patched() {
throw new Error ( "Should not happen!" ) ;
}
2019-10-25 15:45:56 +02:00
willUpdateProps() {
return def ;
}
2019-09-10 22:37:08 +02:00
}
2019-05-06 14:23:29 +02:00
class Child extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <div><SubChild /></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { SubChild } ;
2019-05-06 14:23:29 +02:00
}
class Parent extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <div><t t-if="state.flag"><Child val="state.val"/></t></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true , val : "Framboise Lindemans" } ) ;
2019-05-06 14:23:29 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-05-06 14:23:29 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div><div></div></div></div>" ) ;
// this change triggers a rendering of the parent. This rendering is delayed,
// because child is now waiting for def to be resolved
parent . state . val = "Framboise Girardin" ;
await nextTick ( ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div><div></div></div></div>" ) ;
2019-05-06 14:23:29 +02:00
2019-10-25 15:45:56 +02:00
// with this, we remove child, and subchild, even though it is not finished
2019-05-06 14:23:29 +02:00
// rendering from previous changes
parent . state . flag = false ;
await nextTick ( ) ;
2019-10-25 15:45:56 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
2019-05-06 14:23:29 +02:00
// we now resolve def, so the child rendering is now complete.
( < any > def ) . resolve ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
} ) ;
2019-06-01 21:44:43 +02:00
2019-10-25 15:45:56 +02:00
test . skip ( "reuse widget if possible, in some async situation" , async ( ) = > {
// this optimization has been temporarily deactivated
2019-06-01 21:44:43 +02:00
env . qweb . addTemplates ( `
<templates>
<span t-name="ChildA">a<t t-esc="props.val"/></span>
<span t-name="ChildB">b<t t-esc="props.val"/></span>
<span t-name="Parent">
<t t-if="state.flag">
2019-06-18 09:24:52 +02:00
<ChildA val="state.valA"/>
<ChildB val="state.valB"/>
2019-06-01 21:44:43 +02:00
</t>
</span>
</templates>
` ) ;
let destroyCount = 0 ;
class ChildA extends Widget {
2019-06-03 14:28:10 +02:00
destroy() {
destroyCount ++ ;
super . destroy ( ) ;
}
2019-06-01 21:44:43 +02:00
}
class ChildB extends Widget {
2019-06-03 14:28:10 +02:00
willStart ( ) : any {
return new Promise ( function ( ) { } ) ;
}
2019-06-01 21:44:43 +02:00
}
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { ChildA , ChildB } ;
2019-09-24 14:06:53 +02:00
state = useState ( { valA : 1 , valB : 2 , flag : false } ) ;
2019-06-01 21:44:43 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-01 21:44:43 +02:00
await parent . mount ( fixture ) ;
expect ( destroyCount ) . toBe ( 0 ) ;
parent . state . flag = true ;
await nextTick ( ) ;
expect ( destroyCount ) . toBe ( 0 ) ;
parent . state . valB = 3 ;
await nextTick ( ) ;
expect ( destroyCount ) . toBe ( 0 ) ;
} ) ;
2019-06-14 10:17:13 +02:00
2019-07-30 08:47:31 +02:00
test ( "rendering component again in next microtick" , async ( ) = > {
2019-10-25 15:45:56 +02:00
class Child extends Widget {
static template = xml ` <div t-name="Child">Child</div> ` ;
}
2019-07-30 08:47:31 +02:00
class App extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml `
<div>
<button t-on-click="onClick">Click</button>
<t t-if="env.flag"><Child/></t>
</div> ` ;
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-07-30 08:47:31 +02:00
async onClick() {
( env as any ) . flag = true ;
this . render ( ) ;
await Promise . resolve ( ) ;
this . render ( ) ;
}
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-30 08:47:31 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><button>Click</button></div>" ) ;
fixture . querySelector ( "button" ) ! . click ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><button>Click</button><div>Child</div></div>" ) ;
} ) ;
2019-03-19 11:48:30 +01:00
2019-10-25 15:45:56 +02:00
test ( "concurrent renderings scenario 1" , async ( ) = > {
const def = makeDeferred ( ) ;
let stateB ;
2019-04-16 22:59:54 +02:00
2019-10-25 15:45:56 +02:00
class ComponentC extends Component < any , any > {
static template = xml ` <span><t t-esc="props.fromA"/><t t-esc="someValue()"/></span> ` ;
someValue() {
return this . props . fromB ;
}
willUpdateProps() {
return def ;
}
}
ComponentC . prototype . someValue = jest . fn ( ComponentC . prototype . someValue ) ;
2019-04-16 22:59:54 +02:00
2019-10-25 15:45:56 +02:00
class ComponentB extends Component < any , any > {
static components = { ComponentC } ;
static template = xml ` <p><ComponentC fromA="props.fromA" fromB="state.fromB" /></p> ` ;
state = useState ( { fromB : "b" } ) ;
2019-04-17 16:52:39 +02:00
constructor ( parent , props ) {
super ( parent , props ) ;
2019-10-25 15:45:56 +02:00
stateB = this . state ;
2019-04-17 16:52:39 +02:00
}
}
2019-10-25 15:45:56 +02:00
class ComponentA extends Component < any , any > {
static components = { ComponentB } ;
static template = xml ` <div><ComponentB fromA="state.fromA"/></div> ` ;
state = useState ( { fromA : 1 } ) ;
2019-09-10 22:37:08 +02:00
}
2019-07-09 14:33:56 +02:00
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>1b</span></p></div>" ) ;
stateB . fromB = "c" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>1b</span></p></div>" ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>1b</span></p></div>" ) ;
expect ( ComponentC . prototype . someValue ) . toBeCalledTimes ( 1 ) ;
def . resolve ( ) ;
await nextTick ( ) ;
expect ( ComponentC . prototype . someValue ) . toBeCalledTimes ( 2 ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>2c</span></p></div>" ) ;
} ) ;
test ( "concurrent renderings scenario 2" , async ( ) = > {
// this test asserts that a rendering initiated before another one, and that
// ends after it, is re-mapped to that second rendering
const defs = [ makeDeferred ( ) , makeDeferred ( ) ] ;
let index = 0 ;
let stateB ;
class ComponentC extends Component < any , any > {
static template = xml ` <span><t t-esc="props.fromA"/><t t-esc="props.fromB"/></span> ` ;
willUpdateProps() {
return defs [ index ++ ] ;
}
}
class ComponentB extends Component < any , any > {
static template = xml ` <p><ComponentC fromA="props.fromA" fromB="state.fromB" /></p> ` ;
static components = { ComponentC } ;
state = useState ( { fromB : "b" } ) ;
constructor ( parent , props ) {
super ( parent , props ) ;
stateB = this . state ;
}
}
class ComponentA extends Component < any , any > {
static template = xml ` <div><t t-esc="state.fromA"/><ComponentB fromA="state.fromA"/></div> ` ;
static components = { ComponentB } ;
state = useState ( { fromA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>1<p><span>1b</span></p></div>" ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>1<p><span>1b</span></p></div>" ) ;
stateB . fromB = "c" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>1<p><span>1b</span></p></div>" ) ;
defs [ 1 ] . resolve ( ) ; // resolve rendering initiated in B
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>2<p><span>2c</span></p></div>" ) ;
defs [ 0 ] . resolve ( ) ; // resolve rendering initiated in A
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>2<p><span>2c</span></p></div>" ) ;
} ) ;
test ( "concurrent renderings scenario 2bis" , async ( ) = > {
const defs = [ makeDeferred ( ) , makeDeferred ( ) ] ;
let index = 0 ;
let stateB ;
class ComponentC extends Component < any , any > {
static template = xml ` <span><t t-esc="props.fromA"/><t t-esc="props.fromB"/></span> ` ;
willUpdateProps() {
return defs [ index ++ ] ;
}
}
class ComponentB extends Component < any , any > {
static template = xml ` <p><ComponentC fromA="props.fromA" fromB="state.fromB" /></p> ` ;
static components = { ComponentC } ;
state = useState ( { fromB : "b" } ) ;
constructor ( parent , props ) {
super ( parent , props ) ;
stateB = this . state ;
}
}
class ComponentA extends Component < any , any > {
static template = xml ` <div><ComponentB fromA="state.fromA"/></div> ` ;
static components = { ComponentB } ;
state = useState ( { fromA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>1b</span></p></div>" ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>1b</span></p></div>" ) ;
stateB . fromB = "c" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>1b</span></p></div>" ) ;
defs [ 0 ] . resolve ( ) ; // resolve rendering initiated in A
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>1b</span></p></div>" ) ; // TODO: is this what we want?? 2b could be ok too
defs [ 1 ] . resolve ( ) ; // resolve rendering initiated in B
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>2c</span></p></div>" ) ;
} ) ;
test ( "concurrent renderings scenario 3" , async ( ) = > {
const defB = makeDeferred ( ) ;
const defsD = [ makeDeferred ( ) , makeDeferred ( ) ] ;
let index = 0 ;
let stateC ;
class ComponentD extends Component < any , any > {
static template = xml ` <i><t t-esc="props.fromA"/><t t-esc="someValue()"/></i> ` ;
someValue() {
return this . props . fromC ;
}
willUpdateProps() {
return defsD [ index ++ ] ;
}
}
ComponentD . prototype . someValue = jest . fn ( ComponentD . prototype . someValue ) ;
class ComponentC extends Component < any , any > {
static template = xml ` <span><ComponentD fromA="props.fromA" fromC="state.fromC" /></span> ` ;
static components = { ComponentD } ;
state = useState ( { fromC : "c" } ) ;
constructor ( parent , props ) {
super ( parent , props ) ;
stateC = this . state ;
}
}
class ComponentB extends Component < any , any > {
static template = xml ` <p><ComponentC fromA="props.fromA" /></p> ` ;
static components = { ComponentC } ;
willUpdateProps() {
return defB ;
}
}
class ComponentA extends Component < any , any > {
static components = { ComponentB } ;
static template = xml ` <div><ComponentB fromA="state.fromA"/></div> ` ;
state = useState ( { fromA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
stateC . fromC = "d" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
defB . resolve ( ) ; // resolve rendering initiated in A (still blocked in D)
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
defsD [ 0 ] . resolve ( ) ; // resolve rendering initiated in C (should be ignored)
await nextTick ( ) ;
expect ( ComponentD . prototype . someValue ) . toBeCalledTimes ( 1 ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
defsD [ 1 ] . resolve ( ) ; // completely resolve rendering initiated in A
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>2d</i></span></p></div>" ) ;
expect ( ComponentD . prototype . someValue ) . toBeCalledTimes ( 2 ) ;
} ) ;
test ( "concurrent renderings scenario 4" , async ( ) = > {
const defB = makeDeferred ( ) ;
const defsD = [ makeDeferred ( ) , makeDeferred ( ) ] ;
let index = 0 ;
let stateC ;
class ComponentD extends Component < any , any > {
static template = xml ` <i><t t-esc="props.fromA"/><t t-esc="someValue()"/></i> ` ;
someValue() {
return this . props . fromC ;
}
willUpdateProps() {
return defsD [ index ++ ] ;
}
}
ComponentD . prototype . someValue = jest . fn ( ComponentD . prototype . someValue ) ;
class ComponentC extends Component < any , any > {
static template = xml ` <span><ComponentD fromA="props.fromA" fromC="state.fromC" /></span> ` ;
static components = { ComponentD } ;
state = useState ( { fromC : "c" } ) ;
constructor ( parent , props ) {
super ( parent , props ) ;
stateC = this . state ;
}
}
class ComponentB extends Component < any , any > {
static template = xml ` <p><ComponentC fromA="props.fromA" /></p> ` ;
static components = { ComponentC } ;
willUpdateProps() {
return defB ;
}
}
class ComponentA extends Component < any , any > {
static components = { ComponentB } ;
static template = xml ` <div><ComponentB fromA="state.fromA"/></div> ` ;
state = useState ( { fromA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
stateC . fromC = "d" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
defB . resolve ( ) ; // resolve rendering initiated in A (still blocked in D)
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>1c</i></span></p></div>" ) ;
defsD [ 1 ] . resolve ( ) ; // completely resolve rendering initiated in A
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>2d</i></span></p></div>" ) ;
expect ( ComponentD . prototype . someValue ) . toBeCalledTimes ( 2 ) ;
defsD [ 0 ] . resolve ( ) ; // resolve rendering initiated in C (should be ignored)
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span><i>2d</i></span></p></div>" ) ;
expect ( ComponentD . prototype . someValue ) . toBeCalledTimes ( 2 ) ;
} ) ;
test ( "concurrent renderings scenario 5" , async ( ) = > {
const defsB = [ makeDeferred ( ) , makeDeferred ( ) ] ;
let index = 0 ;
class ComponentB extends Component < any , any > {
static template = xml ` <p><t t-esc="someValue()" /></p> ` ;
someValue() {
return this . props . fromA ;
}
willUpdateProps() {
return defsB [ index ++ ] ;
}
}
ComponentB . prototype . someValue = jest . fn ( ComponentB . prototype . someValue ) ;
class ComponentA extends Component < any , any > {
static components = { ComponentB } ;
static template = xml ` <div><ComponentB fromA="state.fromA"/></div> ` ;
state = useState ( { fromA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1</p></div>" ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1</p></div>" ) ;
component . state . fromA = 3 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1</p></div>" ) ;
defsB [ 0 ] . resolve ( ) ; // resolve first re-rendering (should be ignored)
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1</p></div>" ) ;
expect ( ComponentB . prototype . someValue ) . toBeCalledTimes ( 1 ) ;
defsB [ 1 ] . resolve ( ) ; // resolve second re-rendering
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>3</p></div>" ) ;
expect ( ComponentB . prototype . someValue ) . toBeCalledTimes ( 2 ) ;
} ) ;
test ( "concurrent renderings scenario 6" , async ( ) = > {
const defsB = [ makeDeferred ( ) , makeDeferred ( ) ] ;
let index = 0 ;
class ComponentB extends Component < any , any > {
static template = xml ` <p><t t-esc="someValue()" /></p> ` ;
someValue() {
return this . props . fromA ;
}
willUpdateProps() {
return defsB [ index ++ ] ;
}
}
ComponentB . prototype . someValue = jest . fn ( ComponentB . prototype . someValue ) ;
class ComponentA extends Component < any , any > {
static components = { ComponentB } ;
static template = xml ` <div><ComponentB fromA="state.fromA"/></div> ` ;
state = useState ( { fromA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1</p></div>" ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1</p></div>" ) ;
component . state . fromA = 3 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1</p></div>" ) ;
defsB [ 1 ] . resolve ( ) ; // resolve second re-rendering
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>3</p></div>" ) ;
expect ( ComponentB . prototype . someValue ) . toBeCalledTimes ( 2 ) ;
defsB [ 0 ] . resolve ( ) ; // resolve first re-rendering (should be ignored)
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>3</p></div>" ) ;
expect ( ComponentB . prototype . someValue ) . toBeCalledTimes ( 2 ) ;
} ) ;
test ( "concurrent renderings scenario 7" , async ( ) = > {
class ComponentB extends Component < any , any > {
static template = xml ` <p><t t-esc="props.fromA" /><t t-esc="someValue()" /></p> ` ;
state = useState ( { fromB : "b" } ) ;
someValue() {
return this . state . fromB ;
}
async willUpdateProps() {
this . state . fromB = "c" ;
}
}
ComponentB . prototype . someValue = jest . fn ( ComponentB . prototype . someValue ) ;
class ComponentA extends Component < any , any > {
static components = { ComponentB } ;
static template = xml ` <div><ComponentB fromA="state.fromA"/></div> ` ;
state = useState ( { fromA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1b</p></div>" ) ;
expect ( ComponentB . prototype . someValue ) . toBeCalledTimes ( 1 ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>2c</p></div>" ) ;
expect ( ComponentB . prototype . someValue ) . toBeCalledTimes ( 2 ) ;
} ) ;
test ( "concurrent renderings scenario 8" , async ( ) = > {
const def = makeDeferred ( ) ;
let stateB ;
class ComponentB extends Component < any , any > {
static template = xml ` <p><t t-esc="props.fromA" /><t t-esc="state.fromB" /></p> ` ;
state = useState ( { fromB : "b" } ) ;
constructor ( parent , props ) {
super ( parent , props ) ;
stateB = this . state ;
}
async willUpdateProps ( nextProps ) {
return def ;
}
}
class ComponentA extends Component < any , any > {
static components = { ComponentB } ;
static template = xml ` <div><ComponentB fromA="state.fromA"/></div> ` ;
state = useState ( { fromA : 1 } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1b</p></div>" ) ;
component . state . fromA = 2 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1b</p></div>" ) ;
stateB . fromB = "c" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1b</p></div>" ) ;
def . resolve ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>2c</p></div>" ) ;
} ) ;
2019-10-28 14:41:15 +01:00
test ( "concurrent renderings scenario 9" , async ( ) = > {
// Here is the global idea of this scenario:
// A
// / \
// B C
// |
// D
// A state is updated, triggering a whole re-rendering
// B is async, and blocked
// C (and D) are rendered
// C state is updated, producing a re-rendering of C and D
// this last re-rendering of C should be correctly re-mapped to the whole
// re-rendering
const def = makeDeferred ( ) ;
let stateC ;
class ComponentD extends Component < any , any > {
static template = xml ` <span><t t-esc="props.fromA"/><t t-esc="props.fromC"/></span> ` ;
}
class ComponentC extends Component < any , any > {
static template = xml ` <p><ComponentD fromA="props.fromA" fromC="state.fromC" /></p> ` ;
static components = { ComponentD } ;
state = useState ( { fromC : "b1" } ) ;
constructor ( parent , props ) {
super ( parent , props ) ;
stateC = this . state ;
}
}
class ComponentB extends Component < any , any > {
static template = xml ` <b><t t-esc="props.fromA"/></b> ` ;
willUpdateProps() {
return def ;
}
}
class ComponentA extends Component < any , any > {
static template = xml `
<div>
<t t-esc="state.fromA"/>
<ComponentB fromA="state.fromA"/>
<ComponentC fromA="state.fromA"/>
</div> ` ;
static components = { ComponentB , ComponentC } ;
state = useState ( { fromA : "a1" } ) ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-28 14:41:15 +01:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>a1<b>a1</b><p><span>a1b1</span></p></div>" ) ;
component . state . fromA = "a2" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>a1<b>a1</b><p><span>a1b1</span></p></div>" ) ;
stateC . fromC = "b2" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>a1<b>a1</b><p><span>a1b1</span></p></div>" ) ;
def . resolve ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>a2<b>a2</b><p><span>a2b2</span></p></div>" ) ;
} ) ;
2019-11-08 09:16:04 +01:00
test ( "concurrent renderings scenario 10" , async ( ) = > {
// Here is the global idea of this scenario:
// A
// |
// B <- async willUpdateProps
// ----- <- conditional (initialy false)
// |
// C <- async willStart
// Render A and B normally
// Change the condition on B to trigger a re-rendering with C (async willStart)
// Change the state on A to trigger a global re-rendering, which is blocked
// in B (async willUpdateProps)
// Resolve the willStart of C: the first re-rendering has been cancelled by
// the global re-rendering, but handlers waiting for the rendering promise to
// resolve might execute and we don't want them to crash/do anything
const defB = makeDeferred ( ) ;
const defC = makeDeferred ( ) ;
let stateB ;
class ComponentC extends Component < any , any > {
static template = xml ` <span><t t-esc="props.value"/></span> ` ;
willStart() {
return defC ;
}
}
ComponentC . prototype . __render = jest . fn ( ComponentC . prototype . __render ) ;
class ComponentB extends Component < any , any > {
static template = xml ` <p><ComponentC t-if="state.hasChild" value="props.value"/></p> ` ;
state = useState ( { hasChild : false } ) ;
static components = { ComponentC } ;
constructor ( parent , props ) {
super ( parent , props ) ;
stateB = this . state ;
}
willUpdateProps() {
return defB ;
}
}
class ComponentA extends Component < any , any > {
static template = xml ` <div><ComponentB value="state.value"/></div> ` ;
static components = { ComponentB } ;
state = useState ( { value : 1 } ) ;
}
const componentA = new ComponentA ( ) ;
await componentA . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p></p></div>" ) ;
stateB . hasChild = true ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p></p></div>" ) ;
componentA . state . value = 2 ;
defC . resolve ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p></p></div>" ) ;
defB . resolve ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p><span>2</span></p></div>" ) ;
expect ( ComponentC . prototype . __render ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
2019-11-05 14:23:06 +01:00
test ( "concurrent renderings scenario 11" , async ( ) = > {
// This scenario is the following: we have a component being updated (by props),
// and then rendered (render method), but before the willUpdateProps resolves.
// We check that in that case, the return value of the render method is a promise
// that is resolved when the component is completely rendered (so, properly
// remapped to the promise of the ambient rendering)
const def = makeDeferred ( ) ;
let child ;
class Child extends Component < any , any > {
static template = xml ` <span><t t-esc="props.val"/>|<t t-esc="val"/></span> ` ;
val = 3 ;
willUpdateProps() {
child = this ;
return def ;
}
}
class Parent extends Component < any , any > {
static template = xml ` <div><Child val="state.valA"/></div> ` ;
static components = { Child } ;
state = useState ( { valA : 1 } ) ;
}
const parent = new Parent ( ) ;
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>1|3</span></div>" ) ;
parent . state . valA = 2 ;
await nextTick ( ) ;
setTimeout ( ( ) = > {
def . resolve ( ) ;
} , 20 ) ;
child . val = 5 ;
await child . render ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>2|5</span></div>" ) ;
} ) ;
test ( "change state and call manually render: no unnecessary rendering" , async ( ) = > {
class Widget extends Component < any , any > {
static template = xml ` <div><t t-esc="state.val"/></div> ` ;
state = useState ( { val : 1 } ) ;
}
Widget . prototype . __render = jest . fn ( Widget . prototype . __render ) ;
const widget = new Widget ( ) ;
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>1</div>" ) ;
expect ( Widget . prototype . __render ) . toHaveBeenCalledTimes ( 1 ) ;
widget . state . val = 2 ;
await widget . render ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>2</div>" ) ;
expect ( Widget . prototype . __render ) . toHaveBeenCalledTimes ( 2 ) ;
} ) ;
2019-10-25 15:45:56 +02:00
} ) ;
describe ( "widget and observable state" , ( ) = > {
test ( "widget is rerendered when its state is changed" , async ( ) = > {
class TestWidget extends Widget {
static template = xml ` <div><t t-esc="state.drink"/></div> ` ;
state = useState ( { drink : "water" } ) ;
}
2019-10-14 15:09:17 +02:00
const widget = new TestWidget ( ) ;
2019-10-25 15:45:56 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>water</div>" ) ;
widget . state . drink = "beer" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>beer</div>" ) ;
} ) ;
test ( "subcomponents cannot change observable state received from parent" , async ( ) = > {
const consoleError = console . error ;
console . error = jest . fn ( ) ;
env . qweb . addTemplate ( "Parent" , ` <div><Child obj="state.obj"/></div> ` ) ;
class Child extends Widget {
constructor ( parent , props ) {
super ( parent , props ) ;
props . obj . coffee = 2 ;
}
}
class Parent extends Widget {
state = useState ( { obj : { coffee : 1 } } ) ;
static components = { Child } ;
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-26 16:44:12 +02:00
let error ;
try {
await parent . mount ( fixture ) ;
} catch ( e ) {
error = e ;
}
expect ( error ) . toBeDefined ( ) ;
expect ( error . message ) . toBe ( 'Observed state cannot be changed here! (key: "coffee", val: "2")' ) ;
expect ( console . error ) . toBeCalledTimes ( 0 ) ;
2019-07-09 14:33:56 +02:00
console . error = consoleError ;
2019-04-17 16:52:39 +02:00
} ) ;
2019-04-16 22:59:54 +02:00
} ) ;
2019-05-10 15:03:54 +02:00
2019-05-14 23:34:28 +02:00
describe ( "can deduce template from name" , ( ) = > {
test ( "can find template if name of component" , async ( ) = > {
class ABC extends Widget { }
env . qweb . addTemplate ( "ABC" , "<span>Orval</span>" ) ;
2019-10-14 15:09:17 +02:00
const abc = new ABC ( ) ;
2019-05-14 23:34:28 +02:00
await abc . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>Orval</span>" ) ;
} ) ;
test ( "can find template of parent component" , async ( ) = > {
class ABC extends Widget { }
class DEF extends ABC { }
env . qweb . addTemplate ( "ABC" , "<span>Orval</span>" ) ;
2019-10-14 15:09:17 +02:00
const def = new DEF ( ) ;
2019-05-14 23:34:28 +02:00
await def . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>Orval</span>" ) ;
} ) ;
test ( "can find template of parent component, defined by template key" , async ( ) = > {
class ABC extends Widget {
2019-09-12 09:48:17 +02:00
static template = "Achel" ;
2019-05-14 23:34:28 +02:00
}
class DEF extends ABC { }
env . qweb . addTemplate ( "Achel" , "<span>Orval</span>" ) ;
2019-10-14 15:09:17 +02:00
const def = new DEF ( ) ;
2019-05-14 23:34:28 +02:00
await def . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>Orval</span>" ) ;
} ) ;
test ( "templates are found in proper qweb instance" , async ( ) = > {
2019-05-17 15:52:16 +02:00
const env2 = makeTestEnv ( ) ;
2019-05-14 23:34:28 +02:00
env . qweb . addTemplate ( "ABC" , "<span>Rochefort 8</span>" ) ;
env2 . qweb . addTemplate ( "ABC" , "<span>Rochefort 10</span>" ) ;
class ABC extends Widget { }
2019-10-14 15:09:17 +02:00
const abc = new ABC ( ) ;
2019-05-14 23:34:28 +02:00
await abc . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>Rochefort 8</span>" ) ;
abc . destroy ( ) ;
2019-10-31 13:31:10 +01:00
Component . env = env2 ;
2019-10-14 15:09:17 +02:00
const abc2 = new ABC ( ) ;
2019-05-14 23:34:28 +02:00
await abc2 . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>Rochefort 10</span>" ) ;
} ) ;
} ) ;
2019-06-11 15:02:10 +02:00
describe ( "t-slot directive" , ( ) = > {
test ( "can define and call slots" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
2019-06-18 09:24:52 +02:00
<Dialog>
2019-06-11 15:02:10 +02:00
<t t-set="header"><span>header</span></t>
<t t-set="footer"><span>footer</span></t>
2019-06-18 09:24:52 +02:00
</Dialog>
2019-06-11 15:02:10 +02:00
</div>
<div t-name="Dialog">
<div><t t-slot="header"/></div>
<div><t t-slot="footer"/></div>
</div>
</templates>
` ) ;
class Dialog extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-06-11 15:02:10 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-11 15:02:10 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
"<div><div><div><span>header</span></div><div><span>footer</span></div></div></div>"
) ;
expect ( env . qweb . templates . Parent . fn . toString ( ) ) . toMatchSnapshot ( ) ;
expect ( env . qweb . templates . Dialog . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_header" ] . toString ( ) ) . toMatchSnapshot ( ) ;
expect ( QWeb . slots [ "1_footer" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-11 15:02:10 +02:00
} ) ;
test ( "slots are rendered with proper context" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<span class="counter"><t t-esc="state.val"/></span>
2019-06-18 09:24:52 +02:00
<Dialog>
2019-06-11 15:02:10 +02:00
<t t-set="footer"><button t-on-click="doSomething">do something</button></t>
2019-06-18 09:24:52 +02:00
</Dialog>
2019-06-11 15:02:10 +02:00
</div>
<span t-name="Dialog"><t t-slot="footer"/></span>
</templates>
` ) ;
class Dialog extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 0 } ) ;
2019-06-11 15:02:10 +02:00
doSomething() {
this . state . val ++ ;
}
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-11 15:02:10 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
) ;
fixture . querySelector ( "button" ) ! . click ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_footer" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-11 15:02:10 +02:00
} ) ;
2019-07-05 09:45:55 +02:00
test ( "slots are rendered with proper context, part 2" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<a t-name="Link" t-att-href="props.to">
<t t-slot="default"/>
</a>
<div t-name="App">
<u><li t-foreach="state.users" t-as="user" t-key="user.id">
<Link to="'/user/' + user.id">User <t t-esc="user.name"/></Link>
</li></u>
</div>
</templates>
` ) ;
class Link extends Widget { }
class App extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { users : [ { id : 1 , name : "Aaron" } , { id : 2 , name : "David" } ] } ) ;
2019-09-10 22:37:08 +02:00
static components = { Link } ;
2019-07-05 09:45:55 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-05 09:45:55 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
2019-07-08 16:41:29 +02:00
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User David</a></li></u></div>'
2019-07-05 09:45:55 +02:00
) ;
expect ( env . qweb . templates . Link . fn . toString ( ) ) . toMatchSnapshot ( ) ;
expect ( env . qweb . templates . App . fn . toString ( ) ) . toMatchSnapshot ( ) ;
// test updateprops here
app . state . users [ 1 ] . name = "Mathieu" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe (
2019-07-08 16:41:29 +02:00
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
2019-07-05 09:45:55 +02:00
) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_default" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-07-05 09:45:55 +02:00
} ) ;
test ( "slots are rendered with proper context, part 3" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<a t-name="Link" t-att-href="props.to">
<t t-slot="default"/>
</a>
<div t-name="App">
<u><li t-foreach="state.users" t-as="user" t-key="user.id" >
<t t-set="userdescr" t-value="'User ' + user.name"/>
<Link to="'/user/' + user.id"><t t-esc="userdescr"/></Link>
</li></u>
</div>
</templates>
` ) ;
class Link extends Widget { }
class App extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { users : [ { id : 1 , name : "Aaron" } , { id : 2 , name : "David" } ] } ) ;
2019-09-10 22:37:08 +02:00
static components = { Link } ;
2019-07-05 09:45:55 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-05 09:45:55 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
2019-07-08 16:41:29 +02:00
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User David</a></li></u></div>'
2019-07-05 09:45:55 +02:00
) ;
expect ( env . qweb . templates . Link . fn . toString ( ) ) . toMatchSnapshot ( ) ;
expect ( env . qweb . templates . App . fn . toString ( ) ) . toMatchSnapshot ( ) ;
// test updateprops here
app . state . users [ 1 ] . name = "Mathieu" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe (
2019-07-08 16:41:29 +02:00
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
2019-07-05 09:45:55 +02:00
) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_default" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-07-08 16:41:29 +02:00
} ) ;
test ( "slots are rendered with proper context, part 4" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<a t-name="Link" t-att-href="props.to">
<t t-slot="default"/>
</a>
<div t-name="App">
<t t-set="userdescr" t-value="'User ' + state.user.name"/>
<Link to="'/user/' + state.user.id"><t t-esc="userdescr"/></Link>
</div>
</templates>
` ) ;
class Link extends Widget { }
2019-07-05 09:45:55 +02:00
2019-07-08 16:41:29 +02:00
class App extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { user : { id : 1 , name : "Aaron" } } ) ;
2019-09-10 22:37:08 +02:00
static components = { Link } ;
2019-07-08 16:41:29 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-08 16:41:29 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( '<div><a href="/user/1">User Aaron</a></div>' ) ;
expect ( env . qweb . templates . App . fn . toString ( ) ) . toMatchSnapshot ( ) ;
// test updateprops here
app . state . user . name = "David" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( '<div><a href="/user/1">User David</a></div>' ) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_default" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-07-05 09:45:55 +02:00
} ) ;
2019-06-11 15:02:10 +02:00
test ( "refs are properly bound in slots" , async ( ) = > {
2019-09-26 21:56:01 +02:00
class Dialog extends Widget {
static template = xml ` <span><t t-slot="footer"/></span> ` ;
}
class Parent extends Widget {
static template = xml `
<div>
2019-06-11 15:02:10 +02:00
<span class="counter"><t t-esc="state.val"/></span>
2019-06-18 09:24:52 +02:00
<Dialog>
2019-06-11 15:02:10 +02:00
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
2019-06-18 09:24:52 +02:00
</Dialog>
2019-06-11 15:02:10 +02:00
</div>
2019-09-26 21:56:01 +02:00
` ;
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 0 } ) ;
2019-09-26 21:56:01 +02:00
button = useRef ( "myButton" ) ;
2019-06-11 15:02:10 +02:00
doSomething() {
this . state . val ++ ;
}
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-11 15:02:10 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
) ;
2019-09-26 21:56:01 +02:00
parent . button . el ! . click ( ) ;
2019-06-11 15:02:10 +02:00
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_footer" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-11 15:02:10 +02:00
} ) ;
2019-06-23 09:03:18 +02:00
test ( "content is the default slot" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<Dialog>
<span>sts rocks</span>
</Dialog>
</div>
<div t-name="Dialog"><t t-slot="default"/></div>
</templates>
` ) ;
class Dialog extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-06-23 09:03:18 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-23 09:03:18 +02:00
await parent . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div><span>sts rocks</span></div></div>" ) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_default" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-23 09:03:18 +02:00
} ) ;
2019-06-24 22:59:13 +02:00
test ( "default slot work with text nodes" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<Dialog>sts rocks</Dialog>
</div>
<div t-name="Dialog"><t t-slot="default"/></div>
</templates>
` ) ;
class Dialog extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-06-24 22:59:13 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-24 22:59:13 +02:00
await parent . mount ( fixture ) ;
2019-06-25 14:24:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div>sts rocks</div></div>" ) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_default" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-24 22:59:13 +02:00
} ) ;
2019-06-23 09:03:18 +02:00
test ( "multiple roots are allowed in a named slot" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<Dialog>
<t t-set="content">
<span>sts</span>
<span>rocks</span>
</t>
</Dialog>
</div>
<div t-name="Dialog"><t t-slot="content"/></div>
</templates>
` ) ;
class Dialog extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-06-23 09:03:18 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-23 09:03:18 +02:00
await parent . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div><span>sts</span><span>rocks</span></div></div>" ) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_content" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-23 09:03:18 +02:00
} ) ;
test ( "multiple roots are allowed in a default slot" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<Dialog>
<span>sts</span>
<span>rocks</span>
</Dialog>
</div>
<div t-name="Dialog"><t t-slot="default"/></div>
</templates>
` ) ;
class Dialog extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-06-23 09:03:18 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-23 09:03:18 +02:00
await parent . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><div><span>sts</span><span>rocks</span></div></div>" ) ;
2019-09-12 09:45:32 +02:00
expect ( QWeb . slots [ "1_default" ] . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-23 09:03:18 +02:00
} ) ;
2019-06-24 12:57:15 +02:00
test ( "missing slots are ignored" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<Dialog/>
</div>
<span t-name="Dialog">
<t t-slot="default"/>
<span>some content</span>
<t t-slot="footer"/>
</span>
</templates>
` ) ;
class Dialog extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-06-24 12:57:15 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-24 12:57:15 +02:00
await parent . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span><span>some content</span></span></div>" ) ;
2019-06-24 12:57:15 +02:00
} ) ;
2019-06-24 15:28:01 +02:00
test ( "t-debug on a t-set (defining a slot)" , async ( ) = > {
const consoleLog = console . log ;
console . log = jest . fn ( ) ;
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<Dialog><t t-set="content" t-debug="1">abc</t></Dialog>
</div>
<span t-name="Dialog">
<t t-slot="content"/>
</span>
</templates>
` ) ;
class Dialog extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Dialog } ;
2019-06-24 15:28:01 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-06-24 15:28:01 +02:00
await parent . mount ( fixture ) ;
expect ( console . log ) . toHaveBeenCalledTimes ( 0 ) ;
console . log = consoleLog ;
} ) ;
2019-07-09 16:38:30 +02:00
test ( "slot preserves properly parented relationship" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="Parent">
<Child>
<GrandChild/>
</Child>
</div>
<div t-name="Child"><t t-slot="default"/></div>
<div t-name="GrandChild">Grand Child</div>
</templates>
` ) ;
class Child extends Widget { }
class GrandChild extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Child , GrandChild } ;
2019-07-09 16:38:30 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-07-09 16:38:30 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div><div>Grand Child</div></div></div>" ) ;
const parentChildren = children ( parent ) ;
expect ( parentChildren . length ) . toBe ( 1 ) ;
expect ( parentChildren [ 0 ] ) . toBeInstanceOf ( Child ) ;
const childrenChildren = children ( parentChildren [ 0 ] ) ;
expect ( childrenChildren . length ) . toBe ( 1 ) ;
expect ( childrenChildren [ 0 ] ) . toBeInstanceOf ( GrandChild ) ;
} ) ;
2019-07-11 14:00:15 +02:00
test ( "slot are properly rendered if inner props are changed" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="SomeComponent">
SC:<t t-esc="props.val"/>
</div>
<div t-name="GenericComponent">
<t t-slot="default" />
</div>
<div t-name="App">
<button t-on-click="inc">Inc[<t t-esc="state.val"/>]</button>
<GenericComponent>
<SomeComponent val="state.val"/>
</GenericComponent>
</div>
</templates>
` ) ;
class SomeComponent extends Widget { }
class GenericComponent extends Widget { }
class App extends Widget {
2019-09-10 22:37:08 +02:00
static components = { GenericComponent , SomeComponent } ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 4 } ) ;
2019-07-11 14:00:15 +02:00
inc() {
this . state . val ++ ;
}
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-11 14:00:15 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><button>Inc[4]</button><div><div> SC:4</div></div></div>" ) ;
2019-07-09 14:33:56 +02:00
( < any > fixture . querySelector ( "button" ) ) . click ( ) ;
2019-07-11 14:00:15 +02:00
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><button>Inc[5]</button><div><div> SC:5</div></div></div>" ) ;
} ) ;
2019-09-22 22:24:24 +02:00
test ( "slots and wrapper components" , async ( ) = > {
2019-09-24 14:06:53 +02:00
class Link extends Component < any , any > {
2019-09-22 22:24:24 +02:00
static template = xml `
<a href="abc">
<t t-slot="default"/>
</a> ` ;
}
2019-09-24 14:06:53 +02:00
class A extends Component < any , any > {
2019-09-22 22:24:24 +02:00
static template = xml ` <Link>hey</Link> ` ;
static components = { Link : Link } ;
}
2019-10-14 15:09:17 +02:00
const a = new A ( ) ;
2019-09-22 22:24:24 +02:00
await a . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( ` <a href="abc">hey</a> ` ) ;
} ) ;
2019-10-16 17:19:59 +02:00
test ( "template can just return a slot" , async ( ) = > {
class Child extends Widget {
static template = xml ` <span><t t-esc="props.value"/></span> ` ;
}
class SlotComponent extends Widget {
static template = xml ` <t t-slot="default"/> ` ;
}
class Parent extends Widget {
static template = xml `
<div>
<SlotComponent><Child value="state.value"/></SlotComponent>
</div> ` ;
static components = { SlotComponent , Child } ;
state = useState ( { value : 3 } ) ;
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-16 17:19:59 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>3</span></div>" ) ;
expect ( QWeb . TEMPLATES [ SlotComponent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
parent . state . value = 5 ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>5</span></div>" ) ;
} ) ;
2019-06-11 15:02:10 +02:00
} ) ;
2019-06-13 16:27:12 +02:00
describe ( "t-model directive" , ( ) = > {
test ( "basic use, on an input" , async ( ) = > {
class SomeComponent extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml `
<div>
<input t-model="state.text"/>
<span><t t-esc="state.text"/></span>
</div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { text : "" } ) ;
2019-06-13 16:27:12 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-06-13 16:27:12 +02:00
await comp . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span></span></div>" ) ;
const input = fixture . querySelector ( "input" ) ! ;
await editInput ( input , "test" ) ;
expect ( comp . state . text ) . toBe ( "test" ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span>test</span></div>" ) ;
2019-10-25 15:45:56 +02:00
expect ( env . qweb . templates [ SomeComponent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-13 16:27:12 +02:00
} ) ;
2019-09-24 14:06:53 +02:00
test ( "basic use, on another key in component" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="SomeComponent">
<input t-model="some.text"/>
<span><t t-esc="some.text"/></span>
</div>
</templates> ` ) ;
class SomeComponent extends Widget {
some = useState ( { text : "" } ) ;
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-09-24 14:06:53 +02:00
await comp . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span></span></div>" ) ;
const input = fixture . querySelector ( "input" ) ! ;
await editInput ( input , "test" ) ;
expect ( comp . some . text ) . toBe ( "test" ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span>test</span></div>" ) ;
expect ( env . qweb . templates . SomeComponent . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
2019-06-13 16:27:12 +02:00
test ( "on an input, type=checkbox" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="SomeComponent">
2019-09-24 14:06:53 +02:00
<input type="checkbox" t-model="state.flag"/>
2019-06-13 16:27:12 +02:00
<span>
<t t-if="state.flag">yes</t>
<t t-else="1">no</t>
</span>
</div>
</templates> ` ) ;
class SomeComponent extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { flag : false } ) ;
2019-06-13 16:27:12 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-06-13 16:27:12 +02:00
await comp . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( '<div><input type="checkbox"><span>no</span></div>' ) ;
2019-06-13 16:27:12 +02:00
let input = fixture . querySelector ( "input" ) ! ;
input . click ( ) ;
await nextTick ( ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( '<div><input type="checkbox"><span>yes</span></div>' ) ;
2019-06-13 16:27:12 +02:00
expect ( comp . state . flag ) . toBe ( true ) ;
expect ( env . qweb . templates . SomeComponent . fn . toString ( ) ) . toMatchSnapshot ( ) ;
input . click ( ) ;
await nextTick ( ) ;
expect ( comp . state . flag ) . toBe ( false ) ;
} ) ;
test ( "on an textarea" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="SomeComponent">
2019-09-24 14:06:53 +02:00
<textarea t-model="state.text"/>
2019-06-13 16:27:12 +02:00
<span><t t-esc="state.text"/></span>
</div>
</templates> ` ) ;
class SomeComponent extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { text : "" } ) ;
2019-06-13 16:27:12 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-06-13 16:27:12 +02:00
await comp . mount ( fixture ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><textarea></textarea><span></span></div>" ) ;
2019-06-13 16:27:12 +02:00
const textarea = fixture . querySelector ( "textarea" ) ! ;
await editInput ( textarea , "test" ) ;
expect ( comp . state . text ) . toBe ( "test" ) ;
2019-06-28 10:32:03 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><textarea></textarea><span>test</span></div>" ) ;
2019-06-13 16:27:12 +02:00
} ) ;
test ( "on an input type=radio" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="SomeComponent">
2019-09-24 14:06:53 +02:00
<input type="radio" id="one" value="One" t-model="state.choice"/>
<input type="radio" id="two" value="Two" t-model="state.choice"/>
2019-06-13 16:27:12 +02:00
<span>Choice: <t t-esc="state.choice"/></span>
</div>
</templates> ` ) ;
class SomeComponent extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { choice : "" } ) ;
2019-06-13 16:27:12 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-06-13 16:27:12 +02:00
await comp . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: </span></div>'
) ;
const firstInput = fixture . querySelector ( "input" ) ! ;
firstInput . click ( ) ;
await nextTick ( ) ;
expect ( comp . state . choice ) . toBe ( "One" ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: One</span></div>'
) ;
const secondInput = fixture . querySelectorAll ( "input" ) [ 1 ] ;
secondInput . click ( ) ;
await nextTick ( ) ;
expect ( comp . state . choice ) . toBe ( "Two" ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: Two</span></div>'
) ;
expect ( env . qweb . templates . SomeComponent . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "on a select" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<div t-name="SomeComponent">
2019-09-24 14:06:53 +02:00
<select t-model="state.color">
2019-06-13 16:27:12 +02:00
<option value="">Please select one</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
<span>Choice: <t t-esc="state.color"/></span>
</div>
</templates> ` ) ;
class SomeComponent extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { color : "" } ) ;
2019-06-13 16:27:12 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-06-13 16:27:12 +02:00
await comp . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><select><option value="">Please select one</option><option value="red">Red</option><option value="blue">Blue</option></select><span>Choice: </span></div>'
) ;
const select = fixture . querySelector ( "select" ) ! ;
select . value = "red" ;
select . dispatchEvent ( new Event ( "change" ) ) ;
await nextTick ( ) ;
expect ( comp . state . color ) . toBe ( "red" ) ;
expect ( fixture . innerHTML ) . toBe (
'<div><select><option value="">Please select one</option><option value="red">Red</option><option value="blue">Blue</option></select><span>Choice: red</span></div>'
) ;
expect ( env . qweb . templates . SomeComponent . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
2019-09-04 22:43:17 +02:00
test ( "on a select, initial state" , async ( ) = > {
class SomeComponent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
2019-09-24 14:06:53 +02:00
<select t-model="state.color">
2019-09-12 09:45:32 +02:00
<option value="">Please select one</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
</div>
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { color : "red" } ) ;
2019-09-04 22:43:17 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-09-04 22:43:17 +02:00
await comp . mount ( fixture ) ;
const select = fixture . querySelector ( "select" ) ! ;
expect ( select . value ) . toBe ( "red" ) ;
} ) ;
2019-09-05 08:45:55 +02:00
test ( "on a sub state key" , async ( ) = > {
class SomeComponent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
2019-09-24 14:06:53 +02:00
<input t-model="state.something.text"/>
2019-09-12 09:45:32 +02:00
<span><t t-esc="state.something.text"/></span>
</div>
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { something : { text : "" } } ) ;
2019-09-05 08:45:55 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-09-05 08:45:55 +02:00
await comp . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span></span></div>" ) ;
const input = fixture . querySelector ( "input" ) ! ;
await editInput ( input , "test" ) ;
expect ( comp . state . something . text ) . toBe ( "test" ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span>test</span></div>" ) ;
2019-09-12 09:45:32 +02:00
expect ( env . qweb . templates [ SomeComponent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-09-05 08:45:55 +02:00
} ) ;
2019-06-13 16:27:12 +02:00
test ( ".lazy modifier" , async ( ) = > {
2019-09-12 09:45:32 +02:00
class SomeComponent extends Widget {
static template = xml `
<div>
2019-09-24 14:06:53 +02:00
<input t-model.lazy="state.text"/>
2019-06-13 16:27:12 +02:00
<span><t t-esc="state.text"/></span>
</div>
2019-09-12 09:45:32 +02:00
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { text : "" } ) ;
2019-06-13 16:27:12 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-06-13 16:27:12 +02:00
await comp . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span></span></div>" ) ;
const input = fixture . querySelector ( "input" ) ! ;
input . value = "test" ;
input . dispatchEvent ( new Event ( "input" ) ) ;
await nextTick ( ) ;
expect ( comp . state . text ) . toBe ( "" ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span></span></div>" ) ;
input . dispatchEvent ( new Event ( "change" ) ) ;
await nextTick ( ) ;
expect ( comp . state . text ) . toBe ( "test" ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span>test</span></div>" ) ;
2019-09-12 09:45:32 +02:00
expect ( env . qweb . templates [ SomeComponent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-06-13 16:27:12 +02:00
} ) ;
test ( ".trim modifier" , async ( ) = > {
2019-09-12 09:45:32 +02:00
class SomeComponent extends Widget {
static template = xml `
2019-06-13 16:27:12 +02:00
<div t-name="SomeComponent">
2019-09-24 14:06:53 +02:00
<input t-model.trim="state.text"/>
2019-09-12 09:45:32 +02:00
<span><t t-esc="state.text"/></span>
2019-06-13 16:27:12 +02:00
</div>
2019-09-12 09:45:32 +02:00
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { text : "" } ) ;
2019-06-13 16:27:12 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-06-13 16:27:12 +02:00
await comp . mount ( fixture ) ;
const input = fixture . querySelector ( "input" ) ! ;
await editInput ( input , " test " ) ;
expect ( comp . state . text ) . toBe ( "test" ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span>test</span></div>" ) ;
} ) ;
test ( ".number modifier" , async ( ) = > {
class SomeComponent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
2019-09-24 14:06:53 +02:00
<input t-model.number="state.number"/>
2019-09-12 09:45:32 +02:00
<span><t t-esc="state.number"/></span>
</div>
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { number : 0 } ) ;
2019-06-13 16:27:12 +02:00
}
2019-10-14 15:09:17 +02:00
const comp = new SomeComponent ( ) ;
2019-06-13 16:27:12 +02:00
await comp . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span>0</span></div>" ) ;
const input = fixture . querySelector ( "input" ) ! ;
await editInput ( input , "13" ) ;
expect ( comp . state . number ) . toBe ( 13 ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span>13</span></div>" ) ;
await editInput ( input , "invalid" ) ;
expect ( comp . state . number ) . toBe ( "invalid" ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><input><span>invalid</span></div>" ) ;
} ) ;
} ) ;
2019-06-22 14:44:09 +02:00
describe ( "environment and plugins" , ( ) = > {
// some source of external events
let bus = new EventBus ( ) ;
// definition of a plugin
const somePlugin = env = > {
env . someFlag = true ;
bus . on ( "some-event" , null , ( ) = > {
env . someFlag = ! env . someFlag ;
2019-07-30 14:36:51 +02:00
env . qweb . forceUpdate ( ) ;
2019-06-22 14:44:09 +02:00
} ) ;
} ;
test ( "plugin works as expected" , async ( ) = > {
somePlugin ( env ) ;
2019-09-12 09:45:32 +02:00
class App extends Widget {
2019-09-19 11:42:00 +02:00
static template = xml `
2019-09-12 09:45:32 +02:00
<div>
2019-06-22 14:44:09 +02:00
<t t-if="env.someFlag">Red</t>
<t t-else="1">Blue</t>
</div>
2019-09-12 09:45:32 +02:00
` ;
}
2019-06-22 14:44:09 +02:00
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-06-22 14:44:09 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>Red</div>" ) ;
bus . trigger ( "some-event" ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>Blue</div>" ) ;
} ) ;
2019-10-31 13:31:10 +01:00
test ( "can define specific env for root components" , async ( ) = > {
class App1 extends Component < any , any > {
static template = xml ` <span></span> ` ;
}
App1 . env = { test : 1 } ;
class App2 extends Component < any , any > {
static template = xml ` <span></span> ` ;
}
App2 . env = { test : 2 } ;
class App1B extends App1 { }
class App2B extends App2 { }
App2B . env = { test : 3 } ;
const app1 = new App1 ( ) ;
const app2 = new App2 ( ) ;
const app1B = new App1B ( ) ;
const app2B = new App2B ( ) ;
expect ( app1 . env . qweb ) . toBeDefined ( ) ;
expect ( app1 . env . test ) . toBe ( 1 ) ;
expect ( app2 . env . test ) . toBe ( 2 ) ;
expect ( app1B . env . test ) . toBe ( 1 ) ;
expect ( app2B . env . test ) . toBe ( 3 ) ;
} ) ;
2019-06-22 14:44:09 +02:00
} ) ;
2019-07-09 14:33:56 +02:00
describe ( "component error handling (catchError)" , ( ) = > {
/**
* This test suite requires often to wait for 3 ticks. Here is why:
* - First tick is to let the app render and crash.
* - When we crash, we call the catchError handler in a setTimeout (because we
* need to wait for the previous rendering to be completely stopped). So, we
* need to wait for the second tick.
* - Then, when the handler changes the state, we need to wait for the interface
* to be rerendered.
* */
test ( "can catch an error in a component render function" , async ( ) = > {
const consoleError = console . error ;
console . error = jest . fn ( ) ;
const handler = jest . fn ( ) ;
env . qweb . on ( "error" , null , handler ) ;
2019-10-25 15:45:56 +02:00
class ErrorComponent extends Widget {
static template = xml ` <div>hey<t t-esc="props.flag and state.this.will.crash"/></div> ` ;
}
2019-07-09 14:33:56 +02:00
class ErrorBoundary extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml `
<div>
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { error : false } ) ;
2019-07-09 14:33:56 +02:00
catchError() {
this . state . error = true ;
}
}
class App extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml `
<div>
<ErrorBoundary><ErrorComponent flag="state.flag"/></ErrorBoundary>
</div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : false } ) ;
2019-09-10 22:37:08 +02:00
static components = { ErrorBoundary , ErrorComponent } ;
2019-07-09 14:33:56 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-09 14:33:56 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div><div>hey</div></div></div>" ) ;
app . state . flag = true ;
await nextTick ( ) ;
await nextTick ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>Error handled</div></div>" ) ;
expect ( console . error ) . toBeCalledTimes ( 1 ) ;
console . error = consoleError ;
expect ( handler ) . toBeCalledTimes ( 1 ) ;
} ) ;
test ( "no component catching error lead to full app destruction" , async ( ) = > {
2019-10-26 20:59:51 +02:00
expect . assertions ( 6 ) ;
2019-07-09 14:33:56 +02:00
const handler = jest . fn ( ) ;
env . qweb . on ( "error" , null , handler ) ;
const consoleError = console . error ;
console . error = jest . fn ( ) ;
2019-10-26 16:44:12 +02:00
class ErrorComponent extends Widget {
static template = xml ` <div>hey<t t-esc="props.flag and state.this.will.crash"/></div> ` ;
}
2019-07-09 14:33:56 +02:00
class App extends Widget {
2019-10-26 16:44:12 +02:00
static template = xml ` <div><ErrorComponent flag="state.flag"/></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { ErrorComponent } ;
2019-10-26 16:44:12 +02:00
state = useState ( { flag : false } ) ;
async render() {
try {
await super . render ( ) ;
} catch ( e ) {
expect ( e . message ) . toBe ( "Cannot read property 'this' of undefined" ) ;
}
}
2019-07-09 14:33:56 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-09 14:33:56 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>hey</div></div>" ) ;
app . state . flag = true ;
await nextTick ( ) ;
await nextTick ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "" ) ;
2019-10-26 16:44:12 +02:00
expect ( console . error ) . toBeCalledTimes ( 0 ) ;
2019-07-09 14:33:56 +02:00
console . error = consoleError ;
expect ( app . __owl__ . isDestroyed ) . toBe ( true ) ;
expect ( handler ) . toBeCalledTimes ( 1 ) ;
} ) ;
test ( "can catch an error in the initial call of a component render function" , async ( ) = > {
const handler = jest . fn ( ) ;
env . qweb . on ( "error" , null , handler ) ;
const consoleError = console . error ;
console . error = jest . fn ( ) ;
2019-10-25 15:45:56 +02:00
class ErrorComponent extends Component < any , any > {
static template = xml ` <div>hey<t t-esc="state.this.will.crash"/></div> ` ;
}
class ErrorBoundary extends Component < any , any > {
static template = xml `
<div>
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { error : false } ) ;
2019-07-09 14:33:56 +02:00
catchError() {
this . state . error = true ;
}
}
2019-10-25 15:45:56 +02:00
class App extends Component < any , any > {
static template = xml `
<div>
<ErrorBoundary><ErrorComponent /></ErrorBoundary>
</div> ` ;
2019-09-10 22:37:08 +02:00
static components = { ErrorBoundary , ErrorComponent } ;
2019-07-09 14:33:56 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-09 14:33:56 +02:00
await app . mount ( fixture ) ;
await nextTick ( ) ;
await nextTick ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>Error handled</div></div>" ) ;
expect ( console . error ) . toBeCalledTimes ( 1 ) ;
console . error = consoleError ;
expect ( handler ) . toBeCalledTimes ( 1 ) ;
} ) ;
test ( "can catch an error in the constructor call of a component render function" , async ( ) = > {
const handler = jest . fn ( ) ;
env . qweb . on ( "error" , null , handler ) ;
const consoleError = console . error ;
console . error = jest . fn ( ) ;
env . qweb . addTemplates ( `
<templates>
<div t-name="ErrorBoundary">
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div>
<div t-name="ErrorComponent">Some text</div>
<div t-name="App">
<ErrorBoundary><ErrorComponent /></ErrorBoundary>
</div>
</templates> ` ) ;
class ErrorComponent extends Widget {
constructor ( parent ) {
super ( parent ) ;
throw new Error ( "NOOOOO" ) ;
}
}
class ErrorBoundary extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { error : false } ) ;
2019-07-09 14:33:56 +02:00
catchError() {
this . state . error = true ;
}
}
class App extends Widget {
2019-09-10 22:37:08 +02:00
static components = { ErrorBoundary , ErrorComponent } ;
2019-07-09 14:33:56 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-09 14:33:56 +02:00
await app . mount ( fixture ) ;
await nextTick ( ) ;
await nextTick ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>Error handled</div></div>" ) ;
expect ( console . error ) . toBeCalledTimes ( 1 ) ;
console . error = consoleError ;
expect ( handler ) . toBeCalledTimes ( 1 ) ;
} ) ;
test ( "can catch an error in the willStart call" , async ( ) = > {
const consoleError = console . error ;
console . error = jest . fn ( ) ;
class ErrorComponent extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <div t-name="ErrorComponent">Some text</div> ` ;
2019-07-09 14:33:56 +02:00
async willStart() {
// we wait a little bit to be in a different stack frame
await nextTick ( ) ;
throw new Error ( "NOOOOO" ) ;
}
}
class ErrorBoundary extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml `
<div>
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { error : false } ) ;
2019-07-09 14:33:56 +02:00
catchError() {
this . state . error = true ;
}
}
class App extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <div><ErrorBoundary><ErrorComponent /></ErrorBoundary></div> ` ;
2019-09-10 22:37:08 +02:00
static components = { ErrorBoundary , ErrorComponent } ;
2019-07-09 14:33:56 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-09 14:33:56 +02:00
await app . mount ( fixture ) ;
await nextTick ( ) ;
await nextTick ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>Error handled</div></div>" ) ;
expect ( console . error ) . toBeCalledTimes ( 1 ) ;
console . error = consoleError ;
} ) ;
2019-10-25 15:45:56 +02:00
test . skip ( "can catch an error in the mounted call" , async ( ) = > {
// we do not catch error in mounted anymore
2019-07-09 14:33:56 +02:00
console . error = jest . fn ( ) ;
env . qweb . addTemplates ( `
<templates>
<div t-name="ErrorBoundary">
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div>
<div t-name="ErrorComponent">Some text</div>
<div t-name="App">
<ErrorBoundary><ErrorComponent /></ErrorBoundary>
</div>
</templates> ` ) ;
class ErrorComponent extends Widget {
mounted() {
throw new Error ( "NOOOOO" ) ;
}
}
class ErrorBoundary extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { error : false } ) ;
2019-07-09 14:33:56 +02:00
catchError() {
this . state . error = true ;
}
}
class App extends Widget {
2019-09-10 22:37:08 +02:00
static components = { ErrorBoundary , ErrorComponent } ;
2019-07-09 14:33:56 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-09 14:33:56 +02:00
await app . mount ( fixture ) ;
await nextTick ( ) ;
await nextTick ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><div>Error handled</div></div>" ) ;
} ) ;
2019-10-25 15:45:56 +02:00
test . skip ( "can catch an error in the willPatch call" , async ( ) = > {
// we do not catch error in willPatch anymore
2019-10-26 20:59:51 +02:00
const consoleError = console . error ;
console . error = jest . fn ( ) ;
2019-07-09 14:33:56 +02:00
class ErrorComponent extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml ` <div><t t-esc="props.message"/></div> ` ;
2019-07-09 14:33:56 +02:00
willPatch() {
throw new Error ( "NOOOOO" ) ;
}
}
class ErrorBoundary extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml `
<div>
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { error : false } ) ;
2019-07-09 14:33:56 +02:00
catchError() {
this . state . error = true ;
}
}
class App extends Widget {
2019-10-25 15:45:56 +02:00
static template = xml `
<div>
2019-10-26 20:59:51 +02:00
<span><t t-esc="state.message"/></span>
2019-10-25 15:45:56 +02:00
<ErrorBoundary><ErrorComponent message="state.message" /></ErrorBoundary>
</div> ` ;
2019-09-24 14:06:53 +02:00
state = useState ( { message : "abc" } ) ;
2019-09-10 22:37:08 +02:00
static components = { ErrorBoundary , ErrorComponent } ;
2019-07-09 14:33:56 +02:00
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-07-09 14:33:56 +02:00
await app . mount ( fixture ) ;
2019-10-26 20:59:51 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>abc</span><div><div>abc</div></div></div>" ) ;
2019-07-09 14:33:56 +02:00
app . state . message = "def" ;
await nextTick ( ) ;
await nextTick ( ) ;
await nextTick ( ) ;
2019-10-26 20:59:51 +02:00
expect ( fixture . innerHTML ) . toBe ( "<div><span>def</span><div>Error handled</div></div>" ) ;
expect ( console . error ) . toHaveBeenCalledTimes ( 1 ) ;
console . error = consoleError ;
2019-07-09 14:33:56 +02:00
} ) ;
2019-10-26 16:44:12 +02:00
test ( "a rendering error will reject the mount promise" , async ( ) = > {
const consoleError = console . error ;
console . error = jest . fn ( ( ) = > { } ) ;
// we do not catch error in willPatch anymore
class App extends Component < any , any > {
static template = xml ` <div><t t-esc="this.will.crash"/></div> ` ;
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-10-26 16:44:12 +02:00
let error ;
try {
await app . mount ( fixture ) ;
} catch ( e ) {
error = e ;
}
expect ( error ) . toBeDefined ( ) ;
expect ( error . message ) . toBe ( "Cannot read property 'crash' of undefined" ) ;
expect ( console . error ) . toBeCalledTimes ( 0 ) ;
console . error = consoleError ;
} ) ;
test ( "a rendering error in a sub component will reject the mount promise" , async ( ) = > {
const consoleError = console . error ;
console . error = jest . fn ( ( ) = > { } ) ;
// we do not catch error in willPatch anymore
class Child extends Component < any , any > {
static template = xml ` <div><t t-esc="this.will.crash"/></div> ` ;
}
class App extends Component < any , any > {
static template = xml ` <div><Child/></div> ` ;
static components = { Child } ;
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-10-26 16:44:12 +02:00
let error ;
try {
await app . mount ( fixture ) ;
} catch ( e ) {
error = e ;
}
expect ( error ) . toBeDefined ( ) ;
expect ( error . message ) . toBe ( "Cannot read property 'crash' of undefined" ) ;
expect ( console . error ) . toBeCalledTimes ( 0 ) ;
console . error = consoleError ;
} ) ;
test ( "a rendering error will reject the render promise" , async ( ) = > {
const consoleError = console . error ;
console . error = jest . fn ( ( ) = > { } ) ;
// we do not catch error in willPatch anymore
class App extends Component < any , any > {
static template = xml ` <div><t t-if="flag" t-esc="this.will.crash"/></div> ` ;
flag = false ;
}
2019-10-14 15:09:17 +02:00
const app = new App ( ) ;
2019-10-26 16:44:12 +02:00
await app . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div></div>" ) ;
app . flag = true ;
let error ;
try {
await app . render ( ) ;
} catch ( e ) {
error = e ;
}
expect ( error ) . toBeDefined ( ) ;
expect ( error . message ) . toBe ( "Cannot read property 'crash' of undefined" ) ;
expect ( console . error ) . toBeCalledTimes ( 0 ) ;
console . error = consoleError ;
} ) ;
2019-10-30 11:35:28 +01:00
test ( "a rendering error will reject the render promise (with sub components)" , async ( ) = > {
class Child extends Component < any , any > {
static template = xml ` <span></span> ` ;
}
class Parent extends Component < any , any > {
static template = xml ` <div><Child/><t t-esc="x.y"/></div> ` ;
static components = { Child } ;
}
let error ;
try {
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-10-30 11:35:28 +01:00
await parent . mount ( fixture ) ;
} catch ( e ) {
error = e ;
}
expect ( error ) . toBeDefined ( ) ;
expect ( error . message ) . toBe ( "Cannot read property 'y' of undefined" ) ;
} ) ;
2019-07-09 14:33:56 +02:00
} ) ;
2019-07-11 16:19:59 +02:00
describe ( "top level sub widgets" , ( ) = > {
test ( "basic use" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<t t-name="Parent">
<Child p="1"/>
</t>
<span t-name="Child">child<t t-esc="props.p"/></span>
</templates> ` ) ;
class Child extends Widget { }
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-07-11 16:19:59 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-07-11 16:19:59 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>child1</span>" ) ;
expect ( env . qweb . templates . Parent . fn . toString ( ) ) . toMatchSnapshot ( ) ;
} ) ;
test ( "sub widget is interactive" , async ( ) = > {
env . qweb . addTemplates ( `
<templates>
<t t-name="Parent">
<Child p="1"/>
</t>
<span t-name="Child"><button t-on-click="inc">click</button>child<t t-esc="state.val"/></span>
</templates> ` ) ;
class Child extends Widget {
2019-09-24 14:06:53 +02:00
state = useState ( { val : 1 } ) ;
2019-07-30 08:47:31 +02:00
inc() {
this . state . val ++ ;
}
2019-07-11 16:19:59 +02:00
}
class Parent extends Widget {
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-07-11 16:19:59 +02:00
}
2019-10-14 15:09:17 +02:00
const parent = new Parent ( ) ;
2019-07-11 16:19:59 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span><button>click</button>child1</span>" ) ;
2019-07-30 08:47:31 +02:00
const button = fixture . querySelector ( "button" ) ! ;
2019-07-11 16:19:59 +02:00
button . click ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<span><button>click</button>child2</span>" ) ;
} ) ;
test ( "can select a sub widget " , async ( ) = > {
2019-09-12 09:45:32 +02:00
class Child extends Widget {
static template = xml ` <span>CHILD 1</span> ` ;
}
class OtherChild extends Widget {
static template = xml ` <div>CHILD 2</div> ` ;
}
2019-07-11 16:19:59 +02:00
class Parent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<t>
<t t-if="env.flag"><Child /></t>
<t t-if="!env.flag"><OtherChild /></t>
</t>
` ;
2019-09-10 22:37:08 +02:00
static components = { Child , OtherChild } ;
2019-07-11 16:19:59 +02:00
}
( < any > env ) . flag = true ;
2019-10-14 15:09:17 +02:00
let parent = new Parent ( ) ;
2019-07-11 16:19:59 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>CHILD 1</span>" ) ;
parent . destroy ( ) ;
( < any > env ) . flag = false ;
2019-10-14 15:09:17 +02:00
parent = new Parent ( ) ;
2019-07-11 16:19:59 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>CHILD 2</div>" ) ;
2019-09-12 09:45:32 +02:00
expect ( env . qweb . templates [ Parent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-07-11 16:19:59 +02:00
} ) ;
test ( "can select a sub widget, part 2" , async ( ) = > {
2019-09-12 09:45:32 +02:00
class Child extends Widget {
static template = xml ` <span>CHILD 1</span> ` ;
}
class OtherChild extends Widget {
static template = xml ` <div>CHILD 2</div> ` ;
}
2019-07-11 16:19:59 +02:00
class Parent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<t>
<t t-if="state.flag"><Child /></t>
<t t-if="!state.flag"><OtherChild /></t>
</t>
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true } ) ;
2019-09-10 22:37:08 +02:00
static components = { Child , OtherChild } ;
2019-07-11 16:19:59 +02:00
}
2019-10-14 15:09:17 +02:00
let parent = new Parent ( ) ;
2019-07-11 16:19:59 +02:00
await parent . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>CHILD 1</span>" ) ;
parent . state . flag = false ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>CHILD 2</div>" ) ;
} ) ;
2019-10-25 15:45:56 +02:00
test ( "top level sub widget with a parent" , async ( ) = > {
class ComponentC extends Component < any , any > {
static template = xml ` <span>Hello</span> ` ;
}
class ComponentB extends Component < any , any > {
static template = xml ` <ComponentC /> ` ;
static components = { ComponentC } ;
}
class ComponentA extends Component < any , any > {
static template = xml ` <div><ComponentB/></div> ` ;
static components = { ComponentB } ;
}
2019-10-14 15:09:17 +02:00
const component = new ComponentA ( ) ;
2019-10-25 15:45:56 +02:00
await component . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>Hello</span></div>" ) ;
} ) ;
2019-07-11 16:19:59 +02:00
} ) ;
2019-08-22 13:32:53 +02:00
describe ( "unmounting and remounting" , ( ) = > {
test ( "widget can be unmounted and remounted" , async ( ) = > {
const steps : string [ ] = [ ] ;
class MyWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div>Hey</div> ` ;
2019-08-22 13:32:53 +02:00
async willStart() {
steps . push ( "willstart" ) ;
}
mounted() {
steps . push ( "mounted" ) ;
}
willUnmount() {
steps . push ( "willunmount" ) ;
}
2019-10-25 15:45:56 +02:00
patched() {
throw new Error ( "patched should not be called" ) ;
}
2019-08-22 13:32:53 +02:00
}
2019-10-14 15:09:17 +02:00
const w = new MyWidget ( ) ;
2019-08-22 13:32:53 +02:00
await w . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>Hey</div>" ) ;
expect ( steps ) . toEqual ( [ "willstart" , "mounted" ] ) ;
w . unmount ( ) ;
expect ( fixture . innerHTML ) . toBe ( "" ) ;
expect ( steps ) . toEqual ( [ "willstart" , "mounted" , "willunmount" ] ) ;
await w . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>Hey</div>" ) ;
expect ( steps ) . toEqual ( [ "willstart" , "mounted" , "willunmount" , "mounted" ] ) ;
} ) ;
test ( "widget can be mounted twice without ill effect" , async ( ) = > {
const steps : string [ ] = [ ] ;
class MyWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml ` <div>Hey</div> ` ;
2019-08-22 13:32:53 +02:00
async willStart() {
steps . push ( "willstart" ) ;
}
mounted() {
steps . push ( "mounted" ) ;
}
willUnmount() {
steps . push ( "willunmount" ) ;
}
}
2019-10-14 15:09:17 +02:00
const w = new MyWidget ( ) ;
2019-08-22 13:32:53 +02:00
await w . mount ( fixture ) ;
await w . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>Hey</div>" ) ;
expect ( steps ) . toEqual ( [ "willstart" , "mounted" ] ) ;
} ) ;
2019-08-28 10:02:38 +02:00
test ( "state changes in willUnmount do not trigger rerender" , async ( ) = > {
const steps : string [ ] = [ ] ;
class Child extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<span><t t-esc="props.val"/><t t-esc="state.n"/></span>
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { n : 2 } ) ;
2019-09-19 11:42:00 +02:00
__render ( f ) {
2019-08-28 10:02:38 +02:00
steps . push ( "render" ) ;
2019-09-19 11:42:00 +02:00
return super . __render ( f ) ;
2019-08-28 10:02:38 +02:00
}
willPatch() {
steps . push ( "willPatch" ) ;
}
patched() {
steps . push ( "patched" ) ;
}
willUnmount() {
steps . push ( "willUnmount" ) ;
this . state . n = 3 ;
}
}
class Parent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
<Child t-if="state.flag" val="state.val"/>
</div>
` ;
2019-09-10 22:37:08 +02:00
static components = { Child } ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 1 , flag : true } ) ;
2019-08-28 10:02:38 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-08-28 10:02:38 +02:00
await widget . mount ( fixture ) ;
expect ( steps ) . toEqual ( [ "render" ] ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>12</span></div>" ) ;
widget . state . flag = false ;
await nextTick ( ) ;
// we make sure here that no call to __render is done
expect ( steps ) . toEqual ( [ "render" , "willUnmount" ] ) ;
} ) ;
test ( "state changes in willUnmount will be applied on remount" , async ( ) = > {
class TestWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div><t t-esc="state.val"/></div>
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { val : 1 } ) ;
2019-08-28 10:02:38 +02:00
willUnmount() {
this . state . val = 3 ;
}
}
2019-10-14 15:09:17 +02:00
const widget = new TestWidget ( ) ;
2019-08-28 10:02:38 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>1</div>" ) ;
widget . unmount ( ) ;
expect ( fixture . innerHTML ) . toBe ( "" ) ;
2019-11-05 09:05:05 +01:00
await nextTick ( ) ; // wait for changes to be detected before remounting
2019-08-28 10:02:38 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>3</div>" ) ;
} ) ;
2019-11-13 17:11:42 +01:00
2019-11-05 14:23:06 +01:00
test ( "sub component is still active after being unmounted and remounted" , async ( ) = > {
2019-11-13 17:11:42 +01:00
class Child extends Component < any , any > {
static template = xml `
<p t-on-click="state.value++">
<t t-esc="state.value"/>
</p> ` ;
state = useState ( { value : 1 } ) ;
}
class Parent extends Component < any , any > {
static components = { Child } ;
static template = xml ` <div><Child/></div> ` ;
}
const w = new Parent ( ) ;
await w . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>1</p></div>" ) ;
fixture . querySelector ( "p" ) ! . click ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>2</p></div>" ) ;
w . unmount ( ) ;
await nextTick ( ) ;
await w . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>2</p></div>" ) ;
fixture . querySelector ( "p" ) ! . click ( ) ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><p>3</p></div>" ) ;
} ) ;
2019-11-05 14:23:06 +01:00
test ( "change state just before mounting component" , async ( ) = > {
const steps : number [ ] = [ ] ;
class TestWidget extends Component < any , any > {
static template = xml `
<div><t t-esc="state.val"/></div>
` ;
state = useState ( { val : 1 } ) ;
__render ( f ) {
steps . push ( this . state . val ) ;
return super . __render ( f ) ;
}
}
TestWidget . prototype . __render = jest . fn ( TestWidget . prototype . __render ) ;
const widget = new TestWidget ( ) ;
widget . state . val = 2 ;
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>2</div>" ) ;
expect ( TestWidget . prototype . __render ) . toHaveBeenCalledTimes ( 1 ) ;
// unmount and re-mount, as in this case, willStart won't be called, so it's
// slightly different
widget . unmount ( ) ;
widget . state . val = 3 ;
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>3</div>" ) ;
expect ( TestWidget . prototype . __render ) . toHaveBeenCalledTimes ( 2 ) ;
expect ( steps ) . toEqual ( [ 2 , 3 ] ) ;
} ) ;
test ( "change state while mounting component" , async ( ) = > {
const steps : number [ ] = [ ] ;
class TestWidget extends Component < any , any > {
static template = xml `
<div><t t-esc="state.val"/></div>
` ;
state = useState ( { val : 1 } ) ;
__render ( f ) {
steps . push ( this . state . val ) ;
return super . __render ( f ) ;
}
}
TestWidget . prototype . __render = jest . fn ( TestWidget . prototype . __render ) ;
TestWidget . prototype . __patch = jest . fn ( TestWidget . prototype . __patch ) ;
const widget = new TestWidget ( ) ;
let prom = widget . mount ( fixture ) ;
widget . state . val = 2 ;
await prom ;
expect ( fixture . innerHTML ) . toBe ( "<div>2</div>" ) ;
expect ( TestWidget . prototype . __render ) . toHaveBeenCalledTimes ( 1 ) ;
// unmount and re-mount, as in this case, willStart won't be called, so it's
// slightly different
widget . unmount ( ) ;
prom = widget . mount ( fixture ) ;
widget . state . val = 3 ;
await prom ;
expect ( fixture . innerHTML ) . toBe ( "<div>3</div>" ) ;
expect ( TestWidget . prototype . __render ) . toHaveBeenCalledTimes ( 3 ) ;
expect ( TestWidget . prototype . __patch ) . toHaveBeenCalledTimes ( 2 ) ;
expect ( steps ) . toEqual ( [ 2 , 2 , 3 ] ) ;
} ) ;
2019-08-22 13:32:53 +02:00
} ) ;
2019-09-11 11:28:41 +02:00
describe ( "dynamic root nodes" , ( ) = > {
test ( "template with t-if, part 1" , async ( ) = > {
2019-09-12 09:45:32 +02:00
class TestWidget extends Widget {
static template = xml `
<t>
<t t-if="true"><span>hey</span></t>
<t t-if="false"><div>abc</div></t>
</t>
` ;
}
2019-09-11 11:28:41 +02:00
2019-10-14 15:09:17 +02:00
const widget = new TestWidget ( ) ;
2019-09-11 11:28:41 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>hey</span>" ) ;
} ) ;
test ( "template with t-if, part 2" , async ( ) = > {
2019-09-12 09:45:32 +02:00
class TestWidget extends Widget {
static template = xml `
<t>
<t t-if="false"><span>hey</span></t>
<t t-if="true"><div>abc</div></t>
</t>
` ;
}
2019-09-11 11:28:41 +02:00
2019-10-14 15:09:17 +02:00
const widget = new TestWidget ( ) ;
2019-09-11 11:28:41 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>abc</div>" ) ;
} ) ;
test ( "switching between sub branches dynamically" , async ( ) = > {
class TestWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<t>
<t t-if="state.flag"><span>hey</span></t>
<t t-if="!state.flag"><div>abc</div></t>
</t>
` ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true } ) ;
2019-09-11 11:28:41 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new TestWidget ( ) ;
2019-09-11 11:28:41 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>hey</span>" ) ;
widget . state . flag = false ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>abc</div>" ) ;
} ) ;
test ( "switching between sub components dynamically" , async ( ) = > {
2019-09-12 09:45:32 +02:00
class ChildA extends Widget {
static template = xml ` <span>hey</span> ` ;
}
class ChildB extends Widget {
static template = xml ` <div>abc</div> ` ;
}
2019-09-11 11:28:41 +02:00
class TestWidget extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<t>
<t t-if="state.flag"><ChildA/></t>
<t t-if="!state.flag"><ChildB/></t>
</t>
` ;
2019-09-11 14:01:49 +02:00
static components = { ChildA , ChildB } ;
2019-09-24 14:06:53 +02:00
state = useState ( { flag : true } ) ;
2019-09-11 11:28:41 +02:00
}
2019-10-14 15:09:17 +02:00
const widget = new TestWidget ( ) ;
2019-09-11 11:28:41 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<span>hey</span>" ) ;
widget . state . flag = false ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div>abc</div>" ) ;
} ) ;
2019-09-11 14:01:49 +02:00
} ) ;
2019-09-11 11:28:41 +02:00
2019-09-11 14:01:49 +02:00
describe ( "dynamic t-props" , ( ) = > {
test ( "basic use" , async ( ) = > {
expect . assertions ( 4 ) ;
2019-09-12 09:45:32 +02:00
2019-09-11 14:01:49 +02:00
class Child extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<span>
<t t-esc="props.a + props.b"/>
</span>
` ;
2019-09-11 14:01:49 +02:00
constructor ( parent , props ) {
super ( parent , props ) ;
expect ( props ) . toEqual ( { a : 1 , b : 2 } ) ;
expect ( props ) . not . toBe ( widget . some . obj ) ;
}
}
class Parent extends Widget {
2019-09-12 09:45:32 +02:00
static template = xml `
<div>
<Child t-props="some.obj"/>
</div>
` ;
2019-09-11 14:01:49 +02:00
static components = { Child } ;
some = { obj : { a : 1 , b : 2 } } ;
}
2019-10-14 15:09:17 +02:00
const widget = new Parent ( ) ;
2019-09-11 14:01:49 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>3</span></div>" ) ;
2019-09-12 09:45:32 +02:00
expect ( env . qweb . templates [ Parent . template ] . fn . toString ( ) ) . toMatchSnapshot ( ) ;
2019-09-11 14:01:49 +02:00
} ) ;
} ) ;
2019-09-26 15:19:45 +02:00
describe ( "support svg components" , ( ) = > {
test ( "add proper namespace to svg" , async ( ) = > {
class GComp extends Widget {
static template = xml `
<g>
<circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/>
</g> ` ;
}
class Svg extends Widget {
static template = xml `
<svg>
<GComp/>
</svg> ` ;
static components = { GComp } ;
}
2019-10-14 15:09:17 +02:00
const widget = new Svg ( ) ;
2019-09-26 15:19:45 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
'<svg><g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"></circle></g></svg>'
) ;
} ) ;
} ) ;
2019-10-12 09:07:25 +02:00
describe ( "t-raw in components" , ( ) = > {
test ( "update properly on state changes" , async ( ) = > {
class TestW extends Widget {
static template = xml ` <div><t t-raw="state.value"/></div> ` ;
state = useState ( { value : "<b>content</b>" } ) ;
}
2019-10-14 15:09:17 +02:00
const widget = new TestW ( ) ;
2019-10-12 09:07:25 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><b>content</b></div>" ) ;
widget . state . value = "<span>other content</span>" ;
await nextTick ( ) ;
expect ( fixture . innerHTML ) . toBe ( "<div><span>other content</span></div>" ) ;
} ) ;
test ( "can render list of t-raw " , async ( ) = > {
class TestW extends Widget {
static template = xml `
<div>
<t t-foreach="state.items" t-as="item">
<t t-esc="item"/>
<t t-raw="item"/>
</t>
</div> ` ;
state = useState ( { items : [ "<b>one</b>" , "<b>two</b>" , "<b>tree</b>" ] } ) ;
}
2019-10-14 15:09:17 +02:00
const widget = new TestW ( ) ;
2019-10-12 09:07:25 +02:00
await widget . mount ( fixture ) ;
expect ( fixture . innerHTML ) . toBe (
"<div><b>one</b><b>one</b><b>two</b><b>two</b><b>tree</b><b>tree</b></div>"
) ;
} ) ;
} ) ;