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