diff --git a/.github/workflows/screener-build.yml b/.github/workflows/screener-build.yml index f4ddf2e6633f1..ad95963a40f80 100644 --- a/.github/workflows/screener-build.yml +++ b/.github/workflows/screener-build.yml @@ -272,6 +272,10 @@ jobs: printenv | sort ;\ echo "SHELLOPTS $SHELLOPTS" ;\ + - name: build vr-tests-react-components storybook + run: yarn lage build --to @fluentui/vr-tests-react-components + if: ${{env.SKIP_SCREENER_BUILD == 'false'}} + - name: build vr-tests-react-components storybook run: yarn workspace @fluentui/vr-tests-react-components screener:build if: ${{env.SKIP_SCREENER_BUILD == 'false'}} diff --git a/apps/vr-tests-react-components/.storybook/main.js b/apps/vr-tests-react-components/.storybook/main.js index 66d036830a218..c87c619441da7 100644 --- a/apps/vr-tests-react-components/.storybook/main.js +++ b/apps/vr-tests-react-components/.storybook/main.js @@ -2,6 +2,8 @@ const path = require('path'); const { TsconfigPathsPlugin } = require('tsconfig-paths-webpack-plugin'); module.exports = /** @type {import('../../../.storybook/main').StorybookBaseConfig} */ ({ + addons: ['@fluentui/react-storybook-addon'], + stories: ['../src/**/*.stories.tsx'], core: { builder: 'webpack5', diff --git a/apps/vr-tests-react-components/.storybook/preview.js b/apps/vr-tests-react-components/.storybook/preview.js index 760cf508fdf2c..da9d74b607622 100644 --- a/apps/vr-tests-react-components/.storybook/preview.js +++ b/apps/vr-tests-react-components/.storybook/preview.js @@ -53,7 +53,8 @@ setAddon({ }, }); -export const parameters = { layout: 'none' }; +/** @type {import("@fluentui/react-storybook-addon").FluentParameters} */ +export const parameters = { layout: 'none', mode: 'vr-test' }; // For static storybook per https://github.com/screener-io/screener-storybook#testing-with-static-storybook-app if (typeof window === 'object') { diff --git a/apps/vr-tests-react-components/jest.config.js b/apps/vr-tests-react-components/jest.config.js new file mode 100644 index 0000000000000..30ea9c1e51275 --- /dev/null +++ b/apps/vr-tests-react-components/jest.config.js @@ -0,0 +1,18 @@ +// @ts-check + +/** + * @type {import('@jest/types').Config.InitialOptions} + */ +module.exports = { + displayName: 'vr-tests-react-components', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsConfig: '/tsconfig.json', + diagnostics: false, + }, + }, + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, +}; diff --git a/apps/vr-tests-react-components/package.json b/apps/vr-tests-react-components/package.json index 2bd28f6a9a36b..9743e4411c7ea 100644 --- a/apps/vr-tests-react-components/package.json +++ b/apps/vr-tests-react-components/package.json @@ -13,10 +13,12 @@ "screener": "just-scripts screener", "screener:build": "yarn build", "start": "start-storybook", + "test": "just-scripts test", "type-check": "tsc" }, "devDependencies": { "@fluentui/eslint-plugin": "*", + "@fluentui/scripts": "^1.0.0", "storywright": "0.0.23-beta.6" }, "dependencies": { @@ -46,6 +48,7 @@ "@fluentui/react-slider": "^9.0.9", "@fluentui/react-spinner": "^9.0.9", "@fluentui/react-spinbutton": "^9.0.7", + "@fluentui/react-storybook-addon": "^9.0.0-rc.1", "@fluentui/react-switch": "^9.0.10", "@fluentui/react-tabs": "^9.0.10", "@fluentui/react-table": "9.0.0-alpha.10", diff --git a/apps/vr-tests-react-components/src/stories/Button.stories.tsx b/apps/vr-tests-react-components/src/stories/Button.stories.tsx deleted file mode 100644 index bb064b192f7b4..0000000000000 --- a/apps/vr-tests-react-components/src/stories/Button.stories.tsx +++ /dev/null @@ -1,574 +0,0 @@ -import { storiesOf } from '@storybook/react'; -import * as React from 'react'; -import Screener from 'screener-storybook/src/screener'; -import { Button, CompoundButton, ToggleButton, MenuButton } from '@fluentui/react-button'; -import { bundleIcon, CalendarMonthFilled, CalendarMonthRegular } from '@fluentui/react-icons'; -import { makeStyles } from '@griffel/react'; - -const CalendarMonth = bundleIcon(CalendarMonthFilled, CalendarMonthRegular); - -const steps = new Screener.Steps() - .snapshot('default', { cropTo: '.testWrapper' }) - .hover('#button-id') - .snapshot('hover', { cropTo: '.testWrapper' }) - .mouseDown('#button-id') - .snapshot('pressed', { cropTo: '.testWrapper' }) - .end(); - -const buttonId = 'button-id'; - -const useStyles = makeStyles({ - longText: { - width: '280px', - }, -}); - -storiesOf('Button Converged', module) - .addDecorator(story => {story()}) - .addStory('Default', () => , { - includeRtl: true, - includeHighContrast: true, - includeDarkMode: true, - }) - .addStory('As an anchor', () => ( - - )) - .addStory('Circular', () => ( - - )) - .addStory('Outline', () => ( - - )) - .addStory( - 'Primary', - () => ( - - ), - { - includeHighContrast: true, - includeDarkMode: true, - }, - ) - .addStory( - 'Subtle', - () => ( - - ), - { - includeHighContrast: true, - includeDarkMode: true, - }, - ) - .addStory( - 'Transparent', - () => ( - - ), - { - includeHighContrast: true, - includeDarkMode: true, - }, - ) - .addStory( - 'Disabled', - () => ( - - ), - { - includeHighContrast: true, - includeDarkMode: true, - }, - ) - .addStory( - 'Outline Disabled', - () => ( - - ), - { - includeHighContrast: true, - includeDarkMode: true, - }, - ) - .addStory( - 'Primary Disabled', - () => ( - - ), - { includeHighContrast: true, includeDarkMode: true }, - ) - .addStory( - 'Subtle Disabled', - () => ( - - ), - { includeHighContrast: true, includeDarkMode: true }, - ) - .addStory( - 'Transparent Disabled', - () => ( - - ), - { includeHighContrast: true, includeDarkMode: true }, - ) - .addStory('Size small', () => ( - - )) - .addStory('Size large', () => ( - - )) - .addStory('Size small - with long text wrapping', () => { - const styles = useStyles(); - return ( - - ); - }) - .addStory('Size medium - with long text wrapping', () => { - const styles = useStyles(); - return ( - - ); - }) - .addStory('Size large - with long text wrapping', () => { - const styles = useStyles(); - return ( - - ); - }) - .addStory( - 'With icon before content', - () => ( - - ), - { - includeRtl: true, - }, - ) - .addStory( - 'With icon after content', - () => ( - - ), - { includeRtl: true }, - ) - .addStory('Icon only', () => ; + +export const DefaultRTL = getStoryVariant(Default, RTL); +export const DefaultDarkMode = getStoryVariant(Default, DARK_MODE); +export const DefaultHighContrast = getStoryVariant(Default, HIGH_CONTRAST); + +export const AsAnAnchor = () => ( + +); + +AsAnAnchor.storyName = 'As an anchor'; + +export const Circular = () => ( + +); + +export const Outline = () => ( + +); + +export const Primary = () => ( + +); + +export const PrimaryHighContrast = getStoryVariant(Primary, HIGH_CONTRAST); +export const PrimaryDarkMode = getStoryVariant(Primary, DARK_MODE); + +export const Subtle = () => ( + +); + +export const SubtleHighContrast = getStoryVariant(Subtle, HIGH_CONTRAST); +export const SubtleDarkMode = getStoryVariant(Subtle, DARK_MODE); + +export const Transparent = () => ( + +); + +export const TransparentHighContrast = getStoryVariant(Transparent, HIGH_CONTRAST); +export const TransparentDarkMode = getStoryVariant(Transparent, DARK_MODE); + +export const Disabled = () => ( + +); + +export const DisabledHighContrast = getStoryVariant(Disabled, HIGH_CONTRAST); +export const DisabledDarkMode = getStoryVariant(Disabled, DARK_MODE); + +export const OutlineDisabled = () => ( + +); + +export const OutlineDisabledHighContrast = getStoryVariant(OutlineDisabled, HIGH_CONTRAST); +export const OutlineDisabledDarkMode = getStoryVariant(OutlineDisabled, DARK_MODE); + +export const PrimaryDisabled = () => ( + +); + +export const PrimaryDisabledHighContrast = getStoryVariant(PrimaryDisabled, HIGH_CONTRAST); +export const PrimaryDisabledDarkMode = getStoryVariant(PrimaryDisabled, DARK_MODE); + +export const SubtleDisabled = () => ( + +); + +export const SubtleDisabledHighContrast = getStoryVariant(SubtleDisabled, HIGH_CONTRAST); +export const SubtleDisabledDarkMode = getStoryVariant(SubtleDisabled, DARK_MODE); + +export const TransparentDisabled = () => ( + +); + +export const TransparentDisabledHighContrast = getStoryVariant(TransparentDisabled, HIGH_CONTRAST); +export const TransparentDisabledDarkMode = getStoryVariant(TransparentDisabled, DARK_MODE); + +export const SizeSmall = () => ( + +); + +SizeSmall.storyName = 'Size small'; + +export const SizeLarge = () => ( + +); + +SizeLarge.storyName = 'Size large'; + +export const SizeSmallWithLongTextWrapping = () => { + const styles = useStyles(); + return ( + + ); +}; + +SizeSmallWithLongTextWrapping.storyName = 'Size small - with long text wrapping'; + +export const SizeMediumWithLongTextWrapping = () => { + const styles = useStyles(); + return ( + + ); +}; + +SizeMediumWithLongTextWrapping.storyName = 'Size medium - with long text wrapping'; + +export const SizeLargeWithLongTextWrapping = () => { + const styles = useStyles(); + return ( + + ); +}; + +SizeLargeWithLongTextWrapping.storyName = 'Size large - with long text wrapping'; + +export const WithIconBeforeContent = () => ( + +); + +WithIconBeforeContent.storyName = 'With icon before content'; + +export const WithIconBeforeContentRTL = getStoryVariant(WithIconBeforeContent, RTL); + +export const WithIconAfterContent = () => ( + +); + +WithIconAfterContent.storyName = 'With icon after content'; + +export const WithIconAfterContentRTL = getStoryVariant(WithIconAfterContent, RTL); + +export const IconOnly = () => ; + + it('should set the correct direction for story', () => { + const ltrStory = getStoryVariant(DefaultStory, DARK_MODE); + const rtlStory = getStoryVariant(DefaultStory, RTL); + + expect(ltrStory.parameters.dir).toBe('ltr'); + expect(rtlStory.parameters.dir).toBe('rtl'); + }); + + it('should set the correct theme for story', () => { + const darkModeStory = getStoryVariant(DefaultStory, DARK_MODE); + const highContrastStory = getStoryVariant(DefaultStory, HIGH_CONTRAST); + + expect(darkModeStory.parameters.fluentTheme).toBe('web-dark'); + expect(highContrastStory.parameters.fluentTheme).toBe('teams-high-contrast'); + }); + + it('should set the correct name for story', () => { + const darkModeStory = getStoryVariant(DefaultStory, DARK_MODE); + const highContrastStory = getStoryVariant(DefaultStory, HIGH_CONTRAST); + const rtlStory = getStoryVariant(DefaultStory, RTL); + + expect(darkModeStory.storyName).toEqual('Default Story - Dark Mode'); + expect(highContrastStory.storyName).toEqual('Default Story - High Contrast'); + expect(rtlStory.storyName).toEqual('Default Story - RTL'); + + const buttonStory: ComponentStory = DefaultStory; + buttonStory.storyName = 'button'; + + const buttonDarkModeStory = getStoryVariant(buttonStory, DARK_MODE); + const buttonHighContrastStory = getStoryVariant(buttonStory, HIGH_CONTRAST); + const buttonRtlStory = getStoryVariant(buttonStory, RTL); + + expect(buttonDarkModeStory.storyName).toEqual('button - Dark Mode'); + expect(buttonHighContrastStory.storyName).toEqual('button - High Contrast'); + expect(buttonRtlStory.storyName).toEqual('button - RTL'); + }); + }); +}); diff --git a/apps/vr-tests-react-components/src/utilities/withScreenerSteps.tsx b/apps/vr-tests-react-components/src/utilities/withScreenerSteps.tsx new file mode 100644 index 0000000000000..f38886fbe9562 --- /dev/null +++ b/apps/vr-tests-react-components/src/utilities/withScreenerSteps.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import Screener, { Step } from 'screener-storybook/src/screener'; +import { StoryContext } from './types'; + +export const withScreenerSteps = ({ + story, + context, + steps, +}: { + story: () => React.ReactNode; + steps: Step[]; + context?: StoryContext; +}) => { + return {story()} ; +}; diff --git a/apps/vr-tests-react-components/tsconfig.json b/apps/vr-tests-react-components/tsconfig.json index c94f76d48edec..83dc833d506fd 100644 --- a/apps/vr-tests-react-components/tsconfig.json +++ b/apps/vr-tests-react-components/tsconfig.json @@ -12,7 +12,7 @@ "experimentalDecorators": true, "noUnusedLocals": true, "preserveConstEnums": true, - "types": ["screener-storybook", "node"] + "types": ["screener-storybook", "node", "jest"] }, "include": ["./src", "./.storybook/*", "screener.config.js"] } diff --git a/packages/react-components/react-storybook-addon/etc/react-storybook-addon.api.md b/packages/react-components/react-storybook-addon/etc/react-storybook-addon.api.md index 02800552969e2..684361e99da9f 100644 --- a/packages/react-components/react-storybook-addon/etc/react-storybook-addon.api.md +++ b/packages/react-components/react-storybook-addon/etc/react-storybook-addon.api.md @@ -5,7 +5,8 @@ ```ts import { Args } from '@storybook/api'; -import { Parameters as Parameters_2 } from '@storybook/api'; +import { OptionsParameter } from '@storybook/addons'; +import { Parameters as Parameters_2 } from '@storybook/addons'; import { StoryContext } from '@storybook/addons'; import type { Theme } from '@fluentui/react-theme'; @@ -38,6 +39,10 @@ export function parameters(options?: FluentParameters): { dir: string; fluentTheme: string; mode: string; + fileName?: string | undefined; + options?: OptionsParameter | undefined; + layout?: "centered" | "fullscreen" | "padded" | "none" | undefined; + docsOnly?: boolean | undefined; }; // @public (undocumented) diff --git a/packages/react-components/react-storybook-addon/src/hooks.ts b/packages/react-components/react-storybook-addon/src/hooks.ts index ac9a7055cef9f..79d3cc56323f9 100644 --- a/packages/react-components/react-storybook-addon/src/hooks.ts +++ b/packages/react-components/react-storybook-addon/src/hooks.ts @@ -1,5 +1,5 @@ -import { useGlobals as useStorybookGlobals, Args as StorybookArgs, Parameters } from '@storybook/api'; -import { StoryContext as StorybookContext } from '@storybook/addons'; +import { useGlobals as useStorybookGlobals, Args as StorybookArgs } from '@storybook/api'; +import { StoryContext as StorybookContext, Parameters } from '@storybook/addons'; import { THEME_ID } from './constants'; import { ThemeIds } from './theme';