diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4461b5141ad35..03e3a219ef050 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -250,6 +250,7 @@ packages/react-components/react-tabs/library @microsoft/cxe-prg @dmytrokirpa packages/react-components/react-tabs/stories @microsoft/cxe-prg @dmytrokirpa packages/react-components/react-text/library @microsoft/cxe-prg @marcosmoura packages/react-components/react-text/stories @microsoft/cxe-prg @marcosmoura +packages/react-components/react-text/visual-regression @microsoft/cxe-prg @marcosmoura packages/react-components/react-textarea/library @microsoft/cxe-prg packages/react-components/react-textarea/stories @microsoft/cxe-prg packages/react-components/react-tooltip/library @microsoft/cxe-prg diff --git a/.github/workflows/pr-vrt-poc.yml b/.github/workflows/pr-vrt-poc.yml index f8420110039ae..c8f3501ad49af 100644 --- a/.github/workflows/pr-vrt-poc.yml +++ b/.github/workflows/pr-vrt-poc.yml @@ -18,6 +18,74 @@ env: NX_VERBOSE_LOGGING: true jobs: + debug: + if: github.repository_owner == 'microsoft' && github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: 'write' + actions: 'read' + steps: + - name: Get Pull Request Details + id: pr_details + uses: actions/github-script@v7 + with: + script: | + const response = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + + console.log({issue:context.issue}) + + return response.data; + + - name: Debug + run: | + echo "${{ steps.pr_details.outputs.result.head }}" + echo "${{ fromJson(steps.pr_details.outputs.result).head.ref }}" + echo "${{ fromJson(steps.pr_details.outputs.result).head.repo.full_name }}" + echo '========' + echo '========' + echo '========' + echo $W_RUN + echo '========' + echo '========' + echo '========' + echo $RESULT + env: + W_RUN: ${{ toJSON(github.event) }} + RESULT: ${{steps.pr_details.outputs.result}} + + - name: Checkout Forked Repository + uses: actions/checkout@v4 + with: + ref: ${{ fromJson(steps.pr_details.outputs.result).head.ref }} + repository: ${{ fromJson(steps.pr_details.outputs.result).head.repo.full_name }} + fetch-depth: 10 + + - name: Debug2 + run: | + echo "--base ${{ fromJson(steps.pr_details.outputs.result).base.sha }} --head ${{ fromJson(steps.pr_details.outputs.result).head.sha }}" + echo "====" + git status + echo '====' + ls -la packages/react-components/react-text/visual-regression + + - name: Configure Git + run: | + git config user.email "${{ github.actor }}@users.noreply.github.com" + git config user.name "${{ github.actor }}" + + - name: commit + run: | + git remote -v + touch packages/react-components/react-text/visual-regression/hello.txt + git status + git add *.txt + git commit -m "chore: test by bot" + git push origin ${{ fromJson(steps.pr_details.outputs.result).head.ref }} + approve_vrt_diff: if: github.repository_owner == 'microsoft' && github.event.issue.pull_request && contains(github.event.comment.body,'/approve-visual-regression-diff') runs-on: ubuntu-latest diff --git a/packages/react-components/react-text/visual-regression/.eslintrc.json b/packages/react-components/react-text/visual-regression/.eslintrc.json new file mode 100644 index 0000000000000..7c4a0ec7ee8ec --- /dev/null +++ b/packages/react-components/react-text/visual-regression/.eslintrc.json @@ -0,0 +1,30 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": ["{projectRoot}/eslint.config.{js,cjs,mjs}"] + } + ] + } + } + ] +} diff --git a/packages/react-components/react-text/visual-regression/.storybook/main.js b/packages/react-components/react-text/visual-regression/.storybook/main.js new file mode 100644 index 0000000000000..ec3017dba0856 --- /dev/null +++ b/packages/react-components/react-text/visual-regression/.storybook/main.js @@ -0,0 +1,58 @@ +const path = require('path'); + +const { registerTsPaths, registerRules, rules, loadWorkspaceAddon } = require('@fluentui/scripts-storybook'); +const tsConfigPath = path.resolve(__dirname, '../../../../../tsconfig.base.json'); + +module.exports = /** @type {import('@storybook/react-webpack5').StorybookConfig} */ ({ + addons: [loadWorkspaceAddon('@fluentui/react-storybook-addon', { tsConfigPath })], + stories: ['../src/**/*.stories.tsx'], + core: { + disableTelemetry: true, + }, + framework: { + name: '@storybook/react-webpack5', + options: { + builder: { + useSWC: true, + lazyCompilation: false, + }, + }, + }, + typescript: { + // disable react-docgen-typescript (totally not needed here, slows things down a lot) + reactDocgen: false, + }, + webpackFinal: (config, options) => { + if (options.configType === 'PRODUCTION') { + // Disable source maps + config.devtool = false; + } + registerTsPaths({ config, configFile: tsConfigPath }); + registerRules({ config, rules: [rules.griffelRule] }); + + return config; + }, + swc() { + return { + jsc: { + target: 'es2019', + parser: { + syntax: 'typescript', + tsx: true, + decorators: true, + dynamicImport: true, + }, + transform: { + decoratorMetadata: true, + legacyDecorator: true, + }, + keepClassNames: true, + externalHelpers: true, + loose: true, + minify: { + mangle: false, + }, + }, + }; + }, +}); diff --git a/packages/react-components/react-text/visual-regression/.storybook/preview.js b/packages/react-components/react-text/visual-regression/.storybook/preview.js new file mode 100644 index 0000000000000..1851443859d18 --- /dev/null +++ b/packages/react-components/react-text/visual-regression/.storybook/preview.js @@ -0,0 +1,8 @@ +/** @type {import("@fluentui/react-storybook-addon").FluentParameters} */ +export const parameters = { + layout: 'none', + mode: 'vr-test', + reactStorybookAddon: { + disabledDecorators: ['AriaLive'], + }, +}; diff --git a/packages/react-components/react-text/visual-regression/.storybook/tsconfig.json b/packages/react-components/react-text/visual-regression/.storybook/tsconfig.json new file mode 100644 index 0000000000000..4cdd1ce9d006f --- /dev/null +++ b/packages/react-components/react-text/visual-regression/.storybook/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "", + "allowJs": true, + "checkJs": true, + "types": ["static-assets", "environment"] + }, + "include": ["*.js"] +} diff --git a/packages/react-components/react-text/visual-regression/README.md b/packages/react-components/react-text/visual-regression/README.md new file mode 100644 index 0000000000000..e3991ed888fec --- /dev/null +++ b/packages/react-components/react-text/visual-regression/README.md @@ -0,0 +1,75 @@ +# react-text-visual-regression + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build react-text-visual-regression` to build the library. + +## Running visual regression + +Run `nx run react-text-visual-regression:test-vr-cli`. + +## About + +Project for visual regression scenarios. + +> Uses Free, Secure and OSS VR testing solution based on: +> +> - Storybook to author VR scenarios +> - StoryWright for capturing Stories and their interactions +> - Visual Regression Assert CLI - to execute diffing and updating snapshots baseline + +## Authoring VR scenarios + +1. write stories following Storybook CSF3 format + +```ts +import { Steps, type StoryParameters } from 'storywright'; + +export default { + title: 'Text Converged', + // 1. Define decorators to create scoped images that won't change dimensions over time + decorators: [ + Story => ( +
+ +
+ ), + ], + // 2. If interactions are needed , define those via StoryWright Steps API + parameters: { + storyWright: { steps: new Steps().snapshot('normal', { cropTo: '.testWrapper' }).end() }, + } satisfies StoryParameters, +} satisfies Meta; +``` + +2. Override steps per story if needed + +```ts +export const Default = { + name: 'Default', + render: () => { + return
markup
; + }, + // 1. overriding default export steps - this story will execute different behaviors + parameters: { + storyWright: { steps: new Steps().click().snapshot('normal').end() }, + } satisfies StoryParameters, +} satisfies StoryObj; +``` + +3. leverage `@fluentui/visual-regression-utilities` to create scenarios for Right to Left, Dark Mode, High Contrast Mode etc + +```ts +export const Default = { + // 1. name is mandatory to properly propagate it to getStoryVariant fn call + name: 'Default', + render: () => { + return
markup
; + }, +} satisfies StoryObj; + +// 2. create new Variants of your scenario +export const DefaultRTL = getStoryVariant(Default, RTL); +``` diff --git a/packages/react-components/react-text/visual-regression/package.json b/packages/react-components/react-text/visual-regression/package.json new file mode 100644 index 0000000000000..eea52076de4d6 --- /dev/null +++ b/packages/react-components/react-text/visual-regression/package.json @@ -0,0 +1,19 @@ +{ + "name": "@fluentui/react-text-visual-regression", + "version": "0.0.0", + "private": true, + "dependencies": { + "@fluentui/react-text": "*", + "@fluentui/visual-regression-utilities": "*" + }, + "peerDependencies": { + "@storybook/react": "^7.6.20" + }, + "devDependencies": { + "@fluentui/react-storybook-addon": "*", + "@fluentui/scripts-storybook": "*" + }, + "type": "commonjs", + "main": "./src/index.js", + "typings": "./src/index.d.ts" +} diff --git a/packages/react-components/react-text/visual-regression/project.json b/packages/react-components/react-text/visual-regression/project.json new file mode 100644 index 0000000000000..98460ab41d4e9 --- /dev/null +++ b/packages/react-components/react-text/visual-regression/project.json @@ -0,0 +1,49 @@ +{ + "name": "react-text-visual-regression", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/react-components/react-text/visual-regression/src", + "projectType": "library", + "tags": ["vNext", "platform:web", "visual-regression"], + "targets": { + "build-storybook": { + "command": "storybook build -o dist/storybook --quiet", + "options": { + "cwd": "{projectRoot}" + } + }, + "generate-image-for-vrt": { + "command": "rm -rf dist/vrt/actual && storywright --browsers chromium --url dist/storybook --destpath dist/vrt/actual --waitTimeScreenshot 500 --concurrency 4 --headless true", + "options": { + "cwd": "{projectRoot}" + }, + "metadata": { + "help": { + "command": "yarn storywright --help", + "example": {} + } + }, + "dependsOn": ["build-storybook"], + "inputs": ["{projectRoot}/src/**/*.stories.tsx"], + "outputs": ["{projectRoot}/dist/vrt/actual/**"], + "cache": true + }, + "test-vr-cli": { + "command": "visual-regression-assert assert --baselineDir src/__snapshots__ --outputPath dist/vrt", + "options": { + "cwd": "{projectRoot}" + }, + "dependsOn": [ + "build-storybook", + "generate-image-for-vrt", + { "projects": ["visual-regression-assert"], "target": "build" } + ], + "inputs": ["{projectRoot}/dist/vrt/screenshots/**", "{projectRoot}/src/**/*.stories.tsx"], + "metadata": { + "help": { + "command": "yarn visual-regression-assert --help", + "example": {} + } + } + } + } +} diff --git a/packages/react-components/react-text/visual-regression/src/Text.stories.tsx b/packages/react-components/react-text/visual-regression/src/Text.stories.tsx new file mode 100644 index 0000000000000..c568bf3e913f9 --- /dev/null +++ b/packages/react-components/react-text/visual-regression/src/Text.stories.tsx @@ -0,0 +1,187 @@ +import * as React from 'react'; +import { StoryObj, type Meta } from '@storybook/react'; +import { Steps, type StoryParameters } from 'storywright'; +import { + Body1, + Body1Strong, + Body1Stronger, + Body2, + Caption1, + Caption1Strong, + Caption1Stronger, + Caption2, + Caption2Strong, + Display, + LargeTitle, + Subtitle1, + Subtitle2, + Subtitle2Stronger, + Text, + Title1, + Title2, + Title3, +} from '@fluentui/react-text'; + +import { DARK_MODE, getStoryVariant, HIGH_CONTRAST, RTL } from '@fluentui/visual-regression-utilities'; + +export default { + title: 'Text Converged', + decorators: [ + Story => ( +
+ +
+ ), + ], + parameters: { + storyWright: { steps: new Steps().snapshot('normal', { cropTo: '.testWrapper' }).end() }, + } satisfies StoryParameters, +} satisfies Meta; + +export const Default = { + name: 'Default', + render: () => ( + <> +

+ Default + Lorem ipsum dolor sit amet, nullam rhoncus tristique tellus in Portucale. +

+

+ No wrapping + + Lorem ipsum dolor sit amet, nullam rhoncus tristique tellus in Portucale. + +

+

+ Truncate + + Lorem ipsum dolor sit amet, nullam rhoncus tristique tellus in Portucale. + +

+

+ Italic + + Hello, world + +

+

+ Underline + + Hello, world + +

+

+ Strikethrough + + Hello, world + +

+

+ Sizes + + 100 + + + 200 + + + 300 + + + 400 + + + 500 + + + 600 + + + 700 + + + 800 + + + 900 + + + 1000 + +

+

+ Fonts + + Base + + + Monospace + + + Numeric + +

+

+ Weights + + Regular + + + Medium + + + Semibold + +

+

+ Alignments + + Start + + + Center + + + End + + + Justified lorem ipsum dolor sit amet, nullam rhoncus tristique tellus in Portucale. + +

+ + ), +} satisfies StoryObj; + +export const DefaultRTL = getStoryVariant(Default, RTL); + +export const DefaultHighContrast = getStoryVariant(Default, HIGH_CONTRAST); + +export const DefaultDarkMode = getStoryVariant(Default, DARK_MODE); + +export const TypographyPresets = { + render: () => ( + <> + Body1 + Body1Strong + Body1Stronger + Body2 + Caption1 + Caption1Strong + Caption1Stronger + Caption2 + Caption2Strong + Display + LargeTitle + Subtitle1 + Subtitle2 + Subtitle2Stronger + Title1 + Title2 + Title3 + + ), + + name: 'Typography presets', +} satisfies StoryObj; + +export const TypographyPresetsRTL = getStoryVariant(TypographyPresets, RTL); diff --git a/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - Dark Mode.normal.chromium.png b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - Dark Mode.normal.chromium.png new file mode 100644 index 0000000000000..34a4f5aef75eb Binary files /dev/null and b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - Dark Mode.normal.chromium.png differ diff --git a/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - High Contrast.normal.chromium.png b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - High Contrast.normal.chromium.png new file mode 100644 index 0000000000000..583c03a90ee89 Binary files /dev/null and b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - High Contrast.normal.chromium.png differ diff --git a/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - RTL.normal.chromium.png b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - RTL.normal.chromium.png new file mode 100644 index 0000000000000..19cdb0887b351 Binary files /dev/null and b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default - RTL.normal.chromium.png differ diff --git a/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default.normal.chromium.png b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default.normal.chromium.png new file mode 100644 index 0000000000000..6f94162c82b65 Binary files /dev/null and b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Default.normal.chromium.png differ diff --git a/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Typography presets - RTL.normal.chromium.png b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Typography presets - RTL.normal.chromium.png new file mode 100644 index 0000000000000..318e87ed60b2b Binary files /dev/null and b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Typography presets - RTL.normal.chromium.png differ diff --git a/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Typography presets.normal.chromium.png b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Typography presets.normal.chromium.png new file mode 100644 index 0000000000000..6f0a0a29857df Binary files /dev/null and b/packages/react-components/react-text/visual-regression/src/__snapshots__/Text Converged.Typography presets.normal.chromium.png differ diff --git a/packages/react-components/react-text/visual-regression/src/index.ts b/packages/react-components/react-text/visual-regression/src/index.ts new file mode 100644 index 0000000000000..cb0ff5c3b541f --- /dev/null +++ b/packages/react-components/react-text/visual-regression/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/react-components/react-text/visual-regression/tsconfig.json b/packages/react-components/react-text/visual-regression/tsconfig.json new file mode 100644 index 0000000000000..be0a513aa78c5 --- /dev/null +++ b/packages/react-components/react-text/visual-regression/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "target": "ES2019", + "noEmit": true, + "isolatedModules": true, + "importHelpers": true, + "jsx": "react", + "noUnusedLocals": true, + "preserveConstEnums": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./.storybook/tsconfig.json" + } + ] +} diff --git a/packages/react-components/react-text/visual-regression/tsconfig.lib.json b/packages/react-components/react-text/visual-regression/tsconfig.lib.json new file mode 100644 index 0000000000000..e19d2b095980c --- /dev/null +++ b/packages/react-components/react-text/visual-regression/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["ES2019", "dom"], + "outDir": "../../../../dist/out-tsc", + "inlineSources": true, + "types": ["node"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/tools/workspace-plugin/src/plugins/workspace-plugin.ts b/tools/workspace-plugin/src/plugins/workspace-plugin.ts index 27a2f365b69ca..60a547e090818 100644 --- a/tools/workspace-plugin/src/plugins/workspace-plugin.ts +++ b/tools/workspace-plugin/src/plugins/workspace-plugin.ts @@ -151,6 +151,17 @@ function buildWorkspaceTargets( // react v9 lib if (projectJSON.projectType === 'library' && tags.includes('vNext')) { + // *-visual-regression projects + if (tags.includes('visual-regression')) { + // TODO: add VRT targets + + if (storybookTarget) { + targets.start = { command: `nx run ${config.projectJSON.name}:storybook`, cache: true }; + } + + return targets; + } + // *-stories projects if (tags.includes('type:stories')) { const testSsrTarget = buildTestSsrTarget(projectRoot, options, context, config); diff --git a/tsconfig.base.all.json b/tsconfig.base.all.json index bf9d869d025e1..89d37b3e032ce 100644 --- a/tsconfig.base.all.json +++ b/tsconfig.base.all.json @@ -235,6 +235,7 @@ ], "@fluentui/react-text": ["packages/react-components/react-text/library/src/index.ts"], "@fluentui/react-text-stories": ["packages/react-components/react-text/stories/src/index.ts"], + "@fluentui/react-text-visual-regression": ["packages/react-components/react-text/visual-regression/src/index.ts"], "@fluentui/react-textarea": ["packages/react-components/react-textarea/library/src/index.ts"], "@fluentui/react-textarea-stories": ["packages/react-components/react-textarea/stories/src/index.ts"], "@fluentui/react-theme": ["packages/react-components/react-theme/library/src/index.ts"], diff --git a/tsconfig.base.json b/tsconfig.base.json index ee252b78cc7b0..3f4ed797b4f47 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -169,6 +169,7 @@ ], "@fluentui/react-text": ["packages/react-components/react-text/library/src/index.ts"], "@fluentui/react-text-stories": ["packages/react-components/react-text/stories/src/index.ts"], + "@fluentui/react-text-visual-regression": ["packages/react-components/react-text/visual-regression/src/index.ts"], "@fluentui/react-textarea": ["packages/react-components/react-textarea/library/src/index.ts"], "@fluentui/react-textarea-stories": ["packages/react-components/react-textarea/stories/src/index.ts"], "@fluentui/react-theme": ["packages/react-components/react-theme/library/src/index.ts"],