Skip to content

Commit

Permalink
chore(fluent-accordion): Add screenshot tests (#28441)
Browse files Browse the repository at this point in the history
* chore(fluent-accordion): Add screenshot tests

* remove step

* add basic support for scoped theming

* rewrite the theme decorator to WC

* change file

* update the vr story to match component story
  • Loading branch information
miroslavstastny authored and radium-v committed May 6, 2024
1 parent eb22628 commit e1cc18b
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 27 deletions.
4 changes: 4 additions & 0 deletions apps/vr-tests-web-components/.storybook/preview.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { WCThemeDecorator } from '../src/utilities/WCThemeDecorator'

export const parameters = {
layout: 'fullscreen',
controls: { expanded: true },
Expand All @@ -11,3 +13,5 @@ export const parameters = {
},
},
};

export const decorators = [WCThemeDecorator];
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react';
import parse from 'html-react-parser';
import { StoryWright, Steps } from 'storywright';
import { Steps, StoryWright } from 'storywright';
import { accordionDefinition, accordionItemDefinition, FluentDesignSystem } from '@fluentui/web-components';
import { DARK_MODE, getStoryVariant, RTL } from '../../utilities/WCThemeDecorator';

accordionDefinition.define(FluentDesignSystem.registry);
accordionItemDefinition.define(FluentDesignSystem.registry);
Expand All @@ -11,13 +12,7 @@ export default {
decorators: [
(story: () => React.ReactElement) => {
return (
<StoryWright
steps={new Steps()
.snapshot('normal', { cropTo: '.testWrapper' })
.click('#accordion-0')
.snapshot('opened', { cropTo: '.testWrapper' })
.end()}
>
<StoryWright steps={new Steps().snapshot('normal', { cropTo: '.testWrapper' }).end()}>
<div className="testWrapper" style={{ width: '300px' }}>
{story()}
</div>
Expand All @@ -43,6 +38,48 @@ const subtract20Filled = `<svg
<rect x="3" y="9.25" width="14" height="1.5" rx="0.75" fill="#212121" />
</svg>`;

export const Size = () =>
parse(`
<fluent-accordion>
<fluent-accordion-item expanded size="small">
<span slot="heading">Small</span>
Small Panel
</fluent-accordion-item>
<fluent-accordion-item expanded size="medium">
<span slot="heading">Medium</span>
Medium Panel
</fluent-accordion-item>
<fluent-accordion-item expanded size="large">
<span slot="heading">Large</span>
Large Panel
</fluent-accordion-item>
<fluent-accordion-item expanded size="extra-large">
<span slot="heading">Extra Large</span>
Extra Large Panel
</fluent-accordion-item>
</fluent-accordion>
`);

export const SizeRTL = getStoryVariant(Size, RTL);
export const SizeDarkMode = getStoryVariant(Size, DARK_MODE);

export const ExpandIconPositionEnd = () =>
parse(`
<fluent-accordion openItems={[0]}>
<fluent-accordion-item expanded expand-icon-position="end">
<span slot="heading">Opened</span>
Visible Panel
</fluent-accordion-item>
<fluent-accordion-item expand-icon-position="end">
<span slot="heading">Closed</span>
Hidden Panel
</fluent-accordion-item>
</fluent-accordion>
`);

export const ExpandIconPositionEndRTL = getStoryVariant(ExpandIconPositionEnd, RTL);
export const ExpandIconPositionEndDarkMode = getStoryVariant(ExpandIconPositionEnd, DARK_MODE);

export const AccordionWithCustomIcons = () =>
parse(`
<fluent-accordion>
Expand All @@ -67,26 +104,22 @@ export const AccordionWithCustomIcons = () =>
</fluent-accordion>
`);

export const AccordionWithCustomIconsRTL = () =>
export const AccordionWithCustomIconsRTL = getStoryVariant(AccordionWithCustomIcons, RTL);
export const AccordionWithCustomIconsDarkMode = getStoryVariant(AccordionWithCustomIcons, DARK_MODE);

export const Disabled = () =>
parse(`
<fluent-accordion dir="rtl">
<fluent-accordion-item>
<span slot="collapsed-icon">${add20Filled}</span>
<span slot="expanded-icon">${subtract20Filled}</span>
<span slot="heading">Accordion Header 1</span>
Accordion Panel 1
</fluent-accordion-item>
<fluent-accordion-item>
<span slot="collapsed-icon">${add20Filled}</span>
<span slot="expanded-icon">${subtract20Filled}</span>
<span slot="heading">Accordion Header 2</span>
Accordion Panel 1
<fluent-accordion>
<fluent-accordion-item disabled expanded>
<span slot="heading">Disabled Item Opened</span>
Disabled Item Opened Panel
</fluent-accordion-item>
<fluent-accordion-item>
<span slot="collapsed-icon">${add20Filled}</span>
<span slot="expanded-icon">${subtract20Filled}</span>
<span slot="heading">Accordion Header 3</span>
Accordion Panel 1
<fluent-accordion-item disabled>
<span slot="heading">Disabled Item Closed</span>
Disabled Item Closed Panel
</fluent-accordion-item>
</fluent-accordion>
`);

export const DisabledRTL = getStoryVariant(Disabled, RTL);
export const DisabledDarkMode = getStoryVariant(Disabled, DARK_MODE);
83 changes: 83 additions & 0 deletions apps/vr-tests-web-components/src/utilities/WCThemeDecorator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as React from 'react';
import { StoryContext } from '@storybook/addons';
import { ComponentStory } from '@storybook/react';
import { FASTElement, customElement, html, attr } from '@microsoft/fast-element';
import { DesignToken } from '@microsoft/fast-foundation';
import { teamsLightTheme, teamsDarkTheme, webLightTheme, webDarkTheme } from '@fluentui/tokens';
import { setThemeFor } from '@fluentui/web-components';

DesignToken.registerDefaultStyleTarget();

const themes = [
{ id: 'web-light', label: 'Web Light', theme: webLightTheme },
{ id: 'web-dark', label: 'Web Dark', theme: webDarkTheme },
{ id: 'teams-light', label: 'Teams Light', theme: teamsLightTheme },
{ id: 'teams-dark', label: 'Teams Dark', theme: teamsDarkTheme },
] as const;

const defaultTheme = themes[0];

type ThemeId = typeof themes[number]['id'];

interface WCStoryContext extends StoryContext {
parameters: {
dir?: 'ltr' | 'rtl';
fluentTheme?: ThemeId;
};
}

@customElement({
name: 'fast-theme-decorator',
template: html`<slot></slot>`,
styles: `
:host {
display: block;
color: var(--colorNeutralForeground2);
background-color: var(--colorNeutralBackground2);
font-family: var(--fontFamilyBase);
}
`,
})
export class FASTThemeDecorator extends FASTElement {
@attr({
attribute: 'fluent-theme',
})
public fluentTheme: ThemeId = defaultTheme.id;

connectedCallback() {
super.connectedCallback();
const theme = themes.find(value => value.id === this.fluentTheme)?.theme ?? defaultTheme.theme;
setThemeFor(this, theme);
}
}

export const WCThemeDecorator = (StoryFn: () => JSX.Element, context: WCStoryContext) => {
const { dir = 'ltr', fluentTheme = defaultTheme.id } = context.parameters;

return React.createElement('fast-theme-decorator', { dir, 'fluent-theme': fluentTheme }, StoryFn());
};

export const DARK_MODE = 'Dark Mode';
export const RTL = 'RTL';

type Variant = typeof DARK_MODE | typeof RTL;

function getStoryName(story: ComponentStory<never>) {
if (story.storyName) {
return story.storyName;
}

return story.name.replace(/([a-z])([A-Z])/g, '$1 $2');
}

export const getStoryVariant = (story: () => string | JSX.Element | JSX.Element[], variant: Variant) => {
return {
...story,
render: story,
storyName: `${getStoryName(story as ComponentStory<never>)} - ${variant}`,
parameters: {
...(variant === DARK_MODE && { fluentTheme: 'teams-dark' }),
...(variant === RTL && { dir: 'rtl' }),
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: Add support for scoped theming",
"packageName": "@fluentui/web-components",
"email": "miroslav.stastny@microsoft.com",
"dependentChangeType": "patch"
}
2 changes: 1 addition & 1 deletion packages/web-components/src/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './design-tokens.js';
export { setTheme } from './set-theme.js';
export { setTheme, setThemeFor } from './set-theme.js';
7 changes: 7 additions & 0 deletions packages/web-components/src/theme/set-theme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Theme } from '@fluentui/tokens';
import { FASTElement } from '@microsoft/fast-element';
import * as tokens from './design-tokens.js';

const tokenNames = Object.keys(tokens) as (keyof Theme)[];
Expand All @@ -12,3 +13,9 @@ export const setTheme = (theme: Theme) => {
tokens[t].withDefault(theme[t] as string);
}
};

export const setThemeFor = (element: FASTElement, theme: Theme) => {
for (const t of tokenNames) {
tokens[t].setValueFor(element, theme[t] as string);
}
};

0 comments on commit e1cc18b

Please sign in to comment.