Skip to content

Commit 15ca9d6

Browse files
committed
chore(release): v2.1.25
1 parent aa24fe1 commit 15ca9d6

20 files changed

Lines changed: 480 additions & 110 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## v2.1.25 - 2026-04-21
4+
5+
- Added a shared expandable inspection panel for matching and other zero-diff result rows so reviewers can open the same File A and File B value context even when no differences are present.
6+
- Kept the Results table and standalone HTML export aligned by reusing the same presentation model for paired-value inspection details while preserving the existing mismatch Value Differences behavior.
7+
38
## v2.1.24 - 2026-04-21
49

510
- Tightened release-prep validation by extending the metadata checker to require an explicit expected tag with a matching non-empty changelog entry, backed by integration coverage for both accepted and rejected release states.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "csv-align"
3-
version = "2.1.24"
3+
version = "2.1.25"
44
edition = "2024"
55
description = "CSV file comparison tool with modern web UI"
66
license = "MIT"

frontend/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "csv-align-frontend",
33
"private": true,
4-
"version": "2.1.24",
4+
"version": "2.1.25",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

frontend/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ function App() {
7676
summary={state.summary}
7777
fileAName={state.fileA?.name ?? 'File A'}
7878
fileBName={state.fileB?.name ?? 'File B'}
79+
comparisonColumnsA={mappingSelection.comparisonColumnsA}
80+
comparisonColumnsB={mappingSelection.comparisonColumnsB}
7981
filter={state.filter}
8082
results={state.results}
8183
filteredResults={filteredResults}

frontend/src/components/ResultsTable.test.tsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ const RESULTS: ResultResponse[] = [
4949
},
5050
];
5151

52+
const COMPARISON_COLUMNS_A = ['name'];
53+
const COMPARISON_COLUMNS_B = ['display_name'];
54+
5255
test('shows clearer labels and explanations for one-sided and ignored rows', () => {
53-
render(<ResultsTable results={RESULTS} />);
56+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
5457

5558
expect(screen.getByText('Only in File A')).toBeInTheDocument();
5659
expect(screen.getByText('Ignored in File B')).toBeInTheDocument();
@@ -65,7 +68,7 @@ test('shows clearer labels and explanations for one-sided and ignored rows', ()
6568
});
6669

6770
test('uses shared theme surface classes for table states instead of hardcoded dark overlays', () => {
68-
render(<ResultsTable results={RESULTS} />);
71+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
6972

7073
expect(screen.getByText('A-1')).toHaveClass('kinetic-surface-subtle');
7174

@@ -77,8 +80,24 @@ test('uses shared theme surface classes for table states instead of hardcoded da
7780
expect(screen.getByText('Value Differences').previousElementSibling).toHaveClass('kinetic-surface-accent');
7881
});
7982

83+
test('lets matching rows expand paired file values for inspection', () => {
84+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
85+
86+
const matchRow = screen.getByText('B-2').closest('tr');
87+
expect(within(matchRow as HTMLElement).getByRole('button', { name: /inspect/i })).toBeInTheDocument();
88+
89+
fireEvent.click(within(matchRow as HTMLElement).getByRole('button', { name: /inspect/i }));
90+
91+
expect(screen.getByText('Paired Values')).toBeInTheDocument();
92+
expect(screen.getAllByText('File A').length).toBeGreaterThan(0);
93+
expect(screen.getAllByText('File B').length).toBeGreaterThan(0);
94+
expect(screen.getAllByText('Alpha').length).toBeGreaterThan(0);
95+
expect(screen.getAllByText('Bravo').length).toBeGreaterThan(0);
96+
expect(screen.getAllByText('display_name').length).toBeGreaterThan(0);
97+
});
98+
8099
test('filters visible rows by search query across keys and values', () => {
81-
render(<ResultsTable results={RESULTS} />);
100+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
82101

83102
const search = screen.getByPlaceholderText('Search keys or values');
84103
fireEvent.change(search, { target: { value: 'gamma' } });
@@ -90,7 +109,7 @@ test('filters visible rows by search query across keys and values', () => {
90109
});
91110

92111
test('sorts rows by key when the header is clicked', () => {
93-
render(<ResultsTable results={RESULTS} />);
112+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
94113

95114
fireEvent.click(screen.getByRole('button', { name: /key/i }));
96115

@@ -244,7 +263,7 @@ test('sorts rows by diff count descending when the details header is clicked twi
244263
});
245264

246265
test('updates the controlled search input value synchronously on each keystroke', () => {
247-
render(<ResultsTable results={RESULTS} />);
266+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
248267

249268
const search = screen.getByPlaceholderText('Search keys or values');
250269

@@ -260,7 +279,7 @@ test('updates the controlled search input value synchronously on each keystroke'
260279
});
261280

262281
test('updates sort state from the transition-driven header action', () => {
263-
render(<ResultsTable results={RESULTS} />);
282+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
264283

265284
const keySort = screen.getByRole('button', { name: /key/i });
266285
const keyHeader = keySort.closest('th');
@@ -273,17 +292,18 @@ test('updates sort state from the transition-driven header action', () => {
273292
});
274293

275294
test('renders long diff column names with the larger wrapped header treatment', () => {
276-
render(<ResultsTable results={RESULTS} />);
295+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
277296

278297
fireEvent.click(screen.getByRole('button', { name: /1 diff/i }));
279298

280-
const columnHeader = screen.getByText('name');
299+
const columnHeader = screen.getAllByText('name').find((element) => element.classList.contains('break-all'));
300+
expect(columnHeader).toBeTruthy();
281301
expect(columnHeader).toHaveClass('break-all');
282302
expect(columnHeader).toHaveClass('table-chip');
283303
});
284304

285305
test('positions the mismatch diff arrow on the same row as the value boxes', () => {
286-
render(<ResultsTable results={RESULTS} />);
306+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
287307

288308
fireEvent.click(screen.getByRole('button', { name: /1 diff/i }));
289309

@@ -293,15 +313,23 @@ test('positions the mismatch diff arrow on the same row as the value boxes', ()
293313
});
294314

295315
test('shows the selected-filter empty-state copy when there are zero total results', () => {
296-
render(<ResultsTable results={[]} totalResultsCount={0} />);
316+
render(<ResultsTable results={[]} totalResultsCount={0} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
297317

298318
expect(screen.getByText('No results match the selected filter')).toBeInTheDocument();
299319
expect(screen.queryByText('No results match the current filter and search.')).not.toBeInTheDocument();
300320
});
301321

302322
test('shows the current-filter-and-search empty-state copy when rows exist but none survive filtering', () => {
303-
render(<ResultsTable results={[]} totalResultsCount={RESULTS.length} />);
323+
render(<ResultsTable results={[]} totalResultsCount={RESULTS.length} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
304324

305325
expect(screen.getByText('No results match the current filter and search.')).toBeInTheDocument();
306326
expect(screen.queryByText('No results match the selected filter')).not.toBeInTheDocument();
307327
});
328+
329+
test('renders comparison column names alongside result values', () => {
330+
render(<ResultsTable results={RESULTS} comparisonColumnsA={COMPARISON_COLUMNS_A} comparisonColumnsB={COMPARISON_COLUMNS_B} />);
331+
332+
const row = screen.getByText('Alpha').closest('tr');
333+
expect(within(row as HTMLElement).getByText('name')).toBeInTheDocument();
334+
expect(within(row as HTMLElement).getByText('display_name')).toBeInTheDocument();
335+
});

0 commit comments

Comments
 (0)