Add consts and update tests
Build and Test j3270 / Build JAR & Run Tests (push) Successful in 1m20s

This commit is contained in:
2026-08-31 10:22:12 -04:00
parent a9956298e2
commit 7b5bc7ec11
25 changed files with 4198 additions and 542 deletions
@@ -0,0 +1,361 @@
package haus.nightmare.lib3270j.datastream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import haus.nightmare.lib3270j.TerminalModel;
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
import haus.nightmare.lib3270j.graphics.GocaConstants;
import haus.nightmare.lib3270j.graphics.GraphicsMode;
import haus.nightmare.lib3270j.printer.PrintSCS3270;
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* Comprehensive test suite verifying all Phase 2 functional updates:
* 3270 Data Stream parsing, Graphic Escape & APL mapping, Partitioning,
* Structured Fields (0x0F, 0x20, 0x24, 0x40), and Query Reply builders.
*/
public class DataStreamProcessorPhase2FullTest {
private EbcdicTranslator translator;
private ScreenBuffer screen;
private DataStreamProcessor processor;
private ByteArrayOutputStream output;
@BeforeEach
public void setUp() {
translator = new EbcdicTranslator();
screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
processor = new DataStreamProcessor(screen, translator);
output = new ByteArrayOutputStream();
processor.setOutputSender(bytes -> output.write(bytes, 0, bytes.length));
}
@Test
public void testGraphicEscapeOrderWithAPLTranslation() {
// Write record: CMD_EW (0x05), WCC (0xC3), SBA to 0 (0x11, 0x40, 0x40), ORDER_GE (0x08), 0xA2 (s -> ─)
// followed by ORDER_GE, 0x85 (e -> │), ORDER_GE, 0xCB (Cross -> ┼), ORDER_GE, 0xBA (Omega -> Ω)
byte[] record = new byte[] {
(byte) CMD_EW, (byte) 0xC3,
(byte) ORDER_SBA, 0x40, 0x40,
(byte) ORDER_GE, (byte) 0xA2, // Horizontal Line
(byte) ORDER_GE, (byte) 0x85, // Vertical Line
(byte) ORDER_GE, (byte) 0xCB, // Cross
(byte) ORDER_GE, (byte) 0xBA // Greek Omega
};
processor.processRecord(record);
ExtendedAttribute cell0 = screen.getCell(0);
assertEquals(CS_GE, cell0.cs);
assertEquals((byte) 0xA2, cell0.ec);
assertEquals('─', cell0.ucs4);
ExtendedAttribute cell1 = screen.getCell(1);
assertEquals(CS_GE, cell1.cs);
assertEquals((byte) 0x85, cell1.ec);
assertEquals('│', cell1.ucs4);
ExtendedAttribute cell2 = screen.getCell(2);
assertEquals(CS_GE, cell2.cs);
assertEquals((byte) 0xCB, cell2.ec);
assertEquals('┼', cell2.ucs4);
ExtendedAttribute cell3 = screen.getCell(3);
assertEquals(CS_GE, cell3.cs);
assertEquals((byte) 0xBA, cell3.ec);
assertEquals('Ω', cell3.ucs4);
}
@Test
public void testRepeatToAddressWithGraphicEscape() {
// Repeat to address 5 with ORDER_GE prefix + 0xA2 ('─')
byte[] record = new byte[] {
(byte) CMD_EW, (byte) 0xC3,
(byte) ORDER_SBA, 0x40, 0x40, // Addr 0
(byte) ORDER_RA, 0x40, (byte) 0x45, // Addr 5
(byte) ORDER_GE, (byte) 0xA2
};
processor.processRecord(record);
for (int i = 0; i < 5; i++) {
ExtendedAttribute cell = screen.getCell(i);
assertEquals(CS_GE, cell.cs, "Cell " + i + " should have CS_GE");
assertEquals('─', cell.ucs4, "Cell " + i + " should have '─'");
}
}
@Test
public void testOutbound3270DSNonZeroPartition() {
// First create partition 2 (32 rows x 80 cols)
byte[] createPart = new byte[] {
(byte) CMD_WSF,
0x00, 0x08,
(byte) SF_CREATE_PART, 0x02,
0x00, 80,
0x00, 32
};
processor.processRecord(createPart);
assertEquals(2, screen.getActivePartition());
// Now activate partition 0
screen.setActivePartition(0);
assertEquals(0, screen.getActivePartition());
// Send Outbound 3270DS targeting Partition 2 containing Write command to write 'H' at pos 0
byte[] outboundDs = new byte[] {
(byte) CMD_WSF,
0x00, 0x0A, // Field length = 10
(byte) SF_OUTBOUND_DS, 0x02, // Target PID = 2
(byte) CMD_W, 0x00, // Write command with null WCC
(byte) ORDER_SBA, 0x40, 0x40, // Pos 0
(byte) 0xC8 // EBCDIC 'H'
};
processor.processRecord(outboundDs);
// Verify active partition was switched to PID 2
assertEquals(2, screen.getActivePartition());
assertEquals('H', screen.getCell(0).ucs4);
}
@Test
public void testStructuredField0x0FSetWindow() {
// SF 0x0F with explicit viewport window (xmin=10, ymin=20, xmax=500, ymax=300)
byte[] setWindowSf = new byte[] {
(byte) CMD_WSF,
0x00, 0x0B, // Length = 11
(byte) SF_SET_WINDOW,
0x00, 10, // xMin = 10
0x00, 20, // yMin = 20
0x01, (byte) 0xF4, // xMax = 500
0x01, 0x2C // yMax = 300
};
processor.processRecord(setWindowSf);
// Test direct overload processSFSetWindow
processor.processSFSetWindow(setWindowSf);
}
@Test
public void testStructuredField0x0FDataUnitActivatesGraphicCursor() {
assertFalse(processor.getGocaDecoder().isGraphicsCursorActive());
byte[] objDataSf = new byte[] {
(byte) CMD_WSF,
0x00, 0x04,
(byte) SF_SET_WINDOW,
(byte) GocaConstants.SF_OBJDATA_SUB // 0x0F
};
processor.processRecord(objDataSf);
assertTrue(processor.getGocaDecoder().isGraphicsCursorActive());
}
@Test
public void testStructuredField0x20ObjectControl() {
// SF 0x20 (3270 Graphics / Object Control) with GOCA begin/end area orders
byte[] objControl = new byte[] {
(byte) CMD_WSF,
0x00, 0x07,
(byte) SF_OBJECT_CONTROL,
(byte) GocaConstants.G_GBAR, 0x00, // Begin area
(byte) GocaConstants.G_GEAR, 0x00 // End area
};
processor.processRecord(objControl);
processor.processSFObjectControl(objControl);
}
@Test
public void testStructuredField0x24DocumentDataWithEmbeddedSCS() {
AtomicReference<byte[]> scsCaptured = new AtomicReference<>();
PrintSCS3270 mockScs = new PrintSCS3270(null, null, translator) {
@Override
public void processHostData(byte[] data, int offset, int length) {
byte[] b = new byte[length];
System.arraycopy(data, offset, b, 0, length);
scsCaptured.set(b);
}
};
processor.setEmbeddedScsProcessor(mockScs);
// SF 0x24 (Document Data) with 4 bytes of SCS printer data
byte[] docData = new byte[] {
(byte) CMD_WSF,
0x00, 0x07,
(byte) SF_DOCUMENT_DATA,
(byte) 0x15, (byte) 0xC8, (byte) 0xC9, (byte) 0x15 // NL, 'H', 'I', NL
};
processor.processRecord(docData);
assertNotNull(scsCaptured.get());
assertEquals(4, scsCaptured.get().length);
assertEquals(0x15, scsCaptured.get()[0]);
assertEquals((byte) 0xC8, scsCaptured.get()[1]);
// Test direct method
processor.processSFDocumentData(docData);
}
@Test
public void testQueryReplyBuilderGraphicColor109Bytes() {
QueryReplyBuilder qrBuilder = processor.getQueryReplyBuilder();
byte[] grColor = qrBuilder.buildGraphicColor();
assertNotNull(grColor);
assertEquals(105, grColor.length); // 105 bytes payload + 4 bytes header = 109 bytes in complete SF
// Check header bytes in payload: 0x00, 0x04, 0x00, 0xFF, 0xFF, 0x00, 0x10, 0x00, 0x10
assertEquals(0x00, grColor[0]);
assertEquals(0x04, grColor[1]);
assertEquals(0x00, grColor[2]);
assertEquals((byte) 0xFF, grColor[3]);
assertEquals((byte) 0xFF, grColor[4]);
assertEquals(0x00, grColor[5]);
assertEquals(0x10, grColor[6]);
assertEquals(0x00, grColor[7]);
assertEquals(0x10, grColor[8]);
// Check aliases
assertArrayEquals(grColor, qrBuilder.buildGrColor());
assertArrayEquals(grColor, qrBuilder.buildGColor());
}
@Test
public void testQueryReplyBuilderAuxDeviceAndLineType() {
QueryReplyBuilder qrBuilder = processor.getQueryReplyBuilder();
byte[] lineType = qrBuilder.buildAuxDevice();
assertNotNull(lineType);
assertEquals(20, lineType.length); // 20 bytes payload + 4 bytes header = 24 bytes
assertArrayEquals(lineType, qrBuilder.buildLineType());
assertArrayEquals(lineType, qrBuilder.buildAuxDev());
}
@Test
public void testQueryReplyBuilderOemFormatAndPort() {
QueryReplyBuilder qrBuilder = processor.getQueryReplyBuilder();
byte[] port = qrBuilder.buildOemFormat();
assertNotNull(port);
assertTrue(port.length > 0);
assertArrayEquals(port, qrBuilder.buildPort());
}
@Test
public void testQueryReplyBuilderUsableAreaAspectRatiosAndNoArgOverloads() {
QueryReplyBuilder qrBuilder = processor.getQueryReplyBuilder();
// 1. Vector Graphics enabled (3179G standard: SDH = 16, flags = 0x03)
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
byte[] uaVector = qrBuilder.buildUsableArea();
assertNotNull(uaVector);
assertEquals(19, uaVector.length);
assertEquals(0x03, uaVector[0] & 0xFF); // Flags: 12/14 bit + Graphics
assertEquals(9, uaVector[15] & 0xFF); // AW = 9
assertEquals(16, uaVector[16] & 0xFF); // AH = 16
// 2. Text mode (GraphicsMode.NONE: SDH = 12, flags = 0x01)
qrBuilder.setGraphicsMode(GraphicsMode.NONE);
byte[] uaText = qrBuilder.buildUsableArea();
assertNotNull(uaText);
assertEquals(0x01, uaText[0] & 0xFF); // Flags: 12/14 bit only
assertEquals(9, uaText[15] & 0xFF); // AW = 9
assertEquals(12, uaText[16] & 0xFF); // AH = 12
// 3. No-arg overloads check
assertNotNull(qrBuilder.buildSummary());
assertNotNull(qrBuilder.buildAlphaPartitions());
assertNotNull(qrBuilder.buildCharsets());
assertNotNull(qrBuilder.buildColor());
assertNotNull(qrBuilder.buildHighlighting());
assertNotNull(qrBuilder.buildReplyModes());
assertNotNull(qrBuilder.buildDdm());
assertNotNull(qrBuilder.buildImplicitPartition());
assertNotNull(qrBuilder.buildSegment());
assertNotNull(qrBuilder.buildProcedure());
assertNotNull(qrBuilder.buildGraphics());
assertNotNull(qrBuilder.buildGImage());
}
@Test
public void testQueryReplyBuilderColorTable18Bytes() {
QueryReplyBuilder qrBuilder = processor.getQueryReplyBuilder();
byte[] colorTable = qrBuilder.buildColor();
assertNotNull(colorTable);
assertEquals(18, colorTable.length, "Color table payload must be exactly 18 bytes matching HoD DS3270.java:1850");
assertEquals(0x00, colorTable[0]);
assertEquals(0x08, colorTable[1]);
assertEquals(0x00, colorTable[2]);
assertEquals((byte) 0xF4, colorTable[3]); // Default green
}
@Test
public void testDataStreamProcessorConvenienceOverloads() {
// Test processEraseAllUnprotected
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.getCell(1).ucs4 = 'X';
screen.getCell(1).ec = (byte) 0xE7;
processor.processEraseAllUnprotected();
assertEquals(0, screen.getCell(1).ucs4);
// Test processReadModified and processReadModifiedAll
output.reset();
processor.processReadModified();
assertTrue(output.size() >= 3);
output.reset();
processor.processReadModifiedAll();
assertTrue(output.size() >= 3);
// Test processReadBuffer
output.reset();
processor.processReadBuffer();
assertTrue(output.size() >= 3);
// Test processEraseWrite and processEraseWriteAlternate
byte[] ewData = new byte[] { (byte) CMD_EW, 0x00, (byte) ORDER_SBA, 0x40, 0x40, (byte) 0xC1 };
processor.processEraseWrite(ewData);
assertEquals('A', screen.getCell(0).ucs4);
byte[] ewaData = new byte[] { (byte) CMD_EWA, 0x00, (byte) ORDER_SBA, 0x40, 0x40, (byte) 0xC2 };
processor.processEraseWriteAlternate(ewaData);
assertEquals('B', screen.getCell(0).ucs4);
// Test processSetReplyMode, processCreatePartition, processEraseReset byte[] overloads
byte[] srm = new byte[] { (byte) CMD_WSF, 0x00, 0x05, (byte) SF_SET_REPLY_MODE, 0x00, (byte) SF_SRM_CHAR };
processor.processSetReplyMode(srm);
assertEquals((byte) SF_SRM_CHAR, screen.getReplyMode());
byte[] cp = new byte[] { (byte) CMD_WSF, 0x00, 0x08, (byte) SF_CREATE_PART, 0x01, 0x00, 80, 0x00, 24 };
processor.processCreatePartition(cp);
assertEquals(1, screen.getActivePartition());
byte[] er = new byte[] { (byte) CMD_WSF, 0x00, 0x04, (byte) SF_ERASE_RESET, (byte) SF_ER_DEFAULT };
processor.processEraseReset(er);
assertEquals(0, screen.getActivePartition());
// Test query partition overloads
output.reset();
processor.processSFReadPartitionQuery(new byte[] { (byte) CMD_WSF, 0x00, 0x05, (byte) SF_READ_PART, 0x00, (byte) SF_RP_QUERY });
assertTrue(output.size() > 0);
output.reset();
processor.processSFReadPartitionQueryList(new byte[] {
(byte) CMD_WSF, 0x00, 0x07, (byte) SF_READ_PART, 0x00, (byte) SF_RP_QLIST, (byte) SF_RPQ_LIST, (byte) QR_COLOR
});
assertTrue(output.size() > 0);
}
}
@@ -0,0 +1,128 @@
package haus.nightmare.lib3270j.ecl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import haus.nightmare.lib3270j.TerminalModel;
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
import haus.nightmare.lib3270j.input.InputProcessor;
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import static haus.nightmare.lib3270j.ecl.ECLConstants.*;
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
public class ECLOIAPhase3Test {
private ScreenBuffer screen;
private EbcdicTranslator translator;
private InputProcessor input;
private ECLOIA oia;
@BeforeEach
public void setup() {
translator = new EbcdicTranslator();
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
input = new InputProcessor(screen, translator, null);
oia = new ECLOIA(screen, input, null);
}
@Test
public void testAlphanumericTypeDetection() {
screen.erase(false);
// Unformatted screen
assertEquals(TYPE_ALPHANUMERIC, oia.getAlphanumericType());
assertEquals("A", oia.getAlphanumericTypeString());
// Numeric field
screen.setFieldAttribute(0, (byte) (FA_PRINTABLE | FA_NUMERIC));
screen.setCursorAddress(1);
assertEquals(TYPE_NUMERIC, oia.getAlphanumericType());
assertEquals("N", oia.getAlphanumericTypeString());
// Alphanumeric unprotected field
screen.setFieldAttribute(10, (byte) FA_PRINTABLE);
screen.setCursorAddress(11);
assertEquals(TYPE_ALPHANUMERIC, oia.getAlphanumericType());
assertEquals("A", oia.getAlphanumericTypeString());
// DBCS cell
ExtendedAttribute ea = screen.getCell(11);
ea.cs = ExtendedAttribute.CS_DBCS;
assertEquals(TYPE_DBCS, oia.getAlphanumericType());
assertEquals("D", oia.getAlphanumericTypeString());
}
@Test
public void testOIAStatusFlagsAndStrings() {
screen.erase(false);
assertEquals("READY", oia.getStatusString());
assertFalse(oia.isXSystem());
assertFalse(oia.isXProt());
assertFalse(oia.isXNum());
assertFalse(oia.isXComm());
assertFalse(oia.isXOverflow());
assertFalse(oia.isXOperatorDue());
// Keyboard lock -> X-SYSTEM
input.setKeyboardLocked(true);
assertTrue(oia.isXSystem());
assertTrue(oia.isXWait());
assertEquals("X-SYSTEM", oia.getStatusString());
input.setKeyboardLocked(false);
// Protected field inhibit
oia.setInputInhibited(INHIBIT_PROTECTED_FIELD);
assertTrue(oia.isXProt());
assertEquals("X-PROT", oia.getStatusString());
// Numeric only inhibit
oia.setInputInhibited(INHIBIT_NUMERIC_ONLY);
assertTrue(oia.isXNum());
assertEquals("X-NUM", oia.getStatusString());
// Overflow inhibit
oia.setInputInhibited(INHIBIT_OVERFLOW);
assertTrue(oia.isXOverflow());
assertEquals("X-OVERFLOW", oia.getStatusString());
// Comm check inhibit
oia.setInputInhibited(INHIBIT_COMM_CHECK);
assertTrue(oia.isXComm());
assertEquals("X-COMM", oia.getStatusString());
// Operator due inhibit
oia.setInputInhibited(INHIBIT_OPERATOR_DUE);
assertTrue(oia.isXOperatorDue());
assertEquals("X-OP", oia.getStatusString());
// Insert mode
oia.setInputInhibited(INHIBIT_NOT_INHIBITED);
input.setInsertMode(true);
assertTrue(oia.isXInsert());
assertEquals("X-INSERT", oia.getStatusString());
}
@Test
public void testECLFieldOperations() {
screen.erase(false);
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.setChar(0, 1, 'X');
screen.setChar(0, 2, 'Y');
screen.setFieldAttribute(10, (byte) (FA_PRINTABLE | FA_PROTECT));
ECLPS ps = new ECLPS(screen, input, translator);
ECLField f = ps.getFieldList().getFirstField();
assertNotNull(f);
assertEquals("XY ", f.getText());
assertFalse(f.isModified());
f.setModified(true);
assertTrue(f.isModified());
f.erase();
assertEquals(" ", f.getText());
assertFalse(f.isModified());
}
}
@@ -0,0 +1,124 @@
package haus.nightmare.lib3270j.ecl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import haus.nightmare.lib3270j.TerminalModel;
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
import haus.nightmare.lib3270j.input.InputProcessor;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
public class ECLPSPhase3Test implements ECLConstants {
private ScreenBuffer screen;
private EbcdicTranslator translator;
private InputProcessor input;
private ECLPS ps;
@BeforeEach
public void setup() {
translator = new EbcdicTranslator();
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
input = new InputProcessor(screen, translator, null);
ps = new ECLPS(screen, input, translator);
}
@Test
public void testSearchPSOverloadsAndCaseOptions() {
screen.erase(false);
screen.setText("Host On-Demand ECL Automation");
// 1-based SearchPS
assertEquals(6, ps.SearchPS("On-Demand"));
assertEquals(6, ps.SearchPS("on-demand", 1, 1, SEARCH_FORWARD, true));
assertEquals(0, ps.SearchPS("on-demand", 1, 1, SEARCH_FORWARD, false));
assertEquals(0, ps.SearchPS("NONEXISTENT"));
// SearchPSExt
int pos = ps.SearchPSExt("ECL", 1, 50, SEARCH_FORWARD, false, false);
assertEquals(16, pos);
}
@Test
public void testSearchStringBackwardsAndWrap() {
screen.erase(false);
screen.setChar(0, 10, 'A');
screen.setChar(0, 11, 'B');
screen.setChar(0, 12, 'C');
screen.setChar(1, 20, 'A');
screen.setChar(1, 21, 'B');
screen.setChar(1, 22, 'C');
// Backward search starting from row 1 col 0 (addr 80) should find "ABC" at row 0 col 10 (addr 10)
int found = ps.searchString("ABC", 1, 0, SEARCH_BACKWARD, false);
assertEquals(10, found);
// Backward search starting from row 0 col 5 (addr 5) wraps around and finds "ABC" at row 1 col 20 (addr 100)
int wrapped = ps.searchString("ABC", 0, 5, SEARCH_BACKWARD, false);
assertEquals(100, wrapped);
}
@Test
public void testCopyStringRectangularExtraction() {
screen.erase(false);
// Row 0: "0123456789"
for (int c = 0; c < 10; c++) {
screen.setChar(0, c, (char) ('0' + c));
}
// Row 1: "ABCDEFGHIJ"
for (int c = 0; c < 10; c++) {
screen.setChar(1, c, (char) ('A' + c));
}
// Copy columns 2..5 of rows 0..1
String block = ps.copyString(0, 2, 1, 5);
assertEquals("2345\nCDEF", block);
}
@Test
public void testPasteStringRectangular() {
screen.erase(false);
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.setFieldAttribute(80, (byte) FA_PRINTABLE);
int pasted = ps.pasteString("HELLO\nWORLD", 0, 1);
assertEquals(10, pasted);
assertEquals("HELLO", ps.getString(1, 5));
assertEquals("WORLD", ps.getString(81, 5));
}
@Test
public void testSendCharactersWithDelay() {
screen.erase(false);
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.setFieldAttribute(10, (byte) FA_PRINTABLE);
screen.setCursorAddress(1);
ps.sendCharacters("ABC[tab]DEF", 2);
assertEquals('A', screen.getChar(0, 1));
assertEquals('B', screen.getChar(0, 2));
assertEquals('C', screen.getChar(0, 3));
assertEquals('D', screen.getChar(0, 11));
assertEquals('E', screen.getChar(0, 12));
assertEquals('F', screen.getChar(0, 13));
}
@Test
public void testWaitForScreenAndCursor() {
screen.erase(false);
assertFalse(ps.waitForScreen("READY", 50));
assertFalse(ps.waitForCursor(5, 10, 50));
screen.setText("READY");
assertTrue(ps.waitForScreen("READY", 50));
assertTrue(ps.waitForScreen("READY", 0, 0, 50));
assertFalse(ps.waitForScreen("READY", 1, 0, 50));
ps.setCursorPos(5, 10);
assertTrue(ps.waitForCursor(5, 10, 50));
}
}
@@ -288,6 +288,199 @@ public class GocaDecoderPhase5Test {
assertEquals(56, sf2.length);
assertEquals(0x00, sf2[0]);
assertEquals(0x34, sf2[1]);
byte[] sf3 = GraphicInputBuilder.buildPickCorrelation(120, 240, 5);
assertEquals(56, sf3.length);
assertEquals(120, ((sf3[24] & 0xFF) << 8) | (sf3[25] & 0xFF));
assertEquals(240, ((sf3[26] & 0xFF) << 8) | (sf3[27] & 0xFF));
}
@Test
public void testViewingWindowClipping() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
// Define viewing window covering center of presentation space: [-100..100, -100..100]
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(GocaConstants.G_GSVW_DEF);
out.write(0x08);
out.write((byte) 0xFF); out.write((byte) 0x9C); // xMin = -100
out.write((byte) 0xFF); out.write((byte) 0x9C); // yMin = -100
out.write(0x00); out.write(100); // xMax = 100
out.write(0x00); out.write(100); // yMax = 100
// Draw a line inside presentation space but outside the viewing window: (-300, -150) -> (-200, -150)
out.write(GocaConstants.G_GSCOL); out.write(0x02); // Red
out.write(GocaConstants.G_GLINE); out.write(0x08);
out.write((byte) 0xFE); out.write((byte) 0xD4); out.write((byte) 0xFF); out.write((byte) 0x6A); // (-300, -150)
out.write((byte) 0xFF); out.write((byte) 0x38); out.write((byte) 0xFF); out.write((byte) 0x6A); // (-200, -150)
byte[] stream = out.toByteArray();
decoder.decodeGoca(stream, 0, stream.length);
// Pixels outside viewing window should have been clipped out
int pxClipped = plane.mapX(-300);
int pyClipped = plane.mapY(-150);
int pixelOutside = plane.getRgbBuffer()[pyClipped * plane.getCanvasWidth() + pxClipped];
assertEquals(0, pixelOutside, "Pixel outside viewing window must be 0 (clipped)");
// Now draw a line crossing the center (0, 0) -> (50, 50)
ByteArrayOutputStream insideStream = new ByteArrayOutputStream();
insideStream.write(GocaConstants.G_GLINE); insideStream.write(0x08);
insideStream.write(0x00); insideStream.write(0); insideStream.write(0x00); insideStream.write(0);
insideStream.write(0x00); insideStream.write(50); insideStream.write(0x00); insideStream.write(50);
byte[] inBytes = insideStream.toByteArray();
decoder.decodeGoca(inBytes, 0, inBytes.length);
int pxInside = plane.mapX(0);
int pyInside = plane.mapY(0);
int pixelInside = plane.getRgbBuffer()[pyInside * plane.getCanvasWidth() + pxInside];
assertNotEquals(0, pixelInside, "Pixel inside viewing window must be drawn");
}
@Test
public void testFractionalLineWidth() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Set Fractional Line Width: 2.5 (0x02 0x80)
out.write(GocaConstants.G_GSFLW);
out.write(0x02);
out.write(0x80);
byte[] stream = out.toByteArray();
decoder.decodeGoca(stream, 0, stream.length);
assertEquals(2.5, decoder.getFractionalLineWidth(), 0.01);
assertEquals(2.5, plane.getFractionalLineWidth(), 0.01);
}
@Test
public void testSegmentCharacteristics() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
// Flags = 0xC0: Chained (0x80), Dynamic (0x40), Visible (0x00)
decoder.processSegmentCharacteristics(new byte[]{(byte) 0xC0});
assertTrue(decoder.isSegChained());
assertTrue(decoder.isSegDynamic());
assertTrue(decoder.isSegVisible());
// Flags = 0x20: Invisible
decoder.processSegmentCharacteristics(new byte[]{(byte) 0x20});
assertFalse(decoder.isSegChained());
assertFalse(decoder.isSegDynamic());
assertFalse(decoder.isSegVisible());
}
@Test
public void testCharacterAngleAndShear() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Set Character Angle: 45 degrees vector (ax=10, ay=10)
out.write(GocaConstants.G_GSCA);
out.write(0x04);
out.write(0x00); out.write(10);
out.write(0x00); out.write(10);
// Set Character Shear: vector (sx=10, sy=0)
out.write(GocaConstants.G_GSCR);
out.write(0x04);
out.write(0x00); out.write(10);
out.write(0x00); out.write(0);
byte[] stream = out.toByteArray();
decoder.decodeGoca(stream, 0, stream.length);
assertEquals(45.0, decoder.getCharAngle(), 0.01);
assertEquals(90.0, decoder.getCharShear(), 0.01);
}
@Test
public void testWindingRuleAreaFill() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
FillArea fillArea = new FillArea();
fillArea.setFillRule(GocaConstants.FILL_RULE_WINDING);
assertEquals(GocaConstants.FILL_RULE_WINDING, fillArea.getFillRule());
// Fill self-intersecting bow-tie polygon
int[] px = new int[]{50, 150, 50, 150};
int[] py = new int[]{50, 150, 150, 50};
fillArea.fill(plane, px, py, 4, 0xFF00FF00, GocaConstants.PT_SOLID, false, 0, 0, 0, 2, 0);
assertTrue(plane.hasContent());
}
@Test
public void testMultiBitAndCompressedImages() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
// Test 2-bit per pixel image
byte[] twoBppData = new byte[]{(byte) 0b00011011}; // 4 pixels: 0, 1 (Blue), 2 (Red), 3 (Green)
plane.drawImage(10, 10, 4, 1, twoBppData, 0xFFFFFFFF, GocaConstants.BPP_2, GocaConstants.IMG_UNCOMPRESSED);
assertTrue(plane.hasContent());
// Test 4-bit per pixel image
byte[] fourBppData = new byte[]{(byte) 0x12, (byte) 0x34}; // 4 pixels: 1, 2, 3, 4
plane.clear();
plane.drawImage(10, 10, 4, 1, fourBppData, 0xFFFFFFFF, GocaConstants.BPP_4, GocaConstants.IMG_UNCOMPRESSED);
assertTrue(plane.hasContent());
// Test RLE decompressed image
byte[] rleData = new byte[]{
0x04, (byte) 0xFF // 4 repeats of 0xFF
};
byte[] decompressed = GraphicsPlane.decompressGocaRle(rleData, 32, 1, GocaConstants.BPP_1);
assertEquals(4, decompressed.length);
assertEquals((byte) 0xFF, decompressed[0]);
assertEquals((byte) 0xFF, decompressed[3]);
}
@Test
public void testMultiColorProgrammedSymbolsOverlay() {
ProgramSymbolManager psm = new ProgramSymbolManager(9, 16);
// Load Red plane (colorPlane = 1) into Slot 4 (LCID 0x41, Start 0x40, RWS 0x04)
byte[] loadRed = new byte[4 + 6 + 18];
loadRed[0] = (byte) 0x81; // Extended header present (0x80) | Format 1
loadRed[1] = 0x41; // LCID
loadRed[2] = 0x40; // Start codepoint
loadRed[3] = 0x04; // RWS Slot 4 (Triple Plane)
loadRed[4] = 0x06; // Ext header length = 6
loadRed[5] = 0x00;
loadRed[6] = 9; // Cell width = 9
loadRed[7] = 16; // Cell height = 16
loadRed[8] = 0x00;
loadRed[9] = 0x01; // Plane = Red (1)
for (int i = 0; i < 18; i++) loadRed[10 + i] = (byte) 0xFF;
psm.loadps(loadRed);
// Load Green plane (colorPlane = 2) into the same slot without clearing (clearAll = false)
byte[] loadGreen = new byte[4 + 6 + 18];
loadGreen[0] = (byte) 0x81;
loadGreen[1] = 0x41;
loadGreen[2] = 0x40;
loadGreen[3] = 0x04;
loadGreen[4] = 0x06;
loadGreen[5] = 0x00;
loadGreen[6] = 9;
loadGreen[7] = 16;
loadGreen[8] = 0x00;
loadGreen[9] = 0x02; // Plane = Green (2)
for (int i = 0; i < 18; i++) loadGreen[10 + i] = (byte) 0xFF;
psm.loadps(loadGreen);
ProgramSymbolSet.SymbolSlot slot = psm.getSymbol(0x41, 0x40);
assertNotNull(slot);
byte[] pixels = slot.getPixelData();
assertNotNull(pixels);
// Pixels should be composite of Red (1) | Green (2) = Yellow (3)
assertEquals(3, pixels[0] & 0xFF);
}
private static byte outCoord(int val) {
@@ -0,0 +1,323 @@
package haus.nightmare.lib3270j.input;
import haus.nightmare.lib3270j.ConnectionConfig;
import haus.nightmare.lib3270j.Telnet3270Client;
import haus.nightmare.lib3270j.TerminalModel;
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
import haus.nightmare.lib3270j.ecl.ECLConstants;
import haus.nightmare.lib3270j.ecl.ECLOIA;
import haus.nightmare.lib3270j.protocol.DS3270Constants;
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
import static org.junit.jupiter.api.Assertions.*;
public class InputProcessorPhase4Test {
private ScreenBuffer screen;
private EbcdicTranslator translator;
private InputProcessor inputProcessor;
private ECLOIA oia;
@BeforeEach
public void setUp() {
translator = new EbcdicTranslator();
screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator);
inputProcessor = new InputProcessor(screen, translator, null);
oia = new ECLOIA(screen, inputProcessor, null);
inputProcessor.setOIA(oia);
}
@Test
public void testNumericOnlyValidation() {
// Setup numeric field at pos 0 (FA), data at 1..9
screen.setFieldAttribute(0, (byte) FA_NUMERIC);
screen.setFieldAttribute(10, (byte) FA_PROTECT); // terminate field
screen.setCursorAddress(1);
// Typing valid digits and punctuation
inputProcessor.typeCharacter('1');
assertEquals(2, screen.getCursorAddress());
assertEquals('1', (char) screen.getCell(1).ucs4);
assertFalse(inputProcessor.isKeyboardLocked());
assertEquals(ECLConstants.INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
inputProcessor.typeCharacter('-');
assertEquals(3, screen.getCursorAddress());
assertEquals('-', (char) screen.getCell(2).ucs4);
inputProcessor.typeCharacter('.');
assertEquals(4, screen.getCursorAddress());
assertEquals('.', (char) screen.getCell(3).ucs4);
inputProcessor.typeCharacter(' ');
assertEquals(5, screen.getCursorAddress());
// Typing invalid non-numeric character 'A' in numeric field
inputProcessor.typeCharacter('A');
assertTrue(inputProcessor.isKeyboardLocked());
assertEquals(ECLConstants.INHIBIT_NUMERIC_ONLY, oia.getInputInhibited());
assertEquals("X-NUM", oia.getStatusString());
// Buffer position 5 should NOT have changed
assertEquals(5, screen.getCursorAddress());
assertEquals(0, screen.getCell(5).ec);
// Reset clears error and unlocks keyboard
inputProcessor.reset();
assertFalse(inputProcessor.isKeyboardLocked());
assertEquals(ECLConstants.INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
}
@Test
public void testProtectedFieldInhibition() {
// Setup protected field at pos 0, length 10
screen.setFieldAttribute(0, (byte) FA_PROTECT);
screen.setCursorAddress(1);
// Attempting to type into protected field
inputProcessor.typeCharacter('Z');
assertTrue(inputProcessor.isKeyboardLocked());
assertEquals(ECLConstants.INHIBIT_PROTECTED_FIELD, oia.getInputInhibited());
assertEquals("X-PROT", oia.getStatusString());
assertEquals(0, screen.getCell(1).ec);
// Reset unlocks
inputProcessor.reset();
assertFalse(inputProcessor.isKeyboardLocked());
assertEquals(ECLConstants.INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
}
@Test
public void testAutoSkipFieldBoundary() {
// Field 1: Unprotected at pos 0 (chars at 1, 2)
screen.setFieldAttribute(0, (byte) 0x00);
// Field 2: Auto-skip at pos 3 (FA_PROTECT | FA_NUMERIC) (chars at 4, 5)
screen.setFieldAttribute(3, (byte) (FA_PROTECT | FA_NUMERIC));
// Field 3: Unprotected at pos 6 (chars at 7, 8)
screen.setFieldAttribute(6, (byte) 0x00);
// Delimiter at pos 9
screen.setFieldAttribute(9, (byte) FA_PROTECT);
// Cursor at pos 1, type 'A' -> moves to pos 2
screen.setCursorAddress(1);
inputProcessor.typeCharacter('A');
assertEquals(2, screen.getCursorAddress());
// Type 'B' at pos 2 (the last char in Field 1) -> auto-skips over Field 2 (pos 3..5) to pos 7!
inputProcessor.typeCharacter('B');
assertEquals(7, screen.getCursorAddress());
}
@Test
public void testInsertModeFieldOverflowInhibition() {
// Unprotected field at pos 0, size 3 (positions 1, 2, 3)
screen.setFieldAttribute(0, (byte) 0x00);
screen.setFieldAttribute(4, (byte) FA_PROTECT);
screen.setCursorAddress(1);
inputProcessor.typeCharacter('A');
inputProcessor.typeCharacter('B');
inputProcessor.typeCharacter('C');
// Field is full: pos 1='A', pos 2='B', pos 3='C'
assertEquals('A', (char) screen.getCell(1).ucs4);
assertEquals('B', (char) screen.getCell(2).ucs4);
assertEquals('C', (char) screen.getCell(3).ucs4);
// Enable insert mode and attempt to type at pos 1
inputProcessor.setInsertMode(true);
screen.setCursorAddress(1);
inputProcessor.typeCharacter('X');
assertTrue(inputProcessor.isKeyboardLocked());
assertEquals(ECLConstants.INHIBIT_OVERFLOW, oia.getInputInhibited());
assertEquals("X-OVERFLOW", oia.getStatusString());
assertEquals('A', (char) screen.getCell(1).ucs4); // unchanged
}
@Test
public void testReadModifiedInboundDataFraming() {
// Create field at pos 0, type "TEST"
screen.setFieldAttribute(0, (byte) 0x00);
screen.setFieldAttribute(10, (byte) FA_PROTECT);
screen.setCursorAddress(1);
inputProcessor.typeCharacter('T');
inputProcessor.typeCharacter('E');
inputProcessor.typeCharacter('S');
inputProcessor.typeCharacter('T');
byte[] inbound = inputProcessor.buildReadModifiedInboundData(AID_ENTER, false);
assertNotNull(inbound);
assertTrue(inbound.length >= 7);
assertEquals((byte) AID_ENTER, inbound[0]); // AID
// Next 2 bytes: cursor address
// Next byte: SBA (0x11)
assertEquals((byte) ORDER_SBA, inbound[3]);
// Followed by field address (pos 1), then EBCDIC bytes for "TEST"
assertEquals((byte) translator.unicodeToEbcdic('T'), inbound[6]);
assertEquals((byte) translator.unicodeToEbcdic('E'), inbound[7]);
assertEquals((byte) translator.unicodeToEbcdic('S'), inbound[8]);
assertEquals((byte) translator.unicodeToEbcdic('T'), inbound[9]);
}
@Test
public void testAidSelectDoesNotTransmitFieldCharacters() {
// Create selectable field with designator ' ' at pos 1
screen.setFieldAttribute(0, (byte) (FA_INT_HIGH_SEL | FA_NUMERIC)); // selectable
screen.getCell(0).fa |= FA_MODIFY;
screen.getCell(1).ec = 0x40; // space designator
screen.getCell(2).ec = (byte) translator.unicodeToEbcdic('X');
screen.setFieldAttribute(10, (byte) FA_PROTECT);
screen.setCursorAddress(1);
byte[] inbound = inputProcessor.buildReadModifiedInboundData(AID_SELECT, false);
assertNotNull(inbound);
assertEquals((byte) AID_SELECT, inbound[0]);
// Must contain SBA and designator address, but NO field text bytes
assertEquals(6, inbound.length); // AID (1) + Cursor (2) + SBA (1) + Addr (2) = 6 bytes
assertEquals((byte) ORDER_SBA, inbound[3]);
}
@Test
public void testPAKeysAndClearInboundData() {
screen.setCursorAddress(80);
byte[] pa1 = inputProcessor.buildReadModifiedInboundData(AID_PA1, false);
assertEquals(3, pa1.length); // AID + 2 cursor bytes
assertEquals((byte) AID_PA1, pa1[0]);
byte[] clear = inputProcessor.buildReadModifiedInboundData(AID_CLEAR, false);
assertEquals(3, clear.length);
assertEquals((byte) AID_CLEAR, clear[0]);
}
@Test
public void testReadBufferInboundDataStandardAndExtended() {
screen.setFieldAttribute(0, (byte) 0x00);
screen.getCell(1).ec = (byte) translator.unicodeToEbcdic('A');
screen.setFieldAttribute(10, (byte) FA_PROTECT);
// Standard Field Mode
screen.setReplyMode((byte) SF_SRM_FIELD);
byte[] standardBuf = inputProcessor.buildReadBufferInboundData(AID_ENTER);
assertNotNull(standardBuf);
assertEquals((byte) AID_ENTER, standardBuf[0]);
assertEquals((byte) ORDER_SF, standardBuf[3]);
// Extended Field Mode
screen.setReplyMode((byte) SF_SRM_XFIELD);
byte[] extendedBuf = inputProcessor.buildReadBufferInboundData(AID_ENTER);
assertNotNull(extendedBuf);
assertEquals((byte) AID_ENTER, extendedBuf[0]);
assertEquals((byte) ORDER_SFE, extendedBuf[3]);
}
@Test
public void testWordLeftWordRightAndFieldEndNavigation() {
// Field: "HELLO WORLD "
screen.setFieldAttribute(0, (byte) 0x00);
screen.setFieldAttribute(25, (byte) FA_PROTECT);
String text = "HELLO WORLD ";
for (int i = 0; i < text.length(); i++) {
screen.getCell(1 + i).ec = (byte) translator.unicodeToEbcdic(text.charAt(i));
screen.getCell(1 + i).ucs4 = text.charAt(i);
}
// Test Field End
screen.setCursorAddress(1);
inputProcessor.processFieldEnd();
// Immediately following 'D' in WORLD (pos 1 + 13 = 14)
assertEquals(14, screen.getCursorAddress());
// Test Word Left from pos 14 -> jumps to start of "WORLD" (pos 9)
inputProcessor.processWordLeft();
assertEquals(9, screen.getCursorAddress());
// Test Word Left from pos 9 -> jumps to start of "HELLO" (pos 1)
inputProcessor.processWordLeft();
assertEquals(1, screen.getCursorAddress());
// Test Word Right from pos 1 -> jumps to start of "WORLD" (pos 9)
inputProcessor.processWordRight();
assertEquals(9, screen.getCursorAddress());
}
@Test
public void testAll22CompatibleMethodsAndOverloads() {
ConnectionConfig config = new ConnectionConfig("localhost", 23, TerminalModel.IBM_3279_2, false);
Telnet3270Client client = new Telnet3270Client(config);
// 1. processChar
client.processChar('A');
client.processChar('B', 5, false);
// 2. processEnter
client.processEnter();
// 3. processPF
client.processPF(3);
// 4. processPA
client.processPA(1);
// 5. processClear
client.processClear();
// 6-9. Cursor navigation
client.processCursorUp();
client.processCursorDown();
client.processCursorLeft();
client.processCursorRight();
// 10-13. Field navigation
client.processTab();
client.processBackTab();
client.processHome();
client.processNewline();
// 14-16. Editing
client.processDelete();
client.processBackspace();
client.processEraseEOF();
client.processEraseInput();
// 17-19. Special orders & toggles
client.processDup();
client.processFieldMark();
client.processToggleInsert();
// 20-22. Control / Selection
client.processReset();
client.processWordLeft();
client.processWordRight();
client.processFieldEnd();
client.processAttn();
client.processSysReq();
client.processCurSel();
client.processLightPen();
client.processLightPen(10);
assertNotNull(client.getInputProcessor().buildReadModifiedInboundData());
assertNotNull(client.getInputProcessor().buildReadBufferInboundData());
}
@Test
public void testSendKeysMnemonicTokens() {
screen.setFieldAttribute(0, (byte) 0x00);
screen.setFieldAttribute(15, (byte) 0x00);
screen.setFieldAttribute(30, (byte) FA_PROTECT);
screen.setCursorAddress(1);
inputProcessor.sendKeys("123[tab]456[wordleft][fieldend][reset]");
assertEquals('1', (char) screen.getCell(1).ucs4);
assertEquals('2', (char) screen.getCell(2).ucs4);
assertEquals('3', (char) screen.getCell(3).ucs4);
assertEquals('4', (char) screen.getCell(16).ucs4);
assertEquals('5', (char) screen.getCell(17).ucs4);
assertEquals('6', (char) screen.getCell(18).ucs4);
assertFalse(inputProcessor.isKeyboardLocked());
}
}
@@ -0,0 +1,278 @@
package haus.nightmare.lib3270j.screen;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import haus.nightmare.lib3270j.TerminalModel;
import haus.nightmare.lib3270j.charset.AbstractDBCSCodePage;
import haus.nightmare.lib3270j.charset.CodePageRegistry;
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
import haus.nightmare.lib3270j.ecl.ECLField;
import haus.nightmare.lib3270j.ecl.ECLFieldList;
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
public class ScreenBufferPhase3Test {
private ScreenBuffer screen;
private EbcdicTranslator translator;
@BeforeEach
public void setup() {
translator = new EbcdicTranslator();
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
}
@Test
public void testWrappedFieldListFirstFaNotAtZero() {
screen.erase(false);
// Place FA at pos 100 (unprotected)
screen.setFieldAttribute(100, (byte) FA_PRINTABLE);
// Place FA at pos 150 (protected) - wraps around 1919 back to 99
screen.setFieldAttribute(150, (byte) (FA_PRINTABLE | FA_PROTECT));
ECLFieldList list = screen.buildFieldList();
assertEquals(2, list.getFieldCount());
ECLField f1 = list.getFirstField();
assertNotNull(f1);
assertEquals(100, f1.getStart());
assertEquals(101, f1.getDataStart());
assertEquals(149, f1.getEnd());
assertEquals(49, f1.getLength());
assertFalse(f1.isWrapped());
assertFalse(f1.isProtected());
ECLField f2 = list.getNextField(f1);
assertNotNull(f2);
assertEquals(150, f2.getStart());
assertEquals(151, f2.getDataStart());
assertEquals(99, f2.getEnd());
assertEquals(1920 - 150 - 1 + 100, f2.getLength());
assertTrue(f2.isWrapped());
assertTrue(f2.isProtected());
// Test field containment on wrapped field
assertTrue(f2.contains(150));
assertTrue(f2.contains(151));
assertTrue(f2.contains(1919));
assertTrue(f2.contains(0));
assertTrue(f2.contains(99));
assertFalse(f2.contains(100));
assertFalse(f2.contains(120));
// Test field lookup at position
assertEquals(150, screen.findFieldAt(50).getStart());
assertEquals(100, screen.findFieldAt(120).getStart());
// Test navigation
assertEquals(150, screen.findPrevField(120).getStart());
assertEquals(150, screen.findNextField(120).getStart());
}
@Test
public void testSingleFieldScreenWrap() {
screen.erase(false);
screen.setFieldAttribute(50, (byte) FA_PRINTABLE);
ECLFieldList list = screen.buildFieldList();
assertEquals(1, list.getFieldCount());
ECLField f = list.getFirstField();
assertNotNull(f);
assertEquals(50, f.getStart());
assertEquals(51, f.getDataStart());
assertEquals(49, f.getEnd());
assertEquals(1919, f.getLength());
assertTrue(f.isWrapped());
assertTrue(f.contains(0));
assertTrue(f.contains(50));
assertTrue(f.contains(1919));
}
@Test
public void testInsertCharSBCSAndOverflow() {
screen.erase(false);
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.setFieldAttribute(6, (byte) (FA_PRINTABLE | FA_PROTECT)); // field of 5 chars (pos 1..5)
// Write 'A', 'B', 'C', 'D' at pos 1..4 (pos 5 is null)
screen.setChar(0, 1, 'A');
screen.setChar(0, 2, 'B');
screen.setChar(0, 3, 'C');
screen.setChar(0, 4, 'D');
// Insert 'Z' at pos 1
boolean inserted = screen.insertChar(1, 'Z');
assertTrue(inserted);
assertEquals('Z', screen.getChar(0, 1));
assertEquals('A', screen.getChar(0, 2));
assertEquals('B', screen.getChar(0, 3));
assertEquals('C', screen.getChar(0, 4));
assertEquals('D', screen.getChar(0, 5));
// Field is now full (pos 5 is 'D'). Inserting another char should overflow and return false
boolean overflow = screen.insertChar(1, 'X');
assertFalse(overflow);
}
@Test
public void testInsertAndShiftDBCSChar() {
EbcdicTranslator dbcsTranslator = new EbcdicTranslator("930");
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) dbcsTranslator.getCodePage();
cp.registerDbcsPair(0x4341, '\u6771'); // '東'
ScreenBuffer dbcsScreen = new ScreenBuffer(TerminalModel.IBM_3279_2, dbcsTranslator);
dbcsScreen.erase(false);
dbcsScreen.setFieldAttribute(0, (byte) FA_PRINTABLE);
dbcsScreen.setFieldAttribute(10, (byte) (FA_PRINTABLE | FA_PROTECT));
// Insert DBCS char at pos 1
boolean ok = dbcsScreen.insertChar(1, '\u6771');
assertTrue(ok);
assertEquals(0x43, dbcsScreen.getCell(1).ec & 0xFF);
assertEquals(0x41, dbcsScreen.getCell(2).ec & 0xFF);
assertEquals(ExtendedAttribute.CS_DBCS, dbcsScreen.getCell(1).cs);
assertEquals(ExtendedAttribute.DB_LEFT, dbcsScreen.getCell(1).db);
assertEquals(ExtendedAttribute.DB_RIGHT, dbcsScreen.getCell(2).db);
assertEquals(3, dbcsScreen.getCursorAddress());
}
@Test
public void testDeleteCharDBCSAndCleanAdjacentSISO() {
EbcdicTranslator dbcsTranslator = new EbcdicTranslator("930");
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) dbcsTranslator.getCodePage();
cp.registerDbcsPair(0x4341, '\u6771');
ScreenBuffer dbcsScreen = new ScreenBuffer(TerminalModel.IBM_3279_2, dbcsTranslator);
dbcsScreen.erase(false);
dbcsScreen.setFieldAttribute(0, (byte) FA_PRINTABLE);
dbcsScreen.setFieldAttribute(10, (byte) (FA_PRINTABLE | FA_PROTECT));
// Insert DBCS character at pos 1
dbcsScreen.insertChar(1, '\u6771');
// Delete DBCS character at pos 1
boolean deleted = dbcsScreen.deleteChar(1);
assertTrue(deleted);
assertEquals(0, dbcsScreen.getCell(1).ec);
assertEquals(0, dbcsScreen.getCell(2).ec);
// Test orphaned SO/SI cleanup on delete
dbcsScreen.setCell(3, 0x0E); // SO
dbcsScreen.setCell(4, 0x43); // DBCS byte 1
dbcsScreen.setCell(5, 0x41); // DBCS byte 2
dbcsScreen.setCell(6, 0x0F); // SI
dbcsScreen.getCell(4).cs = ExtendedAttribute.CS_DBCS;
dbcsScreen.getCell(4).db = ExtendedAttribute.DB_LEFT;
dbcsScreen.getCell(5).cs = ExtendedAttribute.CS_DBCS;
dbcsScreen.getCell(5).db = ExtendedAttribute.DB_RIGHT;
// Deleting the DBCS character at pos 4 pulls SI (pos 6) adjacent to SO (pos 3), triggering SISO cleanup
dbcsScreen.deleteChar(4);
assertEquals(0, dbcsScreen.getCell(3).ec);
assertEquals(0, dbcsScreen.getCell(4).ec);
assertEquals(0, dbcsScreen.getCell(5).ec);
assertEquals(0, dbcsScreen.getCell(6).ec);
}
@Test
public void testEntryAssistDOCModeAndTabStops() {
screen.erase(false);
assertFalse(screen.isEntryAssistDOCmode());
assertFalse(screen.isEntryAssistWordWrap());
screen.setEntryAssistDOCmode(true);
screen.setEntryAssistWordWrap(true);
screen.setEntryAssistStartColumn(5);
screen.setEntryAssistEndColumn(75);
screen.setEntryAssistTabStops(new int[]{ 10, 20, 30, 40 });
assertTrue(screen.isEntryAssistDOCmode());
assertTrue(screen.isEntryAssistWordWrap());
assertEquals(5, screen.getEntryAssistStartColumn());
assertEquals(75, screen.getEntryAssistEndColumn());
assertArrayEquals(new int[]{ 10, 20, 30, 40 }, screen.getEntryAssistTabStops());
// Test word tab forward with tab stops
screen.setCursorPosition(0, 0);
screen.processWordTab(true);
assertEquals(10, screen.getCursorCol());
screen.processWordTab(true);
assertEquals(20, screen.getCursorCol());
// Test word tab backward
screen.processWordTab(false);
assertEquals(10, screen.getCursorCol());
}
@Test
public void testProcessDeleteWord() {
screen.erase(false);
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.setFieldAttribute(30, (byte) (FA_PRINTABLE | FA_PROTECT));
// Write "HELLO WORLD" starting at pos 1
String text = "HELLO WORLD";
for (int i = 0; i < text.length(); i++) {
screen.setChar(0, 1 + i, text.charAt(i));
}
screen.setCursorAddress(1);
screen.processDeleteWord();
// "HELLO " is deleted, "WORLD" is shifted left to pos 1
assertEquals('W', screen.getChar(0, 1));
assertEquals('O', screen.getChar(0, 2));
assertEquals('R', screen.getChar(0, 3));
assertEquals('L', screen.getChar(0, 4));
assertEquals('D', screen.getChar(0, 5));
}
@Test
public void testProcessSOSIDisplayTransformation() {
EbcdicTranslator dbcsTranslator = new EbcdicTranslator("930");
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) dbcsTranslator.getCodePage();
cp.registerDbcsPair(0x4341, '\u6771');
ScreenBuffer dbcsScreen = new ScreenBuffer(TerminalModel.IBM_3279_2, dbcsTranslator);
dbcsScreen.erase(false);
// Put SO (0x0E), 0x43, 0x41, SI (0x0F) at pos 0..3
dbcsScreen.setCell(0, 0x0E);
dbcsScreen.setCell(1, 0x43);
dbcsScreen.setCell(2, 0x41);
dbcsScreen.setCell(3, 0x0F);
dbcsScreen.processSOSI();
assertEquals(ExtendedAttribute.DB_SO, dbcsScreen.getCell(0).db);
assertEquals(ExtendedAttribute.DB_LEFT, dbcsScreen.getCell(1).db);
assertEquals(ExtendedAttribute.DB_RIGHT, dbcsScreen.getCell(2).db);
assertEquals(ExtendedAttribute.DB_SI, dbcsScreen.getCell(3).db);
assertEquals('\u6771', dbcsScreen.getCell(1).ucs4);
assertEquals('\u6771', dbcsScreen.getCell(2).ucs4);
}
@Test
public void testAccessorsAndConvenienceMethods() {
screen.erase(false);
assertEquals(24 * 80, screen.getSize());
screen.writeChar(10, (byte) 0xC1); // 'A' in CP037
assertEquals('A', screen.getChar(0, 10));
screen.setChar(1, 5, 'Z');
assertEquals('Z', screen.getChar(1, 5));
ExtendedAttribute ea = new ExtendedAttribute();
ea.fg = 2; // RED
screen.setExtAttr(1, 5, ea);
assertEquals(2, screen.getExtAttr(1, 5).fg);
screen.setText("TESTING 123");
assertEquals("TESTING 123", screen.getString(0, 11));
assertEquals(0, screen.searchString("TESTING"));
assertEquals(-1, screen.searchString("NOTFOUND"));
}
}