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
21 changes: 19 additions & 2 deletions docs/DEFECT-BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ This is the current ledger for defects and review findings in EncodingChecker.
It is organised by status, not discovery date, so the open work is visible in
one place. Longer evidence and history follow the ledger.

<!-- backlog-counts total=84 fixed=74 open=6 not-reproduced=1 withdrawn=1 intentional-behavior=1 decision=1 -->
<!-- backlog-counts total=85 fixed=75 open=6 not-reproduced=1 withdrawn=1 intentional-behavior=1 decision=1 -->

**Derived count: 84 findings — 74 fixed, 6 open, 1 not reproduced, 1 withdrawn,
**Derived count: 85 findings — 75 fixed, 6 open, 1 not reproduced, 1 withdrawn,
1 intentional behavior, and 1 design decision.** Recompute and check these
figures with:

Expand Down Expand Up @@ -91,6 +91,7 @@ the 2026-09-08 reformat to findings that previously had only a sentence.
| BL-38 | `-Validate utf-8` rejected ASCII files that conversion treats as already UTF-8 | Fixed | — | — | [BL-38](#bl-38) |
| BL-39 | A source chosen in a cancelled GUI review carried into the next review | Fixed | — | — | [BL-39](#bl-39) |
| BL-40 | The GUI's Validate status read the same when every file passed and when none was checked | Fixed | — | — | [BL-40](#bl-40) |
| BL-41 | GUI rows kept an earlier run's icon and never showed why a file failed | Fixed | — | — | [BL-41](#bl-41) |
| EC-32 | A cancellation timeout could hide repeated refusals to press Cancel | Fixed | — | — | [EC-32](#ec-32) |
| EC-15 | Plans and journals showed fixed safety flags as if they recorded checks | Fixed | — | — | [EC-15](#ec-15) |
| BL-18 | BOM-less UTF-16 could be detected and converted as UTF-32 | Fixed | — | — | [BL-18](#bl-18) |
Expand Down Expand Up @@ -1441,6 +1442,22 @@ mutations to the counting fail them. The two lines that connect the count to the
window are checked by reading, not by a test, and the GUI smoke suite does not
run Validate.

### BL-41

**GUI rows now show this run's state and reason.** Rows survive between runs. A
file that failed or was previewed as "would convert" in one run and was then left
unchanged or skipped in the next kept the earlier run's icon, so the list described
a run that was over. The reason a file failed, was refused or was left unchanged
went only to the debug output, so the window never said why; the CSV export was the
only place to read it. Found by the 2026-09-20 review from the source.

A row left unchanged or skipped now has its icon cleared. Every row shows its
current reason as a tooltip, set on each run and emptied when the run gives none,
for example "ASCII is already valid UTF-8 without a BOM." on a plain-English file
converted to UTF-8, or a validation failure's reason. No column was added. Tests
cover cleared icons after a failed and a previewed run, the tooltip following each
result, and the window showing a scanned row's reason; four mutations fail them.

## Decisions and mistakes that must remain visible

### A known defect shipped after being reported closed
Expand Down
71 changes: 71 additions & 0 deletions sources/EncodingChecker.Tests/PreviewResultPresentationTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;

namespace EncodingChecker.Tests;
Expand All @@ -20,6 +22,8 @@ public sealed class PreviewResultPresentationTests
// form-less test can only address these images by index.
private const int SuccessIcon = 0;
private const int FailedIcon = 1;
private const int WouldChangeIcon = 2;
private const int NoIcon = -1;

private static ListViewItem Row(string charset) =>
new([charset, "f.txt", ".txt", @"C:\dir"]);
Expand Down Expand Up @@ -158,4 +162,71 @@ public void TheWouldChangeIconIndexActuallyExistsInTheResultsImageList()
imageList.Images.Count >= 3,
$"imgsResults must contain at least 3 images; found {imageList.Images.Count}.");
}

// Rows survive between runs, so an icon or reason from an earlier run must not stay on a
// row whose file this run left alone.
[Theory]
[InlineData(nameof(ConversionRowResult.Unchanged), FailedIcon)]
[InlineData(nameof(ConversionRowResult.Unchanged), WouldChangeIcon)]
[InlineData(nameof(ConversionRowResult.Skipped), FailedIcon)]
public void ARowThisRunLeftAloneLosesAnEarlierRunsIcon(string resultName, int earlierIcon)
{
var result = Enum.Parse<ConversionRowResult>(resultName);
ListViewItem item = Row("us-ascii");
item.ImageIndex = earlierIcon;
item.ToolTipText = "an earlier run's failure";

MainForm.UpdateResultItem(item, Entry(result), "utf-8", wasPreview: false);

Assert.Equal(NoIcon, item.ImageIndex);
Assert.Equal(string.Empty, item.ToolTipText);
}

[Fact]
public void TheRowTooltipShowsThisRunsReason()
{
ListViewItem item = Row("us-ascii");

ConversionReportEntry unchanged = Entry(ConversionRowResult.Unchanged);
unchanged.Diagnostic = "ASCII is already valid UTF-8 without a BOM.";
MainForm.UpdateResultItem(item, unchanged, "utf-8", wasPreview: false);
Assert.Equal("ASCII is already valid UTF-8 without a BOM.", item.ToolTipText);

ConversionReportEntry failed = Entry(ConversionRowResult.Error);
failed.Diagnostic = "SourceChangedDuringConversion: the file changed.";
MainForm.UpdateResultItem(item, failed, "utf-8", wasPreview: false);
Assert.Equal("SourceChangedDuringConversion: the file changed.", item.ToolTipText);
Assert.Equal(FailedIcon, item.ImageIndex);

// A later success carries no reason, so the failure text goes with it.
MainForm.UpdateResultItem(item, Entry(ConversionRowResult.Converted), "utf-8", wasPreview: false);
Assert.Equal(string.Empty, item.ToolTipText);
Assert.Equal(SuccessIcon, item.ImageIndex);
}

[Fact]
public void TheWindowShowsEachScannedRowsReasonAsATooltip()
{
UiTest.OnStaThread(() =>
{
using var form = new MainForm();

var list = (ListView)typeof(MainForm)
.GetField("lstResults", BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(form)!;

Assert.True(list.ShowItemToolTips);

ConversionReportEntry invalid = Entry(ConversionRowResult.Invalid);
invalid.Diagnostic = "The file is us-ascii, which is not in the allowed list.";

typeof(MainForm)
.GetMethod("ActionWorkerProgressChanged", BindingFlags.Instance | BindingFlags.NonPublic)!
.Invoke(form, [form, new ProgressChangedEventArgs(0, invalid)]);

ListViewItem row = Assert.Single(list.Items.Cast<ListViewItem>());
Assert.Equal(invalid.Diagnostic, row.ToolTipText);
Assert.Equal(NoIcon, row.ImageIndex);
});
}
}
1 change: 1 addition & 0 deletions sources/EncodingChecker/MainForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 11 additions & 5 deletions sources/EncodingChecker/MainForm.Execution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,12 @@ private void ActionWorkerProgressChanged(
Path.GetExtension(entry.FilePath),
Path.GetDirectoryName(entry.FilePath) ?? string.Empty
],
-1)
ResultIconNone)
{
Tag = entry,

// The reason a scan gave, such as why a file failed validation.
ToolTipText = entry.Diagnostic ?? string.Empty,
};

lstResults.Items.Add(resultItem);
Expand Down Expand Up @@ -641,6 +644,9 @@ internal static void UpdateResultItem(
// Keep the row tied to the entry that produced its current state.
item.Tag = entry;

// This run's reason, so an earlier run's failure text does not stay on the row.
item.ToolTipText = entry.Diagnostic ?? string.Empty;

if (entry.Result == ConversionRowResult.Converted)
{
// Preview leaves the file unchanged, so only the icon changes.
Expand All @@ -662,12 +668,12 @@ internal static void UpdateResultItem(
Debug.WriteLine(
$"Conversion failed for {entry.FilePath}: {entry.Diagnostic}");
}
else if (entry.Result == ConversionRowResult.Skipped)
else
{
Debug.WriteLine(
$"Conversion skipped for {entry.FilePath}: encoding could not be determined.");
// Unchanged or skipped: nothing to mark. Clearing the icon stops an earlier
// run's failure or "would convert" mark describing this run.
item.ImageIndex = ResultIconNone;
}
// Unchanged: already matches the target; leave the row unchanged.
}

#endregion
Expand Down
3 changes: 3 additions & 0 deletions sources/EncodingChecker/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ private enum CurrentAction
private const int ResultIconFailed = 1;
private const int ResultIconWouldChange = 2;

// A row with nothing to mark: already in the target, or left alone.
private const int ResultIconNone = -1;

private const int ResultsColumnCharset = 0;
private const int ResultsColumnFileName = 1;
private const int ResultsColumnFileExt = 2;
Expand Down
Loading