{}} />)
const input = screen.getByRole("textbox") as HTMLInputElement
assert.equal(input.value, "2.300")
})
it("rounds decimals", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "2.3456" } })
fireEvent.blur(input)
assert(onChange.calledWith(2.346), "" + onChange.args[0])
})
})
describe("comma as decimal separator", function () {
it("accepts comma as decimal separator", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "2,3" } })
fireEvent.blur(input)
assert(onChange.calledWith(2.3), "Expected 2.3 but got " + onChange.args[0])
})
it("accepts comma with multiple decimal places", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "123,456" } })
fireEvent.blur(input)
assert(onChange.calledWith(123.456), "Expected 123.456 but got " + onChange.args[0])
})
it("accepts negative numbers with comma", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "-5,25" } })
fireEvent.blur(input)
assert(onChange.calledWith(-5.25), "Expected -5.25 but got " + onChange.args[0])
})
it("rounds comma decimals with decimalPlaces", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "3,14159" } })
fireEvent.blur(input)
assert(onChange.calledWith(3.14), "Expected 3.14 but got " + onChange.args[0])
})
})
describe("integer mode", function () {
it("parses integers correctly", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "42" } })
fireEvent.blur(input)
assert(onChange.calledWith(42))
})
it("parses negative integers correctly", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "-10" } })
fireEvent.blur(input)
assert(onChange.calledWith(-10))
})
it("does not accept decimal input in integer mode", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "3.5" } })
fireEvent.blur(input)
assert(!onChange.called, "onChange should not be called for decimal input in integer mode")
})
})
describe("min/max validation", function () {
it("rejects values above max", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "15" } })
fireEvent.blur(input)
assert(!onChange.called, "onChange should not be called for value above max")
})
it("rejects values below min", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "-5" } })
fireEvent.blur(input)
assert(!onChange.called, "onChange should not be called for value below min")
})
it("accepts values within range", function () {
const onChange = sinon.spy()
render()
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "5" } })
fireEvent.blur(input)
assert(onChange.calledWith(5))
})
})
})
describe("Toggle", function () {
const options = [{ value: "a", label: "A" }, { value: "b", label: "B" }, { value: "c", label: "C" }]
/** Simulates a shrink-to-fit parent whose rounded width is one pixel under the button
* widths when joined and comfortably over them when wrapped */
function stubLayout(parent: HTMLElement) {
const group = parent.firstElementChild as HTMLElement
const parentWidth = () => (group.classList.contains("flex-wrap") ? 99.8 : 91.8)
for (const child of Array.from(group.children)) {
;(child as HTMLElement).getBoundingClientRect = () => ({ width: 30.6 } as DOMRect)
Object.defineProperty(child, "offsetWidth", { get: () => 31 })
}
parent.getBoundingClientRect = () => ({ width: parentWidth() } as DOMRect)
Object.defineProperty(parent, "clientWidth", { get: () => Math.round(parentWidth()) })
}
it("does not flip between layouts forever when the parent width follows the layout", function () {
const { container, rerender } = render(
value="a" onChange={() => {}} options={options} />
)
stubLayout(container.firstElementChild as HTMLElement)
// Every update re-measures; a loop here throws "Maximum update depth exceeded"
for (const value of ["b", "c", "a", "b"]) {
rerender(
value={value} onChange={() => {}} options={options} />
)
}
assert.equal(container.querySelectorAll("button").length, 3)
})
})
})