feat(vt): Profiles tab Delete action, bound through the markup contract
Campaign VT slice 1 Part A round 2 step 5: the Profiles tab's action set (Create/Select/Save/Delete/name field/mine-only) was missing Delete entirely — every profile family only ever had Create/Copy/Clear. Adds MossTankProfileStore.Delete (removes the selected named profile's real .usd file and its side-car, falls back to "By char"; refuses for "By char" itself, which has nothing to delete — see ClearCurrent for that case) and wires it through MossTankPanel.DeleteProfile to a new "Delete" button in mosstank.xml, next to "Clear profile!". Create/Select/the name field/the mine-only toggle already bind to the directory-backed store from steps 1-4; this closes the one missing verb. Mutation shown to fail: Delete short-circuited to always refuse made DeleteProfileRemovesTheRealFileAndFallsBackToByCharacter fail (selection stayed on the named file instead of falling back); restored, it passes along with the By-char refusal companion test. The markup contract's interactive-control count was updated for the new button (190 -> 191). 595 MossTank tests passing (was 593). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e5c4912b6d
commit
8a146aa8c4
5 changed files with 79 additions and 1 deletions
|
|
@ -850,6 +850,7 @@ internal sealed partial class MossTankPanel
|
|||
public Action CreateProfile => () => CreateProfileCore(copyCurrent: false);
|
||||
public Action CopyProfile => () => CreateProfileCore(copyCurrent: true);
|
||||
public Action ClearProfile => ClearProfileCore;
|
||||
public Action DeleteProfile => DeleteProfileCore;
|
||||
public Action ToggleMineOnly => () =>
|
||||
{
|
||||
string before = _profiles.Selected;
|
||||
|
|
@ -3465,6 +3466,17 @@ internal sealed partial class MossTankPanel
|
|||
ResetProfileConsumers();
|
||||
}
|
||||
|
||||
private void DeleteProfileCore()
|
||||
{
|
||||
if (!_profiles.Delete(out string notice))
|
||||
{
|
||||
_profileLifecycleNotice = notice;
|
||||
return;
|
||||
}
|
||||
LoadSelectedProfile();
|
||||
_profileLifecycleNotice = notice;
|
||||
}
|
||||
|
||||
private void LoadSelectedProfile()
|
||||
{
|
||||
_profiles.LoadCurrent(_allSettings, _noBuffItemNames);
|
||||
|
|
|
|||
|
|
@ -217,6 +217,33 @@ internal sealed class MossTankProfileStore
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the currently selected named profile's <c>.usd</c> file and
|
||||
/// its side-car, then falls back to <see cref="ByCharacter"/>. Refuses
|
||||
/// for <see cref="ByCharacter"/> itself — there is no file to delete,
|
||||
/// only a reset (see <see cref="ClearCurrent"/>).
|
||||
/// </summary>
|
||||
public bool Delete(out string notice)
|
||||
{
|
||||
if (_selected.Equals(ByCharacter, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
notice = "'By char' is the built-in character profile and cannot be deleted.";
|
||||
return false;
|
||||
}
|
||||
string fileName = _selected;
|
||||
if (VtankStorage.IsAvailable)
|
||||
VtankStorage.Delete(fileName);
|
||||
if (_host.Storage.IsAvailable)
|
||||
_host.Storage.Delete(SideCarKey(fileName));
|
||||
_selected = ByCharacter;
|
||||
_pendingLegacyBareName = null;
|
||||
_currentDatabase = null;
|
||||
_currentDatabaseFileName = null;
|
||||
WriteBinding();
|
||||
notice = $"Deleted profile {fileName}.";
|
||||
return true;
|
||||
}
|
||||
|
||||
public void LoadCurrent(
|
||||
VtankSettingsProfileSerializer.AllSettings settings,
|
||||
ISet<string> noBuffItemNames)
|
||||
|
|
|
|||
|
|
@ -136,6 +136,9 @@
|
|||
onclick="{CreateProfile}" />
|
||||
<button x="448" y="36" w="112" h="23" text="Clear profile!"
|
||||
onclick="{ClearProfile}" />
|
||||
<button x="568" y="36" w="76" h="23" text="Delete"
|
||||
onclick="{DeleteProfile}"
|
||||
tooltip="Delete the selected named profile's real .usd file. Not available for By char." />
|
||||
|
||||
<toggle x="580" y="4" w="120" h="20" text="Mine only"
|
||||
checked="{MineOnlyEnabled}" onclick="{ToggleMineOnly}" />
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public sealed class MossTankMarkupContractTests
|
|||
XElement[] controls = root.Descendants()
|
||||
.Where(element => interactive.Contains(element.Name.LocalName))
|
||||
.ToArray();
|
||||
Assert.Equal(190, controls.Length);
|
||||
Assert.Equal(191, controls.Length);
|
||||
|
||||
foreach (XElement control in controls)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -846,6 +846,42 @@ public sealed class MossTankPanelTests
|
|||
Assert.Equal(0.88f, panel.NormalHealthValue, precision: 2);
|
||||
}
|
||||
|
||||
// Campaign VT slice 1 Part A round 2 step 5: the Profiles tab's new
|
||||
// Delete button (mosstank.xml), bound through DeleteProfile exactly
|
||||
// like every other markup action.
|
||||
[Fact]
|
||||
public void DeleteProfileRemovesTheRealFileAndFallsBackToByCharacter()
|
||||
{
|
||||
var storage = new MemoryStorage();
|
||||
var automation = new FakeAutomation { Name = "Moss Wart" };
|
||||
var panel = new MossTankPanel(new FakeHost(automation, storage));
|
||||
panel.SetNormalHealth(0.42f);
|
||||
panel.SetProfileNameDraft("Fellowship");
|
||||
panel.CopyProfile();
|
||||
const string fellowshipFile = "--Moss Wart__Fellowship.usd";
|
||||
Assert.Equal(fellowshipFile, panel.SelectedMacroProfile);
|
||||
Assert.Contains(fellowshipFile, panel.MacroProfileNames);
|
||||
|
||||
panel.DeleteProfile();
|
||||
|
||||
Assert.Equal(MossTankProfileStore.ByCharacter, panel.SelectedMacroProfile);
|
||||
Assert.DoesNotContain(fellowshipFile, panel.MacroProfileNames);
|
||||
Assert.False(storage.Text.ContainsKey(fellowshipFile));
|
||||
Assert.Contains("Deleted", panel.ProfileLifecycleNotice, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteProfileRefusesToRemoveByCharacter()
|
||||
{
|
||||
var storage = new MemoryStorage();
|
||||
var panel = new MossTankPanel(new FakeHost(new FakeAutomation(), storage));
|
||||
|
||||
panel.DeleteProfile();
|
||||
|
||||
Assert.Equal(MossTankProfileStore.ByCharacter, panel.SelectedMacroProfile);
|
||||
Assert.Contains("cannot be deleted", panel.ProfileLifecycleNotice, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
// Item J (slice-1 fix round): renamed from
|
||||
// NavCommandsImportAndExportExactVtankNavFiles — .af is a real writer
|
||||
// output now (item E's header/fold-marker emission), not a byte-exact
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue