Skip to content

Commit

Permalink
test(react): add test cases for devMode in production mode at once, m…
Browse files Browse the repository at this point in the history
…eet coverage 100% (#744)

# Overview

<!--
    A clear and concise description of what this pr is about.
 -->

## PR Checklist

- [x] I did below actions if need

1. I read the [Contributing
Guide](https://github.com/suspensive/react/blob/main/CONTRIBUTING.md)
2. I added documents and tests.
  • Loading branch information
manudeli authored Feb 19, 2024
1 parent 23c025f commit f4a1507
Show file tree
Hide file tree
Showing 21 changed files with 657 additions and 703 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-timers-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@suspensive/react": patch
---

test(react): add test cases for devMode in production mode
13 changes: 1 addition & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
command:
[
'ci:attw',
'ci:eslint',
'ci:lighthouse',
'ci:publint',
'ci:sherif',
'ci:type',
'test',
'test:production',
'build',
]
command: ['ci:attw', 'ci:eslint', 'ci:lighthouse', 'ci:publint', 'ci:sherif', 'ci:type', 'test', 'build']
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/pnpm-setup-node
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ graph/

# lighthouse
.lighthouseci/

# test
tsconfig.vitest-temp.json
1 change: 0 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ comment:

ignore:
- '**/*.test-d.*'
- '**/*.setup.*'

component_management:
individual_components:
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"changeset": "changeset",
"changeset:publish": "pnpm prepack && changeset publish",
"changeset:version": "changeset version && pnpm i --lockfile-only",
"ci:all": "pnpm ci:attw && pnpm ci:eslint && pnpm ci:lighthouse && pnpm ci:publint && pnpm ci:sherif && pnpm ci:type && pnpm test && pnpm test:production && pnpm build",
"ci:all": "pnpm ci:attw && pnpm ci:eslint && pnpm ci:lighthouse && pnpm ci:publint && pnpm ci:sherif && pnpm ci:type && pnpm test && pnpm build",
"ci:attw": "turbo run ci:attw",
"ci:eslint": "turbo run ci:eslint",
"ci:lighthouse": "lhci autorun",
Expand All @@ -38,8 +38,6 @@
"prepare": "husky install",
"start": "turbo run start",
"test": "turbo run test",
"test:production": "turbo run test:production",
"test:production:watch": "turbo run test:production:watch --parallel",
"test:watch": "turbo run test:watch --parallel"
},
"devDependencies": {
Expand Down
2 changes: 0 additions & 2 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@
"dev": "tsup --watch",
"prepack": "pnpm build",
"test": "vitest run --coverage --typecheck",
"test:production": "vitest run --config ./vitest-production.config.ts",
"test:production:watch": "vitest --config ./vitest-production.config.ts --ui",
"test:watch": "vitest --ui --coverage --typecheck"
},
"dependencies": {
Expand Down
8 changes: 8 additions & 0 deletions packages/react/src/DevMode-production.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { describe, expect, it } from 'vitest'
import { ProductionDevMode } from './contexts/SuspensiveDevModeContext'

describe('<DevMode/> in production mode', () => {
it('should show nothing if without SuspensiveProvider', () => {
expect(ProductionDevMode()).toBeNull()
})
})
23 changes: 0 additions & 23 deletions packages/react/src/DevMode.production.spec.tsx

This file was deleted.

432 changes: 3 additions & 429 deletions packages/react/src/DevMode.tsx

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions packages/react/src/ErrorBoundary.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CustomError, ERROR_MESSAGE, FALLBACK, TEXT, ThrowError, ThrowNull } from '@suspensive/test-utils'
import { act, render, screen, waitFor } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import ms from 'ms'
import { type ComponentRef, createElement, createRef } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
Expand Down Expand Up @@ -258,6 +259,23 @@ describe('<ErrorBoundary/>', () => {
})
})

describe('<ErrorBoundary.Consumer/>', () => {
it('should consume ErrorBoundaryContext like useErrorBoundary', async () => {
const user = userEvent.setup()
render(
<ErrorBoundary fallback={({ error }) => <div>{error.message}</div>}>
<ErrorBoundary.Consumer>
{(errorBoundary) => (
<button onClick={() => errorBoundary.setError(new Error(ERROR_MESSAGE))}>error maker</button>
)}
</ErrorBoundary.Consumer>
</ErrorBoundary>
)
user.click(screen.getByRole('button'))
await waitFor(() => expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument())
})
})

describe('useErrorBoundary', () => {
beforeEach(() => ThrowError.reset())

Expand Down
23 changes: 23 additions & 0 deletions packages/react/src/ErrorBoundaryGroup.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,29 @@ describe('<ErrorBoundaryGroup/>', () => {
expect(screen.queryByText(ERROR_MESSAGE)).not.toBeInTheDocument()
})

it('should reset all ErrorBoundaries in children by <ErrorBoundaryGroup.Reset/> too yet', async () => {
render(
<ErrorBoundaryGroup>
<ErrorBoundaryGroup.Reset trigger={(group) => <button onClick={group.reset}>{resetButtonText}</button>} />
{Array.from({ length: innerErrorBoundaryCount }).map((_, key) => (
<ErrorBoundary key={key} fallback={(props) => <div>{props.error.message}</div>}>
<ThrowError message={ERROR_MESSAGE} after={ms('0.1s')}>
<div>{TEXT}</div>
</ThrowError>
</ErrorBoundary>
))}
</ErrorBoundaryGroup>
)

expect(screen.getAllByText(TEXT).length).toBe(innerErrorBoundaryCount)
await waitFor(() => expect(screen.getAllByText(ERROR_MESSAGE).length).toBe(innerErrorBoundaryCount))
ThrowError.reset()

fireEvent.click(screen.getByRole('button', { name: resetButtonText }))
expect(screen.getAllByText(TEXT).length).toBe(innerErrorBoundaryCount)
expect(screen.queryByText(ERROR_MESSAGE)).not.toBeInTheDocument()
})

it('should reset all ErrorBoundaries in children even if it is nested, but if use blockOutside, can block reset by outside', async () => {
render(
<ErrorBoundaryGroup>
Expand Down
54 changes: 54 additions & 0 deletions packages/react/src/contexts/SuspensiveDevMode.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { sleep } from '@suspensive/test-utils'
import { waitFor } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { SuspensiveDevMode, SuspensiveDevModeOnInfoText } from './SuspensiveDevModeContext'

describe('SuspensiveDevMode', () => {
it('should have field `is` that can be changed by on off', () => {
const devMode = new SuspensiveDevMode()

expect(devMode.is).toBe(false)
devMode.on()
expect(devMode.is).toBe(true)
devMode.off()
expect(devMode.is).toBe(false)
})

it('should inform if DevMode is on by console.info', async () => {
const devMode = new SuspensiveDevMode()
const infoSpy = vi.spyOn(console, 'info')

expect(devMode.is).toBe(false)
devMode.on()
expect(devMode.is).toBe(true)
await waitFor(() => expect(infoSpy.mock.calls[0][0]).toBe(SuspensiveDevModeOnInfoText))
await waitFor(() => expect(infoSpy).toBeCalledTimes(1))
await waitFor(() => expect(infoSpy).toBeCalledTimes(2))
await waitFor(() => expect(infoSpy).toBeCalledTimes(3))
await waitFor(() => expect(infoSpy).toBeCalledTimes(4))
devMode.off()
await sleep(1000)
expect(infoSpy).toBeCalledTimes(4)
await sleep(1000)
expect(infoSpy).toBeCalledTimes(4)
})

it('should notify that devMode have changed to subscribers', () => {
const suspensiveDevMode = new SuspensiveDevMode()

const subscriber = new (class Subscriber {
notifiedCount = 0

onChange = () => {
this.notifiedCount = this.notifiedCount + 1
}
})()

suspensiveDevMode.subscribe(subscriber.onChange)
expect(subscriber.notifiedCount).toBe(0)
suspensiveDevMode.on()
expect(subscriber.notifiedCount).toBe(1)
suspensiveDevMode.off()
expect(subscriber.notifiedCount).toBe(2)
})
})

This file was deleted.

107 changes: 12 additions & 95 deletions packages/react/src/contexts/SuspensiveDevModeContext.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,19 @@
import { TEXT, sleep } from '@suspensive/test-utils'
import { render, renderHook, screen, waitFor } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { createElement, useContext } from 'react'
import { describe, expect, it, vi } from 'vitest'
import { DevMode } from '../DevMode'
import { renderHook } from '@testing-library/react'
import { useContext } from 'react'
import { describe, expect, it } from 'vitest'
import { Suspensive, SuspensiveProvider } from '../Suspensive'
import { DevModeContext, SuspensiveDevMode, SuspensiveDevModeOnInfoText, syncDevMode } from './SuspensiveDevModeContext'
import { DevModeContext, SuspensiveDevMode } from './SuspensiveDevModeContext'

describe('SuspensiveDevMode (process.env.NODE_ENV: development)', () => {
it('should have field `is` that can be changed by on off', () => {
const devMode = new SuspensiveDevMode()

expect(devMode.is).toBe(false)
devMode.on()
expect(devMode.is).toBe(true)
devMode.off()
expect(devMode.is).toBe(false)
})

it('should inform if DevMode is on by console.info', async () => {
const devMode = new SuspensiveDevMode()
const infoSpy = vi.spyOn(console, 'info')

expect(devMode.is).toBe(false)
devMode.on()
expect(devMode.is).toBe(true)
await waitFor(() => expect(infoSpy.mock.calls[0][0]).toBe(SuspensiveDevModeOnInfoText))
await waitFor(() => expect(infoSpy).toBeCalledTimes(1))
await waitFor(() => expect(infoSpy).toBeCalledTimes(2))
await waitFor(() => expect(infoSpy).toBeCalledTimes(3))
await waitFor(() => expect(infoSpy).toBeCalledTimes(4))
devMode.off()
await sleep(1000)
expect(infoSpy).toBeCalledTimes(4)
await sleep(1000)
expect(infoSpy).toBeCalledTimes(4)
})

it('should notify that devMode have changed to subscribers', () => {
const suspensiveDevMode = new SuspensiveDevMode()

const subscriber = new (class Subscriber {
notifiedCount = 0

onChange = () => {
this.notifiedCount = this.notifiedCount + 1
}
})()

suspensiveDevMode.subscribe(subscriber.onChange)
expect(subscriber.notifiedCount).toBe(0)
suspensiveDevMode.on()
expect(subscriber.notifiedCount).toBe(1)
suspensiveDevMode.off()
expect(subscriber.notifiedCount).toBe(2)
})

describe('DevModeContext', () => {
it('returns null when no SuspensiveProvider is present', () => {
const { result } = renderHook(() => useContext(DevModeContext))
expect(result.current).toBeNull()
})

it('returns an instance of SuspensiveDevMode when wrapped with SuspensiveProvider', () => {
const { result } = renderHook(() => useContext(DevModeContext), {
wrapper: (props) => <SuspensiveProvider {...props} value={new Suspensive()} />,
})
expect(result.current).toBeInstanceOf(SuspensiveDevMode)
})
describe('DevModeContext', () => {
it('returns null when no SuspensiveProvider is present', () => {
const { result } = renderHook(() => useContext(DevModeContext))
expect(result.current).toBeNull()
})

describe('syncDevMode (process.env.NODE_ENV: development)', () => {
it('should make component synced with DevMode at development', async () => {
const user = userEvent.setup()
const logSpy = vi.spyOn(console, 'log')
render(
createElement(
syncDevMode(({ devMode }) => {
console.log(devMode.is)
return <>{TEXT}</>
})
),
{
wrapper: ({ children }) => (
<SuspensiveProvider value={new Suspensive()}>
{children}
<DevMode />
</SuspensiveProvider>
),
}
)
// expect(screen.queryByText(TEXT)).toBeInTheDocument()
expect(logSpy.mock.calls[0][0]).toBe(false)
user.click(screen.getByRole('Suspensive.DevMode-off'))
await waitFor(() => expect(logSpy.mock.calls[1][0]).toBe(true))
user.click(screen.getByRole('Suspensive.DevMode-on'))
await waitFor(() => expect(logSpy.mock.calls[2][0]).toBe(false))
await waitFor(() => expect(logSpy.mock.calls[3]).toBe(undefined))
it('returns an instance of SuspensiveDevMode when wrapped with SuspensiveProvider', () => {
const { result } = renderHook(() => useContext(DevModeContext), {
wrapper: (props) => <SuspensiveProvider {...props} value={new Suspensive()} />,
})
expect(result.current).toBeInstanceOf(SuspensiveDevMode)
})
})
Loading

0 comments on commit f4a1507

Please sign in to comment.