From ec93171eada5b73309b2301c81276aa677dd4f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Thu, 7 May 2026 00:10:19 -0700 Subject: [PATCH 01/23] Upgrade to PowerShell 7 (#16075) * Ran NuGetRestoreForceEvaluateAllSolutions.ps1 * Define installNuGetPackages task; Install pwsh 7.6.1 * Replace powershell.exe with pwsh.exe * Change files * CI: Ensure NuGet is availabe before `yarn install` * Add NuGetAuthenticate before yarn install * Do not download PowerShell on CI/CD environments * Fix just-task JS syntax * Add dotnet-tools manifest * Fix pwsh.exe tool path * Resolve dotnet-tools.json path * Use dotnet for `nuget locals` * Use PWSH in nuget-restore-task.js * Break dependency loop in just-task * Update other ADO tasks * Update @react-native-windows/automation * Rename findPwsh as findPowerShell * Drop powershell export from commandWithProgress * Change files * Add README for find-dotnet-tools * Add README to package.json * Adjust newline * Run task `installDotnetTools` only when not in CI * Install .NET tools in CI * Install .NET on prepare-js-env * Do not install .NET tools on CI * Quote powershell path * fix yarn lint * Ran NuGetRestoreForceEvaluateAllSolutions.ps1 * Restore deleted lock files * Add missing dependency * Use -Command for compatible PWSH calls * Use -Command pwsh argument * Correct runPowerShellScriptFunction cmd escaping * Quote pwsh command in runPowerShellScriptFunction * Remove quotes * Quote remaining findPowerShell() calls for execSync * Add temporary diagnostic commands * Diagnostics * Diagnostics * Diagnostics * Add runPowerShellScriptFunction argument to delegate Appx-related commands to Windows PowerShell * Remove diagnostics steps * Use execFileSync instead of execSync in deploy.ts * Remove querystrings from FS paths * Suppress warning when using `-UseWindowsPowerShell` * Revert "Remove querystrings from FS paths" This reverts commit e1f3cfdf0520f916e4f7fe6ac62a7eebba3bb51a. * Use pipe semantics for Get-AppxPackage in resolveAppName * Hard-fail if PowerShell 7 cannot be found. Do not fall back to Windows PowerShell (powershell.exe). PowerShell code may not be backwards compatible. * Improve missing pwsh.exe error message * Add package resolution pipeline test * UseWindowsPowerShell for package resolution test * Try to read package Microsoft.PrintDialog * Replace stderr redirection with -WarningAction * Replace stderr redirection with -WarningAction * Print sample app names on appWindow error * Add more diagnostics * Add missing -WarningAction SilentlyContinue * Revert "Add more diagnostics" This reverts commit 8c10bc4d1abd2afc30240d06eb7cf8a113422a6d. * Revert "Print sample app names on appWindow error" This reverts commit 6e70148a35e50d4bd9c62f50908e2b3855ef6dce. * Remove CI probing tasks * Revert "Use pipe semantics for Get-AppxPackage in resolveAppName" This reverts commit 6c46c73413af274b4535e94a327ed1d3202dc2ed. Co-authored-by: Copilot * Remove unused escaping logic * Re-introduce deleted .lock.json --------- Co-authored-by: Copilot --- .ado/jobs/e2e-test.yml | 15 ++++ .ado/scripts/build.js | 2 +- .ado/templates/install-SDK.yml | 1 + ...-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json | 7 ++ ...-d0909d53-2984-4eff-bcf6-fe1702c03277.json | 7 ++ ...-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json | 7 ++ ...-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json | 7 ++ .../automation/package.json | 2 +- .../automation/src/AutomationEnvironment.ts | 13 +++- .../@react-native-windows/cli/package.json | 2 +- .../src/commands/healthCheck/healthChecks.ts | 6 +- .../cli/src/e2etest/healthChecks.test.ts | 6 +- .../cli/src/utils/commandWithProgress.ts | 15 ++-- .../cli/src/utils/deploy.ts | 30 +++++--- .../cli/src/utils/msbuildtools.ts | 4 +- .../find-dotnet-tools/.eslintrc.js | 4 ++ .../find-dotnet-tools/.gitignore | 2 + .../find-dotnet-tools/README.md | 50 +++++++++++++ .../find-dotnet-tools/package.json | 52 ++++++++++++++ .../find-dotnet-tools/src/findDotnetTools.ts | 70 +++++++++++++++++++ .../find-dotnet-tools/tsconfig.json | 5 ++ .../just-task/nuget-restore-task.js | 5 +- vnext/dotnet-tools.json | 12 ++++ vnext/just-task.js | 16 ++++- vnext/package.json | 2 +- 25 files changed, 314 insertions(+), 28 deletions(-) create mode 100644 change/@react-native-windows-automation-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json create mode 100644 change/@react-native-windows-cli-d0909d53-2984-4eff-bcf6-fe1702c03277.json create mode 100644 change/@react-native-windows-find-dotnet-tools-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json create mode 100644 change/react-native-windows-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json create mode 100644 packages/@react-native-windows/find-dotnet-tools/.eslintrc.js create mode 100644 packages/@react-native-windows/find-dotnet-tools/.gitignore create mode 100644 packages/@react-native-windows/find-dotnet-tools/README.md create mode 100644 packages/@react-native-windows/find-dotnet-tools/package.json create mode 100644 packages/@react-native-windows/find-dotnet-tools/src/findDotnetTools.ts create mode 100644 packages/@react-native-windows/find-dotnet-tools/tsconfig.json create mode 100644 vnext/dotnet-tools.json diff --git a/.ado/jobs/e2e-test.yml b/.ado/jobs/e2e-test.yml index d6c9d3b3e18..93d2930689d 100644 --- a/.ado/jobs/e2e-test.yml +++ b/.ado/jobs/e2e-test.yml @@ -320,6 +320,21 @@ jobs: condition: and(failed(), eq(variables.StartedFabricTests, 'true')) continueOnError: true + - pwsh: | + if (Test-Path "packages/e2e-test-app-fabric/test/__image_snapshots__/__diff_output__") { + Write-Host "##vso[task.setvariable variable=DiffOutputExists]true" + } + displayName: Check for image diff output folder + condition: failed() + + - task: CopyFiles@2 + displayName: Copy Fabric image diffs + inputs: + sourceFolder: packages/e2e-test-app-fabric/test/__image_snapshots__/__diff_output__ + targetFolder: $(Build.StagingDirectory)/snapshots-image-diffs + contents: "**" + condition: and(failed(), eq(variables.DiffOutputExists, 'true')) + - script: npx jest --clearCache displayName: clear jest cache workingDirectory: packages/e2e-test-app-fabric diff --git a/.ado/scripts/build.js b/.ado/scripts/build.js index 47df62d2c65..db46551eba9 100644 --- a/.ado/scripts/build.js +++ b/.ado/scripts/build.js @@ -101,7 +101,7 @@ function ensureNuGet(toolsPath) { ensureDir(toolsPath); console.log(`Downloading nuget.exe to: ${localNuGet}`); execSync( - `powershell.exe -NoLogo -NoProfile -Command ` + + `pwsh.exe -NoLogo -NoProfile -Command ` + `"[Net.ServicePointManager]::SecurityProtocol = ` + `[Net.SecurityProtocolType]::Tls12; ` + `Invoke-WebRequest -Uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' ` + diff --git a/.ado/templates/install-SDK.yml b/.ado/templates/install-SDK.yml index ef301a278dd..18fda6f4c47 100644 --- a/.ado/templates/install-SDK.yml +++ b/.ado/templates/install-SDK.yml @@ -7,5 +7,6 @@ steps: targetType: filePath filePath: vnext\Scripts\Install-WindowsSdkISO.ps1 arguments: ${{ parameters.sdkVersion }} + pwsh: true displayName: 'Install Insider SDK (${{ parameters.sdkVersion }})' condition: ne('', '${{ parameters.sdkVersion }}') diff --git a/change/@react-native-windows-automation-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json b/change/@react-native-windows-automation-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json new file mode 100644 index 00000000000..44cad7b1964 --- /dev/null +++ b/change/@react-native-windows-automation-10c8b2ce-ed81-450b-b846-4695f1ed4c4f.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Migrate to PowerShell 7", + "packageName": "@react-native-windows/automation", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@react-native-windows-cli-d0909d53-2984-4eff-bcf6-fe1702c03277.json b/change/@react-native-windows-cli-d0909d53-2984-4eff-bcf6-fe1702c03277.json new file mode 100644 index 00000000000..46065e97e3a --- /dev/null +++ b/change/@react-native-windows-cli-d0909d53-2984-4eff-bcf6-fe1702c03277.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to PowerShell 7", + "packageName": "@react-native-windows/cli", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@react-native-windows-find-dotnet-tools-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json b/change/@react-native-windows-find-dotnet-tools-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json new file mode 100644 index 00000000000..621e68e6ec3 --- /dev/null +++ b/change/@react-native-windows-find-dotnet-tools-7018a5fe-0791-4fb7-ae1d-c95620216ddc.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Migrate to PowerShell 7", + "packageName": "@react-native-windows/find-dotnet-tools", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json b/change/react-native-windows-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json new file mode 100644 index 00000000000..f1e3abb1df6 --- /dev/null +++ b/change/react-native-windows-f8669931-74f3-4c8e-a1d1-ecb19860ee42.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to PowerShell 7", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/automation/package.json b/packages/@react-native-windows/automation/package.json index aacd8b68412..5e3b88e30b8 100644 --- a/packages/@react-native-windows/automation/package.json +++ b/packages/@react-native-windows/automation/package.json @@ -63,4 +63,4 @@ "engines": { "node": ">= 22" } -} +} \ No newline at end of file diff --git a/packages/@react-native-windows/automation/src/AutomationEnvironment.ts b/packages/@react-native-windows/automation/src/AutomationEnvironment.ts index 9088328a9d6..2306bfa0ec6 100644 --- a/packages/@react-native-windows/automation/src/AutomationEnvironment.ts +++ b/packages/@react-native-windows/automation/src/AutomationEnvironment.ts @@ -8,6 +8,7 @@ import chalk from 'chalk'; import {spawnSync, spawn, ChildProcess} from 'child_process'; import fs from '@react-native-windows/fs'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import path from 'path'; import readlineSync from 'readline-sync'; @@ -250,7 +251,7 @@ export default class AutomationEnvironment extends NodeEnvironment { if (this.breakOnStart) { readlineSync.question( chalk.bold.yellow('Breaking before tests start\n') + - 'Press Enter to resume...', + 'Press Enter to resume...', ); } @@ -335,8 +336,14 @@ function resolveAppName(appName: string): string { } try { - const packageFamilyName = spawnSync('powershell', [ - `(Get-AppxPackage -Name ${appName}).PackageFamilyName`, + const useAppxCompatibility = !!process.env.TF_BUILD; + const packageFamilyNameCommand = useAppxCompatibility + ? `& { Import-Module Appx -UseWindowsPowerShell -WarningAction SilentlyContinue; (Get-AppxPackage -Name '${appName}').PackageFamilyName }` + : `(Get-AppxPackage -Name '${appName}').PackageFamilyName`; + const packageFamilyName = spawnSync(findPowerShell(), [ + '-NoProfile', + '-Command', + packageFamilyNameCommand, ]) .stdout.toString() .trim(); diff --git a/packages/@react-native-windows/cli/package.json b/packages/@react-native-windows/cli/package.json index c27f66fa634..41c52c17e75 100644 --- a/packages/@react-native-windows/cli/package.json +++ b/packages/@react-native-windows/cli/package.json @@ -88,4 +88,4 @@ "engines": { "node": ">= 22" } -} +} \ No newline at end of file diff --git a/packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts b/packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts index 90a4a11a1df..74d4797811a 100644 --- a/packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts +++ b/packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts @@ -14,9 +14,11 @@ import type { HealthCheckCategory, HealthCheckInterface, } from '@react-native-community/cli-doctor/build/types'; -import {powershell} from '../../utils/commandWithProgress'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import {HealthCheckList} from './healthCheckList'; +const powershell = findPowerShell(); + export function getHealthChecks(): HealthCheckCategory[] | undefined { // #8471: There are known cases where the dependencies script will error out. // Fail gracefully if that happens in the meantime. @@ -76,7 +78,7 @@ function getHealthChecksUnsafe(): HealthCheckCategory[] | undefined { }; }, runAutomaticFix: async ({loader, logManualInstallation}) => { - const command = `${powershell} -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -Check ${id}`; + const command = `"${powershell}" -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -Check ${id}`; try { const {exitCode} = await execa(command, {stdio: 'inherit'}); if (exitCode) { diff --git a/packages/@react-native-windows/cli/src/e2etest/healthChecks.test.ts b/packages/@react-native-windows/cli/src/e2etest/healthChecks.test.ts index ad0a17edfb2..98d2be3a9f8 100644 --- a/packages/@react-native-windows/cli/src/e2etest/healthChecks.test.ts +++ b/packages/@react-native-windows/cli/src/e2etest/healthChecks.test.ts @@ -6,9 +6,11 @@ import {execSync} from 'child_process'; import path from 'path'; -import {powershell} from '../utils/commandWithProgress'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import {HealthCheckList} from '../commands/healthCheck/healthCheckList'; +const powershell = findPowerShell(); + test('Verify list of health checks aligns with rnw-dependencies', async () => { const rnwDepScriptPath = path.join( path.dirname( @@ -20,7 +22,7 @@ test('Verify list of health checks aligns with rnw-dependencies', async () => { ); const rnwDeps = execSync( - `${powershell} -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -NoPrompt -ListChecks`, + `"${powershell}" -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -NoPrompt -ListChecks`, {stdio: 'pipe'}, ); const deps = rnwDeps.toString().trim().split('\n'); diff --git a/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts b/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts index 74e8bec3d0c..9ee9fe56113 100644 --- a/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts +++ b/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts @@ -14,6 +14,7 @@ import { CodedErrors, CodedErrorType, } from '@react-native-windows/telemetry'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; function setSpinnerText(spinner: ora.Ora, prefix: string, text: string) { text = prefix + spinnerString(text); @@ -47,7 +48,7 @@ export function newSpinner(text: string) { return ora(options).start(); } -export const powershell = `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`; +const powershell = findPowerShell(); export async function runPowerShellScriptFunction( taskDescription: string, @@ -55,10 +56,15 @@ export async function runPowerShellScriptFunction( funcName: string, verbose: boolean, errorCategory: CodedErrorType, + useAppxCompatibility = false, ) { try { const printException = verbose ? '$_;' : ''; - const importScript = script ? `Import-Module "${script}"; ` : ''; + const importAppx = useAppxCompatibility + ? 'Import-Module Appx -UseWindowsPowerShell -WarningAction SilentlyContinue; ' + : ''; + const importScript = script ? `Import-Module '${script}'; ` : ''; + const powershellCommand = `${importAppx}${importScript}try { ${funcName} -ErrorAction Stop; $lec = $LASTEXITCODE; } catch { $lec = 1; ${printException} }; exit $lec`; await commandWithProgress( newSpinner(taskDescription), taskDescription, @@ -67,7 +73,8 @@ export async function runPowerShellScriptFunction( '-NoProfile', '-ExecutionPolicy', 'RemoteSigned', - `${importScript}try { ${funcName} -ErrorAction Stop; $lec = $LASTEXITCODE; } catch { $lec = 1; ${printException} }; exit $lec`, + '-Command', + `&{${powershellCommand}}`, ], verbose, errorCategory, @@ -88,7 +95,7 @@ export function commandWithProgress( errorCategory: CodedErrorType, ) { return new Promise((resolve, reject) => { - const spawnOptions: SpawnOptions = verbose ? {stdio: 'inherit'} : {}; + const spawnOptions: SpawnOptions = verbose ? { stdio: 'inherit' } : {}; if (verbose) { spinner.stop(); diff --git a/packages/@react-native-windows/cli/src/utils/deploy.ts b/packages/@react-native-windows/cli/src/utils/deploy.ts index 39395245d74..4335d166c36 100644 --- a/packages/@react-native-windows/cli/src/utils/deploy.ts +++ b/packages/@react-native-windows/cli/src/utils/deploy.ts @@ -4,7 +4,7 @@ * @format */ -import {spawn, execSync, SpawnOptions} from 'child_process'; +import {spawn, execFileSync, SpawnOptions} from 'child_process'; import fs from '@react-native-windows/fs'; import http from 'http'; import path from 'path'; @@ -19,8 +19,8 @@ import { newSpinner, commandWithProgress, runPowerShellScriptFunction, - powershell, } from './commandWithProgress'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import * as build from './build'; import { BuildConfig, @@ -183,9 +183,12 @@ function getWindowsStoreAppUtils(options: RunWindowsOptions) { 'powershell', 'WindowsStoreAppUtils.psm1', ); - execSync( - `${powershell} -NoProfile Unblock-File '${windowsStoreAppUtilsPath}'`, - ); + const powershell = findPowerShell(); + execFileSync(powershell, [ + '-NoProfile', + '-Command', + `& { Unblock-File '${windowsStoreAppUtilsPath}' }`, + ]); popd(); return windowsStoreAppUtilsPath; } @@ -359,6 +362,7 @@ export async function deployToDesktop( config: Config, buildTools: MSBuildTools, ) { + const useAppxCompatibility = !!process.env.TF_BUILD; const windowsConfig: Partial | undefined = config.project.windows; const slnFile = @@ -391,6 +395,7 @@ export async function deployToDesktop( 'EnableDevMode', verbose, 'EnableDevModeFailure', + useAppxCompatibility, ); const appPackageFolder = getAppPackage(options, projectName); @@ -403,6 +408,7 @@ export async function deployToDesktop( `Uninstall-App ${appName}`, verbose, 'RemoveOldAppVersionFailure', + useAppxCompatibility, ); const script = glob.sync( @@ -415,6 +421,7 @@ export async function deployToDesktop( `Install-App "${script}" -Force`, verbose, 'InstallAppFailure', + useAppxCompatibility, ); } else { // Deploy from layout @@ -442,6 +449,7 @@ export async function deployToDesktop( `Install-AppDependencies ${appxManifestPath} ${appPackageFolder} ${options.arch}`, verbose, 'InstallAppDependenciesFailure', + useAppxCompatibility, ); await build.buildSolution( buildTools, @@ -456,9 +464,14 @@ export async function deployToDesktop( } } - const appFamilyName = execSync( - `${powershell} -NoProfile -c $(Get-AppxPackage -Name ${appName}).PackageFamilyName`, - ) + const appFamilyNameCommand = useAppxCompatibility + ? `& { Import-Module Appx -UseWindowsPowerShell -WarningAction SilentlyContinue; (Get-AppxPackage -Name '${appName}').PackageFamilyName }` + : `(Get-AppxPackage -Name '${appName}').PackageFamilyName`; + const appFamilyName = execFileSync(findPowerShell(), [ + '-NoProfile', + '-Command', + appFamilyNameCommand, + ]) .toString() .trim(); @@ -488,6 +501,7 @@ export async function deployToDesktop( `Start-Locally ${appName} ${args}`, verbose, 'AppStartupFailure', + useAppxCompatibility, ); } else { newInfo('Skip the step to start the app'); diff --git a/packages/@react-native-windows/cli/src/utils/msbuildtools.ts b/packages/@react-native-windows/cli/src/utils/msbuildtools.ts index b4cae081f1f..aa1d00ccfe2 100644 --- a/packages/@react-native-windows/cli/src/utils/msbuildtools.ts +++ b/packages/@react-native-windows/cli/src/utils/msbuildtools.ts @@ -18,8 +18,8 @@ import { newSpinner, newSuccess, newError, - powershell, } from './commandWithProgress'; +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; import {execSync} from 'child_process'; import {BuildArch, BuildConfig} from '../commands/runWindows/runWindowsOptions'; import {findLatestVsInstall} from './vsInstalls'; @@ -317,7 +317,7 @@ export default class MSBuildTools { 'Eval-MsBuildProperties.ps1', ); - let command = `${powershell} -ExecutionPolicy Unrestricted -NoProfile "${msbuildEvalScriptPath}" -SolutionFile '${solutionFile}' -ProjectFile '${projectFile}' -MSBuildPath '${this.msbuildPath()}'`; + let command = `"${findPowerShell()}" -ExecutionPolicy Unrestricted -NoProfile "${msbuildEvalScriptPath}" -SolutionFile '${solutionFile}' -ProjectFile '${projectFile}' -MSBuildPath '${this.msbuildPath()}'`; if (propertyNames && propertyNames.length > 0) { command += ` -PropertyNames '${propertyNames.join(',')}'`; diff --git a/packages/@react-native-windows/find-dotnet-tools/.eslintrc.js b/packages/@react-native-windows/find-dotnet-tools/.eslintrc.js new file mode 100644 index 00000000000..35e0d115126 --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: ['@rnw-scripts'], + parserOptions: {tsconfigRootDir : __dirname}, +}; diff --git a/packages/@react-native-windows/find-dotnet-tools/.gitignore b/packages/@react-native-windows/find-dotnet-tools/.gitignore new file mode 100644 index 00000000000..f42efbb9f7c --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/.gitignore @@ -0,0 +1,2 @@ +lib/ +lib-commonjs/ diff --git a/packages/@react-native-windows/find-dotnet-tools/README.md b/packages/@react-native-windows/find-dotnet-tools/README.md new file mode 100644 index 00000000000..f643400da11 --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/README.md @@ -0,0 +1,50 @@ +# @react-native-windows/find-dotnet-tools + +Helpers to locate .NET-based tools (e.g. PowerShell) restored via `dotnet tool restore` or +available on PATH. + +Used to resolve tool paths consistently across local development and CI +environments. + +## Usage + +Add the package as a dependency: + +```json +{ + "dependencies": { + "@react-native-windows/find-dotnet-tools": "" + } +} +``` + +### findPowerShell + +Locates a PowerShell executable by checking, in order: + +1. A `dotnet-tool`-restored copy of `pwsh.exe` (skipped in CI builds) +2. `pwsh.exe` on the system PATH + +Throws an error if `pwsh.exe` cannot be found. + +```js +import {findPowerShell} from '@react-native-windows/find-dotnet-tools'; + +const pwsh = findPowerShell(); +// e.g. "C:\\Users\\user\\.nuget\\packages\\PowerShell\\7.6.1\\tools\\net10.0\\any\\win\\pwsh.exe" +``` + +### getNugetGlobalPackagesFolder + +Returns the path to the global NuGet packages folder by checking, in order: + +1. The `NUGET_PACKAGES` environment variable +2. The output of `dotnet nuget locals global-packages --list` +3. The default `~/.nuget/packages` location + +```js +import {getNugetGlobalPackagesFolder} from '@react-native-windows/find-dotnet-tools'; + +const packagesDir = getNugetGlobalPackagesFolder(); +// e.g. "C:\\Users\\user\\.nuget\\packages" +``` diff --git a/packages/@react-native-windows/find-dotnet-tools/package.json b/packages/@react-native-windows/find-dotnet-tools/package.json new file mode 100644 index 00000000000..77901e68a1c --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/package.json @@ -0,0 +1,52 @@ +{ + "name": "@react-native-windows/find-dotnet-tools", + "description": "Helpers to locate .NET-based tools (e.g. pwsh) restored via NuGet or available on PATH.", + "version": "0.0.0-canary.1", + "license": "MIT", + "scripts": { + "build": "rnw-scripts build", + "clean": "rnw-scripts clean", + "lint": "rnw-scripts lint", + "lint:fix": "rnw-scripts lint:fix", + "watch": "rnw-scripts watch" + }, + "main": "lib-commonjs/findDotnetTools.js", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/react-native-windows", + "directory": "packages/@react-native-windows/find-dotnet-tools" + }, + "dependencies": { + "@react-native-windows/fs": "^0.0.0-canary.72" + }, + "devDependencies": { + "@rnw-scripts/eslint-config": "1.2.38", + "@rnw-scripts/just-task": "2.3.58", + "@rnw-scripts/ts-config": "2.0.6", + "@types/node": "^22.14.0", + "@typescript-eslint/eslint-plugin": "^8.36.0", + "@typescript-eslint/parser": "^8.36.0", + "eslint": "^8.19.0", + "prettier": "^3.6.2", + "typescript": "5.0.4" + }, + "beachball": { + "defaultNpmTag": "canary", + "disallowedChangeTypes": [ + "major", + "minor", + "patch", + "premajor", + "preminor", + "prepatch" + ] + }, + "promoteRelease": true, + "files": [ + "lib-commonjs", + "README.md" + ], + "engines": { + "node": ">= 22" + } +} diff --git a/packages/@react-native-windows/find-dotnet-tools/src/findDotnetTools.ts b/packages/@react-native-windows/find-dotnet-tools/src/findDotnetTools.ts new file mode 100644 index 00000000000..d684656bee4 --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/src/findDotnetTools.ts @@ -0,0 +1,70 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +import {execSync} from 'child_process'; +import fs from '@react-native-windows/fs'; +import os from 'os'; +import path from 'path'; + +/** + * Returns the path to the global NuGet packages folder, checking (in order): + * 1. The NUGET_PACKAGES environment variable + * 2. The `dotnet nuget locals` command output + * 3. The default ~/.nuget/packages location + */ +export function getNugetGlobalPackagesFolder(): string { + if (process.env.NUGET_PACKAGES) { + return process.env.NUGET_PACKAGES; + } + try { + const output = execSync('dotnet.exe nuget locals global-packages --list', { + encoding: 'utf8', + }).trim(); + const match = output.match(/global-packages:\s*(.+)/i); + if (match) { + return match[1].trim(); + } + } catch {} + return path.join(os.homedir(), '.nuget', 'packages'); +} + +/** + * Locates a PowerShell executable, checking (in order): + * 1. A NuGet-restored copy of pwsh (skipped in CI builds) + * 2. pwsh.exe on the system PATH + * + * Throws if no pwsh.exe can be located. + */ +export function findPowerShell(): string { + // Build agents already have PowerShell (pwsh) installed + if (!process.env.TF_BUILD) { + const nugetPackages = getNugetGlobalPackagesFolder(); + const nugetPwsh = path.join( + nugetPackages, + 'PowerShell', + '7.6.1', + 'tools', + 'net10.0', + 'any', + 'win', + 'pwsh.exe', + ); + if (fs.existsSync(nugetPwsh)) { + return nugetPwsh; + } + } + + try { + const found = execSync('where pwsh.exe', {encoding: 'utf8'}).trim(); + if (found) { + return found.split(/\r?\n/)[0]; + } + } catch {} + + throw new Error( + 'Unable to find pwsh.exe. It should have been made available by `yarn install`.', + ); +} diff --git a/packages/@react-native-windows/find-dotnet-tools/tsconfig.json b/packages/@react-native-windows/find-dotnet-tools/tsconfig.json new file mode 100644 index 00000000000..c62faa78baf --- /dev/null +++ b/packages/@react-native-windows/find-dotnet-tools/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@rnw-scripts/ts-config", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/@rnw-scripts/just-task/nuget-restore-task.js b/packages/@rnw-scripts/just-task/nuget-restore-task.js index d8b316a46b2..97da9d6e163 100644 --- a/packages/@rnw-scripts/just-task/nuget-restore-task.js +++ b/packages/@rnw-scripts/just-task/nuget-restore-task.js @@ -9,6 +9,7 @@ const fs = require('fs'); const path = require('path'); const {execSync, spawnSync} = require('child_process'); const {task} = require('just-scripts'); +const {findPowerShell} = require('@react-native-windows/find-dotnet-tools'); function registerNuGetRestoreTask(options) { const config = normalizeOptions(options); @@ -51,12 +52,14 @@ function executeNuGetRestore(config) { `Restoring NuGet packages (log: ${path.relative(process.cwd(), logPath)})`, ); + const powershell = findPowerShell(); + const scriptArgs = config.scriptArguments.length ? ` ${config.scriptArguments.join(' ')}` : ''; const restoreCommand = `call ${quote( vsDevCmd, - )} && powershell -NoProfile -ExecutionPolicy Bypass -File ${quote( + )} && "${powershell}" -NoProfile -ExecutionPolicy Bypass -File ${quote( config.scriptPath, )}${scriptArgs}`; const wrappedCommand = `${restoreCommand}`; diff --git a/vnext/dotnet-tools.json b/vnext/dotnet-tools.json new file mode 100644 index 00000000000..2468cbda2de --- /dev/null +++ b/vnext/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "powershell": { + "version": "7.6.1", + "commands": [ + "pwsh" + ] + } + } +} \ No newline at end of file diff --git a/vnext/just-task.js b/vnext/just-task.js index e3fb44c76b1..e10779044fb 100644 --- a/vnext/just-task.js +++ b/vnext/just-task.js @@ -25,6 +25,7 @@ const fs = require('fs'); const { registerNuGetRestoreTask, } = require('@rnw-scripts/just-task/nuget-restore-task'); +const {findPowerShell} = require('@react-native-windows/find-dotnet-tools'); option('production'); option('clean'); @@ -44,11 +45,12 @@ function codegen(test) { ); } +const powershell = findPowerShell(); + function layoutMSRNCxx() { if (require('os').platform() === 'win32') { - const powershell = `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`; execSync( - `${powershell} -NoProfile .\\Scripts\\Tfs\\Layout-MSRN-Headers.ps1 -GenerateLocalCxx`, + `"${powershell}" -NoProfile .\\Scripts\\Tfs\\Layout-MSRN-Headers.ps1 -GenerateLocalCxx`, { env: process.env, }, @@ -84,12 +86,22 @@ registerNuGetRestoreTask({ scriptArguments: ['-SkipLockDeletion'], }); +function installDotnetToolsTask() { + execSync( + `dotnet tool restore --tool-manifest ${path.resolve(__dirname, 'dotnet-tools.json')}`, + {env: process.env}, + ); +} + +task('installDotnetTools', installDotnetToolsTask); + task( 'build', series( condition('clean', () => argv().clean), 'copyRNLibraries', 'copyReadmeAndLicenseFromRoot', + condition('installDotnetTools', () => !process.env.TF_BUILD), 'layoutMSRNCxx', 'compileTsPlatformOverrides', 'restoreNuGetPackages', diff --git a/vnext/package.json b/vnext/package.json index 88a52b5455f..ab85043ebb5 100644 --- a/vnext/package.json +++ b/vnext/package.json @@ -151,4 +151,4 @@ "engines": { "node": ">= 22" } -} +} \ No newline at end of file From b057128a16f8deeeeace05c4e840bde54b64ef21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Wed, 20 May 2026 14:16:25 -0700 Subject: [PATCH 02/23] Fix PowerShell installation (#16164) * Defer findPowershell to layoutMSRNCxx * Change files --- ...ative-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json | 7 +++++++ vnext/just-task.js | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 change/react-native-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json diff --git a/change/react-native-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json b/change/react-native-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json new file mode 100644 index 00000000000..9cb98e354c8 --- /dev/null +++ b/change/react-native-windows-f139800b-c305-4d90-a3f6-8d348530a0ce.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Defer findPowershell to layoutMSRNCxx", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/just-task.js b/vnext/just-task.js index e10779044fb..ef22fe5fbbd 100644 --- a/vnext/just-task.js +++ b/vnext/just-task.js @@ -45,10 +45,9 @@ function codegen(test) { ); } -const powershell = findPowerShell(); - function layoutMSRNCxx() { if (require('os').platform() === 'win32') { + const powershell = findPowerShell(); execSync( `"${powershell}" -NoProfile .\\Scripts\\Tfs\\Layout-MSRN-Headers.ps1 -GenerateLocalCxx`, { From ddf803291864a584d1ab4918d76fd863abcace40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Wed, 27 May 2026 19:45:21 -0700 Subject: [PATCH 03/23] Upgrade to Visual Studio 2026 (#16170) * Update deps script. TODO: test * Upgrade rnw-deps to .NET 10 * Defer findPowershell to layoutMSRNCxx * Upgrade VS version to 18.6.1 * Drop /async in MSVC 14.5 * Replace std::future coroutines with WinRT types * clang format * Change files * Drop Windows10SDK.19041 * Change files * Set TargetFramework to net10.0-windows10.0.26100.0 * Use IAsyncOperation instead of out parameters * Install .NET 10 * Revert "Install .NET 10" This reverts commit 5f75dc0cb5edf06607eb97e3d4089857740512b1. * Revert "Set TargetFramework to net10.0-windows10.0.26100.0" This reverts commit 22a4791e8494b6fcf89ba077376c044eedc2659d. * clang format * Upgrade CsWinRT.csproj to .NET 8 * Fix filters * Upgrade MSRN SLN to VS 18 * Revert parameter signature in LoadBundleAsync * Update CSWinRT lock * Allow all versions of VS 2026 * Use IBuffer instead of hstring as payload GetJavaScriptFromServerAsync retuns bundle contents as hstring (16 bit) It then gets converted to an 8-bit string. This conversion drops bytes consideres "invalid". Those bytes are expected as part of the Hermes bundle header. Transmitting the bundle as an IBuffer preserves the original bytes and avoids the conversion issues. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Use WinRT exception types instead of std --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ...-543cf36f-0ce7-4df7-a391-285985d3f62c.json | 7 ++ ...-2a20c629-48b2-458d-80e6-26b5546c04e0.json | 7 ++ .../commands/healthCheck/healthCheckList.ts | 4 +- .../cli/src/utils/vsInstalls.ts | 4 +- .../React.Windows.Desktop.UnitTests.vcxproj | 2 +- .../Microsoft.ReactNative.CsWinRT.csproj | 2 +- .../packages.experimentalwinui3.lock.json | 10 +- .../packages.lock.json | 10 +- ...icrosoft.ReactNative.Cxx.UnitTests.vcxproj | 3 +- ...osoft.ReactNative.IntegrationTests.vcxproj | 3 +- vnext/Microsoft.ReactNative.NewArch.sln | 11 +- .../Microsoft.ReactNative.vcxproj | 3 +- .../Utils/LocalBundleReader.cpp | 58 +++++----- .../Utils/LocalBundleReader.h | 8 +- vnext/Mso.UnitTests/Mso.UnitTests.vcxproj | 3 +- vnext/PropertySheets/React.Cpp.props | 3 +- vnext/Scripts/rnw-dependencies.ps1 | 31 +++--- vnext/Shared/DevSupportManager.cpp | 103 ++++++++++-------- vnext/Shared/DevSupportManager.h | 1 - .../Networking/WinRTWebSocketResource.h | 6 +- 20 files changed, 163 insertions(+), 116 deletions(-) create mode 100644 change/@react-native-windows-cli-543cf36f-0ce7-4df7-a391-285985d3f62c.json create mode 100644 change/react-native-windows-2a20c629-48b2-458d-80e6-26b5546c04e0.json diff --git a/change/@react-native-windows-cli-543cf36f-0ce7-4df7-a391-285985d3f62c.json b/change/@react-native-windows-cli-543cf36f-0ce7-4df7-a391-285985d3f62c.json new file mode 100644 index 00000000000..0552570c866 --- /dev/null +++ b/change/@react-native-windows-cli-543cf36f-0ce7-4df7-a391-285985d3f62c.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to Visual Studio 2026", + "packageName": "@react-native-windows/cli", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-2a20c629-48b2-458d-80e6-26b5546c04e0.json b/change/react-native-windows-2a20c629-48b2-458d-80e6-26b5546c04e0.json new file mode 100644 index 00000000000..74bc56dcc93 --- /dev/null +++ b/change/react-native-windows-2a20c629-48b2-458d-80e6-26b5546c04e0.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to Visual Studio 2026", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/cli/src/commands/healthCheck/healthCheckList.ts b/packages/@react-native-windows/cli/src/commands/healthCheck/healthCheckList.ts index 6e34f37cfe2..c4451f5019b 100644 --- a/packages/@react-native-windows/cli/src/commands/healthCheck/healthCheckList.ts +++ b/packages/@react-native-windows/cli/src/commands/healthCheck/healthCheckList.ts @@ -11,8 +11,8 @@ export const HealthCheckList = [ [true, 'WindowsVersion', 'Windows version >= 10.0.17763.0'], [true, 'DeveloperMode', 'Developer mode is on'], [true, 'LongPath', 'Long path support is enabled'], - [true, 'VSUWP', 'Visual Studio 2022 (>= 17.11.0) & req. components'], + [true, 'VSUWP', 'Visual Studio 2026 (>= 18.6.1) & req. components'], [true, 'Node', 'Node.js (LTS, >= 22.0)'], [true, 'Yarn', 'Yarn'], - [true, 'DotNetCore', '.NET SDK (LTS, = 8.0)'], + [true, 'DotNetCore', '.NET SDK (LTS, = 10.0)'], ]; diff --git a/packages/@react-native-windows/cli/src/utils/vsInstalls.ts b/packages/@react-native-windows/cli/src/utils/vsInstalls.ts index 856cb052f7e..1d529177d89 100644 --- a/packages/@react-native-windows/cli/src/utils/vsInstalls.ts +++ b/packages/@react-native-windows/cli/src/utils/vsInstalls.ts @@ -82,12 +82,12 @@ export function enumerateVsInstalls(opts: { if (minVersionSemVer) { minVersion = minVersionSemVer.toString(); - maxVersion = `${minVersionSemVer.major + 1}.0`; + maxVersion = `${minVersionSemVer.major + 2}.0`; } else if (!Number.isNaN(minVersionNum)) { minVersion = Number.isInteger(minVersionNum) ? `${minVersionNum}.0` : minVersionNum.toString(); - maxVersion = `${Math.floor(minVersionNum) + 1}.0`; + maxVersion = `${Math.floor(minVersionNum) + 2}.0`; } else { // Unable to parse minVersion and determine maxVersion, // caller will throw error that version couldn't be found. diff --git a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj index 83eb2840d0a..2ac2d6d4c58 100644 --- a/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj +++ b/vnext/Desktop.UnitTests/React.Windows.Desktop.UnitTests.vcxproj @@ -79,7 +79,7 @@ $(VCInstallDir)UnitTest\include; %(AdditionalIncludeDirectories) - %(AdditionalOptions) /await + %(AdditionalOptions) /await - /await %(AdditionalOptions) /bigobj /FS + %(AdditionalOptions) /bigobj /FS + %(AdditionalOptions) /await true Cdecl diff --git a/vnext/Microsoft.ReactNative.NewArch.sln b/vnext/Microsoft.ReactNative.NewArch.sln index 46c8480284b..70f9c21dddb 100644 --- a/vnext/Microsoft.ReactNative.NewArch.sln +++ b/vnext/Microsoft.ReactNative.NewArch.sln @@ -1,12 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32929.385 +# Visual Studio Version 18 +VisualStudioVersion = 18.6.11819.183 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}" - ProjectSection(ProjectDependencies) = postProject - {14B93DC8-FD93-4A6D-81CB-8BC96644501C} = {14B93DC8-FD93-4A6D-81CB-8BC96644501C} - EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}" EndProject @@ -111,11 +108,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ReactNative.CsWin EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 + Debug|ARM64 = Debug|ARM64 Debug|x86 = Debug|x86 - Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 + Release|ARM64 = Release|ARM64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index c44789aaba5..de6be8e4203 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -96,7 +96,8 @@ $(IntDir)pch.pch pch.h Level4 - /await %(AdditionalOptions) /bigobj /ZH:SHA_256 + %(AdditionalOptions) /bigobj /ZH:SHA_256 + %(AdditionalOptions) /await $(FmtDir)\include; $(ReactNativeWindowsDir)Microsoft.ReactNative; diff --git a/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.cpp b/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.cpp index 5194f68f6de..f203757a28e 100644 --- a/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.cpp +++ b/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.cpp @@ -54,7 +54,29 @@ std::string GetBundleFromEmbeddedResource(const winrt::Windows::Foundation::Uri return std::string(start, start + size); } -std::future LocalBundleReader::LoadBundleAsync(const std::wstring bundleUri) { +namespace { + +std::string BufferToString(const winrt::Windows::Storage::Streams::IBuffer &buffer) { + std::string result(buffer.Length(), '\0'); + if (!result.empty()) { + auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(buffer); + reader.ReadBytes(winrt::array_view{ + reinterpret_cast(&result[0]), reinterpret_cast(&result[result.length()])}); + } + return result; +} + +winrt::Windows::Storage::Streams::IBuffer BytesToBuffer(const void *data, uint32_t size) { + winrt::Windows::Storage::Streams::DataWriter writer; + auto bytes = static_cast(data); + writer.WriteBytes(winrt::array_view(bytes, bytes + size)); + return writer.DetachBuffer(); +} + +} // namespace + +winrt::Windows::Foundation::IAsyncOperation +LocalBundleReader::LoadBundleAsync(const std::wstring bundleUri) { try { co_await winrt::resume_background(); @@ -66,41 +88,26 @@ std::future LocalBundleReader::LoadBundleAsync(const std::wstring b file = co_await winrt::Windows::Storage::StorageFile::GetFileFromApplicationUriAsync(uri); } else if (bundleUri.starts_with(L"resource://")) { winrt::Windows::Foundation::Uri uri(bundleUri); - co_return GetBundleFromEmbeddedResource(uri); + auto bytes = GetBundleFromEmbeddedResource(uri); + co_return BytesToBuffer(bytes.data(), static_cast(bytes.size())); } else { file = co_await winrt::Windows::Storage::StorageFile::GetFileFromPathAsync(bundleUri); } - // Read the buffer manually to avoid a Utf8 -> Utf16 -> Utf8 encoding - // roundtrip. - auto fileBuffer{co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(file)}; - auto dataReader{winrt::Windows::Storage::Streams::DataReader::FromBuffer(fileBuffer)}; - - // No need to use length + 1, STL guarantees that string storage is null-terminated. - std::string script(fileBuffer.Length(), '\0'); - - // Construct the array_view to slice into the first fileBuffer.Length bytes. - // DataReader.ReadBytes will read as many bytes as are present in the - // array_view. The backing string has fileBuffer.Length() + 1 bytes, without - // an explicit end it will read 1 byte to many and throw. - dataReader.ReadBytes(winrt::array_view{ - reinterpret_cast(&script[0]), reinterpret_cast(&script[script.length()])}); - dataReader.Close(); - - co_return script; + co_return co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(file); } // RuntimeScheduler only handles std::exception or jsi::JSError - catch (winrt::hresult_error const &e) { - throw std::exception(winrt::to_string(e.message()).c_str()); + catch (winrt::hresult_error const &) { + throw; } } std::string LocalBundleReader::LoadBundle(const std::wstring &bundlePath) { - return LoadBundleAsync(bundlePath).get(); + return BufferToString(LoadBundleAsync(bundlePath).get()); } StorageFileBigString::StorageFileBigString(const std::wstring &path) { - m_futureBuffer = LocalBundleReader::LoadBundleAsync(path); + m_pendingLoad = LocalBundleReader::LoadBundleAsync(path); } bool StorageFileBigString::isAscii() const { @@ -118,8 +125,9 @@ size_t StorageFileBigString::size() const { } void StorageFileBigString::ensure() const { - if (m_string.empty()) { - m_string = m_futureBuffer.get(); + if (m_pendingLoad) { + m_string = BufferToString(m_pendingLoad.get()); + m_pendingLoad = nullptr; } } diff --git a/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.h b/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.h index 8671ebf59ee..4286189b94d 100644 --- a/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.h +++ b/vnext/Microsoft.ReactNative/Utils/LocalBundleReader.h @@ -3,14 +3,16 @@ #pragma once #include -#include +#include +#include #include namespace Microsoft::ReactNative { class LocalBundleReader { public: - static std::future LoadBundleAsync(const std::wstring bundlePath); + static winrt::Windows::Foundation::IAsyncOperation LoadBundleAsync( + const std::wstring bundlePath); static std::string LoadBundle(const std::wstring &bundlePath); }; @@ -24,7 +26,7 @@ class StorageFileBigString : public facebook::react::JSBigString { void ensure() const; private: - mutable std::future m_futureBuffer; + mutable winrt::Windows::Foundation::IAsyncOperation m_pendingLoad; mutable std::string m_string; }; diff --git a/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj b/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj index 74fb6cc93fa..1609c75d257 100644 --- a/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj +++ b/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj @@ -94,7 +94,8 @@ /bigobj - /FS - Force Synchronous PDB writes. Useful when setting MultiProcCL. --> - /await %(AdditionalOptions) /bigobj /FS + %(AdditionalOptions) /bigobj /FS + %(AdditionalOptions) /await true Cdecl diff --git a/vnext/PropertySheets/React.Cpp.props b/vnext/PropertySheets/React.Cpp.props index 0c757c4b5a2..0b5ce119add 100644 --- a/vnext/PropertySheets/React.Cpp.props +++ b/vnext/PropertySheets/React.Cpp.props @@ -136,7 +136,8 @@ ProgramDatabase true true - /utf-8 %(AdditionalOptions) /await + /utf-8 %(AdditionalOptions) + %(AdditionalOptions) /await Guard Spectre diff --git a/vnext/Scripts/rnw-dependencies.ps1 b/vnext/Scripts/rnw-dependencies.ps1 index a57d7809f5c..6ec18cf8140 100644 --- a/vnext/Scripts/rnw-dependencies.ps1 +++ b/vnext/Scripts/rnw-dependencies.ps1 @@ -8,7 +8,7 @@ param( [string]$Check = [CheckId]::All, [Parameter(ValueFromRemainingArguments)] - [ValidateSet('appDev', 'rnwDev', 'buildLab', 'vs2022', 'clone')] + [ValidateSet('appDev', 'rnwDev', 'buildLab', 'vs2026', 'clone')] [String[]]$Tags = @('appDev'), [switch]$Enterprise = $false ) @@ -94,7 +94,6 @@ $vsComponents = @('Microsoft.Component.MSBuild', $vcToolsComponent, 'Microsoft.VisualStudio.ComponentGroup.UWP.Support', 'Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core', - 'Microsoft.VisualStudio.Component.Windows10SDK.19041', 'Microsoft.VisualStudio.Component.Windows11SDK.22621'); # UWP.VC is not needed to build the projects with msbuild, but the VS IDE requires it. @@ -113,12 +112,12 @@ $wingetver = "1.7.11261"; # The minimum VS version to check for # Note: For install to work, whatever min version you specify here must be met by the current package available on winget. -$vsver = "17.11.0"; +$vsver = "18.6.1"; # The exact .NET SDK version to check for -$dotnetver = "8.0"; +$dotnetver = "10.0"; # Version name of the winget package -$wingetDotNetVer = "8"; +$wingetDotNetVer = "10"; $v = [System.Environment]::OSVersion.Version; if ($env:Agent_BuildDirectory) { @@ -242,9 +241,9 @@ function InstallVS { if ($Enterprise) { # The CI machines need the enterprise version of VS as that is what is hardcoded in all the scripts - WinGetInstall Microsoft.VisualStudio.2022.Enterprise + WinGetInstall Microsoft.VisualStudio.Enterprise } else { - WinGetInstall Microsoft.VisualStudio.2022.Community + WinGetInstall Microsoft.VisualStudio.Community } $vsWhere = Get-VSWhere; @@ -458,8 +457,8 @@ $requirements = @( }, @{ Id=[CheckId]::VSUWP; - Name = "Visual Studio 2022 (>= $vsver) & req. components"; - Tags = @('appDev', 'vs2022'); + Name = "Visual Studio 2026 (>= $vsver) & req. components"; + Tags = @('appDev', 'vs2026'); Valid = { CheckVS; } Install = { InstallVS }; HasVerboseOutput = $true; @@ -491,7 +490,7 @@ $requirements = @( $downloadPath = "$env:TEMP\WindowsApplicationDriver.msi" Write-Verbose "Downloading WinAppDriver from $url"; Invoke-WebRequest -UseBasicParsing $url -OutFile $downloadPath - + # SDL Compliance: Verify signature (Work Item 58386093) $signature = Get-AuthenticodeSignature $downloadPath if ($signature.Status -ne "Valid") { @@ -499,10 +498,10 @@ $requirements = @( throw "WinAppDriver signature verification failed" } if ($signature.SignerCertificate.Subject -notlike "*Microsoft*") { - Remove-Item $downloadPath -ErrorAction SilentlyContinue + Remove-Item $downloadPath -ErrorAction SilentlyContinue throw "WinAppDriver not signed by Microsoft" } - + & $downloadPath /q Remove-Item $downloadPath -ErrorAction SilentlyContinue }; @@ -600,7 +599,7 @@ function WinGetInstall { Write-Verbose "Executing `winget install `"$wingetPackage`""; & winget install "$wingetPackage" --accept-source-agreements --accept-package-agreements } - + # Refresh PATH environment variable to pick up newly installed tools $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") } @@ -693,12 +692,12 @@ foreach ($req in $filteredRequirements) try { $validAfterInstall = Invoke-Command $req.Valid; } catch { } - + if ($validAfterInstall) { $Installed++; continue; # go to the next item } - + if ($LASTEXITCODE -ne 0) { throw "Last exit code was non-zero: $LASTEXITCODE - $outputFromInstall"; } @@ -737,4 +736,4 @@ if ($NeedsRerun -ne 0) { $Tags | Out-File $MarkerFile; if (!$ShellInvocation) { Read-Host 'Press Enter to exit' } exit 0; -} \ No newline at end of file +} diff --git a/vnext/Shared/DevSupportManager.cpp b/vnext/Shared/DevSupportManager.cpp index b485fc1b304..b964abcdce2 100644 --- a/vnext/Shared/DevSupportManager.cpp +++ b/vnext/Shared/DevSupportManager.cpp @@ -33,7 +33,6 @@ #include #pragma warning(pop) -#include #include #include @@ -47,61 +46,59 @@ using namespace facebook::react; namespace Microsoft::ReactNative { -std::future> GetJavaScriptFromServerAsync(const std::string &url) { - winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter; - filter.CacheControl().ReadBehavior(winrt::Windows::Web::Http::Filters::HttpCacheReadBehavior::NoCache); - winrt::Windows::Web::Http::HttpClient httpClient(filter); - winrt::Windows::Foundation::Uri uri(Microsoft::Common::Unicode::Utf8ToUtf16(url)); +winrt::Windows::Foundation::IAsyncOperation GetJavaScriptFromServerAsync( + const std::string &url) { + try { + winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter; + filter.CacheControl().ReadBehavior(winrt::Windows::Web::Http::Filters::HttpCacheReadBehavior::NoCache); + winrt::Windows::Web::Http::HttpClient httpClient(filter); + winrt::Windows::Foundation::Uri uri(Microsoft::Common::Unicode::Utf8ToUtf16(url)); - co_await winrt::resume_background(); + co_await winrt::resume_background(); - winrt::Windows::Web::Http::HttpRequestMessage request(winrt::Windows::Web::Http::HttpMethod::Get(), uri); - auto asyncRequest = httpClient.SendRequestAsync(request); + winrt::Windows::Web::Http::HttpRequestMessage request(winrt::Windows::Web::Http::HttpMethod::Get(), uri); + auto asyncRequest = httpClient.SendRequestAsync(request); #ifdef DEFAULT_CPPWINRT_EXCEPTIONS - try { winrt::Windows::Web::Http::HttpResponseMessage response = co_await asyncRequest; - } catch (winrt::hresult_error const &e) { - co_return std::make_pair( - Microsoft::Common::Unicode::Utf16ToUtf8(e.message().c_str(), e.message().size()).c_str(), false); - } #else - co_await lessthrow_await_adapter>{asyncRequest}; - - HRESULT hr = asyncRequest.ErrorCode(); - if (FAILED(hr)) { - std::string error; - if (hr == WININET_E_CANNOT_CONNECT) { - error = fmt::format("A connection with the server {} could not be established.\n\nIs the packager running?", url); - } else { - error = fmt::format("Error 0x{:x} downloading {}.", static_cast(asyncRequest.ErrorCode()), url); + co_await lessthrow_await_adapter>{asyncRequest}; + + HRESULT hr = asyncRequest.ErrorCode(); + if (FAILED(hr)) { + std::string error; + if (hr == WININET_E_CANNOT_CONNECT) { + error = + fmt::format("A connection with the server {} could not be established.\n\nIs the packager running?", url); + } else { + error = fmt::format("Error 0x{:x} downloading {}.", static_cast(asyncRequest.ErrorCode()), url); + } + throw winrt::hresult_error(E_FAIL, winrt::to_hstring(error)); } - co_return std::make_pair(error, false); - } - winrt::Windows::Web::Http::HttpResponseMessage response = asyncRequest.GetResults(); + winrt::Windows::Web::Http::HttpResponseMessage response = asyncRequest.GetResults(); #endif - winrt::Windows::Storage::Streams::IBuffer buffer = co_await response.Content().ReadAsBufferAsync(); - auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(buffer); - - reader.UnicodeEncoding(winrt::Windows::Storage::Streams::UnicodeEncoding::Utf8); - uint32_t len = reader.UnconsumedBufferLength(); - std::string result; - if (len > 0 || response.IsSuccessStatusCode()) { - std::string data; - data.resize(len); - auto buf = reinterpret_cast(data.data()); - static_assert( - sizeof(buf[0]) == sizeof(data[0]), "perf optimization relies on uint8_t and char being the same size"); - reader.ReadBytes(winrt::array_view(buf, buf + len)); - result = std::move(data); - } else { - result = fmt::format("HTTP Error {} downloading {}.", static_cast(response.StatusCode()), url); - } + winrt::Windows::Storage::Streams::IBuffer buffer = co_await response.Content().ReadAsBufferAsync(); + + if (!response.IsSuccessStatusCode()) { + std::string error; + if (buffer.Length() > 0) { + auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(buffer); + error.resize(buffer.Length()); + auto buf = reinterpret_cast(error.data()); + reader.ReadBytes(winrt::array_view(buf, buf + buffer.Length())); + } else { + error = fmt::format("HTTP Error {} downloading {}.", static_cast(response.StatusCode()), url); + } + throw winrt::hresult_error(E_FAIL, winrt::to_hstring(error)); + } - co_return std::make_pair(std::move(result), response.IsSuccessStatusCode()); + co_return buffer; + } catch (winrt::hresult_error const &) { + throw; + } } void LaunchDevTools(const facebook::react::DevSettings &settings) { @@ -185,7 +182,8 @@ std::string GetPackageName(const std::string &bundleAppId) { return packageName; } -std::future PollForLiveReload(const std::string &url) { +winrt::Windows::Foundation::IAsyncOperation PollForLiveReload( + const std::string &url) { winrt::Windows::Web::Http::HttpClient httpClient; winrt::Windows::Foundation::Uri uri(Microsoft::Common::Unicode::Utf8ToUtf16(url)); httpClient.DefaultRequestHeaders().Connection().TryParseAdd(L"keep-alive"); @@ -299,7 +297,16 @@ std::pair GetJavaScriptFromServer( inlineSourceMap, hermesBytecodeVersion); try { - return GetJavaScriptFromServerAsync(bundleUrl).get(); + auto buffer = GetJavaScriptFromServerAsync(bundleUrl).get(); + std::string result(buffer.Length(), '\0'); + if (!result.empty()) { + auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(buffer); + reader.ReadBytes(winrt::array_view{ + reinterpret_cast(&result[0]), reinterpret_cast(&result[result.length()])}); + } + return std::make_pair(std::move(result), true); + } catch (std::exception const &e) { + return std::make_pair(std::string{"Error: "} + e.what(), false); } catch (winrt::hresult_error const &e) { return std::make_pair( "Error: " + Microsoft::Common::Unicode::Utf16ToUtf8(e.message().c_str(), e.message().size()), false); diff --git a/vnext/Shared/DevSupportManager.h b/vnext/Shared/DevSupportManager.h index 216bd6c4932..849ec08203b 100644 --- a/vnext/Shared/DevSupportManager.h +++ b/vnext/Shared/DevSupportManager.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include diff --git a/vnext/Shared/Networking/WinRTWebSocketResource.h b/vnext/Shared/Networking/WinRTWebSocketResource.h index 8ebdd3b14d0..eafb1c6fbe9 100644 --- a/vnext/Shared/Networking/WinRTWebSocketResource.h +++ b/vnext/Shared/Networking/WinRTWebSocketResource.h @@ -27,8 +27,12 @@ class WinRTWebSocketResource2 : public IWebSocketResource, void operator=(const TaskSequencer &) = delete; private: +// `experimental` is deprecated starting Visual Studio 2026 +#if _MSC_VER >= 1951 + using CoroHandle = std::coroutine_handle<>; +#else using CoroHandle = std::experimental::coroutine_handle<>; - +#endif struct Suspender { CoroHandle m_handle; From e6ea0a5be666a84d96296a8d5ebf69d504a05e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Fri, 5 Jun 2026 12:46:42 -0700 Subject: [PATCH 04/23] Upgrade to GoogleTest Adapter 1.8.1.8 (#16218) * Upgrade to GoogleTest adapter 1.8.1.8 * Change files * Update package lock files --- ...ative-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json | 7 +++++++ .../Microsoft.ReactNative.Cxx.UnitTests.vcxproj | 2 +- .../packages.experimentalwinui3.lock.json | 6 +++--- .../Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json | 6 +++--- .../Microsoft.ReactNative.IntegrationTests.vcxproj | 2 +- .../packages.experimentalwinui3.lock.json | 6 +++--- .../packages.lock.json | 6 +++--- vnext/Mso.UnitTests/Mso.UnitTests.vcxproj | 2 +- vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json | 6 +++--- vnext/Mso.UnitTests/packages.lock.json | 6 +++--- vnext/PropertySheets/React.Cpp.props | 4 ++-- vnext/ReactCommon.UnitTests/ReactCommon.UnitTests.vcxproj | 2 +- .../packages.experimentalwinui3.lock.json | 6 +++--- vnext/ReactCommon.UnitTests/packages.lock.json | 6 +++--- 14 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 change/react-native-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json diff --git a/change/react-native-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json b/change/react-native-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json new file mode 100644 index 00000000000..ee31db063aa --- /dev/null +++ b/change/react-native-windows-f8f860c1-f597-4280-aaf2-f3250f196760.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade to GoogleTest adapter 1.8.1.8", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj b/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj index 63f176b1173..455cb5aca32 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj @@ -162,7 +162,7 @@ - + diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.experimentalwinui3.lock.json b/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.experimentalwinui3.lock.json index 37e573dcf94..abb0152cdb0 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.experimentalwinui3.lock.json +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.experimentalwinui3.lock.json @@ -4,9 +4,9 @@ "native,Version=v0.0": { "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.Windows.CppWinRT": { "type": "Direct", diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json b/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json index 3077a54470f..236c8f35428 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/packages.lock.json @@ -4,9 +4,9 @@ "native,Version=v0.0": { "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.Windows.CppWinRT": { "type": "Direct", diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj index 81b39fcd7e9..405d35af50f 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj +++ b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj @@ -190,7 +190,7 @@ + Version="1.8.1.8" /> diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/packages.experimentalwinui3.lock.json b/vnext/Microsoft.ReactNative.IntegrationTests/packages.experimentalwinui3.lock.json index 1bd11c85255..2b405022be2 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/packages.experimentalwinui3.lock.json +++ b/vnext/Microsoft.ReactNative.IntegrationTests/packages.experimentalwinui3.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.VCRTForwarders.140": { "type": "Direct", diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/packages.lock.json b/vnext/Microsoft.ReactNative.IntegrationTests/packages.lock.json index 6a8b1c01e7f..b69d443ce7d 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/packages.lock.json +++ b/vnext/Microsoft.ReactNative.IntegrationTests/packages.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.VCRTForwarders.140": { "type": "Direct", diff --git a/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj b/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj index 1609c75d257..6d90cf64452 100644 --- a/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj +++ b/vnext/Mso.UnitTests/Mso.UnitTests.vcxproj @@ -183,7 +183,7 @@ + Version="1.8.1.8" /> diff --git a/vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json b/vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json index 37e573dcf94..abb0152cdb0 100644 --- a/vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json +++ b/vnext/Mso.UnitTests/packages.experimentalwinui3.lock.json @@ -4,9 +4,9 @@ "native,Version=v0.0": { "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.Windows.CppWinRT": { "type": "Direct", diff --git a/vnext/Mso.UnitTests/packages.lock.json b/vnext/Mso.UnitTests/packages.lock.json index 3077a54470f..236c8f35428 100644 --- a/vnext/Mso.UnitTests/packages.lock.json +++ b/vnext/Mso.UnitTests/packages.lock.json @@ -4,9 +4,9 @@ "native,Version=v0.0": { "Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn": { "type": "Direct", - "requested": "[1.8.1.7, )", - "resolved": "1.8.1.7", - "contentHash": "FxNwT4YpsGdqforqFSTGc5f/e+qfRJ+1wf5G1w0nEEkT5pr5M95E5+fOuswpPUGXPZIXM+M7BSVGnCRcQZjomA==" + "requested": "[1.8.1.8, )", + "resolved": "1.8.1.8", + "contentHash": "RHZUuFTnYzkHPRM5muZfw4SRL/EIHFz7nIkKl1mJKPISvzi1wWtK7k/oJIpw3pWVkdFyEfnBQP8vX1fFpe7Mpw==" }, "Microsoft.Windows.CppWinRT": { "type": "Direct", diff --git a/vnext/PropertySheets/React.Cpp.props b/vnext/PropertySheets/React.Cpp.props index 0b5ce119add..cbddacfb5f0 100644 --- a/vnext/PropertySheets/React.Cpp.props +++ b/vnext/PropertySheets/React.Cpp.props @@ -43,8 +43,8 @@ - - false + + true $(WinUI3ExperimentalVersion) - 1.8.260209005 + 1.8.260508005 false diff --git a/vnext/ReactCommon.UnitTests/packages.lock.json b/vnext/ReactCommon.UnitTests/packages.lock.json index be6d83b337b..eec2729d5e3 100644 --- a/vnext/ReactCommon.UnitTests/packages.lock.json +++ b/vnext/ReactCommon.UnitTests/packages.lock.json @@ -61,27 +61,27 @@ }, "Microsoft.WindowsAppSDK": { "type": "Transitive", - "resolved": "1.8.260209005", - "contentHash": "AGHOiZcrDrpaxpHfEFKlI8MVnibfbSixI5DlbU6ozP/9dyWN5FkTFowg+dEOnaFRCnOzTSAjBQ1HuS4lAO+aMQ==", + "resolved": "1.8.260508005", + "contentHash": "+aA+zrvqJKgsn/1TPOSR0Uy7dkMO5jI/+cDWPu2pWmXe4KQxYkR8gQFY9IrfbgCxxSd/yl1zMcAKD/4HBbNqaw==", "dependencies": { - "Microsoft.WindowsAppSDK.AI": "[1.8.47]", + "Microsoft.WindowsAppSDK.AI": "[1.8.76]", "Microsoft.WindowsAppSDK.Base": "[1.8.251216001]", "Microsoft.WindowsAppSDK.DWrite": "[1.8.25122902]", - "Microsoft.WindowsAppSDK.Foundation": "[1.8.260203002]", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260125001]", - "Microsoft.WindowsAppSDK.ML": "[1.8.2124]", - "Microsoft.WindowsAppSDK.Runtime": "[1.8.260209005]", + "Microsoft.WindowsAppSDK.Foundation": "[1.8.260505001]", + "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260430001]", + "Microsoft.WindowsAppSDK.ML": "[1.8.2197]", + "Microsoft.WindowsAppSDK.Runtime": "[1.8.260508005]", "Microsoft.WindowsAppSDK.Widgets": "[1.8.251231004]", - "Microsoft.WindowsAppSDK.WinUI": "[1.8.260204000]" + "Microsoft.WindowsAppSDK.WinUI": "[1.8.260505002]" } }, "Microsoft.WindowsAppSDK.AI": { "type": "Transitive", - "resolved": "1.8.47", - "contentHash": "9il8KT8WR4T826hnm3M/USZTkPtVXFGE0IztmE1l7H9DPYsa3QHEUgGHFHQg88fsMjdr3vhyMvs23AB+1IYF1w==", + "resolved": "1.8.76", + "contentHash": "Ayn9QybcwzH+c8eQlE7dm2oO3Jrcn2uohLcsHJpCbLOAfcisjzwtSBe0oyulbaJ86R4eDSX3RDS25tsjGpIqyQ==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260126001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260501000" } }, "Microsoft.WindowsAppSDK.Base": { @@ -103,34 +103,34 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } }, "Microsoft.WindowsAppSDK.InteractiveExperiences": { "type": "Transitive", - "resolved": "1.8.260125001", - "contentHash": "CTGFd1zhIDbnOltZ6piPvpNXFR1OaNyW3vHvhaILzpGziAgj5DPuVnU3PUp1p5iOBd382FLCBVM6nEPyu/LCOA==", + "resolved": "1.8.260430001", + "contentHash": "fTPCnQb3ZarMh9khlEfbLDllcZzK0tSP+2S6W/T4cPyLLSAmARm8Gd3AC5kqubnDjzTvG+1lNZ1bZGFsIHplJQ==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001" } }, "Microsoft.WindowsAppSDK.ML": { "type": "Transitive", - "resolved": "1.8.2124", - "contentHash": "l7ZptLbvOWHEJgxZtCQhUzDNCakNcqSJyAa7DNXBLKxGIUMDqq9LnWyYRZZFNQwN7hRfDAR8fEAblP1UHYHGgw==", + "resolved": "1.8.2197", + "contentHash": "6Bc1SOLd5HicY3GbF+zr76YBfH4iZOKeEGxTK/lHKAK2ExZTWavOADxB2CKSi/irF4dWSngUdRFopWPmckJ6fA==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260126001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001" } }, "Microsoft.WindowsAppSDK.Runtime": { "type": "Transitive", - "resolved": "1.8.260209005", - "contentHash": "aZjMu/glUGjzACowzzhj9drn/Ddfp1yA+f7CFXpkiSk6iZ2x32vhKfcqT64RpJ6R+Dj1hl9/79aXFhIavYNj9g==", + "resolved": "1.8.260508005", + "contentHash": "2JqXzA4heHSkkaXJNyEvYtpv2wsEUMhwQzgXmZsFmYSkUXSdeVc6hdHcWoBK1SY9l1Sjd1brLt7h16kIpgh2kA==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001" } @@ -145,13 +145,13 @@ }, "Microsoft.WindowsAppSDK.WinUI": { "type": "Transitive", - "resolved": "1.8.260204000", - "contentHash": "DSpA01+iPXwky4O1uZCrdClSi2aRIYTIhmsTeC1EsJmWBFpSirwNAg4EGHejijV6u4ZVkTdyv3px0Y2P3fp72Q==", + "resolved": "1.8.260505002", + "contentHash": "/hGl6EOmo8aeJR2bYbOmGhOicnJK4EY+u+cQI+jm8H1uXThfMX32DsfKOt9LkOcu8AKX3j5FpiBay8ObUSytIw==", "dependencies": { "Microsoft.Web.WebView2": "1.0.3179.45", "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260203002", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001", + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } }, "common": { @@ -184,7 +184,7 @@ "FollyWin32": "[1.0.0, )", "Microsoft.JavaScript.Hermes": "[0.0.0-2604.21001-94aa5e1d, )", "Microsoft.SourceLink.GitHub": "[1.1.1, )", - "Microsoft.WindowsAppSDK": "[1.8.260209005, )", + "Microsoft.WindowsAppSDK": "[1.8.260508005, )", "ReactCommon": "[1.0.0, )", "ReactNative.V8Jsi.Windows": "[0.71.8, )", "boost": "[1.84.0, )" @@ -206,11 +206,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -222,11 +222,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -238,11 +238,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -254,11 +254,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } } From a240f330178e74ab1b9a7131aabd550767eab952 Mon Sep 17 00:00:00 2001 From: Gordon MacMaster <31481849+gmacmaster@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:16:10 -0400 Subject: [PATCH 09/23] =?UTF-8?q?fix:=20WebSocket=20binaryType=20handling?= =?UTF-8?q?=20=E2=80=94=20stop=20unconditional=20Blob=20interception=20of?= =?UTF-8?q?=20binary=20messages=20(#16173)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * blob support (#8) * blob support * pr comments * pr comments * Update DefaultBlobResource.cpp * Create react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json * Add WebSocketArrayBuffer headless test (#9) * Add WebSocketArrayBuffer headless test * Skip test by default * pr comments * Update overrides (#10) --------- Co-authored-by: Julio César Rocha --- ...-c3827e14-777b-475a-bf00-dc169bf89f3d.json | 7 ++ .../RNTesterHeadlessTests.cpp | 30 ++++++++ .../Modules/IWebSocketModuleContentHandler.h | 15 ++++ vnext/Shared/Modules/WebSocketModule.cpp | 11 ++- .../Shared/Networking/DefaultBlobResource.cpp | 37 +++++++++ vnext/Shared/Networking/DefaultBlobResource.h | 12 +++ vnext/overrides.json | 4 + .../WebSocketArrayBufferTest.js | 76 +++++++++++++++++++ 8 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 change/react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json create mode 100644 vnext/src-win/IntegrationTests/WebSocketArrayBufferTest.js diff --git a/change/react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json b/change/react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json new file mode 100644 index 00000000000..e48ac161fcd --- /dev/null +++ b/change/react-native-windows-c3827e14-777b-475a-bf00-dc169bf89f3d.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Fix WebSocket binaryType handling — stop unconditional Blob interception of binary messages", + "packageName": "react-native-windows", + "email": "gordomacmaster@gmail.com", + "dependentChangeType": "patch" +} \ No newline at end of file diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index f25eb3cdd4d..5948e5b9fb8 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -63,6 +63,36 @@ TEST_CLASS (RNTesterHeadlessTests) { auto status = TestModule::AwaitCompletion(); Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); } + + BEGIN_TEST_METHOD_ATTRIBUTE(WebSocketArrayBuffer) + TEST_IGNORE() + END_TEST_METHOD_ATTRIBUTE() + TEST_METHOD(WebSocketArrayBuffer) { + TestModule::Reset(); + + winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; + bool instanceFailed{false}; + + auto holder = TestReactNativeHostHolder( + L"IntegrationTests/WebSocketArrayBufferTest", + [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { + host.InstanceSettings().InstanceLoaded( + [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { + instanceFailed = args.Failed(); + SetEvent(instanceLoadedEvent.get()); + }); + }); + + WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); + if (instanceFailed) { + auto err = holder.GetLastError(); + auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); + Assert::Fail(msg.c_str()); + } + + auto status = TestModule::AwaitCompletion(); + Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); + } }; } // namespace Microsoft::React::Test diff --git a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h index 4d508603865..83a22841f46 100644 --- a/vnext/Shared/Modules/IWebSocketModuleContentHandler.h +++ b/vnext/Shared/Modules/IWebSocketModuleContentHandler.h @@ -18,11 +18,26 @@ namespace Microsoft::React { struct IWebSocketModuleContentHandler { virtual ~IWebSocketModuleContentHandler() noexcept {} + /// Returns true if this handler should process messages for the given socket. + virtual bool CanHandleSocket(int64_t socketId) noexcept = 0; + virtual void ProcessMessage(std::string &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; virtual void ProcessMessage( std::vector &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; + + /// Check CanHandleSocket() then ProcessMessage() in one call. + /// Returns true if the message was handled. + virtual bool TryProcessMessage( + int64_t socketId, + std::string &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; + + virtual bool TryProcessMessage( + int64_t socketId, + std::vector &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketModule.cpp b/vnext/Shared/Modules/WebSocketModule.cpp index d4fe2e5f566..dbcf3927101 100644 --- a/vnext/Shared/Modules/WebSocketModule.cpp +++ b/vnext/Shared/Modules/WebSocketModule.cpp @@ -84,6 +84,7 @@ shared_ptr WebSocketTurboModule::CreateResource(int64_t id, if (auto prop = propBag.Get(BlobModuleContentHandlerPropertyId())) contentHandler = prop.Value().lock(); + bool handled = false; if (contentHandler) { if (isBinary) { auto buffer = CryptographicBuffer::DecodeFromBase64String(winrt::to_hstring(message)); @@ -91,11 +92,15 @@ shared_ptr WebSocketTurboModule::CreateResource(int64_t id, CryptographicBuffer::CopyToByteArray(buffer, arr); auto data = vector(arr.begin(), arr.end()); - contentHandler->ProcessMessage(std::move(data), args); + handled = contentHandler->TryProcessMessage(id, std::move(data), args); } else { - contentHandler->ProcessMessage(string{message}, args); + handled = contentHandler->TryProcessMessage(id, string{message}, args); } - } else { + } + // When the content handler processes the message, it takes ownership of the + // payload and populates args itself (e.g. as a blob reference), so we only + // fall back to setting args["data"] when no handler claimed the message. + if (!handled) { args["data"] = message; } diff --git a/vnext/Shared/Networking/DefaultBlobResource.cpp b/vnext/Shared/Networking/DefaultBlobResource.cpp index 31fdfd6b061..bd408c72843 100644 --- a/vnext/Shared/Networking/DefaultBlobResource.cpp +++ b/vnext/Shared/Networking/DefaultBlobResource.cpp @@ -221,6 +221,11 @@ BlobWebSocketModuleContentHandler::BlobWebSocketModuleContentHandler(shared_ptr< #pragma region IWebSocketModuleContentHandler +bool BlobWebSocketModuleContentHandler::CanHandleSocket(int64_t socketId) noexcept /*override*/ { + scoped_lock lock{m_mutex}; + return m_socketIds.find(socketId) != m_socketIds.end(); +} + void BlobWebSocketModuleContentHandler::ProcessMessage( string &&message, msrn::JSValueObject ¶ms) noexcept /*override*/ @@ -241,6 +246,38 @@ void BlobWebSocketModuleContentHandler::ProcessMessage( params[blobKeys.Type] = blobKeys.Blob; } +bool BlobWebSocketModuleContentHandler::TryProcessMessage( + int64_t socketId, + string &&message, + msrn::JSValueObject ¶ms) noexcept /*override*/ +{ + scoped_lock lock{m_mutex}; + if (m_socketIds.find(socketId) == m_socketIds.end()) + return false; + + params[blobKeys.Data] = std::move(message); + return true; +} + +bool BlobWebSocketModuleContentHandler::TryProcessMessage( + int64_t socketId, + vector &&message, + msrn::JSValueObject ¶ms) noexcept /*override*/ +{ + scoped_lock lock{m_mutex}; + if (m_socketIds.find(socketId) == m_socketIds.end()) + return false; + + auto blob = msrn::JSValueObject{ + {blobKeys.Offset, 0}, + {blobKeys.Size, message.size()}, + {blobKeys.BlobId, m_blobPersistor->StoreMessage(std::move(message))}}; + + params[blobKeys.Data] = std::move(blob); + params[blobKeys.Type] = blobKeys.Blob; + return true; +} + #pragma endregion IWebSocketModuleContentHandler void BlobWebSocketModuleContentHandler::Register(int64_t socketID) noexcept { diff --git a/vnext/Shared/Networking/DefaultBlobResource.h b/vnext/Shared/Networking/DefaultBlobResource.h index 4dfdf5f18aa..9b268912462 100644 --- a/vnext/Shared/Networking/DefaultBlobResource.h +++ b/vnext/Shared/Networking/DefaultBlobResource.h @@ -51,11 +51,23 @@ class BlobWebSocketModuleContentHandler final : public IWebSocketModuleContentHa #pragma region IWebSocketModuleContentHandler + bool CanHandleSocket(int64_t socketId) noexcept override; + void ProcessMessage(std::string &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; void ProcessMessage(std::vector &&message, winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; + bool TryProcessMessage( + int64_t socketId, + std::string &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; + + bool TryProcessMessage( + int64_t socketId, + std::vector &&message, + winrt::Microsoft::ReactNative::JSValueObject ¶ms) noexcept override; + #pragma endregion IWebSocketModuleContentHandler void Register(int64_t socketID) noexcept; diff --git a/vnext/overrides.json b/vnext/overrides.json index b84e6d9ecea..042d9ca817a 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -340,6 +340,10 @@ "type": "platform", "file": "src-win/IntegrationTests/websocket_integration_test_server_blob.js" }, + { + "type": "platform", + "file": "src-win/IntegrationTests/WebSocketArrayBufferTest.js" + }, { "type": "platform", "file": "src-win/IntegrationTests/WebSocketBinaryTest.js" diff --git a/vnext/src-win/IntegrationTests/WebSocketArrayBufferTest.js b/vnext/src-win/IntegrationTests/WebSocketArrayBufferTest.js new file mode 100644 index 00000000000..50360b90b55 --- /dev/null +++ b/vnext/src-win/IntegrationTests/WebSocketArrayBufferTest.js @@ -0,0 +1,76 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +'use strict'; + +const {TurboModuleRegistry} = require('react-native'); +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} + +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; + +const socket = new WebSocket(WS_URL); +socket.binaryType = 'arraybuffer'; + +socket.addEventListener('open', () => { + socket.send('hello'); +}); + +socket.addEventListener('message', event => { + const data = event.data; + + if (!(data instanceof ArrayBuffer)) { + console.log( + 'WebSocketArrayBufferTest FAIL: expected ArrayBuffer, got ' + typeof data, + ); + TestModule.markTestPassed(false); + socket.close(); + return; + } + + const bytes = new Uint8Array(data); + const expected = new Uint8Array([4, 5, 6, 7]); + + if (bytes.length !== expected.length) { + console.log( + 'WebSocketArrayBufferTest FAIL: expected ' + + expected.length + + ' bytes, got ' + + bytes.length, + ); + TestModule.markTestPassed(false); + socket.close(); + return; + } + + for (let i = 0; i < expected.length; i++) { + if (bytes[i] !== expected[i]) { + console.log( + 'WebSocketArrayBufferTest FAIL: byte[' + + i + + '] expected ' + + expected[i] + + ' got ' + + bytes[i], + ); + TestModule.markTestPassed(false); + socket.close(); + return; + } + } + + TestModule.markTestPassed(true); + socket.close(); +}); + +socket.addEventListener('error', () => { + console.log('WebSocketArrayBufferTest FAIL: WebSocket error'); + TestModule.markTestPassed(false); +}); From fb08ea213cedb94d95bc03123d2040660f584c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Mon, 8 Jun 2026 14:28:53 -0700 Subject: [PATCH 10/23] Upgrade projects to .NET 10 (#16226) * Upgrade projects to .NET 10 * Upgrade lock files * Change files --- ...-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json | 7 +++++++ .../Microsoft.ReactNative.CsWinRT.csproj | 2 +- .../packages.experimentalwinui3.lock.json | 2 +- .../packages.lock.json | 2 +- ...actNative.Managed.CodeGen.UnitTests.csproj | 2 +- ...crosoft.ReactNative.Managed.CodeGen.csproj | 20 +++++++++---------- .../PublishProfiles/DeployAsTool-Debug.pubxml | 2 +- .../DeployAsTool-Release.pubxml | 2 +- .../Microsoft.ReactNative.Test.Website.csproj | 2 +- vnext/TestWebSite/packages.lock.json | 2 +- 10 files changed, 25 insertions(+), 18 deletions(-) create mode 100644 change/react-native-windows-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json diff --git a/change/react-native-windows-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json b/change/react-native-windows-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json new file mode 100644 index 00000000000..fae64f74775 --- /dev/null +++ b/change/react-native-windows-2fe39775-b224-4fbb-bf3c-af9bccca23a5.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Upgrade projects to .NET 10", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative.CsWinRT/Microsoft.ReactNative.CsWinRT.csproj b/vnext/Microsoft.ReactNative.CsWinRT/Microsoft.ReactNative.CsWinRT.csproj index 87b3d1e7fef..b96910490cb 100644 --- a/vnext/Microsoft.ReactNative.CsWinRT/Microsoft.ReactNative.CsWinRT.csproj +++ b/vnext/Microsoft.ReactNative.CsWinRT/Microsoft.ReactNative.CsWinRT.csproj @@ -2,7 +2,7 @@ - net8.0-windows10.0.22621.0 + net10.0-windows10.0.22621.0 10.0.22621.0 10.0.18362.0 x86;x64;arm64 diff --git a/vnext/Microsoft.ReactNative.CsWinRT/packages.experimentalwinui3.lock.json b/vnext/Microsoft.ReactNative.CsWinRT/packages.experimentalwinui3.lock.json index f40a5556ae0..10a1900a1c2 100644 --- a/vnext/Microsoft.ReactNative.CsWinRT/packages.experimentalwinui3.lock.json +++ b/vnext/Microsoft.ReactNative.CsWinRT/packages.experimentalwinui3.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0-windows10.0.22621": { + "net10.0-windows10.0.22621": { "Microsoft.Windows.CsWinRT": { "type": "Direct", "requested": "[2.2.0, )", diff --git a/vnext/Microsoft.ReactNative.CsWinRT/packages.lock.json b/vnext/Microsoft.ReactNative.CsWinRT/packages.lock.json index 36b0d0c96dc..22486130110 100644 --- a/vnext/Microsoft.ReactNative.CsWinRT/packages.lock.json +++ b/vnext/Microsoft.ReactNative.CsWinRT/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0-windows10.0.22621": { + "net10.0-windows10.0.22621": { "Microsoft.Windows.CsWinRT": { "type": "Direct", "requested": "[2.2.0, )", diff --git a/vnext/Microsoft.ReactNative.Managed.CodeGen.UnitTests/Microsoft.ReactNative.Managed.CodeGen.UnitTests.csproj b/vnext/Microsoft.ReactNative.Managed.CodeGen.UnitTests/Microsoft.ReactNative.Managed.CodeGen.UnitTests.csproj index 8d5b5fa22a1..4e3c069ddf6 100644 --- a/vnext/Microsoft.ReactNative.Managed.CodeGen.UnitTests/Microsoft.ReactNative.Managed.CodeGen.UnitTests.csproj +++ b/vnext/Microsoft.ReactNative.Managed.CodeGen.UnitTests/Microsoft.ReactNative.Managed.CodeGen.UnitTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 x64 win-x64 false diff --git a/vnext/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj b/vnext/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj index 6b18c3aeecb..bccc8d80bdf 100644 --- a/vnext/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj +++ b/vnext/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj @@ -3,7 +3,7 @@ Exe - net8.0 + net10.0 x64;x86;ARM64 win-x86;win-x64 @@ -31,8 +31,8 @@ - @@ -53,7 +53,7 @@ - @@ -62,13 +62,13 @@ - - diff --git a/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml b/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml index c6c28996632..910d6f9f7d6 100644 --- a/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml +++ b/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml @@ -6,7 +6,7 @@ FileSystem Debug x64 - net8.0 + net10.0 $(OutDir)publish win-x64 true diff --git a/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml b/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml index 373853ddc0e..9973e782006 100644 --- a/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml +++ b/vnext/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml @@ -6,7 +6,7 @@ FileSystem Release x64 - net8.0 + net10.0 $(OutDir)publish win-x64 true diff --git a/vnext/TestWebSite/Microsoft.ReactNative.Test.Website.csproj b/vnext/TestWebSite/Microsoft.ReactNative.Test.Website.csproj index 7d08141d444..0987f9be3a6 100644 --- a/vnext/TestWebSite/Microsoft.ReactNative.Test.Website.csproj +++ b/vnext/TestWebSite/Microsoft.ReactNative.Test.Website.csproj @@ -3,7 +3,7 @@ AnyCPU AnyCPU - net8.0 + net10.0 enable enable $(IntDir)$(TargetFramework)\ diff --git a/vnext/TestWebSite/packages.lock.json b/vnext/TestWebSite/packages.lock.json index 807ab822b67..4a91a8cd78f 100644 --- a/vnext/TestWebSite/packages.lock.json +++ b/vnext/TestWebSite/packages.lock.json @@ -1,6 +1,6 @@ { "version": 1, "dependencies": { - "net8.0": {} + "net10.0": {} } } \ No newline at end of file From 35971ecac884ecef08d839d2e6a1fee139ec77d1 Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 17:01:46 -0700 Subject: [PATCH 11/23] Update yarn lock --- vnext/Desktop.ABITests/packages.lock.json | 76 +++++++++++------------ yarn.lock | 5 ++ 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/vnext/Desktop.ABITests/packages.lock.json b/vnext/Desktop.ABITests/packages.lock.json index 02db5a631c9..d3882cbd963 100644 --- a/vnext/Desktop.ABITests/packages.lock.json +++ b/vnext/Desktop.ABITests/packages.lock.json @@ -60,27 +60,27 @@ }, "Microsoft.WindowsAppSDK": { "type": "Transitive", - "resolved": "1.8.260209005", - "contentHash": "AGHOiZcrDrpaxpHfEFKlI8MVnibfbSixI5DlbU6ozP/9dyWN5FkTFowg+dEOnaFRCnOzTSAjBQ1HuS4lAO+aMQ==", + "resolved": "1.8.260508005", + "contentHash": "+aA+zrvqJKgsn/1TPOSR0Uy7dkMO5jI/+cDWPu2pWmXe4KQxYkR8gQFY9IrfbgCxxSd/yl1zMcAKD/4HBbNqaw==", "dependencies": { - "Microsoft.WindowsAppSDK.AI": "[1.8.47]", + "Microsoft.WindowsAppSDK.AI": "[1.8.76]", "Microsoft.WindowsAppSDK.Base": "[1.8.251216001]", "Microsoft.WindowsAppSDK.DWrite": "[1.8.25122902]", - "Microsoft.WindowsAppSDK.Foundation": "[1.8.260203002]", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260125001]", - "Microsoft.WindowsAppSDK.ML": "[1.8.2124]", - "Microsoft.WindowsAppSDK.Runtime": "[1.8.260209005]", + "Microsoft.WindowsAppSDK.Foundation": "[1.8.260505001]", + "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260430001]", + "Microsoft.WindowsAppSDK.ML": "[1.8.2197]", + "Microsoft.WindowsAppSDK.Runtime": "[1.8.260508005]", "Microsoft.WindowsAppSDK.Widgets": "[1.8.251231004]", - "Microsoft.WindowsAppSDK.WinUI": "[1.8.260204000]" + "Microsoft.WindowsAppSDK.WinUI": "[1.8.260505002]" } }, "Microsoft.WindowsAppSDK.AI": { "type": "Transitive", - "resolved": "1.8.47", - "contentHash": "9il8KT8WR4T826hnm3M/USZTkPtVXFGE0IztmE1l7H9DPYsa3QHEUgGHFHQg88fsMjdr3vhyMvs23AB+1IYF1w==", + "resolved": "1.8.76", + "contentHash": "Ayn9QybcwzH+c8eQlE7dm2oO3Jrcn2uohLcsHJpCbLOAfcisjzwtSBe0oyulbaJ86R4eDSX3RDS25tsjGpIqyQ==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260126001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260501000" } }, "Microsoft.WindowsAppSDK.Base": { @@ -102,34 +102,34 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } }, "Microsoft.WindowsAppSDK.InteractiveExperiences": { "type": "Transitive", - "resolved": "1.8.260125001", - "contentHash": "CTGFd1zhIDbnOltZ6piPvpNXFR1OaNyW3vHvhaILzpGziAgj5DPuVnU3PUp1p5iOBd382FLCBVM6nEPyu/LCOA==", + "resolved": "1.8.260430001", + "contentHash": "fTPCnQb3ZarMh9khlEfbLDllcZzK0tSP+2S6W/T4cPyLLSAmARm8Gd3AC5kqubnDjzTvG+1lNZ1bZGFsIHplJQ==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001" } }, "Microsoft.WindowsAppSDK.ML": { "type": "Transitive", - "resolved": "1.8.2124", - "contentHash": "l7ZptLbvOWHEJgxZtCQhUzDNCakNcqSJyAa7DNXBLKxGIUMDqq9LnWyYRZZFNQwN7hRfDAR8fEAblP1UHYHGgw==", + "resolved": "1.8.2197", + "contentHash": "6Bc1SOLd5HicY3GbF+zr76YBfH4iZOKeEGxTK/lHKAK2ExZTWavOADxB2CKSi/irF4dWSngUdRFopWPmckJ6fA==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260126001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001" } }, "Microsoft.WindowsAppSDK.Runtime": { "type": "Transitive", - "resolved": "1.8.260209005", - "contentHash": "aZjMu/glUGjzACowzzhj9drn/Ddfp1yA+f7CFXpkiSk6iZ2x32vhKfcqT64RpJ6R+Dj1hl9/79aXFhIavYNj9g==", + "resolved": "1.8.260508005", + "contentHash": "2JqXzA4heHSkkaXJNyEvYtpv2wsEUMhwQzgXmZsFmYSkUXSdeVc6hdHcWoBK1SY9l1Sjd1brLt7h16kIpgh2kA==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001" } @@ -144,13 +144,13 @@ }, "Microsoft.WindowsAppSDK.WinUI": { "type": "Transitive", - "resolved": "1.8.260204000", - "contentHash": "DSpA01+iPXwky4O1uZCrdClSi2aRIYTIhmsTeC1EsJmWBFpSirwNAg4EGHejijV6u4ZVkTdyv3px0Y2P3fp72Q==", + "resolved": "1.8.260505002", + "contentHash": "/hGl6EOmo8aeJR2bYbOmGhOicnJK4EY+u+cQI+jm8H1uXThfMX32DsfKOt9LkOcu8AKX3j5FpiBay8ObUSytIw==", "dependencies": { "Microsoft.Web.WebView2": "1.0.3179.45", "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.Foundation": "1.8.260203002", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001", + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } }, "ReactNative.V8Jsi.Windows": { @@ -188,7 +188,7 @@ "FollyWin32": "[1.0.0, )", "Microsoft.JavaScript.Hermes": "[0.0.0-2604.21001-94aa5e1d, )", "Microsoft.SourceLink.GitHub": "[1.1.1, )", - "Microsoft.WindowsAppSDK": "[1.8.260209005, )", + "Microsoft.WindowsAppSDK": "[1.8.260508005, )", "ReactCommon": "[1.0.0, )", "ReactNative.V8Jsi.Windows": "[0.71.8, )", "boost": "[1.84.0, )" @@ -220,11 +220,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -236,11 +236,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -252,11 +252,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } }, @@ -268,11 +268,11 @@ }, "Microsoft.WindowsAppSDK.Foundation": { "type": "Transitive", - "resolved": "1.8.260203002", - "contentHash": "eKQ/prWq98mW7+E+ffot47iZNbDnq/NVN9R9Gi8vmoU/3Ka6zNNivxdICXh6j7g6REFPCV9530/nQYQC0L3fwg==", + "resolved": "1.8.260505001", + "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==", "dependencies": { "Microsoft.WindowsAppSDK.Base": "1.8.251216001", - "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260125001" + "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001" } } } diff --git a/yarn.lock b/yarn.lock index e04c8d9bd55..2959fab6314 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10845,6 +10845,11 @@ prettier@^3.0.0, prettier@^3.4.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== +prettier@^3.6.2: + version "3.8.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0" + integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw== + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" From a1a7b2aea4550f10893849d8f2ba32675cca8e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Rocha?= Date: Mon, 8 Jun 2026 17:30:50 -0700 Subject: [PATCH 12/23] Re-introduce JS Desktop integration tests (#16228) * Remove redundant condition Desktop.IntegrationTests.SkipRNTester Modify Desktop.IntegrationTests.Filter instead. * Re-introduce WebSocket and WebSocketBlob tests * Make WSBlob test compare contents * Rename method to RunTest * Handle initial message from default endpoint * Change files * Migrate XHR test * Port Fetch test * Port Blob test * WebSocketMultipleSendTest * Remove JS WebSocket servers * Skip in CI for now --- .ado/build-template.yml | 1 + .ado/jobs/desktop-single.yml | 6 - ...-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json | 7 + .../RNTesterHeadlessTests.cpp | 104 ++++---- vnext/TestWebSite/wwwroot/static/sample.txt | 2 +- vnext/overrides.json | 12 +- vnext/src-win/IntegrationTests/BlobTest.js | 161 +++++------- vnext/src-win/IntegrationTests/FetchTest.js | 57 ++--- .../IntegrationTests/WebSocketBlobTest.js | 232 ++++++++---------- .../WebSocketMultipleSendTest.js | 199 ++++++--------- .../src-win/IntegrationTests/WebSocketTest.js | 82 +++++++ vnext/src-win/IntegrationTests/XHRTest.js | 89 ++----- ...ebsocket_integration_test_server_binary.js | 33 --- .../websocket_integration_test_server_blob.js | 33 --- 14 files changed, 423 insertions(+), 595 deletions(-) create mode 100644 change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json create mode 100644 vnext/src-win/IntegrationTests/WebSocketTest.js delete mode 100644 vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js delete mode 100644 vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js diff --git a/.ado/build-template.yml b/.ado/build-template.yml index 3b4638f825d..9b0ced95c05 100644 --- a/.ado/build-template.yml +++ b/.ado/build-template.yml @@ -281,6 +281,7 @@ extends: #12714 - Disable for first deployment of test website. - name: Desktop.IntegrationTests.Filter value: > + (FullyQualifiedName!~Microsoft::React::Test::RNTesterHeadlessTests)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectClose)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectNoClose)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveClose)& diff --git a/.ado/jobs/desktop-single.yml b/.ado/jobs/desktop-single.yml index 38d615d27b3..a733ee8cbd7 100644 --- a/.ado/jobs/desktop-single.yml +++ b/.ado/jobs/desktop-single.yml @@ -59,12 +59,6 @@ steps: - template: ../templates/apply-published-version-vars.yml - - ${{ if eq(variables['Desktop.IntegrationTests.SkipRNTester'], true) }}: - - pwsh: | - $newValue = '(FullyQualifiedName!~Microsoft::React::Test::RNTesterHeadlessTests::)&' + "$(Desktop.IntegrationTests.Filter)" - Write-Host "##vso[task.setvariable variable=Desktop.IntegrationTests.Filter]$newValue" - displayName: Update Desktop.IntegrationTests.Filter to exclude RNTester integration tests - - template: ../templates/msbuild-sln.yml parameters: solutionDir: vnext diff --git a/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json b/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json new file mode 100644 index 00000000000..dc96d8037a6 --- /dev/null +++ b/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Re-introduce WebSocket JS integration tests", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index 5948e5b9fb8..c819059c280 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "Modules/TestModule.h" #include "TestReactNativeHostHolder.h" @@ -17,6 +18,35 @@ namespace msrn = winrt::Microsoft::ReactNative; namespace Microsoft::React::Test { +namespace { +void RunTest(std::wstring_view jsBundle) { + TestModule::Reset(); + + winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; + bool instanceFailed{false}; + + auto holder = TestReactNativeHostHolder( + jsBundle, [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { + host.InstanceSettings().InstanceLoaded( + [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { + instanceFailed = args.Failed(); + SetEvent(instanceLoadedEvent.get()); + }); + }); + + // First, wait for instance to load + WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); + if (instanceFailed) { + auto err = holder.GetLastError(); + auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); + Assert::Fail(msg.c_str()); + } + + auto status = TestModule::AwaitCompletion(); + Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); +} +} // namespace + TEST_CLASS (RNTesterHeadlessTests) { TEST_CLASS_INITIALIZE(Initialize) { // https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/win32/mddbootstrap/nf-mddbootstrap-mddbootstrapinitialize2 @@ -37,61 +67,35 @@ TEST_CLASS (RNTesterHeadlessTests) { } TEST_METHOD(Dummy) { - TestModule::Reset(); - - winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; - bool instanceFailed{false}; - - auto holder = TestReactNativeHostHolder( - L"IntegrationTests/DummyTest", - [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { - host.InstanceSettings().InstanceLoaded( - [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { - instanceFailed = args.Failed(); - SetEvent(instanceLoadedEvent.get()); - }); - }); - - // First, wait for instance to load - WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); - if (instanceFailed) { - auto err = holder.GetLastError(); - auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); - Assert::Fail(msg.c_str()); - } + RunTest(L"IntegrationTests/DummyTest"); + } + + TEST_METHOD(Fetch) { + RunTest(L"IntegrationTests/FetchTest"); + } - auto status = TestModule::AwaitCompletion(); - Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); + TEST_METHOD(XHR) { + RunTest(L"IntegrationTests/XHRTest"); + } + + TEST_METHOD(WebSocket) { + RunTest(L"IntegrationTests/WebSocketTest"); + } + + TEST_METHOD(WebSocketBlob) { + RunTest(L"IntegrationTests/WebSocketBlobTest"); } - BEGIN_TEST_METHOD_ATTRIBUTE(WebSocketArrayBuffer) - TEST_IGNORE() - END_TEST_METHOD_ATTRIBUTE() TEST_METHOD(WebSocketArrayBuffer) { - TestModule::Reset(); - - winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; - bool instanceFailed{false}; - - auto holder = TestReactNativeHostHolder( - L"IntegrationTests/WebSocketArrayBufferTest", - [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { - host.InstanceSettings().InstanceLoaded( - [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { - instanceFailed = args.Failed(); - SetEvent(instanceLoadedEvent.get()); - }); - }); - - WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); - if (instanceFailed) { - auto err = holder.GetLastError(); - auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); - Assert::Fail(msg.c_str()); - } + RunTest(L"IntegrationTests/WebSocketArrayBufferTest"); + } + + TEST_METHOD(WebSocketMultipleSend) { + RunTest(L"IntegrationTests/WebSocketMultipleSendTest"); + } - auto status = TestModule::AwaitCompletion(); - Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); + TEST_METHOD(Blob) { + RunTest(L"IntegrationTests/BlobTest"); } }; diff --git a/vnext/TestWebSite/wwwroot/static/sample.txt b/vnext/TestWebSite/wwwroot/static/sample.txt index 5bd282c8887..47e646f426f 100644 --- a/vnext/TestWebSite/wwwroot/static/sample.txt +++ b/vnext/TestWebSite/wwwroot/static/sample.txt @@ -1 +1 @@ -Sample Static Text File +Sample Static Text File \ No newline at end of file diff --git a/vnext/overrides.json b/vnext/overrides.json index 042d9ca817a..fa246816954 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -332,14 +332,6 @@ "type": "platform", "file": "src-win/IntegrationTests/LoggingTest.js" }, - { - "type": "platform", - "file": "src-win/IntegrationTests/websocket_integration_test_server_binary.js" - }, - { - "type": "platform", - "file": "src-win/IntegrationTests/websocket_integration_test_server_blob.js" - }, { "type": "platform", "file": "src-win/IntegrationTests/WebSocketArrayBufferTest.js" @@ -352,6 +344,10 @@ "type": "platform", "file": "src-win/IntegrationTests/WebSocketBlobTest.js" }, + { + "type": "platform", + "file": "src-win/IntegrationTests/WebSocketTest.js" + }, { "type": "platform", "file": "src-win/IntegrationTests/WebSocketMultipleSendTest.js" diff --git a/vnext/src-win/IntegrationTests/BlobTest.js b/vnext/src-win/IntegrationTests/BlobTest.js index 47cf5ecb81e..363ee3c99f7 100644 --- a/vnext/src-win/IntegrationTests/BlobTest.js +++ b/vnext/src-win/IntegrationTests/BlobTest.js @@ -2,115 +2,78 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -type State = { - statusCode: number, - xhr: XMLHttpRequest, - expected: string, -}; - -class BlobTest extends React.Component<{...}, State> { - state: State = { - statusCode: 0, - xhr: new XMLHttpRequest(), - // http://localhost:5555/static/react.png - // cspell:disable - expected: - 'data:application/octet-stream;base64,' + - 'iVBORw0KGgoAAAANSUhEUgAAABcAAAAVCAYAAACt4nWrAAABbWlDQ1BpY2MAACiRdZG9S0JR' + - 'GMZ/alGk4VBDRJCDRYNCFEhj2dAiIWaQ1aLXr0Cvl3uViNagpUFoiFr6GvoPag1aC4KgCCLa' + - '2vtaQm7vUUGJOpdz3x/POc/Le58LzkhBK1od01DUy2ZsLuxbSiz7ul5x48RFiOGkZhkz0WiE' + - 'f9fXPQ5V74Kq1//3/lzudMbSwNEtHNIMsyws0xBZLxuKd4T7tXwyLXwkHDBlQOFrpaca/KI4' + - '1+APxWY8NgtO1dOXa+NUG2t5syg8JuwvFipacx71JZ6MvrggdVD2EBYx5gjjI0WFNQqUCUrV' + - 'JbO/feN13zwl8WjyNtjAFEeOvHgDolaka0ZqVvSMPAU2VO6/87SykxON7p4wdD7b9vsIdO1C' + - 'rWrb38e2XTsB1xNc6i1/SXKa+hS92tL8h+DdgvOrlpbag4ttGHg0kmayLrlkO7NZeDuD3gT0' + - '3ULPSiOr5jmnDxDflF90A/sHMCr3vas/DkpoELlQjWUAAAAJcEhZcwAAnXsAAJ17ATyfd8QA' + - 'AARxSURBVDgRbVRbbFRVFN373Hs7fSBorI/S2FLK9GETQgHjBwlg+fABQT78METKTBtrookR' + - 'QgJFDcUP2wRiUKMfVZnCxBq1Rk0MyA81fjQxhQKJ2naGUDum8a1AW9qZe+/ZrjP0NreOJzmz' + - 'z36te86avTfT/6y2tHtIhPbD5WIPiGN399Xwr0FobELuZ9frhP4UtsNMb5yMOj2BP5AqOASy' + - 'PZ1rBvBeYrsJoE2wT3POuxRPeTtMjJFGx/Fm3m/iEB9L5dYZf3jZYcWcRXOLECX76viPBd8r' + - 'e9PZj5SogXja3Uoi20npbYlo5McgNzbuJhXxNuiXA5uRBTfXROWK83Qsxp2KRn4QVm+T8IvE' + - '6q0wsAkCLZ7JW0xYOBSAK0U3NFNpOBA33sKiO4W87XjaYaOH/SxUavLCNnMuoIU0X1GsW48M' + - 'ij1Zla1R2qoxNIEOPN1ai/PHxPRJLOU+Q5b/U3UmMvEzu/Wa+dR/wTlsiI/PrRZVtBO37AbA' + - 'LAn9gl2Od8/g6YP4R6ZBwh0iukWEy2D7EwAV+GAZaOu0/dyX7zeUTASYefD2a1KtXfcYAB9i' + - '5n4A7mCL9vs+r2CSHqfMXtdbybeCpD1XpMwu8S8D9CB5Mq0sOU6KvxKR3cgd9mznQLKWM6pt' + - 'LLfR9/xBVjQkjtOIen0ZnPeLx0/iy8eEqaN3Jc3Fx92j2BeMTK6lW6ykw/jZ4p3C/KHJM/kG' + - 'x/L9bwwux1PuCGk6kGhwzgc3ax2Vuy3Lvwaev0/UO5vaUrmYECcCP14TP1lX1BdPe0OIafJ9' + - 'e/XpRv4r8MfH3BbU4XFTLdUSsUcCh5GnG+hviCwAM0aHrDUyWCE9A2d2IT5w0wJeFaMBulFG' + - 'DbayY721nC8nlNqjSHod0atUzo761vxytuzvoN+L/bv43sOaiqcty0uBmklQ15mIOucMOkbD' + - 'ncrzElrTGJuSy1R6XbA/Syw9YjsJdt0TLOo8kjbB7ifq7OfzSb7XrC37EubMdYyBd+GzcIkh' + - 'xG0lx9pHOa8NFXQQ9veqpuyufLVAofar82t8sV9CQzyGRqkSpZIW6Ytacxca+TVluV9ridxQ' + - 'nF3ha/U4C79qKTnikdrIWu9B52ZQbWcVe29+sKb4qsFcBDeKWfF09kES1S8svaTVejDeiNts' + - 'QOgUzsshb+IPrcSwugjuRzFnRnD753ySp5N1kdHbKLd/CzoUN9qM+j3XFy0yz84vVNQJIn3P' + - 'TNRpXZZ2k5r4t756Z1/gR7eucoi2QF8CXjBbkLgSN7keJBo5U2J3YmhVLEt7F9Ct982W2IfD' + - 'fhOPjq1YYoNSAA7O0ckyFw789AGeA4HDoKca9uG8Hgow8eAXD166CsANj4hqDsJax6UStHwG' + - 'fb1datcBYgNoGDD2IIY1NePFY4EeyII/tGNKSt1ZbwQz5lst5LPILtz6HZRWz9FH2Fso3UP4' + - 'yAto+y8w+y3MlM2grrngRcFXwrJjXMpdcnexUnPzRdaZ/mr+J+w3592Tcldxzn9CtC7xtfN5' + - 'uP2D2H8BsGL5W7blULYAAAAASUVORK5CYII=', - // cspell:enable - }; +if (!TestModule) { + throw new Error('TestModule is not available'); +} - _get = () => { - this.state.xhr.onloadend = () => { - this.setState({ - statusCode: this.state.xhr.status, - }); - }; - this.state.xhr.open('GET', 'http://localhost:5555/static/react.png'); - this.state.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); - this.state.xhr.responseType = 'blob'; - this.state.xhr.send(); - }; +const URL = 'http://localhost:5555/static/react.png'; - _getSucceeded = (): boolean => { - return this.state.statusCode === 200 && this.state.xhr.response !== null; - }; +// cspell:disable +const EXPECTED_DATA_URL = + 'data:application/octet-stream;base64,' + + 'iVBORw0KGgoAAAANSUhEUgAAABcAAAAVCAYAAACt4nWrAAABbWlDQ1BpY2MAACiRdZG9S0JR' + + 'GMZ/alGk4VBDRJCDRYNCFEhj2dAiIWaQ1aLXr0Cvl3uViNagpUFoiFr6GvoPag1aC4KgCCLa' + + '2vtaQm7vUUGJOpdz3x/POc/Le58LzkhBK1od01DUy2ZsLuxbSiz7ul5x48RFiOGkZhkz0WiE' + + 'f9fXPQ5V74Kq1//3/lzudMbSwNEtHNIMsyws0xBZLxuKd4T7tXwyLXwkHDBlQOFrpaca/KI4' + + '1+APxWY8NgtO1dOXa+NUG2t5syg8JuwvFipacx71JZ6MvrggdVD2EBYx5gjjI0WFNQqUCUrV' + + 'JbO/feN13zwl8WjyNtjAFEeOvHgDolaka0ZqVvSMPAU2VO6/87SykxON7p4wdD7b9vsIdO1C' + + 'rWrb38e2XTsB1xNc6i1/SXKa+hS92tL8h+DdgvOrlpbag4ttGHg0kmayLrlkO7NZeDuD3gT0' + + '3ULPSiOr5jmnDxDflF90A/sHMCr3vas/DkpoELlQjWUAAAAJcEhZcwAAnXsAAJ17ATyfd8QA' + + 'AARxSURBVDgRbVRbbFRVFN373Hs7fSBorI/S2FLK9GETQgHjBwlg+fABQT78METKTBtrookR' + + 'QgJFDcUP2wRiUKMfVZnCxBq1Rk0MyA81fjQxhQKJ2naGUDum8a1AW9qZe+/ZrjP0NreOJzmz' + + 'z36te86avTfT/6y2tHtIhPbD5WIPiGN399Xwr0FobELuZ9frhP4UtsNMb5yMOj2BP5AqOASy' + + 'PZ1rBvBeYrsJoE2wT3POuxRPeTtMjJFGx/Fm3m/iEB9L5dYZf3jZYcWcRXOLECX76viPBd8r' + + 'e9PZj5SogXja3Uoi20npbYlo5McgNzbuJhXxNuiXA5uRBTfXROWK83Qsxp2KRn4QVm+T8IvE' + + '6q0wsAkCLZ7JW0xYOBSAK0U3NFNpOBA33sKiO4W87XjaYaOH/SxUavLCNnMuoIU0X1GsW48M' + + 'ij1Zla1R2qoxNIEOPN1ai/PHxPRJLOU+Q5b/U3UmMvEzu/Wa+dR/wTlsiI/PrRZVtBO37AbA' + + 'LAn9gl2Od8/g6YP4R6ZBwh0iukWEy2D7EwAV+GAZaOu0/dyX7zeUTASYefD2a1KtXfcYAB9i' + + '5n4A7mCL9vs+r2CSHqfMXtdbybeCpD1XpMwu8S8D9CB5Mq0sOU6KvxKR3cgd9mznQLKWM6pt' + + 'LLfR9/xBVjQkjtOIen0ZnPeLx0/iy8eEqaN3Jc3Fx92j2BeMTK6lW6ykw/jZ4p3C/KHJM/kG' + + 'x/L9bwwux1PuCGk6kGhwzgc3ax2Vuy3Lvwaev0/UO5vaUrmYECcCP14TP1lX1BdPe0OIafJ9' + + 'e/XpRv4r8MfH3BbU4XFTLdUSsUcCh5GnG+hviCwAM0aHrDUyWCE9A2d2IT5w0wJeFaMBulFG' + + 'DbayY721nC8nlNqjSHod0atUzo761vxytuzvoN+L/bv43sOaiqcty0uBmklQ15mIOucMOkbD' + + 'ncrzElrTGJuSy1R6XbA/Syw9YjsJdt0TLOo8kjbB7ifq7OfzSb7XrC37EubMdYyBd+GzcIkh' + + 'xG0lx9pHOa8NFXQQ9veqpuyufLVAofar82t8sV9CQzyGRqkSpZIW6Ytacxca+TVluV9ridxQ' + + 'nF3ha/U4C79qKTnikdrIWu9B52ZQbWcVe29+sKb4qsFcBDeKWfF09kES1S8svaTVejDeiNts' + + 'QOgUzsshb+IPrcSwugjuRzFnRnD753ySp5N1kdHbKLd/CzoUN9qM+j3XFy0yz84vVNQJIn3P' + + 'TNRpXZZ2k5r4t756Z1/gR7eucoi2QF8CXjBbkLgSN7keJBo5U2J3YmhVLEt7F9Ct982W2IfD' + + 'fhOPjq1YYoNSAA7O0ckyFw789AGeA4HDoKca9uG8Hgow8eAXD166CsANj4hqDsJax6UStHwG' + + 'fb1datcBYgNoGDD2IIY1NePFY4EeyII/tGNKSt1ZbwQz5lst5LPILtz6HZRWz9FH2Fso3UP4' + + 'yAto+y8w+y3MlM2grrngRcFXwrJjXMpdcnexUnPzRdaZ/mr+J+w3592Tcldxzn9CtC7xtfN5' + + 'uP2D2H8BsGL5W7blULYAAAAASUVORK5CYII='; +// cspell:enable - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; +const xhr = new XMLHttpRequest(); +xhr.responseType = 'blob'; - componentDidMount() { - this._get(); - this._waitFor(this._getSucceeded, 6, doneSucceeded => { - let reader = new FileReader(); - reader.readAsDataURL(this.state.xhr.response); - reader.onload = () => { - TestModule.markTestPassed( - doneSucceeded && this.state.expected === reader.result, - ); - }; - }); +xhr.onloadend = () => { + const succeeded = xhr.status === 200 && xhr.response !== null; + if (!succeeded) { + TestModule.markTestPassed(false); + return; } - render(): React.Node { - return ; - } -} + const reader = new FileReader(); + reader.onload = () => { + TestModule.markTestPassed(reader.result === EXPECTED_DATA_URL); + }; + reader.onerror = () => { + TestModule.markTestPassed(false); + }; + reader.readAsDataURL(xhr.response); +}; -AppRegistry.registerComponent('BlobTest', () => BlobTest); +xhr.onerror = () => { + TestModule.markTestPassed(false); +}; -module.exports = BlobTest; +xhr.open('GET', URL); +xhr.setRequestHeader('Accept-Encoding', 'utf-8'); +xhr.send(); diff --git a/vnext/src-win/IntegrationTests/FetchTest.js b/vnext/src-win/IntegrationTests/FetchTest.js index 79c666fa581..ff645f5516a 100644 --- a/vnext/src-win/IntegrationTests/FetchTest.js +++ b/vnext/src-win/IntegrationTests/FetchTest.js @@ -2,52 +2,27 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -const uri = - 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/.yarnrc.yml'; -const expectedContent = 'enableScripts: false'; - -type State = { - uri: string, - expected: string, - content: string, -}; - -class FetchTest extends React.Component<{...}, State> { - state: State = { - uri: 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/.yarnrc.yml', - expected: 'enableScripts: false', - content: '', - }; - - async componentDidMount() { - const response = await fetch(uri); - const text = await response.text(); - this.setState({content: text}); - - if (this.state.content === expectedContent) { - TestModule.markTestPassed(true); - } else { - TestModule.markTestPassed(false); - } - } - - render(): React.Node { - return ; - } +if (!TestModule) { + throw new Error('TestModule is not available'); } -AppRegistry.registerComponent('FetchTest', () => FetchTest); - -module.exports = FetchTest; +const URL = 'http://localhost:5555/static/sample.txt'; +const EXPECTED_CONTENT = 'Sample Static Text File'; + +fetch(URL) + .then(response => response.text()) + .then(text => { + const passed = text === EXPECTED_CONTENT; + TestModule.markTestPassed(passed); + }) + .catch(() => { + TestModule.markTestPassed(false); + }); diff --git a/vnext/src-win/IntegrationTests/WebSocketBlobTest.js b/vnext/src-win/IntegrationTests/WebSocketBlobTest.js index 7b18bfe2c07..1b7b675276b 100644 --- a/vnext/src-win/IntegrationTests/WebSocketBlobTest.js +++ b/vnext/src-win/IntegrationTests/WebSocketBlobTest.js @@ -1,159 +1,121 @@ /** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); -const {AppRegistry, View} = ReactNative; -const {TestModule} = ReactNative.NativeModules; +const {TurboModuleRegistry} = require('react-native'); +const TestModule = TurboModuleRegistry.get('TestModule'); -// eslint-disable-next-line @microsoft/sdl/no-insecure-url -const DEFAULT_WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; - -const WS_EVENTS = ['close', 'error', 'message', 'open']; - -type State = { - url: string, - fetchStatus: ?string, - socket: ?WebSocket, - socketState: ?number, - lastSocketEvent: ?string, - lastMessage: ?Blob, - testMessage: Uint8Array, - testExpectedResponse: Uint8Array, - ... -}; - -class WebSocketBlobTest extends React.Component<{}, State> { - state: State = { - url: DEFAULT_WS_URL, - fetchStatus: null, - socket: null, - socketState: null, - lastSocketEvent: null, - lastMessage: null, - testMessage: new Uint8Array([1, 2, 3]), - testExpectedResponse: new Uint8Array([4, 5, 6, 7]), - }; +if (!TestModule) { + throw new Error('TestModule is not available'); +} - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; +const TEST_MESSAGE = new Uint8Array([1, 2, 3]); +// Expected bytes come from RNTesterIntegrationTests.WebSocketBinaryTest outgoingBytes. +const EXPECTED_BYTES = new Uint8Array([4, 5, 6, 7]); +const EXPECTED_SIZE = EXPECTED_BYTES.length; + +let completed = false; +const socket = new WebSocket(WS_URL); +socket.binaryType = 'blob'; + +const timeoutId = setTimeout(() => { + complete(false, 'timeout waiting for websocket response'); +}, 10000); + +function complete(passed, reason) { + if (completed) { + return; + } - _connect = () => { - const socket = new WebSocket(this.state.url); - socket.binaryType = 'blob'; - WS_EVENTS.forEach(ev => socket.addEventListener(ev, this._onSocketEvent)); - this.setState({ - socket, - socketState: socket.readyState, - }); - }; + completed = true; + clearTimeout(timeoutId); - _socketIsConnected = (): boolean => { - return this.state.socketState === 1; //'OPEN' - }; + if (!passed && reason) { + console.log('WebSocketBlobTest FAIL: ' + reason); + } - _socketIsDisconnected = (): boolean => { - return this.state.socketState === 3; //'CLOSED' - }; + TestModule.markTestPassed(passed); +} - _disconnect = () => { - if (!this.state.socket) { - return; - } - this.state.socket.close(); - }; +function decodeBlobToBytes(blob, onSuccess, onError) { + if (typeof blob.arrayBuffer === 'function') { + blob + .arrayBuffer() + .then(buffer => onSuccess(new Uint8Array(buffer))) + .catch(onError); + return; + } - _onSocketEvent = (event: any) => { - const state: any = { - socketState: event.target.readyState, - lastSocketEvent: event.type, - }; - if (event.type === 'message') { - state.lastMessage = event.data; - } - this.setState(state); + const reader = new FileReader(); + reader.onload = () => { + onSuccess(new Uint8Array(reader.result)); }; - - _sendBinary = (message: Uint8Array) => { - if (!this.state.socket) { - return; - } - this.state.socket.send(message); + reader.onerror = () => { + onError(reader.error || new Error('failed to read blob')); }; + reader.readAsArrayBuffer(blob); +} - _sendTestMessage = () => { - this._sendBinary(this.state.testMessage); - }; +socket.addEventListener('open', () => { + socket.send(TEST_MESSAGE); +}); - _receivedTestExpectedResponse = (): boolean => { - // Can't iterate through Blob response. Blob.arrayBuffer() not supported. - return ( - this.state.lastMessage?.size === this.state.testExpectedResponse.length - ); - }; +socket.addEventListener('message', event => { + const data = event.data; - componentDidMount() { - this.testConnect(); + if (!(data instanceof Blob)) { + complete(false, 'expected Blob response'); + socket.close(); + return; } - testConnect: () => void = () => { - this._connect(); - this._waitFor(this._socketIsConnected, 5, connectSucceeded => { - if (!connectSucceeded) { - TestModule.markTestPassed(false); - return; - } - this.testSendAndReceive(); - }); - }; + if (data.size !== EXPECTED_SIZE) { + complete(false, 'unexpected response size'); + socket.close(); + return; + } - testSendAndReceive: () => void = () => { - this._sendTestMessage(); - this._waitFor(this._receivedTestExpectedResponse, 5, messageReceived => { - if (!messageReceived) { - TestModule.markTestPassed(false); - return; + decodeBlobToBytes( + data, + bytes => { + for (let i = 0; i < EXPECTED_BYTES.length; i++) { + if (bytes[i] !== EXPECTED_BYTES[i]) { + complete( + false, + 'unexpected response byte at index ' + + i + + ': expected ' + + EXPECTED_BYTES[i] + + ', got ' + + bytes[i], + ); + socket.close(); + return; + } } - this.testDisconnect(); - }); - }; - testDisconnect: () => void = () => { - this._disconnect(); - this._waitFor(this._socketIsDisconnected, 5, disconnectSucceeded => { - TestModule.markTestPassed(disconnectSucceeded); - }); - }; - render(): React.Node { - return ; + complete(true); + socket.close(); + }, + error => { + complete(false, 'failed to decode blob: ' + String(error)); + socket.close(); + }, + ); +}); + +socket.addEventListener('error', () => { + complete(false, 'websocket error'); +}); + +socket.addEventListener('close', () => { + if (!completed) { + complete(false, 'socket closed before completing test'); } -} // class WebSocketBlobTest - -WebSocketBlobTest.displayName = 'WebSocketBlobTest'; - -AppRegistry.registerComponent('WebSocketBlobTest', () => WebSocketBlobTest); - -module.exports = WebSocketBlobTest; +}); diff --git a/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js b/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js index e3658cdab96..afaa789368f 100644 --- a/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js +++ b/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js @@ -10,7 +10,6 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow */ /* @@ -25,10 +24,13 @@ Array.from('abcdef').forEach( e => { ws.send(e.repeat(1025)) }); 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); -const {AppRegistry, View} = ReactNative; -const {TestModule} = ReactNative.NativeModules; +const {TurboModuleRegistry} = require('react-native'); + +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} // eslint-disable-next-line @microsoft/sdl/no-insecure-url const URL_BASE = 'ws://localhost:5555/rnw/rntester/websocketmultiplesendtest'; @@ -41,131 +43,90 @@ const EXPECTED = 'abcdef'; const ID = Math.floor(Math.random() * 1000000); -type State = { - sendUrl: string, - receiveUrl: string, - sendSocket: ?WebSocket, - receiveSocket: ?WebSocket, - result: ?string, -}; - -class WebSocketMultipleSendTest extends React.Component<{}, State> { - state: State = { - sendUrl: `${URL_BASE}/send/${ID}`, - receiveUrl: `${URL_BASE}/receive/${ID}`, - sendSocket: null, - receiveSocket: null, - result: '', - }; +const sendUrl = `${URL_BASE}/send/${ID}`; +const receiveUrl = `${URL_BASE}/receive/${ID}`; - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; +let sendSocket = null; +let receiveSocket = null; +let result = ''; - _socketsAreConnected = (): boolean => { - return ( - this.state.sendSocket?.readyState === 1 && - this.state.sendSocket?.readyState === 1 - ); // OPEN +const waitFor = (condition, timeout, callback) => { + let remaining = timeout; + const timeoutFunction = function () { + if (condition()) { + callback(true); + return; + } + remaining--; + if (remaining === 0) { + callback(false); + } else { + setTimeout(timeoutFunction, 1000); + } }; + setTimeout(timeoutFunction, 1000); +}; - _socketsAreDisconnected = (): boolean => { - return ( - this.state.sendSocket?.readyState === 3 && - this.state.sendSocket?.readyState === 3 - ); // CLOSED - }; +const socketsAreConnected = () => { + return sendSocket?.readyState === 1 && sendSocket?.readyState === 1; // OPEN +}; - _resultIsComplete = (): boolean => { - return this.state.result === EXPECTED; - }; +const socketsAreDisconnected = () => { + return sendSocket?.readyState === 3 && sendSocket?.readyState === 3; // CLOSED +}; - _disconnect = () => { - if (this.state.receiveSocket) { - this.state.receiveSocket.close(); - } - }; +const resultIsComplete = () => { + return result === EXPECTED; +}; - testDisconnect: () => void = () => { - this._disconnect(); - this._waitFor(this._socketsAreDisconnected, 5, disconnectSucceeded => { - TestModule.markTestPassed(disconnectSucceeded); - }); - }; +const disconnect = () => { + if (receiveSocket) { + receiveSocket.close(); + } +}; - testSendMultipleAndClose: () => void = () => { - this.state.sendSocket?.send('a'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('b'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('c'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('d'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('e'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('f'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.close(); - - this._waitFor(this._resultIsComplete, 5, resultComplete => { - if (!resultComplete) { - TestModule.markTestPassed(false); - return; - } - this.testDisconnect(); - }); - }; +const testDisconnect = () => { + disconnect(); + waitFor(socketsAreDisconnected, 5, disconnectSucceeded => { + TestModule.markTestPassed(disconnectSucceeded); + }); +}; - _onSocketEvent = (event: any) => { - if (event.type === 'message' && event.data.length) { - var message = this.state.result + event.data[0]; - this.setState({ - result: message, - }); +const testSendMultipleAndClose = () => { + sendSocket?.send('a'.repeat(MESSAGE_SIZE)); + sendSocket?.send('b'.repeat(MESSAGE_SIZE)); + sendSocket?.send('c'.repeat(MESSAGE_SIZE)); + sendSocket?.send('d'.repeat(MESSAGE_SIZE)); + sendSocket?.send('e'.repeat(MESSAGE_SIZE)); + sendSocket?.send('f'.repeat(MESSAGE_SIZE)); + sendSocket?.close(); + + waitFor(resultIsComplete, 5, resultComplete => { + if (!resultComplete) { + TestModule.markTestPassed(false); + return; } - }; - - _connect = () => { - const sendSocket = new WebSocket(this.state.sendUrl); - const receiveSocket = new WebSocket(this.state.receiveUrl); - WS_EVENTS.forEach(ev => - receiveSocket.addEventListener(ev, this._onSocketEvent), - ); - this.setState({ - sendSocket, - receiveSocket, - }); - }; - - componentDidMount() { - this._connect(); - this._waitFor(this._socketsAreConnected, 5, connectSucceeded => { - if (!connectSucceeded) { - TestModule.markTestPassed(false); - return; - } - this.testSendMultipleAndClose(); - }); - } + testDisconnect(); + }); +}; - render(): React.Node { - return ; +const onSocketEvent = event => { + if (event.type === 'message' && event.data.length) { + result += event.data[0]; } -} // class WebSocketMultipleSendTest - -WebSocketMultipleSendTest.displayName = 'WebSocketMultipleSendTest'; +}; -AppRegistry.registerComponent( - 'WebSocketMultipleSendTest', - () => WebSocketMultipleSendTest, -); +const connect = () => { + sendSocket = new WebSocket(sendUrl); + receiveSocket = new WebSocket(receiveUrl); + WS_EVENTS.forEach(ev => receiveSocket.addEventListener(ev, onSocketEvent)); +}; -module.exports = WebSocketMultipleSendTest; +connect(); +waitFor(socketsAreConnected, 5, connectSucceeded => { + if (!connectSucceeded) { + TestModule.markTestPassed(false); + return; + } + testSendMultipleAndClose(); +}); diff --git a/vnext/src-win/IntegrationTests/WebSocketTest.js b/vnext/src-win/IntegrationTests/WebSocketTest.js new file mode 100644 index 00000000000..b1975486599 --- /dev/null +++ b/vnext/src-win/IntegrationTests/WebSocketTest.js @@ -0,0 +1,82 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +'use strict'; + +const {TurboModuleRegistry} = require('react-native'); +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} + +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const WS_URL = 'ws://localhost:5555/'; +const TEST_MESSAGE = 'testMessage'; +const EXPECTED_RESPONSE = 'testMessage_response'; +const INITIAL_SERVER_GREETING = 'hello'; + +let completed = false; +let messageSent = false; +const socket = new WebSocket(WS_URL); +const timeoutId = setTimeout(() => { + complete(false, 'timeout waiting for websocket response'); +}, 10000); + +function sendTestMessageIfNeeded() { + if (messageSent || socket.readyState !== WebSocket.OPEN) { + return; + } + + messageSent = true; + socket.send(TEST_MESSAGE); +} + +function complete(passed, reason) { + if (completed) { + return; + } + + completed = true; + clearTimeout(timeoutId); + + if (!passed && reason) { + console.log('WebSocketTest FAIL: ' + reason); + } + + TestModule.markTestPassed(passed); +} + +socket.addEventListener('open', () => { + sendTestMessageIfNeeded(); +}); + +socket.addEventListener('message', event => { + if (event.data === INITIAL_SERVER_GREETING) { + return; + } + + if (event.data !== EXPECTED_RESPONSE) { + complete(false, 'unexpected response payload'); + socket.close(); + return; + } + + complete(true); + socket.close(); +}); + +socket.addEventListener('error', () => { + complete(false, 'websocket error'); +}); + +socket.addEventListener('close', () => { + if (!completed) { + complete(false, 'socket closed before completing test'); + } +}); + +sendTestMessageIfNeeded(); diff --git a/vnext/src-win/IntegrationTests/XHRTest.js b/vnext/src-win/IntegrationTests/XHRTest.js index a03ba97c658..2b85a9a0f3d 100644 --- a/vnext/src-win/IntegrationTests/XHRTest.js +++ b/vnext/src-win/IntegrationTests/XHRTest.js @@ -2,81 +2,30 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -type State = { - statusCode: number, - xhr: XMLHttpRequest, -}; - -class XHRTest extends React.Component<{...}, State> { - state: State = { - statusCode: 0, - xhr: new XMLHttpRequest(), - }; - - _get = () => { - this.state.xhr.onloadend = () => { - this.setState({ - statusCode: this.state.xhr.status, - }); - }; - this.state.xhr.open( - 'GET', - 'https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config', - ); - this.state.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); - this.state.xhr.send(); - }; - - _getSucceeded = (): boolean => { - console.log( - `_getSucceeded [${this.state.statusCode}],[${this.state.xhr.responseText.length}]`, - ); - return ( - this.state.statusCode === 200 && - this.state.xhr.responseText.length === 387 - ); - }; - - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; - - componentDidMount() { - this._get(); - this._waitFor(this._getSucceeded, 5, doneSucceeded => { - TestModule.markTestPassed(doneSucceeded); - }); - } - - render(): React.Node { - return ; - } +if (!TestModule) { + throw new Error('TestModule is not available'); } -AppRegistry.registerComponent('XHRTest', () => XHRTest); +const URL = 'http://localhost:5555/static/sample.txt'; +const EXPECTED_CONTENT = 'Sample Static Text File'; + +const xhr = new XMLHttpRequest(); +xhr.onloadend = () => { + const responseText = xhr.responseText || ''; + const passed = + xhr.status === 200 && responseText === EXPECTED_CONTENT; + TestModule.markTestPassed(passed); +}; +xhr.onerror = () => { + TestModule.markTestPassed(false); +}; -module.exports = XHRTest; +xhr.open('GET', URL); +xhr.send(); diff --git a/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js b/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js deleted file mode 100644 index 54583577d48..00000000000 --- a/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ - -'use strict'; - -/* eslint-env node */ - -const WebSocket = require('ws'); - -console.log(`\ -WebSocket binary integration test server - -This will send each incoming message back, in binary form. - -`); - -const server = new WebSocket.Server({port: 5557}); -server.on('connection', ws => { - ws.binaryType = 'arraybuffer'; - ws.on('message', message => { - console.log(message); - - ws.send(new Uint8Array([4, 5, 6, 7]).buffer); - }); -}); diff --git a/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js b/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js deleted file mode 100644 index d08ae16e0c5..00000000000 --- a/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ - -'use strict'; - -/* eslint-env node */ - -const WebSocket = require('ws'); - -console.log(`\ -WebSocket binary integration test server - -This will send each incoming message back, in binary form. - -`); - -const server = new WebSocket.Server({port: 5557}); -server.on('connection', ws => { - ws.binaryType = 'blob'; - ws.on('message', message => { - console.log(message); - - ws.send([4, 5, 6, 7]); - }); -}); From ba6a9c0a62bc0647a927be0eff5a08e1ef353aa3 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 17:48:09 -0700 Subject: [PATCH 13/23] Update yarn lock --- yarn.lock | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/yarn.lock b/yarn.lock index e04c8d9bd55..2959fab6314 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10845,6 +10845,11 @@ prettier@^3.0.0, prettier@^3.4.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== +prettier@^3.6.2: + version "3.8.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0" + integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw== + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" From 2f563ea82424df21439a047bd24d258cc89d657f Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 18:40:45 -0700 Subject: [PATCH 14/23] Remove ban-types --- packages/@rnw-scripts/eslint-config/eslintrc.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/@rnw-scripts/eslint-config/eslintrc.js b/packages/@rnw-scripts/eslint-config/eslintrc.js index bb32fcdd24e..a26b260fdaf 100644 --- a/packages/@rnw-scripts/eslint-config/eslintrc.js +++ b/packages/@rnw-scripts/eslint-config/eslintrc.js @@ -48,15 +48,6 @@ module.exports = { rules: { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-tslint-comment': 'error', - '@typescript-eslint/ban-types': [ - 'error', { - 'extendDefaults': true, - 'types': { - // See https://github.com/typescript-eslint/typescript-eslint/issues/2063 - '{}': false - } - } - ], '@typescript-eslint/no-confusing-non-null-assertion': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-floating-promises': [ From 56ab953ec23208261311f04d5ab54bfc299c14e9 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 19:32:19 -0700 Subject: [PATCH 15/23] Fix DynamicReaderWriterTests --- vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp b/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp index 4f5b68e44f7..a7f5427779e 100644 --- a/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp +++ b/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp @@ -166,10 +166,10 @@ TEST_CLASS (DynamicReaderWriterTests) { } private: - template + template void TestScalar( - void(IJSValueWriter::*writerMethod)(TWriterValue) const, - TReaderValue (IJSValueReader::*readerMethod)() const, + WriterMethod writerMethod, + ReaderMethod readerMethod, JSValueType runtimeType, TWriterValue value) { IJSValueWriter writer = Microsoft::Internal::TestController::CreateDynamicWriter(); From 503614896264ba11900d996cdda72356a76165cd Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 20:24:16 -0700 Subject: [PATCH 16/23] Use WinRT coroutine types for UwpScriptStore --- vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp | 4 ++-- vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp index 59bb0c805db..03e88dbb0dc 100644 --- a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp +++ b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include "Unicode.h" namespace winrt { @@ -43,7 +42,8 @@ facebook::jsi::ScriptVersion_t UwpScriptStore::getScriptVersion(const std::strin return version; } -std::future UwpScriptStore::getScriptVersionAsync(const std::string &bundleUri) { +winrt::Windows::Foundation::IAsyncOperation +UwpScriptStore::getScriptVersionAsync(const std::string &bundleUri) { co_await winrt::resume_background(); const winrt::hstring fileUrl(Microsoft::Common::Unicode::Utf8ToUtf16("ms-appx:///Bundle/" + bundleUri + ".bundle")); diff --git a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h index 6b77db3f9e7..78b1ff112ae 100644 --- a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h +++ b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.h @@ -1,6 +1,6 @@ #pragma once #include -#include +#include namespace Microsoft::ReactNative { @@ -16,7 +16,8 @@ class UwpScriptStore : public facebook::jsi::ScriptStore { static facebook::jsi::ScriptVersion_t GetFileVersion(const std::wstring &filePath); private: - std::future getScriptVersionAsync(const std::string &bundleUri); + winrt::Windows::Foundation::IAsyncOperation getScriptVersionAsync( + const std::string &bundleUri); }; } // namespace Microsoft::ReactNative From 0b38037a7b62e0670a33d61a26adfb0e4209c0bc Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 9 Jun 2026 02:31:30 -0700 Subject: [PATCH 17/23] clang format --- vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp | 6 +----- vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp b/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp index a7f5427779e..a228d77eb56 100644 --- a/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp +++ b/vnext/Desktop.ABITests/DynamicReaderWriterTests.cpp @@ -167,11 +167,7 @@ TEST_CLASS (DynamicReaderWriterTests) { private: template - void TestScalar( - WriterMethod writerMethod, - ReaderMethod readerMethod, - JSValueType runtimeType, - TWriterValue value) { + void TestScalar(WriterMethod writerMethod, ReaderMethod readerMethod, JSValueType runtimeType, TWriterValue value) { IJSValueWriter writer = Microsoft::Internal::TestController::CreateDynamicWriter(); (writer.*writerMethod)(value); diff --git a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp index 03e88dbb0dc..87e21081a06 100644 --- a/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp +++ b/vnext/Microsoft.ReactNative/Utils/UwpScriptStore.cpp @@ -42,8 +42,8 @@ facebook::jsi::ScriptVersion_t UwpScriptStore::getScriptVersion(const std::strin return version; } -winrt::Windows::Foundation::IAsyncOperation -UwpScriptStore::getScriptVersionAsync(const std::string &bundleUri) { +winrt::Windows::Foundation::IAsyncOperation UwpScriptStore::getScriptVersionAsync( + const std::string &bundleUri) { co_await winrt::resume_background(); const winrt::hstring fileUrl(Microsoft::Common::Unicode::Utf8ToUtf16("ms-appx:///Bundle/" + bundleUri + ".bundle")); From 71b067d19da6db9bb6dccacc7726aec66ac5033c Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 9 Jun 2026 03:26:14 -0700 Subject: [PATCH 18/23] yarn lint --- .../@react-native-windows/cli/src/utils/commandWithProgress.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts b/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts index b78eed125b6..98a99520265 100644 --- a/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts +++ b/packages/@react-native-windows/cli/src/utils/commandWithProgress.ts @@ -95,7 +95,7 @@ export function commandWithProgress( errorCategory: CodedErrorType, ) { return new Promise((resolve, reject) => { - const spawnOptions: SpawnOptions = verbose ? { stdio: 'inherit' } : {}; + const spawnOptions: SpawnOptions = verbose ? {stdio: 'inherit'} : {}; if (verbose) { spinner.stop(); From d93d72e11d07a704a26a5024f8ad3596436fecfc Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 9 Jun 2026 04:07:23 -0700 Subject: [PATCH 19/23] prettier --- vnext/src-win/IntegrationTests/XHRTest.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vnext/src-win/IntegrationTests/XHRTest.js b/vnext/src-win/IntegrationTests/XHRTest.js index 2b85a9a0f3d..18d7d3be27d 100644 --- a/vnext/src-win/IntegrationTests/XHRTest.js +++ b/vnext/src-win/IntegrationTests/XHRTest.js @@ -19,8 +19,7 @@ const EXPECTED_CONTENT = 'Sample Static Text File'; const xhr = new XMLHttpRequest(); xhr.onloadend = () => { const responseText = xhr.responseText || ''; - const passed = - xhr.status === 200 && responseText === EXPECTED_CONTENT; + const passed = xhr.status === 200 && responseText === EXPECTED_CONTENT; TestModule.markTestPassed(passed); }; xhr.onerror = () => { From 495d42254f77522f7bce649cc67376384d82773a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 9 Jun 2026 04:53:27 -0700 Subject: [PATCH 20/23] prettier --- .../automation/src/AutomationEnvironment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@react-native-windows/automation/src/AutomationEnvironment.ts b/packages/@react-native-windows/automation/src/AutomationEnvironment.ts index 0e25e19df6c..8cce017d431 100644 --- a/packages/@react-native-windows/automation/src/AutomationEnvironment.ts +++ b/packages/@react-native-windows/automation/src/AutomationEnvironment.ts @@ -251,7 +251,7 @@ export default class AutomationEnvironment extends NodeEnvironment { if (this.breakOnStart) { readlineSync.question( chalk.bold.yellow('Breaking before tests start\n') + - 'Press Enter to resume...', + 'Press Enter to resume...', ); } From 7df616e757ade6a87feed4c4731077b7012ec6b4 Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Tue, 9 Jun 2026 19:40:58 -0700 Subject: [PATCH 21/23] Adjust TextComponentTest.test.ts.snap --- .../TextComponentTest.test.ts.snap | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/TextComponentTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/TextComponentTest.test.ts.snap index db814764293..3e160e45eed 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/TextComponentTest.test.ts.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/TextComponentTest.test.ts.snap @@ -306,7 +306,7 @@ exports[`Text Tests Text can have a customized selection color 1`] = ` "Visual Tree": { "Comment": "text-selection-color", "Offset": "0, 0, 0", - "Size": "916, 19", + "Size": "916, 20", "Visual Type": "SpriteVisual", }, } @@ -568,12 +568,12 @@ exports[`Text Tests Text can have advanced borders 1`] = ` }, { "Offset": "0, 77, 0", - "Size": "916, 22", + "Size": "916, 21", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 22", + "Size": "916, 21", "Visual Type": "SpriteVisual", "__Children": [ { @@ -609,7 +609,7 @@ exports[`Text Tests Text can have advanced borders 1`] = ` "Color": "rgba(0, 128, 0, 255)", }, "Offset": "-1, 4, 0", - "Size": "1, 14", + "Size": "1, 13", "Visual Type": "SpriteVisual", }, { @@ -645,7 +645,7 @@ exports[`Text Tests Text can have advanced borders 1`] = ` "Color": "rgba(0, 128, 0, 255)", }, "Offset": "0, 4, 0", - "Size": "1, 14", + "Size": "1, 13", "Visual Type": "SpriteVisual", }, ], @@ -675,7 +675,7 @@ exports[`Text Tests Text can have an outer color 1`] = ` "Visual Tree": { "Comment": "text-outer-color", "Offset": "0, 0, 0", - "Size": "916, 20", + "Size": "916, 19", "Visual Type": "SpriteVisual", }, } @@ -799,12 +799,12 @@ exports[`Text Tests Text can have borders 1`] = ` }, { "Offset": "100, 127, 0", - "Size": "716, 138", + "Size": "716, 139", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "716, 138", + "Size": "716, 139", "Visual Type": "SpriteVisual", "__Children": [ { @@ -910,7 +910,7 @@ exports[`Text Tests Text can have decoration lines: Underline 1`] = ` "Visual Tree": { "Comment": "text-decoration-underline", "Offset": "0, 0, 0", - "Size": "916, 20", + "Size": "916, 19", "Visual Type": "SpriteVisual", }, } @@ -953,7 +953,7 @@ exports[`Text Tests Text can have inline views/images 1`] = ` "Visual Tree": { "Comment": "text-view", "Offset": "0, 0, 0", - "Size": "916, 27", + "Size": "916, 26", "Visual Type": "SpriteVisual", "__Children": [ { @@ -1049,12 +1049,12 @@ exports[`Text Tests Text can have nested views 1`] = ` "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 19", + "Size": "916, 20", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 19", + "Size": "916, 20", "Visual Type": "SpriteVisual", }, ], From 1f567142e75ce323702c4eeb6fce2b9b46bd646c Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 10 Jun 2026 01:07:36 -0700 Subject: [PATCH 22/23] Increase timeouts --- .ado/jobs/linting.yml | 2 +- .ado/jobs/node-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ado/jobs/linting.yml b/.ado/jobs/linting.yml index 399cd890a9b..d5109fe31cd 100644 --- a/.ado/jobs/linting.yml +++ b/.ado/jobs/linting.yml @@ -12,7 +12,7 @@ parameters: jobs: - job: Linting displayName: Linting - timeoutInMinutes: 40 + timeoutInMinutes: 45 variables: [template: ../variables/windows.yml] pool: ${{ parameters.AgentPool.Medium }} steps: diff --git a/.ado/jobs/node-tests.yml b/.ado/jobs/node-tests.yml index c8772aba334..78a13e317d4 100644 --- a/.ado/jobs/node-tests.yml +++ b/.ado/jobs/node-tests.yml @@ -17,7 +17,7 @@ jobs: - ${{ each nodeVersion in parameters.versions }}: - job: NodeTests${{ nodeVersion }} displayName: Node Tests v${{ nodeVersion }} - timeoutInMinutes: 20 + timeoutInMinutes: 25 variables: [template: ../variables/windows.yml] pool: ${{ parameters.AgentPool.Medium }} From ed4a268108d1d6fdd087cd7bc031b126fe68ce7a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 10 Jun 2026 11:34:05 -0700 Subject: [PATCH 23/23] Revert "Increase timeouts" This reverts commit 1f567142e75ce323702c4eeb6fce2b9b46bd646c. --- .ado/jobs/linting.yml | 2 +- .ado/jobs/node-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ado/jobs/linting.yml b/.ado/jobs/linting.yml index d5109fe31cd..399cd890a9b 100644 --- a/.ado/jobs/linting.yml +++ b/.ado/jobs/linting.yml @@ -12,7 +12,7 @@ parameters: jobs: - job: Linting displayName: Linting - timeoutInMinutes: 45 + timeoutInMinutes: 40 variables: [template: ../variables/windows.yml] pool: ${{ parameters.AgentPool.Medium }} steps: diff --git a/.ado/jobs/node-tests.yml b/.ado/jobs/node-tests.yml index 78a13e317d4..c8772aba334 100644 --- a/.ado/jobs/node-tests.yml +++ b/.ado/jobs/node-tests.yml @@ -17,7 +17,7 @@ jobs: - ${{ each nodeVersion in parameters.versions }}: - job: NodeTests${{ nodeVersion }} displayName: Node Tests v${{ nodeVersion }} - timeoutInMinutes: 25 + timeoutInMinutes: 20 variables: [template: ../variables/windows.yml] pool: ${{ parameters.AgentPool.Medium }}