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

test(project): update tests for React 18 #2673

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
493b945
test(ThemeProvider): update test to use userEvents
joshblack Dec 8, 2022
6bf3d27
test(helpers): add TextEncoder for useMedia
joshblack Dec 8, 2022
02d59c8
test(MarkdownEditor): update tests for MarkdownEditor
joshblack Dec 9, 2022
9f14cc5
test: update TextInputWithTokens test
joshblack Dec 9, 2022
e2b75b4
test(MarkdownEditor): enable skipped tests
joshblack Dec 9, 2022
1950d9d
chore: add spy for console.error() in test
joshblack Dec 12, 2022
4ef894e
chore: add globals for react version
joshblack Dec 12, 2022
14a5c65
chore: remove storybook-addon-html
joshblack Dec 9, 2022
06f24bb
chore: update storybook deps
joshblack Dec 9, 2022
a6c9109
chore: remove conditional install step from ci.yml
joshblack Dec 9, 2022
63c0efa
Revert "chore: remove conditional install step from ci.yml"
joshblack Dec 9, 2022
e32c00f
feat(project): update to React 18 for development
joshblack Dec 9, 2022
01a7b1f
ci: update install step to use 18 by default
joshblack Dec 9, 2022
2e3705f
chore: update lockfile
joshblack Dec 9, 2022
4be894f
chore: update eslint to warn on useSSRSafeId
joshblack Dec 9, 2022
9a07747
feat(project): add useId hook
joshblack Dec 9, 2022
fe2c4c8
feat(project): update useSSRSafeId to useId
joshblack Dec 9, 2022
2f185ad
chore: disable eslint violations
joshblack Dec 9, 2022
7074a78
test: map useId() to useSSRSafeId() in test
joshblack Dec 12, 2022
fdf2284
test(storybook): update interaction tests for UnderlineNav
joshblack Dec 13, 2022
e335265
chore: add changeset
joshblack Dec 13, 2022
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ jobs:

- name: Test
run: npm run test -- --coverage
with:
REACT_VERSION_17: ${{ matrix.react == 17 }}

type-check:
strategy:
Expand Down
12 changes: 12 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
/* eslint-disable github/unescaped-html-literal */

'use strict'

const {REACT_VERSION_17} = process.env

/**
* @type {import('jest').Config}
*/
module.exports = {
globals: {
REACT_VERSION_LATEST: REACT_VERSION_17 ? REACT_VERSION_17 !== 'true' : true,
REACT_VERSION_17: REACT_VERSION_17 === 'true',
},
testEnvironment: 'jsdom',
cacheDirectory: '.test',
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/stories/**', '!**/*.stories.{js,jsx,ts,tsx}'],
Expand Down
68 changes: 49 additions & 19 deletions src/__tests__/TextInputWithTokens.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from 'react'
import {render} from '../utils/testing'
import {render as HTMLRender, fireEvent, act} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
import {axe} from 'jest-axe'
import {TokenSizeKeys, tokenSizes} from '../Token/TokenBase'
import {IssueLabelToken} from '../Token'
import TextInputWithTokens, {TextInputWithTokensProps} from '../TextInputWithTokens'
import {MarkGithubIcon} from '@primer/octicons-react'
expect.extend(toHaveNoViolations)

const mockTokens = [
{text: 'zero', id: 0},
Expand Down Expand Up @@ -467,41 +466,72 @@ describe('TextInputWithTokens', () => {
expect(onRemoveMock).toHaveBeenCalledWith(mockTokens[4].id)
})

it('moves focus to the next token when removing the first token', () => {
it('moves focus to the next token when removing the first token', async () => {
jest.useFakeTimers()
const onRemoveMock = jest.fn()
const {getByText} = HTMLRender(
<TextInputWithTokens tokens={[...mockTokens].slice(0, 2)} onTokenRemove={onRemoveMock} />,
)

function TestComponent() {
const [tokens, setTokens] = React.useState(mockTokens.slice(0, 2))
return (
<TextInputWithTokens
tokens={tokens}
onTokenRemove={id => {
setTokens(
tokens.filter(token => {
return token.id !== id
}),
)
}}
/>
)
}

const {getByText} = HTMLRender(<TestComponent />)
const tokenNode = getByText(mockTokens[0].text)

fireEvent.focus(tokenNode)
fireEvent.keyDown(tokenNode, {key: 'Backspace'})

jest.runAllTimers()
setTimeout(() => {
expect(document.activeElement?.textContent).toBe(mockTokens[1].text)
}, 0)
act(() => {
jest.runAllTimers()
})

expect(document.activeElement?.textContent).toBe(mockTokens[1].text)

jest.useRealTimers()
})

it('moves focus to the input when the last token is removed', () => {
jest.useFakeTimers()
const onRemoveMock = jest.fn()
const {getByText, getByLabelText} = HTMLRender(
<LabelledTextInputWithTokens tokens={[mockTokens[0]]} onTokenRemove={onRemoveMock} />,
)

function TestComponent() {
const [tokens, setTokens] = React.useState([mockTokens[0]])
return (
<LabelledTextInputWithTokens
tokens={tokens}
onTokenRemove={id => {
setTokens(tokens => {
return tokens.filter(token => {
return token.id !== id
})
})
}}
/>
)
}

const {getByText, getByLabelText} = HTMLRender(<TestComponent />)
const tokenNode = getByText(mockTokens[0].text)
const inputNode = getByLabelText('Tokens')

fireEvent.focus(tokenNode)
fireEvent.keyDown(tokenNode, {key: 'Backspace'})

jest.runAllTimers()
setTimeout(() => {
expect(document.activeElement?.id).toBe(inputNode.id)
}, 0)
act(() => {
jest.runAllTimers()
})

expect(document.activeElement?.id).toBe(inputNode.id)

jest.useRealTimers()
})

Expand Down
55 changes: 39 additions & 16 deletions src/__tests__/ThemeProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {render, screen, waitFor} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import 'jest-styled-components'
import React from 'react'
import {Text, ThemeProvider, useColorSchemeVar, useTheme} from '..'
Expand Down Expand Up @@ -195,6 +196,8 @@ it('works in auto mode (dark)', () => {
})

it('updates when colorMode prop changes', async () => {
const user = userEvent.setup()

function App() {
const [colorMode, setColorMode] = React.useState<'day' | 'night'>('day')
return (
Expand All @@ -210,7 +213,7 @@ it('updates when colorMode prop changes', async () => {
// starts in day mode (light scheme)
expect(screen.getByText('day')).toHaveStyleRule('color', 'black')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

await waitFor(() =>
// clicking the toggle button enables night mode (dark scheme)
Expand All @@ -219,6 +222,8 @@ it('updates when colorMode prop changes', async () => {
})

it('updates when dayScheme prop changes', async () => {
const user = userEvent.setup()

function App() {
const [dayScheme, setDayScheme] = React.useState('light')
return (
Expand All @@ -234,7 +239,7 @@ it('updates when dayScheme prop changes', async () => {
// starts in day mode (light scheme)
expect(screen.getByText('light')).toHaveStyleRule('color', 'black')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

await waitFor(() =>
// clicking the toggle sets the day scheme to dark_dimmed
Expand All @@ -243,6 +248,8 @@ it('updates when dayScheme prop changes', async () => {
})

it('updates when nightScheme prop changes', async () => {
const user = userEvent.setup()

function App() {
const [nightScheme, setNightScheme] = React.useState('dark')
return (
Expand All @@ -258,7 +265,7 @@ it('updates when nightScheme prop changes', async () => {
// starts in night mode (dark scheme)
expect(screen.getByText('dark')).toHaveStyleRule('color', 'white')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

await waitFor(() =>
// clicking the toggle button sets the night scheme to dark_dimmed
Expand All @@ -267,6 +274,8 @@ it('updates when nightScheme prop changes', async () => {
})

it('inherits colorMode from parent', async () => {
const user = userEvent.setup()

function App() {
const [colorMode, setcolorMode] = React.useState<'day' | 'night'>('day')
return (
Expand All @@ -283,12 +292,14 @@ it('inherits colorMode from parent', async () => {

expect(screen.getByText('day')).toHaveStyleRule('color', 'black')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

await waitFor(() => expect(screen.getByText('night')).toHaveStyleRule('color', 'white'))
})

it('inherits dayScheme from parent', async () => {
const user = userEvent.setup()

function App() {
const [dayScheme, setDayScheme] = React.useState('light')
return (
Expand All @@ -305,12 +316,14 @@ it('inherits dayScheme from parent', async () => {

expect(screen.getByText('light')).toHaveStyleRule('color', 'black')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

await waitFor(() => expect(screen.getByText('dark_dimmed')).toHaveStyleRule('color', 'gray'))
})

it('inherits nightScheme from parent', async () => {
const user = userEvent.setup()

function App() {
const [nightScheme, setNightScheme] = React.useState('dark')
return (
Expand All @@ -327,13 +340,15 @@ it('inherits nightScheme from parent', async () => {

expect(screen.getByText('dark')).toHaveStyleRule('color', 'white')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

await waitFor(() => expect(screen.getByText('dark_dimmed')).toHaveStyleRule('color', 'gray'))
})

describe('setColorMode', () => {
it('changes the color mode', () => {
it('changes the color mode', async () => {
const user = userEvent.setup()

function ToggleMode() {
const {colorMode, setColorMode} = useTheme()
return <button onClick={() => setColorMode(colorMode === 'day' ? 'night' : 'day')}>Toggle</button>
Expand All @@ -349,15 +364,17 @@ describe('setColorMode', () => {
// starts in day mode (light scheme)
expect(screen.getByText('Hello')).toHaveStyleRule('color', 'black')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

// clicking the toggle button enables night mode (dark scheme)
expect(screen.getByText('Hello')).toHaveStyleRule('color', 'white')
})
})

describe('setDayScheme', () => {
it('changes the day scheme', () => {
it('changes the day scheme', async () => {
const user = userEvent.setup()

function ToggleDayScheme() {
const {dayScheme, setDayScheme} = useTheme()
return <button onClick={() => setDayScheme(dayScheme === 'light' ? 'dark' : 'light')}>Toggle</button>
Expand All @@ -373,15 +390,17 @@ describe('setDayScheme', () => {
// starts in day mode (light scheme)
expect(screen.getByText('Hello')).toHaveStyleRule('color', 'black')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

// clicking the toggle button sets day scheme to dark
expect(screen.getByText('Hello')).toHaveStyleRule('color', 'white')
})
})

describe('setNightScheme', () => {
it('changes the night scheme', () => {
it('changes the night scheme', async () => {
const user = userEvent.setup()

function ToggleNightScheme() {
const {nightScheme, setNightScheme} = useTheme()
return <button onClick={() => setNightScheme(nightScheme === 'dark' ? 'dark_dimmed' : 'dark')}>Toggle</button>
Expand All @@ -397,15 +416,17 @@ describe('setNightScheme', () => {
// starts in night mode (dark scheme)
expect(screen.getByText('Hello')).toHaveStyleRule('color', 'white')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

// clicking the toggle button sets night scheme to dark_dimmed
expect(screen.getByText('Hello')).toHaveStyleRule('color', 'gray')
})
})

describe('useColorSchemeVar', () => {
it('updates value when scheme changes', () => {
it('updates value when scheme changes', async () => {
const user = userEvent.setup()

function ToggleMode() {
const {colorMode, setColorMode} = useTheme()
return <button onClick={() => setColorMode(colorMode === 'day' ? 'night' : 'day')}>Toggle</button>
Expand Down Expand Up @@ -433,12 +454,14 @@ describe('useColorSchemeVar', () => {

expect(screen.getByText('Hello')).toHaveStyleRule('background-color', 'red')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

expect(screen.getByText('Hello')).toHaveStyleRule('background-color', 'green')
})

it('supports fallback value', () => {
it('supports fallback value', async () => {
const user = userEvent.setup()

function ToggleMode() {
const {colorMode, setColorMode} = useTheme()
return <button onClick={() => setColorMode(colorMode === 'day' ? 'night' : 'day')}>Toggle</button>
Expand All @@ -459,7 +482,7 @@ describe('useColorSchemeVar', () => {

expect(screen.getByText('Hello')).toHaveStyleRule('background-color', 'red')

screen.getByRole('button').click()
await user.click(screen.getByRole('button'))

expect(screen.getByText('Hello')).toHaveStyleRule('background-color', 'blue')
})
Expand Down
30 changes: 28 additions & 2 deletions src/drafts/MarkdownEditor/MarkdownEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {act} from 'react-dom/test-utils'
import MarkdownEditor, {Emoji, MarkdownEditorHandle, MarkdownEditorProps, Mentionable, Reference, SavedReply} from '.'
import ThemeProvider from '../../ThemeProvider'

declare const REACT_VERSION_LATEST: boolean

type UncontrolledEditorProps = Omit<MarkdownEditorProps, 'value' | 'onChange' | 'onRenderPreview' | 'children'> &
Partial<Pick<MarkdownEditorProps, 'onChange' | 'onRenderPreview' | 'children'>> & {
hideLabel?: boolean
Expand Down Expand Up @@ -655,6 +657,10 @@ describe('MarkdownEditor', () => {
const fileB = imageFile('b')
fireEvent[method](input, {[dataKey]: {files: [fileA, fileB], types: ['Files']}})

await act(async () => {
await Promise.resolve(process.nextTick)
})

await expectFilesToBeAdded(onChange, fileB)

expect(getFooter()).toHaveTextContent('File type not allowed: .app')
Expand Down Expand Up @@ -983,8 +989,11 @@ describe('MarkdownEditor', () => {
await user.type(input, `hello ${triggerChar}`)
expect(queryForSuggestionsList()).toBeInTheDocument()

// eslint-disable-next-line github/no-blur
input.blur()
act(() => {
// eslint-disable-next-line github/no-blur
input.blur()
})

expect(queryForSuggestionsList()).not.toBeInTheDocument()
})

Expand Down Expand Up @@ -1127,10 +1136,27 @@ describe('MarkdownEditor', () => {
})

it('opens the saved reply menu on Ctrl + .', async () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})

const {getInput, queryByRole, user} = await render(<UncontrolledEditor savedReplies={replies} />)

await user.type(getInput(), 'test{Control>}.{/Control}')

// Note: this spy is currently catching a: "Warning: An update to %s inside a test was not wrapped in act(...)."
// error in React 18. It seems like this is triggered within the `type`
// interaction, specifically through `useOpenAndCloseFocus` when the
// TextInput is being opened
//
// At the moment, it doesn't seem clear how to appropriately wrap this
// interaction in an act() in order to cover this warning
if (REACT_VERSION_LATEST) {
expect(spy).toHaveBeenCalled()
} else {
expect(spy).not.toHaveBeenCalled()
}
expect(queryByRole('listbox')).toBeInTheDocument()

spy.mockClear()
})

it('does not open the saved reply menu on Ctrl + . if no replies are set', async () => {
Expand Down
Loading