Skip to content

Commit 5ebe607

Browse files
feat: track Segment event to quantify users' preferred color scheme (#444)
This releases intends to begin collecting data on users' preference for light vs. dark color scheme as input and/or validation to the prioritization around supporting a true dark mode for the Open edX platform. That is, we'll be able to quantify what percentage of users would prefer to use a dark mode even though we don't offer one on Open edX.
1 parent 2b283af commit 5ebe607

File tree

6 files changed

+152
-2
lines changed

6 files changed

+152
-2
lines changed

package-lock.json

Lines changed: 66 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@
3131
},
3232
"homepage": "https://github.com/openedx/frontend-platform#readme",
3333
"devDependencies": {
34-
"@edx/browserslist-config": "1.1.1",
3534
"@edx/brand": "npm:@edx/[email protected]",
35+
"@edx/browserslist-config": "1.1.1",
3636
"@edx/frontend-build": "12.4.16",
3737
"@edx/paragon": "20.28.4",
38+
"@testing-library/react-hooks": "^8.0.1",
3839
"axios-mock-adapter": "1.21.2",
3940
"core-js": "3.29.0",
4041
"enzyme": "3.11.0",

src/react/AppProvider.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import OptionalReduxProvider from './OptionalReduxProvider';
66

77
import ErrorBoundary from './ErrorBoundary';
88
import AppContext from './AppContext';
9-
import { useAppEvent } from './hooks';
9+
import { useAppEvent, useTrackColorSchemeChoice } from './hooks';
1010
import { getAuthenticatedUser, AUTHENTICATED_USER_CHANGED } from '../auth';
1111
import { getConfig } from '../config';
1212
import { CONFIG_CHANGED } from '../constants';
@@ -49,6 +49,8 @@ export default function AppProvider({ store, children }) {
4949
const [authenticatedUser, setAuthenticatedUser] = useState(getAuthenticatedUser());
5050
const [locale, setLocale] = useState(getLocale());
5151

52+
useTrackColorSchemeChoice();
53+
5254
useAppEvent(AUTHENTICATED_USER_CHANGED, () => {
5355
setAuthenticatedUser(getAuthenticatedUser());
5456
});

src/react/AppProvider.test.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ jest.mock('../analytics', () => ({
1818
identifyAuthenticatedUser: jest.fn(),
1919
}));
2020

21+
jest.mock('./hooks', () => ({
22+
...jest.requireActual('./hooks'),
23+
useTrackColorSchemeChoice: jest.fn(),
24+
}));
25+
2126
describe('AppProvider', () => {
2227
beforeEach(async () => {
2328
await initialize({

src/react/hooks.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable import/prefer-default-export */
22
import { useEffect } from 'react';
33
import { subscribe, unsubscribe } from '../pubSub';
4+
import { sendTrackEvent } from '../analytics';
45

56
/**
67
* A React hook that allows functional components to subscribe to application events. This should
@@ -21,3 +22,27 @@ export const useAppEvent = (type, callback) => {
2122
};
2223
}, [callback, type]);
2324
};
25+
26+
/**
27+
* A React hook that tracks user's preferred color scheme (light or dark) and sends respective
28+
* event to the tracking service.
29+
*
30+
* @memberof module:React
31+
*/
32+
export const useTrackColorSchemeChoice = () => {
33+
useEffect(() => {
34+
const trackColorSchemeChoice = ({ matches }) => {
35+
const preferredColorScheme = matches ? 'dark' : 'light';
36+
37+
sendTrackEvent('openedx.ui.frontend-platform.prefers-color-scheme.selected', { preferredColorScheme });
38+
};
39+
40+
const colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)');
41+
42+
// send user's initial choice
43+
trackColorSchemeChoice(colorSchemeQuery);
44+
45+
colorSchemeQuery.addEventListener('change', trackColorSchemeChoice);
46+
return () => colorSchemeQuery.removeEventListener('change', trackColorSchemeChoice);
47+
}, []);
48+
};

src/react/hooks.test.jsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { renderHook } from '@testing-library/react-hooks';
2+
import { useTrackColorSchemeChoice } from './hooks';
3+
import { sendTrackEvent } from '../analytics';
4+
5+
jest.mock('../analytics');
6+
7+
const mockAddEventListener = jest.fn();
8+
let matchesMock;
9+
10+
Object.defineProperty(window, 'matchMedia', {
11+
value: jest.fn(() => ({
12+
addEventListener: mockAddEventListener,
13+
matches: matchesMock,
14+
})),
15+
});
16+
17+
describe('useTrackColorSchemeChoice hook', () => {
18+
afterEach(() => {
19+
mockAddEventListener.mockClear();
20+
sendTrackEvent.mockClear();
21+
});
22+
23+
it('sends dark preferred color schema event if query matches', async () => {
24+
matchesMock = true;
25+
renderHook(() => useTrackColorSchemeChoice());
26+
27+
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
28+
expect(sendTrackEvent).toHaveBeenCalledWith(
29+
'openedx.ui.frontend-platform.prefers-color-scheme.selected',
30+
{ preferredColorScheme: 'dark' },
31+
);
32+
});
33+
34+
it('sends light preferred color schema event if query does not match', async () => {
35+
matchesMock = false;
36+
renderHook(() => useTrackColorSchemeChoice());
37+
38+
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
39+
expect(sendTrackEvent).toHaveBeenCalledWith(
40+
'openedx.ui.frontend-platform.prefers-color-scheme.selected',
41+
{ preferredColorScheme: 'light' },
42+
);
43+
});
44+
45+
it('adds change event listener to matchMedia query', async () => {
46+
renderHook(() => useTrackColorSchemeChoice());
47+
48+
expect(mockAddEventListener).toHaveBeenCalledTimes(1);
49+
expect(mockAddEventListener).toHaveBeenCalledWith('change', expect.any(Function));
50+
});
51+
});

0 commit comments

Comments
 (0)