Add tests
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 39s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 1m43s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 1m43s

This commit is contained in:
2026-09-28 01:57:27 +00:00
parent 9918c823c2
commit 8d5672c708
4 changed files with 938 additions and 0 deletions
@@ -0,0 +1,195 @@
package haus.nightmare.j3270;
import haus.nightmare.j3270.ui.TerminalPanel;
import haus.nightmare.lib3270j.ConnectionConfig;
import haus.nightmare.lib3270j.ConnectionState;
import haus.nightmare.lib3270j.Telnet3270Client;
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
import haus.nightmare.lib3270j.nvt.NvtProcessor;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import haus.nightmare.lib3270j.telnet.TelnetFSM;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
/**
* Verification test suite for Phase 27:
* Build Hygiene, Release Versioning & Zero-Risk Pruning (v1.2.4)
*/
public class Phase27BuildAndRenderTest {
@Test
@DisplayName("Phase 27.1 & 27.2: Release version 1.2.4 synchronized across build files and android.useAndroidX removed")
public void testVersionSynchronizationAndGradleProperties() throws Exception {
File projectRoot = new File(".").getCanonicalFile();
if (!new File(projectRoot, "settings.gradle").exists() && projectRoot.getParentFile() != null) {
projectRoot = projectRoot.getParentFile();
}
// 1. Verify build.gradle version = '1.2.4'
File buildGradle = new File(projectRoot, "build.gradle");
assertTrue(buildGradle.exists(), "build.gradle must exist");
String buildGradleContent = Files.readString(buildGradle.toPath());
assertTrue(buildGradleContent.contains("version = '1.2.4'"), "build.gradle must define version = '1.2.4'");
assertFalse(buildGradleContent.contains("0.1.0"), "build.gradle must not contain 0.1.0");
// 2. Verify build_all.sh Implementation-Version: 1.2.4
File buildAll = new File(projectRoot, "build_all.sh");
assertTrue(buildAll.exists(), "build_all.sh must exist");
String buildAllContent = Files.readString(buildAll.toPath());
assertTrue(buildAllContent.contains("Implementation-Version: 1.2.4"), "build_all.sh must specify Implementation-Version: 1.2.4");
assertFalse(buildAllContent.contains("Implementation-Version: 0.1.0"), "build_all.sh must not contain 0.1.0");
// 3. Verify setup.ps1 Implementation-Version: 1.2.4
File setupPs1 = new File(projectRoot, "setup.ps1");
assertTrue(setupPs1.exists(), "setup.ps1 must exist");
String setupPs1Content = Files.readString(setupPs1.toPath());
assertTrue(setupPs1Content.contains("Implementation-Version: 1.2.4"), "setup.ps1 must specify Implementation-Version: 1.2.4");
assertFalse(setupPs1Content.contains("Implementation-Version: 0.1.0"), "setup.ps1 must not contain 0.1.0");
// 4. Verify setup.sh Implementation-Version: 1.2.4
File setupSh = new File(projectRoot, "setup.sh");
assertTrue(setupSh.exists(), "setup.sh must exist");
String setupShContent = Files.readString(setupSh.toPath());
assertTrue(setupShContent.contains("Implementation-Version: 1.2.4"), "setup.sh must specify Implementation-Version: 1.2.4");
assertFalse(setupShContent.contains("Implementation-Version: 0.1.0"), "setup.sh must not contain 0.1.0");
// 5. Verify gradle.properties android.useAndroidX removed
File gradleProps = new File(projectRoot, "gradle.properties");
assertTrue(gradleProps.exists(), "gradle.properties must exist");
String gradlePropsContent = Files.readString(gradleProps.toPath());
assertFalse(gradlePropsContent.contains("android.useAndroidX"), "gradle.properties must not contain android.useAndroidX");
}
@Test
@DisplayName("Phase 27.3: T1-B Static Color & BasicStroke constants promoted in TerminalPanel")
public void testHotspotHoverStaticConstants() throws Exception {
Field colorField = TerminalPanel.class.getDeclaredField("HOTSPOT_HOVER_COLOR");
colorField.setAccessible(true);
assertTrue(Modifier.isStatic(colorField.getModifiers()), "HOTSPOT_HOVER_COLOR must be static");
assertTrue(Modifier.isFinal(colorField.getModifiers()), "HOTSPOT_HOVER_COLOR must be final");
Color hoverColor = (Color) colorField.get(null);
assertEquals(new Color(0, 220, 255), hoverColor, "HOTSPOT_HOVER_COLOR must match bright cyan (0, 220, 255)");
Field strokeField = TerminalPanel.class.getDeclaredField("HOTSPOT_HOVER_STROKE");
strokeField.setAccessible(true);
assertTrue(Modifier.isStatic(strokeField.getModifiers()), "HOTSPOT_HOVER_STROKE must be static");
assertTrue(Modifier.isFinal(strokeField.getModifiers()), "HOTSPOT_HOVER_STROKE must be final");
Stroke hoverStroke = (Stroke) strokeField.get(null);
assertNotNull(hoverStroke, "HOTSPOT_HOVER_STROKE must not be null");
assertTrue(hoverStroke instanceof BasicStroke, "HOTSPOT_HOVER_STROKE must be BasicStroke");
assertEquals(2.0f, ((BasicStroke) hoverStroke).getLineWidth(), 0.001f, "HOTSPOT_HOVER_STROKE line width must be 2.0f");
}
@Test
@DisplayName("Phase 27.4: T1-A charScratch Buffer exists and non-ASCII render renders without exceptions")
public void testCharScratchAndNonAsciiRendering() throws Exception {
Field scratchField = TerminalPanel.class.getDeclaredField("charScratch");
scratchField.setAccessible(true);
assertFalse(Modifier.isStatic(scratchField.getModifiers()), "charScratch should be an instance field confined to EDT");
assertTrue(Modifier.isFinal(scratchField.getModifiers()), "charScratch should be final");
TerminalPanel panel = new TerminalPanel();
panel.setSize(800, 600);
char[] scratch = (char[]) scratchField.get(panel);
assertNotNull(scratch, "charScratch must not be null");
assertEquals(1, scratch.length, "charScratch must be length 1");
// Set up client with non-ASCII characters (e.g. U+2500 box drawing, U+00E9 acute e, U+2550 double box)
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
ScreenBuffer sb = client.getScreenBuffer();
sb.erase(false);
// Put non-ASCII characters on screen
sb.getCell(0).ucs4 = 0x2500; // Box drawing horizontal line
sb.getCell(1).ucs4 = 0x00E9; // e with acute
sb.getCell(2).ucs4 = 0x2550; // Box drawing double horizontal
sb.getCell(3).ucs4 = 0x2308; // APL left ceiling
sb.updateDisplaySnapshot();
panel.setClient(client);
BufferedImage img = new BufferedImage(800, 600, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
assertDoesNotThrow(() -> panel.paint(g2), "paint must render non-ASCII cells via drawChars without error");
g2.dispose();
// Check that charScratch was used
assertTrue(scratch[0] > 0, "charScratch should have held the last drawn non-ASCII character");
}
@Test
@DisplayName("Phase 27.5: T1-C Reusable NVT Single-Byte Buffer dispatches without allocations")
public void testReusableNvtSingleByteBuffer() throws Exception {
Field scratchField = TelnetFSM.class.getDeclaredField("nvtSingleByteScratch");
scratchField.setAccessible(true);
assertFalse(Modifier.isStatic(scratchField.getModifiers()), "nvtSingleByteScratch must be an instance field");
assertTrue(Modifier.isFinal(scratchField.getModifiers()), "nvtSingleByteScratch must be final");
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
TelnetFSM fsm = client.getTelnetFSM();
byte[] expectedScratch = (byte[]) scratchField.get(fsm);
assertNotNull(expectedScratch, "nvtSingleByteScratch must not be null");
assertEquals(1, expectedScratch.length, "nvtSingleByteScratch must have length 1");
fsm.setConnectionState(ConnectionState.CONNECTED_NVT);
AtomicBoolean dispatched = new AtomicBoolean(false);
AtomicReference<byte[]> receivedBuffer = new AtomicReference<>();
NvtProcessor customNvtProcessor = new NvtProcessor(client.getScreenBuffer(), new EbcdicTranslator()) {
@Override
public void processNVTData(byte[] buffer, int offset, int length) {
dispatched.set(true);
receivedBuffer.set(buffer);
assertEquals(0, offset);
assertEquals(1, length);
assertEquals((byte) 'Z', buffer[offset]);
super.processNVTData(buffer, offset, length);
}
};
Field nvtProcField = TelnetFSM.class.getDeclaredField("nvtProcessor");
nvtProcField.setAccessible(true);
nvtProcField.set(fsm, customNvtProcessor);
// Feed single byte 'Z' into FSM
fsm.feedByte('Z');
assertTrue(dispatched.get(), "NvtProcessor.processNVTData should have been called");
assertSame(expectedScratch, receivedBuffer.get(), "Buffer passed to processNVTData must be the reusable nvtSingleByteScratch array instance");
}
@Test
@DisplayName("Phase 27.6: J3270App has Settings import and no in-line FQCN occurrences")
public void testJ3270AppSettingsImport() throws Exception {
File projectRoot = new File(".").getCanonicalFile();
if (!new File(projectRoot, "j3270").exists() && projectRoot.getParentFile() != null) {
projectRoot = projectRoot.getParentFile();
}
File appFile = new File(projectRoot, "j3270/src/main/java/haus/nightmare/j3270/J3270App.java");
assertTrue(appFile.exists(), "J3270App.java must exist");
String content = Files.readString(appFile.toPath());
assertTrue(content.contains("import haus.nightmare.j3270.config.Settings;"),
"J3270App.java must import haus.nightmare.j3270.config.Settings");
// The only occurrence of haus.nightmare.j3270.config.Settings should be in the import
int firstIndex = content.indexOf("haus.nightmare.j3270.config.Settings");
assertTrue(firstIndex >= 0, "Must contain import");
int secondIndex = content.indexOf("haus.nightmare.j3270.config.Settings", firstIndex + 1);
assertEquals(-1, secondIndex, "There should be no inline occurrences of haus.nightmare.j3270.config.Settings in J3270App");
}
}
@@ -0,0 +1,341 @@
package haus.nightmare.j3270;
import haus.nightmare.j3270.ui.FieldInspectorDialog;
import haus.nightmare.j3270.ui.FindDialog;
import haus.nightmare.j3270.ui.HotspotEngine;
import haus.nightmare.j3270.ui.ScriptDialog;
import haus.nightmare.j3270.ui.TerminalPanel;
import haus.nightmare.lib3270j.ConnectionConfig;
import haus.nightmare.lib3270j.ConnectionState;
import haus.nightmare.lib3270j.Telnet3270Client;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* Verification test suite for Phase 28:
* UI Responsiveness, Dialog Lifecycle & Event Optimization
*/
public class Phase28UiOptimizationTest {
private void setField(Object obj, String fieldName, Object val) throws Exception {
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, val);
}
private void writeString(ScreenBuffer sb, int row, int col, String text) {
int cols = sb.getDisplayCols();
int base = row * cols + col;
for (int i = 0; i < text.length(); i++) {
sb.getCell(base + i).ucs4 = text.charAt(i);
}
sb.updateDisplaySnapshot();
}
@Test
@DisplayName("Phase 28.1: T2-A Hotspot Dirty Flagging & Lazy Text Scraping eliminates redundant scans on mouseMoved")
public void testRapidMouseMoveEventsScanOnlyOnce() throws Exception {
TerminalPanel panel = new TerminalPanel();
panel.setSize(800, 600);
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
ScreenBuffer sb = client.getScreenBuffer();
sb.erase(false);
// Put hotspots on screen: PF keys and URL
writeString(sb, 0, 0, "APPLICATION SELECTION MENU");
writeString(sb, 2, 0, "PF1=Help PF3=Exit PF12=Cancel");
writeString(sb, 4, 0, "Visit http://example.com/3270 for assistance");
sb.updateDisplaySnapshot();
panel.setClient(client);
HotspotEngine engine = panel.getHotspotEngine();
engine.setUrlEnabled(true);
engine.setPfKeyEnabled(true);
engine.setMenuChoiceEnabled(true);
assertTrue(panel.isHotspotsDirty(), "Hotspots should initially be dirty on panel setup");
long initialScanCount = engine.getScanCount();
MouseMotionListener[] motionListeners = panel.getMouseMotionListeners();
assertTrue(motionListeners.length > 0, "TerminalPanel must have at least one MouseMotionListener");
MouseMotionListener mml = motionListeners[0];
// Simulate 1,000 rapid mouse move events across the terminal grid
for (int i = 0; i < 1000; i++) {
int x = 20 + (i % 200);
int y = 30 + (i % 150);
MouseEvent me = new MouseEvent(panel, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, x, y, 0, false);
mml.mouseMoved(me);
}
// After 1,000 moves, hotspotEngine.scan() should only have executed ONCE
assertEquals(initialScanCount + 1, engine.getScanCount(),
"hotspotEngine.scan() should execute exactly once across 1,000 rapid mouse move events on a clean screen");
assertFalse(panel.isHotspotsDirty(), "Hotspots must be marked clean after scanning");
// Verify hotspots were actually discovered
List<HotspotEngine.Hotspot> hotspots = engine.getHotspots();
assertFalse(hotspots.isEmpty(), "Hotspots on screen must be found");
assertNotNull(engine.findHotspot(2, 0), "Hotspot at row 2 col 0 (PF1) must be found");
// Direct calls to HotspotEngine.scan(sb) without snapshot version changes must also bypass StringBuilder
long beforeDirectCalls = engine.getScanCount();
for (int i = 0; i < 1000; i++) {
List<HotspotEngine.Hotspot> res = engine.scan(sb);
assertFalse(res.isEmpty());
}
assertEquals(beforeDirectCalls, engine.getScanCount(),
"Direct calls to HotspotEngine.scan(sb) without snapshot version change must return cachedHotspots");
// Now simulate screen update
writeString(sb, 6, 0, "NEW UPDATE: PF7=Up PF8=Down");
panel.onScreenUpdated();
assertTrue(panel.isHotspotsDirty(), "onScreenUpdated() must flag hotspotsDirty = true");
// Simulate another burst of 1,000 rapid mouse moves
for (int i = 0; i < 1000; i++) {
int x = 30 + (i % 200);
int y = 40 + (i % 150);
MouseEvent me = new MouseEvent(panel, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, x, y, 0, false);
mml.mouseMoved(me);
}
assertEquals(initialScanCount + 2, engine.getScanCount(),
"After screen update, exactly one more scan should run across 1,000 mouse moves");
assertFalse(panel.isHotspotsDirty(), "Hotspots must be marked clean again after rescan");
}
@Test
@DisplayName("Phase 28.2: T2-B Precomputed Selection Scratch Lookup matches isCellSelected exactly across all modes")
public void testSelectionBoundsMatchingPrecomputedScratchVsLegacy() throws Exception {
TerminalPanel panel = new TerminalPanel();
panel.setSize(800, 600);
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
panel.setClient(client);
int rows = 24;
int cols = 80;
// Configuration 1: No selection
panel.clearSelection();
panel.precomputeSelectionScratch(rows, cols);
boolean[] scratch = panel.getSelectedCellsScratch();
assertEquals(rows * cols, scratch.length);
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
int baddr = r * cols + c;
assertFalse(scratch[baddr], "No cell should be selected");
assertEquals(panel.isCellSelected(r, c), scratch[baddr]);
}
}
// Configuration 2: Linear / Stream mode forward selection (spanning multiple rows)
setField(panel, "blockSelectMode", false);
setField(panel, "trimRectSizingHandles", false);
setField(panel, "selectionStartRow", 2);
setField(panel, "selectionStartCol", 15);
setField(panel, "selectionEndRow", 5);
setField(panel, "selectionEndCol", 42);
panel.precomputeSelectionScratch(rows, cols);
scratch = panel.getSelectedCellsScratch();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
int baddr = r * cols + c;
boolean expected = panel.isCellSelected(r, c);
assertEquals(expected, scratch[baddr], "Mismatch in linear forward mode at row " + r + ", col " + c);
}
}
// Configuration 3: Linear / Stream mode backward selection (drag upwards)
setField(panel, "selectionStartRow", 8);
setField(panel, "selectionStartCol", 30);
setField(panel, "selectionEndRow", 4);
setField(panel, "selectionEndCol", 10);
panel.precomputeSelectionScratch(rows, cols);
scratch = panel.getSelectedCellsScratch();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
int baddr = r * cols + c;
boolean expected = panel.isCellSelected(r, c);
assertEquals(expected, scratch[baddr], "Mismatch in linear backward mode at row " + r + ", col " + c);
}
}
// Configuration 4: Block select mode
setField(panel, "blockSelectMode", true);
setField(panel, "trimRectSizingHandles", false);
setField(panel, "selectionStartRow", 3);
setField(panel, "selectionStartCol", 20);
setField(panel, "selectionEndRow", 7);
setField(panel, "selectionEndCol", 60);
panel.precomputeSelectionScratch(rows, cols);
scratch = panel.getSelectedCellsScratch();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
int baddr = r * cols + c;
boolean expected = panel.isCellSelected(r, c);
assertEquals(expected, scratch[baddr], "Mismatch in block select mode at row " + r + ", col " + c);
}
}
// Configuration 5: Trim Rect Sizing Handles mode (block geometry)
setField(panel, "blockSelectMode", false);
setField(panel, "trimRectSizingHandles", true);
setField(panel, "selectionStartRow", 10);
setField(panel, "selectionStartCol", 5);
setField(panel, "selectionEndRow", 15);
setField(panel, "selectionEndCol", 25);
panel.precomputeSelectionScratch(rows, cols);
scratch = panel.getSelectedCellsScratch();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
int baddr = r * cols + c;
boolean expected = panel.isCellSelected(r, c);
assertEquals(expected, scratch[baddr], "Mismatch in trim handles mode at row " + r + ", col " + c);
}
}
// Configuration 6: Model 5 Dynamic Geometry (27 rows x 132 cols)
rows = 27;
cols = 132;
client.getScreenBuffer().setDimensions(rows, cols);
client.getScreenBuffer().updateDisplaySnapshot();
setField(panel, "selectionStartRow", 1);
setField(panel, "selectionStartCol", 1);
setField(panel, "selectionEndRow", 25);
setField(panel, "selectionEndCol", 130);
panel.precomputeSelectionScratch(rows, cols);
scratch = panel.getSelectedCellsScratch();
assertEquals(rows * cols, scratch.length);
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
int baddr = r * cols + c;
boolean expected = panel.isCellSelected(r, c);
assertEquals(expected, scratch[baddr], "Mismatch in Model 5 27x132 at row " + r + ", col " + c);
}
}
}
@Test
@DisplayName("Phase 28.3: Dialog Lifecycle & Reconnection Hardening updates cached dialogs with active client")
public void testReconnectCycleFindDialogInteractsWithNewClient() throws Exception {
// 1. Verify API contract and reflection for dialog lifecycle methods
Method findSetClient = FindDialog.class.getMethod("setClient", Telnet3270Client.class);
assertNotNull(findSetClient);
Method findGetClient = FindDialog.class.getMethod("getClient");
assertEquals(Telnet3270Client.class, findGetClient.getReturnType());
Method scriptSetClient = ScriptDialog.class.getMethod("setClient", Telnet3270Client.class);
assertNotNull(scriptSetClient);
Method scriptGetClient = ScriptDialog.class.getMethod("getClient");
assertEquals(Telnet3270Client.class, scriptGetClient.getReturnType());
Method fieldSetClient = FieldInspectorDialog.class.getMethod("setClient", Telnet3270Client.class);
assertNotNull(fieldSetClient);
Method fieldGetClient = FieldInspectorDialog.class.getMethod("getClient");
assertEquals(Telnet3270Client.class, fieldGetClient.getReturnType());
Method appGetFind = J3270App.class.getMethod("getFindDialog");
assertEquals(FindDialog.class, appGetFind.getReturnType());
Method appGetScript = J3270App.class.getMethod("getScriptDialog");
assertEquals(ScriptDialog.class, appGetScript.getReturnType());
Method appGetField = J3270App.class.getMethod("getFieldInspectorDialog");
assertEquals(FieldInspectorDialog.class, appGetField.getReturnType());
// 2. If non-headless environment, perform end-to-end interactive GUI verification
try {
J3270App app = new J3270App();
TerminalPanel panel = app.getTerminalPanel();
// Session 1: Client 1
ConnectionConfig config1 = new ConnectionConfig("host1.example.com", 23);
Telnet3270Client client1 = new Telnet3270Client(config1);
client1.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
writeString(client1.getScreenBuffer(), 1, 0, "SYSTEM_SESSION_ALPHA_123");
panel.setClient(client1);
setField(app, "client", client1);
// Open FindDialog on session 1
FindDialog findDialog = new FindDialog(app, client1, panel);
assertSame(client1, findDialog.getClient());
// Search for string on Client 1
findDialog.setSearchText("SYSTEM_SESSION_ALPHA_123");
findDialog.findNext();
int expectedPos1 = 1 * client1.getScreenBuffer().getDisplayCols();
assertEquals(expectedPos1, client1.getScreenBuffer().getCursorAddress(),
"FindDialog must move cursor to match on client1");
// Simulate Session Disconnect
setField(app, "findDialog", findDialog);
app.disconnect(false);
assertNull(findDialog.getClient(), "Disconnect must nullify client in cached dialog");
// Subsequent find attempt while disconnected should report disconnected without errors
findDialog.findNext();
assertEquals(expectedPos1, client1.getScreenBuffer().getCursorAddress());
// Session 2: Reconnection to Client 2
ConnectionConfig config2 = new ConnectionConfig("host2.example.com", 23);
Telnet3270Client client2 = new Telnet3270Client(config2);
client2.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
writeString(client2.getScreenBuffer(), 3, 10, "MAINFRAME_SESSION_BETA_999");
// Simulate reconnect hook in J3270App
findDialog.setClient(client2);
assertSame(client2, findDialog.getClient(), "FindDialog must now hold client2");
// Search for string on Client 2
findDialog.setSearchText("MAINFRAME_SESSION_BETA_999");
findDialog.findNext();
int expectedPos2 = 3 * client2.getScreenBuffer().getDisplayCols() + 10;
assertEquals(expectedPos2, client2.getScreenBuffer().getCursorAddress(),
"FindDialog must move cursor to match on active client2 after reconnect");
// Verify search for old client1 string fails on client2
int oldCursorPos = client2.getScreenBuffer().getCursorAddress();
findDialog.setSearchText("SYSTEM_SESSION_ALPHA_123");
findDialog.findNext();
assertEquals(oldCursorPos, client2.getScreenBuffer().getCursorAddress(),
"Search for stale text should not match on client2");
// Verify ScriptDialog and FieldInspectorDialog client lifecycle
ScriptDialog scriptDialog = new ScriptDialog(app, client1, panel);
assertSame(client1, scriptDialog.getClient());
scriptDialog.setClient(client2);
assertSame(client2, scriptDialog.getClient());
FieldInspectorDialog fieldInspector = new FieldInspectorDialog(app, client1, panel);
assertSame(client1, fieldInspector.getClient());
fieldInspector.setClient(client2);
assertSame(client2, fieldInspector.getClient());
// Cleanup
fieldInspector.dispose();
findDialog.dispose();
scriptDialog.dispose();
app.closeWindow();
} catch (java.awt.HeadlessException ignored) {
// Running in headless mode: GUI instantiation safely bypassed
}
}
}