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
7 changes: 5 additions & 2 deletions Tools/OutWit.Database.Studio.Tests/Themes/DesignTokenTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -513,10 +513,13 @@ public void TheCensusOfHardCodedValuesIsWhatItWasMeasuredToBeTest()
// to open at a fixed 480 for content needing about 900 and could not be resized, so
// the footer was painted over. A window's minimum size is a window size, which is
// the remainder this ledger exists to allow.
Assert.That(height, Is.EqualTo(68), "heights written by hand");
// 69, and the newest is the import preview's MaxHeight: the wizard shows the rows it
// parsed, and without a bound it would grow with the file. A box round a block of
// content is the other thing this ledger allows, beside window sizes.
Assert.That(height, Is.EqualTo(69), "heights written by hand");
Assert.That(HeightSiteKinds(), Is.EqualTo(new[]
{
"Border:23", "Button:1", "DataGrid:2", "Ellipse:1", "Grid:1", "GridSplitter:1",
"Border:24", "Button:1", "DataGrid:2", "Ellipse:1", "Grid:1", "GridSplitter:1",
"ListBox:1", "PathIcon:2", "ProgressBar:4", "ScrollViewer:3", "TextBox:1",
"Window:28",
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using System.Text.Json;
using System.Text.RegularExpressions;
using OutWit.Database.Studio.Tests.Helpers;
using OutWit.Database.Studio.ViewModels;

namespace OutWit.Database.Studio.Tests.ViewModels;

/// <summary>
/// Two exports, two names - and a progress panel that waits until it has a number.
/// </summary>
/// <remarks>
/// <para>
/// <b>Finding 5:</b> <i>Tools ▸ Export…</i> and <i>Export Results…</i> are not the same dialog - the
/// first exports a TABLE chosen from a list, the second exports the query result and carries the three
/// scopes with their row counts - and both were called <b>Export Data</b> in the title bar, under a
/// menu entry called <i>Export…</i>. Somebody looking in Tools for the scopes will not find them.
/// </para>
/// <para>
/// <b>Finding 32:</b> the first tick of a large export read <i>Exporting... 0 / 0 rows</i> at
/// <i>0.0 %</i>, because the panel opens when the export starts and the rows are fetched after that.
/// Zero out of zero is not a small number, it is no number - and it is the frame anybody
/// photographing an export in progress is most likely to catch.
/// </para>
/// <para>
/// <b>Finding 4:</b> the output path could only be set with <b>Browse…</b>, which opens the system
/// dialog on the user's own folders - awkward while a screen is being recorded, and slower than
/// typing a path somebody already knows.
/// </para>
/// </remarks>
[TestFixture]
public class TheExportDialogSaysWhichExportItIsTests
{
#region Finding 5

[TestCase("en")]
[TestCase("ru")]
public void TheTwoExportsHaveTwoNamesTest(string language)
{
using var catalogue = JsonDocument.Parse(Source($"Resources/Strings.{language}.json"));

var table = catalogue.RootElement.GetProperty("Dialog.Export.Title.Table").GetString();
var results = catalogue.RootElement.GetProperty("Dialog.Export.Title.Results").GetString();
var menu = catalogue.RootElement.GetProperty("Menu.Export").GetString();

Assert.Multiple(() =>
{
Assert.That(table, Is.Not.EqualTo(results),
"the two windows do not share a name, because they do not do the same thing");

Assert.That(menu, Is.Not.Null.And.Not.Empty);

Assert.That(menu!.Replace("_", string.Empty).TrimEnd('.', '…'),
Does.Contain(table!.Split(' ')[^1]),
"and the menu entry says which of the two it opens");
});
}

[Test]
public async Task TheWindowNamesItselfAfterWhatItIsExportingTest()
{
await using var studio = await StudioFixture.CreateAsync();

using var export = new ExportViewModel(studio.App);

var forTable = export.WindowTitle;

// The same window, handed a query result: this is the call the result grid makes.
using var data = new System.Data.DataTable();
data.Columns.Add("Id");

export.SetDataSource(data, "SELECT 1", null, 0);

Assert.That(export.WindowTitle, Is.Not.EqualTo(forTable),
"the same window used for the other job says so in its title");
}

#endregion

#region Finding 32

[Test]
public async Task NoNumberIsShownBeforeThereIsOneTest()
{
await using var studio = await StudioFixture.CreateAsync();

using var export = new ExportViewModel(studio.App);

Assert.That(export.KnowsHowManyRows, Is.False,
"nothing has been counted yet, so there is nothing to say");

var markup = Source("Views/Dialogs/ExportDialog.axaml");

var progress = Regex.Match(markup, @"<TextBlock[^>]*ExportProgressText[\s\S]*?/>");

Assert.That(progress.Success, Is.True);

Assert.That(progress.Value, Does.Contain("KnowsHowManyRows"),
"and the panel's numbers wait for the total rather than opening at 0 / 0");
}

#endregion

#region Finding 4

[Test]
public void TheOutputPathCanBeTypedTest()
{
var markup = Source("Views/Dialogs/ExportDialog.axaml");

var box = Regex.Match(markup, @"<TextBox[^>]*Text=""\{Binding OutputPath\}""[\s\S]*?/>");

Assert.That(box.Success, Is.True, "the dialog has a box for the path");

Assert.That(box.Value, Does.Not.Contain("IsReadOnly=\"True\""),
"a path can be typed as well as browsed for");
}

#endregion

#region Finding 28

[Test]
public void TheCollisionPolicyIsOnTheStepAboutTheTargetTest()
{
var markup = Source("Views/Dialogs/ImportDialog.axaml");

var block = Regex.Match(markup,
@"<StackPanel[^>]*Grid\.Row=""3""[\s\S]*?ImportConflictSkip");

Assert.That(block.Success, Is.True, "the collision policy is in the wizard");

Assert.That(block.Value, Does.Contain("IsDestination"),
"and on the step named after the thing it is about - it sat on «Columns», which left that "
+ "step holding two unrelated decisions");
}

#endregion

#region Tools

private static string Source(string relative)
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);

while (directory != null)
{
var candidate = Path.Combine(directory.FullName, "Tools", "OutWit.Database.Studio");

if (Directory.Exists(Path.Combine(candidate, "Views")))
{
var path = Path.Combine(candidate, relative.Replace('/', Path.DirectorySeparatorChar));

Assert.That(File.Exists(path), Is.True, $"{relative} must be where this fixture says");

return File.ReadAllText(path);
}

directory = directory.Parent;
}

throw new AssertionException("the Studio project was not found from " + AppContext.BaseDirectory);
}

#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using System.Text.RegularExpressions;
using OutWit.Database.Studio.Tests.Helpers;
using OutWit.Database.Studio.ViewModels;

namespace OutWit.Database.Studio.Tests.ViewModels;

/// <summary>
/// The import wizard reports the file it read, and keeps every row the database refused.
/// </summary>
/// <remarks>
/// <para>
/// <b>Finding 25:</b> step 1 said <i>File contains approximately 13 rows 0 columns</i> for a CSV
/// with four, and <b>Refresh Preview</b> changed nothing. The count was read from
/// <c>ColumnMappings</c>, which is built on step 3 - so on step 1 it was asking a list that does not
/// exist yet. Catching a wrong delimiter before anything is written is what a preview is for, and
/// "0 columns" is exactly what a wrong delimiter would look like if the number meant anything.
/// </para>
/// <para>
/// <b>Finding 27 was half built, which is why it was reported as absent.</b> The CSV path has kept
/// every rejection since WS-36 and the window has a button that writes them to a file beside the
/// source. <b>The JSON path threw away everything past the tenth</b> - it filled the display list and
/// recorded nothing - so an import of a JSON file really did report "15 failed", name ten, and have
/// nothing to save. Both paths record now.
/// </para>
/// <para>
/// <b>Finding 26:</b> the result was painted across the line it replaces because two borders share
/// one grid cell. A cell holds one thing.
/// </para>
/// </remarks>
[TestFixture]
public class TheImportWizardSaysWhatItSawTests
{
#region Fields

private StudioFixture m_studio = null!;
private ImportViewModel m_import = null!;
private string m_csv = null!;

#endregion

#region Setup

[SetUp]
public async Task SetUp()
{
m_studio = await StudioFixture.CreateAsync();

m_csv = Path.Combine(m_studio.Root, "people.csv");

await File.WriteAllTextAsync(m_csv,
"Id,Name,Email,City" + Environment.NewLine
+ "1,Ada,ada@example.com,London" + Environment.NewLine
+ "2,Grace,grace@example.com,New York" + Environment.NewLine);

m_import = new ImportViewModel(m_studio.App);

await m_import.InitializeAsync();
}

[TearDown]
public async Task TearDown()
{
m_import.Dispose();

await m_studio.DisposeAsync();
}

#endregion

#region Finding 25

[Test]
public async Task TheFirstStepCountsTheColumnsItReadTest()
{
m_import.InputPath = m_csv;

await StudioFixture.PressAsync(m_import.PreviewCommand);

Assert.Multiple(() =>
{
Assert.That(m_import.PreviewData, Is.Not.Null, "the file was parsed");

Assert.That(m_import.PreviewData!.Columns, Has.Count.EqualTo(4));

Assert.That(m_import.PreviewColumnsSummary, Does.Contain("4"),
"the line says four - it used to ask the mapping list, which is built two steps later");
});
}

[Test]
public async Task TheRowsItReadCanBeLookedAtTest()
{
m_import.InputPath = m_csv;

await StudioFixture.PressAsync(m_import.PreviewCommand);

Assert.That(m_import.PreviewData!.Rows, Has.Count.EqualTo(2),
"the parsed rows are there to be shown - a preview that shows nothing catches no delimiter");

Assert.That(Markup("Views/Dialogs/ImportDialog.axaml"), Does.Contain("ImportPreviewRowsGrid"),
"and the window draws them");
}

#endregion

#region Finding 26

[Test]
public void TheResultDoesNotLandOnTopOfThePreviewTest()
{
// Comments stripped first: this fixture is about the markup, and the comment explaining
// the fix mentions the cell it is about.
var markup = Regex.Replace(Markup("Views/Dialogs/ImportDialog.axaml"),
@"<!--[\s\S]*?-->", string.Empty);

Assert.That(Regex.Matches(markup, @"Grid\.Row=""5"""), Has.Count.EqualTo(1),
"one thing in the cell: two borders in the same one are drawn over each other, which is "
+ "how the result came to be painted across the line it replaces");
}

#endregion

#region Finding 27

/// <summary>
/// Both import paths keep every rejection, so the button that writes them out has them all.
/// </summary>
/// <remarks>
/// Asserted on the source, because reaching the JSON path here would mean importing into a table
/// whose rows the engine refuses one at a time - a scenario that measures the engine rather than
/// this wizard. What is checked is that neither path records ONLY the ten it shows.
/// </remarks>
[Test]
public void NeitherImportPathThrowsAwayARejectionTest()
{
var source = Source("ViewModels/ImportViewModel.cs");

var displayed = Regex.Matches(source, @"ImportErrors\.Add\(");
var recorded = Regex.Matches(source, @"Rejected\.Add\(");

Assert.Multiple(() =>
{
Assert.That(displayed, Has.Count.EqualTo(2), "CSV and JSON, one display list each");

Assert.That(recorded, Has.Count.EqualTo(2),
"and each of them records the rejection as well - the JSON path used to fill the "
+ "display list and keep nothing");
});
}

[Test]
public void TheButtonThatWritesThemOutFollowsTheRejectionsTest()
{
var markup = Markup("Views/Dialogs/ImportDialog.axaml");

var button = Regex.Match(markup, @"<Button[^>]*ImportWriteReport[\s\S]*?/>");

Assert.That(button.Success, Is.True, "the window offers to write them out");

Assert.Multiple(() =>
{
Assert.That(button.Value, Does.Contain("WriteReportCommand"));

Assert.That(button.Value, Does.Contain("IsVisible=\"{Binding Rejected.Count}\""),
"and it is offered exactly when there is something to write");
});
}

#endregion

#region Tools

private static string Markup(string relative) => Source(relative);

private static string Source(string relative)
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);

while (directory != null)
{
var candidate = Path.Combine(directory.FullName, "Tools", "OutWit.Database.Studio");

if (Directory.Exists(Path.Combine(candidate, "Views")))
{
var path = Path.Combine(candidate, relative.Replace('/', Path.DirectorySeparatorChar));

Assert.That(File.Exists(path), Is.True, $"{relative} must be where this fixture says");

return File.ReadAllText(path);
}

directory = directory.Parent;
}

throw new AssertionException("the Studio project was not found from " + AppContext.BaseDirectory);
}

#endregion
}
4 changes: 3 additions & 1 deletion Tools/OutWit.Database.Studio/Resources/Strings.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"Menu.SaveQueryAs": "Save Query _As…",
"Menu.DumpDatabase": "_Dump Database…",
"Menu.Import": "_Import…",
"Menu.Export": "_Export…",
"Menu.Export": "_Export table…",
"Menu.Settings": "_Settings…",
"Menu.Exit": "E_xit",
"Menu.Copy": "_Copy",
Expand Down Expand Up @@ -349,6 +349,8 @@
"Dialog.CreateIndex.CreateName": "Create index",
"Dialog.CreateIndex.Creating": "Creating index...",
"Dialog.Export.Title": "Export Data",
"Dialog.Export.Title.Table": "Export table",
"Dialog.Export.Title.Results": "Export query results",
"Dialog.Export.Source": "Source",
"Dialog.Export.QueryResult": "Query Result: {0} ({1} rows)",
"Dialog.Export.SelectTable": "Select table to export...",
Expand Down
Loading
Loading