Spaces:
Running
Running
| import { test, describe, assert, afterEach, vi } from "vitest"; | |
| import { cleanup, render } from "@gradio/tootils"; | |
| import event from "@testing-library/user-event"; | |
| import { setupi18n } from "../app/src/i18n"; | |
| import Dropdown from "./Index.svelte"; | |
| import type { LoadingStatus } from "@gradio/statustracker"; | |
| const loading_status: LoadingStatus = { | |
| eta: 0, | |
| queue_position: 1, | |
| queue_size: 1, | |
| status: "complete" as LoadingStatus["status"], | |
| scroll_to_output: false, | |
| visible: true, | |
| fn_index: 0, | |
| show_progress: "full" | |
| }; | |
| describe("Dropdown", () => { | |
| afterEach(() => { | |
| cleanup(); | |
| vi.useRealTimers(); | |
| }); | |
| beforeEach(() => { | |
| setupi18n(); | |
| }); | |
| test("renders provided value", async () => { | |
| const { getByLabelText } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| max_choices: null, | |
| value: "choice", | |
| label: "Dropdown", | |
| choices: [ | |
| ["choice", "choice"], | |
| ["choice2", "choice2"] | |
| ], | |
| filterable: false, | |
| interactive: false | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| assert.equal(item.value, "choice"); | |
| }); | |
| test("selecting the textbox should show the options", async () => { | |
| const { getByLabelText, getAllByTestId } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| max_choices: 10, | |
| value: "choice", | |
| label: "Dropdown", | |
| choices: [ | |
| ["choice", "choice"], | |
| ["name2", "choice2"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await item.focus(); | |
| const options = getAllByTestId("dropdown-option"); | |
| expect(options).toHaveLength(2); | |
| expect(options[0]).toContainHTML("choice"); | |
| expect(options[1]).toContainHTML("name2"); | |
| }); | |
| test("editing the textbox value should trigger the type event and filter the options", async () => { | |
| const { getByLabelText, listen, getAllByTestId } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| max_choices: 10, | |
| value: "", | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple", "apple"], | |
| ["zebra", "zebra"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const key_up_event = listen("key_up"); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await item.focus(); | |
| const options = getAllByTestId("dropdown-option"); | |
| expect(options).toHaveLength(2); | |
| item.value = ""; | |
| await event.keyboard("z"); | |
| const options_new = getAllByTestId("dropdown-option"); | |
| await expect(options_new).toHaveLength(1); | |
| await expect(options[0]).toContainHTML("zebra"); | |
| await assert.equal(key_up_event.callCount, 1); | |
| }); | |
| test("blurring the textbox should cancel the filter", async () => { | |
| const { getByLabelText, listen } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| value: "default", | |
| label: "Dropdown", | |
| max_choices: undefined, | |
| choices: [ | |
| ["default", "default"], | |
| ["other", "other"] | |
| ], | |
| filterable: false, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| item.focus(); | |
| await event.keyboard("other"); | |
| }); | |
| test("blurring the textbox should save the input value", async () => { | |
| const { getByLabelText, listen } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| value: "new ", | |
| label: "Dropdown", | |
| max_choices: undefined, | |
| allow_custom_value: true, | |
| choices: [ | |
| ["dwight", "dwight"], | |
| ["michael", "michael"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| const change_event = listen("change"); | |
| item.focus(); | |
| await event.keyboard("kevin"); | |
| await item.blur(); | |
| assert.equal(item.value, "new kevin"); | |
| assert.equal(change_event.callCount, 1); | |
| }); | |
| test("focusing the label should toggle the options", async () => { | |
| const { getByLabelText, listen } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| value: "default", | |
| label: "Dropdown", | |
| choices: [ | |
| ["default", "default"], | |
| ["other", "other"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| const blur_event = listen("blur"); | |
| const focus_event = listen("focus"); | |
| item.focus(); | |
| item.blur(); | |
| assert.equal(blur_event.callCount, 1); | |
| assert.equal(focus_event.callCount, 1); | |
| }); | |
| test("deselecting and reselcting a filtered dropdown should show all options again", async () => { | |
| vi.useFakeTimers(); | |
| const { getByLabelText, getAllByTestId } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| max_choices: 10, | |
| value: "", | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple", "apple"], | |
| ["zebra", "zebra"], | |
| ["pony", "pony"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| item.focus(); | |
| item.value = ""; | |
| await event.keyboard("z"); | |
| const options = getAllByTestId("dropdown-option"); | |
| expect(options).toHaveLength(1); | |
| await item.blur(); | |
| // Mock 100ms delay between interactions. | |
| vi.runAllTimers(); | |
| await item.focus(); | |
| const options_new = getAllByTestId("dropdown-option"); | |
| expect(options_new).toHaveLength(3); | |
| }); | |
| test("passing in a new set of identical choices when the dropdown is open should not filter the dropdown", async () => { | |
| const { getByLabelText, getAllByTestId, component } = await render( | |
| Dropdown, | |
| { | |
| show_label: true, | |
| loading_status, | |
| value: "", | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple", "apple"], | |
| ["zebra", "zebra"], | |
| ["pony", "pony"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| } | |
| ); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await item.focus(); | |
| const options = getAllByTestId("dropdown-option"); | |
| expect(options).toHaveLength(3); | |
| component.$set({ | |
| value: "", | |
| choices: [ | |
| ["apple", "apple"], | |
| ["zebra", "zebra"], | |
| ["pony", "pony"] | |
| ] | |
| }); | |
| item.focus(); | |
| const options_new = getAllByTestId("dropdown-option"); | |
| expect(options_new).toHaveLength(3); | |
| }); | |
| test("setting a custom value when allow_custom_choice is false should revert to the first valid choice", async () => { | |
| const { getByLabelText, getAllByTestId, component } = await render( | |
| Dropdown, | |
| { | |
| show_label: true, | |
| loading_status, | |
| value: "", | |
| allow_custom_value: false, | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple", "apple"], | |
| ["zebra", "zebra"], | |
| ["pony", "pony"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| } | |
| ); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await item.focus(); | |
| await event.keyboard("pie"); | |
| expect(item.value).toBe("applepie"); | |
| await item.blur(); | |
| expect(item.value).toBe("apple"); | |
| }); | |
| test("setting a custom value when allow_custom_choice is true should keep the value", async () => { | |
| const { getByLabelText, getAllByTestId, component } = await render( | |
| Dropdown, | |
| { | |
| show_label: true, | |
| loading_status, | |
| value: "", | |
| allow_custom_value: true, | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple", "apple"], | |
| ["zebra", "zebra"], | |
| ["pony", "pony"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| } | |
| ); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await item.focus(); | |
| await event.keyboard("pie"); | |
| expect(item.value).toBe("applepie"); | |
| await item.blur(); | |
| expect(item.value).toBe("applepie"); | |
| }); | |
| test("setting a value should update the displayed value and selected indices", async () => { | |
| const { getByLabelText, getAllByTestId, component } = await render( | |
| Dropdown, | |
| { | |
| show_label: true, | |
| loading_status, | |
| value: "", | |
| allow_custom_value: false, | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple", "apple"], | |
| ["zebra", "zebra"], | |
| ["pony", "pony"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| } | |
| ); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| expect(item.value).toBe("apple"); | |
| await item.focus(); | |
| let options = getAllByTestId("dropdown-option"); | |
| expect(options[0]).toHaveClass("selected"); | |
| await component.$set({ value: "zebra" }); | |
| expect(item.value).toBe("zebra"); | |
| options = getAllByTestId("dropdown-option"); | |
| expect(options[0]).toHaveClass("selected"); | |
| await component.$set({ value: undefined }); | |
| expect(item.value).toBe(""); | |
| options = getAllByTestId("dropdown-option"); | |
| expect(options[0]).not.toHaveClass("selected"); | |
| await component.$set({ value: "zebra" }); | |
| expect(item.value).toBe("zebra"); | |
| options = getAllByTestId("dropdown-option"); | |
| expect(options[0]).toHaveClass("selected"); | |
| }); | |
| test("blurring a dropdown should set the input text to the previously selected value", async () => { | |
| const { getByLabelText, getAllByTestId, component } = await render( | |
| Dropdown, | |
| { | |
| show_label: true, | |
| loading_status, | |
| value: "", | |
| allow_custom_value: false, | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple", "apple_internal_value"], | |
| ["zebra", "zebra_internal_value"], | |
| ["pony", "pony_internal_value"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| } | |
| ); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| expect(item.value).toBe("apple"); | |
| await item.focus(); | |
| let options = getAllByTestId("dropdown-option"); | |
| expect(options[0]).toHaveClass("selected"); | |
| await item.blur(); | |
| expect(item.value).toBe("apple"); | |
| await item.focus(); | |
| await event.keyboard("z"); | |
| expect(item.value).toBe("applez"); | |
| await item.blur(); | |
| expect(item.value).toBe("apple"); | |
| }); | |
| test("updating choices should keep the dropdown focus-able and change the value appropriately if custom values are not allowed", async () => { | |
| const { getByLabelText, component } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| value: "apple_internal_value", | |
| allow_custom_value: false, | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple_choice", "apple_internal_value"], | |
| ["zebra_choice", "zebra_internal_value"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await expect(item.value).toBe("apple_choice"); | |
| component.$set({ | |
| choices: [ | |
| ["apple_new_choice", "apple_internal_value"], | |
| ["zebra_new_choice", "zebra_internal_value"] | |
| ] | |
| }); | |
| await item.focus(); | |
| await item.blur(); | |
| await expect(item.value).toBe("apple_new_choice"); | |
| }); | |
| test("updating choices should not reset the value if custom values are allowed", async () => { | |
| const { getByLabelText, component } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| value: "apple_internal_value", | |
| allow_custom_value: true, | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple_choice", "apple_internal_value"], | |
| ["zebra_choice", "zebra_internal_value"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await expect(item.value).toBe("apple_choice"); | |
| component.$set({ | |
| choices: [ | |
| ["apple_new_choice", "apple_internal_value"], | |
| ["zebra_new_choice", "zebra_internal_value"] | |
| ] | |
| }); | |
| await expect(item.value).toBe("apple_choice"); | |
| }); | |
| test("ensure dropdown can have an empty value", async () => { | |
| const { getByLabelText } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| allow_custom_value: false, | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple_choice", "apple_internal_value"], | |
| ["zebra_choice", "zebra_internal_value"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await expect(item.value).toBe(""); | |
| }); | |
| test("ensure dropdown works when initial value is undefined and allow custom value is set", async () => { | |
| const { getByLabelText } = await render(Dropdown, { | |
| show_label: true, | |
| loading_status, | |
| value: undefined, | |
| allow_custom_value: true, | |
| label: "Dropdown", | |
| choices: [ | |
| ["apple_choice", "apple_internal_value"], | |
| ["zebra_choice", "zebra_internal_value"] | |
| ], | |
| filterable: true, | |
| interactive: true | |
| }); | |
| const item: HTMLInputElement = getByLabelText( | |
| "Dropdown" | |
| ) as HTMLInputElement; | |
| await expect(item.value).toBe(""); | |
| }); | |
| }); | |