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.
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.d.ts +5 -10
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +42 -265
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +329 -574
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -1
- package/package.json +1 -1
- package/src/components/IO/Attachments/KeyboardEncoderAttachment.ts +44 -216
- package/src/index.ts +1 -1
- package/src/tests/IO/Attachments/KeyboardEncoderAttachment.test.ts +336 -676
|
@@ -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) // '
|
|
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) // '
|
|
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) // '
|
|
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) // '
|
|
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) // '
|
|
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) // '
|
|
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) // '
|
|
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) // '
|
|
106
|
+
encoder.updateKey(0x04, true) // 'A' = 0x41
|
|
98
107
|
const value = encoder.readPortA(0xFF, 0x00)
|
|
99
|
-
expect(value).toBe(
|
|
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) // '
|
|
113
|
+
encoder.updateKey(0x04, true) // 'A' = 0x41
|
|
105
114
|
const value = encoder.readPortB(0xFF, 0x00)
|
|
106
|
-
expect(value).toBe(
|
|
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) // '
|
|
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) // '
|
|
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(
|
|
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) // '
|
|
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) // '
|
|
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) // '
|
|
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) // '
|
|
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) // '
|
|
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('
|
|
180
|
+
describe('Letter Key Mapping', () => {
|
|
172
181
|
beforeEach(() => {
|
|
173
182
|
encoder.updateControlLines(false, false, false, false)
|
|
174
183
|
})
|
|
175
184
|
|
|
176
|
-
it('should
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
204
|
-
|
|
191
|
+
// 'Z' (HID 0x1D)
|
|
192
|
+
encoder.updateKey(0x1D, true)
|
|
193
|
+
expect(encoder.readPortA(0xFF, 0x00)).toBe(0x5A)
|
|
205
194
|
})
|
|
206
195
|
|
|
207
|
-
it('should
|
|
208
|
-
encoder.updateKey(
|
|
209
|
-
|
|
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(
|
|
213
|
-
expect(encoder.
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it('should
|
|
217
|
-
|
|
218
|
-
|
|
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('
|
|
224
|
+
describe('Number Key Mapping', () => {
|
|
223
225
|
beforeEach(() => {
|
|
224
226
|
encoder.updateControlLines(false, false, false, false)
|
|
225
227
|
})
|
|
226
228
|
|
|
227
|
-
it('should
|
|
228
|
-
encoder.updateKey(
|
|
229
|
-
expect(encoder.
|
|
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(
|
|
247
|
-
encoder.
|
|
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
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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('
|
|
252
|
+
describe('Special Key Mapping', () => {
|
|
268
253
|
beforeEach(() => {
|
|
269
254
|
encoder.updateControlLines(false, false, false, false)
|
|
270
255
|
})
|
|
271
256
|
|
|
272
|
-
it('should map
|
|
273
|
-
encoder.updateKey(
|
|
274
|
-
encoder.
|
|
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
|
|
279
|
-
encoder.updateKey(
|
|
280
|
-
encoder.
|
|
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
|
|
289
|
-
encoder.updateKey(
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
299
|
-
encoder.updateKey(
|
|
300
|
-
expect(encoder.readPortA(0xFF, 0x00)).toBe(
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
encoder.
|
|
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
|
|
310
|
-
encoder.updateKey(
|
|
311
|
-
encoder.
|
|
312
|
-
|
|
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
|
-
|
|
315
|
-
encoder.updateKey(
|
|
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
|
|
320
|
-
encoder.updateKey(
|
|
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(
|
|
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(
|
|
331
|
-
expect(encoder.readPortA(0xFF, 0x00)).toBe(
|
|
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
|
-
|
|
341
|
-
encoder.updateKey(
|
|
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
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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('
|
|
331
|
+
describe('Shift Key Mapping', () => {
|
|
354
332
|
beforeEach(() => {
|
|
355
333
|
encoder.updateControlLines(false, false, false, false)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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('
|
|
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
|
|
381
|
-
encoder.updateKey(
|
|
382
|
-
expect(encoder.readPortA(0xFF, 0x00)).toBe(
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
encoder.
|
|
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
|
|
431
|
-
encoder.updateKey(
|
|
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
|
|
442
|
-
encoder.updateKey(
|
|
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
|
|
453
|
-
encoder.updateKey(
|
|
454
|
-
encoder.
|
|
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
|
|
459
|
-
encoder.updateKey(
|
|
460
|
-
encoder.updateKey(
|
|
461
|
-
|
|
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('
|
|
476
|
+
describe('Modifier Key Behavior', () => {
|
|
466
477
|
beforeEach(() => {
|
|
467
478
|
encoder.updateControlLines(false, false, false, false)
|
|
468
479
|
})
|
|
469
480
|
|
|
470
|
-
it('should
|
|
471
|
-
encoder.updateKey(
|
|
472
|
-
encoder.
|
|
473
|
-
|
|
474
|
-
encoder.
|
|
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
|
|
483
|
-
encoder.updateKey(
|
|
484
|
-
encoder.
|
|
485
|
-
|
|
486
|
-
encoder.
|
|
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
|
|
495
|
-
encoder.updateKey(
|
|
496
|
-
encoder.
|
|
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
|
-
|
|
508
|
-
|
|
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
|
|
513
|
-
encoder.updateKey(
|
|
514
|
-
encoder.
|
|
515
|
-
|
|
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
|
|
519
|
-
encoder.updateKey(
|
|
520
|
-
encoder.
|
|
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
|
|
527
|
-
encoder.updateKey(
|
|
528
|
-
encoder.
|
|
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
|
|
534
|
-
encoder.updateKey(
|
|
535
|
-
encoder.
|
|
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
|
|
548
|
-
encoder.updateKey(0x04, true) // '
|
|
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
|
-
|
|
584
|
-
encoder.
|
|
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
|
|
595
|
-
encoder.updateKey(0x04, true) // '
|
|
596
|
-
expect(encoder.
|
|
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.
|
|
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
|
-
|
|
753
|
-
encoder.
|
|
754
|
-
expect(encoder.
|
|
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
|
|
758
|
-
encoder.updateKey(
|
|
759
|
-
|
|
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
|
-
|
|
763
|
-
encoder.updateKey(
|
|
764
|
-
|
|
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('
|
|
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
|
|
800
|
-
encoder.
|
|
801
|
-
|
|
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
|
|
825
|
-
encoder.
|
|
826
|
-
|
|
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
|
|
830
|
-
encoder.
|
|
831
|
-
|
|
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('
|
|
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
|
|
912
|
-
encoder.updateKey(
|
|
913
|
-
expect(encoder.
|
|
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
|
-
|
|
947
|
-
encoder.
|
|
948
|
-
expect(encoder.
|
|
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
|
|
952
|
-
encoder.updateKey(
|
|
953
|
-
|
|
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
|
|
957
|
-
encoder.updateKey(
|
|
958
|
-
|
|
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
|
})
|