From 034b41edd0410d33d007cfd19098b09d764e4b1b Mon Sep 17 00:00:00 2001 From: David Crespo Date: Sun, 12 Dec 2021 15:54:04 -0600 Subject: [PATCH] proof of concept for making all page-level tests into integration tests --- .../__tests__/InstanceCreateForm.spec.tsx | 31 +++++++++---------- app/test-utils.tsx | 21 +++++++++++-- package.json | 4 +-- yarn.lock | 23 +++++++++++--- 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/app/pages/__tests__/InstanceCreateForm.spec.tsx b/app/pages/__tests__/InstanceCreateForm.spec.tsx index 1c396f9391..1dd6c0a656 100644 --- a/app/pages/__tests__/InstanceCreateForm.spec.tsx +++ b/app/pages/__tests__/InstanceCreateForm.spec.tsx @@ -2,7 +2,7 @@ import React from 'react' import { fireEvent, lastBody, - renderWithRouter, + renderAppAt, screen, waitFor, } from '../../test-utils' @@ -10,8 +10,6 @@ import fetchMock from 'fetch-mock' import { org, project, instance } from '@oxide/api-mocks' -import { InstanceCreateForm } from '../project/instances/create/InstancesCreatePage' - const submitButton = () => screen.getByRole('button', { name: 'Create instance' }) @@ -20,21 +18,14 @@ const instancesUrl = `${projectUrl}/instances` const disksUrl = `${projectUrl}/disks` const vpcsUrl = `${projectUrl}/vpcs` -let successSpy: jest.Mock +const renderPage = () => + renderAppAt(`/orgs/${org.name}/projects/${project.name}/instances/new`) describe('InstanceCreateForm', () => { beforeEach(() => { // existing disk modal fetches disks on render even if it's not visible fetchMock.get(disksUrl, 200) fetchMock.get(vpcsUrl, 200) - successSpy = jest.fn() - renderWithRouter( - - ) }) afterEach(() => { @@ -43,6 +34,7 @@ describe('InstanceCreateForm', () => { it('disables submit button on submit and enables on response', async () => { const mock = fetchMock.post(instancesUrl, 201) + renderPage() const submit = submitButton() expect(submit).not.toBeDisabled() @@ -52,7 +44,6 @@ describe('InstanceCreateForm', () => { expect(mock.called(instancesUrl)).toBeFalsy() await waitFor(() => expect(submit).toBeDisabled()) expect(mock.done()).toBeTruthy() - expect(submit).not.toBeDisabled() }) it('shows specific message for known server error code', async () => { @@ -60,6 +51,7 @@ describe('InstanceCreateForm', () => { status: 400, body: { error_code: 'ObjectAlreadyExists' }, }) + renderPage() fireEvent.click(submitButton()) @@ -73,6 +65,7 @@ describe('InstanceCreateForm', () => { status: 400, body: { error_code: 'UnknownCode' }, }) + renderPage() fireEvent.click(submitButton()) @@ -81,6 +74,7 @@ describe('InstanceCreateForm', () => { it('posts form on submit', async () => { const mock = fetchMock.post(instancesUrl, 201) + renderPage() fireEvent.change(screen.getByLabelText('Choose a name'), { target: { value: 'new-instance' }, @@ -99,15 +93,18 @@ describe('InstanceCreateForm', () => { ) }) - it('calls onSuccess on success', async () => { + it('navigates to project instances page on success', async () => { const mock = fetchMock.post(instancesUrl, { status: 201, body: instance }) - - expect(successSpy).not.toHaveBeenCalled() + const { history } = renderPage() fireEvent.click(submitButton()) await waitFor(() => expect(mock.called(instancesUrl)).toBeTruthy()) await waitFor(() => expect(mock.done()).toBeTruthy()) - await waitFor(() => expect(successSpy).toHaveBeenCalled()) + await waitFor(() => + expect(history.location.pathname).toEqual( + `/orgs/${org.name}/projects/${project.name}/instances` + ) + ) }) }) diff --git a/app/test-utils.tsx b/app/test-utils.tsx index 229f5b323e..85938611bf 100644 --- a/app/test-utils.tsx +++ b/app/test-utils.tsx @@ -1,8 +1,13 @@ import React from 'react' -import { BrowserRouter as Router } from 'react-router-dom' +import { + BrowserRouter, + unstable_HistoryRouter as HistoryRouter, +} from 'react-router-dom' +import { createMemoryHistory } from 'history' import { render } from '@testing-library/react' import { QueryClient, QueryClientProvider } from 'react-query' import type { FetchMockStatic } from 'fetch-mock' +import { routes } from './routes' const queryClient = new QueryClient({ defaultOptions: { @@ -22,14 +27,24 @@ const customRender = (ui: React.ReactElement) => export const renderWithRouter = (ui: React.ReactElement) => render(ui, { wrapper: ({ children }) => ( - + {children} - + ), }) +export function renderAppAt(location: string) { + const history = createMemoryHistory({ initialEntries: [location] }) + const rendered = render( + + {routes} + + ) + return { history, rendered } +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any export const lastBody = (mock: FetchMockStatic): any => JSON.parse(mock.lastOptions()?.body as unknown as string) diff --git a/package.json b/package.json index 010d417452..fa7ddbd92a 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "react-is": "^17.0.2", "react-popper": "^2.2.5", "react-query": "^3.13.12", - "react-router": "^6.0.0", - "react-router-dom": "^6.0.0", + "react-router": "^6.1.1", + "react-router-dom": "^6.1.1", "react-table": "^7.7.0", "react-transition-group": "^4.4.1", "recharts": "^2.1.6", diff --git a/yarn.lock b/yarn.lock index 359d29fab6..194f5182b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12015,7 +12015,7 @@ react-docgen@^5.0.0: node-dir "^0.1.10" strip-indent "^3.0.0" -react-dom@^18.0.0-rc.0-next-f2a59df48-20211208: +react-dom@^18.0.0-rc.0: version "18.0.0-rc.0-next-f2a59df48-20211208" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0-rc.0-next-f2a59df48-20211208.tgz#96b4e0f0d15ba5cbad24de2c852742f5ef9a6991" integrity sha512-9v9UGcJRoPtPrKOxtfuZDy5ZQKat8Mz1crciGuyFOphxgtN5jShRj8KB2TTTfeEysyheKyaJ1dZG4Uj6+cRNGg== @@ -12175,7 +12175,7 @@ react-resize-detector@^6.6.3: lodash.throttle "^4.1.1" resize-observer-polyfill "^1.5.1" -react-router-dom@^6.0.0, react-router-dom@^6.0.0-beta.8: +react-router-dom@^6.0.0-beta.8: version "6.0.2" resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.0.2.tgz" integrity sha512-cOpJ4B6raFutr0EG8O/M2fEoyQmwvZWomf1c6W2YXBZuFBx8oTk/zqjXghwScyhfrtnt0lANXV2182NQblRxFA== @@ -12183,13 +12183,28 @@ react-router-dom@^6.0.0, react-router-dom@^6.0.0-beta.8: history "^5.1.0" react-router "6.0.2" -react-router@6.0.2, react-router@^6.0.0, react-router@^6.0.0-beta.8: +react-router-dom@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.1.1.tgz#ed59376ff9115bc49227e87982a32e91e9530ca3" + integrity sha512-O3UH89DI4o+swd2q6lF4dSmpuNCxwkUXcj0zAFcVc1H+YoPE6T7uwoFMX0ws1pUvCY8lYDucFpOqCCdal6VFzg== + dependencies: + history "^5.1.0" + react-router "6.1.1" + +react-router@6.0.2, react-router@^6.0.0-beta.8: version "6.0.2" resolved "https://registry.npmjs.org/react-router/-/react-router-6.0.2.tgz" integrity sha512-8/Wm3Ed8t7TuedXjAvV39+c8j0vwrI5qVsYqjFr5WkJjsJpEvNSoLRUbtqSEYzqaTUj1IV+sbPJxvO+accvU0Q== dependencies: history "^5.1.0" +react-router@6.1.1, react-router@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.1.1.tgz#16f41bf54e87d995bcd4d447720a693f77d8fcb9" + integrity sha512-55o96RiDZmC0uD17DPqVmzzfdNd2Dc+EjkYvMAmHl43du/GItaTdFr5WwjTryNWPXZ+OOVQxQhwAX25UwxpHtw== + dependencies: + history "^5.1.0" + react-sizeme@^3.0.1: version "3.0.2" resolved "https://registry.npmjs.org/react-sizeme/-/react-sizeme-3.0.2.tgz" @@ -12263,7 +12278,7 @@ react-transition-group@^4.4.1: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^18.0.0-rc.0-next-f2a59df48-20211208: +react@^18.0.0-rc.0: version "18.0.0-rc.0-next-f2a59df48-20211208" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0-rc.0-next-f2a59df48-20211208.tgz#d53a505c35ad7cd416b6dcef81bc2fe7dc4cb554" integrity sha512-7C9NnHiOJl/qYhP8POXy53+3C4M6MGs7DIEYbL5jHnYZ0VTk/+dZGFJYiGx+Zn+HKXk7hW+mMTWjvJr2EVD16A==