'use strict'; const tapahtumaTyypit = { lisääAste: 'lisääAste', poistaAste: 'poistaAste', muutaAste: 'muutaAste', lisääLuokka: 'lisääLuokka', poistaLuokka: 'poistaLuokka', }; class Tapahtuma { constructor(tyyppi, argumentit) { this.tyyppi = tyyppi; this.argumentit = argumentit; } } let historia, tulevaisuus; let luokkaAsteet; alustaMalli(); function alustaMalli() { historia = []; tulevaisuus = []; luokkaAsteet = new LuokkaAsteet(); } function suorita(tyyppi, ...argumentit) { let paluuarvo = undefined; switch (tyyppi) { case tapahtumaTyypit.lisääAste: assertRange('lisääAste argumentit määrä', argumentit.length, 0, 1); paluuarvo = luokkaAsteet.lisää(...argumentit) break; case tapahtumaTyypit.poistaAste: assertEq('poistaAste argumentit määrä', argumentit.length, 1); paluuarvo = luokkaAsteet.poista(...argumentit) break; case tapahtumaTyypit.muutaAste: assertEq('muutaAste argumentit määrä', argumentit.length, 2); luokkaAsteet.muuta(...argumentit) break; case tapahtumaTyypit.lisääLuokka: assertEq('lisääLuokka argumentit määrä', argumentit.length, 1); luokkaAsteet.asteet[argumentit[0]].lisää(); break; case tapahtumaTyypit.poistaLuokka: assertEq('poistaLuokka argumentit määrä', argumentit.length, 1); luokkaAsteet.asteet[argumentit[0]].poista(); break; default: throw new Error(`tuntematon tapahtumatyyppi ${tyyppi}`); } historia.push(new Tapahtuma(tyyppi, argumentit)); tulevaisuus = []; return paluuarvo; } function kumoa() { if (historia.length === 0) { return; } // Kumoaminen tapahtuu ottamalla historia uusinta tapahtumaa lukuun // ottamatta ja suorittamalla se siihen asti uudestaan tyhjältä mallilta let kumottu = historia.pop(); let uusi_tulevaisuus = tulevaisuus.concat(kumottu); let vanha_historia = historia; alustaMalli(); for (let {tyyppi, argumentit} of vanha_historia) { suorita(tyyppi, ...argumentit); } tulevaisuus = uusi_tulevaisuus; } function teeUudelleen() { if (tulevaisuus.length === 0) { return; } let {tyyppi, argumentit} = tulevaisuus.pop(); // Tulevaisuus tulee tallentaa, sillä suorita() tuhoaa sen let uusi_tulevaisuus = tulevaisuus; suorita(tyyppi, ...argumentit); tulevaisuus = uusi_tulevaisuus; } testi('mallin alustaminen', () => { historia = undefined; tulevaisuus = undefined; luokkaAsteet = undefined; alustaMalli(); assertNe('historia', historia, undefined); assertNe('tulevaisuus', tulevaisuus, undefined); assertNe('luokkaAsteet', luokkaAsteet, undefined); }); testi('tapahtumahistoria', () => { alustaMalli(); suorita(tapahtumaTyypit.lisääAste); suorita(tapahtumaTyypit.poistaAste, 1); assertEq('historia.length', historia.length, 2); assertEq('historia[0].tyyppi', historia[0].tyyppi, tapahtumaTyypit.lisääAste); assertEq('historia[0].argumentit.length', historia[0].argumentit.length, 0); assertEq('historia[1].tyyppi', historia[1].tyyppi, tapahtumaTyypit.poistaAste); assertEq('historia[1].argumentit.length', historia[1].argumentit.length, 1); assertEq('historia[1].argumentit[0]', historia[1].argumentit[0], 1); assertThrow('poistaAste 1', 'luokka-astetta 1 ei ole olemassa', () => { suorita(tapahtumaTyypit.poistaAste, 1); }); assertEq('historia.length poikkeuksen jälkeen', historia.length, 2); alustaMalli(); }); testi('kumoamiminen', () => { alustaMalli(); kumoa(); suorita(tapahtumaTyypit.lisääAste); suorita(tapahtumaTyypit.lisääLuokka, 1); suorita(tapahtumaTyypit.lisääAste); suorita(tapahtumaTyypit.lisääLuokka, 2); suorita(tapahtumaTyypit.lisääLuokka, 1); assertEq('aluksi 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'ABC'); assertEq('aluksi 2. aste', luokkaAsteet.asteet[2].luokat().join(''), 'AB'); kumoa(); assertEq('kerran 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'AB'); assertEq('kerran 2. aste', luokkaAsteet.asteet[2].luokat().join(''), 'AB'); kumoa(); assertEq('kahdesti 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'AB'); assertEq('kahdesti 2. aste', luokkaAsteet.asteet[2].luokat().join(''), 'A'); kumoa(); assertEq('kolmesti 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'AB'); assertEq('kolmesti 2. aste', luokkaAsteet.asteet[2], undefined); kumoa(); assertEq('neljästi 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'A'); kumoa(); assertEq('viidesti 1. aste', luokkaAsteet.asteet[1], undefined); alustaMalli(); }); testi('uudelleen tekeminen', () => { alustaMalli(); teeUudelleen(); suorita(tapahtumaTyypit.lisääAste); suorita(tapahtumaTyypit.lisääAste); suorita(tapahtumaTyypit.lisääLuokka, 1); suorita(tapahtumaTyypit.lisääLuokka, 2); suorita(tapahtumaTyypit.lisääLuokka, 2); kumoa(); kumoa(); kumoa(); assertEq('aluksi 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'A'); assertEq('aluksi 2. aste', luokkaAsteet.asteet[2].luokat().join(''), 'A'); teeUudelleen(); assertEq('kerran 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'AB'); assertEq('kerran 2. aste', luokkaAsteet.asteet[2].luokat().join(''), 'A'); teeUudelleen(); assertEq('kahdesti 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'AB'); assertEq('kahdesti 2. aste', luokkaAsteet.asteet[2].luokat().join(''), 'AB'); suorita(tapahtumaTyypit.lisääLuokka, 1); teeUudelleen(); assertEq('kolmesti 1. aste', luokkaAsteet.asteet[1].luokat().join(''), 'ABC'); assertEq('kolmesti 2. aste', luokkaAsteet.asteet[2].luokat().join(''), 'AB'); alustaMalli(); }); testi('asteiden käsittely', () => { alustaMalli(); assertEq('lisää', suorita(tapahtumaTyypit.lisääAste), 1); suorita(tapahtumaTyypit.muutaAste, 1, 2); assertEq('muutettua seuraava aste', luokkaAsteet.seuraavaAste(), 3); suorita(tapahtumaTyypit.poistaAste, 2); assertEq('lopuksi seuraava aste', luokkaAsteet.seuraavaAste(), 1); alustaMalli(); }); testi('luokkien käsittely', () => { alustaMalli(); suorita(tapahtumaTyypit.lisääAste); assertEq('aluksi', luokkaAsteet.asteet[1].luokat().join(''), 'A'); suorita(tapahtumaTyypit.lisääLuokka, 1); assertEq('lisättyä', luokkaAsteet.asteet[1].luokat().join(''), 'AB'); suorita(tapahtumaTyypit.poistaLuokka, 1); assertEq('poistettua', luokkaAsteet.asteet[1].luokat().join(''), 'A'); alustaMalli(); });