diff --git a/.github/DEVELOPMENT.md b/.github/DEVELOPMENT.md
index ad114e16d37c..d4f2a618f03a 100644
--- a/.github/DEVELOPMENT.md
+++ b/.github/DEVELOPMENT.md
@@ -59,9 +59,13 @@ Before opening the solution in Visual Studio / VS Code you **MUST** build the bu
*NOTE*: IntelliSense takes a decent amount of time to fully process your solution. It will eventually work through all the necessary tasks. If you are having IntelliSense issues, usually unloading/reloading the `maui.core` and `maui.controls` projects will resolve.
## What branch should I use?
-- main
-Always use main no matter what you are working on or where you are hoping your change will get applied. We make sure that main always works against the current stable releases of Visual Studio and the .NET MAUI SDK. Even if you are working on features that will only be released with a future version of .NET. `main` is the only relevant branch for current development.
+As a general rule:
+- [main](https://github.com/dotnet/maui/tree/main)
+
+Use ‘main’ for bug fixes that don’t require API changes. For new features and changes to public APIs, you must use the branch of the next .NET version.
+
+- [net9.0](https://github.com/dotnet/maui/tree/net9.0)
## Repository projects
@@ -116,8 +120,10 @@ These are tests used for exercising the UI through accessibility layers to simul
```
├── Controls
+│ ├── samples
+│ │ ├── Controls.Sample.UITests
│ ├── tests
-│ │ ├── UITests
+│ │ ├── Controls.AppiumTests
```
### Unit Test Projects
@@ -146,6 +152,8 @@ These tests can be ran using the test explorer in VS, or from command line with
dotnet test src/TestUtils/src/Microsoft.Maui.IntegrationTests --logger "console;verbosity=diagnostic" --filter "Name=Build\(%22maui%22,%22net7.0%22,%22Debug%22,False\)"
```
+You can find detailed information about testing in the [Wiki](https://github.com/dotnet/maui/wiki/Testing).
+
### Additional Cake Commands
#### Clean
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 172a757d1d3c..4ac562325168 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -45,6 +45,7 @@ body:
description: In what version do you see this issue? Run `dotnet workload list` to find your version.
options:
-
+ - 9.0.0-preview.1.9973
- 8.0.7 SR2
- 8.0.6 SR1
- 8.0.3 GA
@@ -79,7 +80,7 @@ body:
id: version-that-worked
attributes:
label: Last version that worked well
- description: If you answered yes, there a version on which this _did_ work, which one? If no or unknown, please select `Unknown/Other`. Run `dotnet workload list` to find your version.
+ description: If you answered yes, is there a version on which this _did_ work, which one? If no or unknown, please select `Unknown/Other`. Run `dotnet workload list` to find your version.
options:
-
- Unknown/Other
@@ -107,6 +108,7 @@ body:
- 8.0.3 GA
- 8.0.6 SR1
- 8.0.7 SR2
+ - 9.0.0-preview.1.9973
validations:
required: true
- type: dropdown
diff --git a/eng/Versions.targets b/eng/Versions.targets
index 2419b505db60..b6fbc83806b7 100644
--- a/eng/Versions.targets
+++ b/eng/Versions.targets
@@ -57,12 +57,12 @@
-$(GitSemVerLabel)
-
+
-
+
diff --git a/eng/automation/guardian/CredScanSuppressions.json b/eng/automation/guardian/CredScanSuppressions.json
deleted file mode 100644
index 3d66276257ba..000000000000
--- a/eng/automation/guardian/CredScanSuppressions.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "tool": "Credential Scanner",
- "suppressions": [
- {
- "file": "\\Xamarin.Forms.ControlGallery.WindowsUniversal\\Xamarin.Forms.ControlGallery.WindowsUniversal_TemporaryKey.pfx",
- "_justification": "Dummy pfx file used for testing."
- },
- {
- "file": "\\debug.keystore",
- "_justification": "Dummy debug store file used for testing."
- }
- ]
-}
\ No newline at end of file
diff --git a/eng/automation/guardian/PoliCheck.Exclusions.xml b/eng/automation/guardian/PoliCheck.Exclusions.xml
deleted file mode 100644
index dd494420606a..000000000000
--- a/eng/automation/guardian/PoliCheck.Exclusions.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
- SAMPLES|CONTROLGALLERY|PUBLICAPI
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/eng/automation/guardian/source.gdnsuppress b/eng/automation/guardian/source.gdnsuppress
index 7506141a6d67..f062f2b243b5 100644
--- a/eng/automation/guardian/source.gdnsuppress
+++ b/eng/automation/guardian/source.gdnsuppress
@@ -152,6 +152,14 @@
"default"
],
"createdDate": "2023-12-19 01:00:51Z"
+ },
+ "db932a43593049dd3d581d65ef9043ce4a5cc9f6970942c61f7ff29a8395ab5a": {
+ "signature": "db932a43593049dd3d581d65ef9043ce4a5cc9f6970942c61f7ff29a8395ab5a",
+ "alternativeSignatures": [],
+ "memberOf": [
+ "default"
+ ],
+ "createdDate": "2024-02-27 23:34:18Z"
}
}
}
\ No newline at end of file
diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1
index 6db6baac5c09..6a05966d57ce 100644
--- a/eng/common/SetupNugetSources.ps1
+++ b/eng/common/SetupNugetSources.ps1
@@ -25,7 +25,7 @@
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)][string]$ConfigFile,
- [Parameter(Mandatory = $true)][string]$Password
+ [Parameter(Mandatory = $true)][SecureString]$Password
)
$ErrorActionPreference = "Stop"
@@ -35,7 +35,7 @@ Set-StrictMode -Version 2.0
. $PSScriptRoot\tools.ps1
# Add source entry to PackageSources
-function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $Password) {
+function AddPackageSource($sources, $SourceName, $SourceEndPoint, [SecureString] $creds, $Username, [SecureString] $Password) {
$packageSource = $sources.SelectSingleNode("add[@key='$SourceName']")
if ($packageSource -eq $null)
@@ -53,7 +53,7 @@ function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Usern
}
# Add a credential node for the specified source
-function AddCredential($creds, $source, $username, $password) {
+function AddCredential([SecureString] $creds, $source, $username, [SecureString] $password) {
# Looks for credential configuration for the given SourceName. Create it if none is found.
$sourceElement = $creds.SelectSingleNode($Source)
if ($sourceElement -eq $null)
@@ -85,7 +85,7 @@ function AddCredential($creds, $source, $username, $password) {
$passwordElement.SetAttribute("value", $Password)
}
-function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $Password) {
+function InsertMaestroPrivateFeedCredentials($Sources, [SecureString] $Creds, $Username, [SecureString] $Password) {
$maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]")
Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds."
@@ -164,4 +164,4 @@ foreach ($dotnetVersion in $dotnetVersions) {
}
}
-$doc.Save($filename)
\ No newline at end of file
+$doc.Save($filename)
diff --git a/eng/common/pipeline-logging-functions.sh b/eng/common/pipeline-logging-functions.sh
old mode 100644
new mode 100755
diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1
index aa74ab4a81e7..269fdb9420da 100644
--- a/eng/common/tools.ps1
+++ b/eng/common/tools.ps1
@@ -65,6 +65,9 @@ $ErrorActionPreference = 'Stop'
# Base-64 encoded SAS token that has permission to storage container described by $runtimeSourceFeed
[string]$runtimeSourceFeedKey = if (Test-Path variable:runtimeSourceFeedKey) { $runtimeSourceFeedKey } else { $null }
+# True if the build is a product build
+[bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false }
+
function Create-Directory ([string[]] $path) {
New-Item -Path $path -Force -ItemType 'Directory' | Out-Null
}
@@ -158,18 +161,13 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) {
$env:DOTNET_MULTILEVEL_LOOKUP=0
# Disable first run since we do not need all ASP.NET packages restored.
- $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+ $env:DOTNET_NOLOGO=1
# Disable telemetry on CI.
if ($ci) {
$env:DOTNET_CLI_TELEMETRY_OPTOUT=1
}
- # Source Build uses DotNetCoreSdkDir variable
- if ($env:DotNetCoreSdkDir -ne $null) {
- $env:DOTNET_INSTALL_DIR = $env:DotNetCoreSdkDir
- }
-
# Find the first path on %PATH% that contains the dotnet.exe
if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) {
$dotnetExecutable = GetExecutableFileName 'dotnet'
@@ -228,7 +226,7 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) {
Write-PipelinePrependPath -Path $dotnetRoot
Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0'
- Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1'
+ Write-PipelineSetVariable -Name 'DOTNET_NOLOGO' -Value '1'
return $global:_DotNetInstallDir = $dotnetRoot
}
@@ -379,13 +377,13 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements =
}
# Minimum VS version to require.
- $vsMinVersionReqdStr = '17.6'
+ $vsMinVersionReqdStr = '17.7'
$vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr)
# If the version of msbuild is going to be xcopied,
# use this version. Version matches a package here:
- # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.6.0-2
- $defaultXCopyMSBuildVersion = '17.6.0-2'
+ # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/Microsoft.DotNet.Arcade.MSBuild.Xcopy/versions/17.8.5
+ $defaultXCopyMSBuildVersion = '17.8.5'
if (!$vsRequirements) {
if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') {
@@ -450,7 +448,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements =
if ($xcopyMSBuildVersion.Trim() -ine "none") {
$vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install
if ($vsInstallDir -eq $null) {
- throw "Could not xcopy msbuild. Please check that package 'RoslynTools.MSBuild @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'."
+ throw "Could not xcopy msbuild. Please check that package 'Microsoft.DotNet.Arcade.MSBuild.Xcopy @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'."
}
}
if ($vsInstallDir -eq $null) {
@@ -487,7 +485,7 @@ function InstallXCopyMSBuild([string]$packageVersion) {
}
function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) {
- $packageName = 'RoslynTools.MSBuild'
+ $packageName = 'Microsoft.DotNet.Arcade.MSBuild.Xcopy'
$packageDir = Join-Path $ToolsDir "msbuild\$packageVersion"
$packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg"
@@ -601,7 +599,15 @@ function InitializeBuildTool() {
ExitWithExitCode 1
}
$dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet')
- $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net8.0' }
+
+ # Use override if it exists - commonly set by source-build
+ if ($null -eq $env:_OverrideArcadeInitializeBuildToolFramework) {
+ $initializeBuildToolFramework="net9.0"
+ } else {
+ $initializeBuildToolFramework=$env:_OverrideArcadeInitializeBuildToolFramework
+ }
+
+ $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = $initializeBuildToolFramework }
} elseif ($msbuildEngine -eq "vs") {
try {
$msbuildPath = InitializeVisualStudioMSBuild -install:$restore
@@ -676,8 +682,14 @@ function Read-ArcadeSdkVersion() {
}
function InitializeToolset() {
- if (Test-Path variable:global:_ToolsetBuildProj) {
- return $global:_ToolsetBuildProj
+ # For Unified Build/Source-build support, check whether the environment variable is
+ # set. If it is, then use this as the toolset build project.
+ if ($env:_InitializeToolset -ne $null) {
+ return $global:_InitializeToolset = $env:_InitializeToolset
+ }
+
+ if (Test-Path variable:global:_InitializeToolset) {
+ return $global:_InitializeToolset
}
$nugetCache = GetNuGetPackageCachePath
@@ -688,7 +700,7 @@ function InitializeToolset() {
if (Test-Path $toolsetLocationFile) {
$path = Get-Content $toolsetLocationFile -TotalCount 1
if (Test-Path $path) {
- return $global:_ToolsetBuildProj = $path
+ return $global:_InitializeToolset = $path
}
}
@@ -711,7 +723,7 @@ function InitializeToolset() {
throw "Invalid toolset path: $path"
}
- return $global:_ToolsetBuildProj = $path
+ return $global:_InitializeToolset = $path
}
function ExitWithExitCode([int] $exitCode) {
@@ -763,12 +775,10 @@ function MSBuild() {
# new scripts need to work with old packages, so we need to look for the old names/versions
(Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')),
(Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')),
- (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.ArcadeLogging.dll')),
- (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.Arcade.Sdk.dll'))
- (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.ArcadeLogging.dll')),
- (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.Arcade.Sdk.dll'))
(Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.ArcadeLogging.dll')),
- (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.Arcade.Sdk.dll'))
+ (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.Arcade.Sdk.dll')),
+ (Join-Path $basePath (Join-Path net8.0 'Microsoft.DotNet.ArcadeLogging.dll')),
+ (Join-Path $basePath (Join-Path net8.0 'Microsoft.DotNet.Arcade.Sdk.dll'))
)
$selectedPath = $null
foreach ($path in $possiblePaths) {
@@ -827,7 +837,8 @@ function MSBuild-Core() {
}
}
- $env:ARCADE_BUILD_TOOL_COMMAND = "$($buildTool.Path) $cmdArgs"
+ # Be sure quote the path in case there are spaces in the dotnet installation location.
+ $env:ARCADE_BUILD_TOOL_COMMAND = "`"$($buildTool.Path)`" $cmdArgs"
$exitCode = Exec-Process $buildTool.Path $cmdArgs
@@ -842,7 +853,8 @@ function MSBuild-Core() {
}
# When running on Azure Pipelines, override the returned exit code to avoid double logging.
- if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null) {
+ # Skip this when the build is a child of the VMR orchestrator build.
+ if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and $properties -notlike "*DotNetBuildRepo=true*") {
Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed."
# Exiting with an exit code causes the azure pipelines task to log yet another "noise" error
# The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error
diff --git a/eng/common/tools.sh b/eng/common/tools.sh
old mode 100644
new mode 100755
index e8d478943341..6f0141e7d513
--- a/eng/common/tools.sh
+++ b/eng/common/tools.sh
@@ -68,6 +68,9 @@ fi
runtime_source_feed=${runtime_source_feed:-''}
runtime_source_feed_key=${runtime_source_feed_key:-''}
+# True if the build is a product build
+product_build=${product_build:-false}
+
# Resolve any symlinks in the given path.
function ResolvePath {
local path=$1
@@ -112,7 +115,7 @@ function InitializeDotNetCli {
export DOTNET_MULTILEVEL_LOOKUP=0
# Disable first run since we want to control all package sources
- export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+ export DOTNET_NOLOGO=1
# Disable telemetry on CI
if [[ $ci == true ]]; then
@@ -123,11 +126,6 @@ function InitializeDotNetCli {
# so it doesn't output warnings to the console.
export LTTNG_HOME="$HOME"
- # Source Build uses DotNetCoreSdkDir variable
- if [[ -n "${DotNetCoreSdkDir:-}" ]]; then
- export DOTNET_INSTALL_DIR="$DotNetCoreSdkDir"
- fi
-
# Find the first path on $PATH that contains the dotnet.exe
if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then
local dotnet_path=`command -v dotnet`
@@ -146,7 +144,7 @@ function InitializeDotNetCli {
if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then
dotnet_root="$DOTNET_INSTALL_DIR"
else
- dotnet_root="$repo_root/.dotnet"
+ dotnet_root="${repo_root}.dotnet"
export DOTNET_INSTALL_DIR="$dotnet_root"
@@ -165,7 +163,7 @@ function InitializeDotNetCli {
Write-PipelinePrependPath -path "$dotnet_root"
Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0"
- Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1"
+ Write-PipelineSetVariable -name "DOTNET_NOLOGO" -value "1"
# return value
_InitializeDotNetCli="$dotnet_root"
@@ -310,7 +308,7 @@ function GetDotNetInstallScript {
curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || {
if command -v openssl &> /dev/null; then
echo "Curl failed; dumping some information about dotnet.microsoft.com for later investigation"
- echo | openssl s_client -showcerts -servername dotnet.microsoft.com -connect dotnet.microsoft.com:443
+ echo | openssl s_client -showcerts -servername dotnet.microsoft.com -connect dotnet.microsoft.com:443 || true
fi
echo "Will now retry the same URL with verbose logging."
with_retries curl "$install_script_url" -sSL --verbose --retry 10 --create-dirs -o "$install_script" || {
@@ -341,7 +339,12 @@ function InitializeBuildTool {
# return values
_InitializeBuildTool="$_InitializeDotNetCli/dotnet"
_InitializeBuildToolCommand="msbuild"
- _InitializeBuildToolFramework="net8.0"
+ # use override if it exists - commonly set by source-build
+ if [[ "${_OverrideArcadeInitializeBuildToolFramework:-x}" == "x" ]]; then
+ _InitializeBuildToolFramework="net9.0"
+ else
+ _InitializeBuildToolFramework="${_OverrideArcadeInitializeBuildToolFramework}"
+ fi
}
# Set RestoreNoCache as a workaround for https://github.com/NuGet/Home/issues/3116
@@ -453,12 +456,10 @@ function MSBuild {
local possiblePaths=()
possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.ArcadeLogging.dll" )
possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" )
- possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.ArcadeLogging.dll" )
- possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.Arcade.Sdk.dll" )
- possiblePaths+=( "$toolset_dir/netcoreapp3.1/Microsoft.DotNet.ArcadeLogging.dll" )
- possiblePaths+=( "$toolset_dir/netcoreapp3.1/Microsoft.DotNet.Arcade.Sdk.dll" )
possiblePaths+=( "$toolset_dir/net7.0/Microsoft.DotNet.ArcadeLogging.dll" )
possiblePaths+=( "$toolset_dir/net7.0/Microsoft.DotNet.Arcade.Sdk.dll" )
+ possiblePaths+=( "$toolset_dir/net8.0/Microsoft.DotNet.ArcadeLogging.dll" )
+ possiblePaths+=( "$toolset_dir/net8.0/Microsoft.DotNet.Arcade.Sdk.dll" )
for path in "${possiblePaths[@]}"; do
if [[ -f $path ]]; then
selectedPath=$path
@@ -505,7 +506,8 @@ function MSBuild-Core {
echo "Build failed with exit code $exit_code. Check errors above."
# When running on Azure Pipelines, override the returned exit code to avoid double logging.
- if [[ "$ci" == "true" && -n ${SYSTEM_TEAMPROJECT:-} ]]; then
+ # Skip this when the build is a child of the VMR orchestrator build.
+ if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && $properties != *"DotNetBuildRepo=true"* ]]; then
Write-PipelineSetResult -result "Failed" -message "msbuild execution failed."
# Exiting with an exit code causes the azure pipelines task to log yet another "noise" error
# The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error
diff --git a/eng/pipelines/common/apiscan.yml b/eng/pipelines/common/apiscan.yml
new file mode 100644
index 000000000000..996c12c1c53d
--- /dev/null
+++ b/eng/pipelines/common/apiscan.yml
@@ -0,0 +1,23 @@
+parameters:
+ poolName: VSEngSS-MicroBuild2022-1ES
+ vmImage: ''
+ os: windows
+ softwareName: 'MAUI'
+ softwareVersion: 8.0
+ dependsOn: []
+ scanArtifacts: []
+ stageName: 'api_scan'
+ displayName: 'ApiScan'
+
+stages:
+ - template: security/apiscan/v0.yml@yaml-templates
+ parameters:
+ windowsPoolName: ${{ parameters.poolName }}
+ windowsImageOverride: ${{ parameters.vmImage }}
+ stageDependsOn: ${{ parameters.dependsOn }}
+ timeoutInMinutes: 600
+ scanArtifacts: ${{ parameters.scanArtifacts }}
+ sourceGdnSuppressionFile: '$(System.DefaultWorkingDirectory)\eng\automation\guardian\source.gdnsuppress'
+ tsaConfigFile: '$(System.DefaultWorkingDirectory)\eng\automation\guardian\tsaoptions-v2.json'
+ apiScanSoftwareName: ${{ parameters.softwareName }}
+ apiScanSoftwareVersionNum: ${{ parameters.softwareVersion }}
\ No newline at end of file
diff --git a/eng/pipelines/common/insertion.yml b/eng/pipelines/common/insertion.yml
index f51397c389e0..bbd556645c02 100644
--- a/eng/pipelines/common/insertion.yml
+++ b/eng/pipelines/common/insertion.yml
@@ -1,29 +1,22 @@
parameters:
poolName: VSEngSS-MicroBuild2022-1ES
vmImage: ''
+ os: windows
+ dependsOn: []
+ stageName: 'sdk_insertion'
+ displayName: 'SDK Insertion'
pushMauiPackagesToMaestro: false
stages:
- - stage: sdk_insertion
- displayName: 'SDK Insertion'
- dependsOn: nuget_signing
+ - stage: ${{ parameters.stageName }}
+ displayName: ${{ parameters.displayName }}
+ dependsOn: ${{ parameters.dependsOn }}
condition: and(succeeded(), eq(variables.signingCondition, true))
jobs:
- - template: sdk-insertion.yml
+ - template: /eng/pipelines/common/sdk-insertion.yml@self
parameters:
poolName: ${{ parameters.poolName }}
vmImage: ${{ parameters.vmImage }}
+ os: ${{ parameters.os }}
pushMauiPackagesToMaestro: ${{ parameters.pushMauiPackagesToMaestro }}
- - stage: sbom
- displayName: 'Software Bill of Materials'
- dependsOn: nuget_signing
- condition: and(succeeded(), eq(variables.signingCondition, true))
- jobs:
- - template: compliance/sbom/job.v1.yml@yaml-templates
- parameters:
- artifactNames: [ nuget, vs-msi-nugets, vsdrop-signed ]
- artifactMap: [ nuget/signed ] # Use artifacts that match the filter from the signed directory and not the top-level directory for the nuget artifact
- packageName: 'Microsoft Maui'
- packageFilter: '*.msi;*.nupkg'
- condition: and(succeeded(), eq(variables.signingCondition, true))
diff --git a/eng/pipelines/common/pack.yml b/eng/pipelines/common/pack.yml
index 0144fc0b286c..bed2ca312849 100644
--- a/eng/pipelines/common/pack.yml
+++ b/eng/pipelines/common/pack.yml
@@ -3,10 +3,6 @@ parameters:
type: string
default: ''
-- name: poolName
- type: string
- default: ''
-
- name: provisionatorChannel
type: string
default: 'latest'
@@ -19,15 +15,22 @@ parameters:
type: string
default: 'pack-binaries'
+- name: artifactsPath
+ type: string
+ default: (Build.ArtifactStagingDirectory)
+
- name: nugetFolder
type: string
default: 'artifacts'
-
- name: prepareSteps
type: stepList
default: []
+- name: postSteps
+ type: stepList
+ default: []
+
- name: gitHubToken
type: string
default: $(github--pat--vs-mobiletools-engineering-service2)
@@ -36,16 +39,15 @@ parameters:
type: string
default: $(System.DefaultWorkingDirectory)
-steps:
+- name: additionalArtifacts
+ type: object
+ default: []
- - ${{ if ne(variables['Build.DefinitionName'], 'dotnet-maui') }}:
- - template: provision.yml
- parameters:
- checkoutDirectory: ${{ parameters.checkoutDirectory }}
- poolName: ${{ parameters.poolName }}
- provisionatorChannel: ${{ parameters.provisionatorChannel }}
- gitHubToken: ${{ parameters.gitHubToken }}
- skipAndroidImages: true
+- name: publishArtifacts
+ type: boolean
+ default: true
+
+steps:
- ${{ each step in parameters.prepareSteps }}:
- ${{ each pair in step }}:
@@ -116,6 +118,16 @@ steps:
displayName: 'Diff .NET Maui artifacts with NuGet'
workingDirectory: ${{ parameters.checkoutDirectory }}
+ # binaries for compliance scanning
+ - task: CopyFiles@2
+ displayName: 'Copy Binaries Files'
+ condition: succeeded()
+ inputs:
+ Contents: |
+ ${{ parameters.checkoutDirectory }}/src/Controls/src/Nuget/bin/Release/**/*.dll
+ TargetFolder: ${{ parameters.checkoutDirectory }}/artifacts/binaries
+ flattenFolders: false
+
# artifacts
- task: CopyFiles@2
condition: always()
@@ -128,7 +140,8 @@ steps:
${{ parameters.checkoutDirectory }}/eng/automation/SignList.xml
${{ parameters.checkoutDirectory }}/eng/automation/SignVerifyIgnore.txt
!${{ parameters.checkoutDirectory}}/artifacts/docs-packs/**
- TargetFolder: $(build.artifactstagingdirectory)
+ !${{ parameters.checkoutDirectory}}/artifacts/binaries/**
+ TargetFolder: ${{ parameters.artifactsPath }}
flattenFolders: true
- task: CopyFiles@2
@@ -139,7 +152,7 @@ steps:
Contents: |
metadata/**
api-diff/**
- TargetFolder: $(build.artifactstagingdirectory)
+ TargetFolder: ${{ parameters.artifactsPath }}
- task: CopyFiles@2
displayName: 'Copy Log Files'
@@ -147,37 +160,33 @@ steps:
inputs:
Contents: |
${{ parameters.checkoutDirectory }}/artifacts/logs/**
- TargetFolder: $(build.artifactstagingdirectory)/logs
+ TargetFolder: ${{ parameters.artifactsPath }}/logs
flattenFolders: true
-
- - task: PublishBuildArtifacts@1
- condition: always()
- displayName: publish artifacts
- inputs:
- ArtifactName: ${{ parameters.artifact }}
- # xml-docs
- - ${{ if eq(parameters.platform, 'Windows') }}:
+
+ - ${{ if eq(parameters.publishArtifacts, 'true') }}:
- task: PublishBuildArtifacts@1
condition: always()
- displayName: publish docs artifacts
- inputs:
- PathToPublish: ${{ parameters.checkoutDirectory }}/artifacts/docs-packs
- ArtifactName: xml-docs
-
- # binaries for compliance scanning
- - task: CopyFiles@2
- displayName: 'Copy Binaries Files'
- condition: succeeded()
+ displayName: publish artifacts
inputs:
- Contents: |
- ${{ parameters.checkoutDirectory }}/src/Controls/src/Nuget/bin/Release/**/*.dll
- TargetFolder: ${{ parameters.checkoutDirectory }}/binaries
- flattenFolders: false
-
- - task: PublishBuildArtifacts@1
- condition: succeeded()
- displayName: publish binaries artifacts
- inputs:
- PathToPublish: ${{ parameters.checkoutDirectory }}/binaries
- ArtifactName: ${{ parameters.artifactBinaries }}
-
+ ArtifactName: ${{ parameters.artifact }}
+ PathToPublish: ${{ parameters.artifactsPath }}
+
+ # xml-docs
+ - ${{ if eq(parameters.platform, 'Windows') }}:
+ - task: PublishBuildArtifacts@1
+ condition: always()
+ displayName: publish docs artifacts
+ inputs:
+ PathToPublish: ${{ parameters.checkoutDirectory }}/artifacts/docs-packs
+ ArtifactName: xml-docs
+
+ - task: PublishBuildArtifacts@1
+ condition: succeeded()
+ displayName: publish binaries artifacts
+ inputs:
+ PathToPublish: ${{ parameters.checkoutDirectory }}/artifacts/binaries
+ ArtifactName: ${{ parameters.artifactBinaries }}
+
+ - ${{ each step in parameters.postSteps }}:
+ - ${{ each pair in step }}:
+ ${{ pair.key }}: ${{ pair.value }}
\ No newline at end of file
diff --git a/eng/pipelines/common/sdk-insertion.yml b/eng/pipelines/common/sdk-insertion.yml
index 4225a29989bc..d10760fed1a2 100644
--- a/eng/pipelines/common/sdk-insertion.yml
+++ b/eng/pipelines/common/sdk-insertion.yml
@@ -1,7 +1,10 @@
parameters:
poolName: VSEngSS-MicroBuild2022-1ES
vmImage: ''
+ os: windows
pushMauiPackagesToMaestro: false
+ nugetArtifactName: nuget-signed
+ nugetArtifactPath: $(Build.StagingDirectory)\nuget-signed
jobs:
- job: create_artifact_statuses
@@ -9,30 +12,31 @@ jobs:
timeoutInMinutes: 60
pool:
name: ${{ parameters.poolName }}
- vmImage: ${{ parameters.vmImage }}
+ image: ${{ parameters.vmImage }}
+ os: ${{ parameters.os }}
variables:
- group: Publish-Build-Assets
steps:
- checkout: self
- task: DownloadPipelineArtifact@2
inputs:
- artifactName: nuget
- downloadPath: $(Build.StagingDirectory)\nuget
+ artifactName: ${{ parameters.nugetArtifactName }}
+ downloadPath: ${{ parameters.nugetArtifactPath }}
patterns: |
- **/signed/*.nupkg
+ *.nupkg
**/*.snupkg
**/additional-assets.zip
- task: DownloadPipelineArtifact@2
inputs:
artifactName: vs-msi-nugets
- downloadPath: $(Build.StagingDirectory)\nuget
+ downloadPath: ${{ parameters.nugetArtifactPath }}
- template: templates\common\upload-vs-insertion-artifacts.yml@sdk-insertions
parameters:
githubToken: $(github--pat--vs-mobiletools-engineering-service2)
githubContext: $(NupkgCommitStatusName)
blobName: $(NupkgCommitStatusName)
packagePrefix: maui
- artifactsPath: $(Build.StagingDirectory)\nuget
+ artifactsPath: ${{ parameters.nugetArtifactPath }}
yamlResourceName: yaml-templates
- template: templates\common\upload-vs-insertion-artifacts.yml@sdk-insertions
parameters:
@@ -67,7 +71,7 @@ jobs:
arguments: >-
-t:PushManifestToBuildAssetRegistry
-p:BuildAssetRegistryToken=$(MaestroAccessToken)
- -p:OutputPath=$(Build.StagingDirectory)\nuget\
+ -p:OutputPath=$(Build.StagingDirectory)\nuget-signed\
-v:n -bl:$(Build.StagingDirectory)\binlogs\push-bar-manifest.binlog
condition: and(succeeded(), eq('${{ parameters.pushMauiPackagesToMaestro }}', 'true'))
- powershell: |
diff --git a/eng/pipelines/common/sign.yml b/eng/pipelines/common/sign.yml
index eded95eca34a..cb8fbab9d1cd 100644
--- a/eng/pipelines/common/sign.yml
+++ b/eng/pipelines/common/sign.yml
@@ -1,28 +1,34 @@
+parameters:
+ poolName: Azure Pipelines
+ vmImage: windows-latest
+ os: windows
+ teamName: $(TeamName)
+ signType: 'Real'
+ dependsOn: []
+ stageName: 'nuget_signing'
+ displayName: 'Sign Nuget'
+
+
stages:
- - stage: nuget_signing
- dependsOn: pack_net
- displayName: Sign Nuget
+ - stage: ${{ parameters.stageName }}
+ dependsOn: ${{ parameters.dependsOn }}
+ displayName: ${{ parameters.displayName }}
jobs:
- template: sign-artifacts/jobs/v2.yml@yaml-templates
parameters:
- signType: Real
- teamName: $(TeamName)
- usePipelineArtifactTasks: false
- targetFolder: $(Build.ArtifactStagingDirectory)/nuget/signed
- signedArtifactName: nuget
- signedArtifactPath: signed
- displayName: Sign Phase
- condition: and(succeeded(), eq(variables.signingCondition, true))
-
+ signType: ${{ parameters.signType }}
+ teamName: ${{ parameters.teamName }}
+ condition: eq(variables.signingCondition, true)
+ use1ESTemplate: true
+ usePipelineArtifactTasks: true
+
- template: nuget-msi-convert/job/v3.yml@yaml-templates
parameters:
yamlResourceName: yaml-templates
- artifactName: nuget
- artifactPatterns: |
- **/signed/*.nupkg
- artifactPath: signed
- propsArtifactName: nuget
- signType: Real
+ artifactName: nuget-signed
+ propsArtifactName: nuget
+ signType: ${{ parameters.signType }}
+ use1ESTemplate: true
postConvertSteps:
- task: DownloadPipelineArtifact@2
inputs:
diff --git a/eng/pipelines/common/variables.yml b/eng/pipelines/common/variables.yml
index b113de6661af..53556c9d981a 100644
--- a/eng/pipelines/common/variables.yml
+++ b/eng/pipelines/common/variables.yml
@@ -24,11 +24,11 @@ variables:
- name: isPullRequest
value: $[eq(variables['Build.Reason'], 'PullRequest')]
- name: isLocHandoffBranch
- value: $[in(variables['Build.SourceBranch'], 'refs/heads/net9.0', 'refs/heads/net7.0', 'refs/heads/net8.0', 'refs/heads/main')]
+ value: $[in(variables['Build.SourceBranch'], 'refs/heads/net9.0', 'refs/heads/net8.0', 'refs/heads/net7.0', 'refs/heads/main')]
- name: signingCondition
value: $[or(
eq(variables['Sign'], 'true'),
- in(variables['Build.SourceBranch'], 'refs/heads/net9.0', 'refs/heads/net7.0', 'refs/heads/net8.0', 'refs/heads/main'),
+ in(variables['Build.SourceBranch'], 'refs/heads/net9.0', 'refs/heads/net8.0', 'refs/heads/net7.0', 'refs/heads/main'),
startsWith(variables['Build.SourceBranch'], 'refs/tags/'),
startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')
)]
diff --git a/eng/pipelines/handlers.yml b/eng/pipelines/handlers.yml
index 4a628624d718..7a13a1dbf9cc 100644
--- a/eng/pipelines/handlers.yml
+++ b/eng/pipelines/handlers.yml
@@ -67,28 +67,22 @@ parameters:
type: boolean
default: false
- - name: RunCompliance
- type: boolean
- default: false
-
- name: BuildConfigurations
type: object
default:
- Debug
- Release
+
- name: BuildPlatforms
type: object
default:
- name: Windows
poolName: $(windowsNet6VmPool)
vmImage: $(windowsNet6VmImage)
- bootsAndroid: $(Android.Msi)
artifact: build-windows
- name: macOS
poolName: $(macOSXNet6VmPool)
vmImage: $(macOSXNet6VmImage)
- bootsAndroid: $(Android.Pkg)
- bootsMacCatalyst: $(MacCatalyst.Pkg)
artifact: build-macos
- name: PackPlatforms
@@ -97,13 +91,10 @@ parameters:
- name: Windows
poolName: $(windowsNet6VmPool)
vmImage: $(windowsNet6VmImage)
- bootsAndroid: $(Android.Msi)
artifact: nuget
- name: macOS
poolName: $(macOSXNet6VmPool)
vmImage: $(macOSXNet6VmImage)
- bootsAndroid: $(Android.Pkg)
- bootsMacCatalyst: $(MacCatalyst.Pkg)
artifact: nuget-macos
- name: RunTemplatePlatforms
@@ -214,11 +205,18 @@ stages:
- template: common/pack.yml
parameters:
platform: ${{ PackPlatform.name }}
- poolName: ${{ PackPlatform.poolName }}
provisionatorChannel: ${{ parameters.provisionatorChannel }}
artifact: ${{ PackPlatform.artifact }}
+ artifactsPath: '(Build.ArtifactStagingDirectory)'
artifactBinaries: 'pack-binaries'
gitHubToken: $(github--pat--vs-mobiletools-engineering-service2)
+ prepareSteps:
+ - template: common/provision.yml
+ parameters:
+ checkoutDirectory: '$(System.DefaultWorkingDirectory)'
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+ gitHubToken: $(github--pat--vs-mobiletools-engineering-service2)
+ skipAndroidImages: true
- stage: samples_net
displayName: Test .NET MAUI Samples
@@ -285,23 +283,3 @@ stages:
- template: common/localization-handoff.yml # Process outgoing strings [Localization Handoff]
- template: common/localization-handback.yml # Process incoming translations and Create PR to main [Localization Handback]
- template: common/merge-translations-update.yml # Validating incoming translations strings and merge PR [Localization Handback]
- - ${{ if or(eq(variables['Build.Reason'], 'Schedule'), parameters.RunCompliance) }}:
- - template: security/full/v1.yml@yaml-templates
- parameters:
- stageDependsOn: 'pack_net'
- complianceEnabled: true
- complianceTimeoutInMinutes: 480
- scanArtifacts: ['pack-binaries']
- antiMalwareEnabled: true
- binSkimEnabled: true
- #binSkimTargetGlob: '$(Build.ArtifactStagingDirectory)\binaries-to-scan\pack-binaries\src\Controls\src\Nuget\bin\Release\net8.0\*.dll'
- sourceGdnSuppressionFile: $(Build.SourcesDirectory)\eng\automation\guardian\source.gdnsuppress
- tsaConfigFile: '$(Build.SourcesDirectory)\eng\automation\guardian\tsaoptions-v2.json'
- policheckExclusionFile: '$(System.DefaultWorkingDirectory)\eng\automation\guardian\PoliCheck.Exclusions.xml'
- policheckGdnSuppressionFilesFolder: '$(System.DefaultWorkingDirectory)\eng\automation\guardian'
- credScanEnabled: true
- credScanSuppressionFile: '$(System.DefaultWorkingDirectory)\eng\automation\guardian\CredScanSuppressions.json'
- enableCodeInspector: true
- apiScanEnabled: true
- apiScanSoftwareName: 'MAUI'
- apiScanSoftwareVersionNum: 8.0
diff --git a/eng/pipelines/maui-release-internal.yml b/eng/pipelines/maui-release-internal.yml
index 193ccce5347f..6d6679b87222 100644
--- a/eng/pipelines/maui-release-internal.yml
+++ b/eng/pipelines/maui-release-internal.yml
@@ -22,65 +22,83 @@ trigger:
variables:
- - template: /eng/pipelines/common/variables.yml
-
+ - template: /eng/pipelines/common/variables.yml@self
+
parameters:
- name: provisionatorChannel
displayName: 'Provisionator channel'
type: string
default: 'latest' # Support for launching a build against a Provisionator PR (e.g., pr/[github-account-name]/[pr-number]) as a means to test in-progress Provisionator changes
- - name: PackPlatforms
+ - name: VM_IMAGE_HOST
+ type: object
+ default:
+ name: AzurePipelines-EO
+ image: 1ESPT-Windows2022
+ os: windows
+
+ - name: PackPlatform
type: object
default:
- - name: Windows
- poolName: $(windowsNet6VmPool)
- vmImage: $(windowsNet6VmImage)
- artifact: nuget
+ name: Windows
+ artifact: nuget
-# resources:
-# repositories:
-# - repository: yaml-templates
-# type: github
-# name: xamarin/yaml-templates
-# endpoint: xamarin
-# ref: refs/heads/main
-# - repository: sdk-insertions
-# type: github
-# name: xamarin/sdk-insertions
-# ref: refs/heads/main
-# endpoint: xamarin
+ - name: Skip1ESComplianceTasks
+ default: false
-stages:
+resources:
+ repositories:
+ - repository: 1ESPipelineTemplates
+ type: git
+ name: 1ESPipelineTemplates/1ESPipelineTemplates
+ ref: refs/tags/release
- - stage: pack_net
- displayName: Pack .NET MAUI
- dependsOn: []
- jobs:
- - ${{ each PackPlatform in parameters.PackPlatforms }}:
- - job: pack_net_${{ PackPlatform.name }}
- workspace:
- clean: all
- displayName: ${{ PackPlatform.name }}
- timeoutInMinutes: 240
- pool:
- name: ${{ PackPlatform.poolName }}
- vmImage: ${{ PackPlatform.vmImage }}
- ${{ if startsWith(PackPlatform.poolName, 'VSEng-VSMac-Xamarin-Shared') }}:
- demands:
- - macOS.Name -equals Ventura
- - macOS.Architecture -equals x64
- steps:
- - template: common/pack.yml
- parameters:
- platform: ${{ PackPlatform.name }}
- poolName: ${{ PackPlatform.poolName }}
- provisionatorChannel: ${{ parameters.provisionatorChannel }}
- artifact: ${{ PackPlatform.artifact }}
+extends:
+ template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
+ parameters:
+ pool: ${{ parameters.VM_IMAGE_HOST }}
+ sdl:
+ ${{ if eq('${{ parameters.Skip1ESComplianceTasks }}', 'true') }}:
+ enableAllTools: false
+ binskim:
+ scanOutputDirectoryOnly: true
+ codeql:
+ runSourceLanguagesInSourceAnalysis: true
+ policheck:
+ enabled: false
+ justification: Built in task does not support multi-language scanning
+ spotBugs:
+ enabled: false
+ justification: 'Failing with "Could not successfully find the java tool launcher"'
+ sourceRepositoriesToScan:
+ exclude:
+ - repository: yaml-templates
+ suppression:
+ suppressionFile: $(Build.SourcesDirectory)\eng\automation\guardian\source.gdnsuppress
+ stages:
+ - stage: pack_net
+ displayName: Pack .NET MAUI
+ dependsOn: []
+ jobs:
+ - job: pack_net_${{ parameters.PackPlatform.name }}
+ workspace:
+ clean: all
+ displayName: ${{ parameters.PackPlatform.name }}
+ timeoutInMinutes: 240
+
+ pool: ${{ parameters.VM_IMAGE_HOST }}
+
+ templateContext:
+ outputs:
+ - output: pipelineArtifact
+ displayName: 'Publish the ${{ parameters.PackPlatform.artifact }} artifacts'
+ artifactName: ${{ parameters.PackPlatform.artifact }}
+ targetPath: '(Build.ArtifactStagingDirectory)'
- - ${{ if or(eq(variables['System.TeamProject'], 'DevDiv'), eq(variables['Build.DefinitionName'], 'dotnet-maui')) }}:
- - template: common/sign.yml # Sign only using the private server
- # - template: common/insertion.yml # Insert on VS and SDK
- # parameters:
- # poolName: $(windowsNet6VmPool)
- # vmImage: $(windowsNet6VmImage)
+ steps:
+ - template: /eng/pipelines/common/pack.yml@self
+ parameters:
+ platform: ${{ parameters.PackPlatform.name }}
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+ artifact: ${{ parameters.PackPlatform.artifact }}
+ artifactsPath: '(Build.ArtifactStagingDirectory)'
\ No newline at end of file
diff --git a/eng/pipelines/maui-release.yml b/eng/pipelines/maui-release.yml
index 6b7bf2d6f2fc..9428e1d8c1bc 100644
--- a/eng/pipelines/maui-release.yml
+++ b/eng/pipelines/maui-release.yml
@@ -22,7 +22,7 @@ trigger:
variables:
- - template: /eng/pipelines/common/variables.yml
+ - template: /eng/pipelines/common/variables.yml@self
- template: templates/common/vs-release-vars.yml@sdk-insertions
parameters:
@@ -34,13 +34,23 @@ parameters:
type: string
default: 'latest' # Support for launching a build against a Provisionator PR (e.g., pr/[github-account-name]/[pr-number]) as a means to test in-progress Provisionator changes
- - name: PackPlatforms
+ - name: VM_IMAGE_HOST
type: object
default:
- - name: Windows
- poolName: $(windowsNet6VmPool)
- vmImage: $(windowsNet6VmImage)
- artifact: nuget
+ name: AzurePipelines-EO
+ image: 1ESPT-Windows2022
+ os: windows
+
+ - name: PackPlatform
+ type: object
+ default:
+ name: Windows
+ artifact: nuget
+ binariesArtifact: pack-binaries
+ docsArtifact: xml-docs
+
+ - name: Skip1ESComplianceTasks
+ default: false
resources:
repositories:
@@ -54,36 +64,103 @@ resources:
name: xamarin/sdk-insertions
ref: refs/heads/main
endpoint: xamarin
+ - repository: 1ESPipelineTemplates
+ type: git
+ name: 1ESPipelineTemplates/1ESPipelineTemplates
+ ref: refs/tags/release
+
+extends:
+ template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
+ parameters:
+ pool: ${{ parameters.VM_IMAGE_HOST }}
+ sdl:
+ ${{ if eq('${{ parameters.Skip1ESComplianceTasks }}', 'true') }}:
+ enableAllTools: false
+ binskim:
+ scanOutputDirectoryOnly: true
+ codeql:
+ runSourceLanguagesInSourceAnalysis: true
+ policheck:
+ enabled: true
+ spotBugs:
+ enabled: false
+ justification: 'Failing with "Could not successfully find the java tool launcher"'
+ sourceRepositoriesToScan:
+ exclude:
+ - repository: yaml-templates
+ suppression:
+ suppressionFile: $(Build.SourcesDirectory)\eng\automation\guardian\source.gdnsuppress
+ stages:
+ - stage: pack_net
+ displayName: Pack .NET MAUI
+ dependsOn: []
+ jobs:
+ - job: pack_net_${{ parameters.PackPlatform.name }}
+ workspace:
+ clean: all
+ displayName: ${{ parameters.PackPlatform.name }}
+ timeoutInMinutes: 240
+
+ pool: ${{ parameters.VM_IMAGE_HOST }}
+
+ templateContext:
+ outputs:
+ - output: pipelineArtifact
+ displayName: 'Publish the ${{ parameters.PackPlatform.artifact }} artifacts'
+ artifactName: ${{ parameters.PackPlatform.artifact }}
+ targetPath: '(Build.ArtifactStagingDirectory)'
+
+ - output: pipelineArtifact
+ displayName: 'Publish the ${{ parameters.PackPlatform.binariesArtifact }} artifacts'
+ artifactName: ${{ parameters.PackPlatform.binariesArtifact }}
+ targetPath: '$(System.DefaultWorkingDirectory)/artifacts/binaries'
+
+ - output: pipelineArtifact
+ displayName: 'Publish the ${{ parameters.PackPlatform.docsArtifact }} artifacts'
+ artifactName: ${{ parameters.PackPlatform.docsArtifact }}
+ targetPath: '$(System.DefaultWorkingDirectory)/artifacts/docs-packs'
+
+ steps:
+ - template: /eng/pipelines/common/pack.yml@self
+ parameters:
+ publishArtifacts: false
+ platform: ${{ parameters.PackPlatform.name }}
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+ artifact: ${{ parameters.PackPlatform.artifact }}
+ artifactBinaries: ${{ parameters.PackPlatform.binariesArtifact }}
+ artifactsPath: '(Build.ArtifactStagingDirectory)'
+ prepareSteps:
+ - template: /eng/pipelines/common/provision.yml@self
+ parameters:
+ checkoutDirectory: '$(System.DefaultWorkingDirectory)'
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+ gitHubToken: $(github--pat--vs-mobiletools-engineering-service2)
+ skipAndroidImages: true
-stages:
+ - ${{ if eq(variables['System.TeamProject'], 'devdiv') }}: # Sign only using the private server
+ - template: /eng/pipelines/common/sign.yml@self
+ parameters:
+ dependsOn: ['pack_net']
+ stageName: 'nuget_signing'
+ poolName: ${{ parameters.VM_IMAGE_HOST.name }}
+ vmImage: ${{ parameters.VM_IMAGE_HOST.image }}
+ os: ${{ parameters.VM_IMAGE_HOST.os }}
+
+ - template: /eng/pipelines/common/insertion.yml@self # Insert on VS and SDK
+ parameters:
+ dependsOn: ['nuget_signing']
+ stageName: 'sdk_insertion'
+ poolName: ${{ parameters.VM_IMAGE_HOST.name }}
+ vmImage: ${{ parameters.VM_IMAGE_HOST.image }}
+ os: ${{ parameters.VM_IMAGE_HOST.os }}
+ pushMauiPackagesToMaestro: ${{ parameters.pushMauiPackagesToMaestro }}
- - stage: pack_net
- displayName: Pack .NET MAUI
- dependsOn: []
- jobs:
- - ${{ each PackPlatform in parameters.PackPlatforms }}:
- - job: pack_net_${{ PackPlatform.name }}
- workspace:
- clean: all
- displayName: ${{ PackPlatform.name }}
- timeoutInMinutes: 240
- pool:
- name: ${{ PackPlatform.poolName }}
- vmImage: ${{ PackPlatform.vmImage }}
- ${{ if startsWith(PackPlatform.poolName, 'VSEng-VSMac-Xamarin-Shared') }}:
- demands:
- - macOS.Name -equals Ventura
- - macOS.Architecture -equals x64
- steps:
- - template: common/pack.yml
- parameters:
- platform: ${{ PackPlatform.name }}
- poolName: ${{ PackPlatform.poolName }}
- provisionatorChannel: ${{ parameters.provisionatorChannel }}
- artifact: ${{ PackPlatform.artifact }}
+ - template: /eng/pipelines/common/apiscan.yml@self # ApiScan
+ parameters:
+ dependsOn: ['pack_net']
+ poolName: ${{ parameters.VM_IMAGE_HOST.name }}
+ vmImage: ${{ parameters.VM_IMAGE_HOST.image }}
+ os: ${{ parameters.VM_IMAGE_HOST.os }}
+ scanArtifacts: ['${{ parameters.PackPlatform.binariesArtifact }}']
- - ${{ if eq(variables['System.TeamProject'], 'devdiv') }}:
- - template: common/sign.yml # Sign only using the private server
- - template: common/insertion.yml # Insert on VS and SDK
- parameters:
- pushMauiPackagesToMaestro: ${{ parameters.pushMauiPackagesToMaestro }}
+
diff --git a/src/Compatibility/ControlGallery/src/Issues.Shared/Issue10134.cs b/src/Compatibility/ControlGallery/src/Issues.Shared/Issue10134.cs
index 33039cbf8ba6..4c29e18a20dd 100644
--- a/src/Compatibility/ControlGallery/src/Issues.Shared/Issue10134.cs
+++ b/src/Compatibility/ControlGallery/src/Issues.Shared/Issue10134.cs
@@ -44,6 +44,7 @@ protected override void Init()
#if UITEST && __SHELL__
[Test]
+ [Compatibility.UITests.FailsOnMauiIOS]
public void TopTabsDontScrollBackToStartWhenSelected()
{
var element1 = RunningApp.WaitForElement("Tab 1", "Shell hasn't loaded")[0].Rect;
diff --git a/src/Compatibility/Core/src/Compatibility.csproj b/src/Compatibility/Core/src/Compatibility.csproj
index c7fcc4d7bcd0..9c185620b649 100644
--- a/src/Compatibility/Core/src/Compatibility.csproj
+++ b/src/Compatibility/Core/src/Compatibility.csproj
@@ -67,7 +67,6 @@
-
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10947.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10947.xaml
new file mode 100644
index 000000000000..54721464bfec
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10947.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+ First Item
+ Second Item
+ Third Item
+ Fourth Item
+ Fifth Item
+ Sixth Item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10947.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10947.xaml.cs
new file mode 100644
index 000000000000..dda04562112e
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10947.xaml.cs
@@ -0,0 +1,19 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+using Microsoft.Maui.Platform;
+using Microsoft.Maui.Controls.PlatformConfiguration;
+using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
+using System;
+
+namespace Maui.Controls.Sample.Issues;
+
+[XamlCompilation(XamlCompilationOptions.Compile)]
+[Issue(IssueTracker.Github, 10947, "CollectionView Header and Footer Scrolling", PlatformAffected.iOS)]
+
+public partial class Issue10947 : ContentPage
+{
+ public Issue10947()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20903.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20903.xaml
new file mode 100644
index 000000000000..1cd285463610
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20903.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20903.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20903.xaml.cs
new file mode 100644
index 000000000000..e3d09fc32c3e
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20903.xaml.cs
@@ -0,0 +1,31 @@
+#nullable enable
+
+using System;
+using Microsoft.Maui.Controls;
+
+namespace Maui.Controls.Sample.Issues;
+
+[Issue(IssueTracker.Github, 20903, "Double-tap behavior should work correctly when adding a new double-tap handler", PlatformAffected.UWP)]
+public partial class Issue20903 : ContentPage
+{
+ private int _callbackCalledCount;
+
+ public Issue20903()
+ {
+ InitializeComponent();
+ }
+
+ private void OnLabelBeingDoubleTapped(object? sender, TappedEventArgs e)
+ {
+ _callbackCalledCount++;
+ eventCountLabel.Text = _callbackCalledCount.ToString();
+ }
+
+ private void addDoubleTapHandlerButton_Clicked(object sender, EventArgs e)
+ {
+ TapGestureRecognizer doubleTapGestureRecognizer = new();
+ doubleTapGestureRecognizer.Tapped += OnLabelBeingDoubleTapped;
+ doubleTapGestureRecognizer.NumberOfTapsRequired = 2;
+ myLabel.GestureRecognizers.Add(doubleTapGestureRecognizer);
+ }
+}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Platforms/Android/MainActivity.cs b/src/Controls/samples/Controls.Sample.UITests/Platforms/Android/MainActivity.cs
index a0eb1ce025ec..f9ad743df995 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Platforms/Android/MainActivity.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Platforms/Android/MainActivity.cs
@@ -8,6 +8,7 @@ namespace Maui.Controls.Sample.Platform
[Activity(
Theme = "@style/Maui.SplashTheme",
MainLauncher = true,
+ LaunchMode = LaunchMode.SingleTask,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode)]
[IntentFilter(
new[] { Microsoft.Maui.ApplicationModel.Platform.Intent.ActionAppAction },
diff --git a/src/Controls/samples/Controls.Sample/Pages/Core/ShellGalleries/ShellChromeGallery.cs b/src/Controls/samples/Controls.Sample/Pages/Core/ShellGalleries/ShellChromeGallery.cs
index 469754b94148..508f04d8f04b 100644
--- a/src/Controls/samples/Controls.Sample/Pages/Core/ShellGalleries/ShellChromeGallery.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/Core/ShellGalleries/ShellChromeGallery.cs
@@ -101,6 +101,11 @@ void OnToggleFlyoutBackgroundColor(object sender, EventArgs e)
flyoutBackgroundColor.Background = AppShell.FlyoutBackground;
}
+ void OnToggleNavBarHasShadow(object sender, EventArgs e)
+ {
+ Shell.SetNavBarHasShadow(this, !Shell.GetNavBarHasShadow(this));
+ }
+
void OnToggleNavBarIsVisible(object sender, EventArgs e)
{
Shell.SetNavBarIsVisible(this, !Shell.GetNavBarIsVisible(this));
diff --git a/src/Controls/samples/Controls.Sample/Pages/Core/ShellGalleries/ShellChromeGallery.xaml b/src/Controls/samples/Controls.Sample/Pages/Core/ShellGalleries/ShellChromeGallery.xaml
index f1b1ee64a747..6f990b9e7725 100644
--- a/src/Controls/samples/Controls.Sample/Pages/Core/ShellGalleries/ShellChromeGallery.xaml
+++ b/src/Controls/samples/Controls.Sample/Pages/Core/ShellGalleries/ShellChromeGallery.xaml
@@ -21,6 +21,10 @@
Text="Flyout Background Color"
Style="{StaticResource Headline}"/>
+
+
diff --git a/src/Controls/src/Core/Border/Border.cs b/src/Controls/src/Core/Border/Border.cs
index 9086bd938089..9074341a0bcc 100644
--- a/src/Controls/src/Core/Border/Border.cs
+++ b/src/Controls/src/Core/Border/Border.cs
@@ -109,6 +109,9 @@ void NotifyStrokeChanges()
_strokeChanged ??= (sender, e) => OnPropertyChanged(nameof(Stroke));
_strokeProxy ??= new();
_strokeProxy.Subscribe(stroke, _strokeChanged);
+
+ OnParentResourcesChanged(this.GetMergedResources());
+ ((IElementDefinition)this).AddResourcesChangedListener(stroke.OnParentResourcesChanged);
}
}
@@ -121,6 +124,8 @@ void StopNotifyingStrokeChanges()
if (stroke is not null)
{
+ ((IElementDefinition)this).RemoveResourcesChangedListener(stroke.OnParentResourcesChanged);
+
SetInheritedBindingContext(stroke, null);
_strokeProxy?.Unsubscribe();
}
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs
index 5255cc37aef0..4196ae7081df 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs
@@ -563,13 +563,14 @@ void UpdateNavBarHasShadow(Page page)
if (Shell.GetNavBarHasShadow(page))
{
- if (_appBarElevation > 0)
- _appBar.SetElevation(_appBarElevation);
+ if (_appBarElevation <= 0)
+ _appBarElevation = _appBar.Context.ToPixels(4);
+
+ _appBar.SetElevation(_appBarElevation);
}
else
{
// 4 is the default
- _appBarElevation = _appBar.Context.ToPixels(4);
_appBar.SetElevation(0f);
}
}
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs
index fc3f8dea72a5..0c8b06c923d6 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs
@@ -535,11 +535,7 @@ public override CGRect Frame
public override void LayoutSubviews()
{
- if (Height == null || Height == 0)
- {
- UpdateFrame(Superview);
- }
-
+ UpdateFrame(Superview);
base.LayoutSubviews();
}
@@ -553,9 +549,6 @@ void UpdateFrame(UIView newSuper)
{
if (newSuper is not null && newSuper.Bounds != CGRect.Empty)
{
- if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11)))
- Frame = new CGRect(Frame.X, newSuper.Bounds.Y, Frame.Width, newSuper.Bounds.Height);
-
Height = newSuper.Bounds.Height;
}
}
diff --git a/src/Controls/src/Core/Element/Element.cs b/src/Controls/src/Core/Element/Element.cs
index 95da9e965e7a..8e283413e466 100644
--- a/src/Controls/src/Core/Element/Element.cs
+++ b/src/Controls/src/Core/Element/Element.cs
@@ -928,6 +928,10 @@ void SetHandler(IElementHandler newHandler)
}
}
+ ///
+ /// Registers the specified to this element.
+ ///
+ /// The effect to be registered.
void IEffectControlProvider.RegisterEffect(Effect effect)
{
if (effect is RoutingEffect re && re.Inner != null)
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
index 420baed2a5b5..f1b8a02df27b 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
@@ -177,6 +177,13 @@ public override void ViewDidLoad()
EnsureLayoutInitialized();
}
+ public override void LoadView()
+ {
+ base.LoadView();
+
+ CollectionView = new MauiCollectionView(CGRect.Empty, ItemsViewLayout);
+ }
+
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs b/src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs
new file mode 100644
index 000000000000..e99148ddf62a
--- /dev/null
+++ b/src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs
@@ -0,0 +1,17 @@
+using CoreGraphics;
+using UIKit;
+
+namespace Microsoft.Maui.Controls.Handlers.Items;
+
+internal class MauiCollectionView : UICollectionView
+{
+ public MauiCollectionView(CGRect frame, UICollectionViewLayout layout) : base(frame, layout)
+ {
+ }
+
+ public override void ScrollRectToVisible(CGRect rect, bool animated)
+ {
+ if (!KeyboardAutoManagerScroll.IsKeyboardAutoScrollHandling)
+ base.ScrollRectToVisible(rect, animated);
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs b/src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs
index f4945af4ed26..404711e1134b 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs
@@ -303,7 +303,7 @@ void UpdateVisualStates()
void UpdateSelectionColor()
{
- if (PlatformHandler.VirtualView is not View view)
+ if (PlatformHandler?.VirtualView is not View view)
{
return;
}
@@ -313,6 +313,11 @@ void UpdateSelectionColor()
void UpdateSelectionColor(View view)
{
+ if (SelectedBackgroundView is null)
+ {
+ return;
+ }
+
// Prevents the use of default color when there are VisualStateManager with Selected state setting the background color
// First we check whether the cell has the default selected background color; if it does, then we should check
// to see if the cell content is the VSM to set a selected color
diff --git a/src/Controls/src/Core/IEffectControlProvider.cs b/src/Controls/src/Core/IEffectControlProvider.cs
index be427acf4578..8c2d42c9f085 100644
--- a/src/Controls/src/Core/IEffectControlProvider.cs
+++ b/src/Controls/src/Core/IEffectControlProvider.cs
@@ -1,8 +1,15 @@
#nullable disable
namespace Microsoft.Maui.Controls
{
+ ///
+ /// Provides the functionality to register an to an element.
+ ///
public interface IEffectControlProvider
{
+ ///
+ /// Registers the specified to an element.
+ ///
+ /// The effect to be registered.
void RegisterEffect(Effect effect);
}
}
\ No newline at end of file
diff --git a/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs b/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
index a8d6b9d8fcd8..7216735e8de0 100644
--- a/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
+++ b/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
@@ -8,6 +8,7 @@
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Views;
+using AndroidX.AppCompat.Widget;
using AndroidX.CoordinatorLayout.Widget;
using AndroidX.Fragment.App;
using AndroidX.ViewPager2.Widget;
@@ -24,6 +25,7 @@
using ADrawableCompat = AndroidX.Core.Graphics.Drawable.DrawableCompat;
using AView = Android.Views.View;
using Color = Microsoft.Maui.Graphics.Color;
+using Microsoft.Maui.ApplicationModel;
namespace Microsoft.Maui.Controls.Handlers
{
@@ -60,6 +62,9 @@ internal class TabbedPageManager
TabLayoutMediator _tabLayoutMediator;
IDisposable _pendingFragment;
+ NavigationRootManager NavigationRootManager { get; }
+ internal static bool IsDarkTheme => ((Application.Current?.RequestedTheme ?? AppInfo.RequestedTheme) == AppTheme.Dark);
+
public TabbedPageManager(IMauiContext context)
{
_context = context;
@@ -629,7 +634,7 @@ protected virtual ColorStateList GetItemIconTintColorState()
{
if (IsBottomTabPlacement)
{
- if (_orignalTabIconColors == null)
+ if (_orignalTabIconColors is null)
_orignalTabIconColors = _bottomNavigationView.ItemIconTintList;
}
// this ensures that existing behavior doesn't change
@@ -645,7 +650,42 @@ protected virtual ColorStateList GetItemIconTintColorState()
if (_newTabIconColors != null)
return _newTabIconColors;
- int defaultColor = barItemColor.ToPlatform().ToArgb();
+ int defaultColor;
+
+ if (barItemColor is not null)
+ {
+ defaultColor = barItemColor.ToPlatform().ToArgb();
+ }
+ else
+ {
+ var styledAttributes =
+ TintTypedArray.ObtainStyledAttributes(_context.Context, null, Resource.Styleable.NavigationBarView, Resource.Attribute.bottomNavigationStyle, 0);
+
+ try
+ {
+ var defaultColors = styledAttributes.GetColorStateList(Resource.Styleable.NavigationBarView_itemIconTint);
+ if (defaultColors is not null)
+ {
+ defaultColor = defaultColors.DefaultColor;
+ }
+ else
+ {
+ // These are the defaults currently set inside android
+ // It's very unlikely we'll hit this path because the
+ // NavigationBarView_itemIconTint should always resolve
+ // But just in case, we'll just hard code to some defaults
+ // instead of leaving the application in a broken state
+ if(IsDarkTheme)
+ defaultColor = new Color(1, 1, 1, 0.6f).ToPlatform();
+ else
+ defaultColor = new Color(0, 0, 0, 0.6f).ToPlatform();
+ }
+ }
+ finally
+ {
+ styledAttributes.Recycle();
+ }
+ }
if (barItemColor == null && _orignalTabIconColors != null)
defaultColor = _orignalTabIconColors.DefaultColor;
@@ -924,4 +964,4 @@ void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Controls/src/Core/Platform/GestureManager/GesturePlatformManager.Windows.cs b/src/Controls/src/Core/Platform/GestureManager/GesturePlatformManager.Windows.cs
index a0a6a6deb5d1..939d240462b1 100644
--- a/src/Controls/src/Core/Platform/GestureManager/GesturePlatformManager.Windows.cs
+++ b/src/Controls/src/Core/Platform/GestureManager/GesturePlatformManager.Windows.cs
@@ -318,6 +318,7 @@ void ClearContainerEventHandlers()
_container.DragOver -= HandleDragOver;
_container.Drop -= HandleDrop;
_container.Tapped -= OnTap;
+ _container.DoubleTapped -= OnTap;
_container.ManipulationDelta -= OnManipulationDelta;
_container.ManipulationStarted -= OnManipulationStarted;
_container.ManipulationCompleted -= OnManipulationCompleted;
diff --git a/src/Controls/src/Core/Platform/Windows/CollectionView/ItemContentControl.cs b/src/Controls/src/Core/Platform/Windows/CollectionView/ItemContentControl.cs
index d8d4ee380503..3d74c838a162 100644
--- a/src/Controls/src/Core/Platform/Windows/CollectionView/ItemContentControl.cs
+++ b/src/Controls/src/Core/Platform/Windows/CollectionView/ItemContentControl.cs
@@ -147,7 +147,7 @@ protected override void OnContentChanged(object oldContent, object newContent)
internal void Realize()
{
var dataContext = FormsDataContext;
- var formsTemplate = FormsDataTemplate;
+ var dataTemplate = FormsDataTemplate;
var container = FormsContainer;
var mauiContext = MauiContext;
@@ -158,17 +158,21 @@ internal void Realize()
itemsView.RemoveLogicalChild(e);
}
- if (dataContext is null || formsTemplate is null || container is null || mauiContext is null)
+ if (dataContext is null || dataTemplate is null || container is null || mauiContext is null)
{
return;
}
- if (Content is null || _currentTemplate != formsTemplate)
+ // If the template is a DataTemplateSelector, we need to get the actual DataTemplate to use
+ // for our recycle check below
+ dataTemplate = dataTemplate.SelectDataTemplate(dataContext, container);
+
+ if (Content is null || _currentTemplate != dataTemplate)
{
// If the content has never been realized (i.e., this is a new instance),
// or if we need to switch DataTemplates (because this instance is being recycled)
// then we'll need to create the content from the template
- _visualElement = formsTemplate.CreateContent(dataContext, container) as VisualElement;
+ _visualElement = dataTemplate.CreateContent() as VisualElement;
_visualElement.BindingContext = dataContext;
_handler = _visualElement.ToHandler(mauiContext);
@@ -181,7 +185,7 @@ internal void Realize()
SetNativeStateConsistent(_visualElement);
// Keep track of the template in case this instance gets reused later
- _currentTemplate = formsTemplate;
+ _currentTemplate = dataTemplate;
}
else
{
diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
index 9e244e69ce47..b49fb4b75d42 100644
--- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -73,6 +73,7 @@ Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommand.get -> S
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommand.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommandParameter.get -> object!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommandParameter.set -> void
+override Microsoft.Maui.Controls.Handlers.Items.ItemsViewController.LoadView() -> void
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
index 1dcb7384588e..b89030ccd22f 100644
--- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -72,6 +72,7 @@ Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommand.get -> S
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommand.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommandParameter.get -> object!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommandParameter.set -> void
+override Microsoft.Maui.Controls.Handlers.Items.ItemsViewController.LoadView() -> void
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Core/Shapes/Shape.cs b/src/Controls/src/Core/Shapes/Shape.cs
index e4fa57df0e85..922022a76ede 100644
--- a/src/Controls/src/Core/Shapes/Shape.cs
+++ b/src/Controls/src/Core/Shapes/Shape.cs
@@ -203,6 +203,9 @@ void NotifyFillChanges()
_fillChanged ??= (sender, e) => OnPropertyChanged(nameof(Fill));
_fillProxy ??= new();
_fillProxy.Subscribe(fill, _fillChanged);
+
+ OnParentResourcesChanged(this.GetMergedResources());
+ ((IElementDefinition)this).AddResourcesChangedListener(fill.OnParentResourcesChanged);
}
}
@@ -215,6 +218,8 @@ void StopNotifyingFillChanges()
if (fill is not null)
{
+ ((IElementDefinition)this).RemoveResourcesChangedListener(fill.OnParentResourcesChanged);
+
SetInheritedBindingContext(fill, null);
_fillProxy?.Unsubscribe();
}
@@ -233,6 +238,9 @@ void NotifyStrokeChanges()
_strokeChanged ??= (sender, e) => OnPropertyChanged(nameof(Stroke));
_strokeProxy ??= new();
_strokeProxy.Subscribe(stroke, _strokeChanged);
+
+ OnParentResourcesChanged(this.GetMergedResources());
+ ((IElementDefinition)this).AddResourcesChangedListener(stroke.OnParentResourcesChanged);
}
}
@@ -245,6 +253,8 @@ void StopNotifyingStrokeChanges()
if (stroke is not null)
{
+ ((IElementDefinition)this).RemoveResourcesChangedListener(stroke.OnParentResourcesChanged);
+
SetInheritedBindingContext(stroke, null);
_strokeProxy?.Unsubscribe();
}
diff --git a/src/Controls/src/Core/VisualElement/VisualElement.cs b/src/Controls/src/Core/VisualElement/VisualElement.cs
index c4dcb91d7691..243e4a49eda8 100644
--- a/src/Controls/src/Core/VisualElement/VisualElement.cs
+++ b/src/Controls/src/Core/VisualElement/VisualElement.cs
@@ -279,13 +279,15 @@ static void OnTransformChanged(BindableObject bindable, object oldValue, object
public static readonly BindableProperty BackgroundProperty = BindableProperty.Create(nameof(Background), typeof(Brush), typeof(VisualElement), Brush.Default,
propertyChanging: (bindable, oldvalue, newvalue) =>
{
- if (oldvalue != null)
- (bindable as VisualElement)?.StopNotifyingBackgroundChanges();
+ if (oldvalue == null) return;
+
+ (bindable as VisualElement)?.StopNotifyingBackgroundChanges();
},
propertyChanged: (bindable, oldvalue, newvalue) =>
{
- if (newvalue != null)
- (bindable as VisualElement)?.NotifyBackgroundChanges();
+ if (newvalue == null) return;
+
+ (bindable as VisualElement)?.NotifyBackgroundChanges();
});
WeakBackgroundChangedProxy _backgroundProxy;
@@ -316,6 +318,9 @@ void NotifyBackgroundChanges()
_backgroundChanged ??= (sender, e) => OnPropertyChanged(nameof(Background));
_backgroundProxy ??= new();
_backgroundProxy.Subscribe(background, _backgroundChanged);
+
+ OnParentResourcesChanged(this.GetMergedResources());
+ ((IElementDefinition)this).AddResourcesChangedListener(background.OnParentResourcesChanged);
}
}
@@ -327,6 +332,8 @@ void StopNotifyingBackgroundChanges()
if (background != null)
{
+ ((IElementDefinition)this).RemoveResourcesChangedListener(background.OnParentResourcesChanged);
+
SetInheritedBindingContext(background, null);
_backgroundProxy?.Unsubscribe();
}
@@ -2065,6 +2072,9 @@ void NotifyShadowChanges()
_shadowChanged ??= (sender, e) => OnPropertyChanged(nameof(Shadow));
_shadowProxy ??= new();
_shadowProxy.Subscribe(shadow, _shadowChanged);
+
+ OnParentResourcesChanged(this.GetMergedResources());
+ ((IElementDefinition)this).AddResourcesChangedListener(shadow.OnParentResourcesChanged);
}
}
@@ -2074,6 +2084,8 @@ void StopNotifyingShadowChanges()
if (shadow is not null)
{
+ ((IElementDefinition)this).RemoveResourcesChangedListener(shadow.OnParentResourcesChanged);
+
SetInheritedBindingContext(shadow, null);
_shadowProxy?.Unsubscribe();
}
diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs
index 1789a67ae26d..36ad00303c9d 100644
--- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs
+++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs
@@ -460,5 +460,53 @@ await CreateHandlerAndAddToWindow(collectionView, async h
}
record MyRecord(string Name);
+
+
+ [Fact]
+ public async Task SettingSelectedItemAfterModifyingCollectionDoesntCrash()
+ {
+ SetupBuilder();
+
+ var Items = new ObservableCollection();
+ var collectionView = new CollectionView
+ {
+ ItemTemplate = new DataTemplate(() =>
+ {
+ var label = new Label()
+ {
+ Text = "Margin Test",
+ Margin = new Thickness(10, 10, 10, 10),
+ HeightRequest = 50,
+ };
+
+ label.SetBinding(Label.TextProperty, new Binding("."));
+ return label;
+ }),
+ ItemsSource = Items,
+ SelectionMode = SelectionMode.Single
+ };
+
+ var vsl = new VerticalStackLayout()
+ {
+ collectionView
+ };
+
+ vsl.HeightRequest = 500;
+ vsl.WidthRequest = 500;
+
+ var frame = collectionView.Frame;
+
+ await vsl.AttachAndRun(async (handler) =>
+ {
+ await WaitForUIUpdate(frame, collectionView);
+ frame = collectionView.Frame;
+ await Task.Yield();
+ Items.Add("Item 1");
+ Items.Add("Item 2");
+ Items.Add("Item 3");
+ collectionView.SelectedItem = Items.FirstOrDefault(x => x == "Item 3");
+ await WaitForUIUpdate(frame, collectionView);
+ }, MauiContext, (view) => CreateHandlerAsync(view));
+ }
}
}
diff --git a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs
index 9b56366a799f..9fd0cfd58d21 100644
--- a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs
+++ b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs
@@ -28,6 +28,40 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Shell)]
public partial class ShellTests
{
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task CanHideNavBarShadow(bool navBarHasShadow)
+ {
+ SetupBuilder();
+
+ var contentPage = new ContentPage() { Title = "Flyout Item" };
+ var shell = await CreateShellAsync(shell =>
+ {
+ shell.CurrentItem = new FlyoutItem() { Items = { contentPage } };
+
+ shell.FlyoutContent = new VerticalStackLayout()
+ {
+ new Label(){ Text = "Flyout Content"}
+ };
+
+ Shell.SetNavBarHasShadow(contentPage, navBarHasShadow);
+ });
+
+ await CreateHandlerAndAddToWindow(shell, async (handler) =>
+ {
+ await Task.Delay(100);
+
+ var platformToolbar = GetPlatformToolbar(handler);
+ var appBar = platformToolbar.Parent.GetParentOfType();
+
+ if(navBarHasShadow)
+ Assert.True(appBar.Elevation > 0);
+ else
+ Assert.True(appBar.Elevation == 0);
+ });
+ }
+
protected async Task CheckFlyoutState(ShellRenderer handler, bool desiredState)
{
var drawerLayout = GetDrawerLayout(handler);
diff --git a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.iOS.cs
index 8340dc0c3901..69f103e4e3dd 100644
--- a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.iOS.cs
+++ b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.iOS.cs
@@ -18,7 +18,9 @@
using Xunit;
using static Microsoft.Maui.DeviceTests.AssertHelpers;
using ShellHandler = Microsoft.Maui.Controls.Handlers.Compatibility.ShellRenderer;
-using UIModalPresentationStyle = Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.UIModalPresentationStyle;
+using Microsoft.Maui.DeviceTests.Stubs;
+using Microsoft.Maui.Handlers;
+using Microsoft.Maui.Hosting;
namespace Microsoft.Maui.DeviceTests
{
@@ -281,6 +283,120 @@ await CreateHandlerAndAddToWindow(shell, async (handler) =>
});
}
+ [Fact(DisplayName = "TitleView can use constraints to expand area")]
+ public async Task TitleViewConstraints()
+ {
+ EnsureHandlerCreated(builder =>
+ {
+ builder.ConfigureMauiHandlers(handlers =>
+ {
+ SetupShellHandlers(handlers);
+ handlers.AddHandler(typeof(Shell), typeof(CustomShellHandler));
+ });
+ });
+
+ var shellTitleView = new VerticalStackLayout { BackgroundColor = Colors.Green };
+ var titleViewContent = new Label { Text = "Full Height TitleView" };
+ shellTitleView.Children.Add(titleViewContent);
+
+ var label = new Label { Text = "Page Content", Background = Colors.LightBlue };
+
+ var shell = await CreateShellAsync(shell =>
+ {
+ Shell.SetTitleView(shell, shellTitleView);
+
+ shell.CurrentItem = new ContentPage(){
+ Content = new VerticalStackLayout {
+ Background = Colors.Gray,
+ Children = { label }
+ }
+ };
+ });
+
+ await CreateHandlerAndAddToWindow(shell, async (handler) =>
+ {
+ await Task.Delay(50);
+ var titleView = GetTitleView(handler) as UIView;
+ var originalFrame = (handler as CustomShellHandler).PreviousFrame;
+ var expandedFrame = titleView.Frame;
+ var relativeTitleViewFrame = titleView.ConvertRectToView(titleView.Bounds, null);
+ var uiLabel = label.ToPlatform();
+ var relativeLabelFrame = uiLabel.ConvertRectToView(uiLabel.Bounds, null);
+
+ // Make sure the titleView is touching the label. Happens by default on iPhone but not on iPad
+ Assert.True(relativeTitleViewFrame.Bottom == relativeLabelFrame.Top);
+ Assert.True(expandedFrame.Width > originalFrame.Width);
+ });
+ }
+
+ class CustomShellHandler : ShellRenderer
+ {
+ protected override IShellNavBarAppearanceTracker CreateNavBarAppearanceTracker() => new CustomShellNavBarAppearanceTracker(this, base.CreateNavBarAppearanceTracker()) {handler = this};
+
+ public CGRect PreviousFrame { get; set; }
+ }
+
+ class CustomShellNavBarAppearanceTracker : IShellNavBarAppearanceTracker
+ {
+ readonly IShellContext _context;
+ readonly IShellNavBarAppearanceTracker _baseTracker;
+
+ public CGRect previousFrame { get; set; } = CGRect.Empty;
+
+ public CustomShellHandler handler { get; set; }
+
+ public CustomShellNavBarAppearanceTracker(IShellContext context, IShellNavBarAppearanceTracker baseTracker)
+ {
+ _context = context;
+ _baseTracker = baseTracker;
+ }
+
+ public void Dispose() => _baseTracker.Dispose();
+
+ public void ResetAppearance(UINavigationController controller) => _baseTracker.ResetAppearance(controller);
+
+ public void SetAppearance(UINavigationController controller, ShellAppearance appearance) => _baseTracker.SetAppearance(controller, appearance);
+
+ public void SetHasShadow(UINavigationController controller, bool hasShadow) => _baseTracker.SetHasShadow(controller, hasShadow);
+
+ public void UpdateLayout(UINavigationController controller)
+ {
+ UIView titleView = Shell.GetTitleView(_context.Shell.CurrentPage)?.Handler?.PlatformView as UIView ?? Shell.GetTitleView(_context.Shell)?.Handler?.PlatformView as UIView;
+
+ UIView parentView = GetParentByType(titleView, typeof(UIKit.UIControl));
+ handler.PreviousFrame = parentView.Frame;
+
+ if (parentView != null)
+ {
+ // height constraint
+ NSLayoutConstraint.Create(parentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, parentView.Superview, NSLayoutAttribute.Bottom, 1.0f, 0.0f).Active = true;
+ NSLayoutConstraint.Create(parentView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, parentView.Superview, NSLayoutAttribute.Top, 1.0f, 0.0f).Active = true;
+
+ // width constraint
+ NSLayoutConstraint.Create(parentView, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, parentView.Superview, NSLayoutAttribute.Leading, 1.0f, 0.0f).Active = true;
+ NSLayoutConstraint.Create(parentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, parentView.Superview, NSLayoutAttribute.Trailing, 1.0f, 0.0f).Active = true;
+ }
+ _baseTracker.UpdateLayout(controller);
+ }
+
+ static UIView GetParentByType(UIView view, Type type)
+ {
+ UIView currentView = view;
+
+ while (currentView != null)
+ {
+ if (currentView.GetType().UnderlyingSystemType == type)
+ {
+ break;
+ }
+
+ currentView = currentView.Superview;
+ }
+
+ return currentView;
+ }
+ }
+
protected async Task OpenFlyout(ShellRenderer shellRenderer, TimeSpan? timeOut = null)
{
var flyoutView = GetFlyoutPlatformView(shellRenderer);
diff --git a/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs
index 26f6e7185c0b..1944b1714653 100644
--- a/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs
+++ b/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs
@@ -41,6 +41,36 @@ await CreateHandlerAndAddToWindow(new Window(tabbedPage), (ha
});
}
+ [Fact]
+ public async Task SettingJustSelectedATabColorOnBottomTabsDoesntCrash()
+ {
+ SetupBuilder();
+ var tabbedPage = new TabbedPage
+ {
+ Children =
+ {
+ new ContentPage() { Title = "Page1"}
+ ,new ContentPage() { Title = "Page2"}
+ ,new ContentPage() { Title = "Page3"}
+
+ },
+ SelectedTabColor = Colors.Red,
+ };
+
+ Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific.TabbedPage
+ .SetToolbarPlacement(tabbedPage, Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific.ToolbarPlacement.Bottom);
+
+ tabbedPage.SelectedTabColor = Colors.Red;
+
+ bool success = false;
+ await CreateHandlerAndAddToWindow(tabbedPage, handler =>
+ {
+ success = true;
+ });
+
+ Assert.True(success);
+ }
+
[Fact]
public async Task ChangingBottomTabAttributesDoesntRecreateBottomTabs()
{
diff --git a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
index 4bd6f6318a75..e0476db578cf 100644
--- a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
+++ b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
@@ -42,6 +42,7 @@ void SetupBuilder()
handlers.AddHandler();
handlers.AddHandler();
handlers.AddHandler();
+ handlers.AddHandler();
handlers.AddHandler();
handlers.AddHandler();
handlers.AddHandler();
@@ -74,6 +75,7 @@ void SetupBuilder()
[InlineData(typeof(Polyline))]
[InlineData(typeof(RefreshView))]
[InlineData(typeof(ScrollView))]
+ [InlineData(typeof(SearchBar))]
[InlineData(typeof(Slider))]
[InlineData(typeof(Stepper))]
[InlineData(typeof(SwipeView))]
diff --git a/src/Controls/tests/UITests/Tests/BorderUITests.cs b/src/Controls/tests/UITests/Tests/BorderUITests.cs
index 56885702c2d4..58b4d707ce2a 100644
--- a/src/Controls/tests/UITests/Tests/BorderUITests.cs
+++ b/src/Controls/tests/UITests/Tests/BorderUITests.cs
@@ -1,4 +1,5 @@
-using UITest.Appium;
+using NUnit.Framework;
+using UITest.Appium;
using UITest.Core;
namespace Microsoft.Maui.AppiumTests
@@ -26,6 +27,7 @@ protected override void FixtureTeardown()
// TODO: Enable this as a test once fully working
//[Test]
+ //[Category(UITestCategories.Border)]
public void BordersWithVariousShapes()
{
App.WaitForElement("TargetView");
diff --git a/src/Controls/tests/UITests/Tests/ButtonUITests.cs b/src/Controls/tests/UITests/Tests/ButtonUITests.cs
index fbee87eab580..cd0ae371c205 100644
--- a/src/Controls/tests/UITests/Tests/ButtonUITests.cs
+++ b/src/Controls/tests/UITests/Tests/ButtonUITests.cs
@@ -20,6 +20,7 @@ protected override void NavigateToGallery()
}
[Test]
+ [Category(UITestCategories.Button)]
public void Clicked()
{
var remote = new EventViewContainerRemote(UITestContext, Test.Button.Clicked);
diff --git a/src/Controls/tests/UITests/Tests/CarouselViewUITests.cs b/src/Controls/tests/UITests/Tests/CarouselViewUITests.cs
index cac5827cd7ac..e9ebf60c13d6 100644
--- a/src/Controls/tests/UITests/Tests/CarouselViewUITests.cs
+++ b/src/Controls/tests/UITests/Tests/CarouselViewUITests.cs
@@ -26,6 +26,7 @@ protected override void FixtureTeardown()
}
[Test]
+ [Category(UITestCategories.CarouselView)]
public async Task CarouselViewSetPosition()
{
if (Device != TestDevice.Android)
@@ -42,6 +43,7 @@ public async Task CarouselViewSetPosition()
}
[Test]
+ [Category(UITestCategories.CarouselView)]
public void CarouselViewGoToNextCurrentItem()
{
if (Device != TestDevice.Android)
diff --git a/src/Controls/tests/UITests/Tests/CheckBoxUITests.cs b/src/Controls/tests/UITests/Tests/CheckBoxUITests.cs
index 0a0131af679a..5aa94d622113 100644
--- a/src/Controls/tests/UITests/Tests/CheckBoxUITests.cs
+++ b/src/Controls/tests/UITests/Tests/CheckBoxUITests.cs
@@ -18,6 +18,7 @@ protected override void NavigateToGallery()
}
[Test]
+ [Category(UITestCategories.CheckBox)]
public override void _IsEnabled()
{
if (Device == TestDevice.Mac ||
diff --git a/src/Controls/tests/UITests/Tests/DragAndDropUITests.cs b/src/Controls/tests/UITests/Tests/DragAndDropUITests.cs
index 0b3ce6fa8636..1e13aef407c4 100644
--- a/src/Controls/tests/UITests/Tests/DragAndDropUITests.cs
+++ b/src/Controls/tests/UITests/Tests/DragAndDropUITests.cs
@@ -27,6 +27,7 @@ protected override void FixtureTeardown()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public void DragEvents()
{
App.WaitForElement("TargetView");
@@ -51,6 +52,7 @@ public void DragEvents()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public void DragAndDropBetweenLayouts()
{
App.WaitForElement("TargetView");
@@ -80,6 +82,7 @@ public void DragAndDropBetweenLayouts()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public void PlatformDragEventArgs()
{
App.WaitForElement("TargetView");
@@ -164,6 +167,7 @@ public void PlatformDragEventArgs()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public void DragStartEventCoordinates()
{
App.WaitForElement("TargetView");
@@ -197,6 +201,7 @@ public void DragStartEventCoordinates()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public void DragEventCoordinates()
{
App.WaitForElement("TargetView");
@@ -235,6 +240,7 @@ public void DragEventCoordinates()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public void DropEventCoordinates()
{
App.WaitForElement("TargetView");
diff --git a/src/Controls/tests/UITests/Tests/EditorUITests.cs b/src/Controls/tests/UITests/Tests/EditorUITests.cs
index 22a8926160e7..0239dfe53e0a 100644
--- a/src/Controls/tests/UITests/Tests/EditorUITests.cs
+++ b/src/Controls/tests/UITests/Tests/EditorUITests.cs
@@ -18,6 +18,7 @@ protected override void NavigateToGallery()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public override void _IsEnabled()
{
if (Device == TestDevice.Mac ||
diff --git a/src/Controls/tests/UITests/Tests/GestureRecognizerUITests.cs b/src/Controls/tests/UITests/Tests/GestureRecognizerUITests.cs
index 7dbc2e2678a3..8f227d42f3b0 100644
--- a/src/Controls/tests/UITests/Tests/GestureRecognizerUITests.cs
+++ b/src/Controls/tests/UITests/Tests/GestureRecognizerUITests.cs
@@ -26,6 +26,7 @@ protected override void FixtureTeardown()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public void PointerGestureTest()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.iOS },
@@ -45,6 +46,7 @@ public void PointerGestureTest()
}
[Test]
+ [Category(UITestCategories.Gestures)]
public void DoubleTap()
{
App.WaitForElement("TargetView");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue10947.cs b/src/Controls/tests/UITests/Tests/Issues/Issue10947.cs
new file mode 100644
index 000000000000..79c433aef50f
--- /dev/null
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue10947.cs
@@ -0,0 +1,38 @@
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.AppiumTests.Issues;
+
+public class Issue10947 : _IssuesUITest
+{
+ public Issue10947(TestDevice device)
+ : base(device)
+ { }
+
+ public override string Issue => "CollectionView Header and Footer Scrolling";
+ string HeaderEntry => "HeaderEntry";
+ string FooterEntry => "FooterEntry";
+
+ [Test]
+ public void CollectionViewHeaderShouldNotScroll()
+ {
+ var headerEntry = App.WaitForElement(HeaderEntry);
+ var headerLocation = headerEntry.GetRect();
+ var footerEntry = App.WaitForElement(FooterEntry);
+ var footerLocation = headerEntry.GetRect();
+
+ App.Click(HeaderEntry);
+
+ var newHeaderEntry = App.WaitForElement(HeaderEntry);
+ var newHeaderLocation = headerEntry.GetRect();
+ Assert.AreEqual(headerLocation, newHeaderLocation);
+
+ App.Click(FooterEntry);
+
+ var newFooterEntry = App.WaitForElement(FooterEntry);
+ var newFooterLocation = headerEntry.GetRect();
+
+ Assert.AreEqual(footerLocation, newFooterLocation);
+ }
+}
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue11501.cs b/src/Controls/tests/UITests/Tests/Issues/Issue11501.cs
index 9999d9c3038f..958146af29a2 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue11501.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue11501.cs
@@ -16,7 +16,7 @@ public Issue11501(TestDevice device) : base(device)
[TestCase("SwapFlyoutPage")]
[TestCase("SwapTabbedPage")]
[TestCase("RemoveAddTabs")]
- public async Task MakingFragmentRelatedChangesWhileAppIsBackgroundedFails(string scenario)
+ public void MakingFragmentRelatedChangesWhileAppIsBackgroundedFails(string scenario)
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Mac, TestDevice.Windows });
@@ -24,10 +24,8 @@ public async Task MakingFragmentRelatedChangesWhileAppIsBackgroundedFails(string
{
App.WaitForElement(scenario);
App.Click(scenario);
+ App.WaitForElement("BackgroundMe");
App.BackgroundApp();
-
- // Wait for app to finish backgrounding
- await Task.Yield();
App.WaitForNoElement("BackgroundMe");
App.ForegroundApp();
App.WaitForElement("Restore");
@@ -35,6 +33,8 @@ public async Task MakingFragmentRelatedChangesWhileAppIsBackgroundedFails(string
}
catch
{
+ SaveUIDiagnosticInfo();
+
// Just in case these tests leave the app in an unreliable state
App.ResetApp();
FixtureSetup();
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue12211.cs b/src/Controls/tests/UITests/Tests/Issues/Issue12211.cs
index d3717d13dde3..da9144f00cba 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue12211.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue12211.cs
@@ -15,6 +15,7 @@ public Issue12211(TestDevice device) : base(device)
public override string Issue => "[Android] BoxView Opacity not working";
[Test]
+ [Category(UITestCategories.BoxView)]
public void WhenChangingBoxViewOpacityThenValueIsCorrectlySet()
{
App.WaitForElement(buttonId);
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue14257.cs b/src/Controls/tests/UITests/Tests/Issues/Issue14257.cs
index 24182243db7a..67fae153df46 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue14257.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue14257.cs
@@ -11,6 +11,7 @@ public Issue14257(TestDevice device) : base(device) { }
public override string Issue => "VerticalStackLayout inside Scrollview: Button at the bottom not clickable on IOS";
[Test]
+ [Category(UITestCategories.ScrollView)]
public void ResizeScrollViewAndTapButtonTest()
{
// Tapping the Resize button will change the height of the ScrollView content
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue14557.cs b/src/Controls/tests/UITests/Tests/Issues/Issue14557.cs
index fbc12bea4a1f..40c5629c27e6 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue14557.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue14557.cs
@@ -10,6 +10,7 @@ public Issue14557(TestDevice device) : base(device) { }
public override string Issue => "CollectionView header and footer not displaying on Windows";
[Test]
+ [Category(UITestCategories.CollectionView)]
public void HeaderAndFooterRender()
{
App.WaitForElement("collectionView");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue15330.cs b/src/Controls/tests/UITests/Tests/Issues/Issue15330.cs
index a1e96fa1c22d..912870e2d617 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue15330.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue15330.cs
@@ -13,6 +13,7 @@ public Issue15330(TestDevice device)
public override string Issue => "Grid wrong Row height";
[Test]
+ [Category(UITestCategories.Layout)]
public void Issue15330Test()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.iOS },
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue15357.cs b/src/Controls/tests/UITests/Tests/Issues/Issue15357.cs
index 0a415c15ff8c..3b2670ef6925 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue15357.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue15357.cs
@@ -15,6 +15,7 @@ public Issue15357(TestDevice device) : base(device)
public override string Issue => "IsVisible binding not showing items again if Shadow is set";
[Test]
+ [Category(UITestCategories.ListView)]
public void WhenTapButtonThenListViewsChangesVisibility()
{
App.WaitForElement(buttonId);
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue15826.cs b/src/Controls/tests/UITests/Tests/Issues/Issue15826.cs
index c6c535bb0f9a..4f2ae15a15f1 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue15826.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue15826.cs
@@ -15,6 +15,7 @@ public Issue15826(TestDevice device) : base(device)
public override string Issue => "ListView visibility doesn't work well";
[Test]
+ [Category(UITestCategories.ListView)]
public void WhenTapButtonThenListViewsChangesVisibility()
{
App.WaitForElement(buttonId);
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue16094.cs b/src/Controls/tests/UITests/Tests/Issues/Issue16094.cs
index 5f75dd822313..91a1b430e712 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue16094.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue16094.cs
@@ -13,6 +13,7 @@ public Issue16094(TestDevice device)
public override string Issue => "Shadows don't respect control shape";
[Test]
+ [Category(UITestCategories.Editor)]
public void Issue16094Test()
{
App.WaitForElement("EditorControl");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue16320.cs b/src/Controls/tests/UITests/Tests/Issues/Issue16320.cs
index c6ba967dcbbe..fae11c7ecf77 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue16320.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue16320.cs
@@ -13,6 +13,7 @@ public Issue16320(TestDevice device)
public override string Issue => "Adding an item to a CollectionView with linear layout crashes";
[Test]
+ [Category(UITestCategories.CollectionView)]
public void Issue16320Test()
{
// TODO: It looks like this test has never passed on Android, failing with
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue16321.cs b/src/Controls/tests/UITests/Tests/Issues/Issue16321.cs
index 8c1e2bee2243..0c02e35076d4 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue16321.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue16321.cs
@@ -11,6 +11,7 @@ public Issue16321(TestDevice device) : base(device) { }
public override string Issue => "Alerts Open on top of current presented view";
[Test]
+ [Category(UITestCategories.DisplayAlert)]
public void OpenAlertWithModals()
{
this.IgnoreIfPlatforms(new[]
@@ -23,6 +24,7 @@ public void OpenAlertWithModals()
}
[Test]
+ [Category(UITestCategories.DisplayAlert)]
public void OpenAlertWithNewUIWindow()
{
this.IgnoreIfPlatforms(new[]
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue16386.cs b/src/Controls/tests/UITests/Tests/Issues/Issue16386.cs
index 46928bd43093..66ad7106910c 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue16386.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue16386.cs
@@ -14,6 +14,7 @@ public Issue16386(TestDevice device)
public override string Issue => "Process the hardware enter key as \"Done\"";
[Test]
+ [Category(UITestCategories.Entry)]
public void HittingEnterKeySendsDone()
{
this.IgnoreIfPlatforms(new[]
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue16499.cs b/src/Controls/tests/UITests/Tests/Issues/Issue16499.cs
index ec3476acc67e..67f651e775b2 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue16499.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue16499.cs
@@ -13,6 +13,7 @@ public Issue16499(TestDevice device) : base(device)
public override string Issue => "Crash when using NavigationPage.TitleView and Restarting App";
[Test]
+ [Category(UITestCategories.Navigation)]
public void AppDoesntCrashWhenReusingSameTitleView()
{
App.WaitForElement("SuccessLabel");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue16561.cs b/src/Controls/tests/UITests/Tests/Issues/Issue16561.cs
index be555e5b8e08..87886b62e1b2 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue16561.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue16561.cs
@@ -21,6 +21,7 @@ public Issue16561(TestDevice device) : base(device)
public override string Issue => "Quick single taps on Android have wrong second tap location";
[Test]
+ [Category(UITestCategories.Label)]
public void TapTwoPlacesQuickly()
{
// https://github.com/dotnet/maui/issues/17435
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue16787.cs b/src/Controls/tests/UITests/Tests/Issues/Issue16787.cs
index 4d7cb6999a79..8a2a53269e99 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue16787.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue16787.cs
@@ -13,6 +13,7 @@ public Issue16787(TestDevice device) : base(device)
public override string Issue => "CollectionView runtime binding errors when loading the ItemSource asynchronously";
[Test]
+ [Category(UITestCategories.CollectionView)]
public void CollectionViewBindingContextOnlyChangesOnce()
{
Assert.AreEqual("1", App.WaitForElement("LabelBindingCount").GetText());
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue17022.cs b/src/Controls/tests/UITests/Tests/Issues/Issue17022.cs
index cdbdd1d39d8d..9de8d7537e7f 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue17022.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue17022.cs
@@ -14,7 +14,8 @@ public Issue17022(TestDevice device)
// TODO: Add shell navigation bar tests when we can call shell in UITest
[Test]
- [TestCase("NewNavigationPageButton", false)]
+ [Category(UITestCategories.Navigation)]
+ [TestCase("NewNavigationPageButton", false)]
[TestCase("NewNavigationPageTransparentButton", false)]
[TestCase("NewNavigationPageTranslucentButton", false)]
[TestCase("NewNavigationPageTransparentTranslucentButton", false)]
@@ -69,7 +70,6 @@ public void Issue17022Test(string testButtonID, bool isTopOfScreen, bool require
{
App.WaitForElement("PopPageButton").Click();
}
-
}
}
}
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue17347.cs b/src/Controls/tests/UITests/Tests/Issues/Issue17347.cs
index 5934779f73e1..2c0db910e5cc 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue17347.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue17347.cs
@@ -13,6 +13,7 @@ public Issue17347(TestDevice device) : base(device)
public override string Issue => "Setting a new TitleView on an already created page crashes iOS";
[Test]
+ [Category(UITestCategories.Page)]
public void AppDoesntCrashWhenSettingNewTitleViewOnExistingPage()
{
try
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue17366.cs b/src/Controls/tests/UITests/Tests/Issues/Issue17366.cs
index 2a0a32831863..86d9bf45efe6 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue17366.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue17366.cs
@@ -13,6 +13,7 @@ public Issue17366(TestDevice device) : base(device)
public override string Issue => "Wrong gray color using transparent in iOS gradients";
[Test]
+ [Category(UITestCategories.Brush)]
public void Issue17366Test()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows },
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue17400.cs b/src/Controls/tests/UITests/Tests/Issues/Issue17400.cs
index 87e9d66a1f6e..3c4ce40c42d1 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue17400.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue17400.cs
@@ -13,6 +13,7 @@ public Issue17400(TestDevice device)
public override string Issue => "CollectionView wrong Layout";
[Test]
+ [Category(UITestCategories.CollectionView)]
public void Issue17400Test()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.iOS, TestDevice.Android, TestDevice.Mac },
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue17453.cs b/src/Controls/tests/UITests/Tests/Issues/Issue17453.cs
index 95037f397973..79285278dfeb 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue17453.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue17453.cs
@@ -11,6 +11,7 @@ public Issue17453(TestDevice device) : base(device) { }
public override string Issue => "Clear Entry text tapping the clear button not working";
[Test]
+ [Category(UITestCategories.Entry)]
public void EntryClearButtonWorksEntryDoesntClearWhenNotClickingOnClear()
{
// https://github.com/dotnet/maui/issues/17453
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue17490.cs b/src/Controls/tests/UITests/Tests/Issues/Issue17490.cs
index 2f8cec39fda3..5a1eee49bb30 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue17490.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue17490.cs
@@ -13,6 +13,7 @@ public Issue17490(TestDevice device) : base(device)
public override string Issue => "Crash using Pinvoke.SetParent to create Window as Child";
[Test]
+ [Category(UITestCategories.Window)]
public void AppDoesntCrashWhenOpeningWinUIWindowParentedToCurrentWindow()
{
this.IgnoreIfPlatforms(new[]
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue17610.cs b/src/Controls/tests/UITests/Tests/Issues/Issue17610.cs
index cffd4f613741..947133bb7081 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue17610.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue17610.cs
@@ -16,6 +16,7 @@ public Issue17610(TestDevice device) : base(device)
public override string Issue => "Cancelling Refresh With Slow Scroll Leaves Refresh Icon Visible";
[Test]
+ [Category(UITestCategories.RefreshView)]
public void RefreshIconDisappearsWhenUserCancelsRefreshByScrollingBackUp()
{
this.IgnoreIfPlatforms(new[]
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue17789.cs b/src/Controls/tests/UITests/Tests/Issues/Issue17789.cs
index 64ee7d9a158e..f2dbe4068d6b 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue17789.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue17789.cs
@@ -13,6 +13,7 @@ public Issue17789(TestDevice device) : base(device)
public override string Issue => "ContentPage BackgroundImageSource not working";
[Test]
+ [Category(UITestCategories.Page)]
public void ContentPageBackgroundImageSourceWorks()
{
App.WaitForElement("WaitForStubControl");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18111.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18111.cs
index 658c5a0bc7f5..dcce741df479 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18111.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18111.cs
@@ -13,6 +13,7 @@ public Issue18111(TestDevice device) : base(device)
public override string Issue => "Setting MaximumTrackColor on Slider has no effect";
[Test]
+ [Category(UITestCategories.Slider)]
public void SettingMaximumTrackColorOnSliderWorks()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Windows }, "Regression test validating the design differences between iOS and Mac specifically");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18282.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18282.cs
index d7d63feff9e7..93bc6f58b830 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18282.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18282.cs
@@ -11,6 +11,7 @@ public Issue18282(TestDevice device) : base(device) { }
public override string Issue => "The editor placeholder can't able to changed, It's default placed at center";
[Test]
+ [Category(UITestCategories.Editor)]
public void EditorPlaceholderPosition()
{
this.IgnoreIfPlatforms(new[]
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18617.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18617.cs
index 4c4e9aa2258d..9f5b5bae4806 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18617.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18617.cs
@@ -13,6 +13,7 @@ public Issue18617(TestDevice device)
public override string Issue => "Button Command CanExecute can disable the control";
[Test]
+ [Category(UITestCategories.Button)]
public void CommandCanExecuteDisableButton()
{
App.WaitForElement("WaitForStubControl");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18623.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18623.cs
index 3a4ef1dcb8d6..3ce1d2f2b92f 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18623.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18623.cs
@@ -13,6 +13,7 @@ public Issue18623(TestDevice device) : base(device)
public override string Issue => "Entry IsPassword obscure the text";
[Test]
+ [Category(UITestCategories.Entry)]
public async Task EntryIsPasswordObscureText()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.iOS },
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18645.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18645.cs
index f371992127c2..46d8b772a89b 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18645.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18645.cs
@@ -13,6 +13,7 @@ public Issue18645(TestDevice device) : base(device)
public override string Issue => "Editor MaxLength property works as expected";
[Test]
+ [Category(UITestCategories.Editor)]
public void EditorMaxLengthWorks()
{
App.WaitForElement("WaitForStubControl");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18647.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18647.cs
index b9222a9252fb..1e51648f05fd 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18647.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18647.cs
@@ -14,6 +14,7 @@ public Issue18647(TestDevice device) : base(device)
public override string Issue => "Editor TextTransform property works as expected";
[Test]
+ [Category(UITestCategories.Editor)]
public void EditorTextTransformWorks()
{
App.WaitForElement("WaitForStubControl");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18675.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18675.cs
index e29b31af3cfd..70fe4ce0fa2e 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18675.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18675.cs
@@ -13,6 +13,7 @@ public Issue186751(TestDevice device) : base(device)
public override string Issue => "Editor IsReadOnly property prevent from modifying the text";
[Test]
+ [Category(UITestCategories.Editor)]
public async Task EditorIsReadOnlyPreventModify()
{
App.WaitForElement("WaitForStubControl");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18706.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18706.cs
index 323f78ea4ff5..39635f809bee 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18706.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18706.cs
@@ -11,6 +11,7 @@ public Issue18706(TestDevice device) : base(device) { }
public override string Issue => "Editor Background works";
[Test]
+ [Category(UITestCategories.Editor)]
public void EditorBackgroundWorks()
{
App.WaitForElement("WaitForStubControl");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18740.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18740.cs
index 7edd26c23093..25f39f105279 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18740.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18740.cs
@@ -13,6 +13,7 @@ public Issue18740(TestDevice device)
public override string Issue => "Virtual keyboard appears with focus on Entry";
[Test]
+ [Category(UITestCategories.Entry)]
[TestCase("Entry")]
[TestCase("Editor")]
[TestCase("SearchBar")]
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18751.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18751.cs
index 687e8f73b70a..0c098fa34f19 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18751.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18751.cs
@@ -13,6 +13,7 @@ public Issue18751(TestDevice device) : base(device)
public override string Issue => "Can scroll CollectionView inside RefreshView";
[Test]
+ [Category(UITestCategories.CollectionView)]
[Ignore("This test is failing on iOS17, https://github.com/dotnet/maui/issues/20582")]
public async Task Issue18751Test()
{
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18754.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18754.cs
index 00048271c337..f9a4292e53ad 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18754.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18754.cs
@@ -11,6 +11,7 @@ public Issue18754(TestDevice device) : base(device) { }
public override string Issue => "[D9] Editor IsReadOnly works";
[Test]
+ [Category(UITestCategories.Editor)]
public void Issue18754Test()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Mac, TestDevice.Windows },
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue18896.cs b/src/Controls/tests/UITests/Tests/Issues/Issue18896.cs
index d4dcfef6627e..4055a6900d96 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue18896.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue18896.cs
@@ -15,6 +15,7 @@ public Issue18896(TestDevice device) : base(device)
public override string Issue => "Can scroll ListView inside RefreshView";
[Test]
+ [Category(UITestCategories.ListView)]
public async Task Issue18896Test()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.iOS, TestDevice.Mac },
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue19283.cs b/src/Controls/tests/UITests/Tests/Issues/Issue19283.cs
index 33cb5d7f0d70..aede24dfcb56 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue19283.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue19283.cs
@@ -11,6 +11,7 @@ public Issue19283(TestDevice device) : base(device) { }
public override string Issue => "PointerOver VSM Breaks Button";
[Test]
+ [Category(UITestCategories.Button)]
public void ButtonStillWorksWhenItHasPointerOverVSMSet()
{
App.Click("btn");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue19329.cs b/src/Controls/tests/UITests/Tests/Issues/Issue19329.cs
index 1996c4bfbbde..51faa6023337 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue19329.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue19329.cs
@@ -12,6 +12,7 @@ public Issue19329(TestDevice device) : base(device) { }
public override string Issue => "Pointer gestures should work with relative positions correctly";
[Test]
+ [Category(UITestCategories.Gestures)]
public void RelativePointerPositionIsComputedCorrectly()
{
_ = App.WaitForElement("TapHere");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue19379.cs b/src/Controls/tests/UITests/Tests/Issues/Issue19379.cs
index cfd631b85e64..11e77a4eccfe 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue19379.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue19379.cs
@@ -13,6 +13,7 @@ public Issue19379(TestDevice device)
public override string Issue => "Not able to update CollectionView header";
[Test]
+ [Category(UITestCategories.CollectionView)]
public void UpdateCollectionViewHeaderTest()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows });
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue19657.cs b/src/Controls/tests/UITests/Tests/Issues/Issue19657.cs
index c393586abbb0..43e2a82d581c 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue19657.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue19657.cs
@@ -12,6 +12,7 @@ public Issue19657(TestDevice device) : base(device) { }
public override string Issue => "CarouselView Content disappears when 'Loop' is false and inside ScrollView";
[Test]
+ [Category(UITestCategories.CarouselView)]
public void CarouselItemLoadsInCorrectPosition()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows },
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue19956.cs b/src/Controls/tests/UITests/Tests/Issues/Issue19956.cs
index c5fe2146cae1..dadf88c8b052 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue19956.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue19956.cs
@@ -13,7 +13,8 @@ public Issue19956(TestDevice device) : base(device) { }
public override string Issue => "Sticky headers and bottom content insets";
[Test]
- public void ContentAccountsForStickyHeaders()
+ [Category(UITestCategories.Entry)]
+ public void ContentAccountsForStickyHeaders()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows },
"This is an iOS Keyboard Scrolling issue.");
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue20903.cs b/src/Controls/tests/UITests/Tests/Issues/Issue20903.cs
new file mode 100644
index 000000000000..e117ec656965
--- /dev/null
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue20903.cs
@@ -0,0 +1,33 @@
+using System.Drawing;
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.AppiumTests.Issues;
+
+public class Issue20903 : _IssuesUITest
+{
+ public Issue20903(TestDevice device) : base(device) { }
+
+ public override string Issue => "Double-tap behavior should work correctly when adding a new double-tap handler";
+
+ [Test]
+ public async Task RegisterTwoDoubleTapHandlersAndCheckNumberOfFiredEventsAsync()
+ {
+ _ = App.WaitForElement("AddDoubleTapHandlerButton");
+
+ App.Click("AddDoubleTapHandlerButton");
+ App.Click("AddDoubleTapHandlerButton");
+
+ App.DoubleClick("MyLabel");
+ await Task.Delay(500);
+ App.DoubleClick("MyLabel");
+
+ // Wait for all event handler calls.
+ await Task.Delay(1000);
+
+ IUIElement element = App.FindElement("EventCountLabel");
+ string? count = element.GetText();
+ Assert.AreEqual("4", count);
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue3525.cs b/src/Controls/tests/UITests/Tests/Issues/Issue3525.cs
index 0a04adb6fa83..3f9a8c893044 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue3525.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue3525.cs
@@ -17,6 +17,7 @@ public Issue3525(TestDevice device)
public override string Issue => "Finicky tap gesture recognition on Spans";
[Test]
+ [Category(UITestCategories.Label)]
public void SpanRegionClicking()
{
if (Device == TestDevice.Mac)
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue5191.cs b/src/Controls/tests/UITests/Tests/Issues/Issue5191.cs
index 7140df63eea7..4756fdd693f5 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue5191.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue5191.cs
@@ -12,6 +12,7 @@ public Issue5191(TestDevice device) : base(device) { }
public override string Issue => "PanGesture notify Completed event moving outside View limits";
[Test]
+ [Category(UITestCategories.Gestures)]
public void Issue5191Test()
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.iOS, TestDevice.Mac, TestDevice.Windows },
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue5555.cs b/src/Controls/tests/UITests/Tests/Issues/Issue5555.cs
index 50b721c3bfa4..c735fc8f4c60 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue5555.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue5555.cs
@@ -12,6 +12,7 @@ public Issue5555(TestDevice device) : base(device)
}
[Test]
+ [Category(UITestCategories.TableView)]
public void TableViewMemoryLeakWhenUsingSwitchCellOrEntryCell()
{
this.IgnoreIfPlatforms(new[]
diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue5924.cs b/src/Controls/tests/UITests/Tests/Issues/Issue5924.cs
index 2cacca030eb2..6c80f3d87661 100644
--- a/src/Controls/tests/UITests/Tests/Issues/Issue5924.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/Issue5924.cs
@@ -12,6 +12,7 @@ public Issue5924(TestDevice device) : base(device)
}
[Test]
+ [Category(UITestCategories.Entry)]
public void TableViewViewCellVanishesAfterContentIsUpdated()
{
App.WaitForElement("entry");
diff --git a/src/Controls/tests/UITests/Tests/Issues/RefreshViewTests.cs b/src/Controls/tests/UITests/Tests/Issues/RefreshViewTests.cs
index 1a09da123aed..3163a5bd674b 100644
--- a/src/Controls/tests/UITests/Tests/Issues/RefreshViewTests.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/RefreshViewTests.cs
@@ -13,6 +13,7 @@ public RefreshViewTests(TestDevice device)
public override string Issue => "Refresh View Tests";
[Test]
+ [Category(UITestCategories.RefreshView)]
public void IsRefreshingAndCommandTest()
{
App.Click("ToggleRefresh");
diff --git a/src/Controls/tests/UITests/Tests/Issues/SoftInputExtensionsPageTests.cs b/src/Controls/tests/UITests/Tests/Issues/SoftInputExtensionsPageTests.cs
index e6e92fb4944e..5a66bbb87a94 100644
--- a/src/Controls/tests/UITests/Tests/Issues/SoftInputExtensionsPageTests.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/SoftInputExtensionsPageTests.cs
@@ -11,6 +11,7 @@ public SoftInputExtensionsPageTests(TestDevice device) : base(device) { }
public override string Issue => "Soft Input Extension Methods";
[Test]
+ [Category(UITestCategories.Entry)]
public void SoftInputExtensionsPageTest()
{
// Make sure the buttons appear on the screen.
diff --git a/src/Controls/tests/UITests/Tests/Issues/XFIssue2681.cs b/src/Controls/tests/UITests/Tests/Issues/XFIssue2681.cs
index 776627536686..d6643e3bda7c 100644
--- a/src/Controls/tests/UITests/Tests/Issues/XFIssue2681.cs
+++ b/src/Controls/tests/UITests/Tests/Issues/XFIssue2681.cs
@@ -15,6 +15,7 @@ public XFIssue2681(TestDevice device) : base(device)
public override string Issue => "[UWP] Label inside Listview gets stuck inside infinite loop";
[Test]
+ [Category(UITestCategories.ListView)]
public void ListViewDoesntFreezeApp()
{
App.WaitForElement(NavigateToPage);
diff --git a/src/Controls/tests/UITests/Tests/KeyboardScrollingNonScrollingPageLargeTitlesTests.cs b/src/Controls/tests/UITests/Tests/KeyboardScrollingNonScrollingPageLargeTitlesTests.cs
index 754bfa8c7cfd..4b89e207a56e 100644
--- a/src/Controls/tests/UITests/Tests/KeyboardScrollingNonScrollingPageLargeTitlesTests.cs
+++ b/src/Controls/tests/UITests/Tests/KeyboardScrollingNonScrollingPageLargeTitlesTests.cs
@@ -24,25 +24,25 @@ protected override void FixtureTeardown()
this.Back();
}
- //[Test]
- //public void EntriesScrollingPageTest()
- //{
- // this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows }, KeyboardScrolling.IgnoreMessage);
- // KeyboardScrolling.EntriesScrollingTest(App, KeyboardScrollingGallery);
- //}
+ [Test]
+ public void EntriesScrollingPageTest()
+ {
+ this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows }, KeyboardScrolling.IgnoreMessage);
+ KeyboardScrolling.EntriesScrollingTest(App, KeyboardScrollingGallery);
+ }
- //[Test]
- //public void EditorsScrollingPageTest()
- //{
- // this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows }, KeyboardScrolling.IgnoreMessage);
- // KeyboardScrolling.EditorsScrollingTest(App, KeyboardScrollingGallery);
- //}
+ [Test]
+ public void EditorsScrollingPageTest()
+ {
+ this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows }, KeyboardScrolling.IgnoreMessage);
+ KeyboardScrolling.EditorsScrollingTest(App, KeyboardScrollingGallery);
+ }
- //[Test]
- //public void EntryNextEditorTest()
- //{
- // this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows }, KeyboardScrolling.IgnoreMessage);
- // KeyboardScrolling.EntryNextEditorScrollingTest(App, KeyboardScrollingGallery);
- //}
+ [Test]
+ public void EntryNextEditorTest()
+ {
+ this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.Windows }, KeyboardScrolling.IgnoreMessage);
+ KeyboardScrolling.EntryNextEditorScrollingTest(App, KeyboardScrollingGallery);
+ }
}
}
diff --git a/src/Controls/tests/UITests/Tests/LabelUITests.cs b/src/Controls/tests/UITests/Tests/LabelUITests.cs
index 3c00cb850b3d..265111609736 100644
--- a/src/Controls/tests/UITests/Tests/LabelUITests.cs
+++ b/src/Controls/tests/UITests/Tests/LabelUITests.cs
@@ -24,6 +24,7 @@ public override void _IsEnabled()
}
[Test]
+ [Category(UITestCategories.Label)]
public void SpanTapped()
{
if (Device == TestDevice.Mac)
diff --git a/src/Controls/tests/UITests/Tests/RadioButtonUITests.cs b/src/Controls/tests/UITests/Tests/RadioButtonUITests.cs
index a60c4ed02c03..8eb15468cb49 100644
--- a/src/Controls/tests/UITests/Tests/RadioButtonUITests.cs
+++ b/src/Controls/tests/UITests/Tests/RadioButtonUITests.cs
@@ -18,6 +18,7 @@ protected override void NavigateToGallery()
}
[Test]
+ [Category(UITestCategories.RadioButton)]
public override void _IsEnabled()
{
if (Device == TestDevice.Mac ||
diff --git a/src/Controls/tests/UITests/Tests/ScrollViewUITests.cs b/src/Controls/tests/UITests/Tests/ScrollViewUITests.cs
index 737403b6743a..4e22dfd4c1f4 100644
--- a/src/Controls/tests/UITests/Tests/ScrollViewUITests.cs
+++ b/src/Controls/tests/UITests/Tests/ScrollViewUITests.cs
@@ -26,6 +26,7 @@ protected override void FixtureTeardown()
}
[Test]
+ [Category(UITestCategories.ScrollView)]
[Description("Scroll element to the start")]
public void ScrollToElement1Start()
{
@@ -42,6 +43,7 @@ public void ScrollToElement1Start()
}
[Test]
+ [Category(UITestCategories.ScrollView)]
[Description("Scroll element to the center")]
public void ScrollToElement2Center()
{
@@ -60,6 +62,7 @@ public void ScrollToElement2Center()
}
[Test]
+ [Category(UITestCategories.ScrollView)]
[Description("Scroll element to the end")]
public void ScrollToElement3End()
{
@@ -76,6 +79,7 @@ public void ScrollToElement3End()
}
[Test]
+ [Category(UITestCategories.ScrollView)]
[Description("Scroll down the ScrollView using a gesture")]
public void ScrollUpAndDownWithGestures()
{
diff --git a/src/Controls/tests/UITests/Tests/SliderUITests.cs b/src/Controls/tests/UITests/Tests/SliderUITests.cs
index 5122ab47f5b6..223f7591672d 100644
--- a/src/Controls/tests/UITests/Tests/SliderUITests.cs
+++ b/src/Controls/tests/UITests/Tests/SliderUITests.cs
@@ -26,6 +26,7 @@ protected override void FixtureTeardown()
}
[Test]
+ [Category(UITestCategories.Slider)]
[Description("Set different slider values")]
public void SetSliderValue()
{
diff --git a/src/Controls/tests/UITests/Tests/SwipeViewUITests.cs b/src/Controls/tests/UITests/Tests/SwipeViewUITests.cs
index ed1a9ac7f625..798138852f2d 100644
--- a/src/Controls/tests/UITests/Tests/SwipeViewUITests.cs
+++ b/src/Controls/tests/UITests/Tests/SwipeViewUITests.cs
@@ -31,6 +31,7 @@ protected override void FixtureTeardown()
}
[Test]
+ [Category(UITestCategories.SwipeView)]
[Description("Swipe to right the SwipeView")]
public void SwipeToRight()
{
@@ -48,6 +49,7 @@ public void SwipeToRight()
}
[Test]
+ [Category(UITestCategories.SwipeView)]
[Description("Swipe to left the SwipeView")]
public void SwipeToLeft()
{
diff --git a/src/Controls/tests/UITests/UITestCategories.cs b/src/Controls/tests/UITests/UITestCategories.cs
new file mode 100644
index 000000000000..84b185a469c0
--- /dev/null
+++ b/src/Controls/tests/UITests/UITestCategories.cs
@@ -0,0 +1,68 @@
+namespace Microsoft.Maui.AppiumTests
+{
+ internal static class UITestCategories
+ {
+ public const string ViewBaseTests = "ViewBaseTests";
+ public const string ActionSheet = "ActionSheet";
+ public const string ActivityIndicator = "ActivityIndicator";
+ public const string Animation = "Animation";
+ public const string AutomationId = "AutomationID";
+ public const string Border = "Border";
+ public const string BoxView = "BoxView";
+ public const string Button = "Button";
+ public const string CarouselView = "CarouselView";
+ public const string Cells = "Cells";
+ public const string CheckBox = "CheckBox";
+ public const string CollectionView = "CollectionView";
+ public const string ContextActions = "ContextActions";
+ public const string DatePicker = "DatePicker";
+ public const string DragAndDrop = "DragAndDrop";
+ public const string DisplayAlert = "DisplayAlert";
+ public const string Editor = "Editor";
+ public const string Entry = "Entry";
+ public const string Frame = "Frame";
+ public const string Image = "Image";
+ public const string ImageButton = "ImageButton";
+ public const string Label = "Label";
+ public const string Layout = "Layout";
+ public const string ListView = "ListView";
+ public const string LifeCycle = "Lifecycle";
+ public const string FlyoutPage = "FlyoutPage";
+ public const string Picker = "Picker";
+ public const string ProgressBar = "ProgressBar";
+ public const string ScrollView = "ScrollView";
+ public const string SearchBar = "SearchBar";
+ public const string Slider = "Slider";
+ public const string Stepper = "Stepper";
+ public const string Switch = "Switch";
+ public const string SwipeView = "SwipeView";
+ public const string TableView = "TableView";
+ public const string TimePicker = "TimePicker";
+ public const string ToolbarItem = "ToolbarItem";
+ public const string WebView = "WebView";
+ public const string Window = "Window";
+ public const string Maps = "Maps";
+ public const string InputTransparent = "InputTransparent";
+ public const string IsEnabled = "IsEnabled";
+ public const string Gestures = "Gestures";
+ public const string Navigation = "Navigation";
+ public const string Effects = "Effects";
+ public const string Focus = "Focus";
+ public const string ManualReview = "ManualReview";
+ public const string Performance = "Performance";
+ public const string Visual = "Visual";
+ public const string AppLinks = "AppLinks";
+ public const string Shell = "Shell";
+ public const string TabbedPage = "TabbedPage";
+ public const string CustomRenderers = "CustomRenderers";
+ public const string Page = "Page";
+ public const string RefreshView = "RefreshView";
+ public const string TitleView = "TitleView";
+ public const string DisplayPrompt = "DisplayPrompt";
+ public const string IndicatorView = "IndicatorView";
+ public const string RadioButton = "RadioButton";
+ public const string Shape = "Shape";
+ public const string Accessibility = "Accessibility";
+ public const string Brush = "Brush";
+ }
+}
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui18545.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui18545.xaml
new file mode 100644
index 000000000000..60c52303f1f5
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui18545.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui18545.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui18545.xaml.cs
new file mode 100644
index 000000000000..42fad08617c1
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui18545.xaml.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using Microsoft.Maui.ApplicationModel;
+using Microsoft.Maui.Controls.Core.UnitTests;
+using Microsoft.Maui.Controls.Shapes;
+using Microsoft.Maui.Devices;
+using Microsoft.Maui.Dispatching;
+
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.UnitTests;
+using NUnit.Framework;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public partial class Maui18545 : ContentPage
+{
+
+ public Maui18545() => InitializeComponent();
+
+ public Maui18545(bool useCompiledXaml)
+ {
+ //this stub will be replaced at compile time
+ }
+
+ [TestFixture]
+ class Test
+ {
+ [SetUp]
+ public void Setup()
+ {
+ Application.SetCurrentApplication(new MockApplication());
+ DispatcherProvider.SetCurrent(new DispatcherProviderStub());
+ }
+
+ [TearDown] public void TearDown() => AppInfo.SetCurrent(null);
+
+ [Test]
+ public void DynamicResourcesOnGradient([Values(false, true)] bool useCompiledXaml)
+ {
+ var lighttheme = new ResourceDictionary{
+ ["GradientColorStart"] = Colors.Red,
+ ["GradientColorEnd"] = Colors.Blue
+ };
+ var darktheme = new ResourceDictionary{
+ ["GradientColorStart"] = Colors.Green,
+ ["GradientColorEnd"] = Colors.Yellow
+ };
+ Application.Current.Resources.MergedDictionaries.Add(lighttheme);
+ var page = new Maui18545(useCompiledXaml);
+ Application.Current.MainPage = page;
+
+ Assert.That(page.label.Background, Is.TypeOf());
+ var brush = (LinearGradientBrush)page.label.Background;
+ Assert.That(brush.GradientStops[0].Color, Is.EqualTo(Colors.Red));
+
+ Application.Current.Resources.MergedDictionaries.Remove(lighttheme);
+ Application.Current.Resources.MergedDictionaries.Add(darktheme);
+ page.Resources["GradientColorStart"] = Colors.Green;
+ Assert.That(brush.GradientStops[0].Color, Is.EqualTo(Colors.Green));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs
index 6b22b2ed7b20..9b35ecf7433e 100644
--- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs
+++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs
@@ -8,6 +8,7 @@ namespace Microsoft.Maui.Handlers
public partial class SearchBarHandler : ViewHandler
{
UITextField? _editor;
+ readonly MauiSearchBarProxy _proxy = new();
public UITextField? QueryEditor => _editor;
@@ -23,35 +24,14 @@ protected override MauiSearchBar CreatePlatformView()
protected override void ConnectHandler(MauiSearchBar platformView)
{
- platformView.CancelButtonClicked += OnCancelClicked;
- platformView.SearchButtonClicked += OnSearchButtonClicked;
- platformView.TextSetOrChanged += OnTextPropertySet;
- platformView.OnMovedToWindow += OnMovedToWindow;
- platformView.ShouldChangeTextInRange += ShouldChangeText;
- platformView.OnEditingStarted += OnEditingStarted;
- platformView.EditingChanged += OnEditingChanged;
- platformView.OnEditingStopped += OnEditingStopped;
+ _proxy.Connect(this, VirtualView, platformView);
base.ConnectHandler(platformView);
}
- void OnMovedToWindow(object? sender, EventArgs e)
- {
- // The cancel button doesn't exist until the control has moved to the window
- // so we fire this off again so it can set the color
- UpdateValue(nameof(ISearchBar.CancelButtonColor));
- }
-
protected override void DisconnectHandler(MauiSearchBar platformView)
{
- platformView.CancelButtonClicked -= OnCancelClicked;
- platformView.SearchButtonClicked -= OnSearchButtonClicked;
- platformView.TextSetOrChanged -= OnTextPropertySet;
- platformView.ShouldChangeTextInRange -= ShouldChangeText;
- platformView.OnMovedToWindow -= OnMovedToWindow;
- platformView.OnEditingStarted -= OnEditingStarted;
- platformView.EditingChanged -= OnEditingChanged;
- platformView.OnEditingStopped -= OnEditingStopped;
+ _proxy.Disconnect(platformView, _editor);
base.DisconnectHandler(platformView);
}
@@ -166,56 +146,119 @@ public static void MapKeyboard(ISearchBarHandler handler, ISearchBar searchBar)
handler.PlatformView?.UpdateKeyboard(searchBar);
}
- void OnCancelClicked(object? sender, EventArgs args)
+ void UpdateCancelButtonVisibility()
{
- if (VirtualView != null)
- VirtualView.Text = string.Empty;
+ if (PlatformView.ShowsCancelButton != VirtualView.ShouldShowCancelButton())
+ UpdateValue(nameof(ISearchBar.CancelButtonColor));
}
- void OnSearchButtonClicked(object? sender, EventArgs e)
+ class MauiSearchBarProxy
{
- VirtualView?.SearchButtonPressed();
- }
+ WeakReference? _handler;
+ WeakReference? _virtualView;
- void OnTextPropertySet(object? sender, UISearchBarTextChangedEventArgs a)
- {
- VirtualView.UpdateText(a.SearchText);
+ ISearchBar? VirtualView => _virtualView is not null && _virtualView.TryGetTarget(out var v) ? v : null;
- UpdateCancelButtonVisibility();
- }
+ SearchBarHandler? Handler => _handler is not null && _handler.TryGetTarget(out var h) ? h : null;
- bool ShouldChangeText(UISearchBar searchBar, NSRange range, string text)
- {
- var newLength = searchBar?.Text?.Length + text.Length - range.Length;
- return newLength <= VirtualView?.MaxLength;
- }
+ public void Connect(SearchBarHandler handler, ISearchBar virtualView, MauiSearchBar platformView)
+ {
+ _handler = new(handler);
+ _virtualView = new(virtualView);
+
+ platformView.CancelButtonClicked += OnCancelClicked;
+ platformView.SearchButtonClicked += OnSearchButtonClicked;
+ platformView.TextSetOrChanged += OnTextPropertySet;
+ platformView.OnMovedToWindow += OnMovedToWindow;
+ platformView.ShouldChangeTextInRange += ShouldChangeText;
+ platformView.OnEditingStarted += OnEditingStarted;
+ platformView.OnEditingStopped += OnEditingStopped;
+
+ if (handler.QueryEditor is UITextField editor)
+ editor.EditingChanged += OnEditingChanged;
+ }
- void OnEditingStarted(object? sender, EventArgs e)
- {
- if (VirtualView is not null)
- VirtualView.IsFocused = true;
- }
+ public void Disconnect(MauiSearchBar platformView, UITextField? editor)
+ {
+ _virtualView = null;
+ _handler = null;
+
+ platformView.CancelButtonClicked -= OnCancelClicked;
+ platformView.SearchButtonClicked -= OnSearchButtonClicked;
+ platformView.TextSetOrChanged -= OnTextPropertySet;
+ platformView.ShouldChangeTextInRange -= ShouldChangeText;
+ platformView.OnMovedToWindow -= OnMovedToWindow;
+ platformView.OnEditingStarted -= OnEditingStarted;
+ platformView.OnEditingStopped -= OnEditingStopped;
+
+ if (editor is not null)
+ editor.EditingChanged -= OnEditingChanged;
+ }
- void OnEditingChanged(object? sender, EventArgs e)
- {
- if (VirtualView == null || _editor == null)
- return;
+ void OnMovedToWindow(object? sender, EventArgs e)
+ {
+ // The cancel button doesn't exist until the control has moved to the window
+ // so we fire this off again so it can set the color
+ if (Handler is SearchBarHandler handler)
+ {
+ handler.UpdateValue(nameof(ISearchBar.CancelButtonColor));
+ }
+ }
+
+ void OnCancelClicked(object? sender, EventArgs args)
+ {
+ if (VirtualView is ISearchBar virtualView)
+ virtualView.Text = string.Empty;
+ }
- VirtualView.UpdateText(_editor.Text);
+ void OnSearchButtonClicked(object? sender, EventArgs e)
+ {
+ VirtualView?.SearchButtonPressed();
+ }
- UpdateCancelButtonVisibility();
- }
+ void OnTextPropertySet(object? sender, UISearchBarTextChangedEventArgs a)
+ {
+ if (VirtualView is ISearchBar virtualView)
+ {
+ virtualView.UpdateText(a.SearchText);
+
+ if (Handler is SearchBarHandler handler)
+ {
+ handler.UpdateCancelButtonVisibility();
+ }
+ }
+ }
- void OnEditingStopped(object? sender, EventArgs e)
- {
- if (VirtualView is not null)
- VirtualView.IsFocused = false;
- }
+ bool ShouldChangeText(UISearchBar searchBar, NSRange range, string text)
+ {
+ var newLength = searchBar?.Text?.Length + text.Length - range.Length;
+ return newLength <= VirtualView?.MaxLength;
+ }
- void UpdateCancelButtonVisibility()
- {
- if (PlatformView.ShowsCancelButton != VirtualView.ShouldShowCancelButton())
- UpdateValue(nameof(ISearchBar.CancelButtonColor));
+ void OnEditingStarted(object? sender, EventArgs e)
+ {
+ if (VirtualView is ISearchBar virtualView)
+ virtualView.IsFocused = true;
+ }
+
+ void OnEditingChanged(object? sender, EventArgs e)
+ {
+ if (sender is UITextField textField && VirtualView is ISearchBar virtualView)
+ {
+ virtualView.UpdateText(textField.Text);
+ }
+
+ if (Handler is SearchBarHandler handler)
+ {
+ handler.UpdateCancelButtonVisibility();
+ }
+ }
+
+ void OnEditingStopped(object? sender, EventArgs e)
+ {
+ if (VirtualView is ISearchBar virtualView)
+ virtualView.IsFocused = false;
+ }
}
}
}
diff --git a/src/Core/src/Handlers/View/ViewHandler.Standard.cs b/src/Core/src/Handlers/View/ViewHandler.Standard.cs
index 16ecfd3a3cb4..afe985fcb346 100644
--- a/src/Core/src/Handlers/View/ViewHandler.Standard.cs
+++ b/src/Core/src/Handlers/View/ViewHandler.Standard.cs
@@ -2,26 +2,82 @@
{
public partial class ViewHandler
{
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapTranslationX(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapTranslationY(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapScale(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapScaleX(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapScaleY(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapRotation(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapRotationX(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapRotationY(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapAnchorX(IViewHandler handler, IView view) { }
+ ///
+ /// Maps a view's abstract property to the platform-specific implementations.
+ ///
+ /// The associated handler.
+ /// The associated instance.
public static void MapAnchorY(IViewHandler handler, IView view) { }
+ ///
+ /// Maps the abstract to the platform-specific implementations of a .
+ ///
+ /// If the can't be cast to a , this method does nothing.
+ /// The associated handler.
+ /// The associated instance.
public static void MapContextFlyout(IViewHandler handler, IView view) { }
}
}
\ No newline at end of file
diff --git a/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs b/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs
index 20146d154923..fb5a8713a617 100644
--- a/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs
+++ b/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs
@@ -282,7 +282,7 @@ internal static async Task AdjustPositionDebounce()
// while we have the keyboard up, we need a delay to recalculate
// the height of the InputAccessoryView
if (IsKeyboardShowing && View?.InputAccessoryView is not null)
- await Task.Delay(20);
+ await Task.Delay(30);
if (entranceCount == DebounceCount)
{
@@ -317,7 +317,7 @@ internal static void AdjustPosition()
nfloat statusBarHeight;
nfloat navigationBarAreaHeight;
- if (ContainerView.FindResponder() is UINavigationController navigationController)
+ if (View.FindResponder() is UINavigationController navigationController)
{
navigationBarAreaHeight = navigationController.NavigationBar.Frame.GetMaxY();
}
diff --git a/src/Core/src/Platform/iOS/MauiSearchBar.cs b/src/Core/src/Platform/iOS/MauiSearchBar.cs
index 4967c9b35bcc..4aa8ba6a2507 100644
--- a/src/Core/src/Platform/iOS/MauiSearchBar.cs
+++ b/src/Core/src/Platform/iOS/MauiSearchBar.cs
@@ -33,7 +33,7 @@ protected internal MauiSearchBar(NativeHandle handle) : base(handle)
// Native Changed doesn't fire when the Text Property is set in code
// We use this event as a way to fire changes whenever the Text changes
// via code or user interaction.
- [UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "FIXME: https://github.com/dotnet/maui/pull/16383")]
+ [UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
public event EventHandler? TextSetOrChanged;
public override string? Text
@@ -52,9 +52,9 @@ public override string? Text
}
}
- [UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "FIXME: https://github.com/dotnet/maui/pull/16383")]
+ [UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
internal event EventHandler? OnMovedToWindow;
- [UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "FIXME: https://github.com/dotnet/maui/pull/16383")]
+ [UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
internal event EventHandler? EditingChanged;
public override void WillMoveToWindow(UIWindow? window)
@@ -74,7 +74,7 @@ public override void WillMoveToWindow(UIWindow? window)
OnMovedToWindow?.Invoke(this, EventArgs.Empty);
}
- [UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "FIXME: https://github.com/dotnet/maui/pull/16383")]
+ [UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
void OnEditingChanged(object? sender, EventArgs e)
{
EditingChanged?.Invoke(this, EventArgs.Empty);
diff --git a/src/Core/src/Platform/iOS/SearchBarExtensions.cs b/src/Core/src/Platform/iOS/SearchBarExtensions.cs
index 7b7c6da052d7..a7dda89425ac 100644
--- a/src/Core/src/Platform/iOS/SearchBarExtensions.cs
+++ b/src/Core/src/Platform/iOS/SearchBarExtensions.cs
@@ -10,9 +10,25 @@ public static class SearchBarExtensions
internal static UITextField? GetSearchTextField(this UISearchBar searchBar)
{
if (OperatingSystem.IsIOSVersionAtLeast(13))
+ {
return searchBar.SearchTextField;
- else
- return searchBar.GetSearchTextField();
+ }
+
+ // Search Subviews up to two levels deep
+ // https://stackoverflow.com/a/58056700
+ foreach (var child in searchBar.Subviews)
+ {
+ if (child is UITextField childTextField)
+ return childTextField;
+
+ foreach (var grandChild in child.Subviews)
+ {
+ if (grandChild is UITextField grandChildTextField)
+ return grandChildTextField;
+ }
+ }
+
+ return null;
}
internal static void UpdateBackground(this UISearchBar uiSearchBar, ISearchBar searchBar)
diff --git a/src/Essentials/src/Connectivity/Connectivity.uwp.cs b/src/Essentials/src/Connectivity/Connectivity.uwp.cs
index 57f85afd168f..3a1c045f7440 100644
--- a/src/Essentials/src/Connectivity/Connectivity.uwp.cs
+++ b/src/Essentials/src/Connectivity/Connectivity.uwp.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Net.NetworkInformation;
using Windows.Networking.Connectivity;
@@ -75,7 +76,16 @@ public IEnumerable ConnectionProfiles
{
get
{
- var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
+ NetworkInterface[] networkInterfaces = [];
+ try
+ {
+ networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
+ }
+ catch (NetworkInformationException ex)
+ {
+ Debug.WriteLine($"Unable to get network interfaces. Error: {ex.Message}");
+ }
+
foreach (var nic in networkInterfaces)
{
if (nic.OperationalStatus is not OperationalStatus.Up ||
diff --git a/src/Essentials/src/SecureStorage/SecureStorage.ios.tvos.watchos.macos.cs b/src/Essentials/src/SecureStorage/SecureStorage.ios.tvos.watchos.macos.cs
index 368cd8455fec..ea54d1cd3066 100644
--- a/src/Essentials/src/SecureStorage/SecureStorage.ios.tvos.watchos.macos.cs
+++ b/src/Essentials/src/SecureStorage/SecureStorage.ios.tvos.watchos.macos.cs
@@ -8,7 +8,8 @@ namespace Microsoft.Maui.Storage
{
partial class SecureStorageImplementation : ISecureStorage, IPlatformSecureStorage
{
- public SecAccessible DefaultAccessible { get; set; }
+ public SecAccessible DefaultAccessible { get; set; } =
+ SecAccessible.AfterFirstUnlock;
public Task SetAsync(string key, string value, SecAccessible accessible)
{
diff --git a/src/Graphics/src/Graphics/Text/TextAttributeExtensions.cs b/src/Graphics/src/Graphics/Text/TextAttributeExtensions.cs
index 8c374f8db812..da2bccc5f8d3 100644
--- a/src/Graphics/src/Graphics/Text/TextAttributeExtensions.cs
+++ b/src/Graphics/src/Graphics/Text/TextAttributeExtensions.cs
@@ -23,14 +23,14 @@ public static float GetFontSize(
this ITextAttributes attributes,
float? fontSize = null)
{
- return attributes.GetFloatAttribute(TextAttribute.FontName, fontSize ?? DefaultFontSize);
+ return attributes.GetFloatAttribute(TextAttribute.FontSize, fontSize ?? DefaultFontSize);
}
public static void SetFontSize(
this Dictionary attributes,
float value)
{
- attributes.SetFloatAttribute(TextAttribute.FontName, value, DefaultFontSize);
+ attributes.SetFloatAttribute(TextAttribute.FontSize, value, DefaultFontSize);
}
public static bool GetUnderline(this ITextAttributes attributes)
diff --git a/src/TestUtils/src/UITest.Appium/Actions/AppiumLifecycleActions.cs b/src/TestUtils/src/UITest.Appium/Actions/AppiumLifecycleActions.cs
index d84b5e1d2d27..6e7a6d743ff6 100644
--- a/src/TestUtils/src/UITest.Appium/Actions/AppiumLifecycleActions.cs
+++ b/src/TestUtils/src/UITest.Appium/Actions/AppiumLifecycleActions.cs
@@ -74,6 +74,8 @@ CommandResponse BackgroundApp(IDictionary parameters)
return CommandResponse.FailedEmptyResponse;
_app.Driver.BackgroundApp();
+ if (_app.GetTestDevice() == TestDevice.Android)
+ Thread.Sleep(500);
return CommandResponse.SuccessEmptyResponse;
}
diff --git a/src/TestUtils/src/UITest.NUnit/UITestBase.cs b/src/TestUtils/src/UITest.NUnit/UITestBase.cs
index e188916eb306..055e0029d89e 100644
--- a/src/TestUtils/src/UITest.NUnit/UITestBase.cs
+++ b/src/TestUtils/src/UITest.NUnit/UITestBase.cs
@@ -131,7 +131,7 @@ void SaveDeviceDiagnosticInfo([CallerMemberName] string? note = null)
}
}
- void SaveUIDiagnosticInfo([CallerMemberName] string? note = null)
+ protected void SaveUIDiagnosticInfo([CallerMemberName] string? note = null)
{
var screenshotPath = GetGeneratedFilePath("ScreenShot.png", note);
if (screenshotPath is not null)