ac6502 1.0.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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/dist/components/CPU.js +1170 -0
  4. package/dist/components/CPU.js.map +1 -0
  5. package/dist/components/Cart.js +23 -0
  6. package/dist/components/Cart.js.map +1 -0
  7. package/dist/components/IO/Empty.js +19 -0
  8. package/dist/components/IO/Empty.js.map +1 -0
  9. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
  10. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
  11. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
  12. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
  13. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
  14. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
  15. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
  16. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
  17. package/dist/components/IO/GPIOCard.js +597 -0
  18. package/dist/components/IO/GPIOCard.js.map +1 -0
  19. package/dist/components/IO/InputBoard.js +19 -0
  20. package/dist/components/IO/InputBoard.js.map +1 -0
  21. package/dist/components/IO/LCDCard.js +19 -0
  22. package/dist/components/IO/LCDCard.js.map +1 -0
  23. package/dist/components/IO/RAMCard.js +63 -0
  24. package/dist/components/IO/RAMCard.js.map +1 -0
  25. package/dist/components/IO/RTCCard.js +483 -0
  26. package/dist/components/IO/RTCCard.js.map +1 -0
  27. package/dist/components/IO/SerialCard.js +282 -0
  28. package/dist/components/IO/SerialCard.js.map +1 -0
  29. package/dist/components/IO/SoundCard.js +620 -0
  30. package/dist/components/IO/SoundCard.js.map +1 -0
  31. package/dist/components/IO/StorageCard.js +428 -0
  32. package/dist/components/IO/StorageCard.js.map +1 -0
  33. package/dist/components/IO/VGACard.js +9 -0
  34. package/dist/components/IO/VGACard.js.map +1 -0
  35. package/dist/components/IO/VideoCard.js +623 -0
  36. package/dist/components/IO/VideoCard.js.map +1 -0
  37. package/dist/components/IO.js +3 -0
  38. package/dist/components/IO.js.map +1 -0
  39. package/dist/components/Machine.js +310 -0
  40. package/dist/components/Machine.js.map +1 -0
  41. package/dist/components/RAM.js +24 -0
  42. package/dist/components/RAM.js.map +1 -0
  43. package/dist/components/ROM.js +23 -0
  44. package/dist/components/ROM.js.map +1 -0
  45. package/dist/index.js +441 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/tests/CPU.test.js +1626 -0
  48. package/dist/tests/CPU.test.js.map +1 -0
  49. package/dist/tests/Cart.test.js +119 -0
  50. package/dist/tests/Cart.test.js.map +1 -0
  51. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
  52. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
  53. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
  54. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
  55. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
  56. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
  57. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
  58. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
  59. package/dist/tests/IO/GPIOCard.test.js +503 -0
  60. package/dist/tests/IO/GPIOCard.test.js.map +1 -0
  61. package/dist/tests/IO/RAMCard.test.js +229 -0
  62. package/dist/tests/IO/RAMCard.test.js.map +1 -0
  63. package/dist/tests/IO/RTCCard.test.js +177 -0
  64. package/dist/tests/IO/RTCCard.test.js.map +1 -0
  65. package/dist/tests/IO/SerialCard.test.js +423 -0
  66. package/dist/tests/IO/SerialCard.test.js.map +1 -0
  67. package/dist/tests/IO/SoundCard.test.js +528 -0
  68. package/dist/tests/IO/SoundCard.test.js.map +1 -0
  69. package/dist/tests/IO/StorageCard.test.js +647 -0
  70. package/dist/tests/IO/StorageCard.test.js.map +1 -0
  71. package/dist/tests/IO/VideoCard.test.js +549 -0
  72. package/dist/tests/IO/VideoCard.test.js.map +1 -0
  73. package/dist/tests/Machine.test.js +383 -0
  74. package/dist/tests/Machine.test.js.map +1 -0
  75. package/dist/tests/RAM.test.js +160 -0
  76. package/dist/tests/RAM.test.js.map +1 -0
  77. package/dist/tests/ROM.test.js +123 -0
  78. package/dist/tests/ROM.test.js.map +1 -0
  79. package/jest.config.cjs +9 -0
  80. package/package.json +43 -0
  81. package/src/components/CPU.ts +1371 -0
  82. package/src/components/Cart.ts +20 -0
  83. package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
  84. package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
  85. package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
  86. package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
  87. package/src/components/IO/GPIOCard.ts +677 -0
  88. package/src/components/IO/RAMCard.ts +68 -0
  89. package/src/components/IO/RTCCard.ts +518 -0
  90. package/src/components/IO/SerialCard.ts +335 -0
  91. package/src/components/IO/SoundCard.ts +711 -0
  92. package/src/components/IO/StorageCard.ts +473 -0
  93. package/src/components/IO/VideoCard.ts +730 -0
  94. package/src/components/IO.ts +11 -0
  95. package/src/components/Machine.ts +364 -0
  96. package/src/components/RAM.ts +23 -0
  97. package/src/components/ROM.ts +19 -0
  98. package/src/index.ts +474 -0
  99. package/src/tests/CPU.test.ts +2045 -0
  100. package/src/tests/Cart.test.ts +149 -0
  101. package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
  102. package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
  103. package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
  104. package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
  105. package/src/tests/IO/GPIOCard.test.ts +644 -0
  106. package/src/tests/IO/RAMCard.test.ts +284 -0
  107. package/src/tests/IO/RTCCard.test.ts +222 -0
  108. package/src/tests/IO/SerialCard.test.ts +530 -0
  109. package/src/tests/IO/SoundCard.test.ts +659 -0
  110. package/src/tests/IO/StorageCard.test.ts +787 -0
  111. package/src/tests/IO/VideoCard.test.ts +668 -0
  112. package/src/tests/Machine.test.ts +437 -0
  113. package/src/tests/RAM.test.ts +196 -0
  114. package/src/tests/ROM.test.ts +154 -0
  115. package/tsconfig.json +12 -0
@@ -0,0 +1,149 @@
1
+ import { Cart } from '../components/Cart'
2
+
3
+ describe('Cart', () => {
4
+ let cart: Cart
5
+
6
+ beforeEach(() => {
7
+ cart = new Cart()
8
+ })
9
+
10
+ describe('Static Properties', () => {
11
+ test('should have correct START address', () => {
12
+ expect(Cart.START).toBe(0x8000)
13
+ })
14
+
15
+ test('should have correct END address', () => {
16
+ expect(Cart.END).toBe(0xFFFF)
17
+ })
18
+
19
+ test('should have correct CODE address', () => {
20
+ expect(Cart.CODE).toBe(0xC000)
21
+ })
22
+
23
+ test('should have correct SIZE', () => {
24
+ expect(Cart.SIZE).toBe(0x8000)
25
+ expect(Cart.SIZE).toBe(Cart.END - Cart.START + 1)
26
+ })
27
+ })
28
+
29
+ describe('Initialization', () => {
30
+ test('should create a new Cart instance', () => {
31
+ expect(cart).not.toBeNull()
32
+ expect(cart).toBeInstanceOf(Cart)
33
+ })
34
+
35
+ test('should initialize data array with correct size', () => {
36
+ expect(cart.data).toHaveLength(Cart.SIZE)
37
+ })
38
+
39
+ test('should initialize all data to 0x00', () => {
40
+ for (let i = 0; i < cart.data.length; i++) {
41
+ expect(cart.data[i]).toBe(0x00)
42
+ }
43
+ })
44
+ })
45
+
46
+ describe('read()', () => {
47
+ test('should read data from address', () => {
48
+ cart.data[0x0000] = 0x42
49
+ expect(cart.read(0x0000)).toBe(0x42)
50
+ })
51
+
52
+ test('should read 0x00 from uninitialized address', () => {
53
+ expect(cart.read(0x1000)).toBe(0x00)
54
+ })
55
+
56
+ test('should read from first address', () => {
57
+ cart.data[0] = 0xAA
58
+ expect(cart.read(0)).toBe(0xAA)
59
+ })
60
+
61
+ test('should read from last address', () => {
62
+ cart.data[Cart.SIZE - 1] = 0xBB
63
+ expect(cart.read(Cart.SIZE - 1)).toBe(0xBB)
64
+ })
65
+
66
+ test('should read multiple different values', () => {
67
+ cart.data[0x1000] = 0x12
68
+ cart.data[0x2000] = 0x34
69
+ cart.data[0x3000] = 0x56
70
+
71
+ expect(cart.read(0x1000)).toBe(0x12)
72
+ expect(cart.read(0x2000)).toBe(0x34)
73
+ expect(cart.read(0x3000)).toBe(0x56)
74
+ })
75
+ })
76
+
77
+ describe('load()', () => {
78
+ test('should load data array with correct size', () => {
79
+ const testData = new Array(Cart.SIZE).fill(0xFF)
80
+ cart.load(testData)
81
+
82
+ expect(cart.data).toBe(testData)
83
+ expect(cart.data[0]).toBe(0xFF)
84
+ expect(cart.data[Cart.SIZE - 1]).toBe(0xFF)
85
+ })
86
+
87
+ test('should not load data array with incorrect size (too small)', () => {
88
+ const originalData = [...cart.data]
89
+ const testData = new Array(Cart.SIZE - 1).fill(0xFF)
90
+
91
+ cart.load(testData)
92
+
93
+ expect(cart.data).toEqual(originalData)
94
+ expect(cart.data).not.toBe(testData)
95
+ })
96
+
97
+ test('should not load data array with incorrect size (too large)', () => {
98
+ const originalData = [...cart.data]
99
+ const testData = new Array(Cart.SIZE + 1).fill(0xFF)
100
+
101
+ cart.load(testData)
102
+
103
+ expect(cart.data).toEqual(originalData)
104
+ expect(cart.data).not.toBe(testData)
105
+ })
106
+
107
+ test('should not load empty array', () => {
108
+ const originalData = [...cart.data]
109
+ cart.load([])
110
+
111
+ expect(cart.data).toEqual(originalData)
112
+ })
113
+
114
+ test('should load data with specific pattern', () => {
115
+ const testData = new Array(Cart.SIZE).fill(0x00).map((_, i) => i & 0xFF)
116
+ cart.load(testData)
117
+
118
+ for (let i = 0; i < 256; i++) {
119
+ expect(cart.read(i)).toBe(i)
120
+ }
121
+ })
122
+
123
+ test('should replace existing data when loading', () => {
124
+ // Set some initial data directly
125
+ cart.data[0x0000] = 0xAA
126
+ cart.data[0x1000] = 0xBB
127
+ cart.data[0x2000] = 0xCC
128
+
129
+ // Load new data
130
+ const testData = new Array(Cart.SIZE).fill(0x55)
131
+ cart.load(testData)
132
+
133
+ expect(cart.read(0x0000)).toBe(0x55)
134
+ expect(cart.read(0x1000)).toBe(0x55)
135
+ expect(cart.read(0x2000)).toBe(0x55)
136
+ })
137
+
138
+ test('should allow reading loaded data', () => {
139
+ const testData = new Array(Cart.SIZE).fill(0x00)
140
+ testData[0x1234] = 0xAB
141
+ testData[0x5678] = 0xCD
142
+
143
+ cart.load(testData)
144
+
145
+ expect(cart.read(0x1234)).toBe(0xAB)
146
+ expect(cart.read(0x5678)).toBe(0xCD)
147
+ })
148
+ })
149
+ })
@@ -0,0 +1,413 @@
1
+ import { GPIOAttachment, GPIOAttachmentBase } from '../../../components/IO/GPIOAttachments/GPIOAttachment'
2
+
3
+ /**
4
+ * Concrete implementation of GPIOAttachmentBase for testing
5
+ */
6
+ class TestGPIOAttachment extends GPIOAttachmentBase {
7
+ public portAValue: number = 0xFF
8
+ public portBValue: number = 0xFF
9
+ public tickCount: number = 0
10
+ public writeAValue: number = 0
11
+ public writeBValue: number = 0
12
+
13
+ constructor(
14
+ priority: number = 0,
15
+ ca1Interrupt: boolean = false,
16
+ ca2Interrupt: boolean = false,
17
+ cb1Interrupt: boolean = false,
18
+ cb2Interrupt: boolean = false
19
+ ) {
20
+ super(priority, ca1Interrupt, ca2Interrupt, cb1Interrupt, cb2Interrupt)
21
+ }
22
+
23
+ readPortA(ddr: number, or: number): number {
24
+ return this.portAValue
25
+ }
26
+
27
+ readPortB(ddr: number, or: number): number {
28
+ return this.portBValue
29
+ }
30
+
31
+ writePortA(value: number, ddr: number): void {
32
+ this.writeAValue = value
33
+ }
34
+
35
+ writePortB(value: number, ddr: number): void {
36
+ this.writeBValue = value
37
+ }
38
+
39
+ tick(cpuFrequency: number): void {
40
+ this.tickCount++
41
+ }
42
+
43
+ // Expose protected members for testing
44
+ public setCA1Interrupt(value: boolean): void {
45
+ this.ca1Interrupt = value
46
+ }
47
+
48
+ public setCA2Interrupt(value: boolean): void {
49
+ this.ca2Interrupt = value
50
+ }
51
+
52
+ public setCB1Interrupt(value: boolean): void {
53
+ this.cb1Interrupt = value
54
+ }
55
+
56
+ public setCB2Interrupt(value: boolean): void {
57
+ this.cb2Interrupt = value
58
+ }
59
+
60
+ public setEnabled(value: boolean): void {
61
+ this.enabled = value
62
+ }
63
+ }
64
+
65
+ describe('GPIOAttachmentBase', () => {
66
+ let attachment: TestGPIOAttachment
67
+
68
+ beforeEach(() => {
69
+ attachment = new TestGPIOAttachment()
70
+ })
71
+
72
+ describe('Initialization', () => {
73
+ it('should initialize with default priority 0', () => {
74
+ expect(attachment.getPriority()).toBe(0)
75
+ })
76
+
77
+ it('should initialize with custom priority', () => {
78
+ const customAttachment = new TestGPIOAttachment(5)
79
+ expect(customAttachment.getPriority()).toBe(5)
80
+ })
81
+
82
+ it('should initialize as enabled', () => {
83
+ expect(attachment.isEnabled()).toBe(true)
84
+ })
85
+
86
+ it('should initialize with no interrupts pending', () => {
87
+ expect(attachment.hasCA1Interrupt()).toBe(false)
88
+ expect(attachment.hasCA2Interrupt()).toBe(false)
89
+ expect(attachment.hasCB1Interrupt()).toBe(false)
90
+ expect(attachment.hasCB2Interrupt()).toBe(false)
91
+ })
92
+
93
+ it('should initialize with specified interrupt states', () => {
94
+ const interruptAttachment = new TestGPIOAttachment(0, true, true, true, true)
95
+ expect(interruptAttachment.hasCA1Interrupt()).toBe(true)
96
+ expect(interruptAttachment.hasCA2Interrupt()).toBe(true)
97
+ expect(interruptAttachment.hasCB1Interrupt()).toBe(true)
98
+ expect(interruptAttachment.hasCB2Interrupt()).toBe(true)
99
+ })
100
+ })
101
+
102
+ describe('Reset', () => {
103
+ it('should reset to enabled state', () => {
104
+ attachment.setEnabled(false)
105
+ attachment.reset()
106
+ expect(attachment.isEnabled()).toBe(true)
107
+ })
108
+
109
+ it('should clear all interrupt flags', () => {
110
+ attachment.setCA1Interrupt(true)
111
+ attachment.setCA2Interrupt(true)
112
+ attachment.setCB1Interrupt(true)
113
+ attachment.setCB2Interrupt(true)
114
+
115
+ attachment.reset()
116
+
117
+ expect(attachment.hasCA1Interrupt()).toBe(false)
118
+ expect(attachment.hasCA2Interrupt()).toBe(false)
119
+ expect(attachment.hasCB1Interrupt()).toBe(false)
120
+ expect(attachment.hasCB2Interrupt()).toBe(false)
121
+ })
122
+
123
+ it('should maintain priority after reset', () => {
124
+ const priorityAttachment = new TestGPIOAttachment(10)
125
+ priorityAttachment.reset()
126
+ expect(priorityAttachment.getPriority()).toBe(10)
127
+ })
128
+ })
129
+
130
+ describe('Priority', () => {
131
+ it('should return correct priority value', () => {
132
+ const lowPriority = new TestGPIOAttachment(0)
133
+ const highPriority = new TestGPIOAttachment(10)
134
+
135
+ expect(lowPriority.getPriority()).toBe(0)
136
+ expect(highPriority.getPriority()).toBe(10)
137
+ })
138
+
139
+ it('should support negative priority values', () => {
140
+ const negativePriority = new TestGPIOAttachment(-5)
141
+ expect(negativePriority.getPriority()).toBe(-5)
142
+ })
143
+ })
144
+
145
+ describe('Enable/Disable', () => {
146
+ it('should start enabled', () => {
147
+ expect(attachment.isEnabled()).toBe(true)
148
+ })
149
+
150
+ it('should be disableable', () => {
151
+ attachment.setEnabled(false)
152
+ expect(attachment.isEnabled()).toBe(false)
153
+ })
154
+
155
+ it('should be re-enableable', () => {
156
+ attachment.setEnabled(false)
157
+ attachment.setEnabled(true)
158
+ expect(attachment.isEnabled()).toBe(true)
159
+ })
160
+ })
161
+
162
+ describe('Tick', () => {
163
+ it('should call tick method', () => {
164
+ attachment.tick(1000000)
165
+ expect(attachment.tickCount).toBe(1)
166
+ })
167
+
168
+ it('should call tick multiple times', () => {
169
+ attachment.tick(1000000)
170
+ attachment.tick(1000000)
171
+ attachment.tick(1000000)
172
+ expect(attachment.tickCount).toBe(3)
173
+ })
174
+
175
+ it('should accept different CPU frequencies', () => {
176
+ attachment.tick(1000000)
177
+ attachment.tick(2000000)
178
+ attachment.tick(4000000)
179
+ expect(attachment.tickCount).toBe(3)
180
+ })
181
+ })
182
+
183
+ describe('Port Reading', () => {
184
+ it('should read from Port A', () => {
185
+ attachment.portAValue = 0xAA
186
+ expect(attachment.readPortA(0xFF, 0x00)).toBe(0xAA)
187
+ })
188
+
189
+ it('should read from Port B', () => {
190
+ attachment.portBValue = 0x55
191
+ expect(attachment.readPortB(0xFF, 0x00)).toBe(0x55)
192
+ })
193
+
194
+ it('should handle DDR parameter in Port A read', () => {
195
+ attachment.portAValue = 0xFF
196
+ expect(attachment.readPortA(0x00, 0x00)).toBe(0xFF)
197
+ expect(attachment.readPortA(0xFF, 0x00)).toBe(0xFF)
198
+ })
199
+
200
+ it('should handle OR parameter in Port A read', () => {
201
+ attachment.portAValue = 0xFF
202
+ expect(attachment.readPortA(0xFF, 0x00)).toBe(0xFF)
203
+ expect(attachment.readPortA(0xFF, 0xFF)).toBe(0xFF)
204
+ })
205
+ })
206
+
207
+ describe('Port Writing', () => {
208
+ it('should write to Port A', () => {
209
+ attachment.writePortA(0xAA, 0xFF)
210
+ expect(attachment.writeAValue).toBe(0xAA)
211
+ })
212
+
213
+ it('should write to Port B', () => {
214
+ attachment.writePortB(0x55, 0xFF)
215
+ expect(attachment.writeBValue).toBe(0x55)
216
+ })
217
+
218
+ it('should handle DDR parameter in Port A write', () => {
219
+ attachment.writePortA(0xAA, 0x0F)
220
+ expect(attachment.writeAValue).toBe(0xAA)
221
+ })
222
+
223
+ it('should handle DDR parameter in Port B write', () => {
224
+ attachment.writePortB(0x55, 0xF0)
225
+ expect(attachment.writeBValue).toBe(0x55)
226
+ })
227
+
228
+ it('should handle multiple writes to same port', () => {
229
+ attachment.writePortA(0xAA, 0xFF)
230
+ attachment.writePortA(0x55, 0xFF)
231
+ expect(attachment.writeAValue).toBe(0x55)
232
+ })
233
+ })
234
+
235
+ describe('Interrupt Management - CA1', () => {
236
+ it('should check CA1 interrupt flag', () => {
237
+ expect(attachment.hasCA1Interrupt()).toBe(false)
238
+ attachment.setCA1Interrupt(true)
239
+ expect(attachment.hasCA1Interrupt()).toBe(true)
240
+ })
241
+
242
+ it('should clear CA1 interrupt', () => {
243
+ attachment.setCA1Interrupt(true)
244
+ attachment.clearInterrupts(true, false, false, false)
245
+ expect(attachment.hasCA1Interrupt()).toBe(false)
246
+ })
247
+
248
+ it('should not clear CA1 when clearing other interrupts', () => {
249
+ attachment.setCA1Interrupt(true)
250
+ attachment.clearInterrupts(false, true, true, true)
251
+ expect(attachment.hasCA1Interrupt()).toBe(true)
252
+ })
253
+ })
254
+
255
+ describe('Interrupt Management - CA2', () => {
256
+ it('should check CA2 interrupt flag', () => {
257
+ expect(attachment.hasCA2Interrupt()).toBe(false)
258
+ attachment.setCA2Interrupt(true)
259
+ expect(attachment.hasCA2Interrupt()).toBe(true)
260
+ })
261
+
262
+ it('should clear CA2 interrupt', () => {
263
+ attachment.setCA2Interrupt(true)
264
+ attachment.clearInterrupts(false, true, false, false)
265
+ expect(attachment.hasCA2Interrupt()).toBe(false)
266
+ })
267
+
268
+ it('should not clear CA2 when clearing other interrupts', () => {
269
+ attachment.setCA2Interrupt(true)
270
+ attachment.clearInterrupts(true, false, true, true)
271
+ expect(attachment.hasCA2Interrupt()).toBe(true)
272
+ })
273
+ })
274
+
275
+ describe('Interrupt Management - CB1', () => {
276
+ it('should check CB1 interrupt flag', () => {
277
+ expect(attachment.hasCB1Interrupt()).toBe(false)
278
+ attachment.setCB1Interrupt(true)
279
+ expect(attachment.hasCB1Interrupt()).toBe(true)
280
+ })
281
+
282
+ it('should clear CB1 interrupt', () => {
283
+ attachment.setCB1Interrupt(true)
284
+ attachment.clearInterrupts(false, false, true, false)
285
+ expect(attachment.hasCB1Interrupt()).toBe(false)
286
+ })
287
+
288
+ it('should not clear CB1 when clearing other interrupts', () => {
289
+ attachment.setCB1Interrupt(true)
290
+ attachment.clearInterrupts(true, true, false, true)
291
+ expect(attachment.hasCB1Interrupt()).toBe(true)
292
+ })
293
+ })
294
+
295
+ describe('Interrupt Management - CB2', () => {
296
+ it('should check CB2 interrupt flag', () => {
297
+ expect(attachment.hasCB2Interrupt()).toBe(false)
298
+ attachment.setCB2Interrupt(true)
299
+ expect(attachment.hasCB2Interrupt()).toBe(true)
300
+ })
301
+
302
+ it('should clear CB2 interrupt', () => {
303
+ attachment.setCB2Interrupt(true)
304
+ attachment.clearInterrupts(false, false, false, true)
305
+ expect(attachment.hasCB2Interrupt()).toBe(false)
306
+ })
307
+
308
+ it('should not clear CB2 when clearing other interrupts', () => {
309
+ attachment.setCB2Interrupt(true)
310
+ attachment.clearInterrupts(true, true, true, false)
311
+ expect(attachment.hasCB2Interrupt()).toBe(true)
312
+ })
313
+ })
314
+
315
+ describe('Interrupt Management - Multiple', () => {
316
+ it('should clear multiple interrupts at once', () => {
317
+ attachment.setCA1Interrupt(true)
318
+ attachment.setCA2Interrupt(true)
319
+ attachment.setCB1Interrupt(true)
320
+ attachment.setCB2Interrupt(true)
321
+
322
+ attachment.clearInterrupts(true, true, false, false)
323
+
324
+ expect(attachment.hasCA1Interrupt()).toBe(false)
325
+ expect(attachment.hasCA2Interrupt()).toBe(false)
326
+ expect(attachment.hasCB1Interrupt()).toBe(true)
327
+ expect(attachment.hasCB2Interrupt()).toBe(true)
328
+ })
329
+
330
+ it('should clear all interrupts', () => {
331
+ attachment.setCA1Interrupt(true)
332
+ attachment.setCA2Interrupt(true)
333
+ attachment.setCB1Interrupt(true)
334
+ attachment.setCB2Interrupt(true)
335
+
336
+ attachment.clearInterrupts(true, true, true, true)
337
+
338
+ expect(attachment.hasCA1Interrupt()).toBe(false)
339
+ expect(attachment.hasCA2Interrupt()).toBe(false)
340
+ expect(attachment.hasCB1Interrupt()).toBe(false)
341
+ expect(attachment.hasCB2Interrupt()).toBe(false)
342
+ })
343
+
344
+ it('should handle clearing when no interrupts are set', () => {
345
+ attachment.clearInterrupts(true, true, true, true)
346
+
347
+ expect(attachment.hasCA1Interrupt()).toBe(false)
348
+ expect(attachment.hasCA2Interrupt()).toBe(false)
349
+ expect(attachment.hasCB1Interrupt()).toBe(false)
350
+ expect(attachment.hasCB2Interrupt()).toBe(false)
351
+ })
352
+ })
353
+
354
+ describe('Control Lines', () => {
355
+ it('should call updateControlLines', () => {
356
+ // Default implementation does nothing, just verify it doesn't crash
357
+ expect(() => {
358
+ attachment.updateControlLines(true, false, true, false)
359
+ }).not.toThrow()
360
+ })
361
+
362
+ it('should accept all control line combinations', () => {
363
+ expect(() => {
364
+ attachment.updateControlLines(false, false, false, false)
365
+ attachment.updateControlLines(true, true, true, true)
366
+ attachment.updateControlLines(true, false, true, false)
367
+ attachment.updateControlLines(false, true, false, true)
368
+ }).not.toThrow()
369
+ })
370
+ })
371
+
372
+ describe('Edge Cases', () => {
373
+ it('should handle rapid enable/disable cycles', () => {
374
+ for (let i = 0; i < 100; i++) {
375
+ attachment.setEnabled(i % 2 === 0)
376
+ }
377
+ // Last iteration (i=99) sets enabled to false (99 % 2 !== 0)
378
+ expect(attachment.isEnabled()).toBe(false)
379
+ })
380
+
381
+ it('should handle rapid interrupt set/clear cycles', () => {
382
+ for (let i = 0; i < 100; i++) {
383
+ attachment.setCA1Interrupt(true)
384
+ attachment.clearInterrupts(true, false, false, false)
385
+ }
386
+ expect(attachment.hasCA1Interrupt()).toBe(false)
387
+ })
388
+
389
+ it('should handle reset during active interrupts', () => {
390
+ attachment.setCA1Interrupt(true)
391
+ attachment.setCA2Interrupt(true)
392
+ attachment.setCB1Interrupt(true)
393
+ attachment.setCB2Interrupt(true)
394
+
395
+ attachment.reset()
396
+
397
+ expect(attachment.hasCA1Interrupt()).toBe(false)
398
+ expect(attachment.hasCA2Interrupt()).toBe(false)
399
+ expect(attachment.hasCB1Interrupt()).toBe(false)
400
+ expect(attachment.hasCB2Interrupt()).toBe(false)
401
+ })
402
+
403
+ it('should maintain state across multiple operations', () => {
404
+ attachment.setCA1Interrupt(true)
405
+ attachment.tick(1000000)
406
+ attachment.writePortA(0xAA, 0xFF)
407
+
408
+ expect(attachment.hasCA1Interrupt()).toBe(true)
409
+ expect(attachment.tickCount).toBe(1)
410
+ expect(attachment.writeAValue).toBe(0xAA)
411
+ })
412
+ })
413
+ })
@@ -0,0 +1,147 @@
1
+ import { GPIOJoystickAttachment } from '../../../components/IO/GPIOAttachments/GPIOJoystickAttachment'
2
+
3
+ describe('GPIOJoystickAttachment', () => {
4
+ let joystick: GPIOJoystickAttachment
5
+
6
+ beforeEach(() => {
7
+ joystick = new GPIOJoystickAttachment(true, 0)
8
+ })
9
+
10
+ describe('constructor and reset', () => {
11
+ it('should initialize with no buttons pressed', () => {
12
+ expect(joystick.getButtonState()).toBe(0x00)
13
+ })
14
+
15
+ it('should reset button state', () => {
16
+ joystick.updateJoystick(0xFF)
17
+ joystick.reset()
18
+ expect(joystick.getButtonState()).toBe(0x00)
19
+ })
20
+ })
21
+
22
+ describe('button state management', () => {
23
+ it('should update joystick state', () => {
24
+ joystick.updateJoystick(0x55)
25
+ expect(joystick.getButtonState()).toBe(0x55)
26
+ })
27
+
28
+ it('should check if button is pressed', () => {
29
+ joystick.pressButton(GPIOJoystickAttachment.BUTTON_A)
30
+ expect(joystick.isButtonPressed(GPIOJoystickAttachment.BUTTON_A)).toBe(true)
31
+ expect(joystick.isButtonPressed(GPIOJoystickAttachment.BUTTON_B)).toBe(false)
32
+ })
33
+
34
+ it('should press a button', () => {
35
+ joystick.pressButton(GPIOJoystickAttachment.BUTTON_START)
36
+ expect(joystick.getButtonState()).toBe(GPIOJoystickAttachment.BUTTON_START)
37
+ })
38
+
39
+ it('should release a button', () => {
40
+ joystick.updateJoystick(0xFF)
41
+ joystick.releaseButton(GPIOJoystickAttachment.BUTTON_A)
42
+ expect(joystick.getButtonState()).toBe(0xFF & ~GPIOJoystickAttachment.BUTTON_A)
43
+ })
44
+
45
+ it('should release all buttons', () => {
46
+ joystick.updateJoystick(0xFF)
47
+ joystick.releaseAllButtons()
48
+ expect(joystick.getButtonState()).toBe(0x00)
49
+ })
50
+ })
51
+
52
+ describe('port reading - Port A', () => {
53
+ beforeEach(() => {
54
+ joystick = new GPIOJoystickAttachment(true, 0) // Attach to Port A
55
+ })
56
+
57
+ it('should return inverted button state on Port A when attached', () => {
58
+ joystick.updateJoystick(0x00) // No buttons pressed
59
+ expect(joystick.readPortA(0x00, 0x00)).toBe(0xFF) // All bits high (active-low)
60
+ })
61
+
62
+ it('should return active-low values when buttons pressed', () => {
63
+ joystick.updateJoystick(0xFF) // All buttons pressed
64
+ expect(joystick.readPortA(0x00, 0x00)).toBe(0x00) // All bits low
65
+ })
66
+
67
+ it('should return FF on Port B when attached to Port A', () => {
68
+ joystick.updateJoystick(0xFF)
69
+ expect(joystick.readPortB(0x00, 0x00)).toBe(0xFF)
70
+ })
71
+
72
+ it('should handle individual button presses correctly', () => {
73
+ joystick.updateJoystick(GPIOJoystickAttachment.BUTTON_UP)
74
+ const result = joystick.readPortA(0x00, 0x00)
75
+ expect(result & 0x01).toBe(0x00) // UP button bit should be low (pressed)
76
+ expect(result & 0xFE).toBe(0xFE) // Other bits should be high (not pressed)
77
+ })
78
+ })
79
+
80
+ describe('port reading - Port B', () => {
81
+ beforeEach(() => {
82
+ joystick = new GPIOJoystickAttachment(false, 0) // Attach to Port B
83
+ })
84
+
85
+ it('should return inverted button state on Port B when attached', () => {
86
+ joystick.updateJoystick(0x00)
87
+ expect(joystick.readPortB(0x00, 0x00)).toBe(0xFF)
88
+ })
89
+
90
+ it('should return active-low values when buttons pressed on Port B', () => {
91
+ joystick.updateJoystick(0xFF)
92
+ expect(joystick.readPortB(0x00, 0x00)).toBe(0x00)
93
+ })
94
+
95
+ it('should return FF on Port A when attached to Port B', () => {
96
+ joystick.updateJoystick(0xFF)
97
+ expect(joystick.readPortA(0x00, 0x00)).toBe(0xFF)
98
+ })
99
+ })
100
+
101
+ describe('button constants', () => {
102
+ it('should have correct button bit values', () => {
103
+ expect(GPIOJoystickAttachment.BUTTON_UP).toBe(0x01)
104
+ expect(GPIOJoystickAttachment.BUTTON_DOWN).toBe(0x02)
105
+ expect(GPIOJoystickAttachment.BUTTON_LEFT).toBe(0x04)
106
+ expect(GPIOJoystickAttachment.BUTTON_RIGHT).toBe(0x08)
107
+ expect(GPIOJoystickAttachment.BUTTON_A).toBe(0x10)
108
+ expect(GPIOJoystickAttachment.BUTTON_B).toBe(0x20)
109
+ expect(GPIOJoystickAttachment.BUTTON_SELECT).toBe(0x40)
110
+ expect(GPIOJoystickAttachment.BUTTON_START).toBe(0x80)
111
+ })
112
+ })
113
+
114
+ describe('priority and enabled', () => {
115
+ it('should return correct priority', () => {
116
+ const j = new GPIOJoystickAttachment(true, 5)
117
+ expect(j.getPriority()).toBe(5)
118
+ })
119
+
120
+ it('should be enabled by default', () => {
121
+ expect(joystick.isEnabled()).toBe(true)
122
+ })
123
+ })
124
+
125
+ describe('interrupts', () => {
126
+ it('should not have any interrupts by default', () => {
127
+ expect(joystick.hasCA1Interrupt()).toBe(false)
128
+ expect(joystick.hasCA2Interrupt()).toBe(false)
129
+ expect(joystick.hasCB1Interrupt()).toBe(false)
130
+ expect(joystick.hasCB2Interrupt()).toBe(false)
131
+ })
132
+
133
+ it('should clear interrupts (no-op for joystick)', () => {
134
+ expect(() => {
135
+ joystick.clearInterrupts(true, true, true, true)
136
+ }).not.toThrow()
137
+ })
138
+ })
139
+
140
+ describe('tick', () => {
141
+ it('should not throw on tick', () => {
142
+ expect(() => {
143
+ joystick.tick(1000000)
144
+ }).not.toThrow()
145
+ })
146
+ })
147
+ })