Skip to content

Commit 8c2fae6

Browse files
feat: assessment feature implementation completed > test coverage
1 parent eb6bdae commit 8c2fae6

1 file changed

Lines changed: 323 additions & 0 deletions

File tree

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2+
import { configureStore } from '@reduxjs/toolkit';
3+
import { setupListeners } from '@reduxjs/toolkit/query';
4+
import { CourseMetadataSlice } from '../course-metadata';
5+
6+
describe('CourseMetadataSlice', () => {
7+
beforeEach(() => {
8+
vi.clearAllMocks();
9+
});
10+
11+
it('exports CourseMetadataSlice API with correct reducerPath', () => {
12+
expect(CourseMetadataSlice).toBeDefined();
13+
expect(CourseMetadataSlice.reducerPath).toBe('CourseMetadataSlice');
14+
});
15+
16+
it('has reducer function', () => {
17+
expect(CourseMetadataSlice.reducer).toBeDefined();
18+
expect(typeof CourseMetadataSlice.reducer).toBe('function');
19+
});
20+
21+
it('has middleware function', () => {
22+
expect(CourseMetadataSlice.middleware).toBeDefined();
23+
});
24+
25+
it('has endpoints defined', () => {
26+
expect(CourseMetadataSlice.endpoints).toBeDefined();
27+
});
28+
});
29+
30+
describe('CourseMetadataSlice hooks exports', () => {
31+
it('exports useGetCourseMetaDataQuery', async () => {
32+
const { useGetCourseMetaDataQuery } = await import('../course-metadata');
33+
expect(useGetCourseMetaDataQuery).toBeDefined();
34+
expect(typeof useGetCourseMetaDataQuery).toBe('function');
35+
});
36+
37+
it('exports useLazyGetCourseMetaDataQuery', async () => {
38+
const { useLazyGetCourseMetaDataQuery } = await import('../course-metadata');
39+
expect(useLazyGetCourseMetaDataQuery).toBeDefined();
40+
expect(typeof useLazyGetCourseMetaDataQuery).toBe('function');
41+
});
42+
43+
it('exports useGetCourseCompletionOutlinesQuery', async () => {
44+
const { useGetCourseCompletionOutlinesQuery } = await import('../course-metadata');
45+
expect(useGetCourseCompletionOutlinesQuery).toBeDefined();
46+
expect(typeof useGetCourseCompletionOutlinesQuery).toBe('function');
47+
});
48+
49+
it('exports useLazyGetCourseCompletionOutlinesQuery', async () => {
50+
const { useLazyGetCourseCompletionOutlinesQuery } = await import('../course-metadata');
51+
expect(useLazyGetCourseCompletionOutlinesQuery).toBeDefined();
52+
expect(typeof useLazyGetCourseCompletionOutlinesQuery).toBe('function');
53+
});
54+
55+
it('exports useGetCourseEligibilityQuery', async () => {
56+
const { useGetCourseEligibilityQuery } = await import('../course-metadata');
57+
expect(useGetCourseEligibilityQuery).toBeDefined();
58+
expect(typeof useGetCourseEligibilityQuery).toBe('function');
59+
});
60+
61+
it('exports useLazyGetCourseEligibilityQuery', async () => {
62+
const { useLazyGetCourseEligibilityQuery } = await import('../course-metadata');
63+
expect(useLazyGetCourseEligibilityQuery).toBeDefined();
64+
expect(typeof useLazyGetCourseEligibilityQuery).toBe('function');
65+
});
66+
67+
it('exports useCreateCourseEnrollmentMutation', async () => {
68+
const { useCreateCourseEnrollmentMutation } = await import('../course-metadata');
69+
expect(useCreateCourseEnrollmentMutation).toBeDefined();
70+
expect(typeof useCreateCourseEnrollmentMutation).toBe('function');
71+
});
72+
73+
it('exports useGetCourseProgressQuery', async () => {
74+
const { useGetCourseProgressQuery } = await import('../course-metadata');
75+
expect(useGetCourseProgressQuery).toBeDefined();
76+
expect(typeof useGetCourseProgressQuery).toBe('function');
77+
});
78+
79+
it('exports useLazyGetCourseProgressQuery', async () => {
80+
const { useLazyGetCourseProgressQuery } = await import('../course-metadata');
81+
expect(useLazyGetCourseProgressQuery).toBeDefined();
82+
expect(typeof useLazyGetCourseProgressQuery).toBe('function');
83+
});
84+
85+
it('exports useGetCourseCompletionQuery', async () => {
86+
const { useGetCourseCompletionQuery } = await import('../course-metadata');
87+
expect(useGetCourseCompletionQuery).toBeDefined();
88+
expect(typeof useGetCourseCompletionQuery).toBe('function');
89+
});
90+
91+
it('exports useLazyGetCourseCompletionQuery', async () => {
92+
const { useLazyGetCourseCompletionQuery } = await import('../course-metadata');
93+
expect(useLazyGetCourseCompletionQuery).toBeDefined();
94+
expect(typeof useLazyGetCourseCompletionQuery).toBe('function');
95+
});
96+
97+
it('exports useGetCourseBlockDetailsQuery', async () => {
98+
const { useGetCourseBlockDetailsQuery } = await import('../course-metadata');
99+
expect(useGetCourseBlockDetailsQuery).toBeDefined();
100+
expect(typeof useGetCourseBlockDetailsQuery).toBe('function');
101+
});
102+
103+
it('exports useLazyGetCourseBlockDetailsQuery', async () => {
104+
const { useLazyGetCourseBlockDetailsQuery } = await import('../course-metadata');
105+
expect(useLazyGetCourseBlockDetailsQuery).toBeDefined();
106+
expect(typeof useLazyGetCourseBlockDetailsQuery).toBe('function');
107+
});
108+
});
109+
110+
describe('CourseMetadataSlice endpoint structure', () => {
111+
it('getCourseMetaData endpoint exists', () => {
112+
expect(CourseMetadataSlice.endpoints.getCourseMetaData).toBeDefined();
113+
});
114+
115+
it('getCourseCompletionOutlines endpoint exists', () => {
116+
expect(CourseMetadataSlice.endpoints.getCourseCompletionOutlines).toBeDefined();
117+
});
118+
119+
it('getCourseEligibility endpoint exists', () => {
120+
expect(CourseMetadataSlice.endpoints.getCourseEligibility).toBeDefined();
121+
});
122+
123+
it('createCourseEnrollment endpoint exists', () => {
124+
expect(CourseMetadataSlice.endpoints.createCourseEnrollment).toBeDefined();
125+
});
126+
127+
it('getCourseProgress endpoint exists', () => {
128+
expect(CourseMetadataSlice.endpoints.getCourseProgress).toBeDefined();
129+
});
130+
131+
it('getCourseCompletion endpoint exists', () => {
132+
expect(CourseMetadataSlice.endpoints.getCourseCompletion).toBeDefined();
133+
});
134+
135+
it('getCourseBlockDetails endpoint exists', () => {
136+
expect(CourseMetadataSlice.endpoints.getCourseBlockDetails).toBeDefined();
137+
});
138+
});
139+
140+
describe('CourseMetadataSlice endpoint matchers', () => {
141+
it.each([
142+
'getCourseMetaData',
143+
'getCourseCompletionOutlines',
144+
'getCourseEligibility',
145+
'createCourseEnrollment',
146+
'getCourseProgress',
147+
'getCourseCompletion',
148+
'getCourseBlockDetails',
149+
] as const)('%s has matcher functions', (name) => {
150+
const endpoint = (CourseMetadataSlice.endpoints as Record<string, any>)[name];
151+
expect(endpoint.matchPending).toBeDefined();
152+
expect(endpoint.matchFulfilled).toBeDefined();
153+
expect(endpoint.matchRejected).toBeDefined();
154+
});
155+
156+
it.each([
157+
'getCourseMetaData',
158+
'getCourseCompletionOutlines',
159+
'getCourseEligibility',
160+
'createCourseEnrollment',
161+
'getCourseProgress',
162+
'getCourseCompletion',
163+
'getCourseBlockDetails',
164+
] as const)('%s has initiate function', (name) => {
165+
const endpoint = (CourseMetadataSlice.endpoints as Record<string, any>)[name];
166+
expect(endpoint.initiate).toBeDefined();
167+
expect(typeof endpoint.initiate).toBe('function');
168+
expect(endpoint.name).toBe(name);
169+
});
170+
});
171+
172+
describe('CourseMetadataSlice internal utils', () => {
173+
it('has util methods for cache management', () => {
174+
expect(CourseMetadataSlice.util).toBeDefined();
175+
expect(CourseMetadataSlice.util.resetApiState).toBeDefined();
176+
expect(CourseMetadataSlice.util.invalidateTags).toBeDefined();
177+
});
178+
179+
it('has internalActions for advanced usage', () => {
180+
expect(CourseMetadataSlice.internalActions).toBeDefined();
181+
});
182+
183+
it('endpoints have select function for creating selectors', () => {
184+
const endpoint = CourseMetadataSlice.endpoints.getCourseMetaData as any;
185+
expect(endpoint.select).toBeDefined();
186+
expect(typeof endpoint.select).toBe('function');
187+
188+
const selector = endpoint.select({ courseKey: 'test-course' });
189+
expect(selector).toBeDefined();
190+
expect(typeof selector).toBe('function');
191+
});
192+
});
193+
194+
describe('CourseMetadataSlice query functions (executed via store dispatch)', () => {
195+
let store: ReturnType<typeof configureStore>;
196+
let fetchMock: ReturnType<typeof vi.fn>;
197+
let capturedRequests: Array<{ url: string; method: string; body?: string | null }>;
198+
199+
beforeEach(() => {
200+
capturedRequests = [];
201+
fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
202+
const isRequest = typeof Request !== 'undefined' && input instanceof Request;
203+
const url = typeof input === 'string' ? input : isRequest ? input.url : String(input);
204+
const method = (
205+
init?.method ??
206+
(isRequest ? input.method : undefined) ??
207+
'GET'
208+
).toUpperCase();
209+
let body: string | null = null;
210+
if (init?.body) {
211+
body = String(init.body);
212+
} else if (isRequest) {
213+
try {
214+
body = await input.clone().text();
215+
} catch {
216+
body = null;
217+
}
218+
}
219+
capturedRequests.push({ url, method, body });
220+
return new Response(JSON.stringify({ ok: true }), {
221+
status: 200,
222+
headers: { 'Content-Type': 'application/json' },
223+
});
224+
});
225+
vi.stubGlobal('fetch', fetchMock);
226+
227+
store = configureStore({
228+
reducer: {
229+
[CourseMetadataSlice.reducerPath]: CourseMetadataSlice.reducer,
230+
},
231+
middleware: (getDefault) => getDefault().concat(CourseMetadataSlice.middleware),
232+
});
233+
setupListeners(store.dispatch);
234+
});
235+
236+
afterEach(() => {
237+
vi.unstubAllGlobals();
238+
});
239+
240+
it('getCourseMetaData builds the expected URL with encoded courseKey', async () => {
241+
await store.dispatch(
242+
(CourseMetadataSlice.endpoints.getCourseMetaData as any).initiate({
243+
courseKey: 'course-v1:Org+Run+1',
244+
}),
245+
);
246+
expect(capturedRequests).toHaveLength(1);
247+
expect(capturedRequests[0].method).toBe('GET');
248+
expect(capturedRequests[0].url).toContain(
249+
'/api/ibl/v1/course_metadata?course_key=course-v1%3AOrg%2BRun%2B1',
250+
);
251+
});
252+
253+
it('getCourseCompletionOutlines builds the expected URL with encoded courseKey', async () => {
254+
await store.dispatch(
255+
(CourseMetadataSlice.endpoints.getCourseCompletionOutlines as any).initiate({
256+
courseKey: 'course-v1:Org+Run+1',
257+
}),
258+
);
259+
expect(capturedRequests).toHaveLength(1);
260+
expect(capturedRequests[0].url).toContain(
261+
'/api/ibl/completion/course_outline/course-v1:Org+Run+1?course_id=course-v1%3AOrg%2BRun%2B1',
262+
);
263+
});
264+
265+
it('getCourseEligibility builds the expected URL with encoded courseKey', async () => {
266+
await store.dispatch(
267+
(CourseMetadataSlice.endpoints.getCourseEligibility as any).initiate({
268+
courseKey: 'course-v1:Org+Run+1',
269+
}),
270+
);
271+
expect(capturedRequests).toHaveLength(1);
272+
expect(capturedRequests[0].url).toContain(
273+
'/api/ibl/enrollment/enroll_status?course_id=course-v1%3AOrg%2BRun%2B1',
274+
);
275+
});
276+
277+
it('createCourseEnrollment performs a POST with the request body', async () => {
278+
const request = { course_details: { course_id: 'course-v1:Org+Run+1' } } as any;
279+
await store.dispatch(
280+
(CourseMetadataSlice.endpoints.createCourseEnrollment as any).initiate(request),
281+
);
282+
expect(capturedRequests).toHaveLength(1);
283+
expect(capturedRequests[0].method).toBe('POST');
284+
expect(capturedRequests[0].url).toContain('/api/enrollment/v1/enrollment');
285+
expect(capturedRequests[0].body).toBe(JSON.stringify(request));
286+
});
287+
288+
it('getCourseProgress builds the expected URL', async () => {
289+
await store.dispatch(
290+
(CourseMetadataSlice.endpoints.getCourseProgress as any).initiate({
291+
courseKey: 'course-v1:Org+Run+1',
292+
}),
293+
);
294+
expect(capturedRequests).toHaveLength(1);
295+
expect(capturedRequests[0].url).toContain('/api/course_home/progress/course-v1:Org+Run+1');
296+
});
297+
298+
it('getCourseCompletion builds the expected URL with course and user id', async () => {
299+
await store.dispatch(
300+
(CourseMetadataSlice.endpoints.getCourseCompletion as any).initiate({
301+
courseKey: 'course-v1:Org+Run+1',
302+
userID: 42,
303+
}),
304+
);
305+
expect(capturedRequests).toHaveLength(1);
306+
expect(capturedRequests[0].url).toContain(
307+
'/api/catalog/milestones/completions/course/manage/?course_id=course-v1:Org+Run+1&user_id=42',
308+
);
309+
});
310+
311+
it('getCourseBlockDetails builds the expected URL with encoded blockId and username', async () => {
312+
await store.dispatch(
313+
(CourseMetadataSlice.endpoints.getCourseBlockDetails as any).initiate({
314+
blockId: 'block-v1:Org+Run+1+type@vertical+block@abc',
315+
username: 'jane doe',
316+
}),
317+
);
318+
expect(capturedRequests).toHaveLength(1);
319+
expect(capturedRequests[0].url).toContain(
320+
'/api/courses/v2/blocks/block-v1%3AOrg%2BRun%2B1%2Btype%40vertical%2Bblock%40abc?username=jane%20doe&depth=all',
321+
);
322+
});
323+
});

0 commit comments

Comments
 (0)