mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] compiler: t-model on select with options with dynamic values
This commit is contained in:
committed by
Géry Debongnie
parent
0737bb39b4
commit
b208894d38
@@ -128,6 +128,7 @@ interface Context {
|
|||||||
translate: boolean;
|
translate: boolean;
|
||||||
tKeyExpr: string | null;
|
tKeyExpr: string | null;
|
||||||
nameSpace?: string;
|
nameSpace?: string;
|
||||||
|
tModelSelectedExpr?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createContext(parentCtx: Context, params?: Partial<Context>) {
|
function createContext(parentCtx: Context, params?: Partial<Context>) {
|
||||||
@@ -139,6 +140,7 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
|
|||||||
translate: parentCtx.translate,
|
translate: parentCtx.translate,
|
||||||
tKeyExpr: null,
|
tKeyExpr: null,
|
||||||
nameSpace: parentCtx.nameSpace,
|
nameSpace: parentCtx.nameSpace,
|
||||||
|
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
|
||||||
},
|
},
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
@@ -543,24 +545,36 @@ export class CodeGenerator {
|
|||||||
// specific namespace uri
|
// specific namespace uri
|
||||||
attrs["block-ns"] = nameSpace;
|
attrs["block-ns"] = nameSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let key in ast.attrs) {
|
for (let key in ast.attrs) {
|
||||||
|
let expr, attrName;
|
||||||
if (key.startsWith("t-attf")) {
|
if (key.startsWith("t-attf")) {
|
||||||
let expr = interpolate(ast.attrs[key]);
|
expr = interpolate(ast.attrs[key]);
|
||||||
const idx = block!.insertData(expr, "attr");
|
const idx = block!.insertData(expr, "attr");
|
||||||
attrs["block-attribute-" + idx] = key.slice(7);
|
|
||||||
|
attrName = key.slice(7);
|
||||||
|
attrs["block-attribute-" + idx] = attrName;
|
||||||
} else if (key.startsWith("t-att")) {
|
} else if (key.startsWith("t-att")) {
|
||||||
let expr = compileExpr(ast.attrs[key]);
|
expr = compileExpr(ast.attrs[key]);
|
||||||
const idx = block!.insertData(expr, "attr");
|
const idx = block!.insertData(expr, "attr");
|
||||||
if (key === "t-att") {
|
if (key === "t-att") {
|
||||||
attrs[`block-attributes`] = String(idx);
|
attrs[`block-attributes`] = String(idx);
|
||||||
} else {
|
} else {
|
||||||
attrs[`block-attribute-${idx}`] = key.slice(6);
|
attrName = key.slice(6);
|
||||||
|
attrs[`block-attribute-${idx}`] = attrName;
|
||||||
}
|
}
|
||||||
} else if (this.translatableAttributes.includes(key)) {
|
} else if (this.translatableAttributes.includes(key)) {
|
||||||
attrs[key] = this.translateFn(ast.attrs[key]);
|
attrs[key] = this.translateFn(ast.attrs[key]);
|
||||||
} else {
|
} else {
|
||||||
|
expr = `"${ast.attrs[key]}"`;
|
||||||
|
attrName = key;
|
||||||
attrs[key] = ast.attrs[key];
|
attrs[key] = ast.attrs[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attrName === "value" && ctx.tModelSelectedExpr) {
|
||||||
|
let selectedId = block!.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, "attr");
|
||||||
|
attrs[`block-attribute-${selectedId}`] = "selected";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// event handlers
|
// event handlers
|
||||||
@@ -600,8 +614,10 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// t-model
|
// t-model
|
||||||
|
let tModelSelectedExpr;
|
||||||
if (ast.model) {
|
if (ast.model) {
|
||||||
const {
|
const {
|
||||||
|
hasDynamicChildren,
|
||||||
baseExpr,
|
baseExpr,
|
||||||
expr,
|
expr,
|
||||||
eventType,
|
eventType,
|
||||||
@@ -624,6 +640,9 @@ export class CodeGenerator {
|
|||||||
"attr"
|
"attr"
|
||||||
);
|
);
|
||||||
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
||||||
|
} else if (hasDynamicChildren) {
|
||||||
|
tModelSelectedExpr = `bValue${id}`;
|
||||||
|
this.addLine(`let ${tModelSelectedExpr} = ${baseExpression}[${expression}]`);
|
||||||
} else {
|
} else {
|
||||||
idx = block!.insertData(`${baseExpression}[${expression}]`, "attr");
|
idx = block!.insertData(`${baseExpression}[${expression}]`, "attr");
|
||||||
attrs[`block-attribute-${idx}`] = targetAttr;
|
attrs[`block-attribute-${idx}`] = targetAttr;
|
||||||
@@ -658,6 +677,7 @@ export class CodeGenerator {
|
|||||||
isLast: ctx.isLast && i === children.length - 1,
|
isLast: ctx.isLast && i === children.length - 1,
|
||||||
tKeyExpr: ctx.tKeyExpr,
|
tKeyExpr: ctx.tKeyExpr,
|
||||||
nameSpace,
|
nameSpace,
|
||||||
|
tModelSelectedExpr,
|
||||||
});
|
});
|
||||||
this.compileAST(child, subCtx);
|
this.compileAST(child, subCtx);
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-12
@@ -33,6 +33,17 @@ export interface ASTComment {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TModelInfo {
|
||||||
|
hasDynamicChildren?: boolean;
|
||||||
|
baseExpr: string;
|
||||||
|
expr: string;
|
||||||
|
targetAttr: string;
|
||||||
|
specialInitTargetAttr: string | null;
|
||||||
|
eventType: "change" | "click" | "input";
|
||||||
|
shouldTrim: boolean;
|
||||||
|
shouldNumberize: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ASTDomNode {
|
export interface ASTDomNode {
|
||||||
type: ASTType.DomNode;
|
type: ASTType.DomNode;
|
||||||
tag: string;
|
tag: string;
|
||||||
@@ -41,15 +52,7 @@ export interface ASTDomNode {
|
|||||||
content: AST[];
|
content: AST[];
|
||||||
ref: string | null;
|
ref: string | null;
|
||||||
on: { [key: string]: string };
|
on: { [key: string]: string };
|
||||||
model: {
|
model?: TModelInfo | null;
|
||||||
baseExpr: string;
|
|
||||||
expr: string;
|
|
||||||
targetAttr: string;
|
|
||||||
specialInitTargetAttr: string | null;
|
|
||||||
eventType: "change" | "click" | "input";
|
|
||||||
shouldTrim: boolean;
|
|
||||||
shouldNumberize: boolean;
|
|
||||||
} | null;
|
|
||||||
ns: string | null;
|
ns: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +181,7 @@ export type AST =
|
|||||||
// Parser
|
// Parser
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
interface ParsingContext {
|
interface ParsingContext {
|
||||||
|
tModelInfo?: TModelInfo | null;
|
||||||
inPreTag: boolean;
|
inPreTag: boolean;
|
||||||
inSVG: boolean;
|
inSVG: boolean;
|
||||||
}
|
}
|
||||||
@@ -298,12 +302,10 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
const ref = node.getAttribute("t-ref");
|
const ref = node.getAttribute("t-ref");
|
||||||
node.removeAttribute("t-ref");
|
node.removeAttribute("t-ref");
|
||||||
|
|
||||||
const children = parseChildren(node, ctx);
|
|
||||||
|
|
||||||
const nodeAttrsNames = node.getAttributeNames();
|
const nodeAttrsNames = node.getAttributeNames();
|
||||||
const attrs: ASTDomNode["attrs"] = {};
|
const attrs: ASTDomNode["attrs"] = {};
|
||||||
const on: ASTDomNode["on"] = {};
|
const on: ASTDomNode["on"] = {};
|
||||||
let model: ASTDomNode["model"] = null;
|
let model: TModelInfo | null = null;
|
||||||
|
|
||||||
for (let attr of nodeAttrsNames) {
|
for (let attr of nodeAttrsNames) {
|
||||||
const value = node.getAttribute(attr)!;
|
const value = node.getAttribute(attr)!;
|
||||||
@@ -351,13 +353,24 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
||||||
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
||||||
};
|
};
|
||||||
|
if (isSelect) {
|
||||||
|
// don't pollute the original ctx
|
||||||
|
ctx = Object.assign({}, ctx);
|
||||||
|
ctx.tModelInfo = model;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
||||||
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
||||||
}
|
}
|
||||||
|
const tModel = ctx.tModelInfo;
|
||||||
|
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
|
||||||
|
tModel.hasDynamicChildren = true;
|
||||||
|
}
|
||||||
attrs[attr] = value;
|
attrs[attr] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const children = parseChildren(node, ctx);
|
||||||
return {
|
return {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: tagName,
|
tag: tagName,
|
||||||
|
|||||||
@@ -1132,6 +1132,81 @@ describe("qweb parser", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// t-model
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test("t-model select", async () => {
|
||||||
|
expect(parse(`<select t-model="state.model"><option value="1" /></select>`)).toEqual({
|
||||||
|
type: 2,
|
||||||
|
tag: "select",
|
||||||
|
dynamicTag: null,
|
||||||
|
attrs: {},
|
||||||
|
on: {},
|
||||||
|
ref: null,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 2,
|
||||||
|
tag: "option",
|
||||||
|
dynamicTag: null,
|
||||||
|
attrs: { value: "1" },
|
||||||
|
on: {},
|
||||||
|
ref: null,
|
||||||
|
content: [],
|
||||||
|
model: null,
|
||||||
|
ns: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
model: {
|
||||||
|
baseExpr: "state",
|
||||||
|
expr: "'model'",
|
||||||
|
targetAttr: "value",
|
||||||
|
specialInitTargetAttr: null,
|
||||||
|
eventType: "change",
|
||||||
|
shouldTrim: false,
|
||||||
|
shouldNumberize: false,
|
||||||
|
},
|
||||||
|
ns: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-model select dynamic options", async () => {
|
||||||
|
expect(
|
||||||
|
parse(`<select t-model="state.model"><option t-att-value="valueVar" /></select>`)
|
||||||
|
).toEqual({
|
||||||
|
type: 2,
|
||||||
|
tag: "select",
|
||||||
|
dynamicTag: null,
|
||||||
|
attrs: {},
|
||||||
|
on: {},
|
||||||
|
ref: null,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 2,
|
||||||
|
tag: "option",
|
||||||
|
dynamicTag: null,
|
||||||
|
attrs: { "t-att-value": "valueVar" },
|
||||||
|
on: {},
|
||||||
|
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
|
// t-component
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -360,6 +360,123 @@ exports[`t-model directive on an textarea 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive t-model on select with static options 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { toNumber } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><select block-attribute-0=\\"value\\" block-handler-1=\\"change\\"><option value=\\"a\\"><block-text-2/></option><option value=\\"b\\"><block-text-3/></option><option value=\\"c\\"><block-text-4/></option></select></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const bExpr1 = ctx['state'];
|
||||||
|
let attr1 = ctx['state']['model'];
|
||||||
|
let hdlr1 = [(ev) => { bExpr1['model'] = ev.target.value; }];
|
||||||
|
let txt1 = 'a';
|
||||||
|
let txt2 = 'b';
|
||||||
|
let txt3 = 'c';
|
||||||
|
return block1([attr1, hdlr1, txt1, txt2, txt3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive t-model with dynamic values on select options -- 2 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { toNumber } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option block-attribute-4=\\"value\\" block-attribute-5=\\"selected\\"><block-text-6/></option></select></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const bExpr1 = ctx['state'];
|
||||||
|
let bValue1 = ctx['state']['model']
|
||||||
|
let hdlr1 = [(ev) => { bExpr1['model'] = ev.target.value; }];
|
||||||
|
let attr1 = ctx['options'][0];
|
||||||
|
let attr2 = bValue1 === ctx['options'][0];
|
||||||
|
let txt1 = ctx['options'][0];
|
||||||
|
let attr3 = (ctx['options'][1]);
|
||||||
|
let attr4 = bValue1 === (ctx['options'][1]);
|
||||||
|
let txt2 = ctx['options'][1];
|
||||||
|
return block1([hdlr1, attr1, attr2, txt1, attr3, attr4, txt2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive t-model with dynamic values on select options -- 3 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { toNumber } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option value=\\"b\\" block-attribute-4=\\"selected\\"><block-text-5/></option></select></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const bExpr1 = ctx['state'];
|
||||||
|
let bValue1 = ctx['state']['model']
|
||||||
|
let hdlr1 = [(ev) => { bExpr1['model'] = ev.target.value; }];
|
||||||
|
let attr1 = ctx['options'][0];
|
||||||
|
let attr2 = bValue1 === ctx['options'][0];
|
||||||
|
let txt1 = ctx['options'][0];
|
||||||
|
let attr3 = bValue1 === \\"b\\";
|
||||||
|
let txt2 = ctx['options'][1];
|
||||||
|
return block1([hdlr1, attr1, attr2, txt1, attr3, txt2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive t-model with dynamic values on select options 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { toNumber } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option block-attribute-4=\\"value\\" block-attribute-5=\\"selected\\"><block-text-6/></option></select></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const bExpr1 = ctx['state'];
|
||||||
|
let bValue1 = ctx['state']['model']
|
||||||
|
let hdlr1 = [(ev) => { bExpr1['model'] = ev.target.value; }];
|
||||||
|
let attr1 = ctx['options'][0];
|
||||||
|
let attr2 = bValue1 === ctx['options'][0];
|
||||||
|
let txt1 = ctx['options'][0];
|
||||||
|
let attr3 = ctx['options'][1];
|
||||||
|
let attr4 = bValue1 === ctx['options'][1];
|
||||||
|
let txt2 = ctx['options'][1];
|
||||||
|
return block1([hdlr1, attr1, attr2, txt1, attr3, attr4, txt2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive t-model with dynamic values on select options in foreach 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { toNumber, prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><block-child-0/></select></div>\`);
|
||||||
|
let block3 = createBlock(\`<option block-attribute-0=\\"value\\" block-attribute-1=\\"selected\\"><block-text-2/></option>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const bExpr1 = ctx['state'];
|
||||||
|
let bValue1 = ctx['state']['model']
|
||||||
|
let hdlr1 = [(ev) => { bExpr1['model'] = ev.target.value; }];
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['options']);
|
||||||
|
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||||
|
ctx[\`v\`] = v_block2[i1];
|
||||||
|
let key1 = ctx['v'];
|
||||||
|
let attr1 = ctx['v'];
|
||||||
|
let attr2 = bValue1 === ctx['v'];
|
||||||
|
let txt1 = ctx['v'];
|
||||||
|
c_block2[i1] = withKey(block3([attr1, attr2, txt1]), key1);
|
||||||
|
}
|
||||||
|
let b2 = list(c_block2);
|
||||||
|
return block1([hdlr1], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-model directive two inputs in a div alternating with a t-if 1`] = `
|
exports[`t-model directive two inputs in a div alternating with a t-if 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -452,4 +452,116 @@ describe("t-model directive", () => {
|
|||||||
expect(comp.state.choice).toBe("Three");
|
expect(comp.state.choice).toBe("Three");
|
||||||
expect(comp.state.lastClicked).toBe("Three");
|
expect(comp.state.lastClicked).toBe("Three");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-model on select with static options", async () => {
|
||||||
|
class Test extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<select t-model="state.model">
|
||||||
|
<option value="a" t-esc="'a'"/>
|
||||||
|
<option value="b" t-esc="'b'"/>
|
||||||
|
<option value="c" t-esc="'c'"/>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
state: any;
|
||||||
|
options: any;
|
||||||
|
setup() {
|
||||||
|
this.state = useState({ model: "b" });
|
||||||
|
this.options = ["a", "b", "c"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Test, fixture);
|
||||||
|
expect(fixture.querySelector("select")!.value).toEqual("b");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-model with dynamic values on select options", async () => {
|
||||||
|
class Test extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<select t-model="state.model">
|
||||||
|
<option t-att-value="options[0]" t-esc="options[0]"/>
|
||||||
|
<option t-att-value="options[1]" t-esc="options[1]"/>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
state: any;
|
||||||
|
options: any;
|
||||||
|
setup() {
|
||||||
|
this.state = useState({ model: "b" });
|
||||||
|
this.options = ["a", "b"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Test, fixture);
|
||||||
|
expect(fixture.querySelector("select")!.value).toEqual("b");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-model with dynamic values on select options -- 2", async () => {
|
||||||
|
class Test extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<select t-model="state.model">
|
||||||
|
<option t-att-value="options[0]" t-esc="options[0]"/>
|
||||||
|
<option t-attf-value="{{ options[1] }}" t-esc="options[1]"/>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
state: any;
|
||||||
|
options: any;
|
||||||
|
setup() {
|
||||||
|
this.state = useState({ model: "b" });
|
||||||
|
this.options = ["a", "b"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Test, fixture);
|
||||||
|
expect(fixture.querySelector("select")!.value).toEqual("b");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-model with dynamic values on select options -- 3", async () => {
|
||||||
|
class Test extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<select t-model="state.model">
|
||||||
|
<option t-att-value="options[0]" t-esc="options[0]"/>
|
||||||
|
<option value="b" t-esc="options[1]"/>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
state: any;
|
||||||
|
options: any;
|
||||||
|
setup() {
|
||||||
|
this.state = useState({ model: "b" });
|
||||||
|
this.options = ["a", "b"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Test, fixture);
|
||||||
|
expect(fixture.querySelector("select")!.value).toEqual("b");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-model with dynamic values on select options in foreach", async () => {
|
||||||
|
class Test extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<select t-model="state.model">
|
||||||
|
<t t-foreach="options" t-as="v" t-key="v">
|
||||||
|
<option t-att-value="v" t-esc="v"/>
|
||||||
|
</t>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
state: any;
|
||||||
|
options: any;
|
||||||
|
setup() {
|
||||||
|
this.state = useState({ model: "b" });
|
||||||
|
this.options = ["a", "b", "c"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Test, fixture);
|
||||||
|
expect(fixture.querySelector("select")!.value).toEqual("b");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user