jsbeeb 1.1.1
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/.editorconfig +15 -0
- package/.git-blame-ignore-revs +3 -0
- package/.github/copilot-instructions.md +94 -0
- package/.github/workflows/claude-issue-triage.yml +105 -0
- package/.github/workflows/claude.yml +63 -0
- package/.github/workflows/release-please.yml +75 -0
- package/.github/workflows/test-and-deploy.yml +86 -0
- package/.gitmodules +6 -0
- package/.husky/pre-commit +1 -0
- package/.idea/codeStyleSettings.xml +9 -0
- package/.idea/codeStyles/Project.xml +62 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/compiler.xml +22 -0
- package/.idea/copyright/profiles_settings.xml +3 -0
- package/.idea/encodings.xml +6 -0
- package/.idea/inspectionProfiles/Project_Default.xml +7 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/jsLinters/jshint.xml +85 -0
- package/.idea/jsLinters/jslint.xml +15 -0
- package/.idea/jsbeeb.iml +11 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/runConfigurations/Debug.xml +5 -0
- package/.idea/scopes/scope_settings.xml +5 -0
- package/.idea/vcs.xml +8 -0
- package/.prettierignore +4 -0
- package/.prettierrc.json +1 -0
- package/.release-please-manifest.json +3 -0
- package/.vscode/launch.json +14 -0
- package/.vscode/settings.json +6 -0
- package/CHANGELOG.md +32 -0
- package/CLAUDE.md +136 -0
- package/COPYING +674 -0
- package/Dockerfile +22 -0
- package/Makefile +30 -0
- package/README.md +259 -0
- package/docker/nginx-default.conf +10 -0
- package/docs/pal-comb-filter-research.md +129 -0
- package/docs/pal-simulation-design.md +368 -0
- package/eslint.config.js +35 -0
- package/index.html +954 -0
- package/jsconfig.json +10 -0
- package/package.json +102 -0
- package/public/discs/README.Irq-Timing +3 -0
- package/public/discs/README.bcdtest +5 -0
- package/public/discs/README.elite +6 -0
- package/public/discs/README.eng_test +3 -0
- package/public/discs/README.protection +7 -0
- package/public/favicon.ico +0 -0
- package/public/images/botbar.png +0 -0
- package/public/images/cub-monitor.png +0 -0
- package/public/images/jsbeeb-example.png +0 -0
- package/public/images/placeholder.png +0 -0
- package/public/images/red-off-16.png +0 -0
- package/public/images/red-on-16.png +0 -0
- package/public/images/sb/CD-left.jpg +0 -0
- package/public/images/sb/CD-right.jpg +0 -0
- package/public/images/sidebar.png +0 -0
- package/public/images/tv.png +0 -0
- package/public/images/yellow-off-16.png +0 -0
- package/public/images/yellow-on-16.png +0 -0
- package/public/jsbeeb-icon.png +0 -0
- package/public/robots.txt +3 -0
- package/public/roms/ADFS1-53.rom +0 -0
- package/public/roms/BASIC.ROM +0 -0
- package/public/roms/README +4 -0
- package/public/roms/a01/BASIC1.rom +0 -0
- package/public/roms/ample.rom +0 -0
- package/public/roms/ats-3.0.rom +0 -0
- package/public/roms/b/DFS-0.9.rom +0 -0
- package/public/roms/b/DFS-1.2.rom +0 -0
- package/public/roms/b1770/dfs1770.rom +0 -0
- package/public/roms/b1770/zADFS.ROM +0 -0
- package/public/roms/bp/dfs.rom +0 -0
- package/public/roms/bp/zADFS.ROM +0 -0
- package/public/roms/bpos.rom +0 -0
- package/public/roms/compact/adfs210.rom +0 -0
- package/public/roms/compact/basic48.rom +0 -0
- package/public/roms/compact/basic486.rom +0 -0
- package/public/roms/compact/os51.rom +0 -0
- package/public/roms/compact/utils.rom +0 -0
- package/public/roms/deos.rom +0 -0
- package/public/roms/master/anfs-4.25.rom +0 -0
- package/public/roms/master/mos.txt +68819 -0
- package/public/roms/master/mos3.20 +0 -0
- package/public/roms/os.rom +0 -0
- package/public/roms/os01.rom +0 -0
- package/public/roms/tube/6502Tube.rom +0 -0
- package/public/roms/tube/ARMeval_100.rom +0 -0
- package/public/roms/tube/BIOS.ROM +0 -0
- package/public/roms/tube/ReCo6502ROM_816 +0 -0
- package/public/roms/tube/Z80_120.rom +0 -0
- package/public/roms/us/USBASIC.rom +0 -0
- package/public/roms/us/USDNFS.rom +0 -0
- package/public/roms/usmos.rom +0 -0
- package/public/sounds/disc525/motor.wav +0 -0
- package/public/sounds/disc525/motoroff.wav +0 -0
- package/public/sounds/disc525/motoron.wav +0 -0
- package/public/sounds/disc525/seek.wav +0 -0
- package/public/sounds/disc525/seek2.wav +0 -0
- package/public/sounds/disc525/seek3.wav +0 -0
- package/public/sounds/disc525/step.wav +0 -0
- package/public/teletext/txt0.dat +0 -0
- package/public/teletext/txt1.dat +0 -0
- package/public/teletext/txt2.dat +0 -0
- package/public/teletext/txt3.dat +0 -0
- package/release-please-config.json +13 -0
- package/run-container.sh +92 -0
- package/src/6502.js +1347 -0
- package/src/6502.opcodes.js +1411 -0
- package/src/acia.js +261 -0
- package/src/adc.js +149 -0
- package/src/analogue-source.js +21 -0
- package/src/app/app.js +175 -0
- package/src/app/electron.js +20 -0
- package/src/app/preload.js +8 -0
- package/src/app-bench.js +33 -0
- package/src/basic/multiline-tetris +9 -0
- package/src/basic-tokenise.js +104 -0
- package/src/canvas.js +177 -0
- package/src/cmos.js +141 -0
- package/src/config.js +165 -0
- package/src/ddnoise.js +138 -0
- package/src/disc-drive.js +371 -0
- package/src/disc-hfe.js +396 -0
- package/src/disc.js +997 -0
- package/src/econet/L3FS.dat +0 -0
- package/src/econet/scsi.dat +0 -0
- package/src/econet.js +714 -0
- package/src/fake6502.js +32 -0
- package/src/fdc.js +248 -0
- package/src/filestore.js +666 -0
- package/src/gamepad-source.js +59 -0
- package/src/gamepads.js +268 -0
- package/src/google-drive.js +160 -0
- package/src/intel-fdc.js +1717 -0
- package/src/jsbeeb.css +363 -0
- package/src/keyboard.js +411 -0
- package/src/lib/README +1 -0
- package/src/lib/webgl-debug.js +911 -0
- package/src/main.js +1759 -0
- package/src/microphone-input.js +149 -0
- package/src/models.js +200 -0
- package/src/mouse-joystick-source.js +107 -0
- package/src/music5000-worklet.js +43 -0
- package/src/music5000.js +207 -0
- package/src/scheduler.js +148 -0
- package/src/serial.js +31 -0
- package/src/soundchip.js +314 -0
- package/src/sth.js +59 -0
- package/src/tapes.js +265 -0
- package/src/teletext.js +348 -0
- package/src/teletext_adaptor.js +172 -0
- package/src/teletext_data.js +1064 -0
- package/src/touchscreen.js +86 -0
- package/src/tube.js +349 -0
- package/src/url-params.js +256 -0
- package/src/utils.js +1090 -0
- package/src/via.js +702 -0
- package/src/video-filters/pal-composite.js +94 -0
- package/src/video-filters/passthrough-filter.js +70 -0
- package/src/video-filters/shaders/pal-composite.frag.glsl +147 -0
- package/src/video-filters/shaders/pal-composite.vert.glsl +8 -0
- package/src/video-filters/shaders/passthrough.frag.glsl +6 -0
- package/src/video-filters/shaders/passthrough.vert.glsl +7 -0
- package/src/video.js +794 -0
- package/src/wd-fdc.js +1344 -0
- package/src/web/audio-handler.js +146 -0
- package/src/web/audio-renderer.js +115 -0
- package/src/web/debug.js +529 -0
- package/tests/integration/RmwX.asm +47 -0
- package/tests/integration/TestInstructionsSource +27 -0
- package/tests/integration/TestTimingsResults +27 -0
- package/tests/integration/TestTimingsSource +61 -0
- package/tests/integration/bcd.js +23 -0
- package/tests/integration/dormann.js +101 -0
- package/tests/integration/dp111timing.js +42 -0
- package/tests/integration/ensure-submodules.js +25 -0
- package/tests/integration/nops.bas +119 -0
- package/tests/integration/nops.js +24 -0
- package/tests/integration/protection.js +26 -0
- package/tests/integration/rmw.js +69 -0
- package/tests/integration/teletext/expected_bug_469.png +0 -0
- package/tests/integration/teletext/expected_flash_0.png +0 -0
- package/tests/integration/teletext/expected_flash_1.png +0 -0
- package/tests/integration/teletext/expected_hoglet_held_char.png +0 -0
- package/tests/integration/teletext/expected_reveal_flash_0.png +0 -0
- package/tests/integration/teletext/expected_reveal_flash_1.png +0 -0
- package/tests/integration/teletext.js +126 -0
- package/tests/integration/timings.js +56 -0
- package/tests/integration/via.js +1125 -0
- package/tests/suite/README.md +7 -0
- package/tests/suite/Test Suite 2.15.txt +373 -0
- package/tests/suite/bin/ start +0 -0
- package/tests/suite/bin/adca +0 -0
- package/tests/suite/bin/adcax +0 -0
- package/tests/suite/bin/adcay +0 -0
- package/tests/suite/bin/adcb +0 -0
- package/tests/suite/bin/adcix +0 -0
- package/tests/suite/bin/adciy +0 -0
- package/tests/suite/bin/adcz +0 -0
- package/tests/suite/bin/adczx +0 -0
- package/tests/suite/bin/alrb +0 -0
- package/tests/suite/bin/ancb +0 -0
- package/tests/suite/bin/anda +0 -0
- package/tests/suite/bin/andax +0 -0
- package/tests/suite/bin/anday +0 -0
- package/tests/suite/bin/andb +0 -0
- package/tests/suite/bin/andix +0 -0
- package/tests/suite/bin/andiy +0 -0
- package/tests/suite/bin/andz +0 -0
- package/tests/suite/bin/andzx +0 -0
- package/tests/suite/bin/aneb +0 -0
- package/tests/suite/bin/arrb +0 -0
- package/tests/suite/bin/asla +0 -0
- package/tests/suite/bin/aslax +0 -0
- package/tests/suite/bin/asln +0 -0
- package/tests/suite/bin/aslz +0 -0
- package/tests/suite/bin/aslzx +0 -0
- package/tests/suite/bin/asoa +0 -0
- package/tests/suite/bin/asoax +0 -0
- package/tests/suite/bin/asoay +0 -0
- package/tests/suite/bin/asoix +0 -0
- package/tests/suite/bin/asoiy +0 -0
- package/tests/suite/bin/asoz +0 -0
- package/tests/suite/bin/asozx +0 -0
- package/tests/suite/bin/axsa +0 -0
- package/tests/suite/bin/axsix +0 -0
- package/tests/suite/bin/axsz +0 -0
- package/tests/suite/bin/axszy +0 -0
- package/tests/suite/bin/bccr +0 -0
- package/tests/suite/bin/bcsr +0 -0
- package/tests/suite/bin/beqr +0 -0
- package/tests/suite/bin/bita +0 -0
- package/tests/suite/bin/bitz +0 -0
- package/tests/suite/bin/bmir +0 -0
- package/tests/suite/bin/bner +0 -0
- package/tests/suite/bin/bplr +0 -0
- package/tests/suite/bin/branchwrap +0 -0
- package/tests/suite/bin/brkn +0 -0
- package/tests/suite/bin/bvcr +0 -0
- package/tests/suite/bin/bvsr +0 -0
- package/tests/suite/bin/cia1pb6 +0 -0
- package/tests/suite/bin/cia1pb7 +0 -0
- package/tests/suite/bin/cia1ta +0 -0
- package/tests/suite/bin/cia1tab +0 -0
- package/tests/suite/bin/cia1tb +0 -0
- package/tests/suite/bin/cia1tb123 +0 -0
- package/tests/suite/bin/cia2pb6 +0 -0
- package/tests/suite/bin/cia2pb7 +0 -0
- package/tests/suite/bin/cia2ta +0 -0
- package/tests/suite/bin/cia2tb +0 -0
- package/tests/suite/bin/cia2tb123 +0 -0
- package/tests/suite/bin/clcn +0 -0
- package/tests/suite/bin/cldn +0 -0
- package/tests/suite/bin/clin +0 -0
- package/tests/suite/bin/clvn +0 -0
- package/tests/suite/bin/cmpa +0 -0
- package/tests/suite/bin/cmpax +0 -0
- package/tests/suite/bin/cmpay +0 -0
- package/tests/suite/bin/cmpb +0 -0
- package/tests/suite/bin/cmpix +0 -0
- package/tests/suite/bin/cmpiy +0 -0
- package/tests/suite/bin/cmpz +0 -0
- package/tests/suite/bin/cmpzx +0 -0
- package/tests/suite/bin/cntdef +0 -0
- package/tests/suite/bin/cnto2 +0 -0
- package/tests/suite/bin/cpuport +0 -0
- package/tests/suite/bin/cputiming +0 -0
- package/tests/suite/bin/cpxa +0 -0
- package/tests/suite/bin/cpxb +0 -0
- package/tests/suite/bin/cpxz +0 -0
- package/tests/suite/bin/cpya +0 -0
- package/tests/suite/bin/cpyb +0 -0
- package/tests/suite/bin/cpyz +0 -0
- package/tests/suite/bin/dcma +0 -0
- package/tests/suite/bin/dcmax +0 -0
- package/tests/suite/bin/dcmay +0 -0
- package/tests/suite/bin/dcmix +0 -0
- package/tests/suite/bin/dcmiy +0 -0
- package/tests/suite/bin/dcmz +0 -0
- package/tests/suite/bin/dcmzx +0 -0
- package/tests/suite/bin/deca +0 -0
- package/tests/suite/bin/decax +0 -0
- package/tests/suite/bin/decz +0 -0
- package/tests/suite/bin/deczx +0 -0
- package/tests/suite/bin/dexn +0 -0
- package/tests/suite/bin/deyn +0 -0
- package/tests/suite/bin/eora +0 -0
- package/tests/suite/bin/eorax +0 -0
- package/tests/suite/bin/eoray +0 -0
- package/tests/suite/bin/eorb +0 -0
- package/tests/suite/bin/eorix +0 -0
- package/tests/suite/bin/eoriy +0 -0
- package/tests/suite/bin/eorz +0 -0
- package/tests/suite/bin/eorzx +0 -0
- package/tests/suite/bin/finish +0 -0
- package/tests/suite/bin/flipos +0 -0
- package/tests/suite/bin/icr01 +0 -0
- package/tests/suite/bin/imr +0 -0
- package/tests/suite/bin/inca +0 -0
- package/tests/suite/bin/incax +0 -0
- package/tests/suite/bin/incz +0 -0
- package/tests/suite/bin/inczx +0 -0
- package/tests/suite/bin/insa +0 -0
- package/tests/suite/bin/insax +0 -0
- package/tests/suite/bin/insay +0 -0
- package/tests/suite/bin/insix +0 -0
- package/tests/suite/bin/insiy +0 -0
- package/tests/suite/bin/insz +0 -0
- package/tests/suite/bin/inszx +0 -0
- package/tests/suite/bin/inxn +0 -0
- package/tests/suite/bin/inyn +0 -0
- package/tests/suite/bin/irq +0 -0
- package/tests/suite/bin/jmpi +0 -0
- package/tests/suite/bin/jmpw +0 -0
- package/tests/suite/bin/jsrw +0 -0
- package/tests/suite/bin/lasay +0 -0
- package/tests/suite/bin/laxa +0 -0
- package/tests/suite/bin/laxay +0 -0
- package/tests/suite/bin/laxix +0 -0
- package/tests/suite/bin/laxiy +0 -0
- package/tests/suite/bin/laxz +0 -0
- package/tests/suite/bin/laxzy +0 -0
- package/tests/suite/bin/ldaa +0 -0
- package/tests/suite/bin/ldaax +0 -0
- package/tests/suite/bin/ldaay +0 -0
- package/tests/suite/bin/ldab +0 -0
- package/tests/suite/bin/ldaix +0 -0
- package/tests/suite/bin/ldaiy +0 -0
- package/tests/suite/bin/ldaz +0 -0
- package/tests/suite/bin/ldazx +0 -0
- package/tests/suite/bin/ldxa +0 -0
- package/tests/suite/bin/ldxay +0 -0
- package/tests/suite/bin/ldxb +0 -0
- package/tests/suite/bin/ldxz +0 -0
- package/tests/suite/bin/ldxzy +0 -0
- package/tests/suite/bin/ldya +0 -0
- package/tests/suite/bin/ldyax +0 -0
- package/tests/suite/bin/ldyb +0 -0
- package/tests/suite/bin/ldyz +0 -0
- package/tests/suite/bin/ldyzx +0 -0
- package/tests/suite/bin/loadth +0 -0
- package/tests/suite/bin/lsea +0 -0
- package/tests/suite/bin/lseax +0 -0
- package/tests/suite/bin/lseay +0 -0
- package/tests/suite/bin/lseix +0 -0
- package/tests/suite/bin/lseiy +0 -0
- package/tests/suite/bin/lsez +0 -0
- package/tests/suite/bin/lsezx +0 -0
- package/tests/suite/bin/lsra +0 -0
- package/tests/suite/bin/lsrax +0 -0
- package/tests/suite/bin/lsrn +0 -0
- package/tests/suite/bin/lsrz +0 -0
- package/tests/suite/bin/lsrzx +0 -0
- package/tests/suite/bin/lxab +0 -0
- package/tests/suite/bin/mmu +0 -0
- package/tests/suite/bin/mmufetch +0 -0
- package/tests/suite/bin/nmi +0 -0
- package/tests/suite/bin/nopa +0 -0
- package/tests/suite/bin/nopax +0 -0
- package/tests/suite/bin/nopb +0 -0
- package/tests/suite/bin/nopn +0 -0
- package/tests/suite/bin/nopz +0 -0
- package/tests/suite/bin/nopzx +0 -0
- package/tests/suite/bin/oneshot +0 -0
- package/tests/suite/bin/oraa +0 -0
- package/tests/suite/bin/oraax +0 -0
- package/tests/suite/bin/oraay +0 -0
- package/tests/suite/bin/orab +0 -0
- package/tests/suite/bin/oraix +0 -0
- package/tests/suite/bin/oraiy +0 -0
- package/tests/suite/bin/oraz +0 -0
- package/tests/suite/bin/orazx +0 -0
- package/tests/suite/bin/phan +0 -0
- package/tests/suite/bin/phpn +0 -0
- package/tests/suite/bin/plan +0 -0
- package/tests/suite/bin/plpn +0 -0
- package/tests/suite/bin/rlaa +0 -0
- package/tests/suite/bin/rlaax +0 -0
- package/tests/suite/bin/rlaay +0 -0
- package/tests/suite/bin/rlaix +0 -0
- package/tests/suite/bin/rlaiy +0 -0
- package/tests/suite/bin/rlaz +0 -0
- package/tests/suite/bin/rlazx +0 -0
- package/tests/suite/bin/rola +0 -0
- package/tests/suite/bin/rolax +0 -0
- package/tests/suite/bin/roln +0 -0
- package/tests/suite/bin/rolz +0 -0
- package/tests/suite/bin/rolzx +0 -0
- package/tests/suite/bin/rora +0 -0
- package/tests/suite/bin/rorax +0 -0
- package/tests/suite/bin/rorn +0 -0
- package/tests/suite/bin/rorz +0 -0
- package/tests/suite/bin/rorzx +0 -0
- package/tests/suite/bin/rraa +0 -0
- package/tests/suite/bin/rraax +0 -0
- package/tests/suite/bin/rraay +0 -0
- package/tests/suite/bin/rraix +0 -0
- package/tests/suite/bin/rraiy +0 -0
- package/tests/suite/bin/rraz +0 -0
- package/tests/suite/bin/rrazx +0 -0
- package/tests/suite/bin/rtin +0 -0
- package/tests/suite/bin/rtsn +0 -0
- package/tests/suite/bin/sbca +0 -0
- package/tests/suite/bin/sbcax +0 -0
- package/tests/suite/bin/sbcay +0 -0
- package/tests/suite/bin/sbcb +0 -0
- package/tests/suite/bin/sbcb(eb) +0 -0
- package/tests/suite/bin/sbcix +0 -0
- package/tests/suite/bin/sbciy +0 -0
- package/tests/suite/bin/sbcz +0 -0
- package/tests/suite/bin/sbczx +0 -0
- package/tests/suite/bin/sbxb +0 -0
- package/tests/suite/bin/secn +0 -0
- package/tests/suite/bin/sedn +0 -0
- package/tests/suite/bin/sein +0 -0
- package/tests/suite/bin/shaay +0 -0
- package/tests/suite/bin/shaiy +0 -0
- package/tests/suite/bin/shsay +0 -0
- package/tests/suite/bin/shxay +0 -0
- package/tests/suite/bin/shyax +0 -0
- package/tests/suite/bin/staa +0 -0
- package/tests/suite/bin/staax +0 -0
- package/tests/suite/bin/staay +0 -0
- package/tests/suite/bin/staix +0 -0
- package/tests/suite/bin/staiy +0 -0
- package/tests/suite/bin/staz +0 -0
- package/tests/suite/bin/stazx +0 -0
- package/tests/suite/bin/stxa +0 -0
- package/tests/suite/bin/stxz +0 -0
- package/tests/suite/bin/stxzy +0 -0
- package/tests/suite/bin/stya +0 -0
- package/tests/suite/bin/styz +0 -0
- package/tests/suite/bin/styzx +0 -0
- package/tests/suite/bin/taxn +0 -0
- package/tests/suite/bin/tayn +0 -0
- package/tests/suite/bin/trap1 +0 -0
- package/tests/suite/bin/trap10 +0 -0
- package/tests/suite/bin/trap11 +0 -0
- package/tests/suite/bin/trap12 +0 -0
- package/tests/suite/bin/trap13 +0 -0
- package/tests/suite/bin/trap14 +0 -0
- package/tests/suite/bin/trap15 +0 -0
- package/tests/suite/bin/trap16 +0 -0
- package/tests/suite/bin/trap17 +0 -0
- package/tests/suite/bin/trap2 +0 -0
- package/tests/suite/bin/trap3 +0 -0
- package/tests/suite/bin/trap4 +0 -0
- package/tests/suite/bin/trap5 +0 -0
- package/tests/suite/bin/trap6 +0 -0
- package/tests/suite/bin/trap7 +0 -0
- package/tests/suite/bin/trap8 +0 -0
- package/tests/suite/bin/trap9 +0 -0
- package/tests/suite/bin/tsxn +0 -0
- package/tests/suite/bin/txan +0 -0
- package/tests/suite/bin/txsn +0 -0
- package/tests/suite/bin/tyan +0 -0
- package/tests/suite/cbm-hackers-post.html +178 -0
- package/tests/suite/cbm-hackers-post.md +78 -0
- package/tests/test-machine.js +288 -0
- package/tests/test-suite.js +147 -0
- package/tests/test.css +7 -0
- package/tests/unit/gzip/test-1 +0 -0
- package/tests/unit/gzip/test-1.gz +0 -0
- package/tests/unit/gzip/test-2 +0 -0
- package/tests/unit/gzip/test-2.gz +0 -0
- package/tests/unit/gzip/test-3 +0 -0
- package/tests/unit/gzip/test-3.gz +0 -0
- package/tests/unit/gzip/test-4 +0 -0
- package/tests/unit/gzip/test-4.gz +0 -0
- package/tests/unit/test-adc.js +307 -0
- package/tests/unit/test-bcd.js +30 -0
- package/tests/unit/test-cmos.js +266 -0
- package/tests/unit/test-disc-drive.js +85 -0
- package/tests/unit/test-disc-hfe.js +347 -0
- package/tests/unit/test-disc.js +232 -0
- package/tests/unit/test-fifo.js +35 -0
- package/tests/unit/test-gamepad-source.js +67 -0
- package/tests/unit/test-gzip.js +22 -0
- package/tests/unit/test-intel-fdc.js +93 -0
- package/tests/unit/test-keyboard.js +410 -0
- package/tests/unit/test-mouse-joystick-source.js +128 -0
- package/tests/unit/test-scheduler.js +190 -0
- package/tests/unit/test-serial.js +154 -0
- package/tests/unit/test-teletext-adaptor.js +359 -0
- package/tests/unit/test-tokenise.js +65 -0
- package/tests/unit/test-url-params.js +398 -0
- package/tests/unit/test-utils.js +276 -0
- package/tests/unit/test-video.js +498 -0
- package/tests/unit/test-zip.js +56 -0
- package/tests/unit/zip/test-mixed.zip +0 -0
- package/tests/unit/zip/test-rom.zip +0 -0
- package/tests/unit/zip/test-ssd.zip +0 -0
- package/tools/fir-generator.js +80 -0
- package/tools/vite-plugin-fir-shader.js +131 -0
- package/vite.config.js +34 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as utils from "../../src/utils.js";
|
|
4
|
+
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
function testOneFile(file) {
|
|
11
|
+
const compressed = new Uint8Array(fs.readFileSync(`${file}.gz`));
|
|
12
|
+
const expected = new Uint8Array(fs.readFileSync(file));
|
|
13
|
+
expect(utils.ungzip(compressed)).toEqual(expected);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("gzip tests", function () {
|
|
17
|
+
for (let fileIndex = 1; ; fileIndex++) {
|
|
18
|
+
let file = join(__dirname, "gzip", `test-${fileIndex}`);
|
|
19
|
+
if (!fs.existsSync(file)) break;
|
|
20
|
+
it("handles test case " + file, () => testOneFile(file));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { Scheduler } from "../../src/scheduler.js";
|
|
4
|
+
import { IntelFdc } from "../../src/intel-fdc.js";
|
|
5
|
+
import { fake6502 } from "../../src/fake6502.js";
|
|
6
|
+
|
|
7
|
+
class FakeDrive {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.spinning = false;
|
|
10
|
+
this.pulsesCallback = null;
|
|
11
|
+
this.upperSide = false;
|
|
12
|
+
this.track = 0;
|
|
13
|
+
}
|
|
14
|
+
selectSide(side) {
|
|
15
|
+
this.upperSide = side;
|
|
16
|
+
}
|
|
17
|
+
setPulsesCallback(callback) {
|
|
18
|
+
this.pulsesCallback = callback;
|
|
19
|
+
}
|
|
20
|
+
startSpinning() {
|
|
21
|
+
this.spinning = true;
|
|
22
|
+
}
|
|
23
|
+
stopSpinning() {
|
|
24
|
+
this.spinning = false;
|
|
25
|
+
}
|
|
26
|
+
seekOneTrack(dir) {
|
|
27
|
+
this.track = this.track + dir;
|
|
28
|
+
}
|
|
29
|
+
notifySeek() {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {IntelFdc} fdc
|
|
34
|
+
* @param {Number} command
|
|
35
|
+
* @param {...Number} params
|
|
36
|
+
*/
|
|
37
|
+
function sendCommand(fdc, command, ...params) {
|
|
38
|
+
fdc.write(0, command);
|
|
39
|
+
for (const param of params) fdc.write(1, param);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("Intel 8271 tests", function () {
|
|
43
|
+
it("should contruct and start out idle", () => {
|
|
44
|
+
const fakeCpu = fake6502();
|
|
45
|
+
const scheduler = new Scheduler();
|
|
46
|
+
const fdc = new IntelFdc(fakeCpu, scheduler);
|
|
47
|
+
expect(fdc.internalStatus).toBe(0);
|
|
48
|
+
expect(scheduler.headroom()).toBe(Scheduler.MaxHeadroom);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should go busy as soon as a command is registered", () => {
|
|
52
|
+
const fakeCpu = fake6502();
|
|
53
|
+
const scheduler = new Scheduler();
|
|
54
|
+
const fdc = new IntelFdc(fakeCpu, scheduler);
|
|
55
|
+
fdc.write(0, 0x3a);
|
|
56
|
+
expect(fdc.internalStatus).toBe(0x80); // 0x80 = busy
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const loadHead = 0x08;
|
|
60
|
+
const select1 = 0x40;
|
|
61
|
+
const writeRegCmd = 0x3a;
|
|
62
|
+
const mmioWrite = 0x23;
|
|
63
|
+
const seekCmd = (0x0a << 2) | select1 | 1;
|
|
64
|
+
|
|
65
|
+
it("should spin up when poked", () => {
|
|
66
|
+
const fakeCpu = fake6502();
|
|
67
|
+
const scheduler = new Scheduler();
|
|
68
|
+
const fakeDrive = new FakeDrive();
|
|
69
|
+
const fdc = new IntelFdc(fakeCpu, scheduler, [fakeDrive]);
|
|
70
|
+
expect(fdc._driveOut & loadHead).toBe(0);
|
|
71
|
+
expect(fakeDrive.spinning).toBe(false);
|
|
72
|
+
sendCommand(fdc, writeRegCmd, mmioWrite, loadHead | select1);
|
|
73
|
+
expect(fdc._driveOut & loadHead).toBe(loadHead);
|
|
74
|
+
expect(fakeDrive.spinning).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
it("should seek to a track", () => {
|
|
77
|
+
const fakeCpu = fake6502();
|
|
78
|
+
const scheduler = new Scheduler();
|
|
79
|
+
const fakeDrive = new FakeDrive();
|
|
80
|
+
const fdc = new IntelFdc(fakeCpu, scheduler, [fakeDrive]);
|
|
81
|
+
sendCommand(fdc, writeRegCmd, mmioWrite, loadHead | select1);
|
|
82
|
+
// nb will seek two more due to bad track nonsense
|
|
83
|
+
sendCommand(fdc, seekCmd, 2);
|
|
84
|
+
expect(fakeDrive.track).toBe(1);
|
|
85
|
+
// We should have some 3ms step scheduled
|
|
86
|
+
expect(scheduler.headroom()).toBe(6000);
|
|
87
|
+
scheduler.polltime(6000);
|
|
88
|
+
expect(fakeDrive.track).toBe(2);
|
|
89
|
+
// We should reach and stop at track 4.
|
|
90
|
+
scheduler.polltime(6000 * 10);
|
|
91
|
+
expect(fakeDrive.track).toBe(4);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { expect, describe, test, beforeEach, vi } from "vitest";
|
|
2
|
+
import { Keyboard } from "../../src/keyboard.js";
|
|
3
|
+
import * as utils from "../../src/utils.js";
|
|
4
|
+
|
|
5
|
+
describe("Keyboard", () => {
|
|
6
|
+
let keyboard;
|
|
7
|
+
let mockProcessor;
|
|
8
|
+
let mockSysvia;
|
|
9
|
+
let mockInputEnabledFunction;
|
|
10
|
+
|
|
11
|
+
// Helper function to create an async event tester
|
|
12
|
+
const waitForEvent = (eventName) => {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
keyboard.on(eventName, (...args) => {
|
|
15
|
+
resolve(args);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Helper to trigger an event and wait for the result
|
|
21
|
+
const triggerAndWaitForEvent = async (eventName, action) => {
|
|
22
|
+
const eventPromise = waitForEvent(eventName);
|
|
23
|
+
action();
|
|
24
|
+
return await eventPromise;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
mockSysvia = {
|
|
29
|
+
keyDown: vi.fn(),
|
|
30
|
+
keyUp: vi.fn(),
|
|
31
|
+
clearKeys: vi.fn(),
|
|
32
|
+
disableKeyboard: vi.fn(),
|
|
33
|
+
enableKeyboard: vi.fn(),
|
|
34
|
+
keyToggleRaw: vi.fn(),
|
|
35
|
+
setKeyLayout: vi.fn(),
|
|
36
|
+
capsLockLight: false,
|
|
37
|
+
shiftLockLight: false,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
mockProcessor = {
|
|
41
|
+
sysvia: mockSysvia,
|
|
42
|
+
debugInstruction: {
|
|
43
|
+
add: vi.fn().mockReturnValue({
|
|
44
|
+
remove: vi.fn(),
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
setReset: vi.fn(),
|
|
48
|
+
cpuMultiplier: 1,
|
|
49
|
+
cycleSeconds: 0,
|
|
50
|
+
currentCycles: 0,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
mockInputEnabledFunction = vi.fn().mockReturnValue(false);
|
|
54
|
+
|
|
55
|
+
keyboard = new Keyboard({
|
|
56
|
+
processor: mockProcessor,
|
|
57
|
+
inputEnabledFunction: mockInputEnabledFunction,
|
|
58
|
+
keyLayout: "physical",
|
|
59
|
+
dbgr: {
|
|
60
|
+
enabled: vi.fn().mockReturnValue(false),
|
|
61
|
+
keyPress: vi.fn(),
|
|
62
|
+
hide: vi.fn(),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("should create a keyboard instance", () => {
|
|
68
|
+
expect(keyboard).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("keyCode should handle location properly", () => {
|
|
72
|
+
// Test SHIFT key
|
|
73
|
+
const shiftEvent = { which: utils.keyCodes.SHIFT, location: 1 };
|
|
74
|
+
expect(keyboard.keyCode(shiftEvent)).toBe(utils.keyCodes.SHIFT_LEFT);
|
|
75
|
+
|
|
76
|
+
const shiftEvent2 = { which: utils.keyCodes.SHIFT, location: 2 };
|
|
77
|
+
expect(keyboard.keyCode(shiftEvent2)).toBe(utils.keyCodes.SHIFT_RIGHT);
|
|
78
|
+
|
|
79
|
+
// Test CTRL key
|
|
80
|
+
const ctrlEvent = { which: utils.keyCodes.CTRL, location: 1 };
|
|
81
|
+
expect(keyboard.keyCode(ctrlEvent)).toBe(utils.keyCodes.CTRL_LEFT);
|
|
82
|
+
|
|
83
|
+
const ctrlEvent2 = { which: utils.keyCodes.CTRL, location: 2 };
|
|
84
|
+
expect(keyboard.keyCode(ctrlEvent2)).toBe(utils.keyCodes.CTRL_RIGHT);
|
|
85
|
+
|
|
86
|
+
// Test ALT key
|
|
87
|
+
const altEvent = { which: utils.keyCodes.ALT, location: 1 };
|
|
88
|
+
expect(keyboard.keyCode(altEvent)).toBe(utils.keyCodes.ALT_LEFT);
|
|
89
|
+
|
|
90
|
+
const altEvent2 = { which: utils.keyCodes.ALT, location: 2 };
|
|
91
|
+
expect(keyboard.keyCode(altEvent2)).toBe(utils.keyCodes.ALT_RIGHT);
|
|
92
|
+
|
|
93
|
+
// Test numpad
|
|
94
|
+
const enterEvent = { which: utils.keyCodes.ENTER, location: 3 };
|
|
95
|
+
expect(keyboard.keyCode(enterEvent)).toBe(utils.keyCodes.NUMPADENTER);
|
|
96
|
+
|
|
97
|
+
const deleteEvent = { which: utils.keyCodes.DELETE, location: 3 };
|
|
98
|
+
expect(keyboard.keyCode(deleteEvent)).toBe(utils.keyCodes.NUMPAD_DECIMAL_POINT);
|
|
99
|
+
|
|
100
|
+
// Test normal key
|
|
101
|
+
const normalEvent = { which: utils.keyCodes.A, location: 0 };
|
|
102
|
+
expect(keyboard.keyCode(normalEvent)).toBe(utils.keyCodes.A);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("keyCode should remember last modifier key locations", () => {
|
|
106
|
+
// First, set modifier locations to known values
|
|
107
|
+
keyboard.keyCode({ which: utils.keyCodes.SHIFT, location: 1 });
|
|
108
|
+
keyboard.keyCode({ which: utils.keyCodes.CTRL, location: 1 });
|
|
109
|
+
keyboard.keyCode({ which: utils.keyCodes.ALT, location: 1 });
|
|
110
|
+
|
|
111
|
+
// When location = 0 (like in keyUp events), should return based on last location
|
|
112
|
+
expect(keyboard.keyCode({ which: utils.keyCodes.SHIFT, location: 0 })).toBe(utils.keyCodes.SHIFT_LEFT);
|
|
113
|
+
expect(keyboard.keyCode({ which: utils.keyCodes.CTRL, location: 0 })).toBe(utils.keyCodes.CTRL_LEFT);
|
|
114
|
+
expect(keyboard.keyCode({ which: utils.keyCodes.ALT, location: 0 })).toBe(utils.keyCodes.ALT_LEFT);
|
|
115
|
+
|
|
116
|
+
// Change the locations to right side
|
|
117
|
+
keyboard.keyCode({ which: utils.keyCodes.SHIFT, location: 2 });
|
|
118
|
+
keyboard.keyCode({ which: utils.keyCodes.CTRL, location: 2 });
|
|
119
|
+
keyboard.keyCode({ which: utils.keyCodes.ALT, location: 2 });
|
|
120
|
+
|
|
121
|
+
// Should now use the updated locations
|
|
122
|
+
expect(keyboard.keyCode({ which: utils.keyCodes.SHIFT, location: 0 })).toBe(utils.keyCodes.SHIFT_RIGHT);
|
|
123
|
+
expect(keyboard.keyCode({ which: utils.keyCodes.CTRL, location: 0 })).toBe(utils.keyCodes.CTRL_RIGHT);
|
|
124
|
+
expect(keyboard.keyCode({ which: utils.keyCodes.ALT, location: 0 })).toBe(utils.keyCodes.ALT_RIGHT);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("keyDown should handle normal key press", () => {
|
|
128
|
+
const event = {
|
|
129
|
+
which: utils.keyCodes.A,
|
|
130
|
+
location: 0,
|
|
131
|
+
preventDefault: vi.fn(),
|
|
132
|
+
ctrlKey: false,
|
|
133
|
+
altKey: false,
|
|
134
|
+
shiftKey: false,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
keyboard.setRunning(true);
|
|
138
|
+
keyboard.keyDown(event);
|
|
139
|
+
|
|
140
|
+
expect(mockSysvia.keyDown).toHaveBeenCalledWith(utils.keyCodes.A, false);
|
|
141
|
+
expect(event.preventDefault).toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("keyDown should not handle keys when not running", () => {
|
|
145
|
+
const event = {
|
|
146
|
+
which: utils.keyCodes.A,
|
|
147
|
+
location: 0,
|
|
148
|
+
preventDefault: vi.fn(),
|
|
149
|
+
ctrlKey: false,
|
|
150
|
+
altKey: false,
|
|
151
|
+
shiftKey: false,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
keyboard.setRunning(false);
|
|
155
|
+
keyboard.keyDown(event);
|
|
156
|
+
|
|
157
|
+
expect(mockSysvia.keyDown).not.toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("keyDown should not handle keys when input is enabled", () => {
|
|
161
|
+
const event = {
|
|
162
|
+
which: utils.keyCodes.A,
|
|
163
|
+
location: 0,
|
|
164
|
+
preventDefault: vi.fn(),
|
|
165
|
+
ctrlKey: false,
|
|
166
|
+
altKey: false,
|
|
167
|
+
shiftKey: false,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Set input enabled to true
|
|
171
|
+
mockInputEnabledFunction.mockReturnValueOnce(true);
|
|
172
|
+
|
|
173
|
+
keyboard.setRunning(true);
|
|
174
|
+
keyboard.keyDown(event);
|
|
175
|
+
|
|
176
|
+
expect(mockInputEnabledFunction).toHaveBeenCalled();
|
|
177
|
+
expect(mockSysvia.keyDown).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("keyDown should handle F12/BREAK and emit break event", async () => {
|
|
181
|
+
const event = {
|
|
182
|
+
which: utils.keyCodes.F12,
|
|
183
|
+
location: 0,
|
|
184
|
+
preventDefault: vi.fn(),
|
|
185
|
+
ctrlKey: false,
|
|
186
|
+
altKey: false,
|
|
187
|
+
shiftKey: false,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
keyboard.setRunning(true);
|
|
191
|
+
|
|
192
|
+
const [breakState] = await triggerAndWaitForEvent("break", () => {
|
|
193
|
+
keyboard.keyDown(event);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(mockProcessor.setReset).toHaveBeenCalledWith(true);
|
|
197
|
+
expect(event.preventDefault).toHaveBeenCalled();
|
|
198
|
+
expect(breakState).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("keyUp should call sysvia.keyUp", () => {
|
|
202
|
+
const event = {
|
|
203
|
+
which: utils.keyCodes.A,
|
|
204
|
+
location: 0,
|
|
205
|
+
preventDefault: vi.fn(),
|
|
206
|
+
altKey: false,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
keyboard.setRunning(true);
|
|
210
|
+
keyboard.keyUp(event);
|
|
211
|
+
|
|
212
|
+
expect(mockSysvia.keyUp).toHaveBeenCalledWith(utils.keyCodes.A);
|
|
213
|
+
expect(event.preventDefault).toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("keyUp should not proceed when input is enabled", () => {
|
|
217
|
+
const event = {
|
|
218
|
+
which: utils.keyCodes.A,
|
|
219
|
+
location: 0,
|
|
220
|
+
preventDefault: vi.fn(),
|
|
221
|
+
altKey: false,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Set input enabled to true
|
|
225
|
+
mockInputEnabledFunction.mockReturnValueOnce(true);
|
|
226
|
+
|
|
227
|
+
keyboard.setRunning(true);
|
|
228
|
+
keyboard.keyUp(event);
|
|
229
|
+
|
|
230
|
+
expect(mockInputEnabledFunction).toHaveBeenCalled();
|
|
231
|
+
expect(mockSysvia.keyUp).not.toHaveBeenCalled();
|
|
232
|
+
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("keyUp should handle F12/BREAK and emit break event", async () => {
|
|
236
|
+
const event = {
|
|
237
|
+
which: utils.keyCodes.F12,
|
|
238
|
+
location: 0,
|
|
239
|
+
preventDefault: vi.fn(),
|
|
240
|
+
altKey: false,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
keyboard.setRunning(true);
|
|
244
|
+
|
|
245
|
+
const [breakState] = await triggerAndWaitForEvent("break", () => {
|
|
246
|
+
keyboard.keyUp(event);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(mockProcessor.setReset).toHaveBeenCalledWith(false);
|
|
250
|
+
expect(event.preventDefault).toHaveBeenCalled();
|
|
251
|
+
expect(breakState).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("clearKeys should call sysvia.clearKeys", () => {
|
|
255
|
+
keyboard.clearKeys();
|
|
256
|
+
expect(mockSysvia.clearKeys).toHaveBeenCalled();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("setKeyLayout should update config and call processor to update layout", () => {
|
|
260
|
+
keyboard.setKeyLayout("gaming");
|
|
261
|
+
expect(mockSysvia.setKeyLayout).toHaveBeenCalledWith("gaming");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("keyPress should not proceed when input is enabled", () => {
|
|
265
|
+
const event = {
|
|
266
|
+
which: 103, // lowercase g key
|
|
267
|
+
location: 0,
|
|
268
|
+
preventDefault: vi.fn(),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Set input enabled to true
|
|
272
|
+
mockInputEnabledFunction.mockReturnValueOnce(true);
|
|
273
|
+
|
|
274
|
+
// Add a resume event listener to check it's not called
|
|
275
|
+
const resumeListener = vi.fn();
|
|
276
|
+
keyboard.on("resume", resumeListener);
|
|
277
|
+
|
|
278
|
+
keyboard.keyPress(event);
|
|
279
|
+
|
|
280
|
+
expect(mockInputEnabledFunction).toHaveBeenCalled();
|
|
281
|
+
// No events should be emitted when input is enabled
|
|
282
|
+
expect(resumeListener).not.toHaveBeenCalled();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("keyPress should emit resume event when lowercase g pressed in pause mode", async () => {
|
|
286
|
+
const event = {
|
|
287
|
+
which: 103, // lowercase g key
|
|
288
|
+
location: 0,
|
|
289
|
+
preventDefault: vi.fn(),
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
keyboard.pauseEmu = true;
|
|
293
|
+
|
|
294
|
+
const eventPromise = waitForEvent("resume");
|
|
295
|
+
keyboard.keyPress(event);
|
|
296
|
+
await eventPromise;
|
|
297
|
+
|
|
298
|
+
expect(keyboard.pauseEmu).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("keyPress should handle debugger g key and emit resume event", async () => {
|
|
302
|
+
const event = {
|
|
303
|
+
which: 103, // lowercase g key
|
|
304
|
+
location: 0,
|
|
305
|
+
preventDefault: vi.fn(),
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Mock debugger enabled
|
|
309
|
+
const mockDbgr = {
|
|
310
|
+
enabled: vi.fn().mockReturnValue(true),
|
|
311
|
+
keyPress: vi.fn(),
|
|
312
|
+
hide: vi.fn(),
|
|
313
|
+
};
|
|
314
|
+
keyboard.dbgr = mockDbgr;
|
|
315
|
+
|
|
316
|
+
const eventPromise = waitForEvent("resume");
|
|
317
|
+
keyboard.keyPress(event);
|
|
318
|
+
await eventPromise;
|
|
319
|
+
|
|
320
|
+
expect(mockDbgr.hide).toHaveBeenCalled();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("registerKeyHandler should add a handler for a key with Alt modifier", () => {
|
|
324
|
+
const mockHandler = vi.fn();
|
|
325
|
+
keyboard.registerKeyHandler(utils.keyCodes.Q, mockHandler, { alt: true, ctrl: false });
|
|
326
|
+
|
|
327
|
+
const event = {
|
|
328
|
+
which: utils.keyCodes.Q,
|
|
329
|
+
location: 0,
|
|
330
|
+
preventDefault: vi.fn(),
|
|
331
|
+
ctrlKey: false,
|
|
332
|
+
altKey: true,
|
|
333
|
+
shiftKey: false,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
keyboard.setRunning(true);
|
|
337
|
+
keyboard.keyDown(event);
|
|
338
|
+
|
|
339
|
+
expect(mockHandler).toHaveBeenCalledWith(true, utils.keyCodes.Q);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("registerKeyHandler should add a handler for a key with Ctrl modifier", () => {
|
|
343
|
+
const mockHandler = vi.fn();
|
|
344
|
+
keyboard.registerKeyHandler(utils.keyCodes.E, mockHandler, { alt: false, ctrl: true });
|
|
345
|
+
|
|
346
|
+
const event = {
|
|
347
|
+
which: utils.keyCodes.E,
|
|
348
|
+
location: 0,
|
|
349
|
+
preventDefault: vi.fn(),
|
|
350
|
+
ctrlKey: true,
|
|
351
|
+
altKey: false,
|
|
352
|
+
shiftKey: false,
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
keyboard.setRunning(true);
|
|
356
|
+
keyboard.keyDown(event);
|
|
357
|
+
|
|
358
|
+
expect(mockHandler).toHaveBeenCalledWith(true, utils.keyCodes.E);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("sendRawKeyboardToBBC should setup the keyboard input", () => {
|
|
362
|
+
keyboard.sendRawKeyboardToBBC([utils.BBC.A], false);
|
|
363
|
+
|
|
364
|
+
expect(mockSysvia.disableKeyboard).toHaveBeenCalled();
|
|
365
|
+
expect(mockProcessor.debugInstruction.add).toHaveBeenCalled();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("postFrameShouldPause should handle single step", () => {
|
|
369
|
+
// Initially should not pause
|
|
370
|
+
expect(keyboard.postFrameShouldPause()).toBe(false);
|
|
371
|
+
|
|
372
|
+
// Set step to true
|
|
373
|
+
keyboard.stepEmuWhenPaused = true;
|
|
374
|
+
|
|
375
|
+
// Should pause and reset flag
|
|
376
|
+
expect(keyboard.postFrameShouldPause()).toBe(true);
|
|
377
|
+
|
|
378
|
+
// Flag should be reset
|
|
379
|
+
expect(keyboard.stepEmuWhenPaused).toBe(false);
|
|
380
|
+
|
|
381
|
+
// Subsequent call should not pause
|
|
382
|
+
expect(keyboard.postFrameShouldPause()).toBe(false);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("requestStep should set the step flag", () => {
|
|
386
|
+
expect(keyboard.stepEmuWhenPaused).toBe(false);
|
|
387
|
+
keyboard.requestStep();
|
|
388
|
+
expect(keyboard.stepEmuWhenPaused).toBe(true);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("pauseEmulation should set pause flag and emit pause event", async () => {
|
|
392
|
+
const eventPromise = waitForEvent("pause");
|
|
393
|
+
keyboard.pauseEmulation();
|
|
394
|
+
await eventPromise;
|
|
395
|
+
|
|
396
|
+
expect(keyboard.pauseEmu).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("resumeEmulation should clear pause flag and emit resume event", async () => {
|
|
400
|
+
keyboard.pauseEmu = true;
|
|
401
|
+
|
|
402
|
+
const eventPromise = waitForEvent("resume");
|
|
403
|
+
keyboard.resumeEmulation();
|
|
404
|
+
await eventPromise;
|
|
405
|
+
|
|
406
|
+
expect(keyboard.pauseEmu).toBe(false);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// We don't test Mac-specific code as it requires global mocking
|
|
410
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { MouseJoystickSource } from "../../src/mouse-joystick-source.js";
|
|
3
|
+
|
|
4
|
+
describe("MouseJoystickSource", () => {
|
|
5
|
+
let canvas, source, mockVia;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// Create a mock canvas element (simpler since we don't use event listeners)
|
|
9
|
+
canvas = {
|
|
10
|
+
addEventListener: vi.fn(),
|
|
11
|
+
removeEventListener: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
mockVia = {
|
|
15
|
+
setJoystickButton: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
source = new MouseJoystickSource(canvas);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.restoreAllMocks();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should initialize with center position", () => {
|
|
26
|
+
expect(source.mouseX).toBe(0.5);
|
|
27
|
+
expect(source.mouseY).toBe(0.5);
|
|
28
|
+
expect(source.isActive).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should return center value initially", () => {
|
|
32
|
+
// Mouse starts at center position (0.5, 0.5)
|
|
33
|
+
// Math.floor((1 - 0.5) * 0xffff) = Math.floor(0.5 * 0xffff) = 32767
|
|
34
|
+
expect(source.getValue(0)).toBe(32767);
|
|
35
|
+
expect(source.getValue(1)).toBe(32767);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should handle mouse position tracking via API", () => {
|
|
39
|
+
// Use the API method to set mouse position
|
|
40
|
+
source.onMouseMove(0, 0); // top-left corner
|
|
41
|
+
expect(source.isActive).toBe(true);
|
|
42
|
+
expect(source.mouseX).toBe(0);
|
|
43
|
+
expect(source.mouseY).toBe(0);
|
|
44
|
+
expect(source.getValue(0)).toBe(0xffff); // X channel (left = max)
|
|
45
|
+
expect(source.getValue(1)).toBe(0xffff); // Y channel (top = max)
|
|
46
|
+
|
|
47
|
+
// Use the API method to move to bottom-right corner
|
|
48
|
+
source.onMouseMove(1, 1); // bottom-right corner
|
|
49
|
+
expect(source.mouseX).toBe(1);
|
|
50
|
+
expect(source.mouseY).toBe(1);
|
|
51
|
+
expect(source.getValue(0)).toBe(0); // X channel (right = min)
|
|
52
|
+
expect(source.getValue(1)).toBe(0); // Y channel (bottom = min)
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should track active state via API", () => {
|
|
56
|
+
expect(source.isActive).toBe(false);
|
|
57
|
+
|
|
58
|
+
// Using the API method sets the source as active
|
|
59
|
+
source.onMouseMove(0.8, 0.3);
|
|
60
|
+
expect(source.isActive).toBe(true);
|
|
61
|
+
expect(source.mouseX).toBe(0.8);
|
|
62
|
+
expect(source.mouseY).toBe(0.3);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should handle fire button clicks via API", () => {
|
|
66
|
+
source.setVia(mockVia);
|
|
67
|
+
|
|
68
|
+
// Use API method for mouse button down
|
|
69
|
+
source.onMouseDown(0); // left button
|
|
70
|
+
expect(mockVia.setJoystickButton).toHaveBeenCalledWith(0, true);
|
|
71
|
+
|
|
72
|
+
// Use API method for mouse button up
|
|
73
|
+
source.onMouseUp(0); // left button
|
|
74
|
+
expect(mockVia.setJoystickButton).toHaveBeenCalledWith(0, false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should ignore non-left mouse buttons via API", () => {
|
|
78
|
+
source.setVia(mockVia);
|
|
79
|
+
|
|
80
|
+
// Use API method with right button (button 2)
|
|
81
|
+
source.onMouseDown(2);
|
|
82
|
+
expect(mockVia.setJoystickButton).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should return correct values for all channels", () => {
|
|
86
|
+
// Use API method to set position
|
|
87
|
+
source.onMouseMove(0.25, 0.75);
|
|
88
|
+
|
|
89
|
+
expect(source.getValue(0)).toBe(Math.floor((1 - 0.25) * 0xffff)); // X channel (inverted)
|
|
90
|
+
expect(source.getValue(1)).toBe(Math.floor((1 - 0.75) * 0xffff)); // Y channel (inverted)
|
|
91
|
+
expect(source.getValue(2)).toBe(0x8000); // Unused channel
|
|
92
|
+
expect(source.getValue(3)).toBe(0x8000); // Unused channel
|
|
93
|
+
expect(source.getValue(99)).toBe(0x8000); // Invalid channel
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should validate input range via API", () => {
|
|
97
|
+
// Test boundary clamping in API method
|
|
98
|
+
source.onMouseMove(-0.5, 1.5); // Out of bounds values
|
|
99
|
+
|
|
100
|
+
// Should be clamped to valid range [0, 1]
|
|
101
|
+
expect(source.mouseX).toBe(0);
|
|
102
|
+
expect(source.mouseY).toBe(1);
|
|
103
|
+
expect(source.getValue(0)).toBe(0xffff); // X channel (left = max)
|
|
104
|
+
expect(source.getValue(1)).toBe(0); // Y channel (bottom = min)
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should reset state on dispose", () => {
|
|
108
|
+
source.setVia(mockVia);
|
|
109
|
+
source.onMouseMove(0.8, 0.3);
|
|
110
|
+
|
|
111
|
+
source.dispose();
|
|
112
|
+
|
|
113
|
+
expect(source.via).toBe(null);
|
|
114
|
+
expect(source.isActive).toBe(false);
|
|
115
|
+
// No event listeners to remove since we don't register any
|
|
116
|
+
expect(canvas.removeEventListener).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should report enabled state correctly", () => {
|
|
120
|
+
expect(source.isEnabled()).toBe(false);
|
|
121
|
+
|
|
122
|
+
source.setVia(mockVia);
|
|
123
|
+
expect(source.isEnabled()).toBe(true);
|
|
124
|
+
|
|
125
|
+
source.setVia(null);
|
|
126
|
+
expect(source.isEnabled()).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
});
|