import sdAttributes from "../libs/snabbdom/src/modules/attributes"; import sdListeners from "../libs/snabbdom/src/modules/eventlisteners"; import { init } from "../libs/snabbdom/src/snabbdom"; import { EvalContext, QWeb } from "../src/qweb"; import { normalize } from "./helpers"; //------------------------------------------------------------------------------ // Setup and helpers //------------------------------------------------------------------------------ // We create before each test: // - qweb: a new QWeb instance const patch = init([sdAttributes, sdListeners]); let qweb: QWeb; beforeEach(() => { qweb = new QWeb(); }); function trim(str: string): string { return str.replace(/\s/g, ""); } function renderToDOM( qweb: QWeb, template: string, context: EvalContext = {}, extra?: any ): HTMLElement | Text { const vnode = qweb.render(template, context, extra); // we snapshot here the compiled code. This is useful to prevent unwanted code // change. expect(qweb.templates[template].toString()).toMatchSnapshot(); if (vnode.sel === undefined) { return document.createTextNode(vnode.text!); } const node = document.createElement(vnode.sel!); const result = patch(node, vnode); return result.elm as HTMLElement; } function renderToString( qweb: QWeb, t: string, context: EvalContext = {} ): string { const node = renderToDOM(qweb, t, context); return node instanceof Text ? node.textContent! : node.outerHTML; } //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("static templates", () => { test("simple string", () => { qweb.addTemplate("test", "hello vdom"); expect(renderToString(qweb, "test")).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 span child node", () => { qweb.addTemplate("test", "
word
"); expect(renderToString(qweb, "test")).toBe("
word
"); }); }); 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", "
")).toThrow("already defined"); }); test("loadTemplates throw if parser error", () => { expect(() => { qweb.loadTemplates(">"); }).toThrow("Invalid XML in template"); }); test("nice error when t-on-directive is evaluated with a missing handler", () => { qweb.addTemplate("templatename", `
`); expect(() => qweb.render("templatename", {}, { handlers: [] })).toThrow( "Missing handler 'somemethod' when evaluating template 'templatename'" ); }); test("error when compiled code is invalid", () => { qweb.addTemplate( "templatename", `
` ); expect(() => qweb.render("templatename")).toThrow( "Invalid generated code while compiling template 'templatename': Unexpected token }" ); }); 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.skip("escaping", () => { qweb.addTemplate("test", ``); expect(renderToString(qweb, "test", { var: "" })).toBe( "<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"); }); }); 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("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("evaluate value expression, part 2", () => { qweb.addTemplate( "test", `
` ); expect(renderToString(qweb, "test", { somevariable: 43 })).toBe( "
45
" ); }); }); 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
" ); }); }); 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 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 multiple", () => { qweb.addTemplate( "test", `
` ); const result = renderToString(qweb, "test", { value1: 0, value2: 1, value3: 2 }); expect(result).toBe(`
`); }); test.skip("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("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); }); 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); }); }); 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(even)-(odd)-(even)-(odd)-last(even)
`; expect(result).toBe(expected); }); test("iterate, integer param", () => { qweb.addTemplate( "test", `
[: ]
` ); const result = trim(renderToString(qweb, "test")); const expected = `
[0:00][1:11][2:22]
`; 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-even][1:b2-odd][2:c3-even]
`; expect(result).toBe(expected); }); test("does not pollute the rendering context", () => { qweb.addTemplate( "test", `
` ); const context = {}; renderToString(qweb, "test", context); expect(Object.keys(context).length).toBe(0); }); test("throws error if invalid loop expression", () => { qweb.addTemplate( "test", `
` ); expect(() => qweb.render("test")).toThrow("Invalid loop expression"); }); }); 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(); }); }); describe("t-ref", () => { test("can get a ref on a node", () => { qweb.addTemplate("test", `
`); let refs: any = {}; renderToDOM(qweb, "test", { 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", { refs }); expect(refs.myspan3.tagName).toBe("SPAN"); }); }); describe("loading templates", () => { test("can initialize qweb with a string", () => { const data = `
jupiler
`; const qweb = new QWeb(data); expect(renderToString(qweb, "hey")).toBe("
jupiler
"); }); test("can load a few templates from a xml string", () => { const data = `
  • ok
  • foo
    `; qweb.loadTemplates(data); const result = renderToString(qweb, "main"); expect(result).toBe("
    • ok
    • foo
    "); }); test("does not crash if string does not have templates", () => { const data = ""; qweb.loadTemplates(data); expect(qweb.processedTemplates).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); }); });