< Summary

Information
Class: Allyaria.Theming.Services.ThemingService
Assembly: Allyaria.Theming
File(s): /home/runner/work/allyaria/allyaria/src/Allyaria.Theming/Services/ThemingService.cs
Line coverage
97%
Covered lines: 45
Uncovered lines: 1
Coverable lines: 46
Total lines: 162
Line coverage: 97.8%
Branch coverage
91%
Covered branches: 22
Total branches: 24
Branch coverage: 91.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
get_EffectiveType()100%11100%
get_StoredType()100%11100%
GetComponentCss(...)100%11100%
GetComponentCssVars(...)90%101094.11%
GetDocumentCss()100%11100%
OnThemeChanged()100%22100%
SetEffectiveType(...)100%44100%
SetStoredType(...)83.33%66100%

File(s)

/home/runner/work/allyaria/allyaria/src/Allyaria.Theming/Services/ThemingService.cs

#LineLine coverage
 1namespace Allyaria.Theming.Services;
 2
 3/// <summary>
 4/// Provides the core implementation of the <see cref="IThemingService" /> interface, managing the currently active and
 5/// stored theme types, as well as CSS generation for Allyaria-themed components and documents.
 6/// </summary>
 7/// <remarks>
 8///     <para>
 9///     This service maintains both the <see cref="EffectiveType" /> (the theme currently applied in the UI) and the
 10///     <see cref="StoredType" /> (the user’s persisted theme preference).
 11///     </para>
 12///     <para>
 13///     When <see cref="StoredType" /> changes, the service raises the <see cref="ThemeChanged" /> event so that UI
 14///     components can reactively update their styling.
 15///     </para>
 16/// </remarks>
 17public sealed class ThemingService : IThemingService
 18{
 19    /// <summary>The underlying theme definition used to compute and generate CSS styles.</summary>
 20    private readonly Theme _theme;
 21
 22    /// <summary>
 23    /// Initializes a new instance of the <see cref="ThemingService" /> class with the specified theme and initial type.
 24    /// </summary>
 25    /// <param name="theme">The <see cref="Theme" /> instance used to generate CSS and map component styles.</param>
 26    /// <param name="themeType">The initial <see cref="ThemeType" /> to use. Defaults to <see cref="ThemeType.System" />
 1627    internal ThemingService(Theme theme, ThemeType themeType = ThemeType.System)
 28    {
 1629        _theme = theme;
 1630        StoredType = themeType;
 31
 1632        EffectiveType = StoredType is ThemeType.System
 1633            ? ThemeType.Light
 1634            : StoredType;
 1635    }
 36
 37    /// <summary>Occurs when the currently active (effective) theme changes.</summary>
 38    public event EventHandler? ThemeChanged;
 39
 40    /// <summary>Gets the currently effective <see cref="ThemeType" /> applied to the UI.</summary>
 4141    public ThemeType EffectiveType { get; private set; }
 42
 43    /// <summary>
 44    /// Gets the persisted or stored <see cref="ThemeType" />, representing user preference or saved configuration.
 45    /// </summary>
 5646    public ThemeType StoredType { get; private set; }
 47
 48    /// <summary>
 49    /// Generates component-level CSS for a given prefix, component type, and state, based on the current effective them
 50    /// </summary>
 51    /// <param name="prefix">The CSS class prefix for the component.</param>
 52    /// <param name="componentType">The <see cref="ComponentType" /> representing the themed component.</param>
 53    /// <param name="componentState">The <see cref="ComponentState" /> representing the current visual state.</param>
 54    /// <returns>A string containing CSS rules scoped to the specified component and theme state.</returns>
 55    public string GetComponentCss(string prefix, ComponentType componentType, ComponentState componentState)
 456        => _theme.GetComponentCss(
 457            prefix: prefix,
 458            componentType: componentType,
 459            themeType: EffectiveType,
 460            componentState: componentState
 461        );
 62
 63    /// <summary>
 64    /// Generates CSS variable declarations for a specific <see cref="ThemeType" />, <see cref="ComponentType" />, and
 65    /// <see cref="ComponentState" /> by transforming computed component CSS into corresponding <c>var(--prefix-property
 66    /// references.
 67    /// </summary>
 68    /// <param name="themeType">
 69    /// The theme from which variables should be generated. If <see cref="ThemeType.System" /> is specified, no variable
 70    /// produced.
 71    /// </param>
 72    /// <param name="componentType">The component type whose themed styles are being converted into variables.</param>
 73    /// <param name="componentState">The visual state of the component whose styles should be mapped.</param>
 74    /// <returns>
 75    /// A string containing CSS variable references derived from the component’s themed CSS, or an empty string when no
 76    /// variables can be generated.
 77    /// </returns>
 78    public string GetComponentCssVars(ThemeType themeType, ComponentType componentType, ComponentState componentState)
 79    {
 380        if (themeType is ThemeType.System)
 81        {
 182            return string.Empty;
 83        }
 84
 285        var cssVars = GetComponentCss(
 286            prefix: StyleDefaults.VarPrefix, componentType: componentType, componentState: componentState
 287        );
 88
 289        if (string.IsNullOrWhiteSpace(value: cssVars))
 90        {
 91            // Code Coverage: Unreachable code path
 092            return string.Empty;
 93        }
 94
 295        var builder = new StringBuilder();
 296        var prefix = $"{StyleDefaults.VarPrefix}-{componentType}-{themeType}-{componentState}".ToCssName();
 297        var split = cssVars.Split(separator: ';', options: StringSplitOptions.RemoveEmptyEntries);
 98
 1099        foreach (var item in split)
 100        {
 3101            var pair = item.Split(separator: ':', options: StringSplitOptions.RemoveEmptyEntries);
 102
 3103            if (pair.Length < 2)
 104            {
 105                continue;
 106            }
 107
 1108            var property = pair[0].ToCssName();
 109
 1110            if (string.IsNullOrWhiteSpace(value: property))
 111            {
 112                // Code Coverage: Unreachable code path
 113                continue;
 114            }
 115
 1116            builder.Append(handler: $"{property}:var(--{prefix}-{property});");
 117        }
 118
 2119        return builder.ToString();
 120    }
 121
 122    /// <summary>Generates global document-level CSS reflecting the currently active theme.</summary>
 123    /// <returns>A string containing CSS rules applicable at the document scope.</returns>
 2124    public string GetDocumentCss() => _theme.GetDocumentCss(themeType: EffectiveType);
 125
 126    /// <summary>Raises the <see cref="ThemeChanged" /> event to notify subscribers of theme changes.</summary>
 3127    private void OnThemeChanged() => ThemeChanged?.Invoke(sender: this, e: EventArgs.Empty);
 128
 129    /// <summary>Sets the currently effective <see cref="ThemeType" /> and triggers a theme update if it changes.</summa
 130    /// <param name="themeType">The new <see cref="ThemeType" /> to apply.</param>
 131    public void SetEffectiveType(ThemeType themeType)
 132    {
 5133        if (themeType == ThemeType.System || EffectiveType == themeType)
 134        {
 2135            return;
 136        }
 137
 3138        EffectiveType = themeType;
 3139        OnThemeChanged();
 3140    }
 141
 142    /// <summary>Sets the stored <see cref="ThemeType" /> preference and updates the effective type accordingly.</summar
 143    /// <param name="themeType">The <see cref="ThemeType" /> to store and potentially activate.</param>
 144    public void SetStoredType(ThemeType themeType)
 145    {
 3146        if (StoredType == themeType)
 147        {
 1148            return;
 149        }
 150
 2151        StoredType = themeType;
 152
 2153        if (themeType != ThemeType.System)
 154        {
 1155            SetEffectiveType(themeType: themeType);
 156        }
 157        else
 158        {
 1159            ThemeChanged?.Invoke(sender: this, e: EventArgs.Empty);
 160        }
 1161    }
 162}