import { QWeb } from "../../src/qweb/index"; import { nextTick, normalize, renderToDOM, renderToString, trim } from "../helpers"; import { patch } from "../../src/vdom"; //------------------------------------------------------------------------------ // Setup and helpers //------------------------------------------------------------------------------ // We create before each test: // - qweb: a new QWeb instance let qweb: QWeb; beforeEach(() => { QWeb.TEMPLATES = {}; qweb = new QWeb(); }); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("static templates", () => { test("simple string", () => { qweb.addTemplate("test", "hello vdom"); expect(renderToString(qweb, "test")).toBe("hello vdom"); }); test("simple dynamic value", () => { qweb.addTemplate("test", ''); expect(renderToString(qweb, "test", { text: "hello vdom" })).toBe("hello vdom"); }); test("simple string, with some dynamic value", () => { qweb.addTemplate("test", 'hello '); expect(renderToString(qweb, "test", { text: "vdom" })).toBe("hello vdom"); }); test("empty div", () => { qweb.addTemplate("test", "
"); expect(renderToString(qweb, "test")).toBe("
"); }); test("div with a text node", () => { qweb.addTemplate("test", "
word
"); expect(renderToString(qweb, "test")).toBe("
word
"); }); test("div with a class attribute", () => { qweb.addTemplate("test", `
word
`); expect(renderToString(qweb, "test")).toBe(`
word
`); }); test("div with a class attribute with a quote", () => { qweb.addTemplate("test", `
word
`); expect(renderToString(qweb, "test")).toBe(`
word
`); }); test("div with an arbitrary attribute with a quote", () => { qweb.addTemplate("test", `
word
`); expect(renderToString(qweb, "test")).toBe(`
word
`); }); test("div with a empty class attribute", () => { qweb.addTemplate("test", `
word
`); expect(renderToString(qweb, "test")).toBe(`
word
`); }); test("div with a span child node", () => { qweb.addTemplate("test", "
word
"); expect(renderToString(qweb, "test")).toBe("
word
"); }); test("properly handle comments", () => { qweb.addTemplate("test", "
hello owl
"); expect(renderToString(qweb, "test")).toBe("
hello owl
"); }); test("properly handle comments between t-if/t-else", () => { qweb.addTemplate( "test", `
true owl
` ); expect(renderToString(qweb, "test")).toBe("
true
"); }); }); describe("error handling", () => { test("invalid xml", () => { expect(() => qweb.addTemplate("test", "
")).toThrow("Invalid XML in template"); }); test("template with text node and tag", () => { qweb.addTemplate("test", `textother node`); expect(() => renderToString(qweb, "test")).toThrow( "A template should not have more than one root node" ); }); test("nice warning if no template with given name", () => { expect(() => qweb.render("invalidname")).toThrow("does not exist"); }); test("cannot add twice the same template", () => { qweb.addTemplate("test", ``); expect(() => qweb.addTemplate("test", "
", true)).not.toThrow("already defined"); expect(() => qweb.addTemplate("test", "
")).toThrow("already defined"); }); test("addTemplates throw if parser error", () => { expect(() => { qweb.addTemplates(">"); }).toThrow("Invalid XML in template"); }); test("nice error when t-on is evaluated with a missing event", () => { qweb.addTemplate("templatename", `
`); expect(() => qweb.render("templatename", { someMethod() {} }, { handlers: [] })).toThrow( "Missing event name with t-on directive" ); }); test("error when unknown directive", () => { qweb.addTemplate("templatename", `
test
`); expect(() => qweb.render("templatename")).toThrow("Unknown QWeb directive: 't-best-beer'"); }); }); describe("t-esc", () => { test("literal", () => { qweb.addTemplate("test", ``); expect(renderToString(qweb, "test")).toBe("ok"); }); test("variable", () => { qweb.addTemplate("test", ``); expect(renderToString(qweb, "test", { var: "ok" })).toBe("ok"); }); test("escaping", () => { qweb.addTemplate("test", ``); expect(renderToString(qweb, "test", { var: "abc" })).toBe( "<ok>abc</ok>" ); }); test("escaping on a node", () => { qweb.addTemplate("test", ``); expect(renderToString(qweb, "test")).toBe("ok"); }); test("escaping on a node with a body", () => { qweb.addTemplate("test", `nope`); expect(renderToString(qweb, "test")).toBe("ok"); }); test("escaping on a node with a body, as a default", () => { qweb.addTemplate("test", `nope`); expect(renderToString(qweb, "test")).toBe("nope"); }); test("t-esc is escaped", () => { qweb.addTemplate("test", `

escaped

`); const domRendered = renderToDOM(qweb, "test"); expect(domRendered.textContent).toBe("

escaped

"); }); test("t-esc=0 is escaped", () => { qweb.addTemplate("test", ``); qweb.addTemplate("testCaller", `

escaped

`); const domRendered = renderToDOM(qweb, "testCaller") as HTMLElement; expect(domRendered.querySelector("span")!.textContent).toBe("

escaped

"); }); test("div with falsy values", () => { qweb.addTemplate( "test", `

` ); const vals = { v1: false, v2: undefined, v3: null, v4: 0, v5: "", }; expect(renderToString(qweb, "test", vals)).toBe( "

false

0

" ); }); test("t-esc work with spread operator", () => { qweb.addTemplate("test", ``); const result = renderToString(qweb, "test", { state: { list: [1, 2] } }); expect(result).toBe("1,2"); }); }); describe("t-raw", () => { test("literal", () => { qweb.addTemplate("test", ``); expect(renderToString(qweb, "test")).toBe("ok"); }); test("variable", () => { qweb.addTemplate("test", ``); expect(renderToString(qweb, "test", { var: "ok" })).toBe("ok"); }); test("not escaping", () => { qweb.addTemplate("test", `
`); expect(renderToString(qweb, "test", { var: "" })).toBe("
"); }); test("t-raw and another sibling node", () => { qweb.addTemplate("test", `hello`); expect(renderToString(qweb, "test", { var: "world" })).toBe( "helloworld" ); }); }); describe("t-set", () => { test("set from attribute literal", () => { qweb.addTemplate("test", `
`); expect(renderToString(qweb, "test")).toBe("
ok
"); }); test("t-set and t-if", () => { qweb.addTemplate( "test", `
grimbergen
` ); expect(renderToString(qweb, "test", { value: "ok" })).toBe("
grimbergen
"); }); test("set from body literal", () => { qweb.addTemplate("test", `ok`); expect(renderToString(qweb, "test")).toBe("ok"); }); test("set from attribute lookup", () => { qweb.addTemplate("test", `
`); expect(renderToString(qweb, "test", { value: "ok" })).toBe("
ok
"); }); test("t-set evaluates an expression only once", () => { qweb.addTemplate( "test", `
` ); expect(renderToString(qweb, "test", { value: "stella" })).toBe( "
stella artoisstella artois
" ); }); test("set from body lookup", () => { qweb.addTemplate( "test", `
` ); expect(renderToString(qweb, "test", { value: "ok" })).toBe("
ok
"); }); test("set from empty body", () => { qweb.addTemplate("test", `
`); expect(renderToString(qweb, "test")).toBe("
"); }); test("value priority", () => { qweb.addTemplate("test", `
2
`); expect(renderToString(qweb, "test")).toBe("
1
"); }); test("evaluate value expression", () => { qweb.addTemplate("test", `
`); expect(renderToString(qweb, "test")).toBe("
3
"); }); test("t-set should reuse variable if possible", () => { qweb.addTemplate( "test", `
v
` ); expect(normalize(renderToString(qweb, "test", { list: ["a", "b"] }))).toBe( "
v1
va
" ); }); test("t-set with content and sub t-esc", () => { qweb.addTemplate( "test", `
boop
` ); expect(renderToString(qweb, "test", { beep: "beep" })).toBe("
beep boop
"); }); test("evaluate value expression, part 2", () => { qweb.addTemplate( "test", `
` ); expect(renderToString(qweb, "test", { somevariable: 43 })).toBe("
45
"); }); test("t-set, t-if, and mix of expression/body lookup, 1", () => { qweb.addTemplate( "test", `
1
` ); expect(renderToString(qweb, "test", { flag: true })).toBe("
1
"); expect(renderToString(qweb, "test", { flag: false })).toBe("
0
"); }); test("t-set, t-if, and mix of expression/body lookup, 2", () => { qweb.addTemplate( "test", `
0
` ); expect(renderToString(qweb, "test", { flag: true })).toBe("
1
"); expect(renderToString(qweb, "test", { flag: false })).toBe("
0
"); }); test("t-set body is evaluated immediately", () => { qweb.addTemplate( "test", `
` ); expect(renderToString(qweb, "test")).toBe("
before
"); }); test("t-set with t-value (falsy) and body", () => { qweb.addTemplate( "test", `
` ); expect(renderToString(qweb, "test")).toBe("
before
"); }); test("t-set with t-value (truthy) and body", () => { qweb.addTemplate( "test", `
` ); expect(renderToString(qweb, "test")).toBe("
Truthy
"); }); }); describe("t-if", () => { test("boolean value true condition", () => { qweb.addTemplate("test", `
ok
`); expect(renderToString(qweb, "test", { condition: true })).toBe("
ok
"); }); test("boolean value false condition", () => { qweb.addTemplate("test", `
ok
`); expect(renderToString(qweb, "test", { condition: false })).toBe("
"); }); test("boolean value condition missing", () => { qweb.addTemplate("test", `fail`); expect(renderToString(qweb, "test")).toBe(""); }); test("boolean value condition elif", () => { qweb.addTemplate( "test", `
black pearl yellow submarine red is dead beer
` ); expect(renderToString(qweb, "test", { color: "red" })).toBe("
red is dead
"); }); test("boolean value condition else", () => { qweb.addTemplate( "test", `
begin ok ok-else end
` ); const result = trim(renderToString(qweb, "test", { condition: true })); expect(result).toBe("
beginokend
"); }); test("boolean value condition false else", () => { qweb.addTemplate( "test", `
beginfail fail-elseend
` ); const result = trim(renderToString(qweb, "test", { condition: false })); expect(result).toBe("
beginfail-elseend
"); }); test("can use some boolean operators in expressions", () => { qweb.addTemplate( "test", `
and nope or nope mgt ngt mlt nlt
` ); const context = { cond1: true, cond2: true, cond3: false, cond4: false, m: 5, n: 2, }; expect(normalize(renderToString(qweb, "test", context))).toBe("
andormgtnlt
"); }); test("t-esc with t-if", () => { qweb.addTemplate("test", `
`); expect(renderToString(qweb, "test")).toBe("
x
"); }); test("t-esc with t-elif", () => { qweb.addTemplate("test", `
abc
`); expect(renderToString(qweb, "test")).toBe("
x
"); }); test("t-set, then t-if", () => { qweb.addTemplate( "test", `
` ); const result = renderToString(qweb, "test"); const expected = `
test
`; expect(result).toBe(expected); }); test("t-set, then t-if, part 2", () => { qweb.addTemplate( "test", `
COUCOU
` ); const result = renderToString(qweb, "test"); const expected = `
COUCOU
`; expect(result).toBe(expected); }); test("t-set, then t-elif, part 3", () => { qweb.addTemplate( "test", `
AAA BBB
` ); const result = renderToString(qweb, "test"); const expected = `
BBB
`; expect(result).toBe(expected); }); }); describe("attributes", () => { test("static attributes", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test"); const expected = `
`; expect(result).toBe(expected); }); test("static attributes with dashes", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test"); const expected = `
`; expect(result).toBe(expected); }); test("static attributes on void elements", () => { qweb.addTemplate("test", `Test`); const result = renderToString(qweb, "test"); expect(result).toBe(`Test`); }); test("dynamic attributes", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test"); expect(result).toBe(`
`); }); test("dynamic class attribute", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { c: "abc" }); expect(result).toBe(`
`); }); test("dynamic empty class attribute", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { c: "" }); expect(result).toBe(`
`); }); test("dynamic attribute with a dash", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { id: 32 }); expect(result).toBe(`
`); }); test("dynamic formatted attributes with a dash", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { id: 32 }); expect(result).toBe(`
`); }); test("fixed variable", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: "ok" }); expect(result).toBe(`
`); }); test("dynamic attribute falsy variable ", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: false }); expect(result).toBe(`
`); }); test("tuple literal", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test"); expect(result).toBe(`
`); }); test("tuple variable", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: ["foo", "bar"] }); expect(result).toBe(`
`); }); test("object", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: { a: 1, b: 2, c: 3 }, }); expect(result).toBe(`
`); }); test("format literal", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test"); expect(result).toBe(`
`); }); test("t-attf-class should combine with class", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test"); expect(result).toBe(`
`); }); test("format value", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: "a" }); expect(result).toBe(`
`); }); test("from variables set previously", () => { qweb.addTemplate( "test", `
` ); const result = renderToString(qweb, "test"); expect(result).toBe('
'); }); test("from object variables set previously", () => { // Note: standard qweb does not allow this... qweb.addTemplate( "test", `
` ); const result = renderToString(qweb, "test"); expect(result).toBe('
'); }); test("format expression", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: 5 }); expect(result).toBe(`
`); }); test("format expression, other format", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: 5 }); expect(result).toBe(`
`); }); test("format multiple", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value1: 0, value2: 1, value3: 2, }); expect(result).toBe(`
`); }); test("various escapes", () => { // not needed?? qweb.addTemplate( "test", `
` ); const result = renderToString(qweb, "test", { bar: 0, baz: 1, qux: { qux: "<>" }, }); const expected = '
'; expect(result).toBe(expected); }); test("t-att-class and class should combine together", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: "world" }); expect(result).toBe(`
`); }); test("class and t-att-class should combine together", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: "world" }); expect(result).toBe(`
`); }); test("class and t-attf-class with ternary operation", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { value: true }); expect(result).toBe(`
`); }); test("t-att-class with object", () => { qweb.addTemplate("test", `
`); const result = renderToString(qweb, "test", { b: true, d: false, f: true }); expect(result).toBe(`
`); }); }); describe("t-call (template calling", () => { test("basic caller", () => { qweb.addTemplate("_basic-callee", "ok"); qweb.addTemplate("caller", '
'); const expected = "
ok
"; expect(renderToString(qweb, "caller")).toBe(expected); expect(qweb.subTemplates["_basic-callee"].toString()).toMatchSnapshot(); }); test("basic caller, no parent node", () => { qweb.addTemplate("_basic-callee", "
ok
"); qweb.addTemplate("caller", ''); const expected = "
ok
"; expect(renderToString(qweb, "caller")).toBe(expected); expect(qweb.subTemplates["_basic-callee"].toString()).toMatchSnapshot(); }); test("t-call with t-if", () => { qweb.addTemplate("sub", "ok"); qweb.addTemplate("caller", '
'); const expected = "
ok
"; expect(renderToString(qweb, "caller", { flag: true })).toBe(expected); expect(qweb.subTemplates["sub"].toString()).toMatchSnapshot(); }); test("t-call not allowed on a non t node", () => { qweb.addTemplate("_basic-callee", "ok"); qweb.addTemplate("caller", '
'); expect(() => renderToString(qweb, "caller")).toThrow("Invalid tag"); }); test("with unused body", () => { qweb.addTemplate("_basic-callee", "
ok
"); qweb.addTemplate("caller", 'WHEEE'); const expected = "
ok
"; expect(renderToString(qweb, "caller")).toBe(expected); }); test("with unused setbody", () => { qweb.addTemplate("_basic-callee", "
ok
"); qweb.addTemplate("caller", ''); const expected = "
ok
"; expect(renderToString(qweb, "caller")).toBe(expected); }); test("with used body", () => { qweb.addTemplate("_callee-printsbody", '

'); qweb.addTemplate("caller", 'ok'); const expected = "

ok

"; expect(renderToString(qweb, "caller")).toBe(expected); }); test("with used set body", () => { qweb.addTemplate("_callee-uses-foo", ''); qweb.addTemplate( "caller", ` ` ); const expected = "ok"; expect(renderToString(qweb, "caller")).toBe(expected); }); test("inherit context", () => { qweb.addTemplate("_callee-uses-foo", ''); qweb.addTemplate( "caller", `
` ); const expected = "
1
"; expect(renderToString(qweb, "caller")).toBe(expected); }); test("scoped parameters", () => { qweb.addTemplate("_basic-callee", `ok`); qweb.addTemplate( "caller", `
` ); const expected = "
ok
"; expect(trim(renderToString(qweb, "caller"))).toBe(expected); }); test("call with several sub nodes on same line", () => { qweb.addTemplates(`
hey yay
`); const expected = "
hey yay
"; expect(renderToString(qweb, "main")).toBe(expected); expect(qweb.subTemplates["SubTemplate"].toString()).toMatchSnapshot(); }); test("cascading t-call t-raw='0'", () => { qweb.addTemplates(`
cascade 2
cascade 1
cascade 0
hey yay
`); const expected = "
cascade 2cascade 1cascade 0hey yay
"; expect(renderToString(qweb, "main")).toBe(expected); }); test("recursive template, part 1", () => { qweb.addTemplates(`
hey
`); const expected = "
hey
"; expect(renderToString(qweb, "recursive")).toBe(expected); const recursiveFn = Object.values(qweb.subTemplates)[0] as any; expect(recursiveFn.toString()).toMatchSnapshot(); }); test("recursive template, part 2", () => { qweb.addTemplates(`

`); const root = { val: "a", children: [{ val: "b" }, { val: "c" }] }; const expected = "

a

b

c

"; expect(renderToString(qweb, "Parent", { root }, { fiber: { vars: {}, scope: {} } })).toBe( expected ); const recursiveFn = Object.values(qweb.subTemplates)[0] as any; expect(recursiveFn.toString()).toMatchSnapshot(); }); test("recursive template, part 3", () => { qweb.addTemplates(`

`); const root = { val: "a", children: [{ val: "b", children: [{ val: "d" }] }, { val: "c" }] }; const expected = "

a

b

d

c

"; expect(renderToString(qweb, "Parent", { root }, { fiber: {} })).toBe(expected); const recursiveFn = Object.values(qweb.subTemplates)[0] as any; expect(recursiveFn.toString()).toMatchSnapshot(); }); test("recursive template, part 4: with t-set recursive index", () => { qweb.addTemplates(`

`); const root = { val: "a", children: [{ val: "b", children: [{ val: "c", children: [{ val: "d" }] }] }], }; const expected = "

a 2

b 3

c 4

d 5

"; expect(renderToString(qweb, "Parent", { root })).toBe(expected); const recursiveFn = Object.values(qweb.subTemplates)[0] as any; expect(recursiveFn.toString()).toMatchSnapshot(); }); test("t-call, global templates", () => { QWeb.registerTemplate("abcd", '
'); qweb.addTemplate("john", `desk`); const expected = "
desk
"; expect(trim(renderToString(qweb, "abcd"))).toBe(expected); }); test("t-call, conditional and t-set in t-call body", () => { QWeb.registerTemplate("callee1", "
callee1
"); QWeb.registerTemplate("callee2", '
callee2
'); QWeb.registerTemplate( "caller", `
` ); const rendered = renderToString(qweb, "caller"); expect(rendered).toBe(`
callee2 success
`); }); test("t-call with t-set inside and outside", () => { qweb.addTemplates(`
`); const expected = "
369
"; const context = { list: [{ val: 1 }, { val: 2 }, { val: 3 }] }; expect(trim(renderToString(qweb, "main", context))).toBe(expected); }); test("t-call with t-set inside and outside. 2", () => { qweb.addTemplates(`

`); const expected = "

3fromwrapper6fromwrapper9fromwrapper

"; const context = { list: [{ val: 1 }, { val: 2 }, { val: 3 }] }; expect(trim(renderToString(qweb, "wrapper", context))).toBe(expected); }); }); describe("foreach", () => { test("iterate on items", () => { qweb.addTemplate( "test", `
[: ]
` ); const result = trim(renderToString(qweb, "test")); const expected = `
[0:33][1:22][2:11]
`; expect(result).toBe(expected); }); test("iterate on items (on a element node)", () => { qweb.addTemplate( "test", `
` ); const result = trim(renderToString(qweb, "test")); const expected = `
12
`; expect(result).toBe(expected); }); test("iterate, position", () => { qweb.addTemplate( "test", `
- first last ()
` ); const result = trim(renderToString(qweb, "test")); const expected = `
-first(0)-(1)-(2)-(3)-last(4)
`; expect(result).toBe(expected); }); test("iterate, dict param", () => { qweb.addTemplate( "test", `
[: ]
` ); const result = trim(renderToString(qweb, "test", { value: { a: 1, b: 2, c: 3 } })); const expected = `
[0:a1][1:b2][2:c3]
`; expect(result).toBe(expected); }); test("does not pollute the rendering context", () => { qweb.addTemplate( "test", `
` ); const context = { __owl__: {} }; renderToString(qweb, "test", context); expect(Object.keys(context)).toEqual(["__owl__"]); }); test("t-foreach in t-forach", () => { qweb.addTemplate( "test", `
[]
` ); const context = { numbers: [1, 2, 3], letters: ["a", "b"] }; expect(renderToString(qweb, "test", context)).toBe( "
[1a] [1b] [2a] [2b] [3a] [3b]
" ); }); test("throws error if invalid loop expression", () => { qweb.addTemplate( "test", `
` ); expect(() => qweb.render("test")).toThrow("Invalid loop expression"); }); test("warn if no key in some case", () => { const consoleWarn = console.warn; console.warn = jest.fn(); qweb.addTemplate( "test", `
` ); renderToString(qweb, "test"); expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith( "Directive t-foreach should always be used with a t-key! (in template: 'test')" ); console.warn = consoleWarn; }); }); describe("misc", () => { test("global", () => { qweb.addTemplate("_callee-asc", ``); qweb.addTemplate("_callee-uses-foo", `foo default`); qweb.addTemplate("_callee-asc-toto", `
toto default
`); qweb.addTemplate( "caller", `
` ); const result = trim(renderToString(qweb, "caller")); const expected = trim(`
4 aaa foo default bbb 5 aaa foo default bbb 6 aaa foo default bbb
toto default
`); expect(result).toBe(expected); }); }); describe("t-on", () => { test("can bind event handler", () => { qweb.addTemplate("test", ``); let a = 1; const node = renderToDOM( qweb, "test", { add() { a = 3; }, }, { handlers: [] } ); (node).click(); expect(a).toBe(3); }); test("can bind two event handlers", () => { qweb.addTemplate( "test", `` ); let steps: string[] = []; const node = renderToDOM( qweb, "test", { handleClick() { steps.push("click"); }, handleDblClick() { steps.push("dblclick"); }, }, { handlers: [] } ); expect(steps).toEqual([]); (node).click(); expect(steps).toEqual(["click"]); (node).dispatchEvent(new Event("dblclick")); expect(steps).toEqual(["click", "dblclick"]); }); test("can bind handlers with arguments", () => { qweb.addTemplate("test", ``); let a = 1; const node = renderToDOM( qweb, "test", { add(n) { a = a + n; }, }, { handlers: [] } ); (node).click(); expect(a).toBe(6); }); test("can bind handlers with object arguments", () => { qweb.addTemplate("test", ``); let a = 1; const node = renderToDOM( qweb, "test", { add({ val }) { a = a + val; }, }, { handlers: [] } ); (node).click(); expect(a).toBe(6); }); test("can bind handlers with empty object", () => { expect.assertions(2); qweb.addTemplate("test", ``); const node = renderToDOM( qweb, "test", { doSomething(arg) { expect(arg).toEqual({}); }, }, { handlers: [] } ); (node).click(); }); test("can bind handlers with empty object (with non empty inner string)", () => { expect.assertions(2); qweb.addTemplate("test", ``); const node = renderToDOM( qweb, "test", { doSomething(arg) { expect(arg).toEqual({}); }, }, { handlers: [] } ); (node).click(); }); test("can bind handlers with loop variable as argument", () => { expect.assertions(2); qweb.addTemplate( "test", ` ` ); const node = renderToDOM( qweb, "test", { activate(action) { expect(action).toBe("someval"); }, }, { handlers: [] } ); (node).getElementsByTagName("a")[0].click(); }); test("handler is bound to proper owner", () => { expect.assertions(2); qweb.addTemplate("test", ``); let owner = { add() { expect(this).toBe(owner); }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); (node).click(); }); test("t-on with inline statement", () => { qweb.addTemplate("test", ``); let owner = { state: { counter: 0, }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); expect(owner.state.counter).toBe(0); (node).click(); expect(owner.state.counter).toBe(1); }); test("t-on with inline statement (function call)", () => { qweb.addTemplate("test", ``); let owner = { state: { counter: 0, incrementCounter: (inc) => { owner.state.counter += inc; }, }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); expect(owner.state.counter).toBe(0); (node).click(); expect(owner.state.counter).toBe(2); }); test("t-on with inline statement, part 2", () => { qweb.addTemplate("test", ``); let owner = { state: { flag: true, }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); expect(owner.state.flag).toBe(true); (node).click(); expect(owner.state.flag).toBe(false); (node).click(); expect(owner.state.flag).toBe(true); }); test("t-on with inline statement, part 3", () => { qweb.addTemplate("test", ``); let owner = { someFunction(n) { return n + 1; }, state: { n: 11, }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); expect(owner.state.n).toBe(11); (node).click(); expect(owner.state.n).toBe(4); }); test("t-on with t-call", async () => { expect.assertions(2); qweb.addTemplate("sub", `

lucas

`); qweb.addTemplate("main", `
`); let owner = { update() { expect(this).toBe(owner); }, }; const node = renderToDOM(qweb, "main", owner, { handlers: [] }); (node).querySelector("p")!.click(); }); test("t-on, with arguments and t-call", async () => { expect.assertions(3); qweb.addTemplate("sub", `

lucas

`); qweb.addTemplate("main", `
`); let owner = { update(val) { expect(this).toBe(owner); expect(val).toBe(444); }, value: 444, }; const node = renderToDOM(qweb, "main", owner, { handlers: [] }); (node).querySelector("p")!.click(); }); test("t-on with prevent and/or stop modifiers", async () => { expect.assertions(7); qweb.addTemplate( "test", `
` ); let owner = { onClickPrevented(e) { expect(e.defaultPrevented).toBe(true); expect(e.cancelBubble).toBe(false); }, onClickStopped(e) { expect(e.defaultPrevented).toBe(false); expect(e.cancelBubble).toBe(true); }, onClickPreventedAndStopped(e) { expect(e.defaultPrevented).toBe(true); expect(e.cancelBubble).toBe(true); }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); const buttons = (node).getElementsByTagName("button"); buttons[0].click(); buttons[1].click(); buttons[2].click(); }); test("t-on with self modifier", async () => { expect.assertions(2); qweb.addTemplate( "test", `
` ); let steps: string[] = []; let owner = { onClick(e) { steps.push("onClick"); }, onClickSelf(e) { steps.push("onClickSelf"); }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); const buttons = (node).getElementsByTagName("button"); const spans = (node).getElementsByTagName("span"); spans[0].click(); spans[1].click(); buttons[0].click(); buttons[1].click(); expect(steps).toEqual(["onClick", "onClick", "onClickSelf"]); }); test("t-on with self and prevent modifiers (order matters)", async () => { expect.assertions(2); qweb.addTemplate( "test", `
` ); let steps: boolean[] = []; let owner = { onClick() {}, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); (node).addEventListener("click", function (e) { steps.push(e.defaultPrevented); }); const button = (node).getElementsByTagName("button")[0]; const span = (node).getElementsByTagName("span")[0]; span.click(); button.click(); expect(steps).toEqual([false, true]); }); test("t-on with prevent and self modifiers (order matters)", async () => { expect.assertions(2); qweb.addTemplate( "test", `
` ); let steps: boolean[] = []; let owner = { onClick() {}, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); (node).addEventListener("click", function (e) { steps.push(e.defaultPrevented); }); const button = (node).getElementsByTagName("button")[0]; const span = (node).getElementsByTagName("span")[0]; span.click(); button.click(); expect(steps).toEqual([true, true]); }); test("t-on with prevent modifier in t-foreach", async () => { expect.assertions(5); qweb.addTemplate( "test", `` ); const steps: string[] = []; const owner = { projects: [ { id: 1, name: "Project 1" }, { id: 2, name: "Project 2" }, ], onEdit(projectId, ev) { expect(ev.defaultPrevented).toBe(true); steps.push(projectId); }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); expect(node.outerHTML).toBe( `` ); const links = node.querySelectorAll("a")!; links[0].click(); links[1].click(); expect(steps).toEqual([1, 2]); }); test("t-on with empty handler (only modifiers)", () => { expect.assertions(2); qweb.addTemplate( "test", `
` ); const node = renderToDOM(qweb, "test", {}, { handlers: [] }); node.addEventListener("click", (e) => { expect(e.defaultPrevented).toBe(true); }); const button = (node).getElementsByTagName("button")[0]; button.click(); }); test("t-on combined with t-esc", async () => { expect.assertions(3); qweb.addTemplate("test", `
`); const steps: string[] = []; const owner = { text: "Click here", onClick() { steps.push("onClick"); }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); expect(node.outerHTML).toBe(`
`); node.querySelector("button")!.click(); expect(steps).toEqual(["onClick"]); }); test("t-on combined with t-raw", async () => { expect.assertions(3); qweb.addTemplate("test", `
`); const steps: string[] = []; const owner = { html: "Click here", onClick() { steps.push("onClick"); }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); expect(node.outerHTML).toBe(`
`); node.querySelector("button")!.click(); expect(steps).toEqual(["onClick"]); }); test("t-on with .capture modifier", () => { expect.assertions(2); qweb.addTemplate( "test", `
` ); const steps: string[] = []; const owner = { onCapture() { steps.push("captured"); }, doSomething() { steps.push("normal"); }, }; const node = renderToDOM(qweb, "test", owner, { handlers: [] }); const button = (node).getElementsByTagName("button")[0]; button.click(); expect(steps).toEqual(["captured", "normal"]); }); }); describe("t-ref", () => { test("can get a ref on a node", () => { qweb.addTemplate("test", `
`); let refs: any = {}; renderToDOM(qweb, "test", { __owl__: { refs } }); expect(refs.myspan.tagName).toBe("SPAN"); }); test("can get a dynamic ref on a node", () => { qweb.addTemplate("test", `
`); let refs: any = {}; renderToDOM(qweb, "test", { id: 3, __owl__: { refs } }); expect(refs.myspan3.tagName).toBe("SPAN"); }); test("refs in a loop", () => { qweb.addTemplate( "test", `
` ); let refs: any = {}; renderToDOM(qweb, "test", { items: [1, 2, 3], __owl__: { refs } }); expect(Object.keys(refs)).toEqual(["1", "2", "3"]); }); }); describe("loading templates", () => { test("can initialize qweb with a string", () => { const templates = `
jupiler
`; const qweb = new QWeb({ templates }); expect(renderToString(qweb, "hey")).toBe("
jupiler
"); }); test("can load a few templates from a xml string", () => { const data = `
  • ok
  • foo
    `; qweb.addTemplates(data); const result = renderToString(qweb, "main"); expect(result).toBe("
    • ok
    • foo
    "); }); test("does not crash if string does not have templates", () => { const data = ""; qweb.addTemplates(data); expect(Object.keys(qweb.templates)).toEqual([]); }); }); describe("special cases for some boolean html attributes/properties", () => { test("input type= checkbox, with t-att-checked", () => { qweb.addTemplate("test", ``); const result = renderToString(qweb, "test", { flag: true }); expect(result).toBe(``); }); test("various boolean html attributes", () => { // the unique assertion here is the code snapshot automatically done by // renderToString expect.assertions(1); qweb.addTemplate( "test", `
    ` ); renderToString(qweb, "test", { flag: true }); }); }); describe("whitespace handling", () => { test("white space only text nodes are condensed into a single space", () => { qweb.addTemplate("test", `
    `); const result = renderToString(qweb, "test"); expect(result).toBe(`
    `); }); test("consecutives whitespaces are condensed into a single space", () => { qweb.addTemplate("test", `
    abc
    `); const result = renderToString(qweb, "test"); expect(result).toBe(`
    abc
    `); }); test("whitespace only text nodes with newlines are removed", () => { qweb.addTemplate( "test", `
    abc
    ` ); const result = renderToString(qweb, "test"); expect(result).toBe(`
    abc
    `); }); test("nothing is done in pre tags", () => { qweb.addTemplate("test", `
      
    `); const result = renderToString(qweb, "test"); expect(result).toBe(`
      
    `); const pretagtext = `
            some text
          
    `; qweb.addTemplate("test2", pretagtext); const result2 = renderToString(qweb, "test2"); expect(result2).toBe(pretagtext); const pretagwithonlywhitespace = `
            
          
    `; qweb.addTemplate("test3", pretagwithonlywhitespace); const result3 = renderToString(qweb, "test3"); expect(result3).toBe(pretagwithonlywhitespace); }); }); describe("t-key", () => { test("can use t-key directive on a node", () => { qweb.addTemplate("test", `
    `); expect(renderToString(qweb, "test", { beer: { id: 12, name: "Chimay Rouge" } })).toBe( "
    Chimay Rouge
    " ); }); test("t-key directive in a list", () => { qweb.addTemplate( "test", `
    ` ); expect( renderToString(qweb, "test", { beers: [{ id: 12, name: "Chimay Rouge" }], }) ).toMatchSnapshot(); }); }); describe("debugging", () => { test("t-debug", () => { const consoleLog = console.log; console.log = jest.fn(); qweb.addTemplate( "test", `
    hey
    ` ); qweb.render("test"); expect(qweb.templates.test.fn.toString()).toMatchSnapshot(); expect(console.log).toHaveBeenCalledTimes(1); console.log = consoleLog; }); test("t-debug on sub template", () => { const consoleLog = console.log; console.log = jest.fn(); qweb.addTemplates(`

    coucou

    `); qweb.render("test"); expect(console.log).toHaveBeenCalledTimes(1); console.log = consoleLog; }); test("t-log", () => { const consoleLog = console.log; console.log = jest.fn(); qweb.addTemplate( "test", `
    ` ); qweb.render("test"); expect(qweb.templates.test.fn.toString()).toMatchSnapshot(); expect(console.log).toHaveBeenCalledWith(45); console.log = consoleLog; }); }); describe("update on event bus", () => { test("two consecutive forceUpdates only causes one listener update", async () => { const fn = jest.fn(); qweb.on("update", null, fn); qweb.forceUpdate(); qweb.forceUpdate(); expect(fn).toBeCalledTimes(0); await nextTick(); expect(fn).toBeCalledTimes(1); }); }); describe("global template registration", () => { test("can register template globally", () => { expect.assertions(5); let qweb = new QWeb(); try { qweb.render("mytemplate"); } catch (e) { expect(e.message).toMatch("Template mytemplate does not exist"); } expect(qweb.templates.mytemplate).toBeUndefined(); QWeb.registerTemplate("mytemplate", "
    global
    "); expect(qweb.templates.mytemplate).toBeDefined(); const vnode = qweb.render("mytemplate"); expect(vnode.sel).toBe("div"); expect((vnode as any).children[0].text).toBe("global"); }); }); describe("properly support svg", () => { test("add proper namespace to svg", () => { qweb.addTemplate( "test", ` ` ); expect(renderToString(qweb, "test")).toBe( ` ` ); }); test("add proper namespace to g tags", () => { // this is necessary if one wants to use components in a svg qweb.addTemplate( "test", ` ` ); expect(renderToString(qweb, "test")).toBe( ` ` ); }); }); describe("translation support", () => { test("can translate node content", () => { const translations = { word: "mot", }; const translateFn = (expr) => translations[expr] || expr; const qweb = new QWeb({ translateFn }); qweb.addTemplate("test", "
    word
    "); expect(renderToString(qweb, "test")).toBe("
    mot
    "); }); test("does not translate node content if disabled", () => { const translations = { word: "mot", }; const translateFn = (expr) => translations[expr] || expr; const qweb = new QWeb({ translateFn }); qweb.addTemplate( "test", `
    word word
    ` ); expect(renderToString(qweb, "test")).toBe("
    motword
    "); }); test("some attributes are translated", () => { const translations = { word: "mot", }; const translateFn = (expr) => translations[expr] || expr; const qweb = new QWeb({ translateFn }); qweb.addTemplate( "test", `

    word

    word

    word

    word

    word

    ` ); expect(renderToString(qweb, "test")).toBe( '

    mot

    mot

    mot

    mot

    mot

    ' ); }); }); describe("t-key tests", () => { test("t-key on t-foreach", async () => { qweb.addTemplate( "test", `
    ` ); let vnode = qweb.render("test", { things: [1, 2] }); vnode = patch(document.createElement("div"), vnode); let elm = vnode.elm as HTMLElement; expect(elm.outerHTML).toBe("
    "); const first = elm.querySelectorAll("span")[0]; const second = elm.querySelectorAll("span")[1]; patch(vnode, qweb.render("test", { things: [2, 1] })); expect(elm.outerHTML).toBe("
    "); expect(first).toBe(elm.querySelectorAll("span")[1]); expect(second).toBe(elm.querySelectorAll("span")[0]); }); });