mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
* Update jest to v29.5.0 This updates jest to the latest version (29.5.0) and fixes everything that the upgrade breaks. One of the biggest differences is that the mock types changed and I'm once again confused as to the "proper" way to create mocks in jest. Whatever. * Fix issue #187 were bank writing was not working correctly Before, `mmu.ts` would deactivate writing to the language card if `prewrite` was reset. However, it is totally possible to reset `prewrite` and leave writing enabled, as shown my Sather in _Understanding the Apple IIe_, table 5.5, p. 5-24. For example: ```assembly_x86 sta $c08a ; WRITE DISABLE; READ DISABLE lda $c08b ; PRE-WRITE set lda $c08b ; WRITE enabled sta $c08a ; PRE-WRITE reset; WRITE still enabled! lda $c08b ; PRE-WRITE set; WRITE still enabled! ``` would not work correctly because the last line would clear `_writebsr` before setting `_prewrite`, which is incorrect. Now, `_writebsr` is only set when `_prewrite` is set and thus only cleared when `writeSwitch` is false. This matches Table 5.5. * Fix pre-write for the language card This is the same issue as the `MMU`, namely that `langcard.ts` would deactivate writing to the language card if `prewrite` was reset. However, it is totally possible to reset `prewrite` and leave writing enabled, as shown my Sather in _Understanding the Apple II_, table 5.4, p. 5-30. See the previous commit for an example. This change also adds a test for the `LanguageCard` class.
This commit is contained in:
parent
c52f03a7e1
commit
6923d43873
@ -4,6 +4,10 @@ module.exports = {
|
||||
'^test/(.*)': '<rootDir>/test/$1',
|
||||
'\\.css$': 'identity-obj-proxy',
|
||||
'\\.scss$': 'identity-obj-proxy',
|
||||
// For some reason the preact modules are not where they are
|
||||
// expected. This seems to have something to do with jest > v27.
|
||||
// https://github.com/preactjs/enzyme-adapter-preact-pure/issues/179#issuecomment-1201096897
|
||||
'^preact(/(.*)|$)': 'preact$1',
|
||||
},
|
||||
'roots': [
|
||||
'js/',
|
||||
@ -17,6 +21,9 @@ module.exports = {
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
'^.*\\.tsx$': 'ts-jest',
|
||||
},
|
||||
'transformIgnorePatterns': [
|
||||
'/node_modules/(?!(@testing-library/preact/dist/esm)/)',
|
||||
],
|
||||
'setupFilesAfterEnv': [
|
||||
'<rootDir>/test/jest-setup.ts'
|
||||
],
|
||||
@ -24,5 +31,6 @@ module.exports = {
|
||||
'/node_modules/',
|
||||
'/js/roms/',
|
||||
'/test/',
|
||||
]
|
||||
],
|
||||
'preset': 'ts-jest',
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import RAM, { RAMState } from '../ram';
|
||||
import { debug } from '../util';
|
||||
// import { debug } from '../util';
|
||||
import { Card, Memory, byte, Restorable } from '../types';
|
||||
|
||||
export interface LanguageCardState {
|
||||
@ -30,7 +30,7 @@ export default class LanguageCard implements Card, Restorable<LanguageCardState>
|
||||
private write2: Memory;
|
||||
|
||||
constructor(private rom: Memory) {
|
||||
debug('Language card');
|
||||
// debug('Language card');
|
||||
|
||||
this.bank1 = new RAM(0xd0, 0xdf);
|
||||
this.bank2 = new RAM(0xd0, 0xdf);
|
||||
@ -88,9 +88,11 @@ export default class LanguageCard implements Card, Restorable<LanguageCardState>
|
||||
let bankStr;
|
||||
let rwStr;
|
||||
|
||||
if (writeSwitch) { // $C081, $C083, $C089, $C08B
|
||||
if (writeSwitch) { // 0xC081, 0xC083
|
||||
if (readMode) {
|
||||
this._writebsr = this._prewrite;
|
||||
if (this._prewrite) {
|
||||
this._writebsr = true;
|
||||
}
|
||||
}
|
||||
this._prewrite = readMode;
|
||||
|
||||
|
@ -684,7 +684,9 @@ export default class MMU implements Memory, Restorable<MMUState> {
|
||||
|
||||
if (writeSwitch) { // 0xC081, 0xC083
|
||||
if (readMode) {
|
||||
this._writebsr = this._prewrite;
|
||||
if (this._prewrite) {
|
||||
this._writebsr = true;
|
||||
}
|
||||
}
|
||||
this._prewrite = readMode;
|
||||
|
||||
|
7651
package-lock.json
generated
7651
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -32,14 +32,14 @@
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/preact": "^3.0.1",
|
||||
"@testing-library/user-event": "^13.1.3",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/jest-image-snapshot": "^4.3.1",
|
||||
"@types/micromodal": "^0.3.2",
|
||||
"@types/wicg-file-system-access": "^2020.9.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"ajv": "^6.12.0",
|
||||
"babel-jest": "^27.2.4",
|
||||
"babel-jest": "^29.5.0",
|
||||
"canvas": "^2.8.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.17.0",
|
||||
@ -48,7 +48,8 @@
|
||||
"eslint-plugin-react-hooks": "^4.5.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^27.2.4",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jest-image-snapshot": "^4.5.1",
|
||||
"node-forge": "^1.3.0",
|
||||
"raw-loader": "^4.0.0",
|
||||
@ -61,7 +62,7 @@
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"stylelint-config-standard-scss": "^9.0.0",
|
||||
"stylelint-scss": "^4.6.0",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.3.0",
|
||||
"typescript": "^4.7.3",
|
||||
"webpack": "^5.76.0",
|
||||
|
2
test/env/jsdom-with-backdoors.js
vendored
2
test/env/jsdom-with-backdoors.js
vendored
@ -9,7 +9,7 @@
|
||||
* setup. It still requires typing.
|
||||
*/
|
||||
|
||||
const JsdomEnvironment = require('jest-environment-jsdom');
|
||||
import JsdomEnvironment from 'jest-environment-jsdom';
|
||||
|
||||
export default class JsdomEnvironmentWithBackDoors extends JsdomEnvironment {
|
||||
async setup() {
|
||||
|
@ -8,7 +8,6 @@ import { DriveNumber, NibbleDisk, WozDisk } from 'js/formats/types';
|
||||
import { byte } from 'js/types';
|
||||
import { toHex } from 'js/util';
|
||||
import { VideoModes } from 'js/videomodes';
|
||||
import { mocked } from 'ts-jest/utils';
|
||||
import { BYTES_BY_SECTOR_IMAGE, BYTES_BY_TRACK_IMAGE } from '../formats/testdata/16sector';
|
||||
|
||||
jest.mock('js/apple2io');
|
||||
@ -34,7 +33,7 @@ function setWriteProtected(diskII: DiskII, isWriteProtected: boolean) {
|
||||
|
||||
describe('DiskII', () => {
|
||||
const mockApple2IO = new Apple2IO({} as unknown as CPU6502, {} as unknown as VideoModes);
|
||||
const callbacks: Callbacks = {
|
||||
const callbacks: jest.Mocked<Callbacks> = {
|
||||
driveLight: jest.fn(),
|
||||
dirty: jest.fn(),
|
||||
label: jest.fn(),
|
||||
@ -66,7 +65,7 @@ describe('DiskII', () => {
|
||||
|
||||
const state = diskII.getState();
|
||||
// These are just arbitrary changes, not an exhaustive list of fields.
|
||||
(state.drives[1].driver as {skip:number}).skip = 1;
|
||||
(state.drives[1].driver as { skip: number }).skip = 1;
|
||||
state.controllerState.driveNo = 2;
|
||||
state.controllerState.latch = 0x42;
|
||||
state.controllerState.on = true;
|
||||
@ -115,7 +114,7 @@ describe('DiskII', () => {
|
||||
jest.useFakeTimers();
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
mocked(callbacks.driveLight).mockReset();
|
||||
callbacks.driveLight.mockReset();
|
||||
|
||||
diskII.ioSwitch(0x88); // turn off the motor
|
||||
|
||||
@ -140,7 +139,7 @@ describe('DiskII', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.ioSwitch(0x8B); // select drive 2
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
mocked(callbacks.driveLight).mockReset();
|
||||
callbacks.driveLight.mockReset();
|
||||
|
||||
diskII.ioSwitch(0x88); // turn off the motor
|
||||
|
||||
@ -571,7 +570,7 @@ describe('DiskII', () => {
|
||||
|
||||
it('spins the disk when motor is on', () => {
|
||||
let cycles: number = 0;
|
||||
mocked(mockApple2IO).cycles.mockImplementation(() => cycles);
|
||||
(mockApple2IO.cycles as jest.Mock).mockImplementation(() => cycles);
|
||||
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'DOS 3.3 System Master', 'woz', DOS33_SYSTEM_MASTER_IMAGE);
|
||||
@ -589,7 +588,7 @@ describe('DiskII', () => {
|
||||
|
||||
it('does not spin the disk when motor is off', () => {
|
||||
let cycles: number = 0;
|
||||
mocked(mockApple2IO).cycles.mockImplementation(() => cycles);
|
||||
(mockApple2IO.cycles as jest.Mock).mockImplementation(() => cycles);
|
||||
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'DOS 3.3 System Master', 'woz', DOS33_SYSTEM_MASTER_IMAGE);
|
||||
@ -606,7 +605,7 @@ describe('DiskII', () => {
|
||||
|
||||
it('reads an FF sync byte from the beginning of the image', () => {
|
||||
let cycles: number = 0;
|
||||
mocked(mockApple2IO).cycles.mockImplementation(() => cycles);
|
||||
(mockApple2IO.cycles as jest.Mock).mockImplementation(() => cycles);
|
||||
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'DOS 3.3 System Master', 'woz', DOS33_SYSTEM_MASTER_IMAGE);
|
||||
@ -632,7 +631,7 @@ describe('DiskII', () => {
|
||||
|
||||
it('reads several FF sync bytes', () => {
|
||||
let cycles: number = 0;
|
||||
mocked(mockApple2IO).cycles.mockImplementation(() => cycles);
|
||||
(mockApple2IO.cycles as jest.Mock).mockImplementation(() => cycles);
|
||||
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'DOS 3.3 System Master', 'woz', DOS33_SYSTEM_MASTER_IMAGE);
|
||||
@ -665,7 +664,7 @@ describe('DiskII', () => {
|
||||
|
||||
it('reads random garbage on uninitialized tracks', () => {
|
||||
let cycles: number = 0;
|
||||
mocked(mockApple2IO).cycles.mockImplementation(() => cycles);
|
||||
(mockApple2IO.cycles as jest.Mock).mockImplementation(() => cycles);
|
||||
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'DOS 3.3 System Master', 'woz', DOS33_SYSTEM_MASTER_IMAGE);
|
||||
@ -761,7 +760,7 @@ class TestDiskReader {
|
||||
diskII: DiskII;
|
||||
|
||||
constructor(driveNo: DriveNumber, label: string, image: ArrayBufferLike, apple2IO: Apple2IO, callbacks: Callbacks) {
|
||||
mocked(apple2IO).cycles.mockImplementation(() => this.cycles);
|
||||
(apple2IO.cycles as jest.Mock).mockImplementation(() => this.cycles);
|
||||
|
||||
this.diskII = new DiskII(apple2IO, callbacks);
|
||||
this.diskII.setBinary(driveNo, label, 'woz', image);
|
||||
|
51
test/js/cards/langcard.spec.ts
Normal file
51
test/js/cards/langcard.spec.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import LanguageCard from '../../../js/cards/langcard';
|
||||
import Apple2ROM from '../../../js/roms/system/fpbasic';
|
||||
|
||||
|
||||
describe('Language Card', () => {
|
||||
|
||||
it('is constructable', () => {
|
||||
const langCard = new LanguageCard(new Apple2ROM());
|
||||
expect(langCard).not.toBeNull();
|
||||
});
|
||||
|
||||
it('requires prewrite to write to bank1', () => {
|
||||
const langCard = new LanguageCard(new Apple2ROM());
|
||||
|
||||
// From https://github.com/whscullin/apple2js/issues/187
|
||||
// Action descriptions from Sather, Table 5.5, p. 5-24, UtAIIe:
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write enabled)
|
||||
langCard.ioSwitch(0x89, 0x00); // WRTCOUNT = 0, READ DISABLE (write still enabled)
|
||||
langCard.ioSwitch(0x89); // WRTCOUNT = WRITCOUNT + 1, READ DISABLE (write still enabled)
|
||||
langCard.write(0xd0, 0x00, 0xa1);
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write still enabled)
|
||||
expect(langCard.read(0xd0, 0x00)).toBe(0xa1);
|
||||
});
|
||||
|
||||
it('prewrite is reset on write access before write', () => {
|
||||
const langCard = new LanguageCard(new Apple2ROM());
|
||||
|
||||
// Action descriptions from Sather, Table 5.5, p. 5-24, UtAIIe:
|
||||
langCard.ioSwitch(0x89, 0x00); // WRTCOUNT = 0, READ DISABLE
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write not enabled yet)
|
||||
langCard.ioSwitch(0x8b, 0x00); // WRTCOUNT = 0, READ ENABLE (write still not enabled)
|
||||
const oldValue = langCard.read(0xd0, 0x00);
|
||||
langCard.write(0xd0, 0x00, 0xa1); // writes to the void
|
||||
expect(langCard.read(0xd0, 0x00)).toBe(oldValue); // reads old value
|
||||
});
|
||||
|
||||
|
||||
it('write stays active with overzealous switching', () => {
|
||||
const langCard = new LanguageCard(new Apple2ROM());
|
||||
|
||||
// Action descriptions from Sather, Table 5.5, p. 5-24, UtAIIe:
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write enabled)
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write enabled)
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write enabled)
|
||||
langCard.write(0xd0, 0x00, 0xa1);
|
||||
langCard.ioSwitch(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write still enabled)
|
||||
expect(langCard.read(0xd0, 0x00)).toBe(0xa1);
|
||||
});
|
||||
});
|
90
test/js/mmu.test.ts
Normal file
90
test/js/mmu.test.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import Apple2IO from 'js/apple2io';
|
||||
import MMU from '../../js/mmu';
|
||||
import CPU6502 from 'js/cpu6502';
|
||||
import { HiresPage, LoresPage, VideoModes, VideoPage } from 'js/videomodes';
|
||||
import Apple2eROM from '../../js/roms/system/apple2e';
|
||||
import { MemoryPages } from 'js/types';
|
||||
|
||||
function newFakeMemoryPages() {
|
||||
return {} as unknown as MemoryPages;
|
||||
}
|
||||
|
||||
function newFakeVideoPage(): VideoPage {
|
||||
const bank0Pages = newFakeMemoryPages();
|
||||
const bank1Pages = newFakeMemoryPages();
|
||||
return {
|
||||
bank0() {
|
||||
return bank0Pages;
|
||||
},
|
||||
bank1() {
|
||||
return bank1Pages;
|
||||
},
|
||||
} as unknown as VideoPage;
|
||||
}
|
||||
|
||||
function newFakeLoresPage(): LoresPage {
|
||||
return newFakeVideoPage() as unknown as LoresPage;
|
||||
}
|
||||
|
||||
function newFakeHiresPage(): HiresPage {
|
||||
return newFakeVideoPage() as unknown as HiresPage;
|
||||
}
|
||||
|
||||
describe('MMU', () => {
|
||||
const fakeVideoModes = {} as unknown as VideoModes;
|
||||
const fakeCPU = {} as unknown as CPU6502;
|
||||
const fakeLoResPage1 = newFakeLoresPage();
|
||||
const fakeLoResPage2 = newFakeLoresPage();
|
||||
const fakeHiResPage1 = newFakeHiresPage();
|
||||
const fakeHiResPage2 = newFakeHiresPage();
|
||||
const fakeApple2IO = {} as unknown as Apple2IO;
|
||||
|
||||
it('is constructable', () => {
|
||||
const mmu = new MMU(fakeCPU, fakeVideoModes, fakeLoResPage1, fakeLoResPage2,
|
||||
fakeHiResPage1, fakeHiResPage2, fakeApple2IO, new Apple2eROM());
|
||||
expect(mmu).not.toBeNull();
|
||||
});
|
||||
|
||||
it('requires prewrite to write to bank1', () => {
|
||||
const mmu = new MMU(fakeCPU, fakeVideoModes, fakeLoResPage1, fakeLoResPage2,
|
||||
fakeHiResPage1, fakeHiResPage2, fakeApple2IO, new Apple2eROM());
|
||||
|
||||
// From https://github.com/whscullin/apple2js/issues/187
|
||||
// Action descriptions from Sather, Table 5.5, p. 5-24, UtAIIe:
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write enabled)
|
||||
mmu._access(0x89, 0x00); // WRTCOUNT = 0, READ DISABLE (write still enabled)
|
||||
mmu._access(0x89); // WRTCOUNT = WRITCOUNT + 1, READ DISABLE (write still enabled)
|
||||
mmu.write(0xd0, 0x00, 0xa1);
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write still enabled)
|
||||
expect(mmu.read(0xd0, 0x00)).toBe(0xa1);
|
||||
});
|
||||
|
||||
it('prewrite is reset on write access before write', () => {
|
||||
const mmu = new MMU(fakeCPU, fakeVideoModes, fakeLoResPage1, fakeLoResPage2,
|
||||
fakeHiResPage1, fakeHiResPage2, fakeApple2IO, new Apple2eROM());
|
||||
|
||||
// Action descriptions from Sather, Table 5.5, p. 5-24, UtAIIe:
|
||||
mmu._access(0x89, 0x00); // WRTCOUNT = 0, READ DISABLE
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write not enabled yet)
|
||||
mmu._access(0x8b, 0x00); // WRTCOUNT = 0, READ ENABLE (write still not enabled)
|
||||
const oldValue = mmu.read(0xd0, 0x00);
|
||||
mmu.write(0xd0, 0x00, 0xa1); // writes to the void
|
||||
expect(mmu.read(0xd0, 0x00)).toBe(oldValue); // reads old value
|
||||
});
|
||||
|
||||
|
||||
it('write stays active with overzealous switching', () => {
|
||||
const mmu = new MMU(fakeCPU, fakeVideoModes, fakeLoResPage1, fakeLoResPage2,
|
||||
fakeHiResPage1, fakeHiResPage2, fakeApple2IO, new Apple2eROM());
|
||||
|
||||
// Action descriptions from Sather, Table 5.5, p. 5-24, UtAIIe:
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write enabled)
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write enabled)
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write enabled)
|
||||
mmu.write(0xd0, 0x00, 0xa1);
|
||||
mmu._access(0x8b); // WRTCOUNT = WRTCOUNT + 1, READ ENABLE (write still enabled)
|
||||
expect(mmu.read(0xd0, 0x00)).toBe(0xa1);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user