Skip to content

Commit 24ee87c

Browse files
authoredMar 14, 2025··
test(releases): add unit tests for createReleaseMetadataAggregator and useActiveReleases hooks (#8926)
1 parent 621fc11 commit 24ee87c

File tree

2 files changed

+271
-0
lines changed

2 files changed

+271
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import {type SanityClient} from '@sanity/client'
2+
import {catchError, firstValueFrom, Observable, of, Subject, take, toArray} from 'rxjs'
3+
import {beforeEach, describe, expect, it, type Mock, vi} from 'vitest'
4+
5+
import {createReleaseMetadataAggregator} from '../createReleaseMetadataAggregator'
6+
7+
describe('createReleaseMetadataAggregator', () => {
8+
const mockClient = {
9+
observable: {
10+
fetch: vi.fn(),
11+
listen: vi.fn(),
12+
},
13+
} as unknown as SanityClient & {
14+
observable: {
15+
fetch: Mock<SanityClient['observable']['fetch']>
16+
listen: Mock<SanityClient['observable']['listen']>
17+
}
18+
}
19+
20+
beforeEach(() => {
21+
vi.clearAllMocks()
22+
mockClient.observable.listen.mockReturnValue(new Subject())
23+
})
24+
25+
it('should emit empty state when no release ids provided', async () => {
26+
const result = await firstValueFrom(createReleaseMetadataAggregator(mockClient)([]))
27+
expect(result).toEqual({data: null, error: null, loading: false})
28+
})
29+
30+
it('should emit metadata for releases with loading states', async () => {
31+
mockClient.observable.fetch.mockReturnValue(
32+
of({
33+
ms: 0,
34+
result: {
35+
'_.releases.release-1': {
36+
updatedAt: '2024-01-01T00:00:00Z',
37+
documentCount: 1,
38+
},
39+
},
40+
}),
41+
)
42+
43+
const values = await createReleaseMetadataAggregator(mockClient)(['_.releases.release-1'])
44+
.pipe(take(2), toArray())
45+
.toPromise()
46+
47+
expect(values).toEqual([
48+
{loading: true, data: null, error: null},
49+
{
50+
loading: false,
51+
error: null,
52+
data: {
53+
ms: {documentCount: 0},
54+
result: {
55+
'_.releases.release-1': {
56+
updatedAt: '2024-01-01T00:00:00Z',
57+
documentCount: 1,
58+
},
59+
'documentCount': 0,
60+
},
61+
},
62+
},
63+
])
64+
})
65+
66+
it('should handle fetch errors with loading states', async () => {
67+
const error = new Error('Fetch failed')
68+
mockClient.observable.fetch.mockReturnValue(
69+
new Observable((subscriber) => subscriber.error(error)),
70+
)
71+
72+
const values = await createReleaseMetadataAggregator(mockClient)(['_.releases.release-1'])
73+
.pipe(
74+
take(2),
75+
toArray(),
76+
catchError((err) =>
77+
of([
78+
{loading: true, data: null, error: null},
79+
{loading: false, data: null, error: err},
80+
]),
81+
),
82+
)
83+
.toPromise()
84+
85+
expect(values).toEqual([
86+
{loading: true, data: null, error: null},
87+
{loading: false, data: null, error},
88+
])
89+
})
90+
91+
it('should handle null client', async () => {
92+
const result = await firstValueFrom(
93+
createReleaseMetadataAggregator(null)(['_.releases.release-1']),
94+
)
95+
expect(result).toEqual({data: null, error: null, loading: false})
96+
})
97+
98+
it('should fetch metadata for multiple releases', async () => {
99+
mockClient.observable.fetch.mockReturnValue(
100+
of({
101+
ms: 0,
102+
result: {
103+
'_.releases.release-1': {updatedAt: '2024-01-01T00:00:00Z', documentCount: 1},
104+
'_.releases.release-2': {updatedAt: '2024-01-02T00:00:00Z', documentCount: 2},
105+
},
106+
}),
107+
)
108+
109+
const values: any[] = []
110+
const subscription = createReleaseMetadataAggregator(mockClient)([
111+
'_.releases.release-1',
112+
'_.releases.release-2',
113+
]).subscribe((value) => values.push(value))
114+
115+
await new Promise((resolve) => setTimeout(resolve, 0))
116+
subscription.unsubscribe()
117+
118+
expect(values).toEqual([
119+
{loading: true, data: null, error: null},
120+
{
121+
loading: false,
122+
error: null,
123+
data: {
124+
ms: {documentCount: 0},
125+
result: {
126+
'_.releases.release-1': {updatedAt: '2024-01-01T00:00:00Z', documentCount: 1},
127+
'_.releases.release-2': {updatedAt: '2024-01-02T00:00:00Z', documentCount: 2},
128+
'documentCount': 0,
129+
},
130+
},
131+
},
132+
])
133+
})
134+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import {act, renderHook, waitFor} from '@testing-library/react'
2+
import {BehaviorSubject} from 'rxjs'
3+
import {beforeEach, describe, expect, it, vi} from 'vitest'
4+
5+
import {createTestProvider} from '../../../../../test/testUtils/TestProvider'
6+
import {
7+
activeASAPRelease,
8+
activeScheduledRelease,
9+
archivedScheduledRelease,
10+
} from '../../__fixtures__/release.fixture'
11+
import {type ReleaseDocument} from '../types'
12+
import {useActiveReleases} from '../useActiveReleases'
13+
14+
interface ReleasesState {
15+
releases: Map<string, ReleaseDocument>
16+
error?: Error
17+
state: 'initialising' | 'loading' | 'loaded' | 'error'
18+
}
19+
20+
const initialState: ReleasesState = {
21+
releases: new Map(),
22+
state: 'initialising',
23+
}
24+
25+
const mockState$ = new BehaviorSubject<ReleasesState>(initialState)
26+
const mockDispatch = vi.fn()
27+
28+
const mockUseReleasesStore = vi.fn(() => ({
29+
state$: mockState$,
30+
dispatch: mockDispatch,
31+
}))
32+
33+
vi.mock('../useReleasesStore', () => ({
34+
useReleasesStore: () => mockUseReleasesStore(),
35+
}))
36+
37+
describe('useActiveReleases', () => {
38+
beforeEach(() => {
39+
mockState$.next(initialState)
40+
vi.clearAllMocks()
41+
})
42+
43+
it('should return initial loading state', async () => {
44+
const wrapper = await createTestProvider()
45+
const {result} = renderHook(() => useActiveReleases(), {wrapper})
46+
47+
expect(result.current).toEqual({
48+
loading: true,
49+
data: [],
50+
error: undefined,
51+
dispatch: mockDispatch,
52+
})
53+
})
54+
55+
it('should return error state', async () => {
56+
const wrapper = await createTestProvider()
57+
const {result} = renderHook(() => useActiveReleases(), {wrapper})
58+
59+
const testError = new Error('Test error')
60+
act(() => {
61+
mockState$.next({
62+
releases: new Map(),
63+
error: testError,
64+
state: 'error',
65+
})
66+
})
67+
68+
await waitFor(() => {
69+
expect(result.current).toEqual({
70+
loading: false,
71+
data: [],
72+
error: testError,
73+
dispatch: mockDispatch,
74+
})
75+
})
76+
})
77+
78+
it('should filter out archived releases', async () => {
79+
const wrapper = await createTestProvider()
80+
const {result} = renderHook(() => useActiveReleases(), {wrapper})
81+
82+
const mockReleases = [activeASAPRelease, archivedScheduledRelease]
83+
84+
act(() => {
85+
mockState$.next({
86+
releases: new Map(mockReleases.map((release) => [release._id, release])),
87+
error: undefined,
88+
state: 'loaded',
89+
})
90+
})
91+
92+
await waitFor(() => {
93+
expect(result.current).toEqual({
94+
loading: false,
95+
data: [activeASAPRelease],
96+
error: undefined,
97+
dispatch: mockDispatch,
98+
})
99+
})
100+
})
101+
102+
it('should sort releases in reverse order', async () => {
103+
const wrapper = await createTestProvider()
104+
const {result} = renderHook(() => useActiveReleases(), {wrapper})
105+
106+
const mockReleases = [activeASAPRelease, activeScheduledRelease]
107+
108+
act(() => {
109+
mockState$.next({
110+
releases: new Map(mockReleases.map((release) => [release._id, release])),
111+
error: undefined,
112+
state: 'loaded',
113+
})
114+
})
115+
116+
await waitFor(() => {
117+
expect(result.current).toEqual({
118+
loading: false,
119+
data: mockReleases,
120+
error: undefined,
121+
dispatch: mockDispatch,
122+
})
123+
})
124+
})
125+
126+
it('should expose dispatch function', async () => {
127+
const wrapper = await createTestProvider()
128+
const {result} = renderHook(() => useActiveReleases(), {wrapper})
129+
130+
expect(result.current).toEqual({
131+
loading: true,
132+
data: [],
133+
error: undefined,
134+
dispatch: mockDispatch,
135+
})
136+
})
137+
})

0 commit comments

Comments
 (0)
Please sign in to comment.