diff --git a/web/app/src/components/LocationSelect/index.test.tsx b/web/app/src/components/LocationSelect/index.test.tsx
new file mode 100644
index 0000000..2a32c49
--- /dev/null
+++ b/web/app/src/components/LocationSelect/index.test.tsx
@@ -0,0 +1,164 @@
+import {act, fireEvent, render, waitFor} from "@testing-library/react";
+import React from "react";
+import {LocationSelect} from "./index";
+import {ChakraProvider} from "@chakra-ui/react";
+import {Api, LocationsApi, pinballMap} from "../../api";
+import {fake} from "../../test";
+
+window.matchMedia = window.matchMedia || function () {
+ return {
+ matches: false,
+ addListener: function () {
+ },
+ removeListener: function () {
+ }
+ };
+};
+jest.useFakeTimers();
+
+jest.mock('../../api')
+const mockApi = jest.mocked(Api)
+const mockPinballMapApi = jest.mocked(pinballMap.Api)
+
+beforeEach(() => {
+ mockApi.prototype.parseError.mockReset()
+ mockApi.prototype.locationsApi.mockReset()
+
+ mockPinballMapApi.prototype.locationsGet.mockReset()
+ mockPinballMapApi.prototype.parseError.mockReset()
+ jest.resetAllMocks()
+})
+
+describe('LocationSelect', () => {
+ it('renders without error', async () => {
+ render(
+
+
+
+ )
+ });
+ it('invokes the onError callback if pinball map api call fails', async () => {
+ mockPinballMapApi.prototype.locationsGet.mockRejectedValue(false)
+ const mockError: pinballMap.ErrorResponse = {
+ errors: "test error",
+ }
+ mockPinballMapApi.prototype.parseError.mockReturnValue(mockError)
+
+ jest.mocked(LocationsApi).prototype.locationsGet.mockResolvedValue({
+ config: {},
+ data: {
+ locations: [fake.location()],
+ },
+ headers: {},
+ request: {},
+ status: 200,
+ statusText: "",
+ })
+ mockApi.prototype.locationsApi.mockReturnValue(new LocationsApi())
+
+ const mockOnError = jest.fn()
+
+ const wrapper = render(
+
+
+
+ )
+
+ await act(async () => {
+ fireEvent.change(wrapper.getByRole('combobox'), {target: {value: 'valid search'}})
+ jest.runAllTimers()
+ })
+
+ await waitFor(() => {
+ expect(mockOnError).toBeCalled()
+ })
+ })
+ it('invokes the onError callback when pinman api call fails', async () => {
+ const mockError = fake.errorResponse()
+ mockApi.prototype.parseError.mockReturnValue(mockError)
+
+ mockPinballMapApi.prototype.locationsGet.mockResolvedValue([
+ fake.pinballMapLocation(),
+ ])
+ jest.mocked(LocationsApi).prototype.locationsGet.mockRejectedValue(false)
+ mockApi.prototype.locationsApi.mockReturnValue(new LocationsApi())
+ const mockOnError = jest.fn()
+
+ const wrapper = render(
+
+
+
+ )
+
+ await act(async () => {
+ fireEvent.change(wrapper.getByRole('combobox'), {target: {value: 'te'}})
+ jest.advanceTimersByTime(500)
+ fireEvent.change(wrapper.getByRole('combobox'), {target: {value: 'test l'}})
+ jest.runAllTimers()
+ })
+
+ await waitFor(() => {
+ expect(mockOnError).toBeCalled()
+ })
+ })
+ it('renders with options when filter provided', async () => {
+ const mockPinballMapLocation = {
+ ...fake.pinballMapLocation(),
+ name: "test location",
+ id: 456,
+ }
+ const mockKnownPinballMapLocation = {
+ ...fake.pinballMapLocation(),
+ name: "should not be in results",
+ id: 123,
+ }
+ const mockPinmanLocation = {
+ ...fake.location(),
+ name: "test location 2",
+ pinball_map_id: 123,
+ }
+ mockPinballMapApi.prototype.locationsGet.mockResolvedValue([
+ mockPinballMapLocation,
+ mockKnownPinballMapLocation
+ ])
+ jest.mocked(LocationsApi).prototype.locationsGet.mockResolvedValue({
+ config: {},
+ data: {
+ locations: [
+ mockPinmanLocation,
+ ]
+ },
+ headers: {},
+ request: {},
+ status: 200,
+ statusText: "",
+ })
+ mockApi.prototype.locationsApi.mockReturnValue(new LocationsApi())
+
+ const wrapper = render(
+
+
+
+ )
+
+ jest.spyOn(global, 'setTimeout')
+
+ await act(async () => {
+ fireEvent.change(wrapper.getByRole('combobox'), {target: {value: 'te'}})
+ expect(setTimeout).not.toBeCalled()
+ })
+
+ await act(async () => {
+ fireEvent.change(wrapper.getByRole('combobox'), {target: {value: 'test'}})
+ jest.advanceTimersByTime(500)
+ fireEvent.change(wrapper.getByRole('combobox'), {target: {value: 'test l'}})
+ jest.runAllTimers()
+ })
+
+ await waitFor(async () => {
+ expect(await wrapper.findByText(mockPinballMapLocation.name)).toBeInTheDocument()
+ expect(await wrapper.findByText(mockPinmanLocation.name)).toBeInTheDocument()
+ expect(wrapper.baseElement).not.toHaveTextContent(mockKnownPinballMapLocation.name)
+ })
+ });
+});
diff --git a/web/app/src/components/LocationSelect/index.tsx b/web/app/src/components/LocationSelect/index.tsx
index 79a3dff..14ead76 100644
--- a/web/app/src/components/LocationSelect/index.tsx
+++ b/web/app/src/components/LocationSelect/index.tsx
@@ -1,6 +1,6 @@
import React, {useEffect, useState} from "react";
import {AxiosError} from "axios";
-import {Api, ErrorResponse, pinballMap} from "../../api";
+import {Api, pinballMap} from "../../api";
import {ActionMeta, Select, SingleValue} from "chakra-react-select";
export type LocationOption = {
@@ -19,8 +19,8 @@ const pinmanApi = new Api()
const pinballMapApi = new pinballMap.Api()
type LocationSelectProps = {
- onChange: (value: LocationOption) => void
- onError: (error: any) => void
+ onChange?: (value: LocationOption) => void
+ onError?: (error: any) => void
value?: LocationOption
}
@@ -78,10 +78,6 @@ export function LocationSelect(props: LocationSelectProps) {
}
function updatePinballMapLocations(nameFilter: string) {
- if (nameFilter.length < 4) {
- return
- }
-
pinballMapApi.locationsGet(nameFilter).then((locations) => {
setPinmapLocations(
locations.map((location): LocationOption => ({
@@ -108,6 +104,7 @@ export function LocationSelect(props: LocationSelectProps) {
useEffect(() => {
if (locationSelectFilterValue.length < 4) {
setLocationSelectIsLoading(false)
+ return
} else {
setLocationSelectIsLoading(true)
}
@@ -126,21 +123,22 @@ export function LocationSelect(props: LocationSelectProps) {
};
}, [locationSelectFilterValue]);
- function onChange(newValue: SingleValue, _ : ActionMeta) {
+ function onChange(newValue: SingleValue, _: ActionMeta) {
if (props.onChange) {
props.onChange(newValue as LocationOption)
}
}
return (
-