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