From 3988e264612575de34e41de28d3864692f2c230a Mon Sep 17 00:00:00 2001 From: Oleg Samarin Date: Sat, 18 Apr 2026 20:09:34 +0300 Subject: [PATCH] Fixed diff panel height and stale card on second diff - Fixed diff panel appearing at nearly invisible height after Reset Windows or on first use: unified componentResized first/subsequent paths, added total * 2 / 3 default for CARD_DIFF, deferred divider set when splitPane.getHeight() == 0 in switchSouthCard - Fixed second diff in the same session being invisible: track currentDiffCard field and remove old card before adding new one in showEmbeddedDiff/hideEmbeddedDiff/cleanupChatLayout - Added fine-level diagnostic logging for applyDivider, componentResized, switchSouthCard to aid future debugging Co-Authored-By: Claude Sonnet 4.6 --- .../ui/ClaudeSessionTabDiffTest.java | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 src/test/java/io/github/nbclaudecodegui/ui/ClaudeSessionTabDiffTest.java diff --git a/src/test/java/io/github/nbclaudecodegui/ui/ClaudeSessionTabDiffTest.java b/src/test/java/io/github/nbclaudecodegui/ui/ClaudeSessionTabDiffTest.java new file mode 100644 index 00000000..4dd2807d --- /dev/null +++ b/src/test/java/io/github/nbclaudecodegui/ui/ClaudeSessionTabDiffTest.java @@ -0,0 +1,175 @@ +package io.github.nbclaudecodegui.ui; + +import java.awt.CardLayout; +import java.awt.Component; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the two embedded-diff bugs fixed in 0.22.2: + *
    + *
  1. Stale {@code CARD_DIFF} accumulates in the CardLayout when + * {@link ClaudeSessionTab#showEmbeddedDiff} is called multiple times.
  2. + *
  3. Wrong divider position when the diff card first appears ({@link + * ClaudeSessionTab#computeBottomHeight} default logic).
  4. + *
+ */ +class ClaudeSessionTabDiffTest { + + // ------------------------------------------------------------------------- + // Bug 1 — computeBottomHeight default values + // ------------------------------------------------------------------------- + + @Test + void computeBottomHeight_savedValue_returnsSaved() { + assertEquals(123, ClaudeSessionTab.computeBottomHeight("diff", 900, 123, -1, 50)); + } + + @Test + void computeBottomHeight_diffCard_noSaved_returnsTwoThirdsTotal() { + int total = 900; + int expected = total * 2 / 3; // 600 + assertEquals(expected, ClaudeSessionTab.computeBottomHeight("diff", total, -1, -1, 50)); + } + + @Test + void computeBottomHeight_promptCard_noSaved_legacyPresent_returnsLegacy() { + assertEquals(200, ClaudeSessionTab.computeBottomHeight("prompt", 900, -1, 200, 50)); + } + + @Test + void computeBottomHeight_promptCard_noSaved_noLegacy_returnsPreferred() { + assertEquals(50, ClaudeSessionTab.computeBottomHeight("prompt", 900, -1, -1, 50)); + } + + @Test + void computeBottomHeight_diffCard_savedZeroIgnored_returnsTwoThirds() { + // saved == 0 is treated as "not saved" (condition is saved > 0) + int total = 600; + assertEquals(total * 2 / 3, ClaudeSessionTab.computeBottomHeight("diff", total, 0, -1, 50)); + } + + // ------------------------------------------------------------------------- + // getSavedCardKeyFor + // ------------------------------------------------------------------------- + + @Test + void savedCardKey_promptCard_returnsBackwardCompatKey() { + ClaudeSessionTab tab = new ClaudeSessionTab(); + assertEquals("bottomHeight", tab.getSavedCardKeyFor("prompt")); + } + + @Test + void savedCardKey_nullCard_returnsBackwardCompatKey() { + ClaudeSessionTab tab = new ClaudeSessionTab(); + assertEquals("bottomHeight", tab.getSavedCardKeyFor(null)); + } + + @Test + void savedCardKey_diffCard_returnsDiffHeight() { + ClaudeSessionTab tab = new ClaudeSessionTab(); + assertEquals("diffHeight", tab.getSavedCardKeyFor("diff")); + } + + // ------------------------------------------------------------------------- + // Bug 2 — stale CARD_DIFF accumulates in CardLayout + // ------------------------------------------------------------------------- + + /** + * Sets up a minimal south-card environment on a tab (without starting a PTY) + * so that {@link ClaudeSessionTab#showEmbeddedDiff} can be called. + * {@code switchSouthCard} is overridden to a no-op to avoid needing a full + * JediTerm widget and splitPane. + */ + private ClaudeSessionTab tabWithSouthCard() { + ClaudeSessionTab tab = new ClaudeSessionTab() { + @Override + void switchSouthCard(String card) { + // no-op: avoids splitPane / terminalWidget dependency in tests + activeCard = card; + } + }; + CardLayout layout = new CardLayout(); + JPanel south = new JPanel(layout); + tab.southCard = south; + tab.southCardLayout = layout; + tab.activeCard = "prompt"; + return tab; + } + + @Test + void showEmbeddedDiff_firstCall_addsDiffCard() { + ClaudeSessionTab tab = tabWithSouthCard(); + + boolean shown = tab.showEmbeddedDiff(false, null, null, + new JLabel("diff1"), + new FileDiffPermissionPanel(() -> {}, r -> {}, () -> {}, () -> {}, null), + null); + + assertTrue(shown, "showEmbeddedDiff must return true when southCard is set"); + assertNotNull(tab.currentDiffCard, "currentDiffCard must be set after first call"); + // southCard now contains exactly one CARD_DIFF panel + assertEquals(1, countComponents(tab.southCard)); + } + + @Test + void showEmbeddedDiff_secondCall_replacesOldCard() { + ClaudeSessionTab tab = tabWithSouthCard(); + + tab.showEmbeddedDiff(false, null, null, + new JLabel("diff1"), + new FileDiffPermissionPanel(() -> {}, r -> {}, () -> {}, () -> {}, null), + null); + JPanel firstCard = tab.currentDiffCard; + + tab.showEmbeddedDiff(false, null, null, + new JLabel("diff2"), + new FileDiffPermissionPanel(() -> {}, r -> {}, () -> {}, () -> {}, null), + null); + JPanel secondCard = tab.currentDiffCard; + + assertNotSame(firstCard, secondCard, "currentDiffCard must be replaced on second call"); + // Only the new card should remain in southCard — no stale accumulation + assertEquals(1, countComponents(tab.southCard), + "southCard must contain exactly one diff card after second showEmbeddedDiff"); + assertFalse(isChildOf(tab.southCard, firstCard), + "Old diff card must be removed from southCard"); + assertTrue(isChildOf(tab.southCard, secondCard), + "New diff card must be present in southCard"); + } + + @Test + void hideEmbeddedDiff_removesCardAndClearsField() { + ClaudeSessionTab tab = tabWithSouthCard(); + + tab.showEmbeddedDiff(false, null, null, + new JLabel("diff"), + new FileDiffPermissionPanel(() -> {}, r -> {}, () -> {}, () -> {}, null), + null); + + tab.hideEmbeddedDiff(); + + assertNull(tab.currentDiffCard, "currentDiffCard must be null after hide"); + assertEquals(0, countComponents(tab.southCard), + "southCard must have no diff card after hide"); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private static int countComponents(JPanel panel) { + return panel.getComponentCount(); + } + + private static boolean isChildOf(JPanel parent, Component child) { + for (int i = 0; i < parent.getComponentCount(); i++) { + if (parent.getComponent(i) == child) return true; + } + return false; + } +}