Port latest master to frontend-base (incl. Redux-to-React-Query migration)#801
Conversation
1b7091b to
39b38c8
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## frontend-base #801 +/- ##
================================================
Coverage ? 90.36%
================================================
Files ? 152
Lines ? 1287
Branches ? 276
================================================
Hits ? 1163
Misses ? 119
Partials ? 5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
f574820 to
55f36bb
Compare
55f36bb to
83dd294
Compare
83dd294 to
4cb9aa3
Compare
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
Co-authored-by: Deborah Kaplan <deborahgu@users.noreply.github.com> Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
…extra repositories (openedx#752) Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
Use 'test' string literal instead of EnvironmentTypes.TEST and import only the type to avoid circular dependency when mocking @openedx/frontend-base itself. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove all Redux files and packages. Add React Query hooks, Context providers, typed API layer, data transformers, and tests. Migrate all components including CourseCard, CourseCardBanners, CourseFilterControls, CoursesPanel, Dashboard, modals, and remaining widgets. Reconcile masquerade implementations with frontend-base providers.ts architecture. Co-Authored-By: Jacobo Dominguez <jacobo.dominguez@wgu.edu> Co-Authored-By: Claude <noreply@anthropic.com>
useInitializeLearnerHome() is consumed by 15+ components, and with staleTime defaulting to 0, every mount and window focus triggered a redundant background refetch. Set staleTime to 5 minutes since dashboard data rarely changes while the user is viewing it. Co-Authored-By: Claude <noreply@anthropic.com>
The useUnenrollFromCourse mutation already calls invalidateQueries on success, which triggers a refetch automatically. The manual refetch() call in closeAndRefresh caused the API to be called twice. Co-Authored-By: Claude <noreply@anthropic.com>
The fallback enrollment date was frozen when the module first loaded. If the app stayed open across midnight, courses with null lastEnrolled would get a stale timestamp. Co-Authored-By: Claude <noreply@anthropic.com>
Previously, modals closed immediately after firing mutations via mutate(), giving no feedback on failure. Now the close action is passed as an onSuccess callback so the modal stays open if the mutation fails. Co-Authored-By: Claude <noreply@anthropic.com>
Auto-fixed stylistic issues (indentation, semicolons, brace style, type→interface, Array<T>→T[]) and manually fixed display-name errors (named wrapper functions), @ts-ignore→@ts-expect-error, and Function→explicit signature. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert GlobalDataContext.jsx to .tsx with proper interface so setEmailConfirmation and setPlatformSettings are typed as Dispatch<SetStateAction<T>> | null instead of literal null. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The tsconfig declares "types": ["jest"] but @types/jest was not installed, so TypeScript treated jest as a namespace rather than a value. Adding the matching type definitions lets the webpack type-checker process test files without errors. Also bump frontend-base to fix the @src alias in jest config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, each useCourseData(cardId) call independently transformed the full course list via getTransformedCourseDataList just to find one item. With N course cards, this ran N times per render cycle. Now the transformation runs once in the queryFn, stored as coursesByCardId on the cached data. useCourseData does an O(1) lookup and CoursesPanel reads directly from the pre-transformed data. Co-Authored-By: Claude <noreply@anthropic.com>
The masquerade fallback data was maintained in a separate context provider, duplicating what React Query already caches. Now when masquerading fails, the normal user's data is read directly from the query cache via queryClient.getQueryData(). This eliminates BackedDataProvider and its associated useEffect sync. Co-Authored-By: Claude <noreply@anthropic.com>
Mutations were invalidating with initialize() which produces a key with trailing undefined, relying on React Query implementation details for prefix matching. Added initializeBase() that produces a clean prefix key ['learner-dashboard', 'initialize'] for unambiguous invalidation of all initialize queries regardless of masquerade user. Co-Authored-By: Claude <noreply@anthropic.com>
… errors Client errors (4xx) won't resolve on retry, so skip them. Server errors (5xx) and network failures get up to 3 retries with React Query's default exponential backoff. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split learnerDashboardQueryKeys into distinct query and mutation key factories for clearer semantics. Mutation keys now live in learnerDashboardMutationKeys while query invalidation still uses learnerDashboardQueryKeys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MasqueradeProvider and FiltersProvider used useReducer with action type enums for what are simple value states. Replaced with useState for less boilerplate while preserving the same public API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies the basic flow: API is called without masquerade user, data is returned with coursesByCardId transformation applied. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Port of master PR openedx#738 to frontend-base. Differences from master: - Config added to src/app.ts instead of .env files and src/config/index.js (frontend-base convention) - Uses useAppConfig() hook instead of importing configuration object directly (idiomatic for code running inside the provider tree) - Default is false (master defaults to true) Co-Authored-By: Adolfo R. Brandes <adolfo@opencraft.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s/ dirs Move the asset copy step before tsc-alias so that .svg and .png files exist in dist/ when tsc-alias resolves @src path aliases. Also restrict image file copying to files under assets/ directories, excluding screenshots in slots/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6af1c15 to
9734e01
Compare
|
Review assisted by @claude (I used claude to talk through changes, record my thoughts, keep track of where I was throughout the review process, and assist in writing this summary) Reviewed all 25 commits commit-by-commit, comparing each against the master commit(s) it ports. The full review log is in the details block below. OverallThe migration is solid. Commits 1–7 are clean master ports and frontend-base fixes. Commit 8 (the Redux→React Query rewrite) is the bulk of the change and is largely correct — component-level masquerade migrations are handled consistently, deleted Redux infrastructure is appropriate, and new context providers/hooks/API layer are well-structured. Commits 9–25 are focused fixes and improvements with no master equivalents — most address real bugs or performance issues that also exist on master and are worth porting back. A few things need attention before merge, noted below. Follow-ups for this PRDefinitely discuss
Worth investigating / is this intentional?
Nits
Items to port back to masterThese bugs/improvements were caught or introduced during the frontend-base port and don't yet exist on master:
Pre-existing gaps (not introduced by this PR)
|
| # | PR Commit SHA | Headline | Master Commit(s) | Status |
|---|---|---|---|---|
| 1 | 424422ca |
feat: added a generic creditPurchase Url logic (#675) | 90aa652c |
✅ |
| 2 | 439bcbdc |
feat: improve unenrollment process (#704) | ff925b06 |
✅ |
| 3 | 67eb0b23 |
feat: added the ability for instances to use local translations from extra repositories (#752) | c2771507 |
✅ |
| 4 | 2473745b |
fix(deps): remove filesize dependency (#767) | 75396f1d |
✅ |
| 5 | f8fee455 |
fix: update react-share to v5 (#795) | eef5d3f6 |
✅ |
| 6 | c1806e0b |
fix(docs): use correct image for custom course banner (#796) | f1180bff |
✅ |
Group B — Frontend-base specific fix (commit 7)
| # | PR Commit SHA | Headline | Master Commit(s) | Status |
|---|---|---|---|---|
| 7 | df355049 |
fix: break circular dependency in site.config.test.tsx | (frontend-base-only) | ✅ |
Group C — Redux→React Query migration (commit 8)
| # | PR Commit SHA | Headline | Master Commit(s) | Status |
|---|---|---|---|---|
| 8 | 3d1ec2bd |
refactor: migrate from Redux to React Query | 0d2eb96c |
Group D — Bug fixes and style (commits 9–13)
| # | PR Commit SHA | Headline | Status |
|---|---|---|---|
| 9 | 2234bd4d |
fix: add staleTime to prevent excessive refetching | ✅ |
| 10 | 675ba935 |
fix: remove redundant manual refetch on unenroll | ✅ |
| 11 | 79fd39f8 |
fix: compute Date.now() per call instead of at module load | ✅ |
| 12 | d0ce95d8 |
fix: close modals only after mutation succeeds | ✅ |
| 13 | 22754dba |
style: fix lint errors across migrated files | ✅ |
Group E — React Query improvements and follow-ups (commits 14–25)
| # | PR Commit SHA | Headline | Status |
|---|---|---|---|
| 14 | b1955cff |
fix: type GlobalDataContext to fix build | ✅ |
| 15 | e3b2a473 |
fix: add @types/jest so ForkTsCheckerWebpackPlugin sees jest globals | ✅ |
| 16 | ce3baa97 |
perf: transform course data once in queryFn instead of per card | ✅ |
| 17 | 3f47d3d7 |
refactor: replace BackedDataProvider with React Query cache lookup | |
| 18 | b0ac3b67 |
fix: use explicit initializeBase key for query invalidation | ✅ |
| 19 | 62b98f95 |
fix: use smart retry that skips 4xx errors but retries server/network errors | |
| 20 | 8c4bcd4d |
refactor: separate mutation keys from query keys | ✅ |
| 21 | 5ded3933 |
refactor: replace useReducer with useState in context providers | ✅ |
| 22 | d854f8c4 |
test: add normal user happy path test for useInitializeLearnerHome | ✅ |
| 23 | e64fc19d |
style: fix lint errors in queryHooks test | ✅ |
| 24 | a71bd5a7 |
feat: Unenroll survey is configurable through environment variable | ✅ |
| 25 | 9734e01e |
fix: run tsc-alias after copying assets and limit asset copy to assets/ dirs | ✅ |
Status legend: 🔲 Not started · 🔍 In progress · ✅ Approved ·
Reviews
Commit 1 — 424422ca: feat: added a generic creditPurchase Url logic (#675) ✅
Type: Port of master commit
Master refs: 90aa652c
src/data/services/lms/urls.js✅ (getConfig()from@edx/frontend-platform→getAppConfig(appId)from@openedx/frontend-base; core logic identical)src/data/services/lms/urls.test.js✅ (identical to master)example.env.config.js⏭️ (master addsCREDIT_PURCHASE_URL; not used on frontend-base — site config is used instead)src/config/index.js⏭️ (master addsCREDIT_PURCHASE_URL: process.env.CREDIT_PURCHASE_URL; not needed on frontend-base for the same reason)
Commit 2 — 439bcbdc: feat: improve unenrollment process (#704) ✅
Type: Port of master commit
Master refs: ff925b06
- Changes across all 13 files identical to master ✅
Commit 3 — 67eb0b23: feat: added the ability for instances to use local translations from extra repositories (#752) ✅
Type: Port of master commit
Master refs: c2771507
- Changes identical to master ✅
Commit 4 — 2473745b: fix(deps): remove filesize dependency (#767) ✅
Type: Port of master commit
Master refs: 75396f1d
package.jsonchange identical to master ✅package-lock.jsondiffers (expected — regen on frontend-base) ✅
Commit 5 — f8fee455: fix: update react-share to v5 (#795) ✅
Type: Port of master commit
Master refs: eef5d3f6
- Changes identical to master ✅
Commit 6 — c1806e0b: fix(docs): use correct image for custom course banner (#796) ✅
Type: Port of master commit
Master refs: f1180bff
- Changes identical to master ✅
Commit 7 — df355049: fix: break circular dependency in site.config.test.tsx ✅
Type: Frontend-base-only fix (no master equivalent)
- Switches
EnvironmentTypes.TEST→'test' as SiteConfig['environment']to avoid circular dep when mocking@openedx/frontend-baseitself ✅ - Fix is acceptable for this PR; comment explains the reasoning clearly ✅
⚠️ See follow-up: worth investigating upstream fix in@openedx/frontend-base
Commit 8 — 3d1ec2bd: refactor: migrate from Redux to React Query ⚠️
Type: Port of master commit
Master refs: 0d2eb96c
The migration is solid. The core Redux-to-React-Query rewrite is faithfully ported — all Redux infrastructure (reducers, selectors, store, request hooks, fakeData) deleted, replaced with React Query hooks, React Context providers, a typed API layer, and data transformers. Component-level changes are largely correct, with the masquerade migration (useContext(MasqueradeUserContext) → useIsMasquerading() on frontend-base; reduxHooks.useMasqueradeData() → useIsMasquerading() on master) handled consistently across all affected files.
Frontend-base-specific differences are expected: @openedx/frontend-base imports, @src path aliases, src/containers/ → src/widgets/ reorganization, AppContext → SiteContext/useAuthenticatedUser(). Several additions from 21cb5186 (a previous frontend-base-only commit) are removed as part of aligning with master — most of these removals are appropriate, though a few are worth double-checking (see follow-ups). A handful of items need attention before merge, noted below.
Files (master commit 0d2eb96c)
Deleted files
src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.js(+0/-56) ✅src/containers/CourseFilterControls/hooks.js(+0/-60) ✅src/containers/CourseFilterControls/hooks.test.js(+0/-122) ✅src/containers/CoursesPanel/hooks.js(+0/-54) ✅src/containers/CoursesPanel/hooks.test.js(+0/-115) ✅src/containers/EmailSettingsModal/hooks.test.js(+0/-71) ✅src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.js(+0/-77) ✅ (removed on master; never existed on frontend-base)src/containers/MasqueradeBar/hooks.js(+0/-69) ✅ (deleted on master; logic inlined intoindex.jsx; frontend-base keeps hooks abstraction with same logic insrc/widgets/LearnerDashboardHeader/MasqueradeBar/hooks.js— see follow-up)src/containers/MasqueradeBar/hooks.test.js(+0/-112) ✅ (deleted on master; never existed on frontend-base — see follow-up)src/containers/RelatedProgramsModal/hooks.js(+0/-10) ✅src/containers/SelectSessionModal/hooks.test.js(+0/-204) ✅src/containers/UnenrollConfirmModal/hooks/reasons.test.js(+0/-178) ✅src/data/redux/app/index.js(+0/-2) ✅src/data/redux/app/reducer.js(+0/-81) ✅src/data/redux/app/reducer.test.js(+0/-124) ✅src/data/redux/app/selectors/appSelectors.js(+0/-23) ✅src/data/redux/app/selectors/appSelectors.test.js(+0/-28) ✅src/data/redux/app/selectors/courseCard.js(+0/-155) ✅src/data/redux/app/selectors/courseCard.test.js(+0/-398) ✅src/data/redux/app/selectors/currentList.js(+0/-58) ✅src/data/redux/app/selectors/currentList.test.js(+0/-185) ✅src/data/redux/app/selectors/index.js(+0/-13) ✅src/data/redux/app/selectors/simpleSelectors.js(+0/-38) ✅src/data/redux/app/selectors/simpleSelectors.test.js(+0/-75) ✅src/data/redux/hooks/app.js(+0/-107) ✅src/data/redux/hooks/index.js(+0/-2) ✅src/data/redux/hooks/requests.js(+0/-45) ✅src/data/redux/index.js(+0/-37) ✅src/data/redux/requests/index.js(+0/-2) ✅src/data/redux/requests/reducer.js(+0/-54) ✅src/data/redux/requests/reducer.test.js(+0/-62) ✅src/data/redux/requests/selectors.js(+0/-40) ✅src/data/redux/requests/selectors.test.js(+0/-110) ✅src/data/services/lms/api.js(+0/-77) ✅src/data/services/lms/api.test.js(+0/-156) ✅src/data/services/lms/fakeData/courses.js(+0/-828) ✅src/data/services/lms/fakeData/testUtils.js(+0/-40) ✅src/data/store.js(+0/-37) ✅src/data/store.test.js(+0/-68) ✅src/data/utils.js(+0/-19) ✅src/data/utils.test.js(+0/-29) ✅src/hooks/api.js(+0/-105) ✅src/hooks/api.test.js(+0/-275) ✅src/test/app.test.jsx(+0/-279) ✅src/test/inspector.js(+0/-50) ✅src/test/messages.js(+0/-29) ✅src/test/utils.js(+0/-3) ✅
Not applicable on frontend-base
.env(+1/-0) ⏭️ (not used on frontend-base — site config instead).env.development(+1/-0) ⏭️ (not used on frontend-base — site config instead).env.test(+1/-0) ⏭️ (not used on frontend-base — site config instead)example.env.config.js(+1/-0) ⏭️ (not used on frontend-base — site config instead)src/config/index.js(+1/-0) ⏭️ (not used on frontend-base — site config instead)
Pure removals on master
src/containers/CourseCard/hooks.js(+0/-17) ✅ (removals identical to master)src/containers/Dashboard/hooks.js(+0/-7) ✅ (removed things never existed on frontend-base)src/containers/Dashboard/hooks.test.js(+0/-21) ✅ (useInitializeDashboard test never existed on frontend-base; other differences are whitespace or expected frontend-base adaptations)src/index.test.jsx(+0/-1) ⏭️ (file doesn't exist on frontend-base — SPA entry point tests don't apply when app is a package)src/setupTest.jsx(+0/-19) ✅ (removals identical to master)src/utils/StrictDict.js(+0/-1) ✅ (master removes a console.log; frontend-base version already has no console logging — see follow-up)
Added files (new in master commit)
src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.tsx(+192/-0) ✅ (master gets authenticated user from test wrapper; frontend-base mocksuseAuthenticatedUserdirectly — expected difference)src/containers/EmailSettingsModal/hooks.test.jsx(+134/-0) ✅ (changes identical except for expected frontend-base differences)src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.jsx(+111/-0) ✅ (changes identical except for expected frontend-base differences; PR equivalent atsrc/widgets/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.jsx)src/containers/SelectSessionModal/hooks.test.jsx(+294/-0) ✅ (mostly identical; nit — see follow-ups)src/containers/UnenrollConfirmModal/hooks/reasons.test.jsx(+262/-0) ✅ (changes identical except for expected frontend-base differences)src/data/context/BackedData.test.tsx(+360/-0) ✅ (identical)src/data/context/BackedDataProvider.tsx(+65/-0) ✅ (identical)src/data/context/Filters.test.tsx(+772/-0) ✅ (identical)src/data/context/FiltersProvider.tsx(+127/-0) ✅ (identical)src/data/context/Masquerade.test.tsx(+580/-0) ✅ (otherwise identical; unicode encoding nit — see follow-ups)src/data/context/MasqueradeProvider.tsx(+65/-0) ✅ (identical)src/data/context/SelectSession.test.tsx(+664/-0) ✅ (otherwise identical; unicode encoding nit — see follow-ups)src/data/context/SelectSessionProvider.tsx(+85/-0) ✅ (identical)src/data/context/index.test.tsx(+61/-0)⚠️ (frontend-base wraps withBackedDataProviderbut drops theMasqueradeAvailableassertion — see follow-up)src/data/context/index.tsx(+25/-0)⚠️ (differs from master — see follow-up)src/data/hooks/index.ts(+19/-0) ✅ (identical)src/data/hooks/mutationHooks.test.tsx(+346/-0) ✅ (otherwise identical; logging mock present here — see follow-up)src/data/hooks/mutationHooks.ts(+131/-0) ✅ (changes identical except for expected frontend-base differences)src/data/hooks/queryHooks.test.tsx(+140/-0) ✅ (differences make sense — frontend-base addsGlobalDataContextmocking)src/data/hooks/queryHooks.ts(+38/-0) ✅ (differences make sense from a frontend-base perspective)src/data/hooks/queryKeys.ts(+10/-0) ✅ (changes identical)src/data/services/lms/api.test.tsx(+288/-0) ✅ (mostly identical; minor formatting differences; two improvements on frontend-base worth backporting — see follow-ups)src/data/services/lms/api.ts(+95/-0) ✅ (changes identical except for expected frontend-base differences)src/hooks/useCourseData.test.tsx(+330/-0) ✅ (changes identical except for expected frontend-base differences)src/hooks/useCourseData.ts(+14/-0) ✅ (changes identical except for expected frontend-base differences)src/hooks/useCourseTrackingEvent.test.tsx(+389/-0) ✅ (identical)src/hooks/useCourseTrackingEvent.ts(+14/-0) ✅ (identical)src/hooks/useEntitlementInfo.test.tsx(+534/-0) ✅ (identical)src/hooks/useEntitlementInfo.ts(+33/-0) ✅ (identical)src/hooks/useIsMasquerading.test.tsx(+450/-0) ✅ (changes identical except for expected frontend-base differences)src/hooks/useIsMasquerading.ts(+10/-0) ✅ (changes identical except for expected frontend-base differences)src/utils/dataTransformers.test.ts(+629/-0) ✅ (changes identical except for expected frontend-base differences)src/utils/dataTransformers.ts(+67/-0) ✅ (changes identical except for expected frontend-base differences)src/utils/hooks.test.tsx(+55/-0) ✅ (changes identical except for expected frontend-base differences)
Changed only on frontend-base
jest.config.js✅ (removes coverage ignores for now-deleted dirs; adds@srcalias mapper; narrows asset mock to.pngonly — see follow-ups)src/data/constants/files.js✅ (one-line@srcpath alias change)src/data/constants/htmlKeys.js✅ (one-line@srcpath alias change)src/data/contexts/MasqueradeUserContext.jsx(+0/-*) ✅ (deleted — replaced byuseIsMasquerading()hook)src/data/contexts/MasqueradeUserProvider.jsx(+0/-*) ✅ (deleted — replaced byuseIsMasquerading()hook)src/data/services/lms/index.js✅ (one-line@srcpath alias change)src/providers.ts✅ (updated to import/exportBackedDataProviderandMasqueradeProviderinstead ofMasqueradeUserProvider)src/widgets/LearnerDashboardHeader/hooks.js✅ (one-line@srcpath alias change)
Tiny changes (≤6 lines)
src/containers/CourseCard/components/CourseCardActions/SelectSessionButton.jsx(+3/-3) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/MustRequestContent.jsx(+2/-2) ✅ (both migrate touseIsMasquerading()from hooks; master came fromreduxHooks, frontend-base came fromuseContext(MasqueradeUserContext))src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/ProviderLink.jsx(+3/-2) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/RelatedProgramsBadge/hooks.jsx(+3/-2) ✅ (changes identical except for expected frontend-base differences)src/containers/CoursesPanel/CourseList/index.jsx(+3/-3) ✅ (changes identical)src/containers/CoursesPanel/NoCoursesView/index.jsx(+3/-2) ✅ (changes identical except for expected frontend-base differences)src/containers/LearnerDashboardHeader/index.jsx(+3/-2) ⏭️ (header is completely reworked on frontend-base; no direct equivalent, changes not applicable)src/containers/UnenrollConfirmModal/components/ConfirmPane.jsx(+3/-2) ✅ (changes identical except for expected frontend-base differences)src/containers/UnenrollConfirmModal/components/FinishedPane.jsx(+3/-3) ✅ (changes identical except for expected frontend-base differences)src/containers/UnenrollConfirmModal/hooks/index.js(+2/-2)⚠️ (master migrates touseInitializeLearnerHome().refetch; frontend-base still usesqueryClient.invalidateQueries— see follow-up)src/plugins/LookingForChallengeWidget/index.jsx(+3/-2) ✅ (changes identical except for expected frontend-base differences)src/tracking/trackers/socialShare.js(+2/-2) ✅ (changes identical except for expected frontend-base differences; nit — see follow-ups)tsconfig.json(+4/-0) ✅ (differences expected — frontend-base extends a different base tsconfig)
Small changes (7–12 lines)
package.json(+2/-9) ✅ (redux ecosystem removals consistent;@tanstack/react-querynot added explicitly on frontend-base as expected;⚠️ react-test-rendererremoved on frontend-base but not on master — see follow-up)src/containers/CourseCard/components/CourseCardActions/ViewCourseButton.jsx(+4/-3) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardActions/index.jsx(+5/-6) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/EligibleContent.jsx(+4/-3) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/PendingContent.jsx(+4/-3) ✅ (both migrate touseIsMasquerading()from hooks; master came fromreduxHooks.useMasqueradeData, frontend-base came fromuseContext(MasqueradeUserContext))src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/RejectedContent.jsx(+4/-3) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/RejectedContent.test.jsx(+6/-6) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/ProviderLink.test.jsx(+4/-6) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.jsx(+5/-5) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.test.jsx(+5/-7) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/index.jsx(+6/-2) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardMenu/index.jsx(+5/-3) ✅ (both migrate touseIsMasquerading()from hooks; master came fromreduxHooks.useMasqueradeData, frontend-base came fromuseContext(MasqueradeUserContext))src/containers/CourseCard/components/CourseCardTitle.jsx(+5/-4) ✅ (changes identical except for expected frontend-base differences)src/containers/Dashboard/DashboardLayout.test.jsx(+10/-0)⚠️ (master only changes a hook mock; frontend-base checks children's length instead of asserting onLookingForChallengeWidget— see follow-up)src/containers/EmailSettingsModal/hooks.js(+7/-4) ✅ (changes identical except for expected frontend-base differences)src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.js(+5/-3) ✅ (both migrate touseInitializeLearnerHome()forisNeeded; master came fromreduxHooks.useEmailConfirmationData(), frontend-base came fromuseContext(GlobalDataContext); nit — see follow-ups)src/containers/RelatedProgramsModal/index.jsx(+4/-3) ✅ (changes identical except for expected frontend-base differences)src/containers/UnenrollConfirmModal/components/ConfirmPane.test.jsx(+10/-0) ✅ (changes identical except for expected frontend-base differences)src/containers/UnenrollConfirmModal/components/FinishedPane.test.jsx(+10/-0) ✅ (changes identical except for expected frontend-base differences)src/containers/UnenrollConfirmModal/index.test.jsx(+8/-1) ✅ (changes identical except for expected frontend-base differences)src/hooks/index.js(+8/-4) ✅ (changes identical)src/plugin-slots/WidgetSidebarSlot/index.test.jsx(+4/-6) ✅ (changes identical except for expected frontend-base differences)
Medium changes (13–20 lines)
src/containers/AppWrapper/index.test.tsx(+15/-0) ⏭️ (file doesn't exist on frontend-base —AppWrapperis only used inApp.jsx, which also doesn't exist on frontend-base)src/containers/CourseCard/components/CourseCardActions/BeginCourseButton.jsx(+13/-5) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardActions/ResumeButton.jsx(+13/-5) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardActions/SelectSessionButton.test.jsx(+10/-6) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CourseBanner.jsx(+12/-7) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/ApprovedContent.jsx(+11/-4) ✅ (both migrate touseIsMasquerading()from hooks; master came fromreduxHooks.useMasqueradeData, frontend-base came fromuseContext(MasqueradeUserContext))src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/ApprovedContent.test.jsx(+7/-9) ✅ (appropriate given masquerade migration — frontend-base no longer needsrenderWithMasquerading())src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/EligibleContent.test.jsx(+5/-13) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/PendingContent.test.jsx(+11/-9) ✅ (mostly identical; nit — see follow-ups)src/containers/CourseCard/components/CourseCardBanners/index.test.jsx(+12/-5) ✅ (changes mostly identical; some formatting differences on frontend-base but functionally the same)src/containers/CourseCard/components/CourseCardImage.jsx(+7/-7) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/RelatedProgramsBadge/hooks.test.js(+8/-8) ✅ (core hook migration identical; frontend-base additionally removes redux-only code not present on master — outerbeforeEachmock andmockReset()calls — which makes sense given the migration; also catches uplet→constfrom master)src/containers/CourseCard/components/hooks.js(+10/-7) ✅ (both migrate touseIsMasquerading()from hooks; master came fromreduxHooks.useMasqueradeData, frontend-base came fromuseContext(MasqueradeUserContext); otherwise identical except for expected frontend-base differences)src/containers/CourseFilterControls/ActiveCourseFilters.jsx(+5/-12) ✅ (changes identical except for expected frontend-base differences)src/containers/CoursesPanel/NoCoursesView/index.test.jsx(+8/-6) ✅ (changes identical except for expected frontend-base differences)src/containers/Dashboard/index.jsx(+10/-9) ✅ (logic identical; frontend-base additionally removes<div id="learnerdashboardroot"><main>wrapper — see follow-up)src/containers/LearnerDashboardHeader/index.test.jsx(+8/-6) ⏭️ (no equivalent on frontend-base — header is completely reworked)src/containers/UnenrollConfirmModal/hooks/index.test.js(+7/-8)⚠️ (core migration identical; several frontend-base-only additions from21cb5186removed — see follow-ups)src/containers/UnenrollConfirmModal/hooks/reasons.js(+12/-6) ✅ (changes identical except for expected frontend-base differences)src/plugins/LookingForChallengeWidget/index.test.jsx(+8/-6) ✅ (changes identical except for expected frontend-base differences)
L changes (21–50 lines)
src/App.jsx(+9/-39) ⏭️ (file doesn't exist on frontend-base — not an SPA)src/App.test.jsx(+21/-24) ⏭️ (file doesn't exist on frontend-base — not an SPA)src/containers/CourseCard/components/CourseCardActions/BeginCourseButton.test.jsx(+25/-23) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardActions/ResumeButton.test.jsx(+29/-21) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardActions/ViewCourseButton.test.jsx(+14/-8) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardActions/index.test.jsx(+13/-22) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CertificateBanner.jsx(+30/-11) ✅ (changes identical except for expected frontend-base differences and minor formatting)src/containers/CourseCard/components/CourseCardBanners/CourseBanner.test.jsx(+20/-14) ✅ (changes identical except for expected frontend-base differences; nit — see follow-ups)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/hooks.js(+26/-4) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/hooks.test.js(+17/-9) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/MustRequestContent.test.jsx(+10/-11) ✅ (both migrate to mockinguseIsMasquerading(); master came fromreduxHooks.useMasqueradeData, frontend-base came fromMasqueradeUserContext.Provider)src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.js(+14/-7) ✅ (expected path diffs; username sourced fromAppContexton master,useAuthenticatedUser()on frontend-base — expected difference)src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.jsx(+15/-7) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardDetails/hooks.js(+11/-10) ✅ (mostly identical;??→||revert — see follow-ups)src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.jsx(+11/-10) ✅ (both migrate touseIsMasquerading(); master came fromreduxHooks.useMasqueradeData, frontend-base came fromuseContext(MasqueradeUserContext); nit — see follow-ups)src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.test.jsx(+20/-22)⚠️ (masquerade migrated fromreduxHooks.useMasqueradeDataon master toMasqueradeUserContext.Provideron frontend-base; frontend-base also removes expects from the "is disabled" test that were added in21cb5186and never on master — see follow-ups)src/containers/CourseCard/components/CourseCardMenu/hooks.js(+26/-5) ✅ (changes identical except for expected frontend-base differences and minor formatting)src/containers/CourseCard/components/CourseCardMenu/index.test.jsx(+13/-14) ✅ (both migrate masquerade; master came fromreduxHooks.useMasqueradeData, frontend-base came fromMasqueradeUserContext.Provider; otherwise identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardTitle.test.jsx(+10/-12) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseFilterControls/ActiveCourseFilters.test.jsx(+32/-6) ✅ (changes identical except for expected frontend-base differences)src/containers/CoursesPanel/index.jsx(+39/-7) ✅ (changes identical except for expected frontend-base differences)src/containers/Dashboard/index.test.jsx(+11/-10)⚠️ (useInitializeAppmock replaced withuseInitializeLearnerHome; "courses still loading" test logic change — see follow-ups)src/containers/MasqueradeBar/index.jsx(+30/-15) ✅ (frontend-base has a cleaner implementation than master — logic delegated touseMasqueradeBarData()hook; the fix ported correctly; see follow-ups)src/containers/RelatedProgramsModal/index.test.jsx(+12/-9) ✅ (identical)src/containers/SelectSessionModal/hooks.js(+26/-13) ✅ (changes identical except for expected frontend-base differences)src/index.jsx(+19/-6) ✅ (closest equivalent on frontend-base isMain.jsx; both migrate away from Redux — master removes the store fromAppProvider, frontend-base usesContextProviders; expected difference)
XL changes (51–100 lines)
src/containers/CourseCard/components/CourseCardBanners/CertificateBanner.test.jsx(+52/-17) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.test.jsx(+68/-15) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardDetails/hooks.test.js(+27/-27) ✅ (changes ported from master are improvements; minor frontend-base difference: "forwards provider name" test no longer needs to explicitly addproviderNamesinceproviderDataalready includes it)src/containers/CourseCard/components/CourseCardImage.test.jsx(+38/-17) ✅ (changes identical except for expected frontend-base differences)src/containers/CourseCard/components/CourseCardMenu/hooks.test.js(+36/-29) ✅ (changes identical except for expected frontend-base differences and minor formatting)src/containers/CourseCard/components/hooks.test.js(+38/-23) ✅ (masquerade migrated fromMasqueradeUserContext.Providerwrapper touseIsMasquerading()mock, removing related wrapper boilerplate; otherwise identical except for expected frontend-base differences)src/containers/CourseCard/hooks.test.js(+23/-49) ✅ (changes identical except for expected frontend-base differences; both master and frontend-base removeforwards formatMessage from useIntlandpasses course title and banner URL from course datatests — see follow-ups)src/containers/CourseFilterControls/CourseFilterControls.jsx(+34/-33) ✅ (changes identical except for expected frontend-base differences)
XXL changes (100+ lines)
package-lock.json(+33/-151) ⏭️ (regenerated on both sides — differences expected)src/containers/CourseFilterControls/CourseFilterControls.test.jsx(+116/-41) ✅ (changes identical except for expected frontend-base differences)src/containers/CoursesPanel/index.test.jsx(+103/-35) ✅ (changes identical except for expected frontend-base differences and minor formatting)src/containers/MasqueradeBar/index.test.jsx(+120/-47) ⏭️ (no equivalent on frontend-base)src/data/constants/app.test.js(+163/-0) ✅ (changes identical)
Commit 25 — 9734e01e: fix: run tsc-alias after copying assets and limit asset copy to assets/ dirs ✅
Type: Frontend-base-only (no master equivalent)
Makefile(+2/-2) — reorders asset copy beforetsc-aliasso.svgand.pngexist indist/when path aliases are resolved; restricts copy toassets/dirs, excludingslots/screenshots. Related to frontend-base PR #182 and thejest.config.jsasset mock narrowing in commit 8.
Commit 24 — a71bd5a7: feat: Unenroll survey is configurable through environment variable ✅
Type: Port of master PR #738
Master refs: ca954e13
src/app.ts(+1/-0) ✅ — addsSHOW_UNENROLL_SURVEYto site config (master uses.envfiles andsrc/config/index.js)src/containers/UnenrollConfirmModal/hooks/index.js✅ — usesuseAppConfig()hook instead of direct config import; changes make sensesrc/containers/UnenrollConfirmModal/hooks/index.test.js✅ — port correct; nit — see follow-ups
Commit 23 — e64fc19d: style: fix lint errors in queryHooks test ✅
Type: Frontend-base-only (no master equivalent)
src/data/hooks/queryHooks.test.tsx(+12/-4) — lint fixes only
Commit 22 — d854f8c4: test: add normal user happy path test for useInitializeLearnerHome ✅
Type: Frontend-base-only (no master equivalent)
src/data/hooks/queryHooks.test.tsx(+28/-0) — adds happy path test for the normal (non-masquerade) flow verifying API call andcoursesByCardIdtransformation
Worth porting to master.
Commit 21 — 5ded3933: refactor: replace useReducer with useState in context providers ✅
Type: Frontend-base-only (no master equivalent)
src/data/context/MasqueradeProvider.tsx— singlestring | undefinedstate;useReducerwith one action type had no real discriminated union benefit;useStateis equally type-safe with the same public APIsrc/data/context/FiltersProvider.tsx— same pattern
No type safety lost; public API unchanged. Worth porting to master.
Commit 20 — 8c4bcd4d: refactor: separate mutation keys from query keys ✅
Type: Frontend-base-only (no master equivalent)
src/data/hooks/queryKeys.ts— splits intolearnerDashboardQueryKeysandlearnerDashboardMutationKeyssrc/data/hooks/mutationHooks.ts— updated to use mutation keys
Worth porting to master.
Commit 19 — 62b98f95: fix: use smart retry that skips 4xx errors but retries server/network errors ⚠️
Type: Frontend-base-only (no master equivalent)
src/data/hooks/queryHooks.ts— changesretry: falseto a custom function that skips retries on 4xx but allows up to 3 retries on 5xx/network errorssrc/data/hooks/queryHooks.test.tsx— adds tests for retry behavior
See follow-ups for questions before approving.
Commit 18 — b0ac3b67: fix: use explicit initializeBase key for query invalidation ✅
Type: Frontend-base-only (no master equivalent)
src/data/hooks/queryKeys.ts(+1/-0) — addsinitializeBase()returning['learner-dashboard', 'initialize']as a clean prefix keysrc/data/hooks/mutationHooks.ts— mutations now invalidate withinitializeBase()instead ofinitialize()(which had trailingundefined, relying on React Query prefix matching implementation details)
Worth porting to master.
Commit 17 — 3f47d3d7: refactor: replace BackedDataProvider with React Query cache lookup ⚠️
Type: Frontend-base-only (no master equivalent)
- Deletes
BackedDataProvider.tsxandBackedData.test.tsxentirely queryHooks.ts— masquerade fallback now reads directly from React Query cache viaqueryClient.getQueryData()providers.ts—BackedDataProviderremoved from provider treeindex.tsx/index.test.tsx—BackedDataProviderremoved from context composition
Solid simplification — eliminates a manual duplication of what React Query already provides. See follow-ups for cache eviction and key-matching gotchas.
Commit 16 — ce3baa97: perf: transform course data once in queryFn instead of per card ✅
Type: Frontend-base-only (no master equivalent)
src/data/hooks/queryHooks.ts— transformation now runs once inqueryFn, storingcoursesByCardIdon the cached datasrc/hooks/useCourseData.ts— simplified to an O(1) lookup intocoursesByCardIdsrc/hooks/useCourseData.test.tsx— memoization and integration test suites removed (no longer relevant); remaining tests updated to usecoursesByCardIdsrc/containers/CoursesPanel/index.jsx/index.test.jsx— reads pre-transformed data directly
Worth porting to master.
Commit 15 — e3b2a473: fix: add @types/jest so ForkTsCheckerWebpackPlugin sees jest globals ✅
Type: Frontend-base-only (no master equivalent)
package.json(+2/-1) ✅ — adds@types/jest; bumpsfrontend-baseto fix@srcalias in jest configpackage-lock.json— expected churn from version bump
Commit 14 — b1955cff: fix: type GlobalDataContext to fix build ✅
Type: Frontend-base-only (no master equivalent)
src/data/contexts/GlobalDataContext.jsx→GlobalDataContext.tsx— converts to TypeScript; addsGlobalDataContextTypeinterface withsetEmailConfirmationandsetPlatformSettingstyped asDispatch<SetStateAction<T>> | null(the.jsxversion had no type annotation, so TypeScript inferred the setters asnullrather than the union, breaking the build)
Commit 13 — 22754dba: style: fix lint errors across migrated files ✅
Type: Frontend-base-only (no master equivalent)
- 21 files, all mechanical lint fixes: indentation, semicolons, brace style,
type→interface(simple object shapes only — functionally identical, enforced byconsistent-type-definitions),Array<T>→T[],@ts-ignore→@ts-expect-error, named wrapper functions for display-name errors, explicit function signatures. No logic changes.
Commit 12 — d0ce95d8: fix: close modals only after mutation succeeds ✅
Type: Frontend-base-only fix (no master equivalent)
src/containers/EmailSettingsModal/hooks.js(+4/-2) ✅ — moves close action from immediatemutate()call toonSuccesscallbacksrc/containers/SelectSessionModal/hooks.js(+4/-7) ✅ — same pattern
Worth porting to master.
Commit 11 — 79fd39f8: fix: compute Date.now() per call instead of at module load ✅
Type: Frontend-base-only fix (no master equivalent)
src/utils/dataTransformers.ts(+5/-3) ✅ — movesDate.now()from module-load time into the function body; fixes a bug where courses with nulllastEnrolledwould get a frozen fallback timestamp if the app stayed open past midnight
Worth porting to master.
Commit 10 — 675ba935: fix: remove redundant manual refetch on unenroll ✅
Type: Frontend-base-only fix (no master equivalent)
src/containers/UnenrollConfirmModal/hooks/index.js(+1/-8) ✅ — removes manualrefetch()call fromcloseAndRefresh;invalidateQuerieson mutation success already triggers a refetch, so the manual call was causing a double API hitsrc/containers/UnenrollConfirmModal/hooks/index.test.js(+0/-12) ✅ — removes tests for the now-deleted manual refetch behavior
Worth porting to master.
Commit 9 — 2234bd4d: fix: add staleTime to prevent excessive refetching ✅
Type: Frontend-base-only fix (no master equivalent)
src/data/hooks/queryHooks.ts(+1/-0) ✅ — addsstaleTime: 5 * 60 * 1000(5 min) touseInitializeLearnerHome; fix is correct and worth porting to master
Follow-ups
Worth porting to master
-
Commit 22 (
d854f8c4) — adds normal user happy path test foruseInitializeLearnerHome. -
Commit 21 (
5ded3933) — replacesuseReducerwithuseStateinMasqueradeProviderandFiltersProvider; single-value states had no benefit from the reducer pattern. -
Commit 20 (
8c4bcd4d) — splitslearnerDashboardQueryKeysinto separate query and mutation key factories for clearer semantics. -
Commit 18 (
b0ac3b67) — addsinitializeBase()as a clean prefix key for mutation invalidation; replaces reliance oninitialize()with trailingundefinedand React Query prefix-matching implementation details. -
Commit 16 (
ce3baa97) — moves course data transformation from per-card O(N²) to once inqueryFnO(N), with O(1) lookups per card viacoursesByCardId. -
Commit 12 (
d0ce95d8) — moves modal close from immediate toonSuccesscallback inEmailSettingsModalandSelectSessionModal; modals previously closed immediately after firing mutations, giving no feedback on failure. -
Commit 11 (
79fd39f8) — movesDate.now()from module-load time to per-call indataTransformers.ts; fixes stale fallback enrollment date if the app stays open past midnight. -
Commit 10 (
675ba935) — removes redundant manualrefetch()incloseAndRefreshafter unenroll;invalidateQuerieson mutation success already handles the refetch, so the manual call caused a double API hit. -
Commit 9 (
2234bd4d) — addsstaleTime: 5mintouseInitializeLearnerHome;useInitializeLearnerHomeis consumed by 15+ components and withoutstaleTime, every mount and window focus triggers a redundant background refetch. -
MasqueradeBar/index.jsx— frontend-base has a cleaner implementation with logic extracted intouseMasqueradeBarData(). Worth investigating whether this can be backported to master. -
data/services/lms/api.test.tsx— frontend-base mocks constants at file level withjest.mock; master does it inbeforeEach. The file-level approach is cleaner and worth backporting to master. -
data/services/lms/api.test.tsx— frontend-base has noeslint-disablefor unused vars; master does. Worth investigating why and backporting the fix.
Worth porting to frontend-base
UnenrollConfirmModal/hooks/index.js— master migratesrefreshListtouseInitializeLearnerHome().refetch; frontend-base still usesqueryClient.invalidateQueries. Worth switching touseInitializeLearnerHome()for consistency.
Consistency / pattern
StrictDict.jsconsole logging — master removes aconsole.logbut keepsconsole.error; frontend-base already has neither. Worth deciding: removeconsole.errorfrom master too, or add it back to frontend-base.MasqueradeBarhooks pattern — master inlines all hook logic directly intoindex.jsxand deleteshooks.js; frontend-base keeps a separatehooks.jswith the same logic. Worth deciding on a preferred pattern and making the two consistent.MasqueradeBar/hooks.jstest coverage — frontend-base kepthooks.js(rewritten) but has no corresponding test file. May already be covered by other test files, but worth verifying.
Frontend-base / upstream
EnvironmentTypescircular dependency insite.config.test.tsx— current workaround ('test' as SiteConfig['environment']) is acceptable; worth investigating whether@openedx/frontend-basecould expose a separate subpath entry point (e.g.@openedx/frontend-base/constants) for enums/constants to avoid the circular dep entirely.
Worth investigating / is this intentional?
-
Commit 19 (
62b98f95) — smart retry logic: Two questions before approving: (1) Why wasretry: falseset in the first place in commit 8? Understanding the original reasoning may clarify whether any retrying is appropriate here. (2) Does skipping retries on 4xx provide enough benefit over React Query's default (retry: 3for all errors) to justify the custom logic? The alternative is to removeretryentirely and let React Query's default handle it. -
Commit 17 (
3f47d3d7) —BackedDataProviderremoval:BackedDataProvidercame to be because Redux always kept the normal user's data in a single global store — masquerade fallback just read from the same store slice. When migrating to React Query, the natural instinct was to manually preserve that data in a context provider. This commit recognizes that the React Query cache already serves that role andqueryClient.getQueryData()is the equivalent of reading from it. Two gotchas to be aware of: (1) if the cache entry gets garbage collected before masquerade failure,getQueryData()returnsundefinedsilently —BackedDataProviderwas more explicit since data lived as long as the provider;staleTime: 5min(commit 9) reduces but doesn't eliminate this risk. (2) the query key ingetQueryData()must match exactly or you getundefinedsilently. Neither is a blocker but worth keeping in mind. -
jest.config.js— asset mock narrowed from many extensions to.pngonly. Non-blocking, but worth a comment — see brian-smith-tcril's note on frontend-base PR #182 about asset file types now needing to be maintained in two places (moduleNameMapper and build target). -
CourseCard/hooks.test.js—forwards formatMessage from useIntlandpasses course title and banner URL from course datatests removed on both master and frontend-base. Presumably covered elsewhere — worth confirming. -
Dashboard/index.test.jsx— "courses still loading, should render LoadingView" test logic changed fromisPending: truetohasCourses: falseon master in6ae8180f, then was changed back on frontend-base in21cb5186— this commit now re-applies theisPending→hasCourseschange. Intentional? -
SocialShareMenu.test.jsx— removes expects from "is disabled" test that were added in21cb5186on frontend-base and never existed on master. Worth keeping? -
CourseCardDetails/hooks.js— reverts||→??(nullish coalescing) change introduced in89559a49on frontend-base, which was never on master. Is this intentional? -
Dashboard/index.jsx— removes<div id="learnerdashboardroot"><main>wrapper (added in89559a49on frontend-base); it's not clear why it was added or why it is being removed. -
react-test-rendererremoved frompackage.jsonon frontend-base but kept on master. Is this intentional? -
DashboardLayout.test.jsx— master assertsexpect(sidebarCol.children[0]).toHaveAttribute('id', 'org.openedx.frontend.learner_dashboard.widget_sidebar.v1'); before this commit, frontend-base hadexpect(sidebarCol.children[0]).toHaveAttribute('id', 'looking-for-challenge-widget'). The PR replaces this withexpect(sidebarCol.children.length).toBeGreaterThan(0)(with a comment "Slot renders its default children directly"). Checking length feels weaker than the previous assertion — putting something in the slot and asserting on it would better reflect how this was tested before.
Nits
UnenrollConfirmModal/hooks/index.test.js(commit 24) — several nestedbeforeEachblocks include mock setup (useCourseData,useUnenrollFromCourse,reasons.useUnenrollReasons) already covered by higher-level mocks; each nested block really only needsuseAppConfig.mockReturnValue({ SHOW_UNENROLL_SURVEY: ... }).SocialShareMenu.jsx— adds a bareReactimport after removinguseContext, butReactisn't needed; worth removing.CourseBanner.test.jsx— test nameinitializes data with course number from enrollment, course and course run datafeels misaligned with the new React Query logic; worth updating to reflect what the test actually does now.src/tracking/trackers/socialShare.js— frontend-base still uses a relative import path (../../data/services/lms/api) instead of@src/data/services/lms/api.ConfirmEmailBanner/hooks.js— switches from nameduseStateimport to importing React itself; not clear why the move away from destructuring.SelectSessionModal/hooks.test.jsx— master has afrontend-platformlogging mock with no equivalent on frontend-base.mutationHooks.test.tsxdoes mock logging on frontend-base, so the pattern exists — worth figuring out why it's missing inSelectSessionModal/hooks.test.jsxand establishing a consistent approach. (Check how this was resolved in the authn PR review.)data/context/index.tsxandindex.test.tsx— frontend-base removesMasqueradeProviderfrom the provider tree and re-exports it instead; the test drops the correspondingMasquerade Availableassertion. The masquerade assertion removal is likely fine since it's handled via a hook on frontend-base and tested elsewhere, but the overall changes to the context index aren't clear — worth understanding whyMasqueradeProvideris being exported rather than composed, and whether the test coverage is still adequate.Masquerade.test.tsxandSelectSession.test.tsx— unicode test strings encoded differently between master and frontend-base.Masquerade.test.tsx:'José.García@université.fr'(master) vs'Jos\u00e9.Garc\u00eda@universit\u00e9.fr'(frontend-base).SelectSession.test.tsx:'card-节点-123'(master) vs'card-\u8282\u70b9-123'(frontend-base). Functionally identical but worth deciding on a consistent convention for unicode in test data.UnenrollConfirmModal/hooks/index.test.js—calls initializeApp api methodtest now asserts onmockRefreshListbut the test name still says "initializeApp"; test name should be updated to match.PendingContent.test.jsx— removesexpect(button).toHaveAttribute('aria-disabled', 'true')with no master equivalent; line was added in21cb5186on frontend-base. Worth investigating why it was added and whether it should be kept.UnenrollConfirmModal/hooks/index.test.js— removescalls refreshList and closetest fromcloseAndRefreshblock; was added in21cb5186on frontend-base with no master equivalent. Worth keeping?UnenrollConfirmModal/hooks/index.test.js—modalStatetests rewritten; version from21cb5186on frontend-base may be preferable. Worth comparing.
Master only
Restore accidental reverts from the master port: Dashboard wrapper div and main landmark, test assertions for disabled state and aria-disabled, and initIsPending in Dashboard test. Clean up nits: remove unused React imports, replace escape sequences with literal unicode, simplify ConfirmEmailBanner hooks, fix test names, use @src alias in socialShare, and deduplicate beforeEach mocks in UnenrollConfirmModal tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Once again, thanks for the review! Definitely discussSmart retry logic (commit 19)
|
holaontiveros
left a comment
There was a problem hiding this comment.
I didn't run a full per commit review as Brian, but I did manually test all the paths I could and didn't found any error or something concerning right away,
This was before the last round changes after Brian review but it doesn't seem those changes should have change any behaviour
|
✅
After reading TanStack/query#372 it makes sense that React Query doesn't include this logic, so implementing it on our side seems reasonable. Reading that thread did make me wonder if we should be passing in everything as part of the object for ✅
On the GC point: 👍. Seems perfectly reasonable. For the key mismatch potential, I also think we're fine, I just want to walk through the flow to make sure I'm understanding why it won't be an issue (and that we aren't setting ourselves up to accidentally introduce an issue going forward). On first load, the initial state of So when we first get into frontend-app-learner-dashboard/src/data/hooks/queryHooks.ts Lines 16 to 17 in 3f47d3d effectively becomes const query = useQuery({
queryKey: learnerDashboardQueryKeys.initialize(undefined),which matches the fallback we have later frontend-app-learner-dashboard/src/data/hooks/queryHooks.ts Lines 45 to 47 in 3f47d3d LGTM! ✅
👍 ✅
Yeah, let's just make a small follow up issue to look into this. Adding it back in e175088 seems good for now. 👍 ✅
👍, might be worth a backport ✅
👍 ✅
Happy with the removal, mostly wondering about the "Presumably covered elsewhere" part. Assuming course title and banner URL have adequate coverage this LGTM! 👍 ✅
I don't love that it's less explicit, but that feels like a rabbit hole. Might be worth making an issue on For now this LGTM! 👍 ✅
I guess my actual question here was, "is not removing it on If we can remove it on master that seems like it's worth a backport. 👍 ✅
Sounds good. I replied to your reply in that PR and approved it openedx/frontend-base#182 (review) 👍 ✅
👍 ✅
👍 ✅
👍 ✅
👍 ✅
Much cleaner. Love it! 👍 ✅
Makes sense. My full comment about that was:
but looking at this now I'm not even sure what I was seeing/referring to. LGTM! 👍 ✅
👍 ✅
👍 ✅
👍 ✅
👍 ✅
👍 ✅
👍 |
|
Merged, and created the following follow-up issues: |
|
🎉 This PR is included in version 1.0.0-alpha.7 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Description
This does what it says on the tin: the main intent is to port the latest master into frontend-base land. However, since these are ports, fixes and improvements related to the changes were added beyond what's in master, as seen below. These show as separate commits so we can decide whether to backport them later.
Headliners
mastercommits to thefrontend-basebranch: dependency cleanups, translation support, unenrollment improvements, credit purchase URL logic, documentation fixes, and new unenrollment survey toggle variablemaster, adapting it tofrontend-baseconventions (@src/imports,@openedx/frontend-baseAPIs, slots/widgets structure)site.config.test.tsxthat broke the entire test suite (corresponding PR to docs)tsc-aliasonly after copying assets to/dist/, otherwise SVG imports are not converted to relative pathsstaleTime(5 min) to prevent excessive refetching across 15+ consuming componentsinvalidateQuerieshandles it)Date.now()per call instead of at module load (was producing stale timestamps)LLM usage notice
Built with assistance from Claude models (mostly Opus 4.6).
Work Log
Porting master commits
Six pre-existing
mastercommits were cherry-picked ontofrontend-basein a prior session, forming the base of thefrontend-base-master-portsbranch:d62b5d3feat: added a generic creditPurchase URL logic (feat: added a generic creditPurchase Url logic #675)51f93a0mubbsharanwar/unenrollment process improvement (mubbsharanwar/unenrollment process improvement #704)96c1c44feat: added the ability for instances to use local translations from extra repositories (feat: added the ability for instances to use local translations from extra repositories #752)ae52f75fix(deps): remove filesize dependency (fix(deps): remove filesize dependency #767)b03407dfix: update react-share to v5 (fix: update react-share to v5 #795)1d97ed6fix(docs): use correct image for custom course banner (fix(docs): use correct image for custom course banner #796)Circular dependency fix (
site.config.test.tsx)Running
npm run testrevealed that every test suite failed withEnvironmentTypesbeing undefined. Root cause:site.config.test.tsximported{ EnvironmentTypes }from@openedx/frontend-base, creating a circular dependency chain (initialize.js→site.config→@openedx/frontend-base→ still loading). Fixed by usingimport type { SiteConfig }(erased at compile time) and the string literal'test'instead ofEnvironmentTypes.TEST.Redux to React Query migration port
The large migration commit from
masterwas ported and adapted tofrontend-baseconventions:@edx/frontend-platformreferences with@openedx/frontend-baseequivalents'utils'imports to use@src/utilsuseAuthenticatedUser()hook instead of the removedAppContextpatterncontainers/→widgets/)Fixing 13 failing test suites
After porting the migration, 13 test suites (43 tests) failed. Issues were categorized and fixed:
AppWrapper/index.test.tsx(no source file); movedConfirmEmailBanner/hooks.test.jsxto correctwidgets/pathjest.mock('@openedx/frontend-plugin-framework')fromCoursesPanelandDashboardLayouttests@src/asset resolution (2 suites): Post-processedjest.config.jsto prepend@src/-prefixed.svg/.pngpatterns before the@srcpath resolver (webpack-merge ordering issue)<MemoryRouter>(2 suites): AddedMemoryRouterwrapper forSlotcomponent'suseLocation()dependency in 4 test filesAppContextnot in frontend-base (1 suite): ChangedCreditBanner/views/hooks.jsfromAppContext+useContext()touseAuthenticatedUser()hookjest.mock(1 suite): Merged twojest.mock('@openedx/frontend-base')calls inSelectSessionModal/hooks.test.jsx'utils'import (1 suite): Fixed 4 files to use'@src/utils'api.test.tsxto provide mock values injest.mock()factoriesuseMasquerade/useBackedDatachecks fromcontext/index.test.tsx(those providers aren't inContextProviders)Also fixed
DashboardLayout.test.jsxSlot assertion —Slotrenders children directly without a wrapper div.Bug fixes from code review
A holistic code review of the migration identified 14 issues, including by not limited to:
4ec911e): Added 5-minutestaleTimeto the main query to prevent redundant refetches when 15+ components mount with the same query498f182): Removed manualqueryClient.invalidateQueries()+refetch()on unenroll — React Query's mutationonSuccesswithinvalidateQueriesalready handles thisDate.now()(1126c07):Date.now()was computed once at module load and reused for all API calls, producing stale timestamps. Changed to compute per call2126010): Modals were closed on form submit before knowing if the mutation succeeded. Moved close logic to the mutation'sonSuccesscallbackCommit-by-commit test verification
Each commit was tested individually by stepping through the history with
git checkout. One test failure was found and fixed: the masquerade fallback test usedgcTime: 0in its testQueryClient, which caused pre-seeded cache data to be garbage collected beforequeryClient.getQueryData()could read it. Fixed by removinggcTime: 0from that specific test'sQueryClient.