Files
HellionChat/HellionChat/SelfTests/WizardStateSmokeStep.cs
T
JonKazama-Hellion 6829a80ff2 refactor(wizard): drop LoadPreviousSession, which never did anything
The wizard asked for it, coupled it to a neighbour, listed it in the
summary as applied, and wrote it to the config. No code in the plugin
has ever read it. A wizard that collects a decision and reports an
effect that does not happen is worse than one that never asked.

Its partner, FilterIncludePreviousSessions, has a real reader and stays.
The coupling was two-way -- switching this on forced the partner on,
switching the partner off forced this one off -- so removing it leaves a
single checkbox that means what it says, and the summary line now
reports the setting that actually took effect.

The self-test asserted that skipping step 3 does not overwrite either
field. That assertion is rewritten rather than repaired: it was pinning
the null-semantics of the wizard's pending state, which still matters,
just with one field instead of two.
2026-08-18 22:54:06 +02:00

130 lines
5.8 KiB
C#

using System.Collections.Generic;
using Dalamud.Bindings.ImGui;
using Dalamud.Plugin.SelfTest;
using HellionChat.Code;
using HellionChat.Ui;
namespace HellionChat.SelfTests;
// Drives the FirstRunWizard state machine through every step and
// commits a no-op pending state (Variant 1), then re-runs picking
// Roleplay on Step 2 and skipping Step 3 (Variant 2). Verifies
// that the staged-commit path does not throw under any combination
// of Pending* values and that CommitPending leaves Config in a
// readable shape. Variant 2's Roleplay commit would normally
// mutate the six PrivacyFilter / Retention fields ApplyRoleplay
// touches, so the step snapshots them before Variant 2 runs and
// CleanUp() restores them — the self-test stays idempotent across
// repeated /xlperf runs and does not overwrite an active privacy
// profile.
internal sealed class WizardStateSmokeStep : ISelfTestStep
{
private readonly Plugin plugin;
// Snapshot slots for the six Configuration fields ApplyRoleplay
// writes in Variant 2. Populated right before Variant 2 mutates
// Config, consumed by CleanUp(). Reference-typed snapshots
// (HashSet, Dictionary) capture the existing slot by reference,
// which is safe because ApplyRoleplay reassigns the slot with
// a fresh instance instead of mutating in place.
private bool? snapshotPrivacyFilterEnabled;
private HashSet<ChatType>? snapshotPrivacyPersistChannels;
private bool? snapshotPrivacyPersistUnknownChannels;
private bool? snapshotRetentionEnabled;
private int? snapshotRetentionDefaultDays;
private Dictionary<ChatType, int>? snapshotRetentionPerChannelDays;
public WizardStateSmokeStep(Plugin plugin)
{
this.plugin = plugin;
}
public string Name => "Hellion Chat - FirstRunWizard state smoke";
public SelfTestStepResult RunStep()
{
var wizard = this.plugin.FirstRunWizard;
if (wizard is null)
{
ImGui.Text("Plugin.FirstRunWizard is null");
return SelfTestStepResult.Fail;
}
try
{
// Variant 1: no-op CommitPending. Walks the state machine and
// verifies the empty-pending write-back path does not throw.
wizard.TestOnly_AdvanceTo(1);
wizard.TestOnly_AdvanceTo(2);
wizard.TestOnly_AdvanceTo(3);
wizard.TestOnly_AdvanceTo(4);
wizard.CommitPending();
// Variant 2: skip Step 3 explicitly. Picks Roleplay on Step 2,
// jumps straight to Step 4 (no Step-3 entry → no seed for
// FilterIncludePreviousSessions), commits, and asserts the history
// toggle remained on its pre-test value. Pins the null-semantics
// from Spec Z.176 so a regression in CommitPending that started
// writing seeded recommendations unconditionally would surface
// here.
// CommitPending → ApplyRoleplay overwrites six privacy /
// retention fields, so snapshot them first and let CleanUp
// restore them after the assert. Keeps /xlperf idempotent.
this.snapshotPrivacyFilterEnabled = Plugin.Config.PrivacyFilterEnabled;
this.snapshotPrivacyPersistChannels = Plugin.Config.PrivacyPersistChannels;
this.snapshotPrivacyPersistUnknownChannels = Plugin
.Config
.PrivacyPersistUnknownChannels;
this.snapshotRetentionEnabled = Plugin.Config.RetentionEnabled;
this.snapshotRetentionDefaultDays = Plugin.Config.RetentionDefaultDays;
this.snapshotRetentionPerChannelDays = Plugin.Config.RetentionPerChannelDays;
var filterPrevBefore = Plugin.Config.FilterIncludePreviousSessions;
wizard.TestOnly_AdvanceTo(2);
wizard.TestOnly_SetPendingProfile(FirstRunWizard.PrivacyProfile.Roleplay);
wizard.TestOnly_AdvanceTo(4);
wizard.CommitPending();
if (Plugin.Config.FilterIncludePreviousSessions != filterPrevBefore)
{
ImGui.Text("Skip-Step-3 path overwrote FilterIncludePreviousSessions");
return SelfTestStepResult.Fail;
}
}
catch (Exception ex)
{
ImGui.Text($"Wizard state smoke threw: {ex.GetType().Name}: {ex.Message}");
return SelfTestStepResult.Fail;
}
return SelfTestStepResult.Pass;
}
public void CleanUp()
{
// Restore the six Variant-2 snapshots so back-to-back /xlperf
// runs don't drift the active privacy profile. If Variant 2
// never ran (Variant 1 threw early), the slots stay null and
// restore is a no-op. After restore the slots are nulled so a
// future RunStep starts fresh.
if (this.snapshotPrivacyFilterEnabled is { } privacyFilter)
Plugin.Config.PrivacyFilterEnabled = privacyFilter;
if (this.snapshotPrivacyPersistChannels is { } persistChannels)
Plugin.Config.PrivacyPersistChannels = persistChannels;
if (this.snapshotPrivacyPersistUnknownChannels is { } persistUnknown)
Plugin.Config.PrivacyPersistUnknownChannels = persistUnknown;
if (this.snapshotRetentionEnabled is { } retentionEnabled)
Plugin.Config.RetentionEnabled = retentionEnabled;
if (this.snapshotRetentionDefaultDays is { } retentionDays)
Plugin.Config.RetentionDefaultDays = retentionDays;
if (this.snapshotRetentionPerChannelDays is { } retentionPolicy)
Plugin.Config.RetentionPerChannelDays = retentionPolicy;
this.snapshotPrivacyFilterEnabled = null;
this.snapshotPrivacyPersistChannels = null;
this.snapshotPrivacyPersistUnknownChannels = null;
this.snapshotRetentionEnabled = null;
this.snapshotRetentionDefaultDays = null;
this.snapshotRetentionPerChannelDays = null;
}
}