-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
web-wallet: Add
ExclusiveChoice
component
Resolves #2070
- Loading branch information
1 parent
fa4ebf1
commit 9523128
Showing
13 changed files
with
681 additions
and
3 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
web-wallet/src/lib/dusk/components/ExclusiveChoice/ExclusiveChoice.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.dusk-exclusive-choice { | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
|
||
.dusk-exclusive-choice__label { | ||
flex: 1; | ||
} | ||
|
||
.dusk-exclusive-choice__radio { | ||
position: absolute; | ||
top: -9999px; | ||
left: -9999px; | ||
} |
50 changes: 50 additions & 0 deletions
50
web-wallet/src/lib/dusk/components/ExclusiveChoice/ExclusiveChoice.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<svelte:options immutable={true} /> | ||
|
||
<script> | ||
import { isType } from "lamb"; | ||
import { makeClassName, randomUUID } from "$lib/dusk/string"; | ||
import "./ExclusiveChoice.css"; | ||
/** @type {string | undefined} */ | ||
export let className = undefined; | ||
/** @type {string | undefined} */ | ||
export let name = undefined; | ||
/** @type {SelectOption[] | String[]} */ | ||
export let options; | ||
/** @type {string} */ | ||
export let value; | ||
/** @type {(v: any) => v is string} */ | ||
const isString = isType("String"); | ||
const baseId = randomUUID(); | ||
$: classes = makeClassName(["dusk-exclusive-choice", className]); | ||
</script> | ||
|
||
<div class={classes} role="radiogroup" {...$$restProps}> | ||
{#each options as option (option)} | ||
{@const isStringOption = isString(option)} | ||
{@const optionValue = isStringOption ? option : option.value} | ||
{@const id = `${baseId}-${optionValue}`} | ||
<input | ||
bind:group={value} | ||
class="dusk-exclusive-choice__radio" | ||
checked={optionValue === value} | ||
disabled={isStringOption ? false : option.disabled} | ||
{id} | ||
name={name ?? baseId} | ||
on:change | ||
type="radio" | ||
value={optionValue} | ||
/> | ||
<label class="dusk-exclusive-choice__label" for={id} | ||
>{isStringOption ? option : option.label ?? optionValue}</label | ||
> | ||
{/each} | ||
</div> |
112 changes: 112 additions & 0 deletions
112
web-wallet/src/lib/dusk/components/__tests__/ExclusiveChoice.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; | ||
import { cleanup, fireEvent, render } from "@testing-library/svelte"; | ||
|
||
import { ExclusiveChoice } from ".."; | ||
|
||
vi.mock("$lib/dusk/string", async (importOriginal) => { | ||
/** @type {typeof import("$lib/dusk/string")} */ | ||
const original = await importOriginal(); | ||
|
||
return { | ||
...original, | ||
randomUUID: () => "some-generated-id", | ||
}; | ||
}); | ||
|
||
describe("ExclusiveChoice", () => { | ||
const stringOptions = ["one", "two", "three", "four"]; | ||
|
||
/** @type {SelectOption[]} */ | ||
const objectOptionsA = [ | ||
{ label: "one", value: "1" }, | ||
{ label: "two", value: "2" }, | ||
{ disabled: true, label: "three", value: "3" }, | ||
{ label: "four", value: "4" }, | ||
]; | ||
|
||
/** @type {SelectOption[]} */ | ||
const objectOptionsB = [ | ||
{ value: "1" }, | ||
{ value: "2" }, | ||
{ value: "3" }, | ||
{ value: "4" }, | ||
]; | ||
|
||
const baseProps = { | ||
options: objectOptionsA, | ||
value: "2", | ||
}; | ||
const baseOptions = { | ||
props: baseProps, | ||
target: document.body, | ||
}; | ||
|
||
afterEach(cleanup); | ||
|
||
afterAll(() => { | ||
vi.doUnmock("$lib/dusk/string"); | ||
}); | ||
|
||
it("should render the `ExclusiveChoice` component", () => { | ||
const { container } = render(ExclusiveChoice, baseOptions); | ||
|
||
expect(container.firstChild).toMatchSnapshot(); | ||
}); | ||
|
||
it("should accept a custom name for the radio elements", () => { | ||
const props = { | ||
...baseProps, | ||
name: "my-custom-name", | ||
}; | ||
const { container } = render(ExclusiveChoice, { ...baseOptions, props }); | ||
|
||
expect(container.firstChild).toMatchSnapshot(); | ||
}); | ||
|
||
it("should accept an array of options object without labels and use the value as labels", () => { | ||
const props = { | ||
...baseProps, | ||
options: objectOptionsB, | ||
}; | ||
const { container } = render(ExclusiveChoice, { ...baseOptions, props }); | ||
|
||
expect(container.firstChild).toMatchSnapshot(); | ||
}); | ||
|
||
it("should accept an array of string as options", () => { | ||
const props = { | ||
...baseProps, | ||
options: stringOptions, | ||
}; | ||
const { container } = render(ExclusiveChoice, { ...baseOptions, props }); | ||
|
||
expect(container.firstChild).toMatchSnapshot(); | ||
}); | ||
|
||
it("should pass additional class names and attributes to the rendered element", () => { | ||
const props = { | ||
...baseProps, | ||
className: "foo bar", | ||
id: "some-id", | ||
}; | ||
const { container } = render(ExclusiveChoice, { ...baseOptions, props }); | ||
|
||
expect(container.firstChild).toMatchSnapshot(); | ||
}); | ||
|
||
it("should accept a change event handler", async () => { | ||
const changeHandler = vi.fn(); | ||
const { component, container } = render(ExclusiveChoice, baseOptions); | ||
const target = /** @type {HTMLInputElement} */ ( | ||
container.querySelector("input[value='4']") | ||
); | ||
|
||
component.$on("change", changeHandler); | ||
|
||
await fireEvent.click(target); | ||
|
||
expect(changeHandler).toHaveBeenCalledTimes(1); | ||
expect(changeHandler).toHaveBeenCalledWith(expect.any(Event)); | ||
expect(target.checked).toBe(true); | ||
}); | ||
}); |
Oops, something went wrong.