├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── merge-dependabot.yml │ └── on-push-do-docs.yml ├── .gitignore ├── code_of_conduct.md ├── license.txt ├── readme.md └── src ├── .editorconfig ├── .gitattributes ├── Directory.Build.props ├── Directory.Packages.props ├── FluentAvaloniaTests ├── CalculatorTests.Render.verified.png ├── CalculatorTests.Render.verified.txt ├── CalculatorTests.cs ├── FluentAvaloniaControlTests.RenderNavigationView.verified.txt ├── FluentAvaloniaControlTests.RenderNavigationViewWithBinding.verified.txt ├── FluentAvaloniaControlTests.RenderNavigationViewWithItemsSource.verified.txt ├── FluentAvaloniaControlTests.cs ├── FluentAvaloniaTests.csproj ├── GlobalUsings.cs ├── ModuleInit.cs └── TestAppBuilder.cs ├── IncludeThemeVariantTests ├── CalculatorTests.Render#dark.verified.png ├── CalculatorTests.Render#light.verified.png ├── CalculatorTests.Render.verified.txt ├── CalculatorTests.cs ├── GlobalUsings.cs ├── IncludeThemeVariantTests.csproj └── ModuleInit.cs ├── NUnitTests ├── CalculatorTests.Should_Add_Numbers.verified.png ├── CalculatorTests.Should_Add_Numbers.verified.txt ├── CalculatorTests.cs ├── GlobalUsings.cs ├── ModuleInit.cs ├── MyUserControlTests.Render.verified.png ├── MyUserControlTests.Render.verified.txt ├── MyUserControlTests.cs ├── NUnitTests.csproj └── TestAppBuilder.cs ├── Shared.sln.DotSettings ├── StandaloneExampleTest.XUnit ├── GlobalUsings.cs ├── StandaloneExampleTest.XUnit.csproj ├── Tests.Test.verified.png ├── Tests.Test.verified.txt ├── Tests.cs └── VerifyAvaloniaSetupApplication.cs ├── TestableApp ├── App.axaml ├── App.axaml.cs ├── GlobalUsings.cs ├── MainWindow.axaml ├── MainWindow.axaml.cs ├── MainWindowViewModel.cs ├── MyUserControl.axaml ├── MyUserControl.axaml.cs ├── Program.cs ├── RecursiveWindow.axaml ├── RecursiveWindow.axaml.cs └── TestableApp.csproj ├── Verify.Avalonia.sln ├── Verify.Avalonia.sln.DotSettings ├── Verify.Avalonia ├── Converters │ ├── AvaloniaConverter.cs │ ├── CornerRadiusConverter.cs │ ├── FontFamilyConverter.cs │ └── ThicknessConverter.cs ├── Extensions.cs ├── GlobalUsings.cs ├── Verify.Avalonia.csproj ├── VerifyAvalonia.cs └── VerifyAvalonia_Converters.cs ├── XUnitTests ├── CalculatorTests.Should_Add_Numbers.verified.png ├── CalculatorTests.Should_Add_Numbers.verified.txt ├── CalculatorTests.cs ├── GlobalUsings.cs ├── ModuleInit.cs ├── MyUserControlTests.Render.verified.png ├── MyUserControlTests.Render.verified.txt ├── MyUserControlTests.cs ├── TestAppBuilder.cs ├── Tests.Recursive.verified.png ├── Tests.Recursive.verified.txt ├── Tests.cs └── XUnitTests.csproj ├── appveyor.yml ├── global.json ├── icon.png ├── key.snk ├── mdsnippets.json └── nuget.config /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: VerifyTests 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug fix 3 | about: Create a bug fix to help us improve 4 | --- 5 | 6 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template. 7 | 8 | 9 | #### Preamble 10 | 11 | General questions may be better placed [StackOveflow](https://stackoverflow.com/). 12 | 13 | Where relevant, ensure you are using the current stable versions on your development stack. For example: 14 | 15 | * Visual Studio 16 | * [.NET SDK or .NET Core SDK](https://www.microsoft.com/net/download) 17 | * Any related NuGet packages 18 | 19 | Any code or stack traces must be properly formatted with [GitHub markdown](https://guides.github.com/features/mastering-markdown/). 20 | 21 | 22 | #### Describe the bug 23 | 24 | A clear and concise description of what the bug is. Include any relevant version information. 25 | 26 | A clear and concise description of what you expected to happen. 27 | 28 | Add any other context about the problem here. 29 | 30 | 31 | #### Minimal Repro 32 | 33 | Ensure you have replicated the bug in a minimal solution with the fewest moving parts. Often this will help point to the true cause of the problem. Upload this repro as part of the issue, preferably a public GitHub repository or a downloadable zip. The repro will allow the maintainers of this project to smoke test the any fix. 34 | 35 | #### Submit a PR that fixes the bug 36 | 37 | Submit a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/) that fixes the bug. Include in this PR a test that verifies the fix. If you were not able to fix the bug, a PR that illustrates your partial progress will suffice. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: How to raise feature requests 4 | --- 5 | 6 | 7 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template. 8 | 9 | If you are certain the feature will be accepted, it is better to raise a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/). 10 | 11 | If you are uncertain if the feature will be accepted, outline the proposal below to confirm it is viable, prior to raising a PR that implements the feature. 12 | 13 | Note that even if the feature is a good idea and viable, it may not be accepted since the ongoing effort in maintaining the feature may outweigh the benefit it delivers. 14 | 15 | 16 | #### Is the feature request related to a problem 17 | 18 | A clear and concise description of what the problem is. 19 | 20 | 21 | #### Describe the solution 22 | 23 | A clear and concise proposal of how you intend to implement the feature. 24 | 25 | 26 | #### Describe alternatives considered 27 | 28 | A clear and concise description of any alternative solutions or features you've considered. 29 | 30 | 31 | #### Additional context 32 | 33 | Add any other context about the feature request here. 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/src" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: merge-dependabot 2 | on: 3 | pull_request: 4 | jobs: 5 | automerge: 6 | runs-on: ubuntu-latest 7 | if: github.actor == 'dependabot[bot]' 8 | steps: 9 | - name: Dependabot Auto Merge 10 | uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6 11 | with: 12 | target: minor 13 | github-token: ${{ secrets.dependabot }} 14 | command: squash and merge -------------------------------------------------------------------------------- /.github/workflows/on-push-do-docs.yml: -------------------------------------------------------------------------------- 1 | name: on-push-do-docs 2 | on: 3 | push: 4 | jobs: 5 | docs: 6 | runs-on: windows-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Run MarkdownSnippets 10 | run: | 11 | dotnet tool install --global MarkdownSnippets.Tool 12 | mdsnippets ${GITHUB_WORKSPACE} 13 | shell: bash 14 | - name: Push changes 15 | run: | 16 | git config --local user.email "action@github.com" 17 | git config --local user.name "GitHub Action" 18 | git commit -m "Docs changes" -a || echo "nothing to commit" 19 | remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git" 20 | branch="${GITHUB_REF:11}" 21 | git push "${remote}" ${branch} || echo "nothing to push" 22 | shell: bash -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | bin/ 4 | obj/ 5 | .vs/ 6 | *.DotSettings.user 7 | .idea/ 8 | *.received.* 9 | nugets/ -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the code of conduct defined by the Contributor Covenant 4 | to clarify expected behavior in our community. 5 | For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/about/code-of-conduct). 6 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Verify.Avalonia 2 | 3 | [![Discussions](https://img.shields.io/badge/Verify-Discussions-yellow?svg=true&label=)](https://github.com/orgs/VerifyTests/discussions) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/5ywtfx5oc257d4tr?svg=true)](https://ci.appveyor.com/project/SimonCropp/verify-avalonia) 5 | [![NuGet Status](https://img.shields.io/nuget/v/Verify.Avalonia.svg)](https://www.nuget.org/packages/Verify.Avalonia/) 6 | 7 | Extends [Verify](https://github.com/VerifyTests/Verify) to allow verification of [Avalonia UIs](https://avaloniaui.net/). 8 | 9 | 10 | ## Getting Started 11 | 12 | The test project needs a `ModuleInitializer` and an Avalonia application with a `Style`. 13 | 14 | 15 | 16 | ```cs 17 | [assembly: AvaloniaTestApplication(typeof(VerifyAvaloniaSetupApplication))] 18 | 19 | public class VerifyAvaloniaSetupApplication : Application 20 | { 21 | [ModuleInitializer] 22 | public static void Init() 23 | { 24 | VerifyImageMagick.RegisterComparers(.24); 25 | VerifierSettings.InitializePlugins(); 26 | } 27 | 28 | public static AppBuilder BuildAvaloniaApp() => 29 | AppBuilder 30 | .Configure() 31 | .UseSkia() 32 | .UseHeadless( 33 | new() 34 | { 35 | UseHeadlessDrawing = false 36 | }); 37 | 38 | public VerifyAvaloniaSetupApplication() => 39 | Styles.Add(new FluentTheme()); 40 | } 41 | ``` 42 | snippet source | anchor 43 | 44 | 45 | And add the following NuGet packages: 46 | 47 | - https://nuget.org/packages/Verify.Avalonia/ 48 | - https://nuget.org/packages/Avalonia.Headless.XUnit/ 49 | - https://nuget.org/packages/Avalonia.Themes.Fluent/ 50 | - https://nuget.org/packages/Avalonia.Skia/ 51 | 52 | 53 | ## More details 54 | 55 | **See [Milestones](../../milestones?state=closed) for release notes.** 56 | 57 | Leverages [Avalonia Headless Testing](https://docs.avaloniaui.net/docs/next/concepts/headless). 58 | 59 | See [Headless Testing with XUnit](https://docs.avaloniaui.net/docs/next/concepts/headless/headless-xunit) and [Headless Testing with NUnit](https://docs.avaloniaui.net/docs/next/concepts/headless/headless-nunit) for more information. 60 | 61 | 62 | ### ModuleInitializer 63 | 64 | 65 | 66 | ```cs 67 | [ModuleInitializer] 68 | public static void Init() 69 | { 70 | VerifyImageMagick.RegisterComparers(0.17); 71 | VerifyAvalonia.Initialize(); 72 | } 73 | ``` 74 | snippet source | anchor 75 | 76 | 77 | This sample uses [Verify.ImageMagick](https://github.com/VerifyTests/Verify.ImageMagick) to ignore small rendering differences that are expected between different operating systems. 78 | 79 | Other [comparers](https://github.com/VerifyTests/Verify/blob/main/docs/comparer.md) options: 80 | 81 | * https://github.com/VerifyTests/Verify.ImageHash 82 | * https://github.com/VerifyTests/Verify.ImageMagick 83 | * https://github.com/VerifyTests/Verify.Phash 84 | * https://github.com/VerifyTests/Verify.ImageSharp.Compare 85 | 86 | 87 | ### Verify.CommunityToolkit.Mvvm 88 | 89 | Many Avalonia projects use [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/). To ensure proper serialization of MVVM commands, use [Verify.CommunityToolkit.Mvvm](https://github.com/VerifyTests/Verify.CommunityToolkit.Mvvm). 90 | 91 | 92 | ### InternalsVisibleTo 93 | 94 | Ensure tests projects have InternalsVisibleTo configured in the target app so tests can use generated controls by name. 95 | 96 | 97 | 98 | ```csproj 99 | 100 | 101 | 102 | 103 | ``` 104 | snippet source | anchor 105 | 106 | 107 | 108 | ### Initialize AvaloniaTestApplication 109 | 110 | The `[AvaloniaTestApplication]` attribute wires the tests in the current project with the specific application. It needs to be defined once per project in any file. Verify.Avalonia requires that `UseHeadlessDrawing` is disabled and `.UseSkia()` is set. 111 | 112 | 113 | 114 | ```cs 115 | [assembly: AvaloniaTestApplication(typeof(TestAppBuilder))] 116 | 117 | public static class TestAppBuilder 118 | { 119 | public static AppBuilder BuildAvaloniaApp() => 120 | AppBuilder.Configure() 121 | .UseSkia() 122 | .UseHeadless( 123 | new() 124 | { 125 | UseHeadlessDrawing = false 126 | }); 127 | } 128 | ``` 129 | snippet source | anchor 130 | 131 | 132 | 133 | ### Window Test 134 | 135 | 136 | 137 | ```cs 138 | public class CalculatorTests 139 | { 140 | [AvaloniaFact] 141 | public Task Should_Add_Numbers() 142 | { 143 | var window = new MainWindow 144 | { 145 | DataContext = new MainWindowViewModel() 146 | }; 147 | 148 | window.Show(); 149 | 150 | // Set values to the input boxes 151 | window.FirstOperandInput.Text = "10"; 152 | window.SecondOperandInput.Text = "20"; 153 | 154 | // Raise click event on the button: 155 | window.AddButton.Focus(); 156 | window.KeyPressQwerty(PhysicalKey.Enter, RawInputModifiers.None); 157 | 158 | Assert.Equal("30", window.ResultBox.Text); 159 | return Verify(window); 160 | } 161 | } 162 | ``` 163 | snippet source | anchor 164 | 165 | 166 | 167 | ### Result in the following snapshots 168 | 169 | 170 | #### Image 171 | 172 | [Should_Add_Numbers.verified.verified.png](/src/XUnitTests/CalculatorTests.Should_Add_Numbers.verified.png): 173 | 174 | 175 | 176 | 177 | #### Text 178 | 179 | 180 | 181 | ```txt 182 | { 183 | Type: MainWindow, 184 | SizeToContent: WidthAndHeight, 185 | Title: Simple Calculator, 186 | CanResize: false, 187 | Content: { 188 | Type: StackPanel, 189 | Spacing: 10.0, 190 | Width: 280.0, 191 | Height: 175.0, 192 | Margin: 10, 193 | HorizontalAlignment: Left, 194 | Children: [ 195 | { 196 | Type: TextBox, 197 | Text: 10, 198 | Watermark: Operand 1, 199 | Name: FirstOperandInput 200 | }, 201 | { 202 | Type: TextBox, 203 | Text: 20, 204 | Watermark: Operand 2, 205 | Name: SecondOperandInput 206 | }, 207 | { 208 | Type: UniformGrid, 209 | Columns: 4, 210 | Children: [ 211 | { 212 | Type: Button, 213 | Command: MainWindowViewModel.Add, 214 | Content: +, 215 | Name: AddButton 216 | }, 217 | { 218 | Type: Button, 219 | Command: MainWindowViewModel.Subtract, 220 | Content: -, 221 | Name: SubtractButton 222 | }, 223 | { 224 | Type: Button, 225 | Command: MainWindowViewModel.Multiply, 226 | Content: *, 227 | Name: MultiplyButton 228 | }, 229 | { 230 | Type: Button, 231 | Command: MainWindowViewModel.Divide, 232 | Content: /, 233 | Name: DivideButton 234 | } 235 | ] 236 | }, 237 | { 238 | Type: StackPanel, 239 | Spacing: 10.0, 240 | Orientation: Horizontal, 241 | Children: [ 242 | { 243 | Type: TextBlock, 244 | Text: Result: 245 | }, 246 | { 247 | Type: TextBlock, 248 | Text: 30, 249 | Name: ResultBox 250 | } 251 | ] 252 | } 253 | ] 254 | }, 255 | Background: LightGray, 256 | Width: 300.0, 257 | Height: 195.0, 258 | IsVisible: true, 259 | DataContext: { 260 | FirstOperand: 10.0, 261 | SecondOperand: 20.0, 262 | Result: 30, 263 | AddCommand: MainWindowViewModel.Add, 264 | SubtractCommand: MainWindowViewModel.Subtract, 265 | MultiplyCommand: MainWindowViewModel.Multiply, 266 | DivideCommand: MainWindowViewModel.Divide 267 | } 268 | } 269 | ``` 270 | snippet source | anchor 271 | 272 | 273 | 274 | ### UserControl Test 275 | 276 | Given the control: 277 | 278 | 279 | 280 | ```axaml 281 | 287 | 288 | 289 |