mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 15:27:21 +00:00
Improved the dialogs for local data sources and the Batch Processing assistant (#1096)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
149395c1a0
commit
f7e6ceb915
45 files changed
+2514
-361
No files matched your search
@@ -0,0 +1,201 @@
|
||||
using AIStudio.Components;
|
||||
|
||||
namespace AIStudio.Tests.Components;
|
||||
|
||||
public sealed class ConfigurationFileExclusionsTests
|
||||
{
|
||||
[Test]
|
||||
public async Task AParentRefreshDoesNotReplaceAnUncommittedDraft()
|
||||
{
|
||||
var component = new TestableConfigurationFileExclusions();
|
||||
try
|
||||
{
|
||||
await component.RefreshAsync();
|
||||
component.Edit("*.new");
|
||||
await component.RefreshAsync();
|
||||
|
||||
Assert.That(component.Draft, Is.EqualTo("*.new"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
component.ReleaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AnExternalSettingsChangeReplacesTheDraft()
|
||||
{
|
||||
var component = new TestableConfigurationFileExclusions();
|
||||
try
|
||||
{
|
||||
await component.RefreshAsync();
|
||||
component.Edit("*.new");
|
||||
component.StoredText = "*.external";
|
||||
await component.RefreshAsync();
|
||||
|
||||
Assert.That(component.Draft, Is.EqualTo("*.external"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
component.ReleaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
public async Task DisabledOrLockedFieldsRejectEdits(bool disabled, bool locked)
|
||||
{
|
||||
var component = new TestableConfigurationFileExclusions();
|
||||
try
|
||||
{
|
||||
await component.RefreshAsync();
|
||||
component.DisableField = disabled;
|
||||
component.LockField = locked;
|
||||
component.Edit("*.new");
|
||||
|
||||
Assert.That(component.Draft, Is.EqualTo("*.original"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
component.ReleaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
public async Task BecomingDisabledOrLockedBeforeAutosaveRejectsThePendingWrite(bool disabled, bool locked)
|
||||
{
|
||||
var component = new TestableConfigurationFileExclusions();
|
||||
try
|
||||
{
|
||||
await component.RefreshAsync();
|
||||
component.Edit("*.new");
|
||||
component.DisableField = disabled;
|
||||
component.LockField = locked;
|
||||
await component.FlushAsync();
|
||||
|
||||
Assert.That(component.StoredText, Is.EqualTo("*.original"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
component.ReleaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LockingAFieldReplacesItsDraftWithTheStoredSetting()
|
||||
{
|
||||
var component = new TestableConfigurationFileExclusions();
|
||||
try
|
||||
{
|
||||
await component.RefreshAsync();
|
||||
component.Edit("*.new");
|
||||
component.LockField = true;
|
||||
await component.RefreshAsync();
|
||||
|
||||
Assert.That(component.Draft, Is.EqualTo("*.original"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
component.ReleaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InvalidRulesAreNotPersisted()
|
||||
{
|
||||
var component = new TestableConfigurationFileExclusions();
|
||||
try
|
||||
{
|
||||
await component.RefreshAsync();
|
||||
component.RejectEdits();
|
||||
component.Edit(":text:");
|
||||
await component.FlushAsync();
|
||||
|
||||
Assert.That(component.StoredText, Is.EqualTo("*.original"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
component.ReleaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
public async Task DisabledOrLockedFieldsRejectExplicitReplacement(bool disabled, bool locked)
|
||||
{
|
||||
var component = new TestableConfigurationFileExclusions();
|
||||
try
|
||||
{
|
||||
await component.RefreshAsync();
|
||||
component.DisableField = disabled;
|
||||
component.LockField = locked;
|
||||
await component.ReplaceAsync("bin/;*.tmp");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(component.Draft, Is.EqualTo("*.original"));
|
||||
Assert.That(component.StoredText, Is.EqualTo("*.original"));
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
component.ReleaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExplicitReplacementKeepsAnInvalidOtherGroupsDraftWithoutPersistingIt()
|
||||
{
|
||||
var component = new TestableConfigurationFileExclusions();
|
||||
try
|
||||
{
|
||||
await component.RefreshAsync();
|
||||
component.RejectEdits();
|
||||
component.Edit("old/;:text:");
|
||||
await component.ReplaceAsync("bin/;:text:");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(component.Draft, Is.EqualTo("bin/;:text:"));
|
||||
Assert.That(component.StoredText, Is.EqualTo("*.original"));
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
component.ReleaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestableConfigurationFileExclusions : ConfigurationFileExclusions
|
||||
{
|
||||
public string StoredText { get; set; } = "*.original";
|
||||
|
||||
public bool DisableField { get; set; }
|
||||
|
||||
public bool LockField { get; set; }
|
||||
|
||||
public string Draft => this.EditingText;
|
||||
|
||||
public TestableConfigurationFileExclusions()
|
||||
{
|
||||
this.Text = () => this.StoredText;
|
||||
this.TextUpdate = text => this.StoredText = text;
|
||||
this.Disabled = () => this.DisableField;
|
||||
this.IsLocked = () => this.LockField;
|
||||
}
|
||||
|
||||
public void Edit(string text) => this.InternalUpdate(text);
|
||||
|
||||
public void RejectEdits() => this.Validation = _ => "Invalid rule";
|
||||
|
||||
public Task RefreshAsync() => this.OnParametersSetAsync();
|
||||
|
||||
public Task FlushAsync() => this.OptionChanged(this.EditingText);
|
||||
|
||||
public Task ReplaceAsync(string text) => this.ApplyTextAsync(text);
|
||||
|
||||
// These state-only tests never register with a message bus or render the component.
|
||||
public void ReleaseResources() => this.DisposeResources();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Tests.Components;
|
||||
|
||||
public sealed class FileExclusionEditorStateTests
|
||||
{
|
||||
[TestCase("")]
|
||||
[TestCase(FileExclusions.DEFAULT_PATTERNS)]
|
||||
[TestCase(" node_modules/ ; bin/\n*.min.js;draft-* ")]
|
||||
public void OpeningTheEditorDoesNotRewriteTheStoredText(string text)
|
||||
{
|
||||
var state = new FileExclusionEditorState();
|
||||
state.Receive(text);
|
||||
|
||||
Assert.That(state.Text, Is.EqualTo(text));
|
||||
}
|
||||
|
||||
[TestCase("node_modules/;bin/;*.min.js;draft-*")]
|
||||
[TestCase("node_modules/\nbin/\n*.min.js\ndraft-*")]
|
||||
[TestCase("node_modules/\r\nbin/\r\n*.min.js\r\ndraft-*")]
|
||||
[TestCase("node_modules/; bin/\r\n*.min.js; draft-*")]
|
||||
public void BothGroupsDisplaySemicolonSeparatedPatternsWithoutRewritingStoredLists(string text)
|
||||
{
|
||||
var state = Create(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.FolderText, Is.EqualTo("node_modules/; bin/"));
|
||||
Assert.That(state.FileText, Is.EqualTo("*.min.js; draft-*"));
|
||||
Assert.That(state.Text, Is.EqualTo(text));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EditingFoldersKeepsEveryFilePattern()
|
||||
{
|
||||
var state = Create("bin/;*.min.js;draft-*");
|
||||
state.EditFolders("obj/\nbuild/");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "obj/", "build/", "*.min.js", "draft-*" }));
|
||||
Assert.That(state.FileText, Is.EqualTo("*.min.js; draft-*"));
|
||||
Assert.That(state.Text, Is.EqualTo("obj/; build/; *.min.js; draft-*"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EditingFilesKeepsEveryFolderPattern()
|
||||
{
|
||||
var state = Create("bin/;node_modules/;*.min.js");
|
||||
state.EditFiles("*.wav;*.md");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "bin/", "node_modules/", "*.wav", "*.md" }));
|
||||
Assert.That(state.FolderText, Is.EqualTo("bin/; node_modules/"));
|
||||
Assert.That(state.Text, Is.EqualTo("bin/; node_modules/; *.wav; *.md"));
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase("")]
|
||||
[TestCase(" ")]
|
||||
public void ClearingOneGroupKeepsTheOtherGroup(string emptyText)
|
||||
{
|
||||
var state = Create("bin/;*.min.js");
|
||||
state.EditFolders(emptyText);
|
||||
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "*.min.js" }));
|
||||
|
||||
state = Create("bin/;*.min.js");
|
||||
state.EditFiles(emptyText);
|
||||
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "bin/" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExplicitFolderRulesKeepTheirExistingMatchingRules()
|
||||
{
|
||||
var state = Create(string.Empty);
|
||||
state.EditFolders("node_modules/;cmake-build-*/");
|
||||
var patterns = FileExclusions.Parse(state.Text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(patterns, Is.EqualTo(new[] { "node_modules/", "cmake-build-*/" }));
|
||||
Assert.That(FileExclusions.IsValid(state.Text), Is.True);
|
||||
Assert.That(state.IsValid, Is.True);
|
||||
Assert.That(FileExclusions.ExcludesFolder("node_modules", patterns), Is.True);
|
||||
Assert.That(FileExclusions.ExcludesFolder("cmake-build-debug", patterns), Is.True);
|
||||
Assert.That(FileExclusions.ExcludesFile("node_modules", patterns), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(":text:")]
|
||||
[TestCase("src/bin/")]
|
||||
[TestCase("/")]
|
||||
[TestCase("**/*.md")]
|
||||
public void EditingTheOtherGroupNeverDropsAnInvalidStoredRule(string invalidRule)
|
||||
{
|
||||
var state = Create(invalidRule);
|
||||
if (invalidRule.EndsWith('/'))
|
||||
state.EditFiles("*.tmp");
|
||||
else
|
||||
state.EditFolders("bin/");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(invalidRule));
|
||||
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(":text:")]
|
||||
[TestCase(":documents:")]
|
||||
public void FolderEditingDoesNotTurnAnInvalidSelectorIntoAValidLiteralFolder(string selector)
|
||||
{
|
||||
var state = Create(string.Empty);
|
||||
state.EditFolders(selector);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.Text, Is.EqualTo(selector));
|
||||
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExistingFolderNamesWhichResembleSelectorsArePreservedExactly()
|
||||
{
|
||||
var state = Create(":text:/;*.tmp");
|
||||
state.EditFiles("*.bak");
|
||||
|
||||
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { ":text:/", "*.bak" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RestoringOneGroupDeduplicatesRulesWithoutLosingOtherRules()
|
||||
{
|
||||
var state = Create("custom/;*.TMP;*.tmp;Caddyfile");
|
||||
state.RestoreFolders("node_modules/;NODE_MODULES/;*.default");
|
||||
|
||||
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "node_modules/", "*.TMP", "Caddyfile" }));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void RestoringOneFieldPreservesTheOtherFieldsDraft(bool folders)
|
||||
{
|
||||
var state = Create("custom/;*.custom");
|
||||
state.EditFolders("latest/; ");
|
||||
state.EditFiles("*.latest; ");
|
||||
|
||||
const string DEFAULTS = "bin/;node_modules/;*.tmp;*.bak";
|
||||
if (folders)
|
||||
state.RestoreFolders(DEFAULTS);
|
||||
else
|
||||
state.RestoreFiles(DEFAULTS);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.FolderText, Is.EqualTo(folders ? "bin/; node_modules/" : "latest/; "));
|
||||
Assert.That(state.FileText, Is.EqualTo(folders ? "*.latest; " : "*.tmp; *.bak"));
|
||||
Assert.That(state.Text, Is.EqualTo(folders ? "bin/; node_modules/; *.latest" : "latest/; *.tmp; *.bak"));
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void EmptyDefaultsClearOnlyTheRestoredField(bool folders)
|
||||
{
|
||||
var state = Create("custom/;*.custom");
|
||||
if (folders)
|
||||
state.RestoreFolders(string.Empty);
|
||||
else
|
||||
state.RestoreFiles(string.Empty);
|
||||
|
||||
Assert.That(state.Text, Is.EqualTo(folders ? "*.custom" : "custom/"));
|
||||
}
|
||||
|
||||
[TestCase(true, ":text:")]
|
||||
[TestCase(false, "src/bin/")]
|
||||
public void RestoringOneFieldPreservesInvalidRulesInTheOtherField(bool folders, string invalidRule)
|
||||
{
|
||||
var state = Create(invalidRule);
|
||||
if (folders)
|
||||
state.RestoreFolders(FileExclusions.DEFAULT_PATTERNS);
|
||||
else
|
||||
state.RestoreFiles(FileExclusions.DEFAULT_PATTERNS);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(invalidRule));
|
||||
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EditingTheFileFieldDoesNotSilentlyMoveFolderRules()
|
||||
{
|
||||
var state = Create("bin/;*.min.js");
|
||||
state.EditFiles("obj/;*.wav;draft-*");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.IsValid, Is.False);
|
||||
Assert.That(state.Text, Is.EqualTo("bin/; obj/; *.wav; draft-*"));
|
||||
Assert.That(state.FolderText, Is.EqualTo("bin/"));
|
||||
Assert.That(state.FileText, Is.EqualTo("obj/;*.wav;draft-*"));
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void EditingBothVisibleFieldsPreservesBothDraftsAndAllRules(bool foldersFirst)
|
||||
{
|
||||
var state = Create("bin/;*.tmp");
|
||||
string earlierValue;
|
||||
if (foldersFirst)
|
||||
{
|
||||
state.EditFolders("obj/; build/; ");
|
||||
earlierValue = state.Text;
|
||||
state.EditFiles("*.wav; draft-*; ");
|
||||
}
|
||||
else
|
||||
{
|
||||
state.EditFiles("*.wav; draft-*; ");
|
||||
earlierValue = state.Text;
|
||||
state.EditFolders("obj/; build/; ");
|
||||
}
|
||||
|
||||
state.Receive(earlierValue);
|
||||
state.Receive(state.Text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.FolderText, Is.EqualTo("obj/; build/; "));
|
||||
Assert.That(state.FileText, Is.EqualTo("*.wav; draft-*; "));
|
||||
Assert.That(state.Text, Is.EqualTo("obj/; build/; *.wav; draft-*"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ADebouncedEchoDoesNotOverwriteANewerDraft()
|
||||
{
|
||||
var state = Create("*.original");
|
||||
state.EditFiles("*.first");
|
||||
var earlierValue = state.Text;
|
||||
state.EditFiles("*.latest");
|
||||
state.Receive(earlierValue);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.Text, Is.EqualTo("*.latest"));
|
||||
Assert.That(state.FileText, Is.EqualTo("*.latest"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnUnchangedParentValueDoesNotDiscardAnUncommittedEdit()
|
||||
{
|
||||
var state = Create("*.original");
|
||||
state.EditFiles("*.latest; ");
|
||||
state.Receive("*.original");
|
||||
|
||||
Assert.That(state.FileText, Is.EqualTo("*.latest; "));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ANewExternalValueReplacesBothGroups()
|
||||
{
|
||||
var state = Create("bin/;*.tmp");
|
||||
state.EditFiles("*.latest");
|
||||
state.Receive("policy/;*.secret");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.FolderText, Is.EqualTo("policy/"));
|
||||
Assert.That(state.FileText, Is.EqualTo("*.secret"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ADisabledEditorShowsTheActualSettingEvenWhenItMatchesAnOlderDraft()
|
||||
{
|
||||
var state = Create("*.original");
|
||||
state.EditFiles("*.earlier");
|
||||
state.EditFiles("*.latest");
|
||||
state.Receive("*.earlier", discardDraft: true);
|
||||
|
||||
Assert.That(state.FileText, Is.EqualTo("*.earlier"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReformattingTheDefaultsDoesNotChangeTheSelectionSignature()
|
||||
{
|
||||
var state = Create(FileExclusions.DEFAULT_PATTERNS);
|
||||
state.EditFiles(state.FileText);
|
||||
var before = new FolderFileSelection(
|
||||
["*"], FileExclusions.Parse(FileExclusions.DEFAULT_PATTERNS), true, false,
|
||||
FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
|
||||
var after = new FolderFileSelection(
|
||||
["*"], FileExclusions.Parse(state.Text), true, false,
|
||||
FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
|
||||
|
||||
Assert.That(after.Describe(), Is.EqualTo(before.Describe()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RestoringDefaultsReplacesBothGroupsAndKeepsTheExactDefaultString()
|
||||
{
|
||||
var state = Create("custom/;*.custom");
|
||||
state.EditFolders(":text:");
|
||||
state.Reset(FileExclusions.DEFAULT_PATTERNS);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.Text, Is.EqualTo(FileExclusions.DEFAULT_PATTERNS));
|
||||
Assert.That(FileExclusions.IsValid(state.Text), Is.True);
|
||||
Assert.That(state.FolderText, Does.Contain("node_modules/"));
|
||||
Assert.That(state.FileText, Does.Contain("*.exe"));
|
||||
Assert.That(state.FolderText, Does.Not.Contain("\n").And.Not.Contain("\r"));
|
||||
Assert.That(state.FileText, Does.Not.Contain("\n").And.Not.Contain("\r"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EditingADefaultGroupPreservesAllRulesInTheOtherGroup()
|
||||
{
|
||||
var state = Create(FileExclusions.DEFAULT_PATTERNS);
|
||||
state.EditFiles(state.FileText + "\n*.scratch");
|
||||
|
||||
Assert.That(
|
||||
FileExclusions.Parse(state.Text),
|
||||
Is.EquivalentTo(FileExclusions.Parse(FileExclusions.DEFAULT_PATTERNS).Append("*.scratch")));
|
||||
}
|
||||
|
||||
[TestCase("*.pdf")]
|
||||
[TestCase("Dockerfile")]
|
||||
[TestCase("node_modules")]
|
||||
public void WrongFieldDraftsStayVisibleWithoutAddingAFolderSuffix(string pattern)
|
||||
{
|
||||
var state = Create("bin/;*.tmp");
|
||||
state.EditFolders(pattern);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.IsValid, Is.False);
|
||||
Assert.That(state.FolderText, Is.EqualTo(pattern));
|
||||
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(pattern));
|
||||
Assert.That(FileExclusions.Parse(state.Text), Does.Not.Contain(pattern + "/"));
|
||||
Assert.That(state.FileText, Is.EqualTo("*.tmp"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FolderRulesInTheFileFieldStayVisibleAndInvalidAfterAParentEcho()
|
||||
{
|
||||
var state = Create("bin/;*.tmp");
|
||||
state.EditFiles("*.bak");
|
||||
var earlierValue = state.Text;
|
||||
state.EditFiles("*.bak;obj/");
|
||||
state.Receive(earlierValue);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(state.IsValid, Is.False);
|
||||
Assert.That(state.FileText, Is.EqualTo("*.bak;obj/"));
|
||||
Assert.That(state.FolderText, Is.EqualTo("bin/"));
|
||||
});
|
||||
}
|
||||
|
||||
private static FileExclusionEditorState Create(string text)
|
||||
{
|
||||
var state = new FileExclusionEditorState();
|
||||
state.Receive(text);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
using AIStudio.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Tests.Components;
|
||||
|
||||
public sealed class FileExclusionEditorTests
|
||||
{
|
||||
[TestCase(true, "*.pdf")]
|
||||
[TestCase(true, "Dockerfile")]
|
||||
[TestCase(true, "node_modules")]
|
||||
[TestCase(false, "bin/")]
|
||||
[TestCase(false, "*.pdf;obj/")]
|
||||
[TestCase(true, ":text:")]
|
||||
[TestCase(false, ":documents:")]
|
||||
[TestCase(true, "src/bin/")]
|
||||
[TestCase(false, "**/*.tmp")]
|
||||
public async Task WrongFieldAndInvalidRulesAreNotPublished(bool folders, string patterns)
|
||||
{
|
||||
var published = new List<string>();
|
||||
var editor = Create(published);
|
||||
|
||||
await editor.EditAsync(patterns, folders);
|
||||
|
||||
Assert.That(published, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task EditingTheOtherFieldCannotPublishAnInvalidDraft()
|
||||
{
|
||||
var published = new List<string>();
|
||||
var editor = Create(published);
|
||||
|
||||
await editor.EditAsync("*.pdf", folders: true);
|
||||
await editor.EditAsync("*.bak", folders: false);
|
||||
Assert.That(published, Is.Empty);
|
||||
|
||||
await editor.EditAsync("build/", folders: true);
|
||||
Assert.That(published, Is.EqualTo(new[] { "build/; *.bak" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task MovingARuleToItsCorrectFieldPublishesItEvenIfTheCombinedTextDoesNotChange()
|
||||
{
|
||||
var published = new List<string>();
|
||||
var editor = Create(published, "bin/");
|
||||
|
||||
await editor.EditAsync("bin/;*.pdf", folders: true);
|
||||
await editor.EditAsync("*.pdf", folders: false);
|
||||
Assert.That(published, Is.Empty);
|
||||
|
||||
await editor.EditAsync("bin/", folders: true);
|
||||
Assert.That(published, Is.EqualTo(new[] { "bin/; *.pdf" }));
|
||||
}
|
||||
|
||||
[TestCase(true, true)]
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
[TestCase(false, false)]
|
||||
public async Task ResettingOneFieldCannotPersistInvalidRulesInTheOtherField(bool folders, bool explicitReset)
|
||||
{
|
||||
var published = new List<string>();
|
||||
var editor = Create(published);
|
||||
var resets = new List<string>();
|
||||
if (explicitReset)
|
||||
editor.SetParameters(new() { [nameof(FileExclusionEditor.ResetRequested)] = Collect(resets) });
|
||||
|
||||
await editor.EditAsync(folders ? "*.keep;wrong/" : "*.wrong", folders: !folders);
|
||||
await editor.ResetAsync(folders);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(published, Is.Empty);
|
||||
Assert.That(resets, Is.Empty);
|
||||
});
|
||||
|
||||
await editor.EditAsync(folders ? "*.keep" : "keep/", folders: !folders);
|
||||
Assert.That(published, Is.EqualTo(new[] { folders ? "bin/; *.keep" : "keep/; *.tmp" }));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public async Task ResettingTheInvalidFieldRestoresAValidValue(bool folders)
|
||||
{
|
||||
var published = new List<string>();
|
||||
var editor = Create(published);
|
||||
var resets = new List<string>();
|
||||
editor.SetParameters(new() { [nameof(FileExclusionEditor.ResetRequested)] = Collect(resets) });
|
||||
|
||||
await editor.EditAsync(folders ? "*.wrong" : "wrong/", folders);
|
||||
await editor.ResetAsync(folders);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(published, Is.Empty);
|
||||
Assert.That(resets, Is.EqualTo(new[] { folders ? "bin/; *.custom" : "custom/; *.tmp" }));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DisabledEditorRejectsEditsAndBothResetCallbacks()
|
||||
{
|
||||
var published = new List<string>();
|
||||
var resets = new List<string>();
|
||||
var editor = Create(published);
|
||||
editor.SetParameters(new()
|
||||
{
|
||||
[nameof(FileExclusionEditor.Disabled)] = true,
|
||||
[nameof(FileExclusionEditor.ResetRequested)] = Collect(resets),
|
||||
});
|
||||
|
||||
await editor.EditAsync("changed/", folders: true);
|
||||
await editor.EditAsync("*.changed", folders: false);
|
||||
await editor.ResetAsync(folders: true);
|
||||
await editor.ResetAsync(folders: false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(published, Is.Empty);
|
||||
Assert.That(resets, Is.Empty);
|
||||
});
|
||||
|
||||
editor.SetParameters(new() { [nameof(FileExclusionEditor.ResetRequested)] = default(EventCallback<string>) });
|
||||
await editor.ResetAsync(folders: true);
|
||||
await editor.ResetAsync(folders: false);
|
||||
Assert.That(published, Is.Empty);
|
||||
|
||||
// Returning to editing must reveal the unchanged source, not either attempted edit:
|
||||
editor.SetParameters(new() { [nameof(FileExclusionEditor.Disabled)] = false });
|
||||
await editor.EditAsync("*.other", folders: false);
|
||||
Assert.That(published, Is.EqualTo(new[] { "custom/; *.other" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AnInvalidDraftMakesTheEditorInvalidWhileTheBoundTextStaysValid()
|
||||
{
|
||||
var published = new List<string>();
|
||||
var editor = Create(published);
|
||||
Assert.That(editor.IsValid, Is.True);
|
||||
|
||||
await editor.EditAsync("*.wrong", folders: true);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(editor.IsValid, Is.False);
|
||||
Assert.That(published, Is.Empty);
|
||||
});
|
||||
|
||||
await editor.EditAsync("custom/", folders: true);
|
||||
Assert.That(editor.IsValid, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnInvalidReceivedListMakesTheEditorInvalid()
|
||||
{
|
||||
var editor = Create(new List<string>(), "**/*.tmp");
|
||||
|
||||
Assert.That(editor.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HiddenResetButtonsCannotChangeTheRules()
|
||||
{
|
||||
var published = new List<string>();
|
||||
var editor = Create(published);
|
||||
editor.SetParameters(new() { [nameof(FileExclusionEditor.ShowReset)] = false });
|
||||
|
||||
await editor.ResetAsync(folders: true);
|
||||
await editor.ResetAsync(folders: false);
|
||||
|
||||
Assert.That(published, Is.Empty);
|
||||
}
|
||||
|
||||
private static TestableFileExclusionEditor Create(List<string> published, string initial = "custom/;*.custom")
|
||||
{
|
||||
var editor = new TestableFileExclusionEditor();
|
||||
editor.SetParameters(new()
|
||||
{
|
||||
[nameof(FileExclusionEditor.Text)] = initial,
|
||||
[nameof(FileExclusionEditor.ResetValue)] = "bin/;*.tmp",
|
||||
[nameof(FileExclusionEditor.TextChanged)] = Collect(published),
|
||||
});
|
||||
return editor;
|
||||
}
|
||||
|
||||
private static EventCallback<string> Collect(List<string> values) => EventCallback.Factory.Create<string>(new object(), values.Add);
|
||||
|
||||
private sealed class TestableFileExclusionEditor : FileExclusionEditor
|
||||
{
|
||||
//
|
||||
// Sets the parameters the way a renderer would, which this test has none of, and then runs
|
||||
// the step of the lifecycle which reacts to them:
|
||||
//
|
||||
public void SetParameters(Dictionary<string, object?> parameters)
|
||||
{
|
||||
ParameterView.FromDictionary(parameters).SetParameterProperties(this);
|
||||
this.OnParametersSet();
|
||||
}
|
||||
|
||||
public Task EditAsync(string patterns, bool folders) => folders ? this.ChangeFoldersAsync(patterns) : this.ChangeFilesAsync(patterns);
|
||||
|
||||
public Task ResetAsync(bool folders) => this.RestoreDefaultsAsync(folders);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Tests.Components;
|
||||
|
||||
public sealed class ManagePandocDependencyTests
|
||||
{
|
||||
[Test]
|
||||
public void TheInstallationPromptStaysHiddenWhileAvailabilityIsChecked()
|
||||
{
|
||||
Assert.That(ManagePandocDependency.ShouldShowInstallationPrompt(null), Is.False);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void AnAvailableInstallationNeedsNoPrompt(bool isLocal)
|
||||
{
|
||||
var installation = new PandocInstallation(true, string.Empty, true, "3.7.0.2", isLocal);
|
||||
|
||||
Assert.That(ManagePandocDependency.ShouldShowInstallationPrompt(installation), Is.False);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void AnUnavailableOrOutdatedInstallationShowsThePrompt(bool checkWasSuccessful)
|
||||
{
|
||||
var installation = new PandocInstallation(checkWasSuccessful, "Unavailable", false, string.Empty, false);
|
||||
|
||||
Assert.That(ManagePandocDependency.ShouldShowInstallationPrompt(installation), Is.True);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user