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