Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions TrayApp.Test/TrayAppBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
}

[TestMethod]
public void Preferences_WithTheLongestDebounce_IsAcceptedAndATimerTakesIt()

Check warning on line 164 in TrayApp.Test/TrayAppBuilderTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.

Check warning on line 164 in TrayApp.Test/TrayAppBuilderTests.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Add at least one assertion to this test case.
{
InMemoryPersistenceProvider<string> provider = new();
TrayAppDefinition app = TrayAppBuilder.Create("demo")
Expand Down Expand Up @@ -200,6 +200,67 @@
Assert.IsFalse(forgotten);
}

[TestMethod]
[DataRow(false)]
[DataRow(true)]
public async Task LoadPreferences_WhenTheToggleIsAlreadyInTheRememberedState_DoesNotCallTheSetter(bool state)
{
InMemoryPersistenceProvider<string> provider = new();

using (DebouncedPreferenceStore seed = new(provider, "tray", Debounce))
{
await seed.LoadAsync().ConfigureAwait(false);

Check warning on line 212 in TrayApp.Test/TrayAppBuilderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_TrayApp&issues=AaGU9YePQ4iSPc4VhyIZ&open=AaGU9YePQ4iSPc4VhyIZ&pullRequest=65
seed.Set("awake", state);
await seed.FlushAsync().ConfigureAwait(false);

Check warning on line 214 in TrayApp.Test/TrayAppBuilderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_TrayApp&issues=AaGU9YePQ4iSPc4VhyIa&open=AaGU9YePQ4iSPc4VhyIa&pullRequest=65
}

bool awake = state;
int setterCalls = 0;

// The setter ignores its argument and flips, as the class example's does.
TrayAppDefinition app = TrayAppBuilder.Create("demo")
.Toggle("Keep awake", () => awake, _ =>
{
setterCalls++;
awake = !awake;
}, persistAs: "awake")
.Preferences(provider)
.Build();

using DebouncedPreferenceStore store = new(provider, "tray", Debounce);

Assert.IsNull(TrayAppBuilder.LoadPreferences(app, store));
Assert.AreEqual(state, awake);
Assert.AreEqual(0, setterCalls);
}

[TestMethod]
public async Task LoadPreferences_WhenTheGetterThrows_KeepsGoingAndHandsBackTheMessage()
{
InMemoryPersistenceProvider<string> provider = new();

using (DebouncedPreferenceStore seed = new(provider, "tray", Debounce))
{
await seed.LoadAsync().ConfigureAwait(false);

Check warning on line 244 in TrayApp.Test/TrayAppBuilderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_TrayApp&issues=AaGU9YePQ4iSPc4VhyIX&open=AaGU9YePQ4iSPc4VhyIX&pullRequest=65
seed.Set("awake", true);
seed.Set("display", true);
await seed.FlushAsync().ConfigureAwait(false);

Check warning on line 247 in TrayApp.Test/TrayAppBuilderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_TrayApp&issues=AaGU9YePQ4iSPc4VhyIY&open=AaGU9YePQ4iSPc4VhyIY&pullRequest=65
}

bool display = false;

TrayAppDefinition app = TrayAppBuilder.Create("demo")
.Toggle("Keep awake", () => throw new InvalidOperationException("no inhibitor here"), _ => { }, persistAs: "awake")
.Toggle("Keep display awake", () => display, value => display = value, persistAs: "display")
.Preferences(provider)
.Build();

using DebouncedPreferenceStore store = new(provider, "tray", Debounce);

Assert.AreEqual("no inhibitor here", TrayAppBuilder.LoadPreferences(app, store));
Assert.IsTrue(display);
}

[TestMethod]
public async Task LoadPreferences_ThenOptions_LetsAnExplicitSwitchWin()
{
Expand Down
6 changes: 6 additions & 0 deletions TrayApp/Menu/TrayToggleItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public override void Refresh()
/// </remarks>
public void Set(bool value) => setState(value);

/// <summary>
/// Reads what the tool currently reports, without touching <see cref="IsChecked"/>.
/// </summary>
/// <returns>The getter's value. The getter belongs to the tool and may throw.</returns>
internal bool ReadState() => getState();

/// <summary>
/// Sets the toggle to the opposite of what the tool currently reports.
/// </summary>
Expand Down
8 changes: 7 additions & 1 deletion TrayApp/TrayAppBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,13 @@ private static async Task<int> FinishAsync(int exitCode, DebouncedPreferenceStor

try
{
toggle.Set(remembered);
// A restore asks for a state, where a click asks for a change, so a toggle already in the
// remembered state is left alone: a setter written as a flip (as the class example's is) would
// otherwise invert it, and one that takes a resource would take it a second time.
if (toggle.ReadState() != remembered)
{
toggle.Set(remembered);
}
}
catch (Exception ex) when (FatalError.IsNotFatal(ex))
{
Expand Down
Loading