ac6502 1.9.3 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ describe('KeyboardEncoderAttachment', () => {
17
17
  });
18
18
  it('should be disabled by default (CA2/CB2 high)', () => {
19
19
  encoder.updateControlLines(false, true, false, true);
20
- encoder.updateKey(0x04, true); // 'a'
20
+ encoder.updateKey(0x04, true); // 'A'
21
21
  expect(encoder.hasCA1Interrupt()).toBe(false);
22
22
  expect(encoder.hasCB1Interrupt()).toBe(false);
23
23
  });
@@ -27,45 +27,53 @@ describe('KeyboardEncoderAttachment', () => {
27
27
  });
28
28
  describe('Reset', () => {
29
29
  it('should clear all data and states', () => {
30
- // Enable and generate some data
31
30
  encoder.updateControlLines(false, false, false, false);
32
- encoder.updateKey(0x04, true); // 'a'
31
+ encoder.updateKey(0x04, true); // 'A'
33
32
  expect(encoder.hasDataReadyA()).toBe(true);
34
- // Reset
35
33
  encoder.reset();
36
34
  expect(encoder.hasDataReadyA()).toBe(false);
37
35
  expect(encoder.hasDataReadyB()).toBe(false);
38
36
  expect(encoder.hasCA1Interrupt()).toBe(false);
39
37
  expect(encoder.hasCB1Interrupt()).toBe(false);
40
38
  });
39
+ it('should clear modifier states on reset', () => {
40
+ encoder.updateControlLines(false, false, false, false);
41
+ // Press Shift, then reset
42
+ encoder.updateKey(0xE1, true); // Left Shift down
43
+ encoder.reset();
44
+ encoder.updateControlLines(false, false, false, false);
45
+ // After reset, Shift should be released - numbers produce numbers, not symbols
46
+ encoder.updateKey(0x1E, true); // '1'
47
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x31); // '1', not '!'
48
+ });
41
49
  });
42
50
  describe('Enable/Disable Control', () => {
43
51
  it('should enable Port A when CA2 is LOW', () => {
44
52
  encoder.updateControlLines(false, false, false, true);
45
- encoder.updateKey(0x04, true); // 'a'
53
+ encoder.updateKey(0x04, true); // 'A'
46
54
  expect(encoder.hasCA1Interrupt()).toBe(true);
47
55
  expect(encoder.hasCB1Interrupt()).toBe(false);
48
56
  });
49
57
  it('should enable Port B when CB2 is LOW', () => {
50
58
  encoder.updateControlLines(false, true, false, false);
51
- encoder.updateKey(0x04, true); // 'a'
59
+ encoder.updateKey(0x04, true); // 'A'
52
60
  expect(encoder.hasCA1Interrupt()).toBe(false);
53
61
  expect(encoder.hasCB1Interrupt()).toBe(true);
54
62
  });
55
63
  it('should enable both ports when both CA2 and CB2 are LOW', () => {
56
64
  encoder.updateControlLines(false, false, false, false);
57
- encoder.updateKey(0x04, true); // 'a'
65
+ encoder.updateKey(0x04, true); // 'A'
58
66
  expect(encoder.hasCA1Interrupt()).toBe(true);
59
67
  expect(encoder.hasCB1Interrupt()).toBe(true);
60
68
  });
61
69
  it('should disable Port A when CA2 is HIGH', () => {
62
70
  encoder.updateControlLines(false, true, false, false);
63
- encoder.updateKey(0x04, true); // 'a'
71
+ encoder.updateKey(0x04, true); // 'A'
64
72
  expect(encoder.hasCA1Interrupt()).toBe(false);
65
73
  });
66
74
  it('should disable Port B when CB2 is HIGH', () => {
67
75
  encoder.updateControlLines(false, false, false, true);
68
- encoder.updateKey(0x04, true); // 'a'
76
+ encoder.updateKey(0x04, true); // 'A'
69
77
  expect(encoder.hasCB1Interrupt()).toBe(false);
70
78
  });
71
79
  });
@@ -80,47 +88,47 @@ describe('KeyboardEncoderAttachment', () => {
80
88
  });
81
89
  it('should return ASCII data when data ready on Port A', () => {
82
90
  encoder.updateControlLines(false, false, false, false);
83
- encoder.updateKey(0x04, true); // 'a' = 0x61
91
+ encoder.updateKey(0x04, true); // 'A' = 0x41
84
92
  const value = encoder.readPortA(0xFF, 0x00);
85
- expect(value).toBe(0x61);
93
+ expect(value).toBe(0x41);
86
94
  });
87
95
  it('should return ASCII data when data ready on Port B', () => {
88
96
  encoder.updateControlLines(false, false, false, false);
89
- encoder.updateKey(0x04, true); // 'a' = 0x61
97
+ encoder.updateKey(0x04, true); // 'A' = 0x41
90
98
  const value = encoder.readPortB(0xFF, 0x00);
91
- expect(value).toBe(0x61);
99
+ expect(value).toBe(0x41);
92
100
  });
93
101
  it('should return 0xFF on disabled port even with data ready', () => {
94
102
  encoder.updateControlLines(false, true, false, true); // Both disabled
95
- encoder.updateKey(0x04, true); // 'a'
103
+ encoder.updateKey(0x04, true); // 'A'
96
104
  expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFF);
97
105
  expect(encoder.readPortB(0xFF, 0x00)).toBe(0xFF);
98
106
  });
99
107
  it('should provide same data on both ports', () => {
100
108
  encoder.updateControlLines(false, false, false, false);
101
- encoder.updateKey(0x04, true); // 'a'
109
+ encoder.updateKey(0x04, true); // 'A'
102
110
  const valueA = encoder.readPortA(0xFF, 0x00);
103
111
  const valueB = encoder.readPortB(0xFF, 0x00);
104
112
  expect(valueA).toBe(valueB);
105
- expect(valueA).toBe(0x61);
113
+ expect(valueA).toBe(0x41);
106
114
  });
107
115
  });
108
116
  describe('Interrupt Handling', () => {
109
117
  it('should trigger CA1 interrupt when Port A enabled and key pressed', () => {
110
118
  encoder.updateControlLines(false, false, false, true);
111
119
  expect(encoder.hasCA1Interrupt()).toBe(false);
112
- encoder.updateKey(0x04, true); // 'a'
120
+ encoder.updateKey(0x04, true); // 'A'
113
121
  expect(encoder.hasCA1Interrupt()).toBe(true);
114
122
  });
115
123
  it('should trigger CB1 interrupt when Port B enabled and key pressed', () => {
116
124
  encoder.updateControlLines(false, true, false, false);
117
125
  expect(encoder.hasCB1Interrupt()).toBe(false);
118
- encoder.updateKey(0x04, true); // 'a'
126
+ encoder.updateKey(0x04, true); // 'A'
119
127
  expect(encoder.hasCB1Interrupt()).toBe(true);
120
128
  });
121
129
  it('should clear CA1 interrupt and data ready when cleared', () => {
122
130
  encoder.updateControlLines(false, false, false, false);
123
- encoder.updateKey(0x04, true); // 'a'
131
+ encoder.updateKey(0x04, true); // 'A'
124
132
  expect(encoder.hasCA1Interrupt()).toBe(true);
125
133
  expect(encoder.hasDataReadyA()).toBe(true);
126
134
  encoder.clearInterrupts(true, false, false, false);
@@ -129,7 +137,7 @@ describe('KeyboardEncoderAttachment', () => {
129
137
  });
130
138
  it('should clear CB1 interrupt and data ready when cleared', () => {
131
139
  encoder.updateControlLines(false, false, false, false);
132
- encoder.updateKey(0x04, true); // 'a'
140
+ encoder.updateKey(0x04, true); // 'A'
133
141
  expect(encoder.hasCB1Interrupt()).toBe(true);
134
142
  expect(encoder.hasDataReadyB()).toBe(true);
135
143
  encoder.clearInterrupts(false, false, true, false);
@@ -138,21 +146,52 @@ describe('KeyboardEncoderAttachment', () => {
138
146
  });
139
147
  it('should not trigger interrupt when port is disabled', () => {
140
148
  encoder.updateControlLines(false, true, false, true); // Both disabled
141
- encoder.updateKey(0x04, true); // 'a'
149
+ encoder.updateKey(0x04, true); // 'A'
142
150
  expect(encoder.hasCA1Interrupt()).toBe(false);
143
151
  expect(encoder.hasCB1Interrupt()).toBe(false);
144
152
  });
145
153
  });
146
- describe('Basic Key Mapping', () => {
154
+ describe('Letter Key Mapping', () => {
147
155
  beforeEach(() => {
148
156
  encoder.updateControlLines(false, false, false, false);
149
157
  });
150
- it('should map lowercase letters correctly', () => {
151
- encoder.updateKey(0x04, true); // 'a'
152
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x61);
158
+ it('should output uppercase letters A-Z', () => {
159
+ // 'A' (HID 0x04)
160
+ encoder.updateKey(0x04, true);
161
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41);
162
+ encoder.clearInterrupts(true, false, true, false);
163
+ // 'Z' (HID 0x1D)
164
+ encoder.updateKey(0x1D, true);
165
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x5A);
166
+ });
167
+ it('should output uppercase letters even with Shift held', () => {
168
+ encoder.updateKey(0xE1, true); // Left Shift down
169
+ encoder.updateKey(0x04, true); // 'A'
170
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41); // Still uppercase A
153
171
  encoder.clearInterrupts(true, false, true, false);
154
- encoder.updateKey(0x1D, true); // 'z'
155
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x7A);
172
+ encoder.updateKey(0x10, true); // 'M'
173
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x4D); // Still uppercase M
174
+ });
175
+ it('should map all letters correctly', () => {
176
+ const letterMap = [
177
+ [0x04, 0x41], [0x05, 0x42], [0x06, 0x43], [0x07, 0x44],
178
+ [0x08, 0x45], [0x09, 0x46], [0x0A, 0x47], [0x0B, 0x48],
179
+ [0x0C, 0x49], [0x0D, 0x4A], [0x0E, 0x4B], [0x0F, 0x4C],
180
+ [0x10, 0x4D], [0x11, 0x4E], [0x12, 0x4F], [0x13, 0x50],
181
+ [0x14, 0x51], [0x15, 0x52], [0x16, 0x53], [0x17, 0x54],
182
+ [0x18, 0x55], [0x19, 0x56], [0x1A, 0x57], [0x1B, 0x58],
183
+ [0x1C, 0x59], [0x1D, 0x5A],
184
+ ];
185
+ for (const [hid, ascii] of letterMap) {
186
+ encoder.clearInterrupts(true, false, true, false);
187
+ encoder.updateKey(hid, true);
188
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(ascii);
189
+ }
190
+ });
191
+ });
192
+ describe('Number Key Mapping', () => {
193
+ beforeEach(() => {
194
+ encoder.updateControlLines(false, false, false, false);
156
195
  });
157
196
  it('should map numbers correctly', () => {
158
197
  encoder.updateKey(0x1E, true); // '1'
@@ -161,618 +200,334 @@ describe('KeyboardEncoderAttachment', () => {
161
200
  encoder.updateKey(0x27, true); // '0'
162
201
  expect(encoder.readPortA(0xFF, 0x00)).toBe(0x30);
163
202
  });
164
- it('should map special keys correctly', () => {
165
- encoder.updateKey(0x28, true); // Enter
166
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x0D);
167
- encoder.clearInterrupts(true, false, true, false);
168
- encoder.updateKey(0x29, true); // Escape
169
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1B);
170
- encoder.clearInterrupts(true, false, true, false);
171
- encoder.updateKey(0x2C, true); // Space
172
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x20);
173
- });
174
- it('should ignore key releases', () => {
175
- encoder.updateKey(0x04, true); // Press 'a'
176
- expect(encoder.hasDataReadyA()).toBe(true);
177
- encoder.clearInterrupts(true, false, true, false);
178
- encoder.updateKey(0x04, false); // Release 'a'
179
- expect(encoder.hasDataReadyA()).toBe(false); // No new data
180
- });
181
- it('should ignore unknown keycodes', () => {
182
- encoder.updateKey(0xFF, true); // Invalid keycode
183
- expect(encoder.hasDataReadyA()).toBe(false);
203
+ it('should map all number keys 0-9', () => {
204
+ const numberMap = [
205
+ [0x1E, 0x31], [0x1F, 0x32], [0x20, 0x33], [0x21, 0x34],
206
+ [0x22, 0x35], [0x23, 0x36], [0x24, 0x37], [0x25, 0x38],
207
+ [0x26, 0x39], [0x27, 0x30],
208
+ ];
209
+ for (const [hid, ascii] of numberMap) {
210
+ encoder.clearInterrupts(true, false, true, false);
211
+ encoder.updateKey(hid, true);
212
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(ascii);
213
+ }
184
214
  });
185
215
  });
186
- describe('Modifier Keys', () => {
216
+ describe('Special Key Mapping', () => {
187
217
  beforeEach(() => {
188
218
  encoder.updateControlLines(false, false, false, false);
189
219
  });
190
- it('should not generate output for modifier keys alone', () => {
191
- encoder.updateKey(0xE0, true); // Left Ctrl
192
- expect(encoder.hasDataReadyA()).toBe(false);
193
- encoder.updateKey(0xE1, true); // Left Shift
194
- expect(encoder.hasDataReadyA()).toBe(false);
195
- encoder.updateKey(0xE2, true); // Left Alt
196
- expect(encoder.hasDataReadyA()).toBe(false);
197
- });
198
- it('should track modifier key state across presses', () => {
199
- // Press Shift
200
- encoder.updateKey(0xE1, true);
201
- encoder.updateKey(0x04, true); // 'a' -> 'A'
202
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41);
203
- // Release Shift
204
- encoder.clearInterrupts(true, false, true, false);
205
- encoder.updateKey(0xE1, false);
206
- encoder.updateKey(0x04, true); // 'a'
207
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x61);
208
- });
209
- it('should handle both left and right modifiers', () => {
210
- // Left Ctrl
211
- encoder.updateKey(0xE0, true);
212
- encoder.updateKey(0x04, true); // Ctrl+a
213
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x01);
214
- encoder.clearInterrupts(true, false, true, false);
215
- encoder.updateKey(0xE0, false);
216
- // Right Ctrl
217
- encoder.updateKey(0xE4, true);
218
- encoder.updateKey(0x04, true); // Ctrl+a
219
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x01);
220
+ it('should map Enter to CR (0x0D)', () => {
221
+ encoder.updateKey(0x28, true);
222
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x0D);
220
223
  });
221
- });
222
- describe('Shift Key Mapping', () => {
223
- beforeEach(() => {
224
- encoder.updateControlLines(false, false, false, false);
224
+ it('should map Escape to ESC (0x1B)', () => {
225
+ encoder.updateKey(0x29, true);
226
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1B);
225
227
  });
226
- it('should map Shift+letter to uppercase', () => {
227
- encoder.updateKey(0xE1, true); // Press Shift
228
- encoder.updateKey(0x04, true); // 'a' -> 'A'
229
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41);
228
+ it('should map Backspace to BS (0x08)', () => {
229
+ encoder.updateKey(0x2A, true);
230
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x08);
230
231
  });
231
- it('should map Shift+number to symbols', () => {
232
- encoder.updateKey(0xE1, true); // Press Shift
233
- encoder.updateKey(0x1E, true); // '1' -> '!'
234
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x21);
235
- encoder.clearInterrupts(true, false, true, false);
236
- encoder.updateKey(0x25, true); // '8' -> '*'
237
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x2A);
232
+ it('should map Tab to HT (0x09)', () => {
233
+ encoder.updateKey(0x2B, true);
234
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x09);
238
235
  });
239
- it('should map Shift+special keys to shifted symbols', () => {
240
- encoder.updateKey(0xE1, true); // Press Shift
241
- encoder.updateKey(0x2D, true); // '-' -> '_'
242
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x5F);
243
- encoder.clearInterrupts(true, false, true, false);
244
- encoder.updateKey(0x2E, true); // '=' -> '+'
245
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x2B);
246
- encoder.clearInterrupts(true, false, true, false);
247
- encoder.updateKey(0x2F, true); // '[' -> '{'
248
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x7B);
236
+ it('should map Space to SP (0x20)', () => {
237
+ encoder.updateKey(0x2C, true);
238
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x20);
249
239
  });
250
- });
251
- describe('Ctrl Key Mapping', () => {
252
- beforeEach(() => {
253
- encoder.updateControlLines(false, false, false, false);
240
+ it('should map Delete to DEL (0x7F)', () => {
241
+ encoder.updateKey(0x4C, true);
242
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x7F);
254
243
  });
255
- it('should map Ctrl+letter to control codes', () => {
256
- encoder.updateKey(0xE0, true); // Press Ctrl
257
- encoder.updateKey(0x04, true); // Ctrl+a -> 0x01
258
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x01);
259
- encoder.clearInterrupts(true, false, true, false);
260
- encoder.updateKey(0x1D, true); // Ctrl+z -> 0x1A
244
+ it('should map Insert to SUB (0x1A)', () => {
245
+ encoder.updateKey(0x49, true);
261
246
  expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1A);
262
247
  });
263
- it('should map Ctrl+special keys to control codes', () => {
264
- encoder.updateKey(0xE0, true); // Press Ctrl
265
- encoder.updateKey(0x2F, true); // Ctrl+[ -> ESC (0x1B)
266
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1B);
248
+ it('should map arrow keys correctly', () => {
249
+ encoder.updateKey(0x4F, true); // Right Arrow
250
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1D);
267
251
  encoder.clearInterrupts(true, false, true, false);
268
- encoder.updateKey(0x31, true); // Ctrl+\ -> FS (0x1C)
252
+ encoder.updateKey(0x50, true); // Left Arrow
269
253
  expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1C);
270
254
  encoder.clearInterrupts(true, false, true, false);
271
- encoder.updateKey(0x30, true); // Ctrl+] -> GS (0x1D)
272
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1D);
273
- });
274
- it('should map Ctrl+2 to NUL', () => {
275
- encoder.updateKey(0xE0, true); // Press Ctrl
276
- encoder.updateKey(0x1F, true); // Ctrl+2 -> 0x00
277
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x00);
278
- });
279
- it('should map Ctrl+6 to RS (UP arrow)', () => {
280
- encoder.updateKey(0xE0, true); // Press Ctrl
281
- encoder.updateKey(0x23, true); // Ctrl+6 -> 0x1E
282
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1E);
283
- });
284
- it('should map Ctrl+- to US (DOWN arrow)', () => {
285
- encoder.updateKey(0xE0, true); // Press Ctrl
286
- encoder.updateKey(0x2D, true); // Ctrl+- -> 0x1F
255
+ encoder.updateKey(0x51, true); // Down Arrow
287
256
  expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1F);
288
- });
289
- });
290
- describe('MENU Key Mapping', () => {
291
- beforeEach(() => {
292
- encoder.updateControlLines(false, false, false, false);
293
- });
294
- it('should map MENU key to 0x80', () => {
295
- encoder.updateKey(0xE3, true); // Left GUI (MENU)
296
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x80);
297
- });
298
- it('should map Alt+MENU to 0x90', () => {
299
- encoder.updateKey(0xE2, true); // Press Alt
300
- encoder.updateKey(0xE3, true); // MENU
301
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x90);
302
- });
303
- it('should handle Right GUI as MENU', () => {
304
- encoder.updateKey(0xE7, true); // Right GUI (MENU)
305
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x80);
306
- });
307
- });
308
- describe('Function Key Mapping', () => {
309
- beforeEach(() => {
310
- encoder.updateControlLines(false, false, false, false);
311
- });
312
- it('should map F1-F12 to 0x81-0x8C', () => {
313
- encoder.updateKey(0x3A, true); // F1
314
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x81);
315
- encoder.clearInterrupts(true, false, true, false);
316
- encoder.updateKey(0x3B, true); // F2
317
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x82);
318
- encoder.clearInterrupts(true, false, true, false);
319
- encoder.updateKey(0x45, true); // F12
320
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x8C);
321
- });
322
- it('should map F13-F15 to 0x8D-0x8F', () => {
323
- encoder.updateKey(0x68, true); // F13
324
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x8D);
325
- encoder.clearInterrupts(true, false, true, false);
326
- encoder.updateKey(0x6A, true); // F15
327
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x8F);
328
- });
329
- it('should map Alt+F1-F12 to 0x91-0x9C', () => {
330
- encoder.updateKey(0xE2, true); // Press Alt
331
- encoder.updateKey(0x3A, true); // Alt+F1
332
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x91);
333
- encoder.clearInterrupts(true, false, true, false);
334
- encoder.updateKey(0x45, true); // Alt+F12
335
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x9C);
336
- });
337
- it('should map Alt+F13-F15 to 0x9D-0x9F', () => {
338
- encoder.updateKey(0xE2, true); // Press Alt
339
- encoder.updateKey(0x68, true); // Alt+F13
340
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x9D);
341
- encoder.clearInterrupts(true, false, true, false);
342
- encoder.updateKey(0x6A, true); // Alt+F15
343
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x9F);
344
- });
345
- });
346
- describe('Alt Key Mapping', () => {
347
- beforeEach(() => {
348
- encoder.updateControlLines(false, false, false, false);
349
- });
350
- it('should map Alt+letter to extended character set', () => {
351
- encoder.updateKey(0xE2, true); // Press Alt
352
- encoder.updateKey(0x04, true); // Alt+a -> 0xE1
353
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xE1);
354
- encoder.clearInterrupts(true, false, true, false);
355
- encoder.updateKey(0x1D, true); // Alt+z -> 0xFA
356
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFA);
357
- });
358
- it('should map Alt+number to extended character set', () => {
359
- encoder.updateKey(0xE2, true); // Press Alt
360
- encoder.updateKey(0x1E, true); // Alt+1 -> 0xB1
361
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xB1);
362
257
  encoder.clearInterrupts(true, false, true, false);
363
- encoder.updateKey(0x27, true); // Alt+0 -> 0xB0
364
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xB0);
365
- });
366
- it('should map Alt+Space to 0xA0', () => {
367
- encoder.updateKey(0xE2, true); // Press Alt
368
- encoder.updateKey(0x2C, true); // Alt+Space -> 0xA0
369
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA0);
370
- });
371
- it('should map Alt+DEL to 0xFF', () => {
372
- encoder.updateKey(0xE2, true); // Press Alt
373
- encoder.updateKey(0x4C, true); // Alt+DEL -> 0xFF
374
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFF);
375
- });
376
- });
377
- describe('Alt+Shift Key Mapping', () => {
378
- beforeEach(() => {
379
- encoder.updateControlLines(false, false, false, false);
380
- });
381
- it('should map Alt+Shift+letter to extended character set', () => {
382
- encoder.updateKey(0xE2, true); // Press Alt
383
- encoder.updateKey(0xE1, true); // Press Shift
384
- encoder.updateKey(0x04, true); // Alt+Shift+a -> 0xC1
385
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC1);
386
- encoder.clearInterrupts(true, false, true, false);
387
- encoder.updateKey(0x1D, true); // Alt+Shift+z -> 0xDA
388
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xDA);
389
- });
390
- it('should map Alt+Shift+number to extended character set', () => {
391
- encoder.updateKey(0xE2, true); // Press Alt
392
- encoder.updateKey(0xE1, true); // Press Shift
393
- encoder.updateKey(0x1E, true); // Alt+Shift+1 -> 0xA1
394
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA1);
395
- encoder.clearInterrupts(true, false, true, false);
396
- encoder.updateKey(0x1F, true); // Alt+Shift+2 -> 0xC0
397
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC0);
398
- });
399
- it('should map Alt+Shift+symbols to extended character set', () => {
400
- encoder.updateKey(0xE2, true); // Press Alt
401
- encoder.updateKey(0xE1, true); // Press Shift
402
- encoder.updateKey(0x2D, true); // Alt+Shift+- -> 0xDF
403
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xDF);
404
- encoder.clearInterrupts(true, false, true, false);
405
- encoder.updateKey(0x2F, true); // Alt+Shift+[ -> 0xFB
406
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFB);
407
- });
408
- });
409
- describe('Complex Key Combinations', () => {
410
- beforeEach(() => {
411
- encoder.updateControlLines(false, false, false, false);
412
- });
413
- it('should handle Ctrl+C combination', () => {
414
- encoder.updateKey(0xE0, true); // Press Ctrl
415
- encoder.updateKey(0x06, true); // c
416
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x03); // ETX
417
- });
418
- it('should prioritize Ctrl over Shift', () => {
419
- encoder.updateKey(0xE0, true); // Press Ctrl
420
- encoder.updateKey(0xE1, true); // Press Shift
421
- encoder.updateKey(0x04, true); // a
422
- // When both Ctrl and Shift are pressed, shift is ignored for Ctrl combinations
423
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x01); // Ctrl+a
424
- });
425
- it('should prioritize Alt+Shift over Alt alone', () => {
426
- encoder.updateKey(0xE2, true); // Press Alt
427
- encoder.updateKey(0xE1, true); // Press Shift
428
- encoder.updateKey(0x04, true); // a
429
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC1); // Alt+Shift+a, not Alt+a (0xE1)
430
- });
431
- it('should apply Alt when both Ctrl and Alt are active', () => {
432
- encoder.updateKey(0xE0, true); // Press Ctrl
433
- encoder.updateKey(0xE2, true); // Press Alt
434
- encoder.updateKey(0x04, true); // a
435
- // Alt takes effect when both Ctrl and Alt are pressed (per C++ implementation)
436
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xE1); // Alt+a
437
- });
438
- });
439
- describe('Sequential Key Presses', () => {
440
- beforeEach(() => {
441
- encoder.updateControlLines(false, false, false, false);
442
- });
443
- it('should handle multiple sequential key presses', () => {
444
- encoder.updateKey(0x04, true); // 'a'
445
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x61);
446
- encoder.clearInterrupts(true, false, true, false);
447
- encoder.updateKey(0x05, true); // 'b'
448
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x62);
449
- encoder.clearInterrupts(true, false, true, false);
450
- encoder.updateKey(0x06, true); // 'c'
451
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x63);
258
+ encoder.updateKey(0x52, true); // Up Arrow
259
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1E);
452
260
  });
453
- it('should overwrite previous data with new key press', () => {
454
- encoder.updateKey(0x04, true); // 'a'
455
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x61);
456
- // New key press without clearing interrupts
457
- encoder.updateKey(0x05, true); // 'b'
458
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x62); // Overwritten
261
+ it('should map symbol keys correctly', () => {
262
+ const symbolMap = [
263
+ [0x2D, 0x2D], // -
264
+ [0x2E, 0x3D], // =
265
+ [0x2F, 0x5B], // [
266
+ [0x30, 0x5D], // ]
267
+ [0x31, 0x5C], // backslash
268
+ [0x33, 0x3B], // ;
269
+ [0x34, 0x27], // '
270
+ [0x35, 0x60], // `
271
+ [0x36, 0x2C], // ,
272
+ [0x37, 0x2E], // .
273
+ [0x38, 0x2F], // /
274
+ ];
275
+ for (const [hid, ascii] of symbolMap) {
276
+ encoder.clearInterrupts(true, false, true, false);
277
+ encoder.updateKey(hid, true);
278
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(ascii);
279
+ }
459
280
  });
460
281
  });
461
- describe('Edge Cases', () => {
282
+ describe('Shift Key Mapping', () => {
462
283
  beforeEach(() => {
463
284
  encoder.updateControlLines(false, false, false, false);
464
- });
465
- it('should handle rapid modifier changes', () => {
466
- encoder.updateKey(0xE1, true); // Press Shift
467
- encoder.updateKey(0xE1, false); // Release Shift
468
- encoder.updateKey(0xE1, true); // Press Shift again
469
- encoder.updateKey(0x04, true); // 'a' -> 'A'
470
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41);
471
- });
472
- it('should handle all modifiers released', () => {
473
- encoder.updateKey(0xE0, true); // Press Ctrl
474
- encoder.updateKey(0xE1, true); // Press Shift
475
- encoder.updateKey(0xE2, true); // Press Alt
476
- encoder.updateKey(0xE0, false); // Release Ctrl
477
- encoder.updateKey(0xE1, false); // Release Shift
478
- encoder.updateKey(0xE2, false); // Release Alt
479
- encoder.updateKey(0x04, true); // 'a'
480
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x61); // Plain 'a'
481
- });
482
- it('should handle port disabled mid-operation', () => {
483
- encoder.updateKey(0x04, true); // 'a'
484
- expect(encoder.hasCA1Interrupt()).toBe(true);
485
- // Disable port
486
- encoder.updateControlLines(false, true, false, true);
487
- expect(encoder.hasCA1Interrupt()).toBe(false); // Interrupt not visible when disabled
488
- });
489
- it('should handle re-enabling port with data still present', () => {
490
- encoder.updateKey(0x04, true); // 'a'
491
- encoder.updateControlLines(false, true, false, true); // Disable
492
- // Re-enable
493
- encoder.updateControlLines(false, false, false, false);
494
- // Data should still be there
495
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x61);
285
+ encoder.updateKey(0xE1, true); // Left Shift down
286
+ });
287
+ it('should produce shifted number symbols', () => {
288
+ const shiftNumberMap = [
289
+ [0x1E, 0x21], // '1' -> '!'
290
+ [0x1F, 0x40], // '2' -> '@'
291
+ [0x20, 0x23], // '3' -> '#'
292
+ [0x21, 0x24], // '4' -> '$'
293
+ [0x22, 0x25], // '5' -> '%'
294
+ [0x23, 0x5E], // '6' -> '^'
295
+ [0x24, 0x26], // '7' -> '&'
296
+ [0x25, 0x2A], // '8' -> '*'
297
+ [0x26, 0x28], // '9' -> '('
298
+ [0x27, 0x29], // '0' -> ')'
299
+ ];
300
+ for (const [hid, ascii] of shiftNumberMap) {
301
+ encoder.clearInterrupts(true, false, true, false);
302
+ encoder.updateKey(hid, true);
303
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(ascii);
304
+ }
305
+ });
306
+ it('should produce shifted symbol keys', () => {
307
+ const shiftSymbolMap = [
308
+ [0x2D, 0x5F], // '-' -> '_'
309
+ [0x2E, 0x2B], // '=' -> '+'
310
+ [0x2F, 0x7B], // '[' -> '{'
311
+ [0x30, 0x7D], // ']' -> '}'
312
+ [0x31, 0x7C], // '\\' -> '|'
313
+ [0x33, 0x3A], // ';' -> ':'
314
+ [0x34, 0x22], // '\'' -> '"'
315
+ [0x36, 0x3C], // ',' -> '<'
316
+ [0x37, 0x3E], // '.' -> '>'
317
+ [0x38, 0x3F], // '/' -> '?'
318
+ [0x35, 0x7E], // '`' -> '~'
319
+ ];
320
+ for (const [hid, ascii] of shiftSymbolMap) {
321
+ encoder.clearInterrupts(true, false, true, false);
322
+ encoder.updateKey(hid, true);
323
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(ascii);
324
+ }
325
+ });
326
+ it('should not change letter output when Shift is held (already uppercase)', () => {
327
+ encoder.updateKey(0x04, true); // 'A'
328
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41); // Still 'A'
329
+ });
330
+ it('should track right Shift the same as left Shift', () => {
331
+ encoder.updateKey(0xE1, false); // Release left Shift
332
+ encoder.updateKey(0xE5, true); // Right Shift down
333
+ encoder.updateKey(0x1E, true); // '1'
334
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x21); // '!'
496
335
  });
497
336
  });
498
- describe('Alt+Shift Symbol Mapping (Extended Character Set)', () => {
337
+ describe('Ctrl Key Mapping', () => {
499
338
  beforeEach(() => {
500
339
  encoder.updateControlLines(false, false, false, false);
501
- encoder.updateKey(0xE2, true); // Press Alt
502
- encoder.updateKey(0xE1, true); // Press Shift
503
- });
504
- afterEach(() => {
505
- encoder.clearInterrupts(true, false, true, false);
506
- encoder.updateKey(0xE2, false); // Release Alt
507
- encoder.updateKey(0xE1, false); // Release Shift
508
- });
509
- it('should map Alt+Shift+1 to ¡ (0xA1)', () => {
510
- encoder.updateKey(0x1E, true); // '1'
511
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA1);
512
- });
513
- it('should map Alt+Shift+\' to ¢ (0xA2)', () => {
514
- encoder.updateKey(0x34, true); // '\'' (apostrophe, USB HID 0x34)
515
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA2);
516
- });
517
- it('should map Alt+Shift+3 to £ (0xA3)', () => {
518
- encoder.updateKey(0x20, true); // '3'
519
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA3);
520
- });
521
- it('should map Alt+Shift+4 to ¤ (0xA4)', () => {
522
- encoder.updateKey(0x21, true); // '4'
523
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA4);
524
- });
525
- it('should map Alt+Shift+5 to ¥ (0xA5)', () => {
526
- encoder.updateKey(0x22, true); // '5'
527
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA5);
528
- });
529
- it('should map Alt+Shift+7 to ¦ (0xA6)', () => {
530
- encoder.updateKey(0x24, true); // '7'
531
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA6);
532
- });
533
- it('should map Alt+Shift+9 to ¨ (0xA8)', () => {
534
- encoder.updateKey(0x26, true); // '9'
535
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA8);
536
- });
537
- it('should map Alt+Shift+0 to © (0xA9)', () => {
538
- encoder.updateKey(0x27, true); // '0'
539
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA9);
540
- });
541
- it('should map Alt+Shift+8 to ª (0xAA)', () => {
542
- encoder.updateKey(0x25, true); // '8'
543
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xAA);
544
- });
545
- it('should map Alt+Shift+= to « (0xAB)', () => {
546
- encoder.updateKey(0x2E, true); // '='
547
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xAB);
548
- });
549
- it('should map Alt+Shift+; to º (0xBA)', () => {
550
- encoder.updateKey(0x33, true); // ';'
551
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xBA);
552
- });
553
- it('should map Alt+Shift+, to ¼ (0xBC)', () => {
554
- encoder.updateKey(0x36, true); // ','
555
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xBC);
556
- });
557
- it('should map Alt+Shift+. to ¾ (0xBE)', () => {
558
- encoder.updateKey(0x37, true); // '.'
559
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xBE);
560
- });
561
- it('should map Alt+Shift+/ to ¿ (0xBF)', () => {
562
- encoder.updateKey(0x38, true); // '/'
563
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xBF);
564
- });
565
- it('should map Alt+Shift+2 to À (0xC0)', () => {
340
+ encoder.updateKey(0xE0, true); // Left Ctrl down
341
+ });
342
+ it('should produce control codes for Ctrl+A through Ctrl+Z', () => {
343
+ const ctrlLetterMap = [
344
+ [0x04, 0x01], // Ctrl+A
345
+ [0x05, 0x02], // Ctrl+B
346
+ [0x06, 0x03], // Ctrl+C
347
+ [0x07, 0x04], // Ctrl+D
348
+ [0x08, 0x05], // Ctrl+E
349
+ [0x09, 0x06], // Ctrl+F
350
+ [0x0A, 0x07], // Ctrl+G
351
+ [0x0B, 0x08], // Ctrl+H
352
+ [0x0C, 0x09], // Ctrl+I
353
+ [0x0D, 0x0A], // Ctrl+J
354
+ [0x0E, 0x0B], // Ctrl+K
355
+ [0x0F, 0x0C], // Ctrl+L
356
+ [0x10, 0x0D], // Ctrl+M
357
+ [0x11, 0x0E], // Ctrl+N
358
+ [0x12, 0x0F], // Ctrl+O
359
+ [0x13, 0x10], // Ctrl+P
360
+ [0x14, 0x11], // Ctrl+Q
361
+ [0x15, 0x12], // Ctrl+R
362
+ [0x16, 0x13], // Ctrl+S
363
+ [0x17, 0x14], // Ctrl+T
364
+ [0x18, 0x15], // Ctrl+U
365
+ [0x19, 0x16], // Ctrl+V
366
+ [0x1A, 0x17], // Ctrl+W
367
+ [0x1B, 0x18], // Ctrl+X
368
+ [0x1C, 0x19], // Ctrl+Y
369
+ [0x1D, 0x1A], // Ctrl+Z
370
+ ];
371
+ for (const [hid, ascii] of ctrlLetterMap) {
372
+ encoder.clearInterrupts(true, false, true, false);
373
+ encoder.updateKey(hid, true);
374
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(ascii);
375
+ }
376
+ });
377
+ it('should produce Ctrl+C = 0x03 (BASIC break)', () => {
378
+ encoder.updateKey(0x06, true); // 'C'
379
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x03);
380
+ });
381
+ it('should produce Ctrl+2 = NUL (0x00)', () => {
566
382
  encoder.updateKey(0x1F, true); // '2'
567
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC0);
568
- });
569
- it('should map Alt+Shift+b to  (0xC2)', () => {
570
- encoder.updateKey(0x05, true); // 'b'
571
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC2);
572
- });
573
- it('should map Alt+Shift+c to à (0xC3)', () => {
574
- encoder.updateKey(0x06, true); // 'c'
575
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC3);
576
- });
577
- it('should map Alt+Shift+d to Ä (0xC4)', () => {
578
- encoder.updateKey(0x07, true); // 'd'
579
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC4);
580
- });
581
- it('should map Alt+Shift+e to Å (0xC5)', () => {
582
- encoder.updateKey(0x08, true); // 'e'
583
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC5);
584
- });
585
- it('should map Alt+Shift+f to Æ (0xC6)', () => {
586
- encoder.updateKey(0x09, true); // 'f'
587
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC6);
588
- });
589
- it('should map Alt+Shift+g to Ç (0xC7)', () => {
590
- encoder.updateKey(0x0A, true); // 'g'
591
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC7);
592
- });
593
- it('should map Alt+Shift+h to È (0xC8)', () => {
594
- encoder.updateKey(0x0B, true); // 'h'
595
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xC8);
383
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x00);
596
384
  });
597
- it('should map Alt+Shift+6 to Þ (0xDE)', () => {
385
+ it('should produce Ctrl+6 = RS (0x1E)', () => {
598
386
  encoder.updateKey(0x23, true); // '6'
599
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xDE);
387
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1E);
600
388
  });
601
- it('should map Alt+Shift+- to ß (0xDF)', () => {
389
+ it('should produce Ctrl+- = US (0x1F)', () => {
602
390
  encoder.updateKey(0x2D, true); // '-'
603
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xDF);
391
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1F);
604
392
  });
605
- it('should map Alt+Shift+[ to û (0xFB)', () => {
393
+ it('should produce Ctrl+[ = ESC (0x1B)', () => {
606
394
  encoder.updateKey(0x2F, true); // '['
607
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFB);
395
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1B);
608
396
  });
609
- it('should map Alt+Shift+\\ to ü (0xFC)', () => {
397
+ it('should produce Ctrl+\\ = FS (0x1C)', () => {
610
398
  encoder.updateKey(0x31, true); // '\\'
611
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFC);
399
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1C);
612
400
  });
613
- it('should map Alt+Shift+] to ý (0xFD)', () => {
401
+ it('should produce Ctrl+] = GS (0x1D)', () => {
614
402
  encoder.updateKey(0x30, true); // ']'
615
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFD);
403
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x1D);
616
404
  });
617
- it('should map Alt+Shift+` to þ (0xFE)', () => {
618
- encoder.updateKey(0x35, true); // '`'
619
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFE);
405
+ it('should track right Ctrl the same as left Ctrl', () => {
406
+ encoder.updateKey(0xE0, false); // Release left Ctrl
407
+ encoder.updateKey(0xE4, true); // Right Ctrl down
408
+ encoder.updateKey(0x04, true); // 'A'
409
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x01); // Ctrl+A
620
410
  });
621
411
  });
622
- describe('Alt Symbol Mapping (Without Shift)', () => {
412
+ describe('Modifier Key Behavior', () => {
623
413
  beforeEach(() => {
624
414
  encoder.updateControlLines(false, false, false, false);
625
- encoder.updateKey(0xE2, true); // Press Alt
626
- });
627
- afterEach(() => {
628
- encoder.clearInterrupts(true, false, true, false);
629
- encoder.updateKey(0xE2, false); // Release Alt
630
415
  });
631
- it('should map Alt+\' to § (0xA7)', () => {
632
- encoder.updateKey(0x34, true); // '\''
633
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xA7);
634
- });
635
- it('should map Alt+, to ¬ (0xAC)', () => {
636
- encoder.updateKey(0x36, true); // ','
637
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xAC);
638
- });
639
- it('should map Alt+- to soft hyphen (0xAD)', () => {
640
- encoder.updateKey(0x2D, true); // '-'
641
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xAD);
642
- });
643
- it('should map Alt+. to ® (0xAE)', () => {
644
- encoder.updateKey(0x37, true); // '.'
645
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xAE);
646
- });
647
- it('should map Alt+/ to ¯ (0xAF)', () => {
648
- encoder.updateKey(0x38, true); // '/'
649
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xAF);
416
+ it('should not generate output for Ctrl press/release alone', () => {
417
+ encoder.updateKey(0xE0, true); // Left Ctrl down
418
+ expect(encoder.hasDataReadyA()).toBe(false);
419
+ encoder.updateKey(0xE0, false); // Left Ctrl up
420
+ expect(encoder.hasDataReadyA()).toBe(false);
650
421
  });
651
- it('should map Alt+; to » (0xBB)', () => {
652
- encoder.updateKey(0x33, true); // ';'
653
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xBB);
422
+ it('should not generate output for Shift press/release alone', () => {
423
+ encoder.updateKey(0xE1, true); // Left Shift down
424
+ expect(encoder.hasDataReadyA()).toBe(false);
425
+ encoder.updateKey(0xE1, false); // Left Shift up
426
+ expect(encoder.hasDataReadyA()).toBe(false);
654
427
  });
655
- it('should map Alt+= to ½ (0xBD)', () => {
656
- encoder.updateKey(0x2E, true); // '='
657
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xBD);
428
+ it('should not generate output for key release events', () => {
429
+ encoder.updateKey(0x04, true); // 'A' pressed
430
+ expect(encoder.hasDataReadyA()).toBe(true);
431
+ encoder.clearInterrupts(true, false, true, false);
432
+ encoder.updateKey(0x04, false); // 'A' released
433
+ expect(encoder.hasDataReadyA()).toBe(false); // No new data from release
658
434
  });
659
- it('should map Alt+[ to Û (0xDB)', () => {
660
- encoder.updateKey(0x2F, true); // '['
661
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xDB);
435
+ it('should ignore Alt key (no output, no modifier effect)', () => {
436
+ encoder.updateKey(0xE2, true); // Left Alt down - ignored
437
+ expect(encoder.hasDataReadyA()).toBe(false);
438
+ encoder.updateKey(0x04, true); // 'A'
439
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41); // Normal 'A', no alt mapping
662
440
  });
663
- it('should map Alt+\\ to Ü (0xDC)', () => {
664
- encoder.updateKey(0x31, true); // '\\'
665
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xDC);
441
+ it('should ignore Caps Lock key', () => {
442
+ encoder.updateKey(0x39, true); // Caps Lock - ignored
443
+ expect(encoder.hasDataReadyA()).toBe(false);
666
444
  });
667
- it('should map Alt+] to Ý (0xDD)', () => {
668
- encoder.updateKey(0x30, true); // ']'
669
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xDD);
445
+ it('should ignore GUI/MENU keys', () => {
446
+ encoder.updateKey(0xE3, true); // Left GUI - ignored (no mapping in table)
447
+ expect(encoder.hasDataReadyA()).toBe(false);
670
448
  });
671
- it('should map Alt+` to à (0xE0)', () => {
672
- encoder.updateKey(0x35, true); // '`'
673
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0xE0);
449
+ it('should ignore unrecognized keycodes', () => {
450
+ encoder.updateKey(0x3A, true); // F1 - no mapping
451
+ expect(encoder.hasDataReadyA()).toBe(false);
674
452
  });
675
453
  });
676
- describe('Ctrl Combinations Coverage', () => {
454
+ describe('Sequential Key Presses', () => {
677
455
  beforeEach(() => {
678
456
  encoder.updateControlLines(false, false, false, false);
679
- encoder.updateKey(0xE0, true); // Press Ctrl
680
- });
681
- afterEach(() => {
682
- encoder.clearInterrupts(true, false, true, false);
683
- encoder.updateKey(0xE0, false); // Release Ctrl
684
- });
685
- it('should map Ctrl+b to 0x02', () => {
686
- encoder.updateKey(0x05, true); // 'b'
687
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x02);
688
- });
689
- it('should map Ctrl+d to 0x04', () => {
690
- encoder.updateKey(0x07, true); // 'd'
691
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x04);
692
- });
693
- it('should map Ctrl+e to 0x05', () => {
694
- encoder.updateKey(0x08, true); // 'e'
695
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x05);
696
- });
697
- it('should map Ctrl+f to 0x06', () => {
698
- encoder.updateKey(0x09, true); // 'f'
699
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x06);
700
- });
701
- it('should map Ctrl+g to 0x07', () => {
702
- encoder.updateKey(0x0A, true); // 'g'
703
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x07);
704
- });
705
- it('should map Ctrl+h to 0x08', () => {
706
- encoder.updateKey(0x0B, true); // 'h'
707
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x08);
708
- });
709
- it('should map Ctrl+i to 0x09', () => {
710
- encoder.updateKey(0x0C, true); // 'i'
711
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x09);
712
- });
713
- it('should map Ctrl+j to 0x0A', () => {
714
- encoder.updateKey(0x0D, true); // 'j'
715
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x0A);
716
- });
717
- it('should map Ctrl+k to 0x0B', () => {
718
- encoder.updateKey(0x0E, true); // 'k'
719
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x0B);
720
- });
721
- it('should map Ctrl+l to 0x0C', () => {
722
- encoder.updateKey(0x0F, true); // 'l'
723
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x0C);
724
- });
725
- it('should map Ctrl+m to 0x0D', () => {
726
- encoder.updateKey(0x10, true); // 'm'
727
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x0D);
728
- });
729
- it('should map Ctrl+n to 0x0E', () => {
730
- encoder.updateKey(0x11, true); // 'n'
731
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x0E);
732
457
  });
733
- it('should map Ctrl+o to 0x0F', () => {
734
- encoder.updateKey(0x12, true); // 'o'
735
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x0F);
458
+ it('should overwrite data with new key press', () => {
459
+ encoder.updateKey(0x04, true); // 'A'
460
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41);
461
+ encoder.updateKey(0x05, true); // 'B' overwrites without clearing
462
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x42);
736
463
  });
737
- it('should map Ctrl+p to 0x10', () => {
738
- encoder.updateKey(0x13, true); // 'p'
739
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x10);
464
+ it('should handle read-clear-press cycle', () => {
465
+ encoder.updateKey(0x04, true); // 'A'
466
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41);
467
+ encoder.clearInterrupts(true, false, true, false);
468
+ expect(encoder.hasDataReadyA()).toBe(false);
469
+ encoder.updateKey(0x05, true); // 'B'
470
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x42);
471
+ expect(encoder.hasDataReadyA()).toBe(true);
740
472
  });
741
- it('should map Ctrl+q to 0x11', () => {
742
- encoder.updateKey(0x14, true); // 'q'
743
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x11);
473
+ it('should handle modifier press then key press', () => {
474
+ encoder.updateKey(0xE0, true); // Ctrl down
475
+ encoder.updateKey(0x06, true); // 'C' -> Ctrl+C = 0x03
476
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x03);
477
+ encoder.clearInterrupts(true, false, true, false);
478
+ encoder.updateKey(0xE0, false); // Ctrl up
479
+ encoder.updateKey(0x06, true); // 'C' -> normal 'C' = 0x43
480
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x43);
744
481
  });
745
- it('should map Ctrl+r to 0x12', () => {
746
- encoder.updateKey(0x15, true); // 'r'
747
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x12);
482
+ });
483
+ describe('Active Port Selection', () => {
484
+ beforeEach(() => {
485
+ encoder.updateControlLines(false, false, false, false);
748
486
  });
749
- it('should map Ctrl+s to 0x13', () => {
750
- encoder.updateKey(0x16, true); // 's'
751
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x13);
487
+ it('should default to both ports active', () => {
488
+ expect(encoder.activePort).toBe('both');
489
+ encoder.updateKey(0x04, true); // 'A'
490
+ expect(encoder.hasDataReadyA()).toBe(true);
491
+ expect(encoder.hasDataReadyB()).toBe(true);
752
492
  });
753
- it('should map Ctrl+t to 0x14', () => {
754
- encoder.updateKey(0x17, true); // 't'
755
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x14);
493
+ it('should only update Port A when activePort is A', () => {
494
+ encoder.activePort = 'A';
495
+ encoder.updateKey(0x04, true); // 'A'
496
+ expect(encoder.hasDataReadyA()).toBe(true);
497
+ expect(encoder.hasDataReadyB()).toBe(false);
756
498
  });
757
- it('should map Ctrl+u to 0x15', () => {
758
- encoder.updateKey(0x18, true); // 'u'
759
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x15);
499
+ it('should only update Port B when activePort is B', () => {
500
+ encoder.activePort = 'B';
501
+ encoder.updateKey(0x04, true); // 'A'
502
+ expect(encoder.hasDataReadyA()).toBe(false);
503
+ expect(encoder.hasDataReadyB()).toBe(true);
760
504
  });
761
- it('should map Ctrl+v to 0x16', () => {
762
- encoder.updateKey(0x19, true); // 'v'
763
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x16);
505
+ });
506
+ describe('Edge Cases', () => {
507
+ beforeEach(() => {
508
+ encoder.updateControlLines(false, false, false, false);
764
509
  });
765
- it('should map Ctrl+w to 0x17', () => {
766
- encoder.updateKey(0x1A, true); // 'w'
767
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x17);
510
+ it('should handle port disable mid-operation', () => {
511
+ encoder.updateKey(0x04, true); // 'A'
512
+ expect(encoder.hasCA1Interrupt()).toBe(true);
513
+ // Disable Port A
514
+ encoder.updateControlLines(false, true, false, false);
515
+ expect(encoder.hasCA1Interrupt()).toBe(false);
516
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0xFF); // Disabled, returns 0xFF
768
517
  });
769
- it('should map Ctrl+x to 0x18', () => {
770
- encoder.updateKey(0x1B, true); // 'x'
771
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x18);
518
+ it('should allow re-enabling port and reading existing data', () => {
519
+ encoder.updateKey(0x04, true); // 'A'
520
+ encoder.updateControlLines(false, true, false, true); // Disable both
521
+ encoder.updateControlLines(false, false, false, false); // Re-enable both
522
+ // Data should still be readable (dataReady is still true, just interrupt was gated)
523
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x41);
772
524
  });
773
- it('should map Ctrl+y to 0x19', () => {
774
- encoder.updateKey(0x1C, true); // 'y'
775
- expect(encoder.readPortA(0xFF, 0x00)).toBe(0x19);
525
+ it('should handle Ctrl+2 producing NUL (0x00) correctly', () => {
526
+ encoder.updateKey(0xE0, true); // Ctrl down
527
+ encoder.updateKey(0x1F, true); // '2'
528
+ // Should produce 0x00 and data should be ready
529
+ expect(encoder.hasDataReadyA()).toBe(true);
530
+ expect(encoder.readPortA(0xFF, 0x00)).toBe(0x00);
776
531
  });
777
532
  });
778
533
  });