mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Enable theme context mocking in tests (#20519)
* Enable theme context mocking in tests * Expose mockThemeContext from grafana/ui * Add docs * Update contribute/style-guides/themes.md Co-Authored-By: Marcus Olsson <olsson.e.marcus@gmail.com> * Update packages/grafana-ui/src/themes/ThemeContext.tsx Co-Authored-By: Marcus Olsson <olsson.e.marcus@gmail.com> * Update contribute/style-guides/themes.md Co-Authored-By: Marcus Olsson <olsson.e.marcus@gmail.com> * Docs update * Update contribute/style-guides/themes.md Co-Authored-By: Marcus Olsson <olsson.e.marcus@gmail.com>
This commit is contained in:
parent
fcad439c29
commit
bff08ab99f
@ -29,7 +29,7 @@ const Foo: React.FunctionComponent<FooProps> = () => {
|
||||
|
||||
```
|
||||
|
||||
#### Using `withTheme` HOC
|
||||
#### Using `withTheme` higher-order component (HOC)
|
||||
|
||||
With this method your component will be automatically wrapped in `ThemeContext.Consumer` and provided with current theme via `theme` prop. Component used with `withTheme` must implement `Themeable` interface.
|
||||
|
||||
@ -43,6 +43,36 @@ const Foo: React.FunctionComponent<FooProps> = () => ...
|
||||
export default withTheme(Foo);
|
||||
```
|
||||
|
||||
### Test components that use ThemeContext
|
||||
|
||||
When implementing snapshot tests for components that use the `withTheme` HOC, the snapshot will contain the entire theme object. Any change to the theme renders the snapshot outdated.
|
||||
|
||||
To make your snapshot theme independent, use the `mockThemeContext` helper function:
|
||||
|
||||
```tsx
|
||||
import { mockThemeContext } from '@grafana/ui';
|
||||
import { MyComponent } from './MyComponent';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
let restoreThemeContext;
|
||||
|
||||
beforeAll(() => {
|
||||
// Create ThemeContext mock before any snapshot test is executed
|
||||
restoreThemeContext = mockThemeContext({ type: GrafanaThemeType.Dark });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Make sure the theme is restored after snapshot tests are performed
|
||||
restoreThemeContext();
|
||||
});
|
||||
|
||||
it('renders correctyl', () => {
|
||||
const wrapper = mount(<MyComponent />)
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Using themes in Storybook
|
||||
|
||||
All stories are wrapped with `ThemeContext.Provider` using global decorator. To render `Themeable` component that's not wrapped by `withTheme` HOC you either create a new component in your story:
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { GrafanaThemeType } from '@grafana/data';
|
||||
import { ThresholdsEditor, Props, thresholdsWithoutKey } from './ThresholdsEditor';
|
||||
import { colors } from '../../utils';
|
||||
import { mockThemeContext } from '../../themes/ThemeContext';
|
||||
|
||||
const setup = (propOverrides?: Partial<Props>) => {
|
||||
const props: Props = {
|
||||
@ -25,6 +27,15 @@ function getCurrentThresholds(editor: ThresholdsEditor) {
|
||||
}
|
||||
|
||||
describe('Render', () => {
|
||||
let restoreThemeContext: any;
|
||||
beforeAll(() => {
|
||||
restoreThemeContext = mockThemeContext({ type: GrafanaThemeType.Dark });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
restoreThemeContext();
|
||||
});
|
||||
|
||||
it('should render with base threshold', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
@ -72,206 +72,7 @@ exports[`Render should render with base threshold 1`] = `
|
||||
onChange={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"dropdown": "#1f1f20",
|
||||
"pageHeader": "linear-gradient(90deg, #292a2d, #000000)",
|
||||
"scrollbar": "#343436",
|
||||
"scrollbar2": "#343436",
|
||||
},
|
||||
"border": Object {
|
||||
"radius": Object {
|
||||
"lg": "5px",
|
||||
"md": "3px",
|
||||
"sm": "2px",
|
||||
},
|
||||
"width": Object {
|
||||
"sm": "1px",
|
||||
},
|
||||
},
|
||||
"breakpoints": Object {
|
||||
"lg": "992px",
|
||||
"md": "769px",
|
||||
"sm": "544px",
|
||||
"xl": "1200px",
|
||||
"xs": "0",
|
||||
},
|
||||
"colors": Object {
|
||||
"black": "#000000",
|
||||
"blue": "#33b5e5",
|
||||
"blue77": "#1f60c4",
|
||||
"blue85": "#3274d9",
|
||||
"blue95": "#5794f2",
|
||||
"blueBase": "#3274d9",
|
||||
"blueFaint": "#041126",
|
||||
"blueLight": "#5794f2",
|
||||
"blueShade": "#1f60c4",
|
||||
"body": "#d8d9da",
|
||||
"bodyBg": "#161719",
|
||||
"brandDanger": "#e02f44",
|
||||
"brandPrimary": "#eb7b18",
|
||||
"brandSuccess": "#299c46",
|
||||
"brandWarning": "#eb7b18",
|
||||
"critical": "#e02f44",
|
||||
"dark1": "#141414",
|
||||
"dark10": "#424345",
|
||||
"dark2": "#161719",
|
||||
"dark3": "#1f1f20",
|
||||
"dark4": "#212124",
|
||||
"dark5": "#222426",
|
||||
"dark6": "#262628",
|
||||
"dark7": "#292a2d",
|
||||
"dark8": "#2f2f32",
|
||||
"dark9": "#343436",
|
||||
"formDescription": "#9fa7b3",
|
||||
"formInputBg": "#202226",
|
||||
"formInputBgDisabled": "#141619",
|
||||
"formInputBorder": "#343b40",
|
||||
"formInputBorderActive": "#5794f2",
|
||||
"formInputBorderHover": "#464c54",
|
||||
"formInputBorderInvalid": "#e02f44",
|
||||
"formInputDisabledText": "#9fa7b3",
|
||||
"formInputFocusOutline": "#1f60c4",
|
||||
"formInputText": "#c7d0d9",
|
||||
"formInputTextStrong": "#c7d0d9",
|
||||
"formInputTextWhite": "#ffffff",
|
||||
"formLabel": "#9fa7b3",
|
||||
"formLegend": "#c7d0d9",
|
||||
"formValidationMessageBg": "#e02f44",
|
||||
"formValidationMessageText": "#ffffff",
|
||||
"gray05": "#0b0c0e",
|
||||
"gray1": "#555555",
|
||||
"gray10": "#141619",
|
||||
"gray15": "#202226",
|
||||
"gray2": "#8e8e8e",
|
||||
"gray25": "#343b40",
|
||||
"gray3": "#b3b3b3",
|
||||
"gray33": "#464c54",
|
||||
"gray4": "#d8d9da",
|
||||
"gray5": "#ececec",
|
||||
"gray6": "#f4f5f8",
|
||||
"gray7": "#fbfbfb",
|
||||
"gray70": "#9fa7b3",
|
||||
"gray85": "#c7d0d9",
|
||||
"gray95": "#e9edf2",
|
||||
"gray98": "#f7f8fa",
|
||||
"grayBlue": "#212327",
|
||||
"greenBase": "#299c46",
|
||||
"greenShade": "#23843b",
|
||||
"headingColor": "#d8d9da",
|
||||
"inputBlack": "#09090b",
|
||||
"link": "#d8d9da",
|
||||
"linkDisabled": "#8e8e8e",
|
||||
"linkExternal": "#33b5e5",
|
||||
"linkHover": "#ffffff",
|
||||
"online": "#299c46",
|
||||
"orange": "#eb7b18",
|
||||
"orangeDark": "#ff780a",
|
||||
"pageBg": "#161719",
|
||||
"pageHeaderBorder": "#343436",
|
||||
"purple": "#9933cc",
|
||||
"queryGreen": "#74e680",
|
||||
"queryKeyword": "#66d9ef",
|
||||
"queryOrange": "#eb7b18",
|
||||
"queryPurple": "#fe85fc",
|
||||
"queryRed": "#e02f44",
|
||||
"red": "#d44a3a",
|
||||
"red88": "#e02f44",
|
||||
"redBase": "#e02f44",
|
||||
"redShade": "#c4162a",
|
||||
"text": "#d8d9da",
|
||||
"textEmphasis": "#ececec",
|
||||
"textFaint": "#222426",
|
||||
"textStrong": "#ffffff",
|
||||
"textWeak": "#8e8e8e",
|
||||
"variable": "#32d1df",
|
||||
"warn": "#f79520",
|
||||
"white": "#ffffff",
|
||||
"yellow": "#ecbb13",
|
||||
},
|
||||
"height": Object {
|
||||
"lg": "48px",
|
||||
"md": "32px",
|
||||
"sm": "24px",
|
||||
},
|
||||
"isDark": true,
|
||||
"isLight": false,
|
||||
"name": "Grafana Dark",
|
||||
"panelHeaderHeight": 28,
|
||||
"panelPadding": 8,
|
||||
"shadow": Object {
|
||||
"pageHeader": "inset 0px -4px 14px #1f1f20",
|
||||
},
|
||||
"spacing": Object {
|
||||
"d": "14px",
|
||||
"formButtonHeight": 32,
|
||||
"formFieldsetMargin": "16px",
|
||||
"formInputAffixPaddingHorizontal": "4px",
|
||||
"formInputHeight": "32px",
|
||||
"formInputMargin": "16px",
|
||||
"formInputPaddingHorizontal": "8px",
|
||||
"formLabelMargin": "0 0 4px 0",
|
||||
"formLabelPadding": "0 0 0 2px",
|
||||
"formLegendMargin": "0 0 16px 0",
|
||||
"formMargin": "32px",
|
||||
"formSpacingBase": 8,
|
||||
"formValidationMessagePadding": "4px 8px",
|
||||
"gutter": "30px",
|
||||
"insetSquishMd": "4px 8px",
|
||||
"lg": "24px",
|
||||
"md": "16px",
|
||||
"sm": "8px",
|
||||
"xl": "32px",
|
||||
"xs": "4px",
|
||||
"xxs": "2px",
|
||||
},
|
||||
"type": "dark",
|
||||
"typography": Object {
|
||||
"fontFamily": Object {
|
||||
"monospace": "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
||||
"sansSerif": "'Roboto', 'Helvetica Neue', Arial, sans-serif",
|
||||
},
|
||||
"heading": Object {
|
||||
"h1": "28px",
|
||||
"h2": "24px",
|
||||
"h3": "21px",
|
||||
"h4": "18px",
|
||||
"h5": "16px",
|
||||
"h6": "14px",
|
||||
},
|
||||
"lineHeight": Object {
|
||||
"lg": 1.5,
|
||||
"md": 1.3333333333333333,
|
||||
"sm": 1.1,
|
||||
"xs": 1,
|
||||
},
|
||||
"link": Object {
|
||||
"decoration": "none",
|
||||
"hoverDecoration": "none",
|
||||
},
|
||||
"size": Object {
|
||||
"base": "13px",
|
||||
"lg": "18px",
|
||||
"md": "14px",
|
||||
"root": "14px",
|
||||
"sm": "12px",
|
||||
"xs": "10px",
|
||||
},
|
||||
"weight": Object {
|
||||
"bold": 600,
|
||||
"light": 300,
|
||||
"regular": 400,
|
||||
"semibold": 500,
|
||||
},
|
||||
},
|
||||
"zIndex": Object {
|
||||
"dropdown": "1000",
|
||||
"modal": "1050",
|
||||
"modalBackdrop": "1040",
|
||||
"navbarFixed": "1020",
|
||||
"sidemenu": "1025",
|
||||
"tooltip": "1030",
|
||||
"typeahead": "1060",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
@ -283,206 +84,7 @@ exports[`Render should render with base threshold 1`] = `
|
||||
onChange={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"dropdown": "#1f1f20",
|
||||
"pageHeader": "linear-gradient(90deg, #292a2d, #000000)",
|
||||
"scrollbar": "#343436",
|
||||
"scrollbar2": "#343436",
|
||||
},
|
||||
"border": Object {
|
||||
"radius": Object {
|
||||
"lg": "5px",
|
||||
"md": "3px",
|
||||
"sm": "2px",
|
||||
},
|
||||
"width": Object {
|
||||
"sm": "1px",
|
||||
},
|
||||
},
|
||||
"breakpoints": Object {
|
||||
"lg": "992px",
|
||||
"md": "769px",
|
||||
"sm": "544px",
|
||||
"xl": "1200px",
|
||||
"xs": "0",
|
||||
},
|
||||
"colors": Object {
|
||||
"black": "#000000",
|
||||
"blue": "#33b5e5",
|
||||
"blue77": "#1f60c4",
|
||||
"blue85": "#3274d9",
|
||||
"blue95": "#5794f2",
|
||||
"blueBase": "#3274d9",
|
||||
"blueFaint": "#041126",
|
||||
"blueLight": "#5794f2",
|
||||
"blueShade": "#1f60c4",
|
||||
"body": "#d8d9da",
|
||||
"bodyBg": "#161719",
|
||||
"brandDanger": "#e02f44",
|
||||
"brandPrimary": "#eb7b18",
|
||||
"brandSuccess": "#299c46",
|
||||
"brandWarning": "#eb7b18",
|
||||
"critical": "#e02f44",
|
||||
"dark1": "#141414",
|
||||
"dark10": "#424345",
|
||||
"dark2": "#161719",
|
||||
"dark3": "#1f1f20",
|
||||
"dark4": "#212124",
|
||||
"dark5": "#222426",
|
||||
"dark6": "#262628",
|
||||
"dark7": "#292a2d",
|
||||
"dark8": "#2f2f32",
|
||||
"dark9": "#343436",
|
||||
"formDescription": "#9fa7b3",
|
||||
"formInputBg": "#202226",
|
||||
"formInputBgDisabled": "#141619",
|
||||
"formInputBorder": "#343b40",
|
||||
"formInputBorderActive": "#5794f2",
|
||||
"formInputBorderHover": "#464c54",
|
||||
"formInputBorderInvalid": "#e02f44",
|
||||
"formInputDisabledText": "#9fa7b3",
|
||||
"formInputFocusOutline": "#1f60c4",
|
||||
"formInputText": "#c7d0d9",
|
||||
"formInputTextStrong": "#c7d0d9",
|
||||
"formInputTextWhite": "#ffffff",
|
||||
"formLabel": "#9fa7b3",
|
||||
"formLegend": "#c7d0d9",
|
||||
"formValidationMessageBg": "#e02f44",
|
||||
"formValidationMessageText": "#ffffff",
|
||||
"gray05": "#0b0c0e",
|
||||
"gray1": "#555555",
|
||||
"gray10": "#141619",
|
||||
"gray15": "#202226",
|
||||
"gray2": "#8e8e8e",
|
||||
"gray25": "#343b40",
|
||||
"gray3": "#b3b3b3",
|
||||
"gray33": "#464c54",
|
||||
"gray4": "#d8d9da",
|
||||
"gray5": "#ececec",
|
||||
"gray6": "#f4f5f8",
|
||||
"gray7": "#fbfbfb",
|
||||
"gray70": "#9fa7b3",
|
||||
"gray85": "#c7d0d9",
|
||||
"gray95": "#e9edf2",
|
||||
"gray98": "#f7f8fa",
|
||||
"grayBlue": "#212327",
|
||||
"greenBase": "#299c46",
|
||||
"greenShade": "#23843b",
|
||||
"headingColor": "#d8d9da",
|
||||
"inputBlack": "#09090b",
|
||||
"link": "#d8d9da",
|
||||
"linkDisabled": "#8e8e8e",
|
||||
"linkExternal": "#33b5e5",
|
||||
"linkHover": "#ffffff",
|
||||
"online": "#299c46",
|
||||
"orange": "#eb7b18",
|
||||
"orangeDark": "#ff780a",
|
||||
"pageBg": "#161719",
|
||||
"pageHeaderBorder": "#343436",
|
||||
"purple": "#9933cc",
|
||||
"queryGreen": "#74e680",
|
||||
"queryKeyword": "#66d9ef",
|
||||
"queryOrange": "#eb7b18",
|
||||
"queryPurple": "#fe85fc",
|
||||
"queryRed": "#e02f44",
|
||||
"red": "#d44a3a",
|
||||
"red88": "#e02f44",
|
||||
"redBase": "#e02f44",
|
||||
"redShade": "#c4162a",
|
||||
"text": "#d8d9da",
|
||||
"textEmphasis": "#ececec",
|
||||
"textFaint": "#222426",
|
||||
"textStrong": "#ffffff",
|
||||
"textWeak": "#8e8e8e",
|
||||
"variable": "#32d1df",
|
||||
"warn": "#f79520",
|
||||
"white": "#ffffff",
|
||||
"yellow": "#ecbb13",
|
||||
},
|
||||
"height": Object {
|
||||
"lg": "48px",
|
||||
"md": "32px",
|
||||
"sm": "24px",
|
||||
},
|
||||
"isDark": true,
|
||||
"isLight": false,
|
||||
"name": "Grafana Dark",
|
||||
"panelHeaderHeight": 28,
|
||||
"panelPadding": 8,
|
||||
"shadow": Object {
|
||||
"pageHeader": "inset 0px -4px 14px #1f1f20",
|
||||
},
|
||||
"spacing": Object {
|
||||
"d": "14px",
|
||||
"formButtonHeight": 32,
|
||||
"formFieldsetMargin": "16px",
|
||||
"formInputAffixPaddingHorizontal": "4px",
|
||||
"formInputHeight": "32px",
|
||||
"formInputMargin": "16px",
|
||||
"formInputPaddingHorizontal": "8px",
|
||||
"formLabelMargin": "0 0 4px 0",
|
||||
"formLabelPadding": "0 0 0 2px",
|
||||
"formLegendMargin": "0 0 16px 0",
|
||||
"formMargin": "32px",
|
||||
"formSpacingBase": 8,
|
||||
"formValidationMessagePadding": "4px 8px",
|
||||
"gutter": "30px",
|
||||
"insetSquishMd": "4px 8px",
|
||||
"lg": "24px",
|
||||
"md": "16px",
|
||||
"sm": "8px",
|
||||
"xl": "32px",
|
||||
"xs": "4px",
|
||||
"xxs": "2px",
|
||||
},
|
||||
"type": "dark",
|
||||
"typography": Object {
|
||||
"fontFamily": Object {
|
||||
"monospace": "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
||||
"sansSerif": "'Roboto', 'Helvetica Neue', Arial, sans-serif",
|
||||
},
|
||||
"heading": Object {
|
||||
"h1": "28px",
|
||||
"h2": "24px",
|
||||
"h3": "21px",
|
||||
"h4": "18px",
|
||||
"h5": "16px",
|
||||
"h6": "14px",
|
||||
},
|
||||
"lineHeight": Object {
|
||||
"lg": 1.5,
|
||||
"md": 1.3333333333333333,
|
||||
"sm": 1.1,
|
||||
"xs": 1,
|
||||
},
|
||||
"link": Object {
|
||||
"decoration": "none",
|
||||
"hoverDecoration": "none",
|
||||
},
|
||||
"size": Object {
|
||||
"base": "13px",
|
||||
"lg": "18px",
|
||||
"md": "14px",
|
||||
"root": "14px",
|
||||
"sm": "12px",
|
||||
"xs": "10px",
|
||||
},
|
||||
"weight": Object {
|
||||
"bold": 600,
|
||||
"light": 300,
|
||||
"regular": 400,
|
||||
"semibold": 500,
|
||||
},
|
||||
},
|
||||
"zIndex": Object {
|
||||
"dropdown": "1000",
|
||||
"modal": "1050",
|
||||
"modalBackdrop": "1040",
|
||||
"navbarFixed": "1020",
|
||||
"sidemenu": "1025",
|
||||
"tooltip": "1030",
|
||||
"typeahead": "1060",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -3,19 +3,29 @@ import hoistNonReactStatics from 'hoist-non-react-statics';
|
||||
|
||||
import { getTheme } from './getTheme';
|
||||
import { Themeable } from '../types/theme';
|
||||
import { GrafanaThemeType } from '@grafana/data';
|
||||
import { GrafanaTheme, GrafanaThemeType } from '@grafana/data';
|
||||
|
||||
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
|
||||
type Subtract<T, K> = Omit<T, keyof K>;
|
||||
|
||||
/**
|
||||
* Mock used in tests
|
||||
*/
|
||||
let ThemeContextMock: React.Context<GrafanaTheme> | null = null;
|
||||
|
||||
// Use Grafana Dark theme by default
|
||||
export const ThemeContext = React.createContext(getTheme(GrafanaThemeType.Dark));
|
||||
ThemeContext.displayName = 'ThemeContext';
|
||||
|
||||
export const withTheme = <P extends Themeable, S extends {} = {}>(Component: React.ComponentType<P>) => {
|
||||
const WithTheme: React.FunctionComponent<Subtract<P, Themeable>> = props => {
|
||||
/**
|
||||
* If theme context is mocked, let's use it instead of the original context
|
||||
* This is used in tests when mocking theme using mockThemeContext function defined below
|
||||
*/
|
||||
const ContextComponent = ThemeContextMock || ThemeContext;
|
||||
// @ts-ignore
|
||||
return <ThemeContext.Consumer>{theme => <Component {...props} theme={theme} />}</ThemeContext.Consumer>;
|
||||
return <ContextComponent.Consumer>{theme => <Component {...props} theme={theme} />}</ContextComponent.Consumer>;
|
||||
};
|
||||
|
||||
WithTheme.displayName = `WithTheme(${Component.displayName})`;
|
||||
@ -25,5 +35,15 @@ export const withTheme = <P extends Themeable, S extends {} = {}>(Component: Rea
|
||||
};
|
||||
|
||||
export function useTheme() {
|
||||
return useContext(ThemeContext);
|
||||
return useContext(ThemeContextMock || ThemeContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables theme context mocking
|
||||
*/
|
||||
export const mockThemeContext = (theme: Partial<GrafanaTheme>) => {
|
||||
ThemeContextMock = React.createContext(theme as GrafanaTheme);
|
||||
return () => {
|
||||
ThemeContextMock = null;
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ThemeContext, withTheme, useTheme } from './ThemeContext';
|
||||
import { ThemeContext, withTheme, useTheme, mockThemeContext } from './ThemeContext';
|
||||
import { getTheme, mockTheme } from './getTheme';
|
||||
import { selectThemeVariant } from './selectThemeVariant';
|
||||
export { stylesFactory } from './stylesFactory';
|
||||
export { ThemeContext, withTheme, mockTheme, getTheme, selectThemeVariant, useTheme };
|
||||
export { ThemeContext, withTheme, mockTheme, getTheme, selectThemeVariant, useTheme, mockThemeContext };
|
||||
|
Loading…
Reference in New Issue
Block a user