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"],