diff --git a/.gitignore b/.gitignore index d82850c71066..8f457824a432 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ templatesTest/ *.user *.userosscache *.sln.docstates +/src/Controls/tests/TestCases.HostApp/MauiProgram.user.cs # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/docs/design/UITesting.md b/docs/design/UITesting.md index a605ff4daac9..e9055b912dae 100644 --- a/docs/design/UITesting.md +++ b/docs/design/UITesting.md @@ -26,6 +26,25 @@ This will be the majority of new tests added which will be primarily for testing You will need to create some kind of UI to test against, which will go in the Controls.TestCases.HostApp project. This will be an actual MAUI app under test and has no dependency on Appium. Create a new class within `src/Controls/tests/TestCases.HostApp/Issues` and attribute it with `[Issue]`. Create it like any normal page you would make in app to reproduce the issue. This could be just in XAML or just code, along with a screenshot. This project requires a rebuild after making changes because the `Controls.TestCases.Shared.Tests` is loosely coupled to this project. It initiates a previously compiled executable from `src/Controls/tests/TestCases.HostApp`. +When working on a single test case it may be convenient to start the app by using the issue page as the main one. +This can be easily achieved by creating a `MauiProgram.user.cs` file in the `src/Controls/tests/TestCases.HostApp` folder and adding the following code: + +```csharp +using Controls.TestCases.HostApp.Issues; + +namespace Maui.Controls.Sample; + +public partial class MauiProgram +{ + static partial void OverrideMainPage(ref Page mainPage) + { + mainPage = new Issue99999(); // My issue number here + } +} +``` + +That file is not tracked by git, so it won't be committed by mistake. + ## Adding a Test to Interact with the Reproduction Next you will need to create the appium test in the `Controls.TestCases.Shared.Tests` project, which is a library project that runs NUnit tests via Appium. Add a new class with the same name as the Reproduction within this folder: `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues`. Have the class derive from `_IssuesUITest` and add your test(s) as methods. diff --git a/eng/Publishing.props b/eng/Publishing.props index b31e222bdfda..96b340af297c 100644 --- a/eng/Publishing.props +++ b/eng/Publishing.props @@ -1,21 +1,21 @@ - - 3 - true - $(PublishDependsOnTargets);_PublishBlobItems - + + 3 + true + - - <_InstallerManifestFilesToPublish Include="$(ArtifactsShippingPackagesDir)\**\*.zip" /> + + <_InstallersToPublish Include="$(ArtifactsShippingPackagesDir)\**\*.zip" /> - - - - true - true - $(_UploadPathRoot)/$(_PackageVersion)/%(Filename)%(Extension) - - - + + <_UploadPathRoot>maui-sdk + + + + + true + false + + \ No newline at end of file diff --git a/eng/Signing.props b/eng/Signing.props index 6fa82b8d6dcc..d4f40599ae49 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -19,10 +19,10 @@ - - - - + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d225b72ed2af..c8ee07b24f42 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -149,37 +149,37 @@ - + https://github.com/dotnet/arcade - f33d9e642f0e68a61312164cd9e0baf4e142a999 + 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee - + https://github.com/dotnet/arcade - f33d9e642f0e68a61312164cd9e0baf4e142a999 + 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee - + https://github.com/dotnet/arcade - f33d9e642f0e68a61312164cd9e0baf4e142a999 + 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee - + https://github.com/dotnet/arcade - f33d9e642f0e68a61312164cd9e0baf4e142a999 + 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee - + https://github.com/dotnet/arcade - f33d9e642f0e68a61312164cd9e0baf4e142a999 + 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee - + https://github.com/dotnet/arcade - f33d9e642f0e68a61312164cd9e0baf4e142a999 + 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee - + https://github.com/dotnet/arcade - f33d9e642f0e68a61312164cd9e0baf4e142a999 + 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee - + https://github.com/dotnet/arcade - f33d9e642f0e68a61312164cd9e0baf4e142a999 + 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee diff --git a/eng/Versions.props b/eng/Versions.props index aea0c799a7cc..b0c5db2301b0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -134,13 +134,13 @@ 0.9.0 4.2.3 9.0.0 - 9.0.0-beta.25161.4 - 9.0.0-beta.25161.4 - 9.0.0-beta.25161.4 - 9.0.0-beta.25161.4 + 9.0.0-beta.25164.2 + 9.0.0-beta.25164.2 + 9.0.0-beta.25164.2 + 9.0.0-beta.25164.2 1.1.87-gba258badda - 9.0.0-beta.25161.4 - 9.0.0-beta.25161.4 + 9.0.0-beta.25164.2 + 9.0.0-beta.25164.2 17.6.0 diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index a46b6deb7598..22b49e09d09b 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -42,7 +42,7 @@ [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true } # Enable repos to use a particular version of the on-line dotnet-install scripts. -# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1 +# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1 [string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. @@ -262,7 +262,7 @@ function GetDotNetInstallScript([string] $dotnetRoot) { if (!(Test-Path $installScript)) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" + $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" Retry({ Write-Host "GET $uri" diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 1159726a10fd..01b09b65796c 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -54,7 +54,7 @@ warn_as_error=${warn_as_error:-true} use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} # Enable repos to use a particular version of the on-line dotnet-install scripts. -# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh +# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} # True to use global NuGet cache instead of restoring packages to repository-local directory. @@ -295,7 +295,7 @@ function with_retries { function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" - local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" + local install_script_url="https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" if [[ ! -a "$install_script" ]]; then mkdir -p "$root" diff --git a/eng/pipelines/arcade/setup-test-env.yml b/eng/pipelines/arcade/setup-test-env.yml index 45a26baa8041..655ca1cf7b15 100644 --- a/eng/pipelines/arcade/setup-test-env.yml +++ b/eng/pipelines/arcade/setup-test-env.yml @@ -11,6 +11,18 @@ steps: fetchDepth: 1 clean: true +- template: /eng/pipelines/common/provision.yml@self + parameters: + checkoutDirectory: '$(System.DefaultWorkingDirectory)' + skipJdk: false + skipAndroidCommonSdks: false + skipAndroidPlatformApis: false + onlyAndroidPlatformDefaultApis: true + skipAndroidEmulatorImages: true + skipAndroidCreateAvds: true + skipProvisioning: true + skipXcode: true + - script: | sudo xcode-select -s /Applications/Xcode_$(REQUIRED_XCODE).app xcrun xcode-select --print-path diff --git a/eng/pipelines/arcade/stage-pack.yml b/eng/pipelines/arcade/stage-pack.yml index b7bdecf9a6de..93ba36c2fbac 100644 --- a/eng/pipelines/arcade/stage-pack.yml +++ b/eng/pipelines/arcade/stage-pack.yml @@ -1,5 +1,11 @@ # Template for build + pack on dnceng parameters: +- name: prepareSteps + type: stepList + default: [] +- name: postSteps + type: stepList + default: [] - name: pool type: object - name: enableSourceBuild @@ -44,7 +50,12 @@ stages: - checkout: self fetchDepth: 1 clean: true - steps: + + steps: + - ${{ each step in parameters.prepareSteps }}: + - ${{ each pair in step }}: + ${{ pair.key }}: ${{ pair.value }} + - script: $(_buildScript) -restore -build -configuration $(_BuildConfig) @@ -70,11 +81,10 @@ stages: - script: $(_buildScript) -restore -pack -sign $(_SignArgs) - -publish $(_PublishArgs) -configuration $(_BuildConfig) /bl:$(Build.Arcade.LogsPath)pack.binlog $(_OfficialBuildIdArgs) - displayName: Pack, Sign & Publish + displayName: Pack, Sign # only for workloads - script: $(_buildScript) @@ -85,10 +95,4 @@ stages: /bl:$(Build.Arcade.LogsPath)/build-workloads.binlog -projects src/Workload/workloads.csproj $(_OfficialBuildIdArgs) - displayName: Build Workloads - - - task: 1ES.PublishPipelineArtifact@1 - displayName: Publish VSDrop MSIs - inputs: - targetPath: '$(Build.SourcesDirectory)/artifacts/VSSetup/$(_BuildConfig)' - artifactName: VSDropInsertion + displayName: Build Workloads, Sign & Publish diff --git a/eng/pipelines/azure-pipelines-internal.yml b/eng/pipelines/azure-pipelines-internal.yml index 6d8f1dfc07cd..1d8a593952d0 100644 --- a/eng/pipelines/azure-pipelines-internal.yml +++ b/eng/pipelines/azure-pipelines-internal.yml @@ -2,7 +2,6 @@ trigger: branches: include: - main - - net9.0 - net10.0 - release/* tags: @@ -27,7 +26,6 @@ schedules: branches: include: - main - - net9.0 - net10.0 variables: @@ -75,14 +73,27 @@ extends: suppression: suppressionFile: $(Build.SourcesDirectory)\eng\automation\guardian\source.gdnsuppress stages: + - template: /eng/pipelines/arcade/stage-pack.yml@self parameters: pool: ${{ parameters.VM_IMAGE_HOST }} - enableSourceIndex: true + enableSourceIndex: false runAsPublic: false sourceIndexParams: sourceIndexBuildCommand: build.cmd -restore -build -ci /bl:$(Build.Arcade.LogsPath)sourceIndexBuild.binlog /p:OfficialBuildId=$(_BuildOfficalId) /p:_SkipUpdateBuildNumber=true binlogPath: $(Build.Arcade.LogsPath)sourceIndexBuild.binlog + prepareSteps: + - template: /eng/pipelines/common/provision.yml@self + parameters: + checkoutDirectory: '$(System.DefaultWorkingDirectory)' + skipJdk: false + skipAndroidCommonSdks: false + skipAndroidPlatformApis: false + onlyAndroidPlatformDefaultApis: true + skipAndroidEmulatorImages: true + skipAndroidCreateAvds: true + skipProvisioning: true + skipXcode: true # Publish and validation steps. Only run in official builds - template: /eng/common/templates-official/post-build/post-build.yml@self diff --git a/eng/pipelines/azure-pipelines-public.yml b/eng/pipelines/azure-pipelines-public.yml index 58a2176a8f40..3ffcbc533721 100644 --- a/eng/pipelines/azure-pipelines-public.yml +++ b/eng/pipelines/azure-pipelines-public.yml @@ -43,8 +43,8 @@ parameters: - name: VM_IMAGE_HOST type: object default: - name: Azure Pipelines - vmImage: $(HostedWindowsImage) + name: NetCore-Public + image: 1es-windows-2022 os: windows stages: @@ -53,6 +53,18 @@ stages: parameters: pool: ${{ parameters.VM_IMAGE_HOST }} runAsPublic: true + prepareSteps: + - template: /eng/pipelines/common/provision.yml@self + parameters: + checkoutDirectory: '$(System.DefaultWorkingDirectory)' + skipJdk: false + skipAndroidCommonSdks: false + skipAndroidPlatformApis: false + onlyAndroidPlatformDefaultApis: true + skipAndroidEmulatorImages: true + skipAndroidCreateAvds: true + skipProvisioning: true + skipXcode: true - template: /eng/pipelines/arcade/stage-unit-tests.yml@self parameters: diff --git a/eng/pipelines/common/device-tests-steps.yml b/eng/pipelines/common/device-tests-steps.yml index 62d095c8321f..40b296025be1 100644 --- a/eng/pipelines/common/device-tests-steps.yml +++ b/eng/pipelines/common/device-tests-steps.yml @@ -44,7 +44,7 @@ steps: displayName: Enable KVM condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) - # Provision the variaous SDKs that are needed + # Provision the various SDKs that are needed - template: provision.yml parameters: skipXcode: ${{ or(eq(parameters.platform, 'android'), eq(parameters.platform, 'windows')) }} diff --git a/eng/pipelines/common/provision.yml b/eng/pipelines/common/provision.yml index e31ccd211d92..5f41bec602d9 100644 --- a/eng/pipelines/common/provision.yml +++ b/eng/pipelines/common/provision.yml @@ -36,13 +36,14 @@ steps: ################################################## # Provisioning macOS - Agent cleanser - - template: agent-cleanser/v1.yml@yaml-templates - parameters: - condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) - UninstallMono: false - UninstallXamarinMac: true - CleanseAgentToolsDotNet: true - SelfHealPowerShell: false + - ${{ if ne(parameters.skipProvisionator, true) }}: + - template: agent-cleanser/v1.yml@yaml-templates + parameters: + condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) + UninstallMono: false + UninstallXamarinMac: true + CleanseAgentToolsDotNet: true + SelfHealPowerShell: false # Provisioning macOS - Xcode - ${{ if ne(parameters.skipXcode, 'true') }}: diff --git a/eng/pipelines/common/ui-tests-steps.yml b/eng/pipelines/common/ui-tests-steps.yml index c0e0b8f46df6..aeeae13db741 100644 --- a/eng/pipelines/common/ui-tests-steps.yml +++ b/eng/pipelines/common/ui-tests-steps.yml @@ -36,6 +36,15 @@ steps: displayName: 'Clean bot' continueOnError: true timeoutInMinutes: 60 + + # Enable KVM for Android builds on Linux + - ${{ if and(ne(parameters.buildType, 'buildOnly'), eq(parameters.platform, 'android')) }}: + - bash: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + displayName: Enable KVM + condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) - ${{ if eq(parameters.platform, 'catalyst')}}: - bash: | @@ -50,9 +59,11 @@ steps: skipProvisioning: true skipJdk: ${{ ne(parameters.platform, 'android') }} skipAndroidCommonSdks: ${{ ne(parameters.platform, 'android') }} - skipAndroidPlatformApis: ${{ ne(parameters.platform, 'android') }} + skipAndroidPlatformApis: true + onlyAndroidPlatformDefaultApis: true skipAndroidEmulatorImages: ${{ ne(parameters.platform, 'android') }} - skipAndroidCreateAvds: ${{ ne(parameters.platform, 'android') }} + skipAndroidCreateAvds: true + androidEmulatorApiLevel: ${{ parameters.version }} skipXcode: ${{ or(eq(parameters.platform, 'android'), eq(parameters.platform, 'windows')) }} provisionatorChannel: ${{ parameters.provisionatorChannel }} ${{ if parameters.skipProvisioning }}: @@ -62,7 +73,7 @@ steps: gitHubToken: $(github--pat--vs-mobiletools-engineering-service2) - task: PowerShell@2 - condition: ne('${{ parameters.platform }}' , 'windows') + condition: and(ne('${{ parameters.platform }}', 'windows'), ne('${{ parameters.platform }}', 'android')) inputs: targetType: 'inline' script: | diff --git a/eng/pipelines/common/ui-tests.yml b/eng/pipelines/common/ui-tests.yml index 23ac76bde0a6..4712098c7e5d 100644 --- a/eng/pipelines/common/ui-tests.yml +++ b/eng/pipelines/common/ui-tests.yml @@ -1,5 +1,6 @@ parameters: androidPool: { } + androidLinuxPool: { } iosPool: { } windowsPool: { } windowsBuildPool: { } @@ -108,7 +109,7 @@ stages: workspace: clean: all displayName: ${{ coalesce(project.desc, project.name) }} (API ${{ api }}) - pool: ${{ parameters.androidPool }} + pool: ${{ parameters.androidLinuxPool }} variables: REQUIRED_XCODE: $(DEVICETESTS_REQUIRED_XCODE) APPIUM_HOME: $(System.DefaultWorkingDirectory)/.appium/ diff --git a/eng/pipelines/ui-tests.yml b/eng/pipelines/ui-tests.yml index 58f9ab6319a3..c9db6cf73888 100644 --- a/eng/pipelines/ui-tests.yml +++ b/eng/pipelines/ui-tests.yml @@ -73,6 +73,14 @@ parameters: demands: - macOS.Name -equals Sequoia - macOS.Architecture -equals x64 + + - name: androidPoolLinux + type: object + default: + name: $(1ESPTPool) + vmImage: $(androidTestsVmImage) + demands: + - ImageOverride -equals 1ESPT-Ubuntu22.04 - name: iosPool type: object @@ -132,6 +140,7 @@ stages: - template: common/ui-tests.yml parameters: androidPool: ${{ parameters.androidPool }} + androidLinuxPool: ${{ parameters.androidPoolLinux }} iosPool: ${{ parameters.iosPool }} windowsPool: ${{ parameters.windowsPool }} windowsBuildPool: ${{ parameters.windowsBuildPool }} diff --git a/global.json b/global.json index 12412bd3ddd3..679feeee8d3e 100644 --- a/global.json +++ b/global.json @@ -5,7 +5,7 @@ "msbuild-sdks": { "MSBuild.Sdk.Extras": "3.0.44", "Microsoft.Build.NoTargets": "3.7.0", - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25161.4" + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25164.2" }, "sdk": { "allowPrerelease": false diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTabbedPageSwipePage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTabbedPageSwipePage.xaml.cs index 43245bf020d9..07ecdcd1badc 100644 --- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTabbedPageSwipePage.xaml.cs +++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTabbedPageSwipePage.xaml.cs @@ -8,20 +8,11 @@ namespace Maui.Controls.Sample.Pages { public partial class AndroidTabbedPageSwipePage : Microsoft.Maui.Controls.TabbedPage { - ICommand? _returnToPlatformSpecificsPage; - public AndroidTabbedPageSwipePage() { InitializeComponent(); } - public AndroidTabbedPageSwipePage(ICommand restore) - { - InitializeComponent(); - - _returnToPlatformSpecificsPage = restore; - } - void OnSwipePagingButtonClicked(object sender, EventArgs e) { On().SetIsSwipePagingEnabled(!On().IsSwipePagingEnabled()); @@ -34,7 +25,7 @@ void OnSmoothScrollButtonClicked(object sender, EventArgs e) void OnReturnButtonClicked(object sender, EventArgs e) { - _returnToPlatformSpecificsPage?.Execute(null); + Navigation.PopAsync(); } } } diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTitleViewPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTitleViewPage.xaml.cs index 1211992f5167..2c681cd7bdb8 100644 --- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTitleViewPage.xaml.cs +++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTitleViewPage.xaml.cs @@ -6,25 +6,14 @@ namespace Maui.Controls.Sample.Pages { public partial class AndroidTitleViewPage : ContentPage { - readonly ICommand? _returnToPlatformSpecificsPage; - public AndroidTitleViewPage() { InitializeComponent(); } - public AndroidTitleViewPage(ICommand restore) - { - InitializeComponent(); - _returnToPlatformSpecificsPage = restore; - } - void OnReturnButtonClicked(object sender, EventArgs e) { - if (_returnToPlatformSpecificsPage == null) - return; - - _returnToPlatformSpecificsPage.Execute(null); + Navigation.PopAsync(); } } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSLargeTitlePage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSLargeTitlePage.xaml.cs index 1f0abc03c9ff..92cad19ebad7 100644 --- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSLargeTitlePage.xaml.cs +++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSLargeTitlePage.xaml.cs @@ -8,12 +8,9 @@ namespace Maui.Controls.Sample.Pages { public partial class iOSLargeTitlePage : ContentPage { - ICommand _returnToPlatformSpecificsPage; - - public iOSLargeTitlePage(ICommand restore) + public iOSLargeTitlePage() { InitializeComponent(); - _returnToPlatformSpecificsPage = restore; } void OnButtonClicked(object sender, EventArgs e) @@ -34,7 +31,7 @@ void OnButtonClicked(object sender, EventArgs e) void OnReturnButtonClicked(object sender, EventArgs e) { - _returnToPlatformSpecificsPage.Execute(null); + Navigation.PopAsync(); } } } diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSScrollViewPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSScrollViewPage.xaml.cs index 0e8c1d513ac5..6c9a340b2760 100644 --- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSScrollViewPage.xaml.cs +++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSScrollViewPage.xaml.cs @@ -7,12 +7,9 @@ namespace Maui.Controls.Sample.Pages { public partial class iOSScrollViewPage : Microsoft.Maui.Controls.FlyoutPage { - ICommand _returnToPlatformSpecificsPage; - - public iOSScrollViewPage(ICommand restore) + public iOSScrollViewPage() { InitializeComponent(); - _returnToPlatformSpecificsPage = restore; } void OnButtonClicked(object sender, EventArgs e) @@ -22,7 +19,7 @@ void OnButtonClicked(object sender, EventArgs e) void OnReturnButtonClicked(object sender, EventArgs e) { - _returnToPlatformSpecificsPage.Execute(null); + Navigation.PopAsync(); } } } diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTitleViewPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTitleViewPage.xaml.cs index 8f1990190b63..5f370facd790 100644 --- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTitleViewPage.xaml.cs +++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTitleViewPage.xaml.cs @@ -6,19 +6,15 @@ namespace Maui.Controls.Sample.Pages { public partial class iOSTitleViewPage : ContentPage { - ICommand _returnToPlatformSpecificsPage; - - public iOSTitleViewPage(ICommand restore) + public iOSTitleViewPage() { InitializeComponent(); - - _returnToPlatformSpecificsPage = restore; _searchBar.Effects.Add(Effect.Resolve("XamarinDocs.SearchBarEffect")); } void OnReturnButtonClicked(object sender, EventArgs e) { - _returnToPlatformSpecificsPage.Execute(null); + Navigation.PopAsync(); } } } diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentNavigationBarPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentNavigationBarPage.xaml.cs index 1c706d495df3..4a5923a12535 100644 --- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentNavigationBarPage.xaml.cs +++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentNavigationBarPage.xaml.cs @@ -8,12 +8,9 @@ namespace Maui.Controls.Sample.Pages { public partial class iOSTranslucentNavigationBarPage : ContentPage { - ICommand _returnToPlatformSpecificsPage; - - public iOSTranslucentNavigationBarPage(ICommand restore) + public iOSTranslucentNavigationBarPage() { InitializeComponent(); - _returnToPlatformSpecificsPage = restore; } void OnTranslucentNavigationBarButtonClicked(object sender, EventArgs e) @@ -23,7 +20,7 @@ void OnTranslucentNavigationBarButtonClicked(object sender, EventArgs e) void OnReturnButtonClicked(object sender, EventArgs e) { - _returnToPlatformSpecificsPage.Execute(null); + Navigation.PopAsync(); } } } diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentTabbedPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentTabbedPage.xaml.cs index d3aecf4a0e21..b65ec80ce919 100644 --- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentTabbedPage.xaml.cs +++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentTabbedPage.xaml.cs @@ -2,17 +2,13 @@ using System.Windows.Input; using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; - namespace Maui.Controls.Sample.Pages { public partial class iOSTranslucentTabbedPage : Microsoft.Maui.Controls.TabbedPage { - ICommand returnToPlatformSpecificsPage; - - public iOSTranslucentTabbedPage(ICommand restore) + public iOSTranslucentTabbedPage() { InitializeComponent(); - returnToPlatformSpecificsPage = restore; } void OnToggleButtonClicked(object sender, EventArgs e) @@ -33,7 +29,7 @@ void OnToggleButtonClicked(object sender, EventArgs e) void OnReturnButtonClicked(object sender, EventArgs e) { - returnToPlatformSpecificsPage.Execute(null); + Navigation.PopAsync(); } } } diff --git a/src/Controls/samples/Controls.Sample/ViewModels/PlatformSpecificsViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModels/PlatformSpecificsViewModel.cs index 11efd4790074..ffc6c5fa5a65 100644 --- a/src/Controls/samples/Controls.Sample/ViewModels/PlatformSpecificsViewModel.cs +++ b/src/Controls/samples/Controls.Sample/ViewModels/PlatformSpecificsViewModel.cs @@ -111,7 +111,7 @@ protected override IEnumerable CreateItems() new SectionModel(typeof(iOSTranslucentNavigationBarPage), "NavigationPage Translucent TabBar", "This iOS platform-specific is used to set the translucency mode of the tab bar on a NavigationPage."), - new SectionModel(typeof(MainPage), "TabbedPage Translucent TabBar", + new SectionModel(typeof(iOSTranslucentTabbedPage), "TabbedPage Translucent TabBar", "This iOS platform-specific is used to set the translucency mode of the tab bar on a TabbedPage."), #if MACCATALYST diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs index a3ae0cd48fd4..ac7a7ff248f9 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs @@ -610,6 +610,7 @@ public override void PushViewController(UIViewController viewController, bool an public override UIViewController PopViewController(bool animated) { + _popRequested = true; _pendingViewControllers = null; if (IsInMoreTab && ParentViewController is UITabBarController tabBarController) { diff --git a/src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs b/src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs index 8e632698b3dc..255278a1b4a4 100644 --- a/src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs +++ b/src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs @@ -114,10 +114,6 @@ void UpdateIconAndStyle(ToolbarItem item) { Image = result?.Value; Style = UIBarButtonItemStyle.Plain; - if (item.IconImageSource is FontImageSource fontImageSource && fontImageSource.Color is not null) - { - TintColor = fontImageSource.Color.ToPlatform(); - } }); } } diff --git a/src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs b/src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs index 1c88ba1eb9ac..0838765d1d26 100644 --- a/src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs +++ b/src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs @@ -271,6 +271,24 @@ void CollectionItemsSourceChanged(object sender, System.Collections.Specialized. return; } + // While Modifying the collection we should consider the ItemsUpdatingScrollMode to update the position + if (Carousel.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepLastItemInView) + { + if (count == 0) + { + carouselPosition = 0; + } + else + { + carouselPosition = count - 1; + } + + } + else if (Carousel.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepItemsInView) + { + carouselPosition = 0; + } + Carousel. Handler. MauiContext. diff --git a/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs b/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs index 4098ac3b456b..272eec544592 100644 --- a/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs +++ b/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs @@ -58,6 +58,7 @@ protected override void DisconnectHandler(ListViewBase platformView) { VirtualView.ScrollToRequested -= ScrollToRequested; CleanUpCollectionViewSource(platformView); + _formsEmptyView?.Handler?.DisconnectHandler(); base.DisconnectHandler(platformView); } diff --git a/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs index efdeffbb634a..ca07824e4a11 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs @@ -334,6 +334,7 @@ void CollectionViewUpdating(object sender, NotifyCollectionChangedEventArgs e) [UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")] void CollectionViewUpdated(object sender, NotifyCollectionChangedEventArgs e) { + int targetPosition; if (_positionAfterUpdate == -1) { return; @@ -341,7 +342,9 @@ void CollectionViewUpdated(object sender, NotifyCollectionChangedEventArgs e) _gotoPosition = -1; - var targetPosition = _positionAfterUpdate; + // We need to update the position while modifying the collection. + targetPosition = GetTargetPosition(); + _positionAfterUpdate = -1; SetPosition(targetPosition); @@ -354,6 +357,22 @@ int GetPositionWhenAddingItems(int carouselPosition, int currentItemPosition) return currentItemPosition != -1 ? currentItemPosition : carouselPosition; } + private int GetTargetPosition() + { + + if (ItemsSource.ItemCount == 0) + { + return 0; + } + + return ItemsView.ItemsUpdatingScrollMode switch + { + ItemsUpdatingScrollMode.KeepItemsInView => 0, + ItemsUpdatingScrollMode.KeepLastItemInView => ItemsSource.ItemCount - 1, + _ => _positionAfterUpdate + }; + } + int GetPositionWhenResetItems() { //If we are reseting the collection Position should go to 0 diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs index c42f5f3a4aad..4111016b918a 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs @@ -164,14 +164,7 @@ void CollectionChanged(NotifyCollectionChangedEventArgs args) // Force UICollectionView to get the internal accounting straight var collectionView = controller.CollectionView; - if (!collectionView.Hidden) - { - var numberOfSections = collectionView.NumberOfSections(); - for (int section = 0; section < numberOfSections; section++) - { - collectionView.NumberOfItemsInSection(section); - } - } + UpdateSection(collectionView); switch (args.Action) { @@ -193,8 +186,23 @@ void CollectionChanged(NotifyCollectionChangedEventArgs args) default: throw new ArgumentOutOfRangeException(); } + + // Calculate section and item counts after processing changes + // to ensure UICollectionView reflects the updated state + UpdateSection(collectionView); } + void UpdateSection(UICollectionView collectionView) + { + if (!collectionView.Hidden) + { + var numberOfSections = collectionView.NumberOfSections(); + for (int section = 0; section < numberOfSections; section++) + { + collectionView.NumberOfItemsInSection(section); + } + } + } void Reload(bool collectionWasReset = false) { ResetGroupTracking(); diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs index b7cfd68c3a19..c7b5d7f429e3 100644 --- a/src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs +++ b/src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs @@ -254,6 +254,7 @@ void CollectionViewUpdating(object sender, NotifyCollectionChangedEventArgs e) [UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")] void CollectionViewUpdated(object sender, NotifyCollectionChangedEventArgs e) { + int targetPosition; if (_positionAfterUpdate == -1) { return; @@ -261,7 +262,9 @@ void CollectionViewUpdated(object sender, NotifyCollectionChangedEventArgs e) //_gotoPosition = -1; - var targetPosition = _positionAfterUpdate; + // We need to update the position while modifying the collection. + targetPosition = GetTargetPosition(); + _positionAfterUpdate = -1; SetPosition(targetPosition); @@ -291,6 +294,21 @@ int GetPositionWhenAddingItems(int carouselPosition, int currentItemPosition) return currentItemPosition != -1 ? currentItemPosition : carouselPosition; } + private int GetTargetPosition() + { + if (ItemsSource.ItemCount == 0) + { + return 0; + } + + return ItemsView.ItemsUpdatingScrollMode switch + { + ItemsUpdatingScrollMode.KeepItemsInView => 0, + ItemsUpdatingScrollMode.KeepLastItemInView => ItemsSource.ItemCount - 1, + _ => _positionAfterUpdate + }; + } + int GetPositionWhenResetItems() { //If we are reseting the collection Position should go to 0 diff --git a/src/Controls/src/Core/Internals/PropertyPropagationExtensions.cs b/src/Controls/src/Core/Internals/PropertyPropagationExtensions.cs index a6ef8ef550c8..93713f124de9 100644 --- a/src/Controls/src/Core/Internals/PropertyPropagationExtensions.cs +++ b/src/Controls/src/Core/Internals/PropertyPropagationExtensions.cs @@ -1,4 +1,5 @@ #nullable disable +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -8,6 +9,15 @@ namespace Microsoft.Maui.Controls.Internals /// public static class PropertyPropagationExtensions { + [Obsolete] + internal static void PropagatePropertyChanged(string propertyName, Element element, IEnumerable children) + { + if (children == null) + return; + + PropagatePropertyChanged(propertyName, element, children.OfType().ToList()); + } + internal static void PropagatePropertyChanged(string propertyName, Element element, IReadOnlyList children) { if (propertyName == null || propertyName == VisualElement.FlowDirectionProperty.PropertyName) diff --git a/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs b/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs index b93f41060075..5d6b9b990f24 100644 --- a/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs +++ b/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs @@ -10,6 +10,8 @@ using Android.Views; using AndroidX.AppCompat.Widget; using AndroidX.CoordinatorLayout.Widget; +using AndroidX.Core.Content; +using AndroidX.Core.Graphics; using AndroidX.Fragment.App; using AndroidX.ViewPager2.Widget; using Google.Android.Material.AppBar; @@ -386,7 +388,7 @@ void TabSelected(TabLayout.Tab tab) if (Element.Children.Count > selectedIndex && selectedIndex >= 0) Element.CurrentPage = Element.Children[selectedIndex]; - SetIconColorFilter(tab, true); + SetIconColorFilter(Element.CurrentPage, tab, true); } void TeardownPage(Page page) @@ -525,10 +527,10 @@ void UpdateTabIcons() } } - protected virtual void SetTabIconImageSource(TabLayout.Tab tab, Drawable icon) + protected virtual void SetTabIconImageSource(Page page, TabLayout.Tab tab, Drawable icon) { tab.SetIcon(icon); - SetIconColorFilter(tab); + SetIconColorFilter(page, tab); } void SetTabIconImageSource(Page page, TabLayout.Tab tab) @@ -537,7 +539,7 @@ void SetTabIconImageSource(Page page, TabLayout.Tab tab) _context, result => { - SetTabIconImageSource(tab, result?.Value); + SetTabIconImageSource(page, tab, result?.Value); }); } @@ -655,25 +657,26 @@ protected virtual ColorStateList GetItemTextColorStates() protected virtual ColorStateList GetItemIconTintColorState() { - if (IsBottomTabPlacement) + if (_orignalTabIconColors is null) { - if (_orignalTabIconColors is null) - _orignalTabIconColors = _bottomNavigationView.ItemIconTintList; + _orignalTabIconColors = IsBottomTabPlacement ? _bottomNavigationView.ItemIconTintList : _tabLayout.TabIconTint; } - // this ensures that existing behavior doesn't change - else if (!IsBottomTabPlacement && BarSelectedItemColor != null && BarItemColor == null) - return null; Color barItemColor = BarItemColor; Color barSelectedItemColor = BarSelectedItemColor; - if (barItemColor == null && barSelectedItemColor == null) + if (barItemColor is null && barSelectedItemColor is null) + { return _orignalTabIconColors; + } - if (_newTabIconColors != null) + if (_newTabIconColors is not null) + { return _newTabIconColors; + } int defaultColor; + int checkedColor; if (barItemColor is not null) { @@ -681,50 +684,67 @@ protected virtual ColorStateList GetItemIconTintColorState() } else { -#pragma warning disable XAOBS001 // Obsolete - var styledAttributes = - TintTypedArray.ObtainStyledAttributes(_context.Context, null, Resource.Styleable.NavigationBarView, Resource.Attribute.bottomNavigationStyle, 0); -#pragma warning restore XAOBS001 // Obsolete + defaultColor = GetDefaultColor(); + } + + if (barSelectedItemColor is not null) + { + checkedColor = barSelectedItemColor.ToPlatform().ToArgb(); + } + else + { + checkedColor = GetDefaultColor(); + } + + _newTabIconColors = GetColorStateList(defaultColor, checkedColor); + return _newTabIconColors; + } - try + int GetDefaultColor() + { + int defaultColor; + var styledAttributes = + _context.Context.Theme.ObtainStyledAttributes( + 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 { - var defaultColors = styledAttributes.GetColorStateList(Resource.Styleable.NavigationBarView_itemIconTint); - if (defaultColors is not null) + // 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 = defaultColors.DefaultColor; + defaultColor = ColorUtils.SetAlphaComponent( + ContextCompat.GetColor(_context.Context, Resource.Color.primary_dark_material_light), + 153); // 60% opacity } 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(); + defaultColor = ColorUtils.SetAlphaComponent( + ContextCompat.GetColor(_context.Context, Resource.Color.primary_dark_material_dark), + 153); // 60% opacity } } - finally - { - styledAttributes.Recycle(); - } } - - if (barItemColor == null && _orignalTabIconColors != null) - defaultColor = _orignalTabIconColors.DefaultColor; - - int checkedColor = defaultColor; - - if (barSelectedItemColor != null) - checkedColor = barSelectedItemColor.ToPlatform().ToArgb(); - - _newTabIconColors = GetColorStateList(defaultColor, checkedColor); - return _newTabIconColors; + finally + { + styledAttributes.Recycle(); + } + return defaultColor; } - void OnMoreSheetDismissed(object sender, EventArgs e) { var index = Element.Children.IndexOf(Element.CurrentPage); @@ -759,11 +779,11 @@ void UpdateItemIconColor() _bottomNavigationView.ItemIconTintList = GetItemIconTintColorState() ?? _orignalTabIconColors; else { - var colors = GetItemIconTintColorState() ?? _orignalTabIconColors; for (int i = 0; i < _tabLayout.TabCount; i++) { TabLayout.Tab tab = _tabLayout.GetTabAt(i); - this.SetIconColorFilter(tab); + var page = Element.Children[i]; + this.SetIconColorFilter(page, tab); } } } @@ -802,18 +822,18 @@ void UpdateBarTextColor() _tabLayout.TabTextColors = _currentBarTextColorStateList; } - void SetIconColorFilter(TabLayout.Tab tab) + void SetIconColorFilter(Page page, TabLayout.Tab tab) { - SetIconColorFilter(tab, _tabLayout.GetTabAt(_tabLayout.SelectedTabPosition) == tab); + SetIconColorFilter(page, tab, _tabLayout.GetTabAt(_tabLayout.SelectedTabPosition) == tab); } - void SetIconColorFilter(TabLayout.Tab tab, bool selected) + void SetIconColorFilter(Page page, TabLayout.Tab tab, bool selected) { var icon = tab.Icon; if (icon == null) return; - var colors = GetItemIconTintColorState(); + ColorStateList colors = (page.IconImageSource is FontImageSource fontImageSource && fontImageSource.Color is not null) ? null : GetItemIconTintColorState(); if (colors == null) ADrawableCompat.SetTintList(icon, null); else @@ -989,8 +1009,8 @@ void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab) void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab) { - _tabbedPageManager.SetIconColorFilter(tab, false); + _tabbedPageManager.SetIconColorFilter(_tabbedPageManager.Element.CurrentPage, tab, false); } } } -} +} \ No newline at end of file diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs index a15e776586d1..1fe23332fa72 100644 --- a/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs +++ b/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs @@ -13,6 +13,7 @@ internal static class ToolbarExtensions public static void UpdateIsVisible(this MauiToolbar platformToolbar, Toolbar toolbar) { platformToolbar.Visibility = (toolbar.IsVisible) ? UI.Xaml.Visibility.Visible : UI.Xaml.Visibility.Collapsed; + UpdateBackButtonVisibility(platformToolbar, toolbar); } public static void UpdateTitleIcon(this MauiToolbar platformToolbar, Toolbar toolbar) @@ -35,8 +36,7 @@ public static void UpdateBackButton(this MauiToolbar platformToolbar, Toolbar to platformToolbar.IsBackEnabled = toolbar.BackButtonEnabled && toolbar.BackButtonVisible; - platformToolbar - .IsBackButtonVisible = (toolbar.BackButtonVisible) ? NavigationViewBackButtonVisible.Visible : NavigationViewBackButtonVisible.Collapsed; + UpdateBackButtonVisibility(platformToolbar, toolbar); toolbar.Handler?.UpdateValue(nameof(Toolbar.BarBackground)); } @@ -84,5 +84,13 @@ public static void UpdateToolbarDynamicOverflowEnabled(this MauiToolbar platform platformToolbar.CommandBar.IsDynamicOverflowEnabled = toolbar.DynamicOverflowEnabled; } + + private static void UpdateBackButtonVisibility(MauiToolbar platformToolbar, Toolbar toolbar) + { + platformToolbar.IsBackButtonVisible = + toolbar.BackButtonVisible + ? NavigationViewBackButtonVisible.Visible + : NavigationViewBackButtonVisible.Collapsed; + } } } diff --git a/src/Controls/src/Core/Stepper/Stepper.cs b/src/Controls/src/Core/Stepper/Stepper.cs index f68bc03f2971..4c649b298fc2 100644 --- a/src/Controls/src/Core/Stepper/Stepper.cs +++ b/src/Controls/src/Core/Stepper/Stepper.cs @@ -12,7 +12,7 @@ public partial class Stepper : View, IElementConfiguration, IStepper { /// Bindable property for . public static readonly BindableProperty MaximumProperty = BindableProperty.Create(nameof(Maximum), typeof(double), typeof(Stepper), 100.0, - validateValue: (bindable, value) => (double)value > ((Stepper)bindable).Minimum, + validateValue: (bindable, value) => (double)value >= ((Stepper)bindable).Minimum, coerceValue: (bindable, value) => { var stepper = (Stepper)bindable; @@ -22,7 +22,7 @@ public partial class Stepper : View, IElementConfiguration, IStepper /// Bindable property for . public static readonly BindableProperty MinimumProperty = BindableProperty.Create(nameof(Minimum), typeof(double), typeof(Stepper), 0.0, - validateValue: (bindable, value) => (double)value < ((Stepper)bindable).Maximum, + validateValue: (bindable, value) => (double)value <= ((Stepper)bindable).Maximum, coerceValue: (bindable, value) => { var stepper = (Stepper)bindable; diff --git a/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.Windows.cs index 82b35e999e42..025a2d4adff6 100644 --- a/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.Windows.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.ComponentModel; +using System.Threading.Tasks; using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.Shapes; using Microsoft.Maui.Graphics; @@ -60,6 +61,23 @@ await AttachAndRun(border, (handler) => await AssertColorAtPoint(border, expected, typeof(BorderHandler), cornerRadius, cornerRadius); } + [Fact] + [Description("The IsVisible property of a Border should match with native IsVisible")] + public async Task VerifyBorderIsVisibleProperty() + { + var border = new Border(); + border.IsVisible = false; + var expectedValue = border.IsVisible; + + var handler = await CreateHandlerAsync(border); + var nativeView = GetNativeBorder(handler); + await InvokeOnMainThreadAsync(() => + { + var isVisible = nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + Assert.Equal(expectedValue, isVisible); + }); + } + ContentPanel GetNativeBorder(BorderHandler borderHandler) => borderHandler.PlatformView; diff --git a/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs b/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs index 4e3d9cb10525..1c981f97c74b 100644 --- a/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.Shapes; using Microsoft.Maui.DeviceTests.ImageAnalysis; using Microsoft.Maui.Graphics; @@ -258,6 +259,35 @@ await InvokeOnMainThreadAsync(() => await AssertionExtensions.WaitForGC(handlerReference, platformViewReference); } + + [Fact(DisplayName = "Border With Stroke Shape And Name Does Not Leak")] + public async Task DoesNotLeakWithStrokeShape() + { + SetupBuilder(); + WeakReference platformViewReference = null; + WeakReference handlerReference = null; + + await InvokeOnMainThreadAsync(() => + { + var layout = new Grid(); + var border = new Border(); + var rect = new RoundRectangle(); + border.StrokeShape = rect; + layout.Add(border); + + var nameScope = new NameScope(); + ((INameScope)nameScope).RegisterName("Border", border); + layout.transientNamescope = nameScope; + border.transientNamescope = nameScope; + rect.transientNamescope = nameScope; + + var handler = CreateHandler(layout); + handlerReference = new WeakReference(border.Handler); + platformViewReference = new WeakReference(border.Handler.PlatformView); + }); + + await AssertionExtensions.WaitForGC(handlerReference, platformViewReference); + } [Fact("Ensures the border renders the expected size - Issue 15339")] public async Task BorderAndStrokeIsCorrectSize() diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs index e087b12bb3fc..0d84d49eb8e3 100644 --- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs @@ -1,15 +1,18 @@ using System; +using System.ComponentModel; using System.Threading.Tasks; +using Microsoft.Maui.Controls; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; +using Xunit; namespace Microsoft.Maui.DeviceTests { - public partial class BoxViewTests - { - MauiShapeView GetNativeBoxView(ShapeViewHandler boxViewViewHandler) => - boxViewViewHandler.PlatformView; + public partial class BoxViewTests + { + MauiShapeView GetNativeBoxView(ShapeViewHandler boxViewViewHandler) => + boxViewViewHandler.PlatformView; Task GetPlatformOpacity(ShapeViewHandler handler) { @@ -19,5 +22,88 @@ Task GetPlatformOpacity(ShapeViewHandler handler) return nativeView.Alpha; }); } + + [Fact] + [Description("The ScaleX property of a BoxView should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var boxView = new BoxView() { ScaleX = 0.45f }; + var expected = boxView.ScaleX; + var handler = await CreateHandlerAsync(boxView); + var platformBoxView = GetNativeBoxView(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformBoxView.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a BoxView should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var boxView = new BoxView() { ScaleY = 1.23f }; + var expected = boxView.ScaleY; + var handler = await CreateHandlerAsync(boxView); + var platformBoxView = GetNativeBoxView(handler); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformBoxView.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a BoxView should match with native Scale")] + public async Task ScaleConsistent() + { + var boxView = new BoxView() { Scale = 2.0f }; + var expected = boxView.Scale; + var handler = await CreateHandlerAsync(boxView); + var platformBoxView = GetNativeBoxView(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformBoxView.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformBoxView.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a BoxView should match with native RotationX")] + public async Task RotationXConsistent() + { + var boxView = new BoxView() { RotationX = 33.0 }; + var expected = boxView.RotationX; + var handler = await CreateHandlerAsync(boxView); + var platformBoxView = GetNativeBoxView(handler); + var platformRotationX = await InvokeOnMainThreadAsync(() => platformBoxView.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a BoxView should match with native RotationY")] + public async Task RotationYConsistent() + { + var boxView = new BoxView() { RotationY = 87.0 }; + var expected = boxView.RotationY; + var handler = await CreateHandlerAsync(boxView); + var platformBoxView = GetNativeBoxView(handler); + var platformRotationY = await InvokeOnMainThreadAsync(() => platformBoxView.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a BoxView should match with native Rotation")] + public async Task RotationConsistent() + { + var boxView = new BoxView() { Rotation = 23.0 }; + var expected = boxView.Rotation; + var handler = await CreateHandlerAsync(boxView); + var platformBoxView = GetNativeBoxView(handler); + var platformRotation = await InvokeOnMainThreadAsync(() => platformBoxView.Rotation); + Assert.Equal(expected, platformRotation); + } + + Task GetPlatformIsVisible(ShapeViewHandler boxViewViewHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetNativeBoxView(boxViewViewHandler); + return nativeView.Visibility == Android.Views.ViewStates.Visible; + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs index 994e01829fd3..88e0735833b1 100644 --- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs @@ -19,5 +19,14 @@ Task GetPlatformOpacity(ShapeViewHandler handler) return (float)nativeView.Opacity; }); } + + Task GetPlatformIsVisible(ShapeViewHandler boxViewHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetNativeBoxView(boxViewHandler); + return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs index e6a20ffb1bcb..cf574048faf1 100644 --- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs @@ -6,7 +6,7 @@ using Microsoft.Maui.Handlers; using Microsoft.Maui.Hosting; using Xunit; - +using Microsoft.Maui.Controls.Handlers; namespace Microsoft.Maui.DeviceTests { @@ -64,5 +64,21 @@ await InvokeOnMainThreadAsync(async () => Assert.Equal(expectedValue, nativeOpacityValue); }); } + + [Fact] + [Description("The IsVisible property of a BoxView should match with native IsVisible")] + public async Task VerifyBoxViewIsVisibleProperty() + { + var boxView = new BoxView(); + boxView.IsVisible = false; + var expectedValue = boxView.IsVisible; + + var handler = await CreateHandlerAsync(boxView); + await InvokeOnMainThreadAsync( async () => + { + var isVisible = await GetPlatformIsVisible(handler); + Assert.Equal(expectedValue, isVisible); + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.iOS.cs index 67c5fbc701a5..a5180aec06a2 100644 --- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.iOS.cs @@ -7,6 +7,8 @@ using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; using Xunit; +using System.ComponentModel; +using Microsoft.Maui.Controls.Handlers; namespace Microsoft.Maui.DeviceTests { @@ -20,7 +22,16 @@ Task GetPlatformOpacity(ShapeViewHandler handler) return InvokeOnMainThreadAsync(() => { var nativeView = GetNativeBoxView(handler); - return (float)nativeView.Alpha; + return (float)nativeView.Alpha; + }); + } + + Task GetPlatformIsVisible(ShapeViewHandler boxViewHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetNativeBoxView(boxViewHandler); + return !nativeView.Hidden; }); } diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Android.cs index b8b71fead772..c0a229238f06 100644 --- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Android.cs @@ -34,6 +34,15 @@ Task GetPlatformOpacity(ButtonHandler buttonHandler) }); } + Task GetPlatformIsVisible(ButtonHandler buttonHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformButton(buttonHandler); + return nativeView.Visibility == Android.Views.ViewStates.Visible; + }); + } + [Theory(DisplayName = "Button Icon has Correct Position"), Category(TestCategory.Layout)] [InlineData(Button.ButtonContentLayout.ImagePosition.Left)] [InlineData(Button.ButtonContentLayout.ImagePosition.Top)] @@ -107,5 +116,79 @@ await InvokeOnMainThreadAsync(async () => Assert.Equal(expectedValue, nativeOpacityValue); }); } + + [Fact] + [Description("The ScaleX property of a Button should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var button = new Button() { ScaleX = 0.45f }; + var expected = button.ScaleX; + var handler = await CreateHandlerAsync(button); + var platformButton = GetPlatformButton(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformButton.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a Button should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var button = new Button() { ScaleY = 1.23f }; + var expected = button.ScaleY; + var handler = await CreateHandlerAsync(button); + var platformButton = GetPlatformButton(handler); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformButton.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a Button should match with native Scale")] + public async Task ScaleConsistent() + { + var button = new Button() { Scale = 2.0f }; + var expected = button.Scale; + var handler = await CreateHandlerAsync(button); + var platformButton = GetPlatformButton(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformButton.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformButton.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a Button should match with native RotationX")] + public async Task RotationXConsistent() + { + var button = new Button() { RotationX = 33.0 }; + var expected = button.RotationX; + var handler = await CreateHandlerAsync(button); + var platformButton = GetPlatformButton(handler); + var platformRotationX = await InvokeOnMainThreadAsync(() => platformButton.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a Button should match with native RotationY")] + public async Task RotationYConsistent() + { + var button = new Button() { RotationY = 87.0 }; + var expected = button.RotationY; + var handler = await CreateHandlerAsync(button); + var platformButton = GetPlatformButton(handler); + var platformRotationY = await InvokeOnMainThreadAsync(() => platformButton.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a Button should match with native Rotation")] + public async Task RotationConsistent() + { + var button = new Button() { Rotation = 23.0 }; + var expected = button.Rotation; + var handler = await CreateHandlerAsync(button); + var platformButton = GetPlatformButton(handler); + var platformRotation = await InvokeOnMainThreadAsync(() => platformButton.Rotation); + Assert.Equal(expected, platformRotation); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Windows.cs index e3029fe05cd2..c7713d1d4c02 100644 --- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Windows.cs @@ -31,6 +31,15 @@ Task GetPlatformOpacity(ButtonHandler buttonHandler) }); } + Task GetPlatformIsVisible(ButtonHandler buttonHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformButton(buttonHandler); + return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + }); + } + [Fact] [Description("The Opacity property of a Button should match with native Opacity")] public async Task VerifyButtonOpacityProperty() @@ -48,5 +57,6 @@ await InvokeOnMainThreadAsync(async () => Assert.Equal(expectedValue, nativeOpacityValue); }); } + } } diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.cs index d0ca13d84f3d..84cced7c4889 100644 --- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.cs @@ -70,5 +70,21 @@ public async Task ButtonBackgroundColorConsistent() await ValidateHasColor(button, expected, typeof(ButtonHandler)); } + + [Fact] + [Description("The IsVisible property of a Button should match with native IsVisible")] + public async Task VerifyButtonIsVisibleProperty() + { + var button = new Button(); + button.IsVisible = false; + var expectedValue = button.IsVisible; + + var handler = await CreateHandlerAsync(button); + await InvokeOnMainThreadAsync(async () => + { + var isVisible = await GetPlatformIsVisible(handler); + Assert.Equal(expectedValue, isVisible); + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs index 61e7b3bbfaba..0b1f27eef54b 100644 --- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs @@ -22,6 +22,15 @@ Task GetPlatformText(ButtonHandler buttonHandler) UILineBreakMode GetPlatformLineBreakMode(ButtonHandler buttonHandler) => GetPlatformButton(buttonHandler).TitleLabel.LineBreakMode; + Task GetPlatformIsVisible(ButtonHandler buttonHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformButton(buttonHandler); + return !nativeView.Hidden; + }); + } + [Fact("Clicked works after GC")] public async Task ClickedWorksAfterGC() { diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs index dd8faaa342a8..7403625f6268 100644 --- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Threading.Tasks; using AndroidX.AppCompat.Widget; using Microsoft.Maui.Controls; @@ -8,11 +9,11 @@ namespace Microsoft.Maui.DeviceTests { - [Collection(RunInNewWindowCollection)] - public partial class CheckBoxTests - { - AppCompatCheckBox GetNativeCheckBox(CheckBoxHandler checkBoxHandler) => - checkBoxHandler.PlatformView; + [Collection(RunInNewWindowCollection)] + public partial class CheckBoxTests + { + AppCompatCheckBox GetNativeCheckBox(CheckBoxHandler checkBoxHandler) => + checkBoxHandler.PlatformView; Task GetPlatformOpacity(CheckBoxHandler CheckBoxHandler) { @@ -22,5 +23,88 @@ Task GetPlatformOpacity(CheckBoxHandler CheckBoxHandler) return nativeView.Alpha; }); } + + [Fact] + [Description("The ScaleX property of a CheckBox should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var checkBox = new CheckBox() { ScaleX = 0.45f }; + var expected = checkBox.ScaleX; + var handler = await CreateHandlerAsync(checkBox); + var PlatformCheckBox = GetNativeCheckBox(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => PlatformCheckBox.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a CheckBox should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var checkBox = new CheckBox() { ScaleY = 1.23f }; + var expected = checkBox.ScaleY; + var handler = await CreateHandlerAsync(checkBox); + var PlatformCheckBox = GetNativeCheckBox(handler); + var platformScaleY = await InvokeOnMainThreadAsync(() => PlatformCheckBox.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a CheckBox should match with native Scale")] + public async Task ScaleConsistent() + { + var checkBox = new CheckBox() { Scale = 2.0f }; + var expected = checkBox.Scale; + var handler = await CreateHandlerAsync(checkBox); + var PlatformCheckBox = GetNativeCheckBox(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => PlatformCheckBox.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => PlatformCheckBox.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a CheckBox should match with native RotationX")] + public async Task RotationXConsistent() + { + var checkBox = new CheckBox() { RotationX = 33.0 }; + var expected = checkBox.RotationX; + var handler = await CreateHandlerAsync(checkBox); + var PlatformCheckBox = GetNativeCheckBox(handler); + var platformRotationX = await InvokeOnMainThreadAsync(() => PlatformCheckBox.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a CheckBox should match with native RotationY")] + public async Task RotationYConsistent() + { + var checkBox = new CheckBox() { RotationY = 87.0 }; + var expected = checkBox.RotationY; + var handler = await CreateHandlerAsync(checkBox); + var PlatformCheckBox = GetNativeCheckBox(handler); + var platformRotationY = await InvokeOnMainThreadAsync(() => PlatformCheckBox.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a CheckBox should match with native Rotation")] + public async Task RotationConsistent() + { + var checkBox = new CheckBox() { Rotation = 23.0 }; + var expected = checkBox.Rotation; + var handler = await CreateHandlerAsync(checkBox); + var PlatformCheckBox = GetNativeCheckBox(handler); + var platformRotation = await InvokeOnMainThreadAsync(() => PlatformCheckBox.Rotation); + Assert.Equal(expected, platformRotation); + } + + Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetNativeCheckBox(checkBoxHandler); + return nativeView.Visibility == Android.Views.ViewStates.Visible; + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs index e291d1855621..b37682a406a4 100644 --- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs @@ -19,5 +19,14 @@ Task GetPlatformOpacity(CheckBoxHandler checkBoxHandler) return (float)nativeView.Opacity; }); } + + Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetNativeCheckBox(checkBoxHandler); + return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.cs index be8021a5bc6c..f2213688efe0 100644 --- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.cs @@ -62,5 +62,21 @@ await InvokeOnMainThreadAsync(async () => Assert.Equal(expectedValue, nativeOpacityValue); }); } + + [Fact] + [Description("The IsVisible property of a CheckBox should match with native IsVisible")] + public async Task VerifyCheckBoxIsVisibleProperty() + { + var checkBox = new CheckBox(); + checkBox.IsVisible = false; + var expectedValue = checkBox.IsVisible; + + var handler = await CreateHandlerAsync(checkBox); + await InvokeOnMainThreadAsync(async () => + { + var isVisible = await GetPlatformIsVisible(handler); + Assert.Equal(expectedValue, isVisible); + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs index 879dcfbab6e8..c907dcf606ff 100644 --- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs @@ -21,5 +21,14 @@ Task GetPlatformOpacity(CheckBoxHandler checkBoxHandler) return (float)nativeView.Alpha; }); } + + Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetNativeCheckBox(checkBoxHandler); + return !nativeView.Hidden; + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.Android.cs index aeca193a7860..92fa952203ba 100644 --- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.Android.cs @@ -5,6 +5,8 @@ using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; using Xunit; +using System.Collections.ObjectModel; +using System.Linq; namespace Microsoft.Maui.DeviceTests { @@ -86,6 +88,102 @@ await CreateHandlerAndAddToWindow(contentPage, }); } + //src/Compatibility/Core/tests/Android/RendererTests.cs + [Fact(DisplayName = "EmptySource should have a count of zero")] + [Trait("Category", "CollectionView")] + public void EmptySourceCountIsZero() + { + var emptySource = new EmptySource(); + var count = emptySource.Count; + Assert.Equal(0, count); + } + + //src/Compatibility/Core/tests/Android/ObservableItemsSourceTests.cs#L52 + [Fact(DisplayName = "CollectionView with SnapPointsType set should not crash")] + public async Task SnapPointsDoNotCrashOnOlderAPIs() + { + SetupBuilder(); + + var collectionView = new CollectionView(); + + var itemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical) + { + SnapPointsType = SnapPointsType.Mandatory + }; + collectionView.ItemsLayout = itemsLayout; + + await InvokeOnMainThreadAsync(() => + { + var handler = CreateHandler(collectionView); + + var platformView = handler.PlatformView; + + collectionView.Handler = null; + }); + } + + //src/Compatibility/Core/tests/Android/ObservableItemsSourceTests.cs#L52 + [Fact(DisplayName = "ObservableCollection modifications are reflected after UI thread processes them")] + public async Task ObservableSourceItemsCountConsistent() + { + SetupBuilder(); + + var source = new ObservableCollection(); + source.Add("Item 1"); + source.Add("Item 2"); + var ois = ItemsSourceFactory.Create(source, Application.Current, new MockCollectionChangedNotifier()); + + Assert.Equal(2, ois.Count); + + source.Add("Item 3"); + var count = 0; + await InvokeOnMainThreadAsync(() => + { + count = ois.Count; + Assert.Equal(3, ois.Count); + }); + } + + class MockCollectionChangedNotifier : ICollectionChangedNotifier + { + public int InsertCount; + public int RemoveCount; + + public void NotifyDataSetChanged() + { + } + + public void NotifyItemChanged(IItemsViewSource source, int startIndex) + { + } + + public void NotifyItemInserted(IItemsViewSource source, int startIndex) + { + InsertCount += 1; + } + + public void NotifyItemMoved(IItemsViewSource source, int fromPosition, int toPosition) + { + } + + public void NotifyItemRangeChanged(IItemsViewSource source, int start, int end) + { + } + + public void NotifyItemRangeInserted(IItemsViewSource source, int startIndex, int count) + { + } + + public void NotifyItemRangeRemoved(IItemsViewSource source, int startIndex, int count) + { + } + + public void NotifyItemRemoved(IItemsViewSource source, int startIndex) + { + RemoveCount += 1; + } + } + Rect GetCollectionViewCellBounds(IView cellContent) { if (!cellContent.ToPlatform().IsLoaded()) diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs index eeb513ac4081..e538b903ce26 100644 --- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs @@ -10,6 +10,8 @@ using UIKit; using Xunit; using Xunit.Sdk; +using Foundation; +using Microsoft.Maui.Handlers; namespace Microsoft.Maui.DeviceTests { @@ -125,6 +127,73 @@ await InvokeOnMainThreadAsync(() => #endif } + //src/Compatibility/Core/tests/iOS/ObservableItemsSourceTests.cs + [Fact(DisplayName = "IndexPath Range Generation Is Correct")] + public void GenerateIndexPathRange() + { + SetupBuilder(); + + var result = IndexPathHelpers.GenerateIndexPathRange(0, 0, 5); + + Assert.Equal(5, result.Length); + + Assert.Equal(0, result[0].Section); + Assert.Equal(0, (int)result[0].Item); + + Assert.Equal(0, result[4].Section); + Assert.Equal(4, (int)result[4].Item); + } + + //src/Compatibility/Core/tests/iOS/ObservableItemsSourceTests.cs + [Fact(DisplayName = "IndexPath Range Generation For Loops Is Correct")] + public void GenerateIndexPathRangeForLoop() + { + SetupBuilder(); + + var result = IndexPathHelpers.GenerateLoopedIndexPathRange(0, 15, 3, 2, 3); + + Assert.Equal(9, result.Length); + + for (int i = 0; i < result.Length; i++) + { + Assert.Equal(0, result[i].Section); + } + + Assert.Equal(2, (int)result[0].Item); + Assert.Equal(3, (int)result[1].Item); + Assert.Equal(4, (int)result[2].Item); + + Assert.Equal(7, (int)result[3].Item); + Assert.Equal(8, (int)result[4].Item); + Assert.Equal(9, (int)result[5].Item); + + Assert.Equal(12, (int)result[6].Item); + Assert.Equal(13, (int)result[7].Item); + Assert.Equal(14, (int)result[8].Item); + } + + //src/Compatibility/Core/tests/iOS/ObservableItemsSourceTests.cs + [Fact(DisplayName = "IndexPath Validity Check Is Correct")] + public void IndexPathValidTest() + { + var list = new List + { + "one", + "two", + "three" + }; + + var source = new ListSource((IEnumerable)list); + + var valid = NSIndexPath.FromItemSection(2, 0); + var invalidItem = NSIndexPath.FromItemSection(7, 0); + var invalidSection = NSIndexPath.FromItemSection(1, 9); + + Assert.True(source.IsIndexPathValid(valid)); + Assert.False(source.IsIndexPathValid(invalidItem)); + Assert.False(source.IsIndexPathValid(invalidSection)); + } + /// /// Simulates what a developer might do with a Page/View /// diff --git a/src/Controls/tests/DeviceTests/Elements/DatePicker/DatePickerTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/DatePicker/DatePickerTests.Windows.cs new file mode 100644 index 000000000000..394255698f83 --- /dev/null +++ b/src/Controls/tests/DeviceTests/Elements/DatePicker/DatePickerTests.Windows.cs @@ -0,0 +1,43 @@ +using System.ComponentModel; +using System.Threading.Tasks; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Platform; +using Microsoft.UI.Xaml.Controls; +using Xunit; +using WSolidColorBrush = Microsoft.UI.Xaml.Media.SolidColorBrush; + +namespace Microsoft.Maui.DeviceTests; + +public partial class DatePickerTests +{ + [Fact] + [Description("The DatePicker Text and Icon Color should work properly on PointerOver")] + public async Task DatePickerTextAndIconColorShouldWorkProperlyOnPointerOver() + { + SetupBuilder(); + + var datePicker = new Controls.DatePicker + { + TextColor = Colors.Red + }; + var expectedValue = datePicker.TextColor; + + var handler = await CreateHandlerAsync(datePicker); + var platformView = GetPlatformControl(handler); + + await InvokeOnMainThreadAsync(() => + { + var foregroundPointerOverBrush = platformView.Resources["CalendarDatePickerTextForegroundPointerOver"] as WSolidColorBrush; + var foregroundPointerOverColor = foregroundPointerOverBrush.Color.ToColor(); + Assert.Equal(expectedValue, foregroundPointerOverColor); + + var glyphForegroundPointerOverBrush = platformView.Resources["CalendarDatePickerCalendarGlyphForegroundPointerOver"] as WSolidColorBrush; + var glyphForegroundPointerOverColor = glyphForegroundPointerOverBrush.Color.ToColor(); + Assert.Equal(expectedValue, glyphForegroundPointerOverColor); + }); + } + + static CalendarDatePicker GetPlatformControl(DatePickerHandler handler) => + handler.PlatformView; +} \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/DatePicker/DatePickerTests.cs b/src/Controls/tests/DeviceTests/Elements/DatePicker/DatePickerTests.cs new file mode 100644 index 000000000000..0a3e865488f9 --- /dev/null +++ b/src/Controls/tests/DeviceTests/Elements/DatePicker/DatePickerTests.cs @@ -0,0 +1,20 @@ +using Microsoft.Maui.Controls; +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Hosting; + +namespace Microsoft.Maui.DeviceTests; + +[Category(TestCategory.DatePicker)] +public partial class DatePickerTests : ControlsHandlerTestBase +{ + void SetupBuilder() + { + EnsureHandlerCreated(builder => + { + builder.ConfigureMauiHandlers(handlers => + { + handlers.AddHandler(); + }); + }); + } +} diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Android.cs index b4fe33fd2a21..00b457c78179 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Android.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.ComponentModel; +using System.Threading.Tasks; using AndroidX.AppCompat.Widget; using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; @@ -49,6 +50,15 @@ Task GetPlatformOpacity(EditorHandler editorHandler) }); } + Task GetPlatformIsVisible(EditorHandler editorHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(editorHandler); + return nativeView.Visibility == Android.Views.ViewStates.Visible; + }); + } + [Fact] public async Task CursorPositionPreservedWhenTextTransformPresent() { @@ -66,5 +76,79 @@ public async Task CursorPositionPreservedWhenTextTransformPresent() Assert.Equal(2, editor.CursorPosition); } + + [Fact] + [Description("The ScaleX property of a Editor should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var editor = new Editor() { ScaleX = 0.45f }; + var expected = editor.ScaleX; + var handler = await CreateHandlerAsync(editor); + var PlatformEditor = GetPlatformControl(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => PlatformEditor.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a Editor should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var editor = new Editor() { ScaleY = 1.23f }; + var expected = editor.ScaleY; + var handler = await CreateHandlerAsync(editor); + var PlatformEditor = GetPlatformControl(handler); + var platformScaleY = await InvokeOnMainThreadAsync(() => PlatformEditor.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a Editor should match with native Scale")] + public async Task ScaleConsistent() + { + var editor = new Editor() { Scale = 2.0f }; + var expected = editor.Scale; + var handler = await CreateHandlerAsync(editor); + var PlatformEditor = GetPlatformControl(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => PlatformEditor.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => PlatformEditor.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a Editor should match with native RotationX")] + public async Task RotationXConsistent() + { + var editor = new Editor() { RotationX = 33.0 }; + var expected = editor.RotationX; + var handler = await CreateHandlerAsync(editor); + var PlatformEditor = GetPlatformControl(handler); + var platformRotationX = await InvokeOnMainThreadAsync(() => PlatformEditor.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a Editor should match with native RotationY")] + public async Task RotationYConsistent() + { + var editor = new Editor() { RotationY = 87.0 }; + var expected = editor.RotationY; + var handler = await CreateHandlerAsync(editor); + var PlatformEditor = GetPlatformControl(handler); + var platformRotationY = await InvokeOnMainThreadAsync(() => PlatformEditor.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a Editor should match with native Rotation")] + public async Task RotationConsistent() + { + var editor = new Editor() { Rotation = 23.0 }; + var expected = editor.Rotation; + var handler = await CreateHandlerAsync(editor); + var PlatformEditor = GetPlatformControl(handler); + var platformRotation = await InvokeOnMainThreadAsync(() => PlatformEditor.Rotation); + Assert.Equal(expected, platformRotation); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Windows.cs index 13608fe74373..e9bb56dc2acf 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Windows.cs @@ -32,5 +32,14 @@ static int GetPlatformCursorPosition(EditorHandler editorHandler) => static int GetPlatformSelectionLength(EditorHandler editorHandler) => GetPlatformControl(editorHandler).SelectionLength; + + Task GetPlatformIsVisible(EditorHandler editorHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(editorHandler); + return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs index 8860d5b8f41b..c5e91d40eef7 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs @@ -309,6 +309,22 @@ await InvokeOnMainThreadAsync(async () => }); } + [Fact] + [Description("The IsVisible property of a Editor should match with native IsVisible")] + public async Task VerifyEditorIsVisibleProperty() + { + var editor = new Editor(); + editor.IsVisible = false; + var expectedValue = editor.IsVisible; + + var handler = await CreateHandlerAsync(editor); + await InvokeOnMainThreadAsync(async () => + { + var isVisible = await GetPlatformIsVisible(handler); + Assert.Equal(expectedValue, isVisible); + }); + } + [Category(TestCategory.Editor)] [Category(TestCategory.TextInput)] [Collection(RunInNewWindowCollection)] diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.iOS.cs index 46de2e3bcbb9..6be8fb8fb7b4 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.iOS.cs @@ -52,6 +52,15 @@ Task GetPlatformOpacity(EditorHandler editorHandler) }); } + Task GetPlatformIsVisible(EditorHandler editorHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(editorHandler); + return !nativeView.Hidden; + }); + } + [Category(TestCategory.Editor)] public class PlaceholderTests : ControlsHandlerTestBase { diff --git a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Android.cs index bac4eaa334fe..608196d9535e 100644 --- a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Android.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.ComponentModel; +using System.Threading.Tasks; using AndroidX.AppCompat.Widget; using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; @@ -48,6 +49,15 @@ Task GetPlatformOpacity(EntryHandler entryHandler) }); } + Task GetPlatformIsVisible(EntryHandler entryHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(entryHandler); + return nativeView.Visibility == Android.Views.ViewStates.Visible; + }); + } + [Fact] public async Task CursorPositionPreservedWhenTextTransformPresent() { @@ -80,5 +90,79 @@ public async Task UpdateTextWithTextLongerThanMaxLength() Assert.Equal(longText[..4], entry.Text); } + + [Fact] + [Description("The ScaleX property of a Entry should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var entry = new Entry() { ScaleX = 0.45f }; + var expected = entry.ScaleX; + var handler = await CreateHandlerAsync(entry); + var platformEntry = GetPlatformControl(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformEntry.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a Entry should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var entry = new Entry() { ScaleY = 1.23f }; + var expected = entry.ScaleY; + var handler = await CreateHandlerAsync(entry); + var platformEntry = GetPlatformControl(handler); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformEntry.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a Entry should match with native Scale")] + public async Task ScaleConsistent() + { + var entry = new Entry() { Scale = 2.0f }; + var expected = entry.Scale; + var handler = await CreateHandlerAsync(entry); + var platformEntry = GetPlatformControl(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformEntry.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformEntry.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a Entry should match with native RotationX")] + public async Task RotationXConsistent() + { + var entry = new Entry() { RotationX = 33.0 }; + var expected = entry.RotationX; + var handler = await CreateHandlerAsync(entry); + var platformEntry = GetPlatformControl(handler); + var platformRotationX = await InvokeOnMainThreadAsync(() => platformEntry.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a Entry should match with native RotationY")] + public async Task RotationYConsistent() + { + var entry = new Entry() { RotationY = 87.0 }; + var expected = entry.RotationY; + var handler = await CreateHandlerAsync(entry); + var platformEntry = GetPlatformControl(handler); + var platformRotationY = await InvokeOnMainThreadAsync(() => platformEntry.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a Entry should match with native Rotation")] + public async Task RotationConsistent() + { + var editor = new Entry() { Rotation = 23.0 }; + var expected = editor.Rotation; + var handler = await CreateHandlerAsync(editor); + var platformEntry = GetPlatformControl(handler); + var platformRotation = await InvokeOnMainThreadAsync(() => platformEntry.Rotation); + Assert.Equal(expected, platformRotation); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Windows.cs index 6beb820d3d19..f76b6526f23b 100644 --- a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Windows.cs @@ -32,5 +32,14 @@ static int GetPlatformCursorPosition(EntryHandler entryHandler) => static int GetPlatformSelectionLength(EntryHandler entryHandler) => GetPlatformControl(entryHandler).SelectionLength; + + Task GetPlatformIsVisible(EntryHandler entryHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(entryHandler); + return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs index 8e62d81c37ed..33557b1e1993 100644 --- a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs @@ -222,6 +222,22 @@ await InvokeOnMainThreadAsync(async () => }); } + [Fact] + [Description("The IsVisible property of a Entry should match with native IsVisible")] + public async Task VerifyEntryIsVisibleProperty() + { + var entry = new Entry(); + entry.IsVisible = false; + var expectedValue = entry.IsVisible; + + var handler = await CreateHandlerAsync(entry); + await InvokeOnMainThreadAsync(async () => + { + var isVisible = await GetPlatformIsVisible(handler); + Assert.Equal(expectedValue, isVisible); + }); + } + [Category(TestCategory.Entry)] [Category(TestCategory.TextInput)] [Collection(RunInNewWindowCollection)] diff --git a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs index a4ce142a1145..4a2e9e48c957 100644 --- a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs @@ -54,6 +54,15 @@ Task GetPlatformOpacity(EntryHandler entryHandler) }); } + Task GetPlatformIsVisible(EntryHandler entryHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(entryHandler); + return !nativeView.Hidden; + }); + } + [Collection(ControlsHandlerTestBase.RunInNewWindowCollection)] public class ScrollTests : ControlsHandlerTestBase { diff --git a/src/Controls/tests/DeviceTests/Elements/Image/ImageTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Image/ImageTests.Android.cs index 2a5f40c37ba2..9a40fd8cb9c0 100644 --- a/src/Controls/tests/DeviceTests/Elements/Image/ImageTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Image/ImageTests.Android.cs @@ -99,6 +99,74 @@ Task GetPlatformOpacity(ImageHandler imageHandler) return (float)nativeView.Alpha; }); } + + [Fact] + [Description("The ScaleX property of a Image should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var image = new Image() { ScaleX = 0.45f }; + var expected = image.ScaleX; + var handler = await CreateHandlerAsync(image); + var platformScaleX = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a Image should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var image = new Image() { ScaleY = 1.23f }; + var expected = image.ScaleY; + var handler = await CreateHandlerAsync(image); + var platformScaleY = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a Image should match with native Scale")] + public async Task ScaleConsistent() + { + var image = new Image() { Scale = 2.0f }; + var expected = image.Scale; + var handler = await CreateHandlerAsync(image); + var platformScaleX = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a Image should match with native RotationX")] + public async Task RotationXConsistent() + { + var image = new Image() { RotationX = 33.0 }; + var expected = image.RotationX; + var handler = await CreateHandlerAsync(image); + var platformRotationX = await InvokeOnMainThreadAsync(() => handler.PlatformView.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a Image should match with native RotationY")] + public async Task RotationYConsistent() + { + var image = new Image() { RotationY = 87.0 }; + var expected = image.RotationY; + var handler = await CreateHandlerAsync(image); + var platformRotationY = await InvokeOnMainThreadAsync(() => handler.PlatformView.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a Image should match with native Rotation")] + public async Task RotationConsistent() + { + var image = new Image() { Rotation = 23.0 }; + var expected = image.Rotation; + var handler = await CreateHandlerAsync(image); + var platformRotation = await InvokeOnMainThreadAsync(() => handler.PlatformView.Rotation); + Assert.Equal(expected, platformRotation); + } } // This subclass of memory stream is deliberately set up to trick Glide into using the cached image diff --git a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Android.cs index d750c7e21143..03e1aadb80aa 100644 --- a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Android.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -88,6 +89,79 @@ await InvokeOnMainThreadAsync((System.Action)(() => })); } + [Fact] + [Description("The ScaleX property of a Label should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var label = new Label() { ScaleX = 0.45f }; + var expected = label.ScaleX; + var handler = await CreateHandlerAsync(label); + var platformLabel = GetPlatformLabel(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformLabel.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a Label should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var label = new Label() { ScaleY = 1.23f }; + var expected = label.ScaleY; + var handler = await CreateHandlerAsync(label); + var platformLabel = GetPlatformLabel(handler); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformLabel.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a Label should match with native Scale")] + public async Task ScaleConsistent() + { + var label = new Label() { Scale = 2.0f }; + var expected = label.Scale; + var handler = await CreateHandlerAsync(label); + var platformLabel = GetPlatformLabel(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformLabel.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformLabel.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a Label should match with native RotationX")] + public async Task RotationXConsistent() + { + var label = new Label() { RotationX = 33.0 }; + var expected = label.RotationX; + var handler = await CreateHandlerAsync(label); + var platformLabel = GetPlatformLabel(handler); + var platformRotationX = await InvokeOnMainThreadAsync(() => platformLabel.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a Label should match with native RotationY")] + public async Task RotationYConsistent() + { + var label = new Label() { RotationY = 87.0 }; + var expected = label.RotationY; + var handler = await CreateHandlerAsync(label); + var platformLabel = GetPlatformLabel(handler); + var platformRotationY = await InvokeOnMainThreadAsync(() => platformLabel.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a Label should match with native Rotation")] + public async Task RotationConsistent() + { + var label = new Label() { Rotation = 23.0 }; + var expected = label.Rotation; + var handler = await CreateHandlerAsync(label); + var platformLabel = GetPlatformLabel(handler); + var platformRotation = await InvokeOnMainThreadAsync(() => platformLabel.Rotation); + Assert.Equal(expected, platformRotation); + } TextView GetPlatformLabel(LabelHandler labelHandler) => labelHandler.PlatformView; @@ -105,5 +179,14 @@ Task GetPlatformOpacity(LabelHandler labelHandler) return (float)nativeView.Alpha; }); } + + Task GetPlatformIsVisible(LabelHandler labelHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformLabel(labelHandler); + return nativeView.Visibility == Android.Views.ViewStates.Visible; + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Windows.cs index cc68b57e8a74..ca2ea5d7b8b5 100644 --- a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Windows.cs @@ -29,5 +29,14 @@ Task GetPlatformOpacity(LabelHandler labelHandler) return (float)nativeView.Opacity; }); } + + Task GetPlatformIsVisible(LabelHandler labelHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformLabel(labelHandler); + return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.cs b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.cs index 1d8f7e9345e2..d779e6798e29 100644 --- a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.cs @@ -11,6 +11,7 @@ using Microsoft.Maui.Hosting; using Microsoft.Maui.Platform; using Xunit; +using System.Diagnostics; namespace Microsoft.Maui.DeviceTests { @@ -755,7 +756,6 @@ await InvokeOnMainThreadAsync(async () => Assert.Equal(expectedValue, nativeOpacityValue); }); } - Color TextColor(LabelHandler handler) { #if __IOS__ @@ -780,7 +780,7 @@ static void AssertEquivalentFont(LabelHandler handler, Font font) Assert.Equal(targetFontSize, platformTypeface.PointSize); #elif __ANDROID__ var targetTypeface = fontManager.GetTypeface(font); - var targetFontSize = handler.MauiContext.Context.ToPixels(fontManager.GetFontSize(font).Value); + var targetFontSize = handler.MauiContext.Context.ToPixels(fontManager.GetFontSize(font).Value); var platformTypeface = handler.PlatformView.Typeface; var platformFontSize = handler.PlatformView.TextSize; @@ -796,5 +796,21 @@ static void AssertEquivalentFont(LabelHandler handler, Font font) Assert.Equal(targetFontWeight, platformFontWeight); #endif } + + [Fact] + [Description("The IsVisible property of a Label should match with native IsVisible")] + public async Task VerifyLabelIsVisibleProperty() + { + var label = new Label(); + label.IsVisible = false; + var expectedValue = label.IsVisible; + + var handler = await CreateHandlerAsync(label); + await InvokeOnMainThreadAsync(async () => + { + var isVisible = await GetPlatformIsVisible(handler); + Assert.Equal(expectedValue, isVisible); + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.iOS.cs index 733be10164a3..650f2c638dfd 100644 --- a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.iOS.cs @@ -29,7 +29,7 @@ await InvokeOnMainThreadAsync((System.Action)(() => Assert.Equal(LineBreakMode.TailTruncation.ToPlatform(), GetPlatformLineBreakMode(handler)); })); } - + UILabel GetPlatformLabel(LabelHandler labelHandler) => (UILabel)labelHandler.PlatformView; @@ -46,6 +46,15 @@ double GetPlatformCharacterSpacing(LabelHandler labelHandler) return characterSpacing; } + Task GetPlatformIsVisible(LabelHandler labelHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformLabel(labelHandler); + return !nativeView.Hidden; + }); + } + double GetPlatformLineHeight(LabelHandler labelHandler) { var attributedText = GetPlatformLabel(labelHandler).AttributedText; diff --git a/src/Controls/tests/DeviceTests/Elements/Picker/PickerTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Picker/PickerTests.Android.cs index 7633630ad563..50ac8aa24fc7 100644 --- a/src/Controls/tests/DeviceTests/Elements/Picker/PickerTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Picker/PickerTests.Android.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; using Android.Widget; using Microsoft.Maui.Controls; @@ -10,7 +11,6 @@ namespace Microsoft.Maui.DeviceTests { - [Category(TestCategory.Picker)] public partial class PickerTests : ControlsHandlerTestBase { protected Task GetPlatformControlText(MauiPicker platformView) @@ -29,5 +29,73 @@ Task GetPlatformOpacity(PickerHandler pickerHandler) return (float)nativeView.Alpha; }); } + + [Fact] + [Description("The ScaleX property of a Picker should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var picker = new Picker() { ScaleX = 0.45f }; + var expected = picker.ScaleX; + var handler = await CreateHandlerAsync(picker); + var platformScaleX = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a Picker should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var picker = new Picker() { ScaleY = 1.23f }; + var expected = picker.ScaleY; + var handler = await CreateHandlerAsync(picker); + var platformScaleY = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a Picker should match with native Scale")] + public async Task ScaleConsistent() + { + var picker = new Picker() { Scale = 2.0f }; + var expected = picker.Scale; + var handler = await CreateHandlerAsync(picker); + var platformScaleX = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a Picker should match with native RotationX")] + public async Task RotationXConsistent() + { + var picker = new Picker() { RotationX = 33.0 }; + var expected = picker.RotationX; + var handler = await CreateHandlerAsync(picker); + var platformRotationX = await InvokeOnMainThreadAsync(() => handler.PlatformView.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a Picker should match with native RotationY")] + public async Task RotationYConsistent() + { + var picker = new Picker() { RotationY = 87.0 }; + var expected = picker.RotationY; + var handler = await CreateHandlerAsync(picker); + var platformRotationY = await InvokeOnMainThreadAsync(() => handler.PlatformView.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a Picker should match with native Rotation")] + public async Task RotationConsistent() + { + var picker = new Picker() { Rotation = 23.0 }; + var expected = picker.Rotation; + var handler = await CreateHandlerAsync(picker); + var platformRotation = await InvokeOnMainThreadAsync(() => handler.PlatformView.Rotation); + Assert.Equal(expected, platformRotation); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs index 3cc783bb4286..30853568b48e 100644 --- a/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs @@ -1,8 +1,8 @@ -using Microsoft.Maui.Handlers; -using Xunit; using System.ComponentModel; using System.Threading.Tasks; using Microsoft.Maui.Controls; +using Microsoft.Maui.Handlers; +using Xunit; namespace Microsoft.Maui.DeviceTests { @@ -32,5 +32,22 @@ await InvokeOnMainThreadAsync(() => Assert.Equal(expectedValue, nativeOpacityValue); }); } + + [Fact] + [Description("The IsVisible property of a RadioButton should match with native IsVisible")] + public async Task VerifyRadioButtonIsVisibleProperty() + { + var radioButton = new RadioButton(); + radioButton.IsVisible = false; + var expectedValue = radioButton.IsVisible; + + var handler = await CreateHandlerAsync(radioButton); + var nativeView = GetNativeRadioButton(handler); + await InvokeOnMainThreadAsync(() => + { + var isVisible = nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + Assert.Equal(expectedValue, isVisible); + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Android.cs index 63fe3c327277..335b55cebb19 100644 --- a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Android.cs @@ -1,8 +1,11 @@ -using System.Linq; +using System.ComponentModel; +using System.Linq; using System.Threading.Tasks; using Android.Widget; +using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; +using Xunit; using SearchView = AndroidX.AppCompat.Widget.SearchView; namespace Microsoft.Maui.DeviceTests @@ -39,5 +42,88 @@ Task GetPlatformOpacity(SearchBarHandler searchBarHandler) return nativeView.Alpha; }); } + + [Fact] + [Description("The ScaleX property of a SearchBar should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var searchBar = new SearchBar() { ScaleX = 0.45f }; + var expected = searchBar.ScaleX; + var handler = await CreateHandlerAsync(searchBar); + var platformSearchBar = GetPlatformControl(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformSearchBar.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a SearchBar should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var searchBar = new SearchBar() { ScaleY = 1.23f }; + var expected = searchBar.ScaleY; + var handler = await CreateHandlerAsync(searchBar); + var platformSearchBar = GetPlatformControl(handler); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformSearchBar.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a SearchBar should match with native Scale")] + public async Task ScaleConsistent() + { + var searchBar = new SearchBar() { Scale = 2.0f }; + var expected = searchBar.Scale; + var handler = await CreateHandlerAsync(searchBar); + var platformSearchBar = GetPlatformControl(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformSearchBar.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformSearchBar.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a SearchBar should match with native RotationX")] + public async Task RotationXConsistent() + { + var searchBar = new SearchBar() { RotationX = 33.0 }; + var expected = searchBar.RotationX; + var handler = await CreateHandlerAsync(searchBar); + var platformSearchBar = GetPlatformControl(handler); + var platformRotationX = await InvokeOnMainThreadAsync(() => platformSearchBar.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a SearchBar should match with native RotationY")] + public async Task RotationYConsistent() + { + var searchBar = new SearchBar() { RotationY = 87.0 }; + var expected = searchBar.RotationY; + var handler = await CreateHandlerAsync(searchBar); + var platformSearchBar = GetPlatformControl(handler); + var platformRotationY = await InvokeOnMainThreadAsync(() => platformSearchBar.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a SearchBar should match with native Rotation")] + public async Task RotationConsistent() + { + var searchBar = new SearchBar() { Rotation = 23.0 }; + var expected = searchBar.Rotation; + var handler = await CreateHandlerAsync(searchBar); + var platformSearchBar = GetPlatformControl(handler); + var platformRotation = await InvokeOnMainThreadAsync(() => platformSearchBar.Rotation); + Assert.Equal(expected, platformRotation); + } + + Task GetPlatformIsVisible(SearchBarHandler searchBarHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(searchBarHandler); + return nativeView.Visibility == Android.Views.ViewStates.Visible; + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Windows.cs index 7542558cf8bd..30a2ed3c6d6a 100644 --- a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Windows.cs @@ -52,5 +52,14 @@ Task GetPlatformOpacity(SearchBarHandler searchBarHandler) return (float)nativeView.Opacity; }); } + + Task GetPlatformIsVisible(SearchBarHandler searchBarHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(searchBarHandler); + return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.cs b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.cs index 35f0e55182e6..13d257c70fd0 100644 --- a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.cs @@ -95,6 +95,24 @@ await InvokeOnMainThreadAsync(async () => }); } + [Fact] + [Description("The IsVisible property of a SearchBar should match with native IsVisible")] + public async Task VerifySearchBarIsVisibleProperty() + { + var searchBar = new SearchBar + { + IsVisible = false + }; + var expectedValue = searchBar.IsVisible; + + var handler = await CreateHandlerAsync(searchBar); + await InvokeOnMainThreadAsync(async () => + { + var isVisible = await GetPlatformIsVisible(handler); + Assert.Equal(expectedValue, isVisible); + }); + } + #if false // TODO: The search bar controls are composite controls and need to be attached to the UI to run [Category(TestCategory.SearchBar)] diff --git a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.iOS.cs index 3aa0a46345b6..d569c60067dd 100644 --- a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.iOS.cs @@ -44,5 +44,13 @@ Task GetPlatformOpacity(SearchBarHandler searchBarHandler) }); } + Task GetPlatformIsVisible(SearchBarHandler searchBarHandler) + { + return InvokeOnMainThreadAsync(() => + { + var nativeView = GetPlatformControl(searchBarHandler); + return !nativeView.Hidden; + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs index a97d45bfe8dd..ddc7fc64d2ef 100644 --- a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs @@ -454,6 +454,68 @@ await CreateHandlerAndAddToWindow(shell, async (handler) => }); } + //src/Compatibility/Core/tests/Android/ShellTests.cs + [Fact(DisplayName = "Flyout Header Changes When Updated")] + public async Task FlyoutHeaderReactsToChanges() + { + SetupBuilder(); + + var initialHeader = new Label() { Text = "Hello" }; + var newHeader = new Label() { Text = "Hello Part 2" }; + + var shell = await CreateShellAsync(shell => + { + shell.CurrentItem = new FlyoutItem() { Items = { new ContentPage() }, Title = "Flyout Item" }; + shell.FlyoutHeader = initialHeader; + }); + + await CreateHandlerAndAddToWindow(shell, async (handler) => + { + var initialHeaderPlatformView = initialHeader.ToPlatform(); + Assert.NotNull(initialHeaderPlatformView); + Assert.NotNull(initialHeader.Handler); + + shell.FlyoutHeader = newHeader; + + var newHeaderPlatformView = newHeader.ToPlatform(); + Assert.NotNull(newHeaderPlatformView); + Assert.NotNull(newHeader.Handler); + + Assert.Null(initialHeader.Handler); + + await OpenFlyout(handler); + + var appBar = newHeaderPlatformView.GetParentOfType(); + Assert.NotNull(appBar); + }); + } + + //src/Compatibility/Core/tests/Android/ShellTests.cs + [Fact(DisplayName = "Ensure Default Colors are White for BottomNavigationView")] + public async Task ShellTabColorsDefaultToWhite() + { + SetupBuilder(); + + var shell = await CreateShellAsync(shell => + { + shell.Items.Add(new Tab() { Items = { new ContentPage() }, Title = "Tab 1" }); + }); + + await CreateHandlerAndAddToWindow(shell, (handler) => + { + var bottomNavigationView = GetDrawerLayout(handler).GetFirstChildOfType(); + Assert.NotNull(bottomNavigationView); + + var background = bottomNavigationView.Background; + Assert.NotNull(background); + + if (background is ColorChangeRevealDrawable changeRevealDrawable) + { + Assert.Equal(Android.Graphics.Color.White, changeRevealDrawable.EndColor); + } + }); + } + protected AView GetFlyoutPlatformView(ShellRenderer shellRenderer) { var drawerLayout = GetDrawerLayout(shellRenderer); diff --git a/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.Android.cs index de8afa21e8ec..37ed82a30149 100644 --- a/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.Android.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.ComponentModel; +using System.Threading.Tasks; using Android.Views; using Microsoft.Maui.Controls; using Microsoft.Maui.Graphics; @@ -6,11 +7,9 @@ using Microsoft.Maui.Platform; using Xunit; using static Microsoft.Maui.DeviceTests.AssertHelpers; -using System.ComponentModel; namespace Microsoft.Maui.DeviceTests { - [Category(TestCategory.SwipeView)] public partial class SwipeViewTests : ControlsHandlerTestBase { [Fact(DisplayName = "SwipeItem Size Initializes Correctly")] @@ -71,6 +70,79 @@ await AttachAndRun(swipeView, async (handler) => }); } + [Fact] + [Description("The ScaleX property of a SwipeView should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var swipeView = new SwipeView() { ScaleX = 0.45f }; + var expected = swipeView.ScaleX; + var handler = await CreateHandlerAsync(swipeView); + var platformSwipeView = GetPlatformControl(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformSwipeView.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a SwipeView should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var swipeView = new SwipeView() { ScaleY = 1.23f }; + var expected = swipeView.ScaleY; + var handler = await CreateHandlerAsync(swipeView); + var platformSwipeView = GetPlatformControl(handler); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformSwipeView.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a SwipeView should match with native Scale")] + public async Task ScaleConsistent() + { + var swipeView = new SwipeView() { Scale = 2.0f }; + var expected = swipeView.Scale; + var handler = await CreateHandlerAsync(swipeView); + var platformSwipeView = GetPlatformControl(handler); + var platformScaleX = await InvokeOnMainThreadAsync(() => platformSwipeView.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => platformSwipeView.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a SwipeView should match with native RotationX")] + public async Task RotationXConsistent() + { + var swipeView = new SwipeView() { RotationX = 33.0 }; + var expected = swipeView.RotationX; + var handler = await CreateHandlerAsync(swipeView); + var platformSwipeView = GetPlatformControl(handler); + var platformRotationX = await InvokeOnMainThreadAsync(() => platformSwipeView.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a SwipeView should match with native RotationY")] + public async Task RotationYConsistent() + { + var swipeView = new SwipeView() { RotationY = 87.0 }; + var expected = swipeView.RotationY; + var handler = await CreateHandlerAsync(swipeView); + var platformSwipeView = GetPlatformControl(handler); + var platformRotationY = await InvokeOnMainThreadAsync(() => platformSwipeView.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a SwipeView should match with native Rotation")] + public async Task RotationConsistent() + { + var swipeView = new SwipeView() { Rotation = 23.0 }; + var expected = swipeView.Rotation; + var handler = await CreateHandlerAsync(swipeView); + var platformSwipeView = GetPlatformControl(handler); + var platformRotation = await InvokeOnMainThreadAsync(() => platformSwipeView.Rotation); + Assert.Equal(expected, platformRotation); + } MauiSwipeView GetPlatformControl(SwipeViewHandler handler) => handler.PlatformView; @@ -98,5 +170,24 @@ await InvokeOnMainThreadAsync(() => Assert.Equal(expectedValue, nativeOpacityValue); }); } + + [Fact] + [Description("The IsVisible property of a SwipeView should match with native IsVisible")] + public async Task VerifySwipeViewIsVisibleProperty() + { + var swipeView = new SwipeView + { + IsVisible = false + }; + var expectedValue = swipeView.IsVisible; + + var handler = await CreateHandlerAsync(swipeView); + var nativeView = GetPlatformControl(handler); + await InvokeOnMainThreadAsync(() => + { + var isVisible = nativeView.Visibility == Android.Views.ViewStates.Visible; + Assert.Equal(expectedValue, isVisible); + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.iOS.cs index e763f6a70312..80b90b1cb47d 100644 --- a/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.iOS.cs @@ -39,6 +39,25 @@ await InvokeOnMainThreadAsync(() => Assert.Equal(expectedValue, nativeOpacityValue); }); } + + [Fact] + [Description("The IsVisible property of a SwipeView should match with native IsVisible")] + public async Task VerifySwipeViewIsVisibleProperty() + { + var swipeView = new SwipeView + { + IsVisible = false + }; + var expectedValue = swipeView.IsVisible; + + var handler = await CreateHandlerAsync(swipeView); + var nativeView = GetPlatformControl(handler); + await InvokeOnMainThreadAsync(() => + { + var isVisible = !nativeView.Hidden; + Assert.Equal(expectedValue, isVisible); + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs index 59512ce7d243..5261a76554b9 100644 --- a/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -22,7 +23,6 @@ namespace Microsoft.Maui.DeviceTests { - [Category(TestCategory.TabbedPage)] public partial class TabbedPageTests : ControlsHandlerTestBase { [Fact(DisplayName = "Using SelectedTab Color doesnt crash")] @@ -142,6 +142,73 @@ await CreateHandlerAndAddToWindow(new Window(tabbedPage), asy }); } + [Fact] + [Description("The ScaleX property of a TabbedPage should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var tabbedPage = new TabbedPage() { ScaleX = 0.45f }; + var expected = tabbedPage.ScaleX; + var handler = await CreateHandlerAsync(tabbedPage); + var platformScaleX = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a TabbedPage should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var tabbedPage = new TabbedPage() { ScaleY = 1.23f }; + var expected = tabbedPage.ScaleY; + var handler = await CreateHandlerAsync(tabbedPage); + var platformScaleY = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a TabbedPage should match with native Scale")] + public async Task ScaleConsistent() + { + var tabbedPage = new TabbedPage() { Scale = 2.0f }; + var expected = tabbedPage.Scale; + var handler = await CreateHandlerAsync(tabbedPage); + var platformScaleX = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a TabbedPage should match with native RotationX")] + public async Task RotationXConsistent() + { + var tabbedPage = new TabbedPage() { RotationX = 33.0 }; + var expected = tabbedPage.RotationX; + var handler = await CreateHandlerAsync(tabbedPage); + var platformRotationX = await InvokeOnMainThreadAsync(() => handler.PlatformView.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a TabbedPage should match with native RotationY")] + public async Task RotationYConsistent() + { + var tabbedPage = new TabbedPage() { RotationY = 87.0 }; + var expected = tabbedPage.RotationY; + var handler = await CreateHandlerAsync(tabbedPage); + var platformRotationY = await InvokeOnMainThreadAsync(() => handler.PlatformView.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a TabbedPage should match with native Rotation")] + public async Task RotationConsistent() + { + var tabbedPage = new TabbedPage() { Rotation = 23.0 }; + var expected = tabbedPage.Rotation; + var handler = await CreateHandlerAsync(tabbedPage); + var platformRotation = await InvokeOnMainThreadAsync(() => handler.PlatformView.Rotation); + Assert.Equal(expected, platformRotation); + } BottomNavigationView GetBottomNavigationView(IPlatformViewHandler tabViewHandler) { var layout = tabViewHandler.PlatformView.FindParent((view) => view is CoordinatorLayout) diff --git a/src/Controls/tests/DeviceTests/Elements/TemplatedView/TemplatedViewTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/TemplatedView/TemplatedViewTests.Android.cs index 5271b304853b..3633ae606278 100644 --- a/src/Controls/tests/DeviceTests/Elements/TemplatedView/TemplatedViewTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/TemplatedView/TemplatedViewTests.Android.cs @@ -1,15 +1,87 @@ -using Android.Views; +using System.ComponentModel; +using System.Threading.Tasks; +using Android.Views; +using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; +using Xunit; namespace Microsoft.Maui.DeviceTests { - public partial class TemplatedViewTests - { - static int GetChildCount(ContentViewHandler contentViewHandler) => - contentViewHandler.PlatformView.ChildCount; - - static View GetChild(ContentViewHandler contentViewHandler, int index = 0) => - contentViewHandler.PlatformView.GetChildAt(index); - } + public partial class TemplatedViewTests + { + static int GetChildCount(ContentViewHandler contentViewHandler) => + contentViewHandler.PlatformView.ChildCount; + + static Android.Views.View GetChild(ContentViewHandler contentViewHandler, int index = 0) => + contentViewHandler.PlatformView.GetChildAt(index); + + [Fact] + [Description("The ScaleX property of a TemplatedView should match with native ScaleX")] + public async Task ScaleXConsistent() + { + var templatedView = new TemplatedView() { ScaleX = 0.45f }; + var expected = templatedView.ScaleX; + var handler = await CreateHandlerAsync(templatedView); + var platformScaleX = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleX); + Assert.Equal(expected, platformScaleX); + } + + [Fact] + [Description("The ScaleY property of a TemplatedView should match with native ScaleY")] + public async Task ScaleYConsistent() + { + var templatedView = new TemplatedView() { ScaleY = 1.23f }; + var expected = templatedView.ScaleY; + var handler = await CreateHandlerAsync(templatedView); + var platformScaleY = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleY); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The Scale property of a TemplatedView should match with native Scale")] + public async Task ScaleConsistent() + { + var templatedView = new TemplatedView() { Scale = 2.0f }; + var expected = templatedView.Scale; + var handler = await CreateHandlerAsync(templatedView); + var platformScaleX = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleX); + var platformScaleY = await InvokeOnMainThreadAsync(() => handler.PlatformView.ScaleY); + Assert.Equal(expected, platformScaleX); + Assert.Equal(expected, platformScaleY); + } + + [Fact] + [Description("The RotationX property of a TemplatedView should match with native RotationX")] + public async Task RotationXConsistent() + { + var templatedView = new TemplatedView() { RotationX = 33.0 }; + var expected = templatedView.RotationX; + var handler = await CreateHandlerAsync(templatedView); + var platformRotationX = await InvokeOnMainThreadAsync(() => handler.PlatformView.RotationX); + Assert.Equal(expected, platformRotationX); + } + + [Fact] + [Description("The RotationY property of a TemplatedView should match with native RotationY")] + public async Task RotationYConsistent() + { + var templatedView = new TemplatedView() { RotationY = 87.0 }; + var expected = templatedView.RotationY; + var handler = await CreateHandlerAsync(templatedView); + var platformRotationY = await InvokeOnMainThreadAsync(() => handler.PlatformView.RotationY); + Assert.Equal(expected, platformRotationY); + } + + [Fact] + [Description("The Rotation property of a TemplatedView should match with native Rotation")] + public async Task RotationConsistent() + { + var templatedView = new TemplatedView() { Rotation = 23.0 }; + var expected = templatedView.Rotation; + var handler = await CreateHandlerAsync(templatedView); + var platformRotation = await InvokeOnMainThreadAsync(() => handler.PlatformView.Rotation); + Assert.Equal(expected, platformRotation); + } + } } diff --git a/src/Controls/tests/DeviceTests/TestCategory.cs b/src/Controls/tests/DeviceTests/TestCategory.cs index 1c6c53d339dd..926c0bfa1cb3 100644 --- a/src/Controls/tests/DeviceTests/TestCategory.cs +++ b/src/Controls/tests/DeviceTests/TestCategory.cs @@ -13,6 +13,7 @@ public static class TestCategory public const string CollectionView = "CollectionView"; public const string Compatibility = "Compatibility"; public const string ContentView = "ContentView"; + public const string DatePicker = nameof(DatePicker); public const string Dispatcher = "Dispatcher"; public const string Editor = "Editor"; public const string Element = "Element"; diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/DynamicFontImageSourceColorShouldApplyOnTabIcon.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/DynamicFontImageSourceColorShouldApplyOnTabIcon.png new file mode 100644 index 000000000000..68adcadb0cb9 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/DynamicFontImageSourceColorShouldApplyOnTabIcon.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FontImageSourceColorShouldApplyOnTabIcon.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FontImageSourceColorShouldApplyOnTabIcon.png new file mode 100644 index 000000000000..df4993b6e3eb Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FontImageSourceColorShouldApplyOnTabIcon.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ImageButtonPaddingDoesNotChangeWhenIsVisibleChanges.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ImageButtonPaddingDoesNotChangeWhenIsVisibleChanges.png new file mode 100644 index 000000000000..632835b4fed6 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ImageButtonPaddingDoesNotChangeWhenIsVisibleChanges.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue6458Test.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue6458Test.png new file mode 100644 index 000000000000..4f7adc73f24b Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue6458Test.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ItemImageSourceShouldBeVisible.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ItemImageSourceShouldBeVisible.png new file mode 100644 index 000000000000..34fe4a3ec89b Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ItemImageSourceShouldBeVisible.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalCenterLayoutOptions.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalCenterLayoutOptions.png new file mode 100644 index 000000000000..c03a622dc216 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalCenterLayoutOptions.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalEndLayoutOptions.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalEndLayoutOptions.png new file mode 100644 index 000000000000..5fe6a68c551a Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalEndLayoutOptions.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalStartLayoutOptions.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalStartLayoutOptions.png new file mode 100644 index 000000000000..f47e1a7f855b Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelShouldSizeCorrectlyOnHorizontalStartLayoutOptions.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelWithPadding.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelWithPadding.png new file mode 100644 index 000000000000..486a8ecd19bd Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LabelWithPadding.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShadowShouldUpdateWhenClipping.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShadowShouldUpdateWhenClipping.png new file mode 100644 index 000000000000..a2963b9da6b9 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShadowShouldUpdateWhenClipping.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShouldIgnoreBottomContentInsetForCollectionViewItems.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShouldIgnoreBottomContentInsetForCollectionViewItems.png index b73bd37ef18e..6a1d97369aa0 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShouldIgnoreBottomContentInsetForCollectionViewItems.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShouldIgnoreBottomContentInsetForCollectionViewItems.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/TabbedPageBackButtonUpdated.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/TabbedPageBackButtonUpdated.png new file mode 100644 index 000000000000..8bf3ada1fd5a Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/TabbedPageBackButtonUpdated.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png index 2d247f247968..4be947230ad1 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issue28212.cs b/src/Controls/tests/TestCases.HostApp/Issue28212.cs new file mode 100644 index 000000000000..b8817db2bc8a --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issue28212.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 28212, "Using CollectionView.EmptyView results in an Exception on Windows", PlatformAffected.WinPhone)] +public class Issue28212 : NavigationPage +{ + Issue28212_Page1 _issue28212_Page1; + public Issue28212() + { + _issue28212_Page1 = new Issue28212_Page1(); + this.PushAsync(_issue28212_Page1); + } +} + +public class Issue28212_Page1 : ContentPage +{ + VerticalStackLayout verticalStackLayout; + Issue28212_Page2 _issue28212_Page2; + Button button; + public Issue28212_Page1() + { + _issue28212_Page2 = new Issue28212_Page2(); + button = new Button(); + button.Text = "Click to Navigate"; + button.AutomationId = "Button"; + button.Clicked += Button_Clicked; + button.HeightRequest = 100; + verticalStackLayout = new VerticalStackLayout(); + verticalStackLayout.Children.Add(button); + this.Content = verticalStackLayout; + } + + private void Button_Clicked(object sender, EventArgs e) + { + Navigation.PushAsync(_issue28212_Page2); + } +} + +public class Issue28212_Page2 : ContentPage +{ + public ObservableCollection Items { get; } = []; + VerticalStackLayout verticalStackLayout; + Button backButton; + Button addButton; + CollectionView _collectionView; + Label emptyLabel; + public Issue28212_Page2() + { + _collectionView = new CollectionView(); + _collectionView.ItemsSource = Items; + emptyLabel = new Label(); + emptyLabel.Text = "Empty"; + _collectionView.EmptyView = emptyLabel; + addButton = new Button(); + addButton.Text = "Add"; + addButton.Clicked += Button_Clicked; + backButton = new Button(); + backButton.Text = "Back"; + backButton.Clicked += BackButton_Clicked; + backButton.AutomationId = "BackButton"; + verticalStackLayout = new VerticalStackLayout(); + verticalStackLayout.Children.Add(addButton); + verticalStackLayout.Children.Add(backButton); + verticalStackLayout.Children.Add(_collectionView); + this.Content = verticalStackLayout; + } + + private void BackButton_Clicked(object sender, EventArgs e) + { + Navigation.PopAsync(); + } + + private void Button_Clicked(object sender, EventArgs e) + { + Items.Add("Item " + (Items.Count + 1)); + } +} diff --git a/src/Controls/tests/TestCases.HostApp/Issues/CarouselViewLoopNoFreeze.cs b/src/Controls/tests/TestCases.HostApp/Issues/CarouselViewLoopNoFreeze.cs index e135c6a92a6f..a1e18bd937d9 100644 --- a/src/Controls/tests/TestCases.HostApp/Issues/CarouselViewLoopNoFreeze.cs +++ b/src/Controls/tests/TestCases.HostApp/Issues/CarouselViewLoopNoFreeze.cs @@ -57,6 +57,7 @@ public CarouselViewLoopNoFreeze() }; _carouselView.SetBinding(CarouselView.ItemsSourceProperty, "Items"); this.SetBinding(Page.TitleProperty, "Title"); + _carouselView.ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepScrollOffset; var layout = new Grid(); layout.RowDefinitions.Add(new RowDefinition { Height = 100 }); diff --git a/src/Controls/tests/TestCases.HostApp/Issues/CarouselViewRemoveAt.cs b/src/Controls/tests/TestCases.HostApp/Issues/CarouselViewRemoveAt.cs index a8ab3a49adf8..28ed14136812 100644 --- a/src/Controls/tests/TestCases.HostApp/Issues/CarouselViewRemoveAt.cs +++ b/src/Controls/tests/TestCases.HostApp/Issues/CarouselViewRemoveAt.cs @@ -65,6 +65,7 @@ public CarouselViewRemoveAt() }; _carousel.CurrentItemChanged += Carousel_CurrentItemChanged; _carousel.PositionChanged += Carousel_PositionChanged; + _carousel.ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepScrollOffset; Grid.SetColumnSpan(_carousel, 2); diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue14689.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue14689.cs new file mode 100644 index 000000000000..c88301b22812 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue14689.cs @@ -0,0 +1,23 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 14689, "TabbedPage Back button not updated", PlatformAffected.UWP, NavigationBehavior.SetApplicationRoot)] +public class Issue14689 : TabbedPage +{ + public Issue14689() + { + Children.Add(new ContentPage() { Title = "NoNavigationPage" }); + Children.Add(new NavigationPage(new Issue14689Page()) { Title = "HasNavigationPage" }); + Children.Add(new NavigationPage(new Issue14689Page()) { Title = "HasNavigationPage1" }); + } +} + +public class Issue14689Page : ContentPage +{ + public Issue14689Page() + { + var button = new Button() { Text = "Push a page", VerticalOptions = LayoutOptions.Start, AutomationId = "button" }; + button.Clicked += (s, e) => { this.Navigation.PushAsync(new Issue14689Page()); }; + Content = button; + } +} + diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue18806.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue18806.xaml new file mode 100644 index 000000000000..4644e8580946 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue18806.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +