Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include typechecking for all files in the CI #571

Merged
merged 19 commits into from
Dec 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/next.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
- run: npm install -g yarn
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn typecheck
- run: yarn test
- run: yarn lint

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dev": "next dev",
"build": "./node_modules/next/dist/bin/next build",
"start": "node -r ./server-preload.js ./node_modules/next/dist/bin/next start -p 8080",
"typecheck": "tsc -p .",
"test": "jest --ci --coverage",
"test:update-snapshots": "jest -u",
"test:watch": "jest --watch",
Expand Down Expand Up @@ -54,14 +55,15 @@
"@testing-library/dom": "^7.30.0",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@types/jest": "^27.0.3",
"@types/luxon": "^2.0.0",
"@types/node": "^14.14.33",
"@types/pem": "^1.9.5",
"@types/pino": "^6.3.11",
"@types/pumpify": "^1.4.1",
"@types/react": "^17.0.3",
"@types/react-test-renderer": "^17.0.1",
"@types/uuid": "^8.3.1",
"@types/uuid": "^8.3.3",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"babel-jest": "^26.6.3",
Expand Down
79 changes: 42 additions & 37 deletions tests/components/Appointment.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import renderer, { act } from 'react-test-renderer'

import i18n from '../jest-i18n'
import { Appointment } from '../../components/Appointment'
import { TimeSlot } from '../../types/common'
import { TimeSlot, TestRendererCreateReturn } from '../../types/common'

/**
* Helper functions.
*/

function renderAppointmentComponent(timeSlot: TimeSlot | undefined): string {
function renderAppointmentComponent(timeSlot: TimeSlot | undefined): TestRendererCreateReturn {
// Set a random date in PT time.
const date = '2021-05-05T00:00:00.000-0800'
return renderer.create(<Appointment date={date} timeSlot={timeSlot} />).toJSON()
Expand All @@ -18,9 +18,11 @@ function renderAppointmentComponent(timeSlot: TimeSlot | undefined): string {
* Appointment snapshot tests.
*/

type AppointmentTestSet = [string, number | null, number | null]

// Each test case should be:
// [test description, timeSlot.rangeStart, timeSlot.rangeEnd]
const testCases = [
const testCases: AppointmentTestSet[] = [
['with no time slot, then match the snapshot', null, null],
['with a morning time slot, then match the snapshot', 8, 10],
['with an afternoon time slot, then match the snapshot', 1, 3],
Expand All @@ -30,45 +32,48 @@ const testCases = [

// Use describe.each() to DRY up the tests.
// See https://jestjs.io/docs/api#describeeachtablename-fn-timeout
describe.each(testCases)('If given an appointment', (description: string, start: number | null, end: number | null) => {
// Construct the timeslot argument.
let timeSlot: TimeSlot | undefined
if (start && end) {
timeSlot = {
rangeStart: start,
rangeEnd: end,
describe.each<AppointmentTestSet>(testCases)(
'If given an appointment',
(description: string, start: number | null, end: number | null) => {
// Construct the timeslot argument.
let timeSlot: TimeSlot | undefined
if (start && end) {
timeSlot = {
rangeStart: start,
rangeEnd: end,
}
}
}

// Run through the test cases first in English.
it(`${description}`, () => {
expect(renderAppointmentComponent(timeSlot)).toMatchSnapshot()
})
// Run through the test cases first in English.
it(`${description}`, () => {
expect(renderAppointmentComponent(timeSlot)).toMatchSnapshot()
})

// Run through the test cases again in Spanish.
it(`${description}, in Spanish`, () => {
// Change the language to Spanish.
// Run through the test cases again in Spanish.
it(`${description}, in Spanish`, () => {
// Change the language to Spanish.

// The call to changeLanguage() must be wrapped in act(), otherwise Jest/react
// complains.
// See https://reactjs.org/link/wrap-tests-with-act
// and https://reactjs.org/docs/test-renderer.html#testrendereract
// The call to changeLanguage() must be wrapped in act(), otherwise Jest/react
// complains.
// See https://reactjs.org/link/wrap-tests-with-act
// and https://reactjs.org/docs/test-renderer.html#testrendereract

// Disable floating promises lint check. eslint really wants us to handle the Promise
// returned by changeLanguage(), but it doesn't appear necessary to this test.
// This can be revisited and refactored in the future if necessary.
/* eslint-disable @typescript-eslint/no-floating-promises */
act(() => {
i18n.changeLanguage('es')
})
// Disable floating promises lint check. eslint really wants us to handle the Promise
// returned by changeLanguage(), but it doesn't appear necessary to this test.
// This can be revisited and refactored in the future if necessary.
/* eslint-disable @typescript-eslint/no-floating-promises */
act(() => {
i18n.changeLanguage('es')
})

// Run the actual test.
expect(renderAppointmentComponent(timeSlot)).toMatchSnapshot()
// Run the actual test.
expect(renderAppointmentComponent(timeSlot)).toMatchSnapshot()

// Change the language back to English so the first it() renders correctly in English.
act(() => {
i18n.changeLanguage('en')
// Change the language back to English so the first it() renders correctly in English.
act(() => {
i18n.changeLanguage('en')
})
/* eslint-enable @typescript-eslint/no-floating-promises */
})
/* eslint-enable @typescript-eslint/no-floating-promises */
})
})
},
)
9 changes: 6 additions & 3 deletions tests/components/ClaimStatus.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import renderer from 'react-test-renderer'
import { DateTime, Settings } from 'luxon'

import { ClaimStatus } from '../../components/ClaimStatus'
import { ClaimStatusContent } from '../../types/common'
import { ClaimStatusContent, TestRendererCreateReturn } from '../../types/common'
import apiGatewayStub from '../../utils/apiGatewayStub'
import getScenarioContent, { ScenarioType } from '../../utils/getScenarioContent'

/**
* Helper functions.
*/

function renderClaimStatusComponent(statusContent: ClaimStatusContent, userArrivedFromUioMobile: boolean): string {
function renderClaimStatusComponent(
statusContent: ClaimStatusContent,
userArrivedFromUioMobile: boolean,
): TestRendererCreateReturn {
return renderer
.create(
<ClaimStatus
Expand All @@ -29,7 +32,7 @@ function testClaimStatus(
scenarioType: ScenarioType,
hasCertificationWeeksAvailable = false,
userArrivedFromUioMobile = false,
): string {
): TestRendererCreateReturn {
const scenarioContent = getScenarioContent(apiGatewayStub(scenarioType, hasCertificationWeeksAvailable))
return renderClaimStatusComponent(scenarioContent.statusContent, userArrivedFromUioMobile)
}
Expand Down
4 changes: 2 additions & 2 deletions tests/components/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Header } from '../../components/Header'

describe('Header component loads', () => {
it('has the desktop UIO Link by default', () => {
render(<Header userArrivedFromUioMobile={false} />)
render(<Header userArrivedFromUioMobile={false} assetPrefix="/claimstatus" />)
expect(screen.queryByText('UI Home')).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'UI Home' })).toHaveAttribute(
'href',
Expand All @@ -12,7 +12,7 @@ describe('Header component loads', () => {
})

it('has the UIO Mobile Link when the user arrived from UIO Mobile', () => {
render(<Header userArrivedFromUioMobile />)
render(<Header userArrivedFromUioMobile assetPrefix="/claimstatus" />)
expect(screen.queryByText('UI Home')).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'UI Home' })).toHaveAttribute(
'href',
Expand Down
22 changes: 12 additions & 10 deletions tests/components/TransLine.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,59 @@ const beta = 'https://example.com/beta'

describe('TransLine component loads', () => {
it('a string with no links or styles', () => {
render(<TransLine i18nKey="test:transLine.plainString" />)
render(<TransLine userArrivedFromUioMobile={false} i18nKey="test:transLine.plainString" />)
expect(screen.getByText('just text')).toBeInTheDocument()
})

it('a string with one link and no styles', () => {
const links = ['test:urls.alpha']
render(<TransLine i18nKey="test:transLine.plainStringOneLink" links={links} />)
render(<TransLine userArrivedFromUioMobile={false} i18nKey="test:transLine.plainStringOneLink" links={links} />)
expect(screen.getByRole('link', { name: 'second' })).toHaveAttribute('href', alpha)
})

it('a string with multiple links and no styles', () => {
const links = ['test:urls.alpha', 'test:urls.beta']
render(<TransLine i18nKey="test:transLine.plainStringLinks" links={links} />)
render(<TransLine userArrivedFromUioMobile={false} i18nKey="test:transLine.plainStringLinks" links={links} />)
expect(screen.getByRole('link', { name: 'second' })).toHaveAttribute('href', alpha)
expect(screen.getByRole('link', { name: 'third' })).toHaveAttribute('href', beta)
})

it('a string with multiple links reused links and out of order', () => {
const links = ['test:urls.alpha', 'test:urls.beta']
render(<TransLine i18nKey="test:transLine.plainStringLinksComplicated" links={links} />)
render(
<TransLine userArrivedFromUioMobile={false} i18nKey="test:transLine.plainStringLinksComplicated" links={links} />,
)
expect(screen.getByRole('link', { name: 'second' })).toHaveAttribute('href', alpha)
expect(screen.getByRole('link', { name: 'fourth' })).toHaveAttribute('href', alpha)
expect(screen.getByRole('link', { name: 'first' })).toHaveAttribute('href', beta)
expect(screen.getByRole('link', { name: 'fifth' })).toHaveAttribute('href', beta)
})

it('a string with no links, but with styles', () => {
render(<TransLine i18nKey="test:transLine.styledString" />)
render(<TransLine userArrivedFromUioMobile={false} i18nKey="test:transLine.styledString" />)
expect(screen.getByText('first', { exact: false })).toBeInTheDocument()
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'strong' && content.startsWith('second')
return element?.tagName.toLowerCase() === 'strong' && content.startsWith('second')
})
expect(screen.getByText('third', { exact: false })).toBeInTheDocument()
})

it('a string with one link and styles', () => {
const links = ['test:urls.alpha']
render(<TransLine i18nKey="test:transLine.styledStringOneLink" links={links} />)
render(<TransLine userArrivedFromUioMobile={false} i18nKey="test:transLine.styledStringOneLink" links={links} />)
expect(screen.getByText('first', { exact: false })).toBeInTheDocument()
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'strong' && content.startsWith('second')
return element?.tagName.toLowerCase() === 'strong' && content.startsWith('second')
})
expect(screen.getByRole('link', { name: 'third' })).toHaveAttribute('href', alpha)
})

it('a string with a styled link', () => {
const links = ['test:urls.alpha']
render(<TransLine i18nKey="test:transLine.styledLink" links={links} />)
render(<TransLine userArrivedFromUioMobile={false} i18nKey="test:transLine.styledLink" links={links} />)
expect(screen.getByText('first', { exact: false })).toBeInTheDocument()
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'strong'
return element?.tagName.toLowerCase() === 'strong'
})
expect(screen.getByRole('link', { name: 'second' })).toHaveAttribute('href', alpha)
})
Expand Down
25 changes: 23 additions & 2 deletions tests/pages/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@ describe('Full page snapshot', () => {
}
;(useRouter as jest.Mock).mockReturnValue(mockRouter)

const tree = renderer.create(<Index scenarioContent={scenarioContent} />).toJSON()
const urlPrefixes = {}
const tree = renderer
.create(
<Index
scenarioContent={scenarioContent}
assetPrefix=""
enableGoogleAnalytics=""
enableMaintenancePage=""
urlPrefixes={urlPrefixes}
/>,
)
.toJSON()
expect(tree).toMatchSnapshot()
})
})
Expand All @@ -38,7 +49,17 @@ describe('Main component shows the timeout', () => {
}
;(useRouter as jest.Mock).mockReturnValue(mockRouter)

render(<Index timedOut scenarioContent={scenarioContent} />)
const urlPrefixes = {}
render(
<Index
timedOut
scenarioContent={scenarioContent}
assetPrefix=""
enableGoogleAnalytics=""
enableMaintenancePage=""
urlPrefixes={urlPrefixes}
/>,
)
expect(screen.queryByText('Your Session Will End Soon')).toBeInTheDocument()
})
})
4 changes: 4 additions & 0 deletions tests/pages/indexServerSide.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ describe('Main component server side logic', () => {
const loggerSpy = jest.spyOn(Logger.prototype, 'log').mockImplementation(jest.fn())
loggerSpy.mockClear()

/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-ignore
const result: GetServerSideProps = await getServerSideProps(context as GetServerSidePropsContext)
// @ts-ignore
const props: HomeProps = result.props as HomeProps
/* eslint-enable @typescript-eslint/ban-ts-comment */
expect(props.errorCode).toBe(500)
expect(context.res.statusCode).toBe(500)
expect(loggerSpy).toHaveBeenCalledWith(expect.anything(), 'error', {}, 'Missing unique number')
Expand Down
38 changes: 21 additions & 17 deletions tests/utils/apiGatewayStub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,43 @@ import { isDatePast, isValidDate, parseApiGatewayDate } from '../../utils/format
import apiGatewayStub from '../../utils/apiGatewayStub'
import { isDeterminationStatusPending, ScenarioType } from '../../utils/getScenarioContent'

/* eslint-disable @typescript-eslint/no-non-null-assertion */
// We only use non-null assertions in tests after we've verified that the value is not null.

describe('The API gateway stub response for the Determination Interview scenarios', () => {
it('is correct for Scenario 1', () => {
const response = apiGatewayStub(ScenarioType.Scenario1)
expect(response.pendingDetermination.length).toBe(1)
const pendingDetermination = response.pendingDetermination
expect([null, false, undefined, '']).toContainEqual(pendingDetermination.determinationStatus)
expect([null, false, undefined, '']).toContainEqual(pendingDetermination.scheduleDate)
expect(pendingDetermination.requestDate).not.toBe('')
expect(response.pendingDetermination?.length).toBe(1)

const pendingDetermination = response?.pendingDetermination?.[0]
expect([null, false, undefined, '']).toContainEqual(pendingDetermination?.determinationStatus)
expect([null, false, undefined, '']).toContainEqual(pendingDetermination?.scheduleDate)
expect(pendingDetermination?.requestDate).not.toBe('')
})

it('is correct for Scenario 2', () => {
const response = apiGatewayStub(ScenarioType.Scenario2)
expect(response.pendingDetermination.length).toBe(1)
expect(response?.pendingDetermination?.length).toBe(1)

const pendingDetermination = response.pendingDetermination[0]
expect(isDeterminationStatusPending(pendingDetermination)).toBe(true)
const pendingDetermination = response?.pendingDetermination?.[0]
expect(pendingDetermination).not.toBe(undefined)
expect(isDeterminationStatusPending(pendingDetermination!)).toBe(true)
expect(isValidDate(pendingDetermination!.scheduleDate)).toBe(true)

expect(isValidDate(pendingDetermination.scheduleDate)).toBe(true)

const convertedDate = parseApiGatewayDate(pendingDetermination.scheduleDate)
const convertedDate = parseApiGatewayDate(pendingDetermination!.scheduleDate)
expect(isDatePast(convertedDate)).toBe(false)
})

it('is correct for Scenario 3', () => {
const response = apiGatewayStub(ScenarioType.Scenario3)
expect(response.pendingDetermination.length).toBe(1)

const pendingDetermination = response.pendingDetermination[0]
expect(isDeterminationStatusPending(pendingDetermination)).toBe(true)
expect(response?.pendingDetermination?.length).toBe(1)

expect(isValidDate(pendingDetermination.scheduleDate)).toBe(true)
const pendingDetermination = response?.pendingDetermination?.[0]
expect(pendingDetermination).not.toBe(undefined)
expect(isDeterminationStatusPending(pendingDetermination!)).toBe(true)
expect(isValidDate(pendingDetermination!.scheduleDate)).toBe(true)

const convertedDate = parseApiGatewayDate(pendingDetermination.scheduleDate)
const convertedDate = parseApiGatewayDate(pendingDetermination!.scheduleDate)
expect(isDatePast(convertedDate)).toBe(true)
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import mockEnv from 'mocked-env'

import getUrl, { stripTrailingSlashes } from '../../utils/browser/getUrl'
import getUrl, { stripTrailingSlashes } from '../../../utils/browser/getUrl'

// Test stripTrailingSlashes()
describe('A string', () => {
Expand Down
Loading