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(``)).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(
``
)
).toEqual({
type: ASTType.TForEach,
collection: "categories",
elem: "category",
key: "category_index",
body: {
type: ASTType.DomNode,
tag: "option",
dynamicTag: null,
attrs: {
"t-att-selected": "category.id==options.active_category_id",
"t-att-value": "category.id",
},
attrsTranslationCtx: null,
on: null,
ref: null,
model: null,
ns: null,
content: [{ type: ASTType.TEsc, expr: "category.name", defaultValue: "" }],
},
memo: "",
hasNoFirst: true,
hasNoIndex: false,
hasNoLast: true,
hasNoValue: true,
});
});
test("t-foreach in a div", async () => {
expect(
parse(`
`)
).toEqual({
type: ASTType.DomNode,
attrs: null,
attrsTranslationCtx: null,
on: null,
ref: null,
model: null,
tag: "div",
dynamicTag: null,
ns: null,
content: [
{
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("simple t-key expression", async () => {
expect(parse(`abc`)).toEqual({
type: ASTType.TKey,
expr: "k",
content: { type: ASTType.Text, value: "abc" },
});
});
test("t-foreach expression on a span with a t-key", async () => {
expect(
parse(``)
).toEqual({
type: ASTType.TForEach,
collection: "list",
elem: "item",
key: "item_index",
body: {
type: ASTType.DomNode,
tag: "span",
dynamicTag: null,
on: null,
ref: null,
model: null,
attrs: null,
attrsTranslationCtx: null,
ns: null,
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
},
memo: "",
hasNoFirst: true,
hasNoIndex: false,
hasNoLast: true,
hasNoValue: true,
});
});
test("t-foreach expression with a component inside", async () => {
expect(parse(``)).toEqual({
type: ASTType.TForEach,
collection: "list",
elem: "item",
key: "item_index",
body: {
type: ASTType.TComponent,
isDynamic: false,
name: "Comp",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
slots: null,
on: null,
},
memo: "",
hasNoFirst: true,
hasNoIndex: false,
hasNoLast: true,
hasNoValue: true,
});
});
test("t-foreach expression with a t-call inside", async () => {
expect(
parse(``)
).toEqual({
type: ASTType.TForEach,
collection: "list",
elem: "item",
key: "item_index",
body: {
type: ASTType.TCall,
name: "blap",
body: null,
context: null,
},
memo: "",
hasNoFirst: false,
hasNoIndex: false,
hasNoLast: false,
hasNoValue: false,
});
});
test("t-foreach expression with t-memo", async () => {
expect(
parse(
``
)
).toEqual({
type: ASTType.TForEach,
collection: "list",
elem: "item",
key: "item_index",
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
memo: "[row.x]",
hasNoFirst: true,
hasNoIndex: false,
hasNoLast: true,
hasNoValue: true,
});
});
// ---------------------------------------------------------------------------
// t-call
// ---------------------------------------------------------------------------
test("simple t-call expression", async () => {
expect(parse(``)).toEqual({
type: ASTType.TCall,
name: "blabla",
body: null,
context: null,
});
});
test("t-call with body", async () => {
expect(parse(`ok`)).toEqual({
type: ASTType.TCall,
name: "sub",
context: null,
body: [{ type: ASTType.Text, value: "ok" }],
});
});
test("t-call expression with t-call-context", async () => {
expect(parse(``)).toEqual({
type: ASTType.TCall,
name: "blabla",
body: null,
context: "someContext",
});
});
test("t-call on a div 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.TCall,
name: "blabla",
body: null,
context: null,
},
],
});
});
test("t-call with t-if", async () => {
expect(parse(``)).toEqual({
type: ASTType.TIf,
condition: "condition",
tElif: null,
tElse: null,
content: {
type: ASTType.TCall,
name: "blabla",
body: null,
context: null,
},
});
});
// ---------------------------------------------------------------------------
// t-on
// ---------------------------------------------------------------------------
test("simple t-on expression", async () => {
expect(parse(``)).toEqual({
type: ASTType.DomNode,
tag: "button",
dynamicTag: null,
attrs: null,
attrsTranslationCtx: null,
on: { click: "add" },
ref: null,
model: null,
ns: null,
content: [{ type: ASTType.Text, value: "Click" }],
});
});
test("t-onclick without dash", async () => {
expect(() => parse(``)).toThrowError(
"Unknown QWeb directive: 't-onclick'"
);
});
test("t-on without event", async () => {
expect(() => parse(``)).toThrowError(
"Missing event name with t-on directive"
);
});
test("t-on- without event", async () => {
expect(() => parse(``)).toThrowError(
"Missing event name with t-on directive"
);
});
// ---------------------------------------------------------------------------
// t-model
// ---------------------------------------------------------------------------
test("t-model select", async () => {
expect(parse(``)).toEqual({
type: 2,
tag: "select",
dynamicTag: null,
attrs: null,
attrsTranslationCtx: null,
on: null,
ref: null,
content: [
{
type: 2,
tag: "option",
dynamicTag: null,
attrs: { value: "1" },
attrsTranslationCtx: null,
on: null,
ref: null,
content: [],
model: null,
ns: null,
},
],
model: {
baseExpr: "state",
expr: "'model'",
targetAttr: "value",
hasDynamicChildren: false,
specialInitTargetAttr: null,
eventType: "change",
shouldTrim: false,
shouldNumberize: false,
},
ns: null,
});
});
test("t-model select dynamic options", async () => {
expect(
parse(``)
).toEqual({
type: 2,
tag: "select",
dynamicTag: null,
attrs: null,
attrsTranslationCtx: null,
on: null,
ref: null,
content: [
{
type: 2,
tag: "option",
dynamicTag: null,
attrs: { "t-att-value": "valueVar" },
attrsTranslationCtx: null,
on: null,
ref: null,
content: [],
model: null,
ns: null,
},
],
model: {
baseExpr: "state",
expr: "'model'",
targetAttr: "value",
specialInitTargetAttr: null,
eventType: "change",
shouldTrim: false,
shouldNumberize: false,
hasDynamicChildren: true,
},
ns: null,
});
});
// ---------------------------------------------------------------------------
// t-component
// ---------------------------------------------------------------------------
test("just a plain component", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
on: null,
slots: null,
isDynamic: false,
});
});
test("component with props", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: { a: "1", b: "'b'" },
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: null,
});
});
test("component with t-props", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: "state",
props: { a: "1" },
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: null,
});
});
test("component with event handler", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: false,
on: { click: "someMethod" },
slots: null,
});
});
test("component with event handler", async () => {
expect(() => parse(``)).toThrowError(
"unsupported directive on Component: t-onclick"
);
});
test("component with t-ref", async () => {
expect(() => parse(``)).toThrow(
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop."
);
});
test("component with t-att", async () => {
expect(() => parse(``)).toThrow(
"t-att makes no sense on component: props are already treated as expressions"
);
});
test("component with t-attf", async () => {
expect(() => parse(``)).toThrow(
"t-attf is not supported on components: use template strings for string interpolation in props"
);
});
test("component with other unsupported directive", async () => {
expect(() => parse(``)).toThrow(
"unsupported directive on Component: t-something"
);
});
test("a component with a default slot", async () => {
expect(parse(`foo`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: {
default: {
content: { type: ASTType.Text, value: "foo" },
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
},
},
});
});
test("a component with a default slot with attributes", async () => {
expect(
parse(`foo`)
).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: {
default: {
content: { type: ASTType.Text, value: "foo" },
attrs: { param: "param" },
attrsTranslationCtx: null,
on: null,
scope: null,
},
},
});
});
test("a component with a default multi root slot", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
isDynamic: false,
dynamicProps: null,
props: null,
propsTranslationCtx: null,
on: null,
slots: {
default: {
content: {
type: ASTType.Multi,
content: [
{
type: ASTType.DomNode,
tag: "span",
dynamicTag: null,
attrs: null,
attrsTranslationCtx: null,
content: [],
ref: null,
model: null,
on: null,
ns: null,
},
{
type: ASTType.DomNode,
tag: "div",
dynamicTag: null,
attrs: null,
attrsTranslationCtx: null,
content: [],
ref: null,
model: null,
on: null,
ns: null,
},
],
},
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
},
},
});
});
test("a component with an empty named slot", async () => {
expect(parse(``)).toEqual({
dynamicProps: null,
isDynamic: false,
name: "MyComponent",
on: null,
props: null,
propsTranslationCtx: null,
slots: {
mySlot: {
attrs: null,
attrsTranslationCtx: null,
content: null,
on: null,
scope: null,
},
},
type: 11,
});
});
test("a component with a named slot", async () => {
expect(parse(`foo`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
isDynamic: false,
dynamicProps: null,
props: null,
propsTranslationCtx: null,
on: null,
slots: {
name: {
content: { type: ASTType.Text, value: "foo" },
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
},
},
});
});
test("a component with a named slot with attributes", async () => {
expect(parse(`foo`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
isDynamic: false,
dynamicProps: null,
props: null,
propsTranslationCtx: null,
on: null,
slots: {
name: {
content: { type: ASTType.Text, value: "foo" },
attrs: { param: "param" },
attrsTranslationCtx: null,
on: null,
scope: null,
},
},
});
});
test("a component with a named slot with t-on", async () => {
expect(
parse(`foo`)
).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
isDynamic: false,
dynamicProps: null,
props: null,
propsTranslationCtx: null,
on: null,
slots: {
name: {
content: { type: ASTType.Text, value: "foo" },
on: { click: "doStuff" },
attrs: null,
attrsTranslationCtx: null,
scope: null,
},
},
});
});
test("a component with a named slot with div tag", async () => {
expect(() =>
parse(`foo
`)
).toThrowError();
});
test("a component with a named slot and some white space", async () => {
expect(parse(`foo `)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: {
default: {
content: { type: ASTType.Text, value: " " },
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
},
name: {
content: { type: ASTType.Text, value: "foo" },
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
},
},
});
});
test("a component with two named slot and some white space", async () => {
const template = `
foo
bar
`;
expect(parse(template)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: {
a: {
content: { type: ASTType.Text, value: "foo" },
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
},
b: {
content: { type: ASTType.Text, value: "bar" },
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
},
},
});
});
test("dynamic t-component", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "myComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: true,
on: null,
slots: null,
});
});
test("dynamic component with props", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "mycomponent",
dynamicProps: null,
props: { a: "1", b: "'b'" },
propsTranslationCtx: null,
isDynamic: true,
on: null,
slots: null,
});
});
test("dynamic component with t-props", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "mycomponent",
dynamicProps: "state",
props: { a: "1" },
propsTranslationCtx: null,
isDynamic: true,
on: null,
slots: null,
});
});
test("component with t-esc", async () => {
expect(parse(``)).toEqual(
parse(``)
);
});
test("component with t-out", async () => {
expect(parse(``)).toEqual(
parse(``)
);
});
test("component with t-esc and content", async () => {
expect(() => parse(`Some content`)).toThrow(
"Cannot have t-esc on a component that already has content"
);
});
test("component with t-call", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: {
default: {
content: { body: null, name: "subTemplate", type: ASTType.TCall, context: null },
attrs: null,
attrsTranslationCtx: null,
scope: null,
on: null,
},
},
});
});
test("component with t-set-slot inside component", async () => {
const template = `
coucou
`;
expect(parse(template)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: {
default: {
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
content: {
type: ASTType.TComponent,
isDynamic: false,
name: "Child",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
on: null,
slots: {
brol: {
content: { type: ASTType.Text, value: "coucou" },
attrs: null,
attrsTranslationCtx: null,
scope: null,
on: null,
},
},
},
},
},
});
});
test("component with t-set-slot inside component, with an extra ", async () => {
const template = `
coucou
`;
expect(parse(template)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
isDynamic: false,
on: null,
slots: {
default: {
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
content: {
type: ASTType.TComponent,
isDynamic: false,
name: "Child",
dynamicProps: null,
props: null,
propsTranslationCtx: null,
on: null,
slots: {
brol: {
content: { type: ASTType.Text, value: "coucou" },
attrs: null,
attrsTranslationCtx: null,
on: null,
scope: null,
},
},
},
},
},
});
});
// ---------------------------------------------------------------------------
// t-slot
// ---------------------------------------------------------------------------
test("a simple t-slot", async () => {
expect(parse(``)).toEqual({
type: ASTType.TSlot,
name: "default",
attrs: null,
attrsTranslationCtx: null,
on: null,
defaultContent: null,
});
});
test("a t-slot with default content", async () => {
expect(parse(`default content`)).toEqual({
type: ASTType.TSlot,
name: "header",
attrs: null,
attrsTranslationCtx: null,
on: null,
defaultContent: { type: ASTType.Text, value: "default content" },
});
});
test("t-slot with t-on-", async () => {
expect(parse(``)).toEqual({
type: ASTType.TSlot,
name: "default",
attrs: null,
attrsTranslationCtx: null,
on: { "click.prevent": "doSomething" },
defaultContent: null,
});
});
// ---------------------------------------------------------------------------
// t-debug
// ---------------------------------------------------------------------------
test("a t-debug on a dom node", async () => {
expect(parse(`hey
`)).toEqual({
type: ASTType.TDebug,
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" }],
},
});
});
test("a t-log on a dom node", async () => {
expect(parse(`hey
`)).toEqual({
type: ASTType.TLog,
expr: "bla",
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" }],
},
});
});
// ---------------------------------------------------------------------------
// t-ref
// ---------------------------------------------------------------------------
test("a t-ref on a dom node", async () => {
expect(parse(`hey
`)).toEqual({
type: ASTType.DomNode,
tag: "div",
dynamicTag: null,
attrs: null,
attrsTranslationCtx: null,
on: null,
ref: "name",
model: null,
ns: null,
content: [{ type: ASTType.Text, value: "hey" }],
});
});
test("node with t-ref and t-out", async () => {
expect(parse(`body
`)).toEqual({
type: ASTType.DomNode,
tag: "div",
dynamicTag: null,
attrs: null,
attrsTranslationCtx: null,
on: null,
ref: "name",
model: null,
ns: null,
content: [
{ type: ASTType.TOut, expr: "text", body: [{ type: ASTType.Text, value: "body" }] },
],
});
});
test("node with t-ref and t-esc", async () => {
expect(parse(`body
`)).toEqual({
type: ASTType.DomNode,
tag: "div",
dynamicTag: null,
attrs: null,
attrsTranslationCtx: null,
on: null,
ref: "name",
model: null,
ns: null,
content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "body" }],
});
});
// ---------------------------------------------------------------------------
// t-call-block
// ---------------------------------------------------------------------------
test("simple t-call-block", async () => {
expect(parse(``)).toEqual({
type: ASTType.TCallBlock,
name: "myBlock",
});
});
// ---------------------------------------------------------------------------
// t-translation
// ---------------------------------------------------------------------------
test('t-translation="off"', async () => {
expect(parse(`word`)).toEqual({
type: ASTType.TTranslation,
content: {
type: ASTType.Text,
value: "word",
},
});
expect(
parse(`word
`)
).toEqual({
body: {
content: {
attrs: null,
attrsTranslationCtx: null,
content: [
{
type: ASTType.Text,
value: "word",
},
],
on: null,
ref: null,
model: null,
tag: "div",
dynamicTag: null,
type: ASTType.DomNode,
ns: null,
},
type: ASTType.TTranslation,
},
collection: "list",
elem: "item",
hasNoFirst: true,
hasNoIndex: false,
hasNoLast: true,
hasNoValue: true,
key: "item_index",
memo: "",
type: ASTType.TForEach,
});
});
// ---------------------------------------------------------------------------
// t-translation-context
// ---------------------------------------------------------------------------
test('t-translation-context="fr"', async () => {
expect(parse(`word`)).toEqual({
type: ASTType.TTranslationContext,
content: {
type: ASTType.Text,
value: "word",
},
translationCtx: "fr",
});
expect(parse(`word
`)).toEqual({
content: {
attrs: null,
attrsTranslationCtx: null,
content: [
{
type: 0,
value: "word",
},
],
dynamicTag: null,
model: null,
ns: null,
on: null,
ref: null,
tag: "div",
type: ASTType.DomNode,
},
translationCtx: "fr",
type: ASTType.TTranslationContext,
});
});
// ---------------------------------------------------------------------------
// t-translation-context-attr
// ---------------------------------------------------------------------------
test('t-translation-context="fr" and t-translation-context-title="pt" for a div attr title', async () => {
expect(
parse(
`word
`
)
).toEqual({
content: {
attrs: { title: "hello" },
attrsTranslationCtx: { title: "pt" },
content: [
{
type: 0,
value: "word",
},
],
dynamicTag: null,
model: null,
ns: null,
on: null,
ref: null,
tag: "div",
type: ASTType.DomNode,
},
translationCtx: "fr",
type: ASTType.TTranslationContext,
});
});
test('t-translation-context-title="fr" for component prop title', async () => {
expect(parse(``)).toEqual({
dynamicProps: null,
isDynamic: false,
name: "Comp",
on: null,
props: {
title: "hello",
},
propsTranslationCtx: {
title: "fr",
},
slots: null,
type: ASTType.TComponent,
});
});
// ---------------------------------------------------------------------------
// t-model
// ---------------------------------------------------------------------------
test("t-model", async () => {
expect(parse(``)).toEqual({
type: ASTType.DomNode,
attrs: null,
attrsTranslationCtx: null,
content: [],
on: null,
ref: null,
tag: "input",
dynamicTag: null,
ns: null,
model: {
baseExpr: "state",
expr: "'stuff'",
eventType: "input",
shouldNumberize: false,
shouldTrim: false,
hasDynamicChildren: false,
targetAttr: "value",
specialInitTargetAttr: null,
},
});
expect(parse(``)).toEqual({
type: ASTType.DomNode,
attrs: null,
attrsTranslationCtx: null,
content: [],
on: null,
ref: null,
tag: "input",
dynamicTag: null,
ns: null,
model: {
baseExpr: "state",
expr: "'stuff'",
eventType: "input",
shouldNumberize: false,
shouldTrim: false,
targetAttr: "value",
specialInitTargetAttr: null,
hasDynamicChildren: false,
},
});
expect(parse(``)).toEqual({
type: ASTType.DomNode,
attrs: null,
attrsTranslationCtx: null,
content: [],
on: null,
ref: null,
tag: "input",
dynamicTag: null,
ns: null,
model: {
baseExpr: "state",
expr: "'stuff'",
eventType: "change",
shouldNumberize: true,
shouldTrim: true,
targetAttr: "value",
hasDynamicChildren: false,
specialInitTargetAttr: null,
},
});
});
expect(parse(``)).toEqual({
type: ASTType.DomNode,
attrs: null,
attrsTranslationCtx: null,
content: [],
on: null,
ref: null,
tag: "textarea",
dynamicTag: null,
ns: null,
model: {
baseExpr: "state",
expr: "'stuff'",
eventType: "input",
shouldNumberize: false,
shouldTrim: false,
targetAttr: "value",
hasDynamicChildren: false,
specialInitTargetAttr: null,
},
});
expect(parse(``)).toEqual({
type: ASTType.DomNode,
attrs: { type: "checkbox" },
attrsTranslationCtx: null,
content: [],
on: null,
ref: null,
tag: "input",
dynamicTag: null,
ns: null,
model: {
baseExpr: "state",
expr: "'stuff'",
eventType: "input",
shouldNumberize: false,
shouldTrim: false,
targetAttr: "checked",
hasDynamicChildren: false,
specialInitTargetAttr: null,
},
});
expect(parse(``)).toEqual({
type: ASTType.DomNode,
attrs: { type: "radio" },
attrsTranslationCtx: null,
content: [],
on: null,
ref: null,
tag: "input",
dynamicTag: null,
ns: null,
model: {
baseExpr: "state",
expr: "'stuff'",
eventType: "click",
shouldNumberize: false,
shouldTrim: false,
targetAttr: "value",
hasDynamicChildren: false,
specialInitTargetAttr: "checked",
},
});
expect(parse(``)).toEqual({
type: ASTType.DomNode,
attrs: { type: "radio" },
attrsTranslationCtx: null,
content: [],
on: null,
ref: null,
tag: "input",
dynamicTag: null,
ns: null,
model: {
baseExpr: "state",
expr: "'stuff'",
eventType: "click",
shouldNumberize: true,
shouldTrim: true,
targetAttr: "value",
hasDynamicChildren: false,
specialInitTargetAttr: "checked",
},
});
// ---------------------------------------------------------------------------
// t-tag
// ---------------------------------------------------------------------------
test("t-tag", async () => {
expect(parse(``)).toEqual({
type: ASTType.DomNode,
attrs: null,
attrsTranslationCtx: null,
content: [],
on: null,
ref: null,
tag: "div",
dynamicTag: "theTag",
model: null,
ns: null,
});
});
// ---------------------------------------------------------------------------
// t-portal
// ---------------------------------------------------------------------------
test("t-portal", async () => {
expect(parse(`Content`)).toEqual({
type: ASTType.TPortal,
target: "target",
content: { type: ASTType.Text, value: "Content" },
});
});
test("t-portal with t-if", async () => {
expect(parse(`Content`)).toEqual({
condition: "condition",
content: {
content: { type: ASTType.Text, value: "Content" },
target: "target",
type: ASTType.TPortal,
},
tElif: null,
tElse: null,
type: ASTType.TIf,
});
});
});