Skip to content

feat!: add design tokens support#759

Merged
arbrandes merged 6 commits intoopenedx:frontend-basefrom
arbrandes:frontend-base-design-tokens
Feb 6, 2026
Merged

feat!: add design tokens support#759
arbrandes merged 6 commits intoopenedx:frontend-basefrom
arbrandes:frontend-base-design-tokens

Conversation

@arbrandes
Copy link
Contributor

@arbrandes arbrandes commented Dec 8, 2025

BREAKING CHANGE: Pre-design-tokens theming is no longer supported.

Depends on openedx/frontend-base#111.

BREAKING CHANGE: Pre-design-tokens theming is no longer supported.
@arbrandes arbrandes force-pushed the frontend-base-design-tokens branch from 4046498 to 3cfcd2c Compare December 12, 2025 16:07
arbrandes and others added 2 commits January 6, 2026 10:05
The shell's SCSS must be explicitly loaded by site.config.dev.tsx.
@codecov
Copy link

codecov bot commented Jan 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (frontend-base@117b518). Learn more about missing BASE report.

Additional details and impacted files
@@               Coverage Diff                @@
##             frontend-base     #759   +/-   ##
================================================
  Coverage                 ?   88.79%           
================================================
  Files                    ?      158           
  Lines                    ?     1294           
  Branches                 ?      214           
================================================
  Hits                     ?     1149           
  Misses                   ?      141           
  Partials                 ?        4           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@arbrandes arbrandes linked an issue Jan 27, 2026 that may be closed by this pull request
@arbrandes arbrandes marked this pull request as ready for review January 27, 2026 16:08
@arbrandes
Copy link
Contributor Author

@brian-smith-tcril, thanks to @jesusbalderramawgu's latest commit, this now passes tests. Ready for review!

Copy link
Contributor

@brian-smith-tcril brian-smith-tcril left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is looking great!

I haven't tested this locally yet, but I have

I found some places where the changes didn't match up and it wasn't clear why, and a couple small issues, but nothing major.

jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
jest.mock('./ResumeButton', () => 'ResumeButton');
jest.mock('../../../../slots/CourseCardActionSlot', () => jest.fn(() => <div>CourseCardActionSlot</div>));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
jest.mock('../../../../slots/CourseCardActionSlot', () => jest.fn(() => <div>CourseCardActionSlot</div>));
jest.mock('@src/slots/CourseCardActionSlot', () => jest.fn(() => <div>CourseCardActionSlot</div>));

Comment on lines 47 to 50
jest.mock('./data/constants/app', () => ({
...jest.requireActual('./data/constants/app'),
locationId: 'fake-location-id',
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? It looks like it was removed on master in #678

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not needed. I'll remove it.

Comment on lines 52 to 55
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
nullMethod: jest.fn().mockName('utils.nullMethod'),
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? It looks like it was removed on master in #678

Comment on lines 57 to 64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? It looks like it was removed on master in #678

Comment on lines 53 to 55
it('logs error with target, name, and error stack', () => {
// eslint-ignore-next-line no-unused-vars
const callBadKey = () => dict.fakeKey;
callBadKey();
expect(window.console.error.mock.calls).toEqual([
[{ target: dict, name: 'fakeKey' }],
[Error('invalid property "fakeKey"').stack],
]);
it('returns undefined for missing key', () => {
// Accessing a missing key should return undefined
expect(dict.fakeKey).toBeUndefined();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this used to test error logging but now it's just testing for undefined in a slightly different way than it is being tested for a couple lines down?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removal was intentional, but the test is redundant.

Comment on lines -66 to -75
describe('useInitializeDashboard', () => {
it('dispatches initialize thunk action on component load', () => {
hooks.useInitializeDashboard();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([]);
expect(initializeApp).not.toHaveBeenCalled();
cb();
expect(initializeApp).toHaveBeenCalledWith();
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like @jesusbalderramawgu's comment about removing this (arbrandes#1 (comment)) didn't get a response, so I'm assuming it's intentional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I left for after we get fully into react-query land, since Jesus had some trouble writing tests that covered the initialization function fully.

describe('useCardDetailsData', () => {
const providerData = {
name: 'my-provider-name',
name: 'Unknown',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it might be hiding an issue with the "forwards provider name if it exists, else formatted unknown provider name" by matching the unknownProviderName

unknownProviderName: {
id: 'learner-dash.courseCard.CourseCardDetails.unknownProviderName',
description: 'Provider name display when name is unknown',
defaultMessage: 'Unknown',
},

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we shouldn't be passing 'Uknown' here. I'll look into it.

Comment on lines +4 to +5
import track from '../../../../tracking';
import { StrictDict } from '../../../../utils';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these use @src?

reduxHooks: { useMasqueradeData: jest.fn(), useCardEnrollmentData: jest.fn() },
jest.mock('@src/hooks', () => ({
reduxHooks: {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: empty line here

Comment on lines 118 to 129
if (isMasquerading) {
it('is disabled', () => {
expect(loadToggle().props.disabled).toEqual(true);
it('renders when masquerading', () => {
const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage });
expect(emailSettingsButton).toBeInTheDocument();
expect(emailSettingsButton).toHaveAttribute('aria-disabled', 'true');
expect(emailSettingsButton).toHaveClass('disabled');
});
} else {
it('is enabled', () => {
expect(loadToggle().props.disabled).toEqual(false);
const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage });
expect(emailSettingsButton).toBeEnabled();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the naming here feels a bit confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should change the name back to "is disabled".

@brian-smith-tcril
Copy link
Contributor

Tested locally with

diff --git a/site.config.dev.tsx b/site.config.dev.tsx
index cf10014..ac25c9a 100644
--- a/site.config.dev.tsx
+++ b/site.config.dev.tsx
@@ -36,6 +36,17 @@ const siteConfig: SiteConfig = {
   ],
 
   accessTokenCookieName: 'edx-jwt-cookie-header-payload',
+
+ theme: {
+    defaults: {
+      light: "light",
+    },
+    variants: {
+      light: {
+        url: "https://cdn.jsdelivr.net/gh/brian-smith-tcril/sample-plugin@brand/brand/dist/light.min.css",
+      },
+    },
+  },
 };
 
 export default siteConfig;

and everything seems to work as expected!

Once the comments are addressed this should be good to merge!

@arbrandes arbrandes merged commit 4f117df into openedx:frontend-base Feb 6, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[frontend-base] Design tokens support

4 participants