@nualang/nualang-ui-components 0.1.1431 → 0.1.1432
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -54,6 +54,39 @@ const DEFAULT_FILTERS = {
|
|
|
54
54
|
difficulty: "all",
|
|
55
55
|
sort: "popular"
|
|
56
56
|
};
|
|
57
|
+
const MY_LIBRARY_TAB = 0;
|
|
58
|
+
// My library lists the user's own courses, so newest first is more useful
|
|
59
|
+
// there than popularity. The Community tab keeps the "popular" default.
|
|
60
|
+
const MY_LIBRARY_DEFAULT_SORT = "newest";
|
|
61
|
+
|
|
62
|
+
// My library rows are fetched in full and filtered here, the same way the
|
|
63
|
+
// Courses screen does it, so search never goes through the global course search.
|
|
64
|
+
function filterLibraryCourses(courses, filters, search) {
|
|
65
|
+
const searchText = search.trim().toLowerCase();
|
|
66
|
+
const filtered = courses.filter(course => {
|
|
67
|
+
if (filters.learnLang !== "all" && course.learnLang !== filters.learnLang) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (filters.forLang !== "all" && course.forLang !== filters.forLang) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (filters.difficulty !== "all") {
|
|
74
|
+
const difficulties = Array.isArray(course.difficulty) ? course.difficulty : [course.difficulty];
|
|
75
|
+
if (!difficulties.includes(filters.difficulty)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (searchText) {
|
|
80
|
+
const nameMatch = (course.courseName || "").toLowerCase().includes(searchText);
|
|
81
|
+
const descriptionMatch = (course.description || "").toLowerCase().includes(searchText);
|
|
82
|
+
if (!nameMatch && !descriptionMatch) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
88
|
+
return sortItems(filtered, filters.sort);
|
|
89
|
+
}
|
|
57
90
|
function CourseFilters({
|
|
58
91
|
t,
|
|
59
92
|
search,
|
|
@@ -368,6 +401,7 @@ export default function FindCoursesDialog({
|
|
|
368
401
|
formatCourses,
|
|
369
402
|
groupCourseIds = [],
|
|
370
403
|
groupCoursesLimit = 100,
|
|
404
|
+
createdCoursesLimit = 1000,
|
|
371
405
|
alreadyAddedCourseIds = [],
|
|
372
406
|
isCommunityAccessRestricted = false,
|
|
373
407
|
learnLangOptions = [],
|
|
@@ -382,16 +416,19 @@ export default function FindCoursesDialog({
|
|
|
382
416
|
} = useInView({
|
|
383
417
|
threshold: 0
|
|
384
418
|
});
|
|
385
|
-
const [tab, setTab] = useState(
|
|
386
|
-
const effectiveTab = isCommunityAccessRestricted ?
|
|
419
|
+
const [tab, setTab] = useState(MY_LIBRARY_TAB);
|
|
420
|
+
const effectiveTab = isCommunityAccessRestricted ? MY_LIBRARY_TAB : tab;
|
|
387
421
|
const [selectedCourseIds, setSelectedCourseIds] = useState([]);
|
|
388
422
|
const [search, setSearch] = useState("");
|
|
389
|
-
const
|
|
423
|
+
const getDefaultFilters = tabIndex => ({
|
|
390
424
|
...DEFAULT_FILTERS,
|
|
391
425
|
learnLang: defaultLearnLang,
|
|
392
|
-
forLang: defaultForLang
|
|
393
|
-
|
|
394
|
-
|
|
426
|
+
forLang: defaultForLang,
|
|
427
|
+
...(tabIndex === MY_LIBRARY_TAB && {
|
|
428
|
+
sort: MY_LIBRARY_DEFAULT_SORT
|
|
429
|
+
})
|
|
430
|
+
});
|
|
431
|
+
const [filters, setFilters] = useState(() => getDefaultFilters(MY_LIBRARY_TAB));
|
|
395
432
|
// Every course object ever handed to this dialog, keyed by id. search/
|
|
396
433
|
// difficulty/sort are now applied upstream (the caller only fetches
|
|
397
434
|
// matching pages), so a course that's selected and then filtered out of
|
|
@@ -407,8 +444,8 @@ export default function FindCoursesDialog({
|
|
|
407
444
|
const reset = () => {
|
|
408
445
|
setSelectedCourseIds([]);
|
|
409
446
|
setSearch("");
|
|
410
|
-
setFilters(
|
|
411
|
-
setTab(
|
|
447
|
+
setFilters(getDefaultFilters(MY_LIBRARY_TAB));
|
|
448
|
+
setTab(MY_LIBRARY_TAB);
|
|
412
449
|
setSearchResetKey(key => key + 1);
|
|
413
450
|
setCourseCache(new Map());
|
|
414
451
|
};
|
|
@@ -432,7 +469,7 @@ export default function FindCoursesDialog({
|
|
|
432
469
|
if (isCommunityAccessRestricted) return;
|
|
433
470
|
setTab(v);
|
|
434
471
|
setSearch("");
|
|
435
|
-
setFilters(
|
|
472
|
+
setFilters(getDefaultFilters(v));
|
|
436
473
|
setSearchResetKey(key => key + 1);
|
|
437
474
|
};
|
|
438
475
|
const handleFilterChange = event => {
|
|
@@ -450,28 +487,19 @@ export default function FindCoursesDialog({
|
|
|
450
487
|
// every page load - so the carousels below know when a reorder is a real
|
|
451
488
|
// reset versus just more of the same sort settling in as data grows.
|
|
452
489
|
const librarySortResetKey = `${search}|${filters.learnLang}|${filters.forLang}|${filters.difficulty}|${filters.sort}`;
|
|
490
|
+
|
|
491
|
+
// Search/filters/sort are applied client-side in libraryCreatedCourses, so
|
|
492
|
+
// changing them doesn't refetch
|
|
453
493
|
const teachCoursesQuery = useInfiniteQuery({
|
|
454
|
-
queryKey: ["findCoursesDialog", "created", username
|
|
494
|
+
queryKey: ["findCoursesDialog", "created", username],
|
|
455
495
|
queryFn: async ({
|
|
456
496
|
pageParam = {}
|
|
457
497
|
}) => {
|
|
458
498
|
const response = await getCourses({
|
|
459
499
|
createdBy: username,
|
|
460
500
|
verbose: false,
|
|
461
|
-
limit
|
|
462
|
-
|
|
463
|
-
...(filters.learnLang !== "all" && {
|
|
464
|
-
learnLang: filters.learnLang
|
|
465
|
-
}),
|
|
466
|
-
...(filters.forLang !== "all" && {
|
|
467
|
-
forLang: filters.forLang
|
|
468
|
-
}),
|
|
469
|
-
...(filters.difficulty !== "all" && {
|
|
470
|
-
difficulty: filters.difficulty
|
|
471
|
-
}),
|
|
472
|
-
...(search && {
|
|
473
|
-
search
|
|
474
|
-
}),
|
|
501
|
+
// Get all using high limit, same as the Courses screen
|
|
502
|
+
limit: createdCoursesLimit,
|
|
475
503
|
page: pageParam.page || 1,
|
|
476
504
|
lastEvaluatedCourseId: pageParam.courseId || "",
|
|
477
505
|
lastEvaluatedKey: pageParam.courseId || "",
|
|
@@ -488,6 +516,20 @@ export default function FindCoursesDialog({
|
|
|
488
516
|
getNextPageParam: nextLibraryPageParam,
|
|
489
517
|
enabled: !!username && open
|
|
490
518
|
});
|
|
519
|
+
const {
|
|
520
|
+
hasNextPage: hasMoreCreated,
|
|
521
|
+
isFetchingNextPage: isFetchingMoreCreated,
|
|
522
|
+
isFetchNextPageError: isCreatedNextPageError
|
|
523
|
+
} = teachCoursesQuery;
|
|
524
|
+
useEffect(() => {
|
|
525
|
+
if (!open || !username || !hasMoreCreated || isFetchingMoreCreated || isCreatedNextPageError) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
teachCoursesQuery.fetchNextPage({
|
|
529
|
+
cancelRefetch: false
|
|
530
|
+
});
|
|
531
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
532
|
+
}, [open, username, hasMoreCreated, isFetchingMoreCreated, isCreatedNextPageError]);
|
|
491
533
|
const learnCoursesQuery = useInfiniteQuery({
|
|
492
534
|
queryKey: ["findCoursesDialog", "joined", username],
|
|
493
535
|
queryFn: async ({
|
|
@@ -530,24 +572,9 @@ export default function FindCoursesDialog({
|
|
|
530
572
|
const groupCourses = groupCoursesQuery.isSuccess && Array.isArray(groupCoursesQuery.data) ? groupCoursesQuery.data : [];
|
|
531
573
|
const combined = [...learnCourses, ...groupCourses];
|
|
532
574
|
const deduped = combined.filter((course, index) => combined.findIndex(c => c.courseId === course.courseId) === index);
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if (filters.forLang !== "all" && course.forLang !== filters.forLang) return false;
|
|
537
|
-
if (filters.difficulty !== "all") {
|
|
538
|
-
const difficulties = Array.isArray(course.difficulty) ? course.difficulty : [course.difficulty];
|
|
539
|
-
if (!difficulties.includes(filters.difficulty)) return false;
|
|
540
|
-
}
|
|
541
|
-
if (searchText) {
|
|
542
|
-
const nameMatch = (course.courseName || "").toLowerCase().includes(searchText);
|
|
543
|
-
const descriptionMatch = (course.description || "").toLowerCase().includes(searchText);
|
|
544
|
-
if (!nameMatch && !descriptionMatch) return false;
|
|
545
|
-
}
|
|
546
|
-
return true;
|
|
547
|
-
});
|
|
548
|
-
return sortItems(filtered, filters.sort);
|
|
549
|
-
}, [learnCoursesQuery.data, groupCoursesQuery.data, groupCoursesQuery.isSuccess, filters.learnLang, filters.forLang, filters.difficulty, search, filters.sort]);
|
|
550
|
-
const libraryCreatedCourses = useMemo(() => (teachCoursesQuery.data?.pages || []).flatMap(page => page.Items || []), [teachCoursesQuery.data]);
|
|
575
|
+
return filterLibraryCourses(deduped, filters, search);
|
|
576
|
+
}, [learnCoursesQuery.data, groupCoursesQuery.data, groupCoursesQuery.isSuccess, filters, search]);
|
|
577
|
+
const libraryCreatedCourses = useMemo(() => filterLibraryCourses((teachCoursesQuery.data?.pages || []).flatMap(page => page.Items || []), filters, search), [teachCoursesQuery.data, filters, search]);
|
|
551
578
|
const communityCoursesQuery = courseQuerys.useCommunityCourses(async queryFilters => {
|
|
552
579
|
const response = await getCourses(queryFilters);
|
|
553
580
|
const formattedCourses = await formatCourses(response.Items, authData);
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { ReactQuery } from "@nualang/nualang-api-and-queries/Queries";
|
|
2
2
|
import "intersection-observer";
|
|
3
3
|
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
|
4
|
-
import { render, fireEvent, act } from "@testing-library/react";
|
|
4
|
+
import { render, fireEvent, act, screen, waitFor, cleanup } from "@testing-library/react";
|
|
5
5
|
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
6
|
+
import { getCourses } from "@nualang/nualang-api-and-queries/APIs/Courses";
|
|
6
7
|
import FindCoursesDialog from "./FindCoursesDialog";
|
|
7
8
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
vi.mock("@nualang/nualang-api-and-queries/APIs/Courses", () => ({
|
|
10
|
+
getCourses: vi.fn(async () => ({
|
|
11
|
+
Items: [],
|
|
12
|
+
Count: 0
|
|
13
|
+
}))
|
|
14
|
+
}));
|
|
8
15
|
const {
|
|
9
16
|
QueryClient,
|
|
10
17
|
QueryClientProvider
|
|
@@ -77,4 +84,252 @@ describe("FindCoursesDialog", () => {
|
|
|
77
84
|
expect(searchInput.value).toBe("");
|
|
78
85
|
});
|
|
79
86
|
});
|
|
87
|
+
describe("My library search", () => {
|
|
88
|
+
const createdCourses = [{
|
|
89
|
+
courseId: "mine-1",
|
|
90
|
+
courseName: "Spanish Basics",
|
|
91
|
+
createdBy: "me",
|
|
92
|
+
learnLang: "spanish",
|
|
93
|
+
forLang: "english",
|
|
94
|
+
difficulty: "A1",
|
|
95
|
+
visibility: "private"
|
|
96
|
+
}, {
|
|
97
|
+
courseId: "mine-2",
|
|
98
|
+
courseName: "French Verbs",
|
|
99
|
+
createdBy: "me",
|
|
100
|
+
learnLang: "french",
|
|
101
|
+
forLang: "english",
|
|
102
|
+
difficulty: "A1",
|
|
103
|
+
visibility: "public"
|
|
104
|
+
}];
|
|
105
|
+
const joinedCourses = [{
|
|
106
|
+
courseId: "joined-1",
|
|
107
|
+
courseName: "Spanish Joined",
|
|
108
|
+
createdBy: "teacher",
|
|
109
|
+
learnLang: "spanish",
|
|
110
|
+
forLang: "english",
|
|
111
|
+
difficulty: "A1",
|
|
112
|
+
visibility: "public"
|
|
113
|
+
}];
|
|
114
|
+
const communityCourses = [{
|
|
115
|
+
courseId: "other-1",
|
|
116
|
+
courseName: "Spanish Community",
|
|
117
|
+
createdBy: "someone-else",
|
|
118
|
+
learnLang: "spanish",
|
|
119
|
+
forLang: "english",
|
|
120
|
+
difficulty: "A1",
|
|
121
|
+
visibility: "public"
|
|
122
|
+
}];
|
|
123
|
+
afterEach(() => {
|
|
124
|
+
cleanup();
|
|
125
|
+
});
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
// Earlier tests leave their dialog mounted - remove it so typeSearch
|
|
128
|
+
// targets this test's search input
|
|
129
|
+
cleanup();
|
|
130
|
+
getCourses.mockReset();
|
|
131
|
+
getCourses.mockImplementation(async filters => {
|
|
132
|
+
if (filters.createdBy) {
|
|
133
|
+
return {
|
|
134
|
+
Items: createdCourses,
|
|
135
|
+
Count: createdCourses.length
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (filters.memberId) {
|
|
139
|
+
return {
|
|
140
|
+
Items: joinedCourses,
|
|
141
|
+
Count: joinedCourses.length
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
Items: communityCourses,
|
|
146
|
+
Count: communityCourses.length
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
const typeSearch = value => {
|
|
151
|
+
const searchInput = document.querySelector('[data-cy="find-courses-filter-search"] input');
|
|
152
|
+
act(() => {
|
|
153
|
+
fireEvent.change(searchInput, {
|
|
154
|
+
target: {
|
|
155
|
+
value
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
it("filters created courses client-side and never sends the search to the API", async () => {
|
|
161
|
+
renderDialog({
|
|
162
|
+
username: "me",
|
|
163
|
+
formatCourses: async items => items
|
|
164
|
+
});
|
|
165
|
+
await screen.findByText("French Verbs");
|
|
166
|
+
typeSearch("spanish");
|
|
167
|
+
|
|
168
|
+
// Allow for the 500ms search debounce
|
|
169
|
+
await waitFor(() => {
|
|
170
|
+
expect(screen.queryByText("French Verbs")).toBeNull();
|
|
171
|
+
}, {
|
|
172
|
+
timeout: 3000
|
|
173
|
+
});
|
|
174
|
+
expect(screen.getByText("Spanish Basics")).toBeTruthy();
|
|
175
|
+
// Other people's courses must not appear in My library
|
|
176
|
+
expect(screen.queryByText("Spanish Community")).toBeNull();
|
|
177
|
+
const createdCalls = getCourses.mock.calls.filter(([filters]) => filters.createdBy);
|
|
178
|
+
// Fetched once with only the creator - search/filters don't refetch
|
|
179
|
+
expect(createdCalls).toHaveLength(1);
|
|
180
|
+
expect(createdCalls[0][0]).not.toHaveProperty("search");
|
|
181
|
+
expect(createdCalls[0][0].createdBy).toBe("me");
|
|
182
|
+
});
|
|
183
|
+
it("defaults My library to newest and Community to popular", async () => {
|
|
184
|
+
getCourses.mockImplementation(async filters => {
|
|
185
|
+
if (filters.createdBy) {
|
|
186
|
+
return {
|
|
187
|
+
Items: [{
|
|
188
|
+
courseId: "old",
|
|
189
|
+
courseName: "Older Course",
|
|
190
|
+
createdBy: "me",
|
|
191
|
+
createdAt: 1000,
|
|
192
|
+
exerciseStartTotal: 50
|
|
193
|
+
}, {
|
|
194
|
+
courseId: "new",
|
|
195
|
+
courseName: "Newer Course",
|
|
196
|
+
createdBy: "me",
|
|
197
|
+
createdAt: 2000,
|
|
198
|
+
exerciseStartTotal: 0
|
|
199
|
+
}]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
Items: []
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
renderDialog({
|
|
207
|
+
username: "me",
|
|
208
|
+
formatCourses: async items => items
|
|
209
|
+
});
|
|
210
|
+
const sortInput = () => document.querySelector('input[name="sort"]');
|
|
211
|
+
expect(sortInput().value).toBe("newest");
|
|
212
|
+
|
|
213
|
+
// Newest first, even though the older course has more plays
|
|
214
|
+
await screen.findByText("Newer Course");
|
|
215
|
+
const names = screen.getAllByText(/^(Older|Newer) Course$/).map(el => el.textContent);
|
|
216
|
+
expect(names).toEqual(["Newer Course", "Older Course"]);
|
|
217
|
+
act(() => {
|
|
218
|
+
fireEvent.click(document.querySelector('[data-cy="find-courses-tab-community"]'));
|
|
219
|
+
});
|
|
220
|
+
expect(sortInput().value).toBe("popular");
|
|
221
|
+
act(() => {
|
|
222
|
+
fireEvent.click(document.querySelector('[data-cy="find-courses-tab-library"]'));
|
|
223
|
+
});
|
|
224
|
+
expect(sortInput().value).toBe("newest");
|
|
225
|
+
});
|
|
226
|
+
it("loads later created-course pages without scrolling when the first page has no matches", async () => {
|
|
227
|
+
const firstPage = [{
|
|
228
|
+
courseId: "mine-1",
|
|
229
|
+
courseName: "French Verbs",
|
|
230
|
+
createdBy: "me",
|
|
231
|
+
learnLang: "french",
|
|
232
|
+
forLang: "english",
|
|
233
|
+
difficulty: "A1"
|
|
234
|
+
}];
|
|
235
|
+
const secondPage = [{
|
|
236
|
+
courseId: "mine-2",
|
|
237
|
+
courseName: "Spanish Later Page",
|
|
238
|
+
createdBy: "me",
|
|
239
|
+
learnLang: "spanish",
|
|
240
|
+
forLang: "english",
|
|
241
|
+
difficulty: "A1"
|
|
242
|
+
}];
|
|
243
|
+
getCourses.mockImplementation(async filters => {
|
|
244
|
+
if (filters.createdBy) {
|
|
245
|
+
if (filters.page === 2) {
|
|
246
|
+
return {
|
|
247
|
+
Items: secondPage,
|
|
248
|
+
Count: secondPage.length
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
Items: firstPage,
|
|
253
|
+
Count: firstPage.length,
|
|
254
|
+
LastEvaluatedKey: {
|
|
255
|
+
page: 2
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (filters.memberId) {
|
|
260
|
+
return {
|
|
261
|
+
Items: joinedCourses,
|
|
262
|
+
Count: joinedCourses.length
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
Items: communityCourses,
|
|
267
|
+
Count: communityCourses.length
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
renderDialog({
|
|
271
|
+
username: "me",
|
|
272
|
+
formatCourses: async items => items,
|
|
273
|
+
createdCoursesLimit: 1
|
|
274
|
+
});
|
|
275
|
+
await screen.findByText("Spanish Joined");
|
|
276
|
+
typeSearch("spanish");
|
|
277
|
+
|
|
278
|
+
// Allow for the 500ms search debounce - no carousel scroll happens here
|
|
279
|
+
await waitFor(() => {
|
|
280
|
+
expect(screen.queryByText("French Verbs")).toBeNull();
|
|
281
|
+
}, {
|
|
282
|
+
timeout: 3000
|
|
283
|
+
});
|
|
284
|
+
expect(await screen.findByText("Spanish Later Page")).toBeTruthy();
|
|
285
|
+
expect(screen.queryByText("No created courses match these filters")).toBeNull();
|
|
286
|
+
const createdCalls = getCourses.mock.calls.filter(([filters]) => filters.createdBy);
|
|
287
|
+
expect(createdCalls).toHaveLength(2);
|
|
288
|
+
expect(createdCalls.map(([filters]) => filters.page)).toEqual([1, 2]);
|
|
289
|
+
createdCalls.forEach(([filters]) => {
|
|
290
|
+
expect(filters).not.toHaveProperty("search");
|
|
291
|
+
expect(filters.limit).toBe(1);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Courses joined still filters client-side without search in its request
|
|
295
|
+
expect(screen.getByText("Spanish Joined")).toBeTruthy();
|
|
296
|
+
getCourses.mock.calls.filter(([filters]) => filters.memberId).forEach(([filters]) => {
|
|
297
|
+
expect(filters).not.toHaveProperty("search");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Community still sends search to the API
|
|
301
|
+
act(() => {
|
|
302
|
+
fireEvent.click(document.querySelector('[data-cy="find-courses-tab-community"]'));
|
|
303
|
+
});
|
|
304
|
+
await screen.findByText("Spanish Community");
|
|
305
|
+
typeSearch("spanish");
|
|
306
|
+
await waitFor(() => {
|
|
307
|
+
const communityCalls = getCourses.mock.calls.filter(([filters]) => !filters.createdBy && !filters.memberId && !filters.courseIds);
|
|
308
|
+
expect(communityCalls.some(([filters]) => filters.search === "spanish")).toBe(true);
|
|
309
|
+
}, {
|
|
310
|
+
timeout: 3000
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
it("leaves Courses joined filtering unchanged", async () => {
|
|
314
|
+
renderDialog({
|
|
315
|
+
username: "me",
|
|
316
|
+
formatCourses: async items => items
|
|
317
|
+
});
|
|
318
|
+
await screen.findByText("Spanish Joined");
|
|
319
|
+
typeSearch("french");
|
|
320
|
+
|
|
321
|
+
// Allow for the 500ms search debounce
|
|
322
|
+
await waitFor(() => {
|
|
323
|
+
expect(screen.queryByText("Spanish Joined")).toBeNull();
|
|
324
|
+
}, {
|
|
325
|
+
timeout: 3000
|
|
326
|
+
});
|
|
327
|
+
expect(screen.getByText("French Verbs")).toBeTruthy();
|
|
328
|
+
const joinedCalls = getCourses.mock.calls.filter(([filters]) => filters.memberId);
|
|
329
|
+
expect(joinedCalls.length).toBeGreaterThan(0);
|
|
330
|
+
joinedCalls.forEach(([filters]) => {
|
|
331
|
+
expect(filters).not.toHaveProperty("search");
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
});
|
|
80
335
|
});
|