mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
70101e4c66
Before to this commit, the .trim modifier did not work correctly. The value in the model is always trim at each input event but not the visual value in the input. This difference between the visual and the model value cause few strange bug. After reflection, we think that we always want to be in .lazy when we use .trim. Because we want to trim the final value during the onchange event and not at each input event. If we do it at each input event, we can't write more then one word easily. So this commit change the behavior of the .trim modifier to always be in .lazy
738 lines
24 KiB
TypeScript
738 lines
24 KiB
TypeScript
import { Component } from "../../src/runtime/component";
|
|
import { mount, useState, xml } from "../../src/index";
|
|
import { editInput, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
|
|
|
snapshotEverything();
|
|
|
|
let fixture: HTMLElement;
|
|
|
|
beforeEach(() => {
|
|
fixture = makeTestFixture();
|
|
});
|
|
|
|
describe("t-model directive", () => {
|
|
test("basic use, on an input", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model="state.text"/>
|
|
<span><t t-esc="state.text"/></span>
|
|
</div>`;
|
|
state = useState({ text: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
await editInput(input, "test");
|
|
expect(comp.state.text).toBe("test");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
});
|
|
|
|
test("t-model on an input with an undefined value", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<input t-model="state.text"/>`;
|
|
state = useState({ text: undefined });
|
|
}
|
|
await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<input>");
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
expect(input.value).toBe("");
|
|
});
|
|
|
|
test("basic use, on an input with bracket expression", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model="state['text']"/>
|
|
<span><t t-esc="state.text"/></span>
|
|
</div>`;
|
|
state = useState({ text: "" });
|
|
}
|
|
|
|
const comp = await mount(SomeComponent, fixture);
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
await editInput(input, "test");
|
|
expect(comp.state.text).toBe("test");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
});
|
|
|
|
test("throws if invalid expression", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model="state"/>
|
|
</div>`;
|
|
state = useState({ text: "" });
|
|
}
|
|
let error: Error;
|
|
try {
|
|
await mount(SomeComponent, fixture);
|
|
} catch (e) {
|
|
error = e as Error;
|
|
}
|
|
expect(error!).toBeDefined();
|
|
expect(error!.message).toBe(`Invalid t-model expression: "state" (it should be assignable)`);
|
|
});
|
|
|
|
test("basic use, on another key in component", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<div>
|
|
<input t-model="some.text"/>
|
|
<span><t t-esc="some.text"/></span>
|
|
</div>`;
|
|
some = useState({ text: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
await editInput(input, "test");
|
|
expect(comp.some.text).toBe("test");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
});
|
|
|
|
test("on an input, type=checkbox", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<div>
|
|
<input type="checkbox" t-model="state.flag"/>
|
|
<span>
|
|
<t t-if="state.flag">yes</t>
|
|
<t t-else="">no</t>
|
|
</span>
|
|
</div>`;
|
|
state = useState({ flag: false });
|
|
}
|
|
|
|
const comp = await mount(SomeComponent, fixture);
|
|
expect(fixture.innerHTML).toBe('<div><input type="checkbox"><span>no</span></div>');
|
|
|
|
let input = fixture.querySelector("input")!;
|
|
input.click();
|
|
await nextTick();
|
|
expect(fixture.innerHTML).toBe('<div><input type="checkbox"><span>yes</span></div>');
|
|
expect(comp.state.flag).toBe(true);
|
|
|
|
input.click();
|
|
await nextTick();
|
|
expect(comp.state.flag).toBe(false);
|
|
});
|
|
|
|
test("on an textarea", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<div>
|
|
<textarea t-model="state.text"/>
|
|
<span><t t-esc="state.text"/></span>
|
|
</div>`;
|
|
state = useState({ text: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<div><textarea></textarea><span></span></div>");
|
|
|
|
const textarea = fixture.querySelector("textarea")!;
|
|
await editInput(textarea, "test");
|
|
expect(comp.state.text).toBe("test");
|
|
expect(fixture.innerHTML).toBe("<div><textarea></textarea><span>test</span></div>");
|
|
});
|
|
|
|
test("on an input type=radio", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<div>
|
|
<input type="radio" id="one" value="One" t-model="state.choice"/>
|
|
<input type="radio" id="two" value="Two" t-model="state.choice"/>
|
|
<span>Choice: <t t-esc="state.choice"/></span>
|
|
</div>`;
|
|
state = useState({ choice: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: </span></div>'
|
|
);
|
|
|
|
const firstInput = fixture.querySelector("input")!;
|
|
firstInput.click();
|
|
await nextTick();
|
|
expect(comp.state.choice).toBe("One");
|
|
expect(fixture.innerHTML).toBe(
|
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: One</span></div>'
|
|
);
|
|
|
|
const secondInput = fixture.querySelectorAll("input")[1];
|
|
secondInput.click();
|
|
await nextTick();
|
|
expect(comp.state.choice).toBe("Two");
|
|
expect(fixture.innerHTML).toBe(
|
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: Two</span></div>'
|
|
);
|
|
});
|
|
|
|
test("on an input type=radio, with initial value", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<div>
|
|
<input type="radio" id="one" value="One" t-model="state.choice"/>
|
|
<input type="radio" id="two" value="Two" t-model="state.choice"/>
|
|
</div>`;
|
|
state = useState({ choice: "Two" });
|
|
}
|
|
await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"></div>'
|
|
);
|
|
|
|
const secondInput = fixture.querySelectorAll("input")[1];
|
|
expect(secondInput.checked).toBe(true);
|
|
});
|
|
|
|
test("on a select", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<div>
|
|
<select t-model="state.color">
|
|
<option value="">Please select one</option>
|
|
<option value="red">Red</option>
|
|
<option value="blue">Blue</option>
|
|
</select>
|
|
<span>Choice: <t t-esc="state.color"/></span>
|
|
</div>`;
|
|
state = useState({ color: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
'<div><select><option value="">Please select one</option><option value="red">Red</option><option value="blue">Blue</option></select><span>Choice: </span></div>'
|
|
);
|
|
|
|
const select = fixture.querySelector("select")!;
|
|
select.value = "red";
|
|
select.dispatchEvent(new Event("change"));
|
|
await nextTick();
|
|
|
|
expect(comp.state.color).toBe("red");
|
|
expect(fixture.innerHTML).toBe(
|
|
'<div><select><option value="">Please select one</option><option value="red">Red</option><option value="blue">Blue</option></select><span>Choice: red</span></div>'
|
|
);
|
|
});
|
|
|
|
test("on a select, initial state", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<select t-model="state.color">
|
|
<option value="">Please select one</option>
|
|
<option value="red">Red</option>
|
|
<option value="blue">Blue</option>
|
|
</select>
|
|
</div>
|
|
`;
|
|
state = useState({ color: "red" });
|
|
}
|
|
await mount(SomeComponent, fixture);
|
|
const select = fixture.querySelector("select")!;
|
|
expect(select.value).toBe("red");
|
|
});
|
|
|
|
test("on a sub state key", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model="state.something.text"/>
|
|
<span><t t-esc="state.something.text"/></span>
|
|
</div>
|
|
`;
|
|
state = useState({ something: { text: "" } });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
await editInput(input, "test");
|
|
expect(comp.state.something.text).toBe("test");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
});
|
|
|
|
test("with expression having a changing key", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model="state.something[text.key]"/>
|
|
<span><t t-esc="state.something[text.key]"/></span>
|
|
</div>
|
|
`;
|
|
state: { something: { [key: string]: string } } = useState({ something: {} });
|
|
text = useState({ key: "foo" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
let input = fixture.querySelector("input")!;
|
|
await editInput(input, "footest");
|
|
expect(comp.state.something[comp.text.key]).toBe("footest");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>footest</span></div>");
|
|
|
|
comp.text.key = "bar";
|
|
await nextTick();
|
|
input = fixture.querySelector("input")!;
|
|
await editInput(input, "test bar");
|
|
expect(comp.state.something[comp.text.key]).toBe("test bar");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test bar</span></div>");
|
|
});
|
|
|
|
test(".lazy modifier", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model.lazy="state.text"/>
|
|
<span><t t-esc="state.text"/></span>
|
|
</div>
|
|
`;
|
|
state = useState({ text: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
input.value = "test";
|
|
input.dispatchEvent(new Event("input"));
|
|
await nextTick();
|
|
expect(comp.state.text).toBe("");
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
input.dispatchEvent(new Event("change"));
|
|
await nextTick();
|
|
expect(comp.state.text).toBe("test");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
});
|
|
|
|
test(".trim modifier", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model.trim="state.text"/>
|
|
<span><t t-esc="state.text"/></span>
|
|
</div>
|
|
`;
|
|
state = useState({ text: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
await editInput(input, " test ");
|
|
expect(comp.state.text).toBe("test");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
});
|
|
|
|
test(".trim modifier implies .lazy modifier", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model.trim="state.text"/>
|
|
<span><t t-esc="state.text"/></span>
|
|
</div>
|
|
`;
|
|
state = useState({ text: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
input.value = "test ";
|
|
input.dispatchEvent(new Event("input"));
|
|
await nextTick();
|
|
expect(comp.state.text).toBe("");
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
input.dispatchEvent(new Event("change"));
|
|
await nextTick();
|
|
expect(comp.state.text).toBe("test");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
});
|
|
|
|
test(".number modifier", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model.number="state.number"/>
|
|
<span><t t-esc="state.number"/></span>
|
|
</div>
|
|
`;
|
|
state = useState({ number: 0 });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
expect(fixture.innerHTML).toBe("<div><input><span>0</span></div>");
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
await editInput(input, "13");
|
|
expect(comp.state.number).toBe(13);
|
|
expect(fixture.innerHTML).toBe("<div><input><span>13</span></div>");
|
|
|
|
await editInput(input, "invalid");
|
|
expect(comp.state.number).toBe("invalid");
|
|
expect(fixture.innerHTML).toBe("<div><input><span>invalid</span></div>");
|
|
});
|
|
|
|
test("in a t-foreach", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<t t-foreach="state" t-as="thing" t-key="thing.id">
|
|
<input type="checkbox" t-model="thing.f"/>
|
|
</t>
|
|
</div>
|
|
`;
|
|
state = useState([
|
|
{ f: false, id: 1 },
|
|
{ f: false, id: 2 },
|
|
{ f: false, id: 3 },
|
|
]);
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
'<div><input type="checkbox"><input type="checkbox"><input type="checkbox"></div>'
|
|
);
|
|
|
|
const input = fixture.querySelectorAll("input")[1]!;
|
|
input.click();
|
|
expect(comp.state[1].f).toBe(true);
|
|
expect(comp.state[0].f).toBe(false);
|
|
expect(comp.state[2].f).toBe(false);
|
|
});
|
|
|
|
test("in a t-foreach, part 2", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<t t-foreach="state" t-as="thing" t-key="thing_index" >
|
|
<input t-model="state[thing_index]"/>
|
|
</t>
|
|
</div>
|
|
`;
|
|
state = useState(["zuko", "iroh"]);
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
expect(comp.state).toEqual(["zuko", "iroh"]);
|
|
|
|
const input = fixture.querySelectorAll("input")[1]!;
|
|
await editInput(input, "uncle iroh");
|
|
expect(comp.state).toEqual(["zuko", "uncle iroh"]);
|
|
});
|
|
|
|
test("in a t-foreach, part 3", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<t t-foreach="names" t-as="name" t-key="name_index">
|
|
<input t-model="state.values[name]"/>
|
|
</t>
|
|
</div>
|
|
`;
|
|
names = ["Crusher", "Data", "Riker", "Worf"];
|
|
state = useState({ values: {} });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
expect(comp.state).toEqual({ values: {} });
|
|
|
|
const input = fixture.querySelectorAll("input")[1]!;
|
|
await editInput(input, "Commander");
|
|
expect(comp.state).toEqual({ values: { Data: "Commander" } });
|
|
});
|
|
|
|
test("two inputs in a div alternating with a t-if", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input class="a" t-if="state.flag" t-model="state.text1"/>
|
|
<input class="b" t-if="!state.flag" t-model="state.text2"/>
|
|
</div>
|
|
`;
|
|
state = useState({ flag: true, text1: "", text2: "" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe('<div><input class="a"></div>');
|
|
let input = fixture.querySelector("input")!;
|
|
expect(input.value).toBe("");
|
|
await editInput(input, "Jean-Luc");
|
|
expect(comp.state.text1).toBe("Jean-Luc");
|
|
|
|
comp.state.flag = false;
|
|
await nextTick();
|
|
|
|
expect(fixture.innerHTML).toBe('<div><input class="b"></div>');
|
|
input = fixture.querySelector("input")!;
|
|
expect(input.value).toBe("");
|
|
await editInput(input, "Picard");
|
|
expect(comp.state.text2).toBe("Picard");
|
|
});
|
|
|
|
test("following a scope protecting directive (e.g. t-set)", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<t t-set="admiral" t-value="'Bruno'"/>
|
|
<input t-model="state['text']"/>
|
|
</div>
|
|
`;
|
|
state = useState({ text: "Jean-Luc Picard" });
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
expect(fixture.innerHTML).toBe("<div><input></div>");
|
|
const input = fixture.querySelector("input")!;
|
|
expect(input.value).toBe("Jean-Luc Picard");
|
|
await editInput(input, "Commander Data");
|
|
expect(comp.state.text).toBe("Commander Data");
|
|
});
|
|
|
|
test("can also define t-on directive on same event, part 1", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model="state['text']" t-on-input="onInput"/>
|
|
</div>
|
|
`;
|
|
state = useState({ text: "", other: "" });
|
|
onInput(ev: InputEvent) {
|
|
this.state.other = (ev.target as HTMLInputElement).value;
|
|
}
|
|
}
|
|
const comp = await mount(SomeComponent, fixture);
|
|
expect(comp.state.text).toBe("");
|
|
expect(comp.state.other).toBe("");
|
|
const input = fixture.querySelector("input")!;
|
|
await editInput(input, "Beam me up, Scotty");
|
|
expect(comp.state.text).toBe("Beam me up, Scotty");
|
|
expect(comp.state.other).toBe("Beam me up, Scotty");
|
|
});
|
|
|
|
test("can also define t-on directive on same event, part 2", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input type="radio" id="one" value="One" t-model="state.choice" t-on-click="onClick"/>
|
|
<input type="radio" id="two" value="Two" t-model="state.choice" t-on-click="onClick"/>
|
|
<input type="radio" id="three" value="Three" t-model="state.choice" t-on-click="onClick"/>
|
|
</div>
|
|
`;
|
|
state = useState({ choice: "", lastClicked: "" });
|
|
onClick(ev: MouseEvent) {
|
|
this.state.lastClicked = (ev.target as HTMLInputElement).value;
|
|
}
|
|
}
|
|
|
|
const comp = await mount(SomeComponent, fixture);
|
|
expect(comp.state.choice).toBe("");
|
|
expect(comp.state.lastClicked).toBe("");
|
|
|
|
const lastInput = fixture.querySelectorAll("input")[2];
|
|
lastInput.click();
|
|
await nextTick();
|
|
expect(comp.state.choice).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");
|
|
});
|
|
|
|
test("t-model with dynamic number values on select options in foreach", async () => {
|
|
class Test extends Component {
|
|
static template = xml`
|
|
<select t-model.number="state.value">
|
|
<t t-foreach="state.options" t-as="o" t-key="o.value">
|
|
<option t-att-value="o.value" t-esc="o.value"/>
|
|
</t>
|
|
</select>
|
|
`;
|
|
state: any;
|
|
setup() {
|
|
this.state = useState({
|
|
value: 2,
|
|
options: [{ value: 1 }, { value: 2 }, { value: 3 }],
|
|
});
|
|
}
|
|
}
|
|
|
|
const comp = await mount(Test, fixture);
|
|
// check that we have a value of 2 selected
|
|
expect(fixture.querySelector("select")!.value).toEqual("2");
|
|
expect(comp.state.value).toBe(2);
|
|
|
|
// emulate a click on the option=3 element
|
|
fixture.querySelectorAll("option")[2].selected = true;
|
|
fixture.querySelector("select")!.dispatchEvent(new Event("change"));
|
|
|
|
await nextTick();
|
|
// check that we have now selected the number 3 (and not the string)
|
|
expect(fixture.querySelector("select")!.value).toEqual("3");
|
|
expect(comp.state.value).toBe(3);
|
|
});
|
|
|
|
test("t-model is applied before t-on-input", async () => {
|
|
expect.assertions(3);
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<input t-model="state['text']" t-on-input="onInput"/>
|
|
</div>
|
|
`;
|
|
state = useState({ text: "", other: "" });
|
|
onInput(ev: InputEvent) {
|
|
expect(this.state.text).toBe("Beam me up, Scotty");
|
|
expect((ev.target as HTMLInputElement).value).toBe("Beam me up, Scotty");
|
|
}
|
|
}
|
|
await mount(SomeComponent, fixture);
|
|
const input = fixture.querySelector("input")!;
|
|
await editInput(input, "Beam me up, Scotty");
|
|
});
|
|
|
|
test("t-model with radio button group in t-foreach", async () => {
|
|
expect.assertions(6);
|
|
const steps: string[] = [];
|
|
class SomeComponent extends Component {
|
|
static template = xml`
|
|
<div t-on-click="getData" id="get_data">
|
|
<t t-foreach="options" t-as="opt" t-key="opt">
|
|
<input type="radio" name="radio_group" t-model="state.group" t-att-value="opt" t-att-id="opt"/>
|
|
</t>
|
|
</div>
|
|
`;
|
|
state = useState({ group: "scotty" });
|
|
options = ["beam", "scotty"];
|
|
|
|
getData() {
|
|
steps.push(`group: ${this.state.group}`);
|
|
}
|
|
}
|
|
await mount(SomeComponent, fixture);
|
|
const divEl = fixture.querySelector("#get_data") as HTMLElement;
|
|
expect(fixture.querySelector("input:checked")!.getAttribute("id")).toBe("scotty");
|
|
divEl.click();
|
|
expect(steps).toEqual(["group: scotty"]);
|
|
fixture.querySelector("input")!.click();
|
|
expect(steps).toEqual(["group: scotty", "group: beam"]);
|
|
await nextTick();
|
|
expect(fixture.querySelector("input:checked")!.getAttribute("id")).toBe("beam");
|
|
divEl.click();
|
|
expect(steps).toEqual(["group: scotty", "group: beam", "group: beam"]);
|
|
});
|
|
});
|