Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
24d556e
feat: convert Legend section from bullet list to table (MD + HTML)
claude Mar 21, 2026
6d73ad7
feat: convert Summary/IL Cache Stats to tables in MD, show borders in…
claude Mar 21, 2026
c6ac5b1
feat: convert file listing sections to tables in Markdown report
claude Mar 21, 2026
32800c5
fix: highlight InlineDiffMaxEditDistance with <code> tag in HTML report
claude Mar 21, 2026
517a7eb
style: darken diff-row background for better contrast with white tables
claude Mar 21, 2026
ccdf4d3
style: bold volume number in Algorithmica citation across all files
claude Mar 21, 2026
5a4dfa3
feat: add clipboard copy button to File Path column header in HTML
claude Mar 21, 2026
3332130
feat: add light purple hover highlight to table rows in HTML report
claude Mar 21, 2026
723bd5f
feat: add EN/JP language toggle to HTML report (Req9)
claude Mar 21, 2026
229e029
fix: move disassembler version from Legend to Disassembler column in …
claude Mar 21, 2026
ce77fd6
refactor: per-row clipboard button, legend table width, IL ignore str…
claude Mar 21, 2026
1f2350e
fix: hide input borders, center MD timestamps, show config values in …
claude Mar 21, 2026
f00cc8b
refactor: remove i18n toggle, add IL ignore table scroll, enrich warn…
claude Mar 21, 2026
a8eb952
fix: remove input borders with appearance reset, fix sample Parameter…
claude Mar 21, 2026
cf0bbca
fix: broaden input border reset to td input[type=text], fix Body colu…
claude Mar 21, 2026
fdfe122
fix: col-path flex border, diff hover, remove MD semantic changes, ex…
claude Mar 21, 2026
9f805cd
fix: align sample data between HTML and MD, add missing Modified File…
claude Mar 21, 2026
5e35684
style: indent diff-summary and diff-skipped to align with checkbox co…
claude Mar 21, 2026
66245b8
Rename .cs files to realistic extensions in sample reports
claude Mar 21, 2026
cdd8a70
Fix inline diff consistency between Modified Files and Warnings sections
claude Mar 21, 2026
6011f91
Rename Change column to Status with markers and colored backgrounds i…
claude Mar 21, 2026
a51d665
Fix semantic changes consistency and remove code emphasis from Status…
claude Mar 21, 2026
66d211d
Add Collapse all button to close all inline details without clearing …
claude Mar 21, 2026
7bada1d
Narrow legend table left column by excluding it from fixed table layout
claude Mar 21, 2026
5f7a8cd
Rename Collapse all button to Fold
claude Mar 21, 2026
c18d264
Rename Fold button to Fold all detail
claude Mar 21, 2026
715b85f
Rename button to Fold all details
claude Mar 21, 2026
0f128b0
Override Status cell background with light purple on row hover
claude Mar 21, 2026
d5aa532
Disable hover on stat/legend/il-ignore tables; color Summary rows
claude Mar 21, 2026
eda1769
Fix 6 failing pipeline tests; register AssemblySemanticChangesSection…
claude Mar 21, 2026
d4b4464
Fix hover test: match !important in semantic-changes-table hover rule
claude Mar 21, 2026
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
64 changes: 58 additions & 6 deletions CHANGELOG.md

Large diffs are not rendered by default.

142 changes: 133 additions & 9 deletions FolderDiffIL4DotNet.Tests/Services/HtmlReportGenerateServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,11 @@ public void GenerateDiffReportHtml_SummarySection_ContainsCorrectCounts()
computerName: "test-host", config);

var html = File.ReadAllText(Path.Combine(reportDir, HtmlReportGenerateService.DIFF_REPORT_HTML_FILE_NAME));
Assert.Contains("stat-label\">Unchanged</td><td class=\"stat-value\">1</td>", html);
Assert.Contains("stat-label\">Added</td><td class=\"stat-value\">1</td>", html);
Assert.Contains("stat-label\">Removed</td><td class=\"stat-value\">1</td>", html);
Assert.Contains("stat-label\">Modified</td><td class=\"stat-value\">1</td>", html);
Assert.Contains("stat-label\">Unchanged</td>", html);
Assert.Contains("stat-label\">Added</td>", html);
Assert.Contains("stat-label\">Removed</td>", html);
Assert.Contains("stat-label\">Modified</td>", html);
Assert.Contains("stat-value\">1</td>", html);
Assert.Contains("3 (Old) vs 3 (New)", html);
}

Expand Down Expand Up @@ -476,9 +477,10 @@ public void GenerateDiffReportHtml_InlineDiffSummary_UsesSameOneBasedNumberAsLef
var html = File.ReadAllText(Path.Combine(reportDir, HtmlReportGenerateService.DIFF_REPORT_HTML_FILE_NAME));
Assert.Contains("<td class=\"col-no\">1</td>", html);
Assert.Contains("<td class=\"col-no\">2</td>", html);
Assert.Contains("#1 Show diff", html);
Assert.Contains("#2 Show diff", html);
Assert.DoesNotContain("#0 Show diff", html);
Assert.Contains(">#1 ", html);
Assert.Contains(">#2 ", html);
Assert.DoesNotContain(">#0 ", html);
Assert.Contains("Show diff", html);
}

// Verify DIFF REASON / Location / Timestamp body cells have text-align: center, but Notes column does not
Expand All @@ -500,7 +502,7 @@ public void GenerateDiffReportHtml_BodyCells_ColReasonPathTs_HaveCenterAlignment
Assert.Contains("td.col-ts { white-space: nowrap; text-align: center; }", html);
Assert.Contains("min-width: 9em; text-align: center; }", html); // col-diff has text-align: center
// File Path is NOT center-aligned / File Path 列は中央揃えではない
Assert.Contains("td.col-path { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }", html);
Assert.Contains("td.col-path { white-space: nowrap; overflow: hidden; }", html);
// Notes column is NOT center-aligned / Notes 列は中央揃えではない
Assert.Contains("td.col-notes { overflow: hidden; }", html);
}
Expand Down Expand Up @@ -894,7 +896,7 @@ public void GenerateDiffReportHtml_AssemblySemanticChanges_KindAccessModifiersUs
Assert.Contains("<code>Method</code>", html); // Kind
Assert.Contains("<code>public</code>", html); // Access
Assert.Contains("<code>virtual</code>", html); // Modifiers
Assert.Contains("<code>Modified</code>", html); // Change
Assert.Contains("style=\"background:#e3f2fd\">[ * ]", html); // Status cell with blue bg (no code emphasis)
Assert.Contains("<code>Changed</code>", html); // Body
}

Expand Down Expand Up @@ -939,6 +941,128 @@ public void GenerateDiffReportHtml_AssemblySemanticChanges_TdHasWhiteBackground(
Assert.Contains("background: #fff", html);
}

// ── Req1: Legend table / 凡例テーブル ─────────────────────────────────

[Fact]
public void GenerateDiffReportHtml_LegendSection_UsesTableFormat()
{
var (oldDir, newDir, reportDir) = MakeDirs("legend-table");
var config = CreateConfig();

_service.GenerateDiffReportHtml(oldDir, newDir, reportDir,
appVersion: "1.0", elapsedTimeString: null,
computerName: "test-host", config);

var html = File.ReadAllText(Path.Combine(reportDir, HtmlReportGenerateService.DIFF_REPORT_HTML_FILE_NAME));
Assert.Contains("legend-table", html);
Assert.Contains("<table class=\"legend-table\">", html);
}

// ── Req2: stat-table borders / 統計テーブルボーダー ────────────────────

[Fact]
public void GenerateDiffReportHtml_StatTable_HasVisibleBorders()
{
var (oldDir, newDir, reportDir) = MakeDirs("stat-border");
var config = CreateConfig();

_service.GenerateDiffReportHtml(oldDir, newDir, reportDir,
appVersion: "1.0", elapsedTimeString: null,
computerName: "test-host", config);

var html = File.ReadAllText(Path.Combine(reportDir, HtmlReportGenerateService.DIFF_REPORT_HTML_FILE_NAME));
Assert.Contains("border: 1px solid #ddd", html);
}

// ── Req4: InlineDiffMaxEditDistance code tag / code タグ ──────────────

[Fact]
public void GenerateDiffReportHtml_EditDistanceSkipped_InlineDiffMaxEditDistanceHasCodeTag()
{
var (oldDir, newDir, reportDir) = MakeDirs("edit-dist-code-tag");

File.WriteAllLines(Path.Combine(oldDir, "huge.txt"), Enumerable.Range(1, 2001).Select(i => $"old{i}"));
File.WriteAllLines(Path.Combine(newDir, "huge.txt"), Enumerable.Range(1, 2001).Select(i => $"new{i}"));

_resultLists.AddModifiedFileRelativePath("huge.txt");
_resultLists.RecordDiffDetail("huge.txt", FileDiffResultLists.DiffDetailResult.TextMismatch);

var config = CreateConfig(enableInlineDiff: true);

_service.GenerateDiffReportHtml(oldDir, newDir, reportDir,
appVersion: "1.0", elapsedTimeString: null,
computerName: "test-host", config);

var html = File.ReadAllText(Path.Combine(reportDir, HtmlReportGenerateService.DIFF_REPORT_HTML_FILE_NAME));
Assert.Contains("<code>InlineDiffMaxEditDistance</code>", html);
Assert.Contains("current value:", html);
}

// ── Req5: diff-row background / 差分行背景色 ─────────────────────────

[Fact]
public void GenerateDiffReportHtml_DiffRowBackground_UsesDarkerColor()
{
var (oldDir, newDir, reportDir) = MakeDirs("diff-row-bg");
var config = CreateConfig();

_service.GenerateDiffReportHtml(oldDir, newDir, reportDir,
appVersion: "1.0", elapsedTimeString: null,
computerName: "test-host", config);

var html = File.ReadAllText(Path.Combine(reportDir, HtmlReportGenerateService.DIFF_REPORT_HTML_FILE_NAME));
// diff-row background should be #edf0f4, not the old #f6f8fa
Assert.Contains("tr.diff-row { background: #edf0f4; }", html);
Assert.DoesNotContain("tr.diff-row { background: #f6f8fa; }", html);
}

// ── Req7: Copy paths button / コピーボタン ──────────────────────────────

[Fact]
public void GenerateDiffReportHtml_FilePathRow_HasCopyButton()
{
var (oldDir, newDir, reportDir) = MakeDirs("copy-btn");

_resultLists.AddModifiedFileRelativePath("src/app.dll");
_resultLists.RecordDiffDetail("src/app.dll", FileDiffResultLists.DiffDetailResult.MD5Mismatch);

var config = CreateConfig();
_service.GenerateDiffReportHtml(oldDir, newDir, reportDir,
appVersion: "1.0", elapsedTimeString: null,
computerName: "test-host", config);

var html = File.ReadAllText(Path.Combine(reportDir, HtmlReportGenerateService.DIFF_REPORT_HTML_FILE_NAME));
Assert.Contains("btn-copy-path", html);
Assert.Contains("copyPath", html);
Assert.Contains("path-text", html);
}

// ── Req8: Row hover highlight / 行ホバーハイライト ──────────────────────

[Fact]
public void GenerateDiffReportHtml_RowHover_HasLightPurpleHighlight()
{
var (oldDir, newDir, reportDir) = MakeDirs("hover-highlight");
var config = CreateConfig();

_service.GenerateDiffReportHtml(oldDir, newDir, reportDir,
appVersion: "1.0", elapsedTimeString: null,
computerName: "test-host", config);

var html = File.ReadAllText(Path.Combine(reportDir, HtmlReportGenerateService.DIFF_REPORT_HTML_FILE_NAME));
Assert.Contains(":not(.stat-table):not(.legend-table):not(.il-ignore-table) > tbody tr:not(.diff-row):not(.diff-hunk-tr):not(.diff-del-tr):not(.diff-add-tr):hover { background: #f3eef8; }", html);
Assert.Contains("table.semantic-changes-table tbody tr:hover td { background: #f3eef8 !important; }", html);
}

// ── Req9: Language toggle / 言語切り替え ────────────────────────────────

[Fact]
public void I18n_ReturnsHtmlEncodedEnglishText()
{
string result = HtmlReportGenerateService.I18n("Hello", "こんにちは");
Assert.Equal("Hello", result);
}

private static ConfigSettings CreateConfig(bool enableInlineDiff = true, bool lazyRender = false) => new()
{
IgnoredExtensions = new List<string>(),
Expand Down
36 changes: 19 additions & 17 deletions FolderDiffIL4DotNet.Tests/Services/ReportGenerateServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@ public void GenerateDiffReport_HeaderShowsIlContainsIgnoreNote_WhenEnabled()

var reportPath = Path.Combine(reportDir, "diff_report.md");
var reportText = File.ReadAllText(reportPath);
Assert.Contains("lines containing any of the configured strings are ignored", reportText);
Assert.Contains("\"buildserver\"", reportText);
Assert.Contains("\"buildPath\"", reportText);
Assert.Contains("lines containing any of the configured strings are ignored:", reportText);
Assert.Contains("| Ignored String |", reportText);
Assert.Contains("| \"buildserver\" |", reportText);
Assert.Contains("| \"buildPath\" |", reportText);
}

[Fact]
Expand Down Expand Up @@ -385,7 +386,7 @@ public void GenerateDiffReport_WritesWarningsInSeverityOrder_WhenMd5MismatchAndT
Path.Combine("nested", "payload.bin"),
"2026-03-14 10:00:00",
"2026-03-14 09:00:00");
_resultLists.RecordDiffDetail("payload.bin", FileDiffResultLists.DiffDetailResult.MD5Mismatch);
_resultLists.RecordDiffDetail(Path.Combine("nested", "payload.bin"), FileDiffResultLists.DiffDetailResult.MD5Mismatch);

var config = CreateConfig();
_service.GenerateDiffReport(
Expand All @@ -402,9 +403,10 @@ public void GenerateDiffReport_WritesWarningsInSeverityOrder_WhenMd5MismatchAndT
Assert.Contains("## Warnings", reportText);
Assert.Contains($"- **WARNING:** {Constants.WARNING_MD5_MISMATCH}", reportText);
Assert.Contains("- **WARNING:** One or more **modified** files in `new` have older last-modified timestamps than the corresponding files in `old`.", reportText);
Assert.Contains(" - nested", reportText);
Assert.Contains("[2026-03-14 10:00:00 → 2026-03-14 09:00:00]", reportText);
Assert.EndsWith("[2026-03-14 10:00:00 → 2026-03-14 09:00:00]", reportText.TrimEnd());
Assert.Contains("| Status | File Path | Timestamp | Legend | Disassembler |", reportText);
Assert.Contains("|:------:|-----------|:---------:|--------|--------------|", reportText);
Assert.Contains("| nested", reportText);
Assert.Contains("2026-03-14 10:00:00 → 2026-03-14 09:00:00", reportText);
Assert.True(
reportText.IndexOf(Constants.WARNING_MD5_MISMATCH, StringComparison.Ordinal) <
reportText.IndexOf("**modified** files in `new` have older last-modified timestamps", StringComparison.Ordinal));
Expand Down Expand Up @@ -473,12 +475,12 @@ public void GenerateDiffReport_ILCacheStats_OutputBetweenSummaryAndWarnings_When

var reportText = File.ReadAllText(Path.Combine(reportDir, "diff_report.md"));
Assert.Contains("## IL Cache Stats", reportText);
Assert.Contains("- Hits :", reportText);
Assert.Contains("- Misses :", reportText);
Assert.Contains("- Hit Rate:", reportText);
Assert.Contains("- Stores :", reportText);
Assert.Contains("- Evicted :", reportText);
Assert.Contains("- Expired :", reportText);
Assert.Contains("| Hits |", reportText);
Assert.Contains("| Misses |", reportText);
Assert.Contains("| Hit Rate |", reportText);
Assert.Contains("| Stores |", reportText);
Assert.Contains("| Evicted |", reportText);
Assert.Contains("| Expired |", reportText);
// IL Cache Stats section must appear between Summary and Warnings
// IL Cache Stats セクションは Summary と Warnings の間に出力されること
int summaryIdx = reportText.IndexOf("## Summary", StringComparison.Ordinal);
Expand Down Expand Up @@ -587,10 +589,10 @@ public void GenerateDiffReport_LargeFileCount_SummaryStatisticsAreCorrect()

var reportText = File.ReadAllText(Path.Combine(reportDir, "diff_report.md"));

// Summary counts must match (labels are left-padded to 10 chars)
// サマリーカウントが一致すること(ラベルは 10 文字に左寄せ
Assert.Contains($"- {"Unchanged",-10}: {fileCount}", reportText, StringComparison.Ordinal);
Assert.Contains($"- {"Compared",-10}: {fileCount} (Old) vs {fileCount} (New)", reportText, StringComparison.Ordinal);
// Summary counts must match (table format)
// サマリーカウントが一致すること(テーブル形式
Assert.Contains($"| Unchanged | {fileCount} |", reportText, StringComparison.Ordinal);
Assert.Contains($"| Compared | {fileCount} (Old) vs {fileCount} (New) |", reportText, StringComparison.Ordinal);
}

[Fact]
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ Column headers for Added / Removed / Modified use colour-coded backgrounds (**gr

Inline diff `<summary>` labels also include a one-based `#N` prefix such as `#3 Show diff` / `#3 Show IL diff`; this number matches the leftmost `#` column for the same row.

A **language toggle** button (`日本語` / `English`) in the controls bar switches all user-facing text between English and Japanese. The preference is persisted in `localStorage` and baked into the downloaded reviewed copy (which also retains the toggle button).

See [doc/samples/diff_report.html](doc/samples/diff_report.html) for a live sample (open in a browser).

### Review workflow
Expand Down Expand Up @@ -622,6 +624,8 @@ Added / Removed / Modified の列ヘッダはそれぞれ**緑・赤・青**の

インライン差分の `<summary>` ラベルにも `#3 Show diff` / `#3 Show IL diff` のような 1 始まりの `#N` プレフィックスが付き、この番号は同じ行の左端 `#` 列と一致します。

コントロールバーの**言語切り替え**ボタン(`日本語` / `English`)で、すべてのユーザー向けテキストを英語と日本語に即座に切り替えられます。言語設定は `localStorage` に保持され、レビュー済みダウンロードにも焼き込まれます(切り替えボタンも引き継がれます)。

ライブサンプルは [doc/samples/diff_report.html](doc/samples/diff_report.html) を参照してください(ブラウザで開いてください)。

### レビュー手順
Expand Down
Loading
Loading