Compare commits
2 commits
1733dababb
...
fdb7acca56
| Author | SHA1 | Date | |
|---|---|---|---|
| fdb7acca56 | |||
| 5104ffeb81 |
6 changed files with 152 additions and 7 deletions
125
src/PostIt.Tests/SessionStatusBannerTests.cs
Normal file
125
src/PostIt.Tests/SessionStatusBannerTests.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Headless.XUnit;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.VisualTree;
|
||||
using PostIt.ViewModels;
|
||||
using PostIt.Views;
|
||||
|
||||
namespace PostIt.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// UI tests for <see cref="SessionStatusBanner"/>. Mounted inside
|
||||
/// a real <see cref="MainWindow"/> via the headless Avalonia
|
||||
/// platform declared in <c>TestApp.cs</c>.
|
||||
///
|
||||
/// <para>The pattern is the one that <c>UnitTest1.MainPage_Should_Load</c>
|
||||
/// established: a test attribute <c>[AvaloniaFact]</c> (from
|
||||
/// <c>Avalonia.Headless.XUnit</c>) instead of plain <c>[Fact]</c>,
|
||||
/// <c>new MainWindow()</c>, <c>window.Show()</c>. The AvaloniaFact
|
||||
/// attribute schedules the test body inside a dispatcher, which
|
||||
/// is the precondition for the headless Window's
|
||||
/// <c>PlatformManager.CreateWindow()</c> to find a registered
|
||||
/// service. A plain <c>[Fact]</c> test that calls
|
||||
/// <c>new Window().Show()</c> throws because the harness has not
|
||||
/// been initialised for that thread.</para>
|
||||
///
|
||||
/// <para>The session banner's <c>DataContext</c> is not wired in
|
||||
/// these tests: <c>App.OnFrameworkInitializationCompleted</c> is
|
||||
/// not called in a unit test, so we set the DataContext on the
|
||||
/// banner directly. The production code path is exercised
|
||||
/// end-to-end by the manual launch, not here.</para>
|
||||
/// </summary>
|
||||
public class SessionStatusBannerTests
|
||||
{
|
||||
[AvaloniaFact]
|
||||
public void Banner_renders_three_buttons_in_the_visual_tree()
|
||||
{
|
||||
var window = new MainWindow();
|
||||
window.SessionBanner.DataContext = new SessionStatusViewModel();
|
||||
window.Show();
|
||||
|
||||
var buttons = window.SessionBanner.GetVisualDescendants()
|
||||
.OfType<Button>()
|
||||
.ToList();
|
||||
|
||||
// Three buttons, named by their content text: Se
|
||||
// déconnecter, Se connecter, Paramètres. If any one is
|
||||
// missing, the user has no way to trigger the
|
||||
// corresponding navigation event.
|
||||
Assert.Equal(3, buttons.Count);
|
||||
Assert.Contains(buttons, b => b.Content as string == "Se déconnecter");
|
||||
Assert.Contains(buttons, b => b.Content as string == "Se connecter");
|
||||
Assert.Contains(buttons, b => b.Content as string == "Paramètres");
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void Banner_login_button_is_visible_when_logged_out()
|
||||
{
|
||||
var window = new MainWindow();
|
||||
var vm = new SessionStatusViewModel();
|
||||
Assert.True(vm.IsLoggedOut); // VM default
|
||||
window.SessionBanner.DataContext = vm;
|
||||
window.Show();
|
||||
|
||||
var login = window.SessionBanner.GetVisualDescendants()
|
||||
.OfType<Button>()
|
||||
.Single(b => b.Content as string == "Se connecter");
|
||||
|
||||
// The XAML binds IsVisible to IsLoggedOut. After Show,
|
||||
// the binding has been evaluated.
|
||||
Assert.True(login.IsVisible);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void Banner_logout_button_is_hidden_when_logged_out()
|
||||
{
|
||||
var window = new MainWindow();
|
||||
var vm = new SessionStatusViewModel();
|
||||
Assert.False(vm.IsLoggedIn); // VM default
|
||||
window.SessionBanner.DataContext = vm;
|
||||
window.Show();
|
||||
|
||||
var logout = window.SessionBanner.GetVisualDescendants()
|
||||
.OfType<Button>()
|
||||
.Single(b => b.Content as string == "Se déconnecter");
|
||||
|
||||
Assert.False(logout.IsVisible);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void Banner_settings_button_is_visible_regardless_of_session()
|
||||
{
|
||||
var window = new MainWindow();
|
||||
window.SessionBanner.DataContext = new SessionStatusViewModel();
|
||||
window.Show();
|
||||
|
||||
var settings = window.SessionBanner.GetVisualDescendants()
|
||||
.OfType<Button>()
|
||||
.Single(b => b.Content as string == "Paramètres");
|
||||
|
||||
// Paramètres is the only button with no IsVisible
|
||||
// binding — always shown. The user's only path to the
|
||||
// settings page goes through this button.
|
||||
Assert.True(settings.IsVisible);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void Banner_session_label_reflects_DataContext()
|
||||
{
|
||||
var window = new MainWindow();
|
||||
window.SessionBanner.DataContext = new SessionStatusViewModel();
|
||||
window.Show();
|
||||
|
||||
var label = window.SessionBanner.GetVisualDescendants()
|
||||
.OfType<TextBlock>()
|
||||
.First(t => t.Text == "Déconnecté" || t.Text == "Connecté");
|
||||
|
||||
// Default SessionLabel is "Déconnecté" until Refresh()
|
||||
// is called with a valid session. This pins the default
|
||||
// so a future refactor that breaks the initial value
|
||||
// (e.g. by removing the field initialiser) is caught.
|
||||
Assert.Equal("Déconnecté", label.Text);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.0.4" />
|
||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.2" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||
<PackageVersion Include="Material.Avalonia" Version="3.17.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
|
||||
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.8.0" />
|
||||
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.2.0" />
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Material.Avalonia" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PostIt\PostIt.csproj" />
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Avalonia;
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
using PostIt.Services;
|
||||
using PostIt.ViewModels;
|
||||
using PostIt.Views;
|
||||
|
|
@ -112,8 +113,24 @@ public partial class App : Application
|
|||
// DataContext, and the TwoWay bindings inside the page keep
|
||||
// mutating the same in-memory Settings instance that the rest
|
||||
// of the app reads (OidcClientOptions construction, etc.).
|
||||
provider.GetRequiredService<SettingsPage>().DataContext =
|
||||
provider.GetRequiredService<Settings>();
|
||||
provider.GetRequiredService<SettingsPage>().DataContext = settings;
|
||||
|
||||
// Settings.DarkMode was previously a dead field: it round-
|
||||
// tripped through the settings file and the SettingsPage
|
||||
// CheckBox, but no consumer ever read it. Wire it here to
|
||||
// Application.RequestedThemeVariant so the toggle takes
|
||||
// effect immediately, and seed the initial theme from the
|
||||
// value Load() just populated (so a dark-mode user lands on
|
||||
// a dark window on first launch, not on a default-light
|
||||
// window that flips after the user touches the toggle).
|
||||
ApplyDarkMode(settings);
|
||||
settings.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Settings.DarkMode))
|
||||
{
|
||||
ApplyDarkMode(settings);
|
||||
}
|
||||
};
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
|
|
@ -190,6 +207,12 @@ public partial class App : Application
|
|||
}
|
||||
}
|
||||
|
||||
private static void ApplyDarkMode(Settings settings)
|
||||
{
|
||||
Application.Current!.RequestedThemeVariant =
|
||||
settings.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run once after the main window is shown: try to refresh the
|
||||
/// cached OIDC tokens silently; on success, push MainPage on top
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using PostIt.Models;
|
||||
|
|
@ -38,9 +37,6 @@ public partial class MainPageViewModel : ViewModelBase
|
|||
[ObservableProperty]
|
||||
public partial bool IsBusy { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
ThemeVariant themeVariant = ThemeVariant.Default;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Settings Settings { get; private set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
x:Class="PostIt.Views.SessionStatusBanner"
|
||||
x:DataType="vm:SessionStatusViewModel">
|
||||
<Border DockPanel.Dock="Top"
|
||||
Background="#ECEFF1"
|
||||
BorderBrush="#B0BEC5"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="12,6">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue