From a8d45acea9bc69fe4cbbdf67e2bde3924cbd1bde Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Wed, 18 May 2022 14:47:27 -0400 Subject: [PATCH] refactor getThemeProps to be available in JS (#27439) * refactor getThemeProps to be available in JS * wip * mike's idea * delete no longer used file --- .../lib/getThemeProps.ts => lib/get-theme.js | 45 ++++++----- pages/_app.tsx | 8 +- pages/_document.tsx | 4 +- tests/unit/get-theme.js | 80 +++++++++++++++++++ 4 files changed, 110 insertions(+), 27 deletions(-) rename components/lib/getThemeProps.ts => lib/get-theme.js (55%) create mode 100644 tests/unit/get-theme.js diff --git a/components/lib/getThemeProps.ts b/lib/get-theme.js similarity index 55% rename from components/lib/getThemeProps.ts rename to lib/get-theme.js index 29d119afb2..50a6e0d181 100644 --- a/components/lib/getThemeProps.ts +++ b/lib/get-theme.js @@ -1,18 +1,18 @@ -type ThemeT = { name: string; color_mode: string } - -const defaultCSSThemeProps = { +// export const defaultCSSThemeProps = { +export const defaultCSSTheme = { colorMode: 'auto', // light, dark, auto nightTheme: 'dark', dayTheme: 'light', } -export const defaultComponentThemeProps = { +// export const defaultComponentThemeProps = { +export const defaultComponentTheme = { colorMode: 'auto', // day, night, auto nightTheme: 'dark', dayTheme: 'light', } -const cssColorModeToComponentColorMode: Record = { +const cssColorModeToComponentColorMode = { auto: 'auto', light: 'day', dark: 'night', @@ -24,7 +24,7 @@ const supportedThemes = ['light', 'dark', 'dark_dimmed'] * Our version of primer/css is out of date, so we can only support known themes. * For the least jarring experience possible, we fallback to the color_mode (light / dark) if provided by the theme, otherwise our own defaults */ -function getSupportedTheme(theme: ThemeT | undefined, fallbackTheme: string) { +function getSupportedTheme(theme, fallbackTheme) { if (!theme) { return fallbackTheme } @@ -33,35 +33,36 @@ function getSupportedTheme(theme: ThemeT | undefined, fallbackTheme: string) { } /* - * Returns theme props for consumption by either primer/css or primer/components + * Returns theme for consumption by either primer/css or primer/components * based on the cookie and/or fallback values */ -export function getThemeProps(req: any, mode?: 'css') { - let cookieValue: { - color_mode?: 'auto' | 'light' | 'dark' - dark_theme?: ThemeT - light_theme?: ThemeT - } = {} - const defaultThemeProps = mode === 'css' ? defaultCSSThemeProps : defaultComponentThemeProps +export function getTheme(req, cssMode = false) { + const cookieValue = {} + + const defaultTheme = cssMode ? defaultCSSTheme : defaultComponentTheme if (req.cookies?.color_mode) { try { - cookieValue = JSON.parse(decodeURIComponent(req.cookies.color_mode)) - } catch { - // do nothing + const parsed = JSON.parse(decodeURIComponent(req.cookies.color_mode)) + cookieValue.color_mode = parsed.color_mode + cookieValue.dark_theme = parsed.dark_theme + cookieValue.light_theme = parsed.light_theme + } catch (err) { + if (process.env.NODE_ENV !== 'test') { + console.warn("Unable to parse 'color_mode' cookie", err) + } } } // The cookie value is a primer/css color_mode. sometimes we need to convert that to a primer/components compatible version const colorMode = - (mode === 'css' + (cssMode ? cookieValue.color_mode - : cssColorModeToComponentColorMode[cookieValue.color_mode || '']) || - defaultThemeProps.colorMode + : cssColorModeToComponentColorMode[cookieValue.color_mode || '']) || defaultTheme.colorMode return { colorMode, - nightTheme: getSupportedTheme(cookieValue.dark_theme, defaultThemeProps.nightTheme), - dayTheme: getSupportedTheme(cookieValue.light_theme, defaultThemeProps.dayTheme), + nightTheme: getSupportedTheme(cookieValue.dark_theme, defaultTheme.nightTheme), + dayTheme: getSupportedTheme(cookieValue.light_theme, defaultTheme.dayTheme), } } diff --git a/pages/_app.tsx b/pages/_app.tsx index 5b5fd697b8..eab4108e82 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -4,17 +4,17 @@ import type { AppProps, AppContext } from 'next/app' import Head from 'next/head' import { ThemeProvider, ThemeProviderProps } from '@primer/react' import { SSRProvider } from '@react-aria/ssr' -import { defaultComponentThemeProps, getThemeProps } from 'components/lib/getThemeProps' import '../stylesheets/index.scss' import events from 'components/lib/events' import experiment from 'components/lib/experiment' import { LanguagesContext, LanguagesContextT } from 'components/context/LanguagesContext' +import { defaultComponentTheme } from 'lib/get-theme.js' type MyAppProps = AppProps & { csrfToken: string - themeProps: typeof defaultComponentThemeProps & Pick + themeProps: typeof defaultComponentTheme & Pick languagesContext: LanguagesContextT } const MyApp = ({ Component, pageProps, csrfToken, themeProps, languagesContext }: MyAppProps) => { @@ -72,9 +72,11 @@ MyApp.getInitialProps = async (appContext: AppContext) => { const appProps = await App.getInitialProps(appContext) const req: any = ctx.req + const { getTheme } = await import('lib/get-theme.js') + return { ...appProps, - themeProps: getThemeProps(req), + themeProps: getTheme(req), csrfToken: req?.csrfToken?.() || '', languagesContext: { languages: req.context.languages }, } diff --git a/pages/_document.tsx b/pages/_document.tsx index 9dc7388c4c..71736d6e66 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -2,7 +2,7 @@ import Document, { DocumentContext, Html, Head, Main, NextScript } from 'next/do import { ServerStyleSheet } from 'styled-components' -import { getThemeProps } from 'components/lib/getThemeProps' +import { getTheme } from 'lib/get-theme.js' export default class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { @@ -18,7 +18,7 @@ export default class MyDocument extends Document { const initialProps = await Document.getInitialProps(ctx) return { ...initialProps, - cssThemeProps: getThemeProps(ctx.req, 'css'), + cssThemeProps: getTheme(ctx.req, true), styles: ( <> {initialProps.styles} diff --git a/tests/unit/get-theme.js b/tests/unit/get-theme.js new file mode 100644 index 0000000000..aba0a20bcb --- /dev/null +++ b/tests/unit/get-theme.js @@ -0,0 +1,80 @@ +import { describe, expect, test } from '@jest/globals' + +import { getTheme, defaultCSSTheme, defaultComponentTheme } from '../../lib/get-theme.js' + +function serializeCookieValue(obj) { + return encodeURIComponent(JSON.stringify(obj)) +} + +describe('getTheme basics', () => { + test('always return an object with certain keys', () => { + const req = {} // doesn't even have a `.cookies`. + const theme = getTheme(req) + expect(theme.colorMode).toBe(defaultComponentTheme.colorMode) + expect(theme.nightTheme).toBe(defaultComponentTheme.nightTheme) + expect(theme.dayTheme).toBe(defaultComponentTheme.dayTheme) + const cssTheme = getTheme(req, true) + expect(cssTheme.colorMode).toBe(defaultCSSTheme.colorMode) + expect(cssTheme.nightTheme).toBe(defaultCSSTheme.nightTheme) + expect(cssTheme.dayTheme).toBe(defaultCSSTheme.dayTheme) + }) + + test('respect the color_mode cookie value', () => { + const req = { + cookies: { + color_mode: serializeCookieValue({ + color_mode: 'dark', + light_theme: { name: 'light_colorblind', color_mode: 'light' }, + dark_theme: { name: 'dark_tritanopia', color_mode: 'dark' }, + }), + }, + } + const theme = getTheme(req) + expect(theme.colorMode).toBe('night') + expect(theme.nightTheme).toBe(defaultComponentTheme.nightTheme) + expect(theme.dayTheme).toBe(defaultComponentTheme.dayTheme) + + const cssTheme = getTheme(req, true) + expect(cssTheme.colorMode).toBe('dark') + expect(cssTheme.nightTheme).toBe(defaultCSSTheme.nightTheme) + expect(cssTheme.dayTheme).toBe(defaultCSSTheme.dayTheme) + }) + + test('respect the color_mode cookie value', () => { + const req = { + cookies: { + color_mode: serializeCookieValue({ + color_mode: 'dark', + light_theme: { name: 'light_colorblind', color_mode: 'light' }, + dark_theme: { name: 'dark_tritanopia', color_mode: 'dark' }, + }), + }, + } + const theme = getTheme(req) + expect(theme.colorMode).toBe('night') + expect(theme.nightTheme).toBe(defaultComponentTheme.nightTheme) + expect(theme.dayTheme).toBe(defaultComponentTheme.dayTheme) + + const cssTheme = getTheme(req, true) + expect(cssTheme.colorMode).toBe('dark') + expect(cssTheme.nightTheme).toBe(defaultCSSTheme.nightTheme) + expect(cssTheme.dayTheme).toBe(defaultCSSTheme.dayTheme) + }) + + test('ignore "junk" cookie values', () => { + const req = { + cookies: { + color_mode: '[This is not valid JSON}', + }, + } + const theme = getTheme(req) + expect(theme.colorMode).toBe('auto') + expect(theme.nightTheme).toBe(defaultComponentTheme.nightTheme) + expect(theme.dayTheme).toBe(defaultComponentTheme.dayTheme) + + const cssTheme = getTheme(req, true) + expect(cssTheme.colorMode).toBe('auto') + expect(cssTheme.nightTheme).toBe(defaultCSSTheme.nightTheme) + expect(cssTheme.dayTheme).toBe(defaultCSSTheme.dayTheme) + }) +})