import { Component } from "../../src/component/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`
`; state = useState({ text: "" }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe("
"); const input = fixture.querySelector("input")!; await editInput(input, "test"); expect(comp.state.text).toBe("test"); expect(fixture.innerHTML).toBe("
test
"); }); test("basic use, on an input with bracket expression", async () => { class SomeComponent extends Component { static template = xml`
`; state = useState({ text: "" }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe("
"); const input = fixture.querySelector("input")!; await editInput(input, "test"); expect(comp.state.text).toBe("test"); expect(fixture.innerHTML).toBe("
test
"); }); test("throws if invalid expression", async () => { class SomeComponent extends Component { static template = xml`
`; 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`
`; some = useState({ text: "" }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe("
"); const input = fixture.querySelector("input")!; await editInput(input, "test"); expect(comp.some.text).toBe("test"); expect(fixture.innerHTML).toBe("
test
"); }); test("on an input, type=checkbox", async () => { class SomeComponent extends Component { static template = xml`
yes no
`; state = useState({ flag: false }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe('
no
'); let input = fixture.querySelector("input")!; input.click(); await nextTick(); expect(fixture.innerHTML).toBe('
yes
'); 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`
"); const textarea = fixture.querySelector("textarea")!; await editInput(textarea, "test"); expect(comp.state.text).toBe("test"); expect(fixture.innerHTML).toBe("
test
"); }); test("on an input type=radio", async () => { class SomeComponent extends Component { static template = xml`
Choice:
`; state = useState({ choice: "" }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe( '
Choice:
' ); const firstInput = fixture.querySelector("input")!; firstInput.click(); await nextTick(); expect(comp.state.choice).toBe("One"); expect(fixture.innerHTML).toBe( '
Choice: One
' ); const secondInput = fixture.querySelectorAll("input")[1]; secondInput.click(); await nextTick(); expect(comp.state.choice).toBe("Two"); expect(fixture.innerHTML).toBe( '
Choice: Two
' ); }); test("on an input type=radio, with initial value", async () => { class SomeComponent extends Component { static template = xml`
`; state = useState({ choice: "Two" }); } await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe( '
' ); const secondInput = fixture.querySelectorAll("input")[1]; expect(secondInput.checked).toBe(true); }); test("on a select", async () => { class SomeComponent extends Component { static template = xml`
Choice:
`; state = useState({ color: "" }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe( '
Choice:
' ); 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( '
Choice: red
' ); }); test("on a select, initial state", async () => { class SomeComponent extends Component { static template = xml`
`; 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`
`; state = useState({ something: { text: "" } }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe("
"); const input = fixture.querySelector("input")!; await editInput(input, "test"); expect(comp.state.something.text).toBe("test"); expect(fixture.innerHTML).toBe("
test
"); }); test(".lazy modifier", async () => { class SomeComponent extends Component { static template = xml`
`; state = useState({ text: "" }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe("
"); const input = fixture.querySelector("input")!; input.value = "test"; input.dispatchEvent(new Event("input")); await nextTick(); expect(comp.state.text).toBe(""); expect(fixture.innerHTML).toBe("
"); input.dispatchEvent(new Event("change")); await nextTick(); expect(comp.state.text).toBe("test"); expect(fixture.innerHTML).toBe("
test
"); }); test(".trim modifier", async () => { class SomeComponent extends Component { static template = xml`
`; 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("
test
"); }); test(".number modifier", async () => { class SomeComponent extends Component { static template = xml`
`; state = useState({ number: 0 }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe("
0
"); const input = fixture.querySelector("input")!; await editInput(input, "13"); expect(comp.state.number).toBe(13); expect(fixture.innerHTML).toBe("
13
"); await editInput(input, "invalid"); expect(comp.state.number).toBe("invalid"); expect(fixture.innerHTML).toBe("
invalid
"); }); test("in a t-foreach", async () => { class SomeComponent extends Component { static template = xml`
`; state = useState([ { f: false, id: 1 }, { f: false, id: 2 }, { f: false, id: 3 }, ]); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe( '
' ); 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`
`; 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("two inputs in a div alternating with a t-if", async () => { class SomeComponent extends Component { static template = xml`
`; state = useState({ flag: true, text1: "", text2: "" }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe('
'); 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('
'); 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`
`; state = useState({ text: "Jean-Luc Picard" }); } const comp = await mount(SomeComponent, fixture); expect(fixture.innerHTML).toBe("
"); 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`
`; 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`
`; 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"); }); });