Skip to content

Commit

Permalink
tests(leagues-frontend): Update tests for Form and new LocationSelect…
Browse files Browse the repository at this point in the history
… component
  • Loading branch information
alexstojda committed Jul 31, 2023
1 parent 4c135e6 commit a64244e
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 26 deletions.
164 changes: 164 additions & 0 deletions web/app/src/components/LocationSelect/index.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<ChakraProvider>
<LocationSelect/>
</ChakraProvider>
)
});
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(
<ChakraProvider>
<LocationSelect onError={mockOnError}/>
</ChakraProvider>
)

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(
<ChakraProvider>
<LocationSelect onError={mockOnError}/>
</ChakraProvider>
)

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(
<ChakraProvider>
<LocationSelect/>
</ChakraProvider>
)

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)
})
});
});
30 changes: 14 additions & 16 deletions web/app/src/components/LocationSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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
}

Expand Down Expand Up @@ -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 => ({
Expand All @@ -108,6 +104,7 @@ export function LocationSelect(props: LocationSelectProps) {
useEffect(() => {
if (locationSelectFilterValue.length < 4) {
setLocationSelectIsLoading(false)
return
} else {
setLocationSelectIsLoading(true)
}
Expand All @@ -126,21 +123,22 @@ export function LocationSelect(props: LocationSelectProps) {
};
}, [locationSelectFilterValue]);

function onChange(newValue: SingleValue<LocationOption>, _ : ActionMeta<LocationOption>) {
function onChange(newValue: SingleValue<LocationOption>, _: ActionMeta<LocationOption>) {
if (props.onChange) {
props.onChange(newValue as LocationOption)
}
}

return (
<Select isMulti={false}
onChange={onChange}
isLoading={locationSelectIsLoading}
onInputChange={(newValue) => {
setLocationSelectFilterValue(newValue)
}}
options={locationOptions}
value={props.value}
<Select
isMulti={false}
onChange={onChange}
isLoading={locationSelectIsLoading}
onInputChange={(newValue) => {
setLocationSelectFilterValue(newValue)
}}
options={locationOptions}
value={props.value}
/>
)
}
75 changes: 66 additions & 9 deletions web/app/src/pages/Leagues/Form.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {act, render, RenderResult} from "@testing-library/react";
import {act, render, RenderResult, waitFor} from "@testing-library/react";
import LeagueForm from "./Form";
import {faker} from "@faker-js/faker";
import {Simulate} from "react-dom/test-utils";
import {randomInt} from "crypto";
import * as helpers from '../../helpers'
import {Api, LeaguesApi} from "../../api";
import {Api, LeaguesApi, LocationsApi} from "../../api";
import {fake} from "../../test";
import {LocationOption} from "../../components/LocationSelect";

jest.mock('../../api')
const mockApi = jest.mocked(Api)
Expand All @@ -16,6 +17,22 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockedNavigate,
}));

jest.mock('../../components/LocationSelect', () => ({
__esModule: true,
LocationSelect: (props: { onChange: (o: LocationOption) => void }) => <input
type='text'
onChange={(e) => {
props.onChange({
label: 'test-location',
type: 'pinman',
value: e.target.value,
pinballMapId: '1',
})
}}
placeholder={'test-location'}
/>,
}));

beforeEach(() => {
mockedNavigate.mockReset()
mockApi.prototype.leaguesApi.mockReset()
Expand Down Expand Up @@ -51,9 +68,8 @@ describe('LeagueForm', function () {
Simulate.click(submitButton)
})

expect(mockedNavigate).toHaveBeenCalledWith('/leagues')
await waitFor(() => expect(mockedNavigate).toBeCalledWith('/leagues'))
})

it('renders error from API', async () => {
const mockError = fake.errorResponse()
mockApi.prototype.parseError.mockReturnValue(mockError)
Expand All @@ -71,9 +87,50 @@ describe('LeagueForm', function () {
Simulate.click(submitButton)
})

expect(mockApi.prototype.parseError).toBeCalled()
expect(result.getByText(mockError.title)).toBeInTheDocument()
await waitFor(() => {
expect(mockApi.prototype.parseError).toBeCalled()
expect(result.getByText(mockError.title)).toBeInTheDocument()
})
})
it(
'creates new location with pinball map location and redirects to /leagues if successful',
async function () {
jest.mocked(LeaguesApi).prototype.leaguesPost.mockResolvedValue({
config: {},
data: {
league: fake.league()
},
headers: {},
request: {},
status: 201,
statusText: "",
})
mockApi.prototype.leaguesApi.mockReturnValue(new LeaguesApi())

jest.mocked(LocationsApi).prototype.locationsPost.mockResolvedValue({
config: {},
data: {
location: fake.location()
},
headers: {},
request: {},
status: 201,
statusText: "",
})
mockApi.prototype.locationsApi.mockReturnValue(new LocationsApi())

const result = render(
<LeagueForm mode={'create'}/>
)
await typeLeagueInfo(result)

const submitButton = await result.findByText("Create")
act(() => {
Simulate.click(submitButton)
})

await waitFor(() => expect(mockedNavigate).toBeCalledWith('/leagues'))
})
});


Expand All @@ -92,9 +149,9 @@ async function typeLeagueInfo(result: RenderResult) {
Simulate.change(slugField)
})

const locationField = await result.findByPlaceholderText("location")
locationField.setAttribute("value", faker.random.words(4))
const locationSelectValue = await result.findByPlaceholderText('test-location')
slugField.setAttribute("value", faker.datatype.uuid())
act(() => {
Simulate.change(locationField)
Simulate.change(locationSelectValue)
})
}
1 change: 1 addition & 0 deletions web/app/src/test/fake/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./user";
export * from "./league";
export * from "./errorResponse";
export * from "./location";
15 changes: 14 additions & 1 deletion web/app/src/test/fake/location.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Location} from "../../api";
import {Location, pinballMap} from "../../api";
import {faker} from "@faker-js/faker";
import * as helpers from "../../helpers";

Expand All @@ -15,3 +15,16 @@ export function location(): Location {
updated_at: faker.date.recent(1).toISOString()
}
}

export function pinballMapLocation(): pinballMap.Location {
return {
id: faker.datatype.number(),
name: faker.hacker.phrase(),
street: faker.address.streetAddress(),
city: faker.address.city(),
state: faker.address.stateAbbr(),
zip: faker.address.zipCode(),
country: faker.address.countryCode(),
num_machines: faker.datatype.number(),
}
}

0 comments on commit a64244e

Please sign in to comment.