Skip to content

Commit 9647463

Browse files
authored
refactor(tests): fix flakyness (#292)
1 parent c2e582b commit 9647463

File tree

7 files changed

+52
-25
lines changed

7 files changed

+52
-25
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"format": "prettier --cache --write .",
6969
"lint": "eslint --cache .",
7070
"prepublishOnly": "pnpm build",
71-
"test": "vitest run --typecheck --retry=30",
71+
"test": "vitest run --typecheck",
7272
"watch": "pnpm build -- --watch"
7373
},
7474
"browserslist": "extends @sanity/browserslist-config",

src/__tests__/errors.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {render} from '@testing-library/react'
2-
import React from 'react'
32
import {mergeMap, of, Subject, throwError} from 'rxjs'
43
import {expect, test} from 'vitest'
54

src/__tests__/strictmode.test.ts renamed to src/__tests__/strictmode.test.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import {act, render} from '@testing-library/react'
2-
import {createElement, Fragment, StrictMode, useEffect, useMemo} from 'react'
2+
import {useEffect, useMemo} from 'react'
33
import {BehaviorSubject, Observable} from 'rxjs'
44
import {expect, test} from 'vitest'
55

66
import {useObservable} from '../useObservable'
77

88
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
99

10-
// NOTE: Jest runs NODE_ENV=test by default, which enables development flags for React
11-
1210
test('Strict mode should trigger double mount effects and re-renders', async () => {
1311
const subject = new BehaviorSubject(0)
1412
const observable = subject.asObservable()
@@ -21,10 +19,10 @@ test('Strict mode should trigger double mount effects and re-renders', async ()
2119
}, [])
2220
const observedValue = useObservable(observable)
2321
returnedValues.push(observedValue)
24-
return createElement(Fragment, null, observedValue)
22+
return <>{observedValue}</>
2523
}
2624

27-
render(createElement(StrictMode, null, createElement(ObservableComponent)))
25+
render(<ObservableComponent />, {reactStrictMode: true})
2826
expect(mountCount).toEqual(2)
2927

3028
expect(returnedValues).toEqual([0, 0])
@@ -53,12 +51,12 @@ test('Strict mode should unsubscribe the source observable on unmount', async ()
5351

5452
function ObservableComponent() {
5553
useObservable(observable)
56-
return createElement(Fragment, null)
54+
return null
5755
}
5856

59-
const {rerender} = render(createElement(StrictMode, null, createElement(ObservableComponent)))
57+
const {unmount} = render(<ObservableComponent />, {reactStrictMode: true})
6058
expect(subscribed).toEqual([0])
61-
rerender(createElement(StrictMode, null, createElement('div')))
59+
unmount()
6260
await Promise.resolve()
6361
expect(unsubscribed).toEqual([0])
6462
})
@@ -76,12 +74,12 @@ test('Strict mode should unsubscribe the source observable on unmount if its cre
7674
function ObservableComponent() {
7775
const memoObservable = useMemo(() => getObservable(), [])
7876
useObservable(memoObservable)
79-
return createElement(Fragment, null)
77+
return null
8078
}
8179

82-
const {rerender} = render(createElement(StrictMode, null, createElement(ObservableComponent)))
80+
const {unmount} = render(<ObservableComponent />, {reactStrictMode: true})
8381
expect(subscriberCount, 'Subscriber count should be 1').toBe(1)
84-
rerender(createElement(StrictMode, null, createElement('div')))
82+
unmount()
8583
await Promise.resolve()
8684
expect(subscriberCount, 'Subscriber count should be 0').toBe(0)
8785
})

src/__tests__/useObservable.test.ts renamed to src/__tests__/useObservable.test.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {act, render, renderHook} from '@testing-library/react'
2-
import {createElement, Fragment, useMemo} from 'react'
2+
import {useMemo} from 'react'
3+
import {renderToString} from 'react-dom/server'
34
import {asyncScheduler, Observable, of, ReplaySubject, scheduled, share, Subject, timer} from 'rxjs'
45
import {map} from 'rxjs/operators'
56
import {expect, test} from 'vitest'
@@ -51,9 +52,9 @@ test('should not return undefined during render if initial value is given', () =
5152
function ObservableComponent() {
5253
const observedValue = useObservable(observable, 'initial value')
5354
returnedValues.push(observedValue)
54-
return createElement(Fragment, null, observedValue)
55+
return <>{observedValue}</>
5556
}
56-
render(createElement(ObservableComponent))
57+
render(<ObservableComponent />)
5758
expect(returnedValues).toEqual(expect.arrayContaining(['initial value']))
5859
})
5960

@@ -64,9 +65,9 @@ test('should not return undefined during render if observable is sync', () => {
6465
function ObservableComponent() {
6566
const observedValue = useObservable(observable)
6667
returnedValues.push(observedValue)
67-
return createElement(Fragment, null, observedValue)
68+
return <>{observedValue}</>
6869
}
69-
render(createElement(ObservableComponent))
70+
render(<ObservableComponent />)
7071
expect(returnedValues).toEqual(expect.arrayContaining(['initial value']))
7172
})
7273

@@ -77,9 +78,9 @@ test('should return undefined during first render if observable is async', () =>
7778
function ObservableComponent() {
7879
const observedValue = useObservable(observable)
7980
returnedValues.push(observedValue)
80-
return createElement(Fragment, null, observedValue)
81+
return <>{observedValue}</>
8182
}
82-
render(createElement(ObservableComponent))
83+
render(<ObservableComponent />)
8384
expect(returnedValues).toEqual(expect.arrayContaining([undefined]))
8485
})
8586

@@ -261,16 +262,16 @@ test('should return undefined if observable emits undefined, also when given ini
261262
[props.prefix],
262263
)
263264
snapshots.push(useObservable(observable, 'initial'))
264-
return createElement(Fragment, null)
265+
return null
265266
}
266267

267-
const {unmount, rerender} = render(createElement(ObservableComponent, {prefix: 'first'}))
268+
const {unmount, rerender} = render(<ObservableComponent prefix="first" />)
268269
act(() => subject.next('foo'))
269270
act(() => subject.next(undefined))
270271
act(() => subject.next('bar'))
271272

272273
// now change the prefix
273-
rerender(createElement(ObservableComponent, {prefix: 'second'}))
274+
rerender(<ObservableComponent prefix="second" />)
274275
act(() => subject.next('foo again'))
275276
act(() => subject.next(undefined))
276277
act(() => subject.next('bar again'))
@@ -286,3 +287,25 @@ test('should return undefined if observable emits undefined, also when given ini
286287
])
287288
unmount()
288289
})
290+
291+
test('should support SSR if an initial value is given', () => {
292+
const observable = scheduled('async value', asyncScheduler)
293+
function ObservableComponent() {
294+
const observedValue = useObservable(observable, 'initial value')
295+
return <>{observedValue}</>
296+
}
297+
298+
expect(renderToString(<ObservableComponent />)).toBe('initial value')
299+
})
300+
301+
test('should throw during SSR if no initial value is defined', () => {
302+
const observable = scheduled('async value', asyncScheduler)
303+
function ObservableComponent() {
304+
const observedValue = useObservable(observable)
305+
return <>{observedValue}</>
306+
}
307+
308+
expect(() => renderToString(<ObservableComponent />)).toThrowErrorMatchingInlineSnapshot(
309+
`[Error: Missing getServerSnapshot, which is required for server-rendered content. Will revert to client rendering.]`,
310+
)
311+
})

vitest-cleanup-after-each.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {cleanup} from '@testing-library/react'
2+
import {afterEach} from 'vitest'
3+
4+
afterEach(() => {
5+
cleanup()
6+
})

vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {defineConfig} from 'vitest/config'
22

33
export default defineConfig({
44
test: {
5+
setupFiles: ['vitest-cleanup-after-each.ts'],
56
typecheck: {
67
ignoreSourceErrors: true,
78
},

vitest.workspace.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import {defineWorkspace} from 'vitest/config'
44
// defineWorkspace provides a nice type hinting DX
55
export default defineWorkspace([
66
{
7-
extends: './vitest.config.js',
7+
extends: './vitest.config',
88
plugins: [react()],
99
test: {
1010
name: 'default',
1111
},
1212
},
1313
{
14-
extends: './vitest.config.js',
14+
extends: './vitest.config',
1515
plugins: [
1616
react({
1717
babel: {plugins: [['babel-plugin-react-compiler', {target: '18'}]]},

0 commit comments

Comments
 (0)