< Summary

Information
Class: Pomodoro.Web.Pages.SettingsPageBase
Assembly: Pomodoro.Web
File(s): /home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Pages/Settings.razor.cs
Line coverage
100%
Covered lines: 135
Uncovered lines: 0
Coverable lines: 135
Total lines: 251
Line coverage: 100%
Branch coverage
100%
Covered branches: 10
Total branches: 10
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_TimerService()100%11100%
get_ExportService()100%11100%
get_TaskService()100%11100%
get_ActivityService()100%11100%
get_JSInteropService()100%11100%
get_Logger()100%11100%
get_SettingsPresenterService()100%11100%
get_Settings()100%11100%
get_OriginalSettings()100%11100%
get_ShowToast()100%11100%
get_ToastMessage()100%11100%
get_IsExporting()100%11100%
get_IsImporting()100%11100%
get_IsClearing()100%11100%
get_ShowClearConfirmation()100%11100%
get_ImportResult()100%11100%
get_HasChanges()100%11100%
MarkDirty()100%11100%
ShowTemporaryToastAsync()100%11100%
<ShowTemporaryToastAsync()100%11100%
get_IsAtDefaults()100%11100%
OnInitialized()100%11100%
HandleSave()100%11100%
ResetToDefaults()100%11100%
ExportJson()100%11100%
HandleImport()100%1010100%
ConfirmClearData()100%11100%
ClearData()100%11100%

File(s)

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Pages/Settings.razor.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Components;
 2using Microsoft.AspNetCore.Components.Forms;
 3using Microsoft.Extensions.Logging;
 4using Microsoft.JSInterop;
 5using Pomodoro.Web.Models;
 6using Pomodoro.Web.Services;
 7
 8namespace Pomodoro.Web.Pages;
 9
 10/// <summary>
 11/// Code-behind for Settings page
 12/// </summary>
 13public class SettingsPageBase : ComponentBase
 14{
 15    #region Services (Dependency Injection)
 16
 17    [Inject]
 43018    protected ITimerService TimerService { get; set; } = default!;
 19
 20    [Inject]
 29421    protected IExportService ExportService { get; set; } = default!;
 22
 23    [Inject]
 28124    protected ITaskService TaskService { get; set; } = default!;
 25
 26    [Inject]
 28127    protected IActivityService ActivityService { get; set; } = default!;
 28
 29    [Inject]
 28030    protected IJSInteropService JSInteropService { get; set; } = default!;
 31
 32    [Inject]
 29433    protected ILogger<SettingsPageBase> Logger { get; set; } = default!;
 34
 35    [Inject]
 45836    protected SettingsPresenterService SettingsPresenterService { get; set; } = null!;
 37
 38    #endregion
 39
 40    #region State
 41
 133642    protected TimerSettings Settings { get; set; } = new TimerSettings();
 43
 46844    protected TimerSettings OriginalSettings { get; set; } = new TimerSettings();
 45
 21546    protected bool ShowToast { get; set; }
 47
 5748    protected string? ToastMessage { get; set; }
 49
 50    // Export/Import state
 18851    protected bool IsExporting { get; set; }
 18552    protected bool IsImporting { get; set; }
 18953    protected bool IsClearing { get; set; }
 20054    protected bool ShowClearConfirmation { get; set; }
 19355    protected string? ImportResult { get; set; }
 56
 57    /// <summary>
 58    /// Indicates whether current settings differ from the original saved settings
 59    /// </summary>
 17860    protected bool HasChanges => !Settings.Equals(OriginalSettings);
 61
 462    protected void MarkDirty() => StateHasChanged();
 63
 64    protected async Task ShowTemporaryToastAsync(string message)
 1565    {
 1566        ToastMessage = message;
 1567        ShowToast = true;
 1568        StateHasChanged();
 69
 1570        SafeTaskRunner.RunAndForget(
 1571            async () =>
 1572            {
 1573                await Task.Delay(Constants.UI.ToastDurationMs);
 1574                ShowToast = false;
 1575                ToastMessage = null;
 1576                await InvokeAsync(StateHasChanged);
 1577            },
 1578            Logger,
 1579            Constants.SafeTaskOperations.ToastHide
 1580        );
 1581    }
 82
 83    /// <summary>
 84    /// Indicates whether current settings differ from default values
 85    /// </summary>
 17886    protected bool IsAtDefaults => SettingsPresenterService.IsAtDefaults(Settings);
 87
 88    #endregion
 89
 90    #region Lifecycle Methods
 91
 92    protected override void OnInitialized()
 13893    {
 94        // Clone settings from TimerService for local editing
 13895        Settings = TimerService.Settings.Clone();
 96        // Store original settings for comparison
 13897        OriginalSettings = Settings.Clone();
 13898    }
 99
 100    #endregion
 101
 102    #region Actions
 103
 104    public async Task HandleSave()
 8105    {
 8106        await TimerService.UpdateSettingsAsync(Settings);
 107
 108        // Update original settings after save using Clone() for consistency
 6109        OriginalSettings = Settings.Clone();
 110
 6111        await ShowTemporaryToastAsync("Settings saved successfully!");
 6112    }
 113
 114    public void ResetToDefaults()
 2115    {
 116        // Use model's default values - single source of truth
 2117        Settings = new TimerSettings();
 2118    }
 119
 120    #endregion
 121
 122    #region Export/Import Actions
 123
 124    public async Task ExportJson()
 5125    {
 5126        IsExporting = true;
 5127        StateHasChanged();
 128
 129        try
 5130        {
 5131            var json = await ExportService.ExportToJsonAsync();
 3132            var filename = $"pomodoro-backup-{DateTime.Today:yyyy-MM-dd}.json";
 3133            await SettingsPresenterService.DownloadFileAsync(JSInteropService, filename, json, "application/json");
 134
 3135            await ShowTemporaryToastAsync("JSON backup exported successfully!");
 3136        }
 2137        catch (Exception ex)
 2138        {
 2139            Logger.LogError(ex, "Failed to export JSON");
 2140            ToastMessage = "Failed to export JSON backup. Please try again.";
 2141            ShowToast = true;
 2142            StateHasChanged();
 2143        }
 144        finally
 5145        {
 5146            IsExporting = false;
 5147            StateHasChanged();
 5148        }
 5149    }
 150
 151    public async Task HandleImport(InputFileChangeEventArgs e)
 8152    {
 8153        var file = e.File;
 8154        if (file is null || file.Size == 0)
 2155        {
 2156            ImportResult = "No file selected";
 2157            return;
 158        }
 159
 160        // Validate file size to prevent memory issues
 6161        if (file.Size > Constants.Validation.MaxImportFileSizeBytes)
 1162        {
 1163            ImportResult = $"File too large. Maximum size is {Constants.Validation.MaxImportFileSizeBytes / (1024 * 1024
 1164            return;
 165        }
 166
 5167        IsImporting = true;
 5168        ImportResult = null;
 5169        StateHasChanged();
 170
 171        try
 5172        {
 173            // Use explicit max size to ensure it matches our validation
 5174            using var stream = file.OpenReadStream(Constants.Validation.MaxImportFileSizeBytes);
 5175            using var reader = new StreamReader(stream);
 5176            var jsonData = await reader.ReadToEndAsync();
 177
 5178            var result = await ExportService.ImportFromJsonAsync(jsonData);
 179
 4180            if (result.Success)
 2181            {
 182                // Build success message with detailed statistics
 2183                var message = SettingsPresenterService.BuildImportSuccessMessage(result.TotalImported, result.TotalSkipp
 184
 185                // Reload all services to reflect imported data without page reload
 2186                await TaskService.ReloadAsync();
 2187                await ActivityService.ReloadAsync();
 188
 189                // Update settings from the newly imported data
 2190                Settings = TimerService.Settings.Clone();
 2191                OriginalSettings = Settings.Clone();
 192
 2193                IsImporting = false;
 2194                ImportResult = null;
 2195                await ShowTemporaryToastAsync(message);
 2196            }
 197            else
 2198            {
 199                // Show error message without reloading
 2200                ImportResult = result.ErrorMessage ?? "Import failed. Please check the file format.";
 2201                IsImporting = false;
 2202                StateHasChanged();
 2203            }
 4204        }
 1205        catch (Exception ex)
 1206        {
 1207            Logger.LogError(ex, "Failed to import JSON");
 1208            ImportResult = "Import failed. Please check the file format.";
 1209            IsImporting = false;
 1210            StateHasChanged();
 1211        }
 8212    }
 213
 214    public void ConfirmClearData()
 16215    {
 16216        ShowClearConfirmation = true;
 16217    }
 218
 219    public async Task ClearData()
 6220    {
 6221        ShowClearConfirmation = false;
 6222        IsClearing = true;
 6223        StateHasChanged();
 224
 225        try
 6226        {
 6227            await ExportService.ClearAllDataAsync();
 228
 229            // Reset settings to defaults
 4230            Settings = new TimerSettings();
 4231            await TimerService.UpdateSettingsAsync(Settings);
 232
 4233            OriginalSettings = new TimerSettings();
 234
 235            // Reload all services to reflect cleared data without page reload
 4236            await TaskService.ReloadAsync();
 4237            await ActivityService.ReloadAsync();
 238
 4239            IsClearing = false;
 4240            await ShowTemporaryToastAsync("All data cleared successfully!");
 4241        }
 2242        catch (Exception ex)
 2243        {
 2244            Logger.LogError(ex, "Failed to clear data");
 2245            IsClearing = false;
 2246            StateHasChanged();
 2247        }
 6248    }
 249
 250    #endregion
 251}