import { ASTType, parse } from "../../src/compiler/parser"; describe("qweb parser", () => { // --------------------------------------------------------------------------- // texts and basic stuff // --------------------------------------------------------------------------- test("simple text node", async () => { expect(parse("foo")).toEqual({ type: ASTType.Text, value: "foo", }); }); test("text in t tag", async () => { expect(parse("foo")).toEqual({ type: ASTType.Text, value: "foo", }); }); test("empty string", async () => { expect(parse("")).toEqual({ type: ASTType.Text, value: "", }); }); test("white spaces are maintained", async () => { expect(parse(" ")).toEqual({ type: ASTType.Text, value: " ", }); }); test("white spaces only text nodes with newlines are removed", async () => { const template = `
`; expect(parse(template)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, content: [], attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, }); }); test("empty string in t tag", async () => { expect(parse("")).toEqual({ type: ASTType.Text, value: "", }); }); test("simple comment node", async () => { expect(parse("")).toEqual({ type: ASTType.Comment, value: " comment ", }); }); test("empty div", async () => { expect(parse("
")).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, content: [], ns: null, }); }); test("div with some text", async () => { expect(parse("
some text
")).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, content: [{ type: ASTType.Text, value: "some text" }], ns: null, }); }); test("div with some more content", async () => { expect(parse("
some textinside
")).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.Text, value: "some text" }, { type: ASTType.DomNode, tag: "span", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.Text, value: "inside" }], }, ], }); }); test("multiple root dom nodes", async () => { expect(parse("
")).toEqual({ type: ASTType.Multi, content: [ { type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [], }, { type: ASTType.DomNode, tag: "span", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [], }, ], }); }); test("dom node with t multi inside", async () => { const template = `
Loading
`; expect(parse(template)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.Text, value: "Loading" }, { type: ASTType.TEsc, expr: "abc", defaultValue: "" }, ], }); }); test("dom node with multiple t multi inside", async () => { const template = `
Loading
`; expect(parse(template)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.TEsc, expr: "a", defaultValue: "" }, { type: ASTType.TEsc, expr: "b", defaultValue: "" }, { type: ASTType.Text, value: "Loading" }, { type: ASTType.TEsc, expr: "c", defaultValue: "" }, ], }); }); test("dom node with t multi inside", async () => { const template = `
Loading
`; expect(parse(template)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.Text, value: "Loading" }, { type: ASTType.TEsc, expr: "abc", defaultValue: "" }, ], }); }); test("dom node with two t multi inside", async () => { const template = `
`; expect(parse(template)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.TEsc, expr: "a", defaultValue: "" }, { type: ASTType.TEsc, expr: "b", defaultValue: "" }, { type: ASTType.TEsc, expr: "c", defaultValue: "" }, { type: ASTType.TEsc, expr: "d", defaultValue: "" }, ], }); }); test("dom node next to text node", async () => { expect(parse("some text")).toEqual({ type: ASTType.Multi, content: [ { type: ASTType.Text, value: "some text" }, { type: ASTType.DomNode, tag: "span", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [], }, ], }); }); test("dom node with class attribute", async () => { expect(parse(`
foo
`)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: { class: "abc" }, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.Text, value: "foo" }], }); }); test("svg dom node", async () => { expect( parse( `` ) ).toEqual({ attrs: { height: "90px", width: "100px", }, attrsTranslationCtx: null, content: [ { attrs: { cx: "50", cy: "50", fill: "yellow", r: "4", stroke: "green", "stroke-width": "1", }, attrsTranslationCtx: null, content: [], dynamicTag: null, model: null, ns: null, on: null, ref: null, tag: "circle", type: 2, }, ], dynamicTag: null, model: null, ns: "http://www.w3.org/2000/svg", on: null, ref: null, tag: "svg", type: 2, }); expect( parse(``) ).toEqual({ attrs: null, attrsTranslationCtx: null, content: [ { attrs: { cx: "50", cy: "50", fill: "yellow", r: "4", stroke: "green", "stroke-width": "1", }, attrsTranslationCtx: null, content: [], dynamicTag: null, model: null, ns: null, on: null, ref: null, tag: "circle", type: 2, }, ], dynamicTag: null, model: null, ns: "http://www.w3.org/2000/svg", on: null, ref: null, tag: "g", type: 2, }); }); test("pre dom node with new line", async () => { expect(parse(`
\n
`)).toEqual({ type: 2, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, content: [ { type: 2, tag: "pre", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, content: [], model: null, ns: null, }, ], model: null, ns: null, }); }); // --------------------------------------------------------------------------- // t-esc // --------------------------------------------------------------------------- test("t-esc node", async () => { expect(parse(``)).toEqual({ type: ASTType.TEsc, expr: "text", defaultValue: "", }); expect(parse(``)).toEqual({ type: ASTType.TEsc, expr: "text", defaultValue: "", }); }); test("dom node with t-esc", async () => { expect(parse(``)).toEqual({ type: ASTType.DomNode, tag: "span", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "" }], }); }); test("t-esc node with default value", async () => { expect(parse(`hey`)).toEqual({ type: ASTType.TEsc, expr: "text", defaultValue: "hey", }); }); test("dom node with t-esc with default value", async () => { expect(parse(`
hey
`)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "hey" }], }); }); // --------------------------------------------------------------------------- // t-out // --------------------------------------------------------------------------- test("t-raw node (deprecated)", async () => { const warn = console.warn; const steps: string[] = []; console.warn = (msg: any) => steps.push(msg); expect(parse(``)).toEqual({ type: ASTType.TOut, expr: "text", body: null, }); expect(steps).toEqual([ 't-raw has been deprecated in favor of t-out. If the value to render is not wrapped by the "markup" function, it will be escaped', ]); console.warn = warn; }); test("t-out node", async () => { expect(parse(``)).toEqual({ type: ASTType.TOut, expr: "text", body: null, }); }); test("t-out node on a dom node", async () => { expect(parse(`
`)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.TOut, expr: "text", body: null }], }); }); test("t-out node with body", async () => { expect(parse(`
body
`)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.TOut, expr: "text", body: [{ type: ASTType.Text, value: "body" }] }, ], }); }); // --------------------------------------------------------------------------- // t-if // --------------------------------------------------------------------------- test("t-if", async () => { expect(parse(`
hey
`)).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.TIf, condition: "condition", content: { type: ASTType.Text, value: "hey", }, tElif: null, tElse: null, }, ], }); }); test("t-if with empty content", async () => { expect(parse(``)).toEqual({ type: ASTType.TIf, condition: "condition", content: { type: ASTType.Text, value: "", }, tElif: null, tElse: null, }); }); test("t-if (on dom node", async () => { expect(parse(`
hey
`)).toEqual({ type: ASTType.TIf, condition: "condition", content: { type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.Text, value: "hey" }], }, tElif: null, tElse: null, }); }); test("t-if and t else", async () => { expect(parse(`heyelse`)).toEqual({ type: ASTType.TIf, condition: "condition", content: { type: ASTType.Text, value: "hey", }, tElif: null, tElse: { type: ASTType.Text, value: "else", }, }); }); test("t-if and t elif", async () => { expect(parse(`heyelif`)).toEqual({ type: ASTType.TIf, condition: "condition", content: { type: ASTType.Text, value: "hey", }, tElif: [ { condition: "cond2", content: { type: ASTType.Text, value: "elif" }, }, ], tElse: null, }); }); test("t-if, t-elif and t-else", async () => { expect(parse(`heyelifelse`)).toEqual({ type: ASTType.TIf, condition: "c1", content: { type: ASTType.Text, value: "hey", }, tElif: [ { condition: "c2", content: { type: ASTType.Text, value: "elif" }, }, ], tElse: { type: ASTType.Text, value: "else", }, }); }); test("t-if, t-elif and t-else on a node", async () => { expect( parse(`
hey

elif

else

`) ).toEqual({ type: ASTType.TIf, condition: "c1", content: { type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.Text, value: "hey", }, ], }, tElif: [ { condition: "c2", content: { type: ASTType.DomNode, tag: "h1", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.Text, value: "elif" }], }, }, ], tElse: { type: ASTType.DomNode, tag: "h2", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.Text, value: "else", }, ], }, }); }); // --------------------------------------------------------------------------- // t-set // --------------------------------------------------------------------------- test("simple t-set expression", async () => { expect(parse(``)).toEqual({ type: ASTType.TSet, name: "key", value: "value", defaultValue: null, body: null, }); }); test("t-set expression with body", async () => { expect(parse(`ok`)).toEqual({ type: ASTType.TSet, name: "key", defaultValue: "ok", value: null, body: null, }); expect(parse(`
ok
`)).toEqual({ type: ASTType.TSet, name: "v", defaultValue: null, value: null, body: [ { type: ASTType.DomNode, attrs: null, attrsTranslationCtx: null, on: null, tag: "div", dynamicTag: null, ref: null, model: null, ns: null, content: [{ type: ASTType.Text, value: "ok" }], }, ], }); expect(parse(`
ok
abc
`)).toEqual({ type: ASTType.TSet, name: "v", defaultValue: null, value: null, body: [ { type: ASTType.DomNode, attrs: null, attrsTranslationCtx: null, on: null, tag: "div", dynamicTag: null, ref: null, model: null, ns: null, content: [{ type: ASTType.Text, value: "ok" }], }, { type: ASTType.Text, value: "abc" }, ], }); }); test("t-if and t-set expression with body", async () => { expect(parse(`ok`)).toEqual({ type: ASTType.TIf, condition: "flag", content: { type: ASTType.TSet, name: "key", defaultValue: "ok", value: null, body: null, }, tElif: null, tElse: null, }); }); test("t-if, t-else and t-set", async () => { expect( parse(`
1
`) ).toEqual({ type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [ { type: ASTType.TIf, condition: "flag", content: { type: ASTType.Text, value: "1" }, tElif: null, tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null }, }, ], }); }); // --------------------------------------------------------------------------- // t-foreach // --------------------------------------------------------------------------- test("simple t-foreach expression, t-key mandatory", async () => { expect(() => parse(``) ).toThrowErrorMatchingSnapshot(); }); test("simple t-foreach expression", async () => { expect( parse(``) ).toEqual({ type: ASTType.TForEach, collection: "list", elem: "item", key: "item_index", body: { type: ASTType.TEsc, expr: "item", defaultValue: "" }, memo: "", hasNoFirst: true, hasNoIndex: false, hasNoLast: true, hasNoValue: true, }); }); test("t-foreach expression with t-esc", async () => { expect(parse(``)).toEqual({ type: ASTType.TForEach, collection: "list", elem: "item", key: "item_index", body: { type: ASTType.TEsc, expr: "item", defaultValue: "" }, memo: "", hasNoFirst: true, hasNoIndex: false, hasNoLast: true, hasNoValue: true, }); }); test("t-foreach on a div expression with t-esc", async () => { expect(parse(`
`)).toEqual({ type: ASTType.TForEach, collection: "list", elem: "item", key: "item_index", body: { type: ASTType.DomNode, tag: "div", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }], }, memo: "", hasNoFirst: true, hasNoIndex: false, hasNoLast: true, hasNoValue: true, }); }); test("simple keyed t-foreach expression", async () => { expect(parse(``)).toEqual({ type: ASTType.TForEach, collection: "list", elem: "item", key: "item.id", body: { type: ASTType.TEsc, expr: "item", defaultValue: "" }, memo: "", hasNoFirst: true, hasNoIndex: true, hasNoLast: true, hasNoValue: true, }); }); test("t-foreach expression on a span", async () => { expect( parse(``) ).toEqual({ type: ASTType.TForEach, collection: "list", elem: "item", key: "item_index", body: { type: ASTType.DomNode, tag: "span", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }], }, memo: "", hasNoFirst: true, hasNoIndex: false, hasNoLast: true, hasNoValue: true, }); }); test("t-foreach expression on a span", async () => { expect( parse( `` ) ).toEqual({ type: ASTType.TForEach, collection: "list", elem: "item", key: "item_index", body: { type: ASTType.TIf, condition: "condition", tElif: null, tElse: null, content: { type: ASTType.DomNode, tag: "span", dynamicTag: null, attrs: null, attrsTranslationCtx: null, on: null, ref: null, model: null, ns: null, content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }], }, }, memo: "", hasNoFirst: true, hasNoIndex: false, hasNoLast: true, hasNoValue: true, }); }); test("more complex t-foreach expression on an option", async () => { expect( parse( `