test: add app-level smoke baselines and mobile CI test jobs

Co-authored-by: Velik <hongochai10@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-02-23 13:15:02 +00:00
parent 969aa81ea2
commit d808f1d4e1
12 changed files with 432 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>AppClientBase.UnitTests</AssemblyName>
<RootNamespace>AppClientBase.UnitTests</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<!-- EN: Test framework dependencies / VI: Phụ thuộc framework test -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<!-- EN: Source generator package used by included view models.
VI: Gói source generator dùng cho view model được include. -->
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>
<ItemGroup>
<!-- EN: Reuse app source files that are platform-neutral.
VI: Tái sử dụng source file ứng dụng không phụ thuộc nền tảng. -->
<Compile Include="..\..\ViewModels\BaseViewModel.cs" Link="ViewModels\BaseViewModel.cs" />
<Compile Include="..\..\ViewModels\MainViewModel.cs" Link="ViewModels\MainViewModel.cs" />
<Compile Include="..\..\Services\INavigationService.cs" Link="Services\INavigationService.cs" />
<Compile Include="..\..\Services\ISettingsService.cs" Link="Services\ISettingsService.cs" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,81 @@
using AppClientBase.Services;
using AppClientBase.ViewModels;
using FluentAssertions;
using Xunit;
namespace AppClientBase.UnitTests.ViewModels;
/// <summary>
/// EN: Unit tests for main view model behavior.
/// VI: Unit tests cho hành vi của main view model.
/// </summary>
public class MainViewModelTests
{
[Fact]
public void Constructor_ShouldSetDefaultTitleAndWelcomeMessage()
{
// Arrange
var navigationService = new FakeNavigationService();
var settingsService = new FakeSettingsService();
// Act
var viewModel = new MainViewModel(navigationService, settingsService);
// Assert
viewModel.Title.Should().Be("Home");
viewModel.WelcomeMessage.Should().Be("Welcome to AppClientBase!");
viewModel.ClickCount.Should().Be(0);
viewModel.ButtonText.Should().Be("Click me");
}
[Fact]
public void IncrementCounterCommand_ShouldIncreaseCountAndPersistSetting()
{
// Arrange
var navigationService = new FakeNavigationService();
var settingsService = new FakeSettingsService();
var viewModel = new MainViewModel(navigationService, settingsService);
// Act
viewModel.IncrementCounterCommand.Execute(null);
viewModel.IncrementCounterCommand.Execute(null);
// Assert
viewModel.ClickCount.Should().Be(2);
viewModel.ButtonText.Should().Be("Clicked 2 times");
settingsService.Get("ClickCount", 0).Should().Be(2);
}
private sealed class FakeNavigationService : INavigationService
{
public Task GoToAsync(string route, IDictionary<string, object>? parameters = null) => Task.CompletedTask;
public Task GoBackAsync() => Task.CompletedTask;
}
private sealed class FakeSettingsService : ISettingsService
{
private readonly Dictionary<string, object?> _store = new(StringComparer.Ordinal);
public T Get<T>(string key, T defaultValue)
{
if (_store.TryGetValue(key, out var value) && value is T typed)
{
return typed;
}
return defaultValue;
}
public void Set<T>(string key, T value)
{
_store[key] = value;
}
public bool Contains(string key) => _store.ContainsKey(key);
public void Remove(string key) => _store.Remove(key);
public void Clear() => _store.Clear();
}
}