Aloita uudestaan

This commit is contained in:
Juhani Krekelä 2023-08-02 20:12:15 +03:00
parent fa1246b318
commit 9bcfb1cf40
5 changed files with 539 additions and 411 deletions

108
käyttöliittymä.js Normal file
View File

@ -0,0 +1,108 @@
'use strict';
document.getElementById('kumoa').addEventListener('click', () => {
kumoa();
piirräKaikki();
});
document.getElementById('tee-uudelleen').addEventListener('click', () => {
teeUudelleen();
piirräKaikki();
});
document.getElementById('lisää-luokka-aste').addEventListener('click', () => {
suorita(tapahtumaTyypit.lisääAste);
piirräLuokat();
});
function piirräKaikki() {
piirräLuokat();
}
function piirräLuokat() {
let vanhaLuokkaAsteLista = document.getElementById('luokka-asteet');
let luokkaAsteLista = luoLuokkaAsteLista();
vanhaLuokkaAsteLista.parentNode.replaceChild(
luokkaAsteLista,
vanhaLuokkaAsteLista
);
}
function luoLuokkaAsteLista() {
let luokkaAsteLista = document.createElement('ul');
luokkaAsteLista.id = 'luokka-asteet';
for (let aste = 0; aste < luokkaAsteet.asteet.length; aste++) {
if (luokkaAsteet.asteet[aste] !== undefined) {
let luokkaAsteListassa = luoLuokkaAsteListassa(aste);
luokkaAsteLista.appendChild(luokkaAsteListassa);
}
}
return luokkaAsteLista;
}
function luoLuokkaAsteListassa(aste) {
let luokkaAsteListassa = document.createElement('li');
luokkaAsteListassa.classList.add('luokka-aste');
luokkaAsteListassa.appendChild(luoPainike('+', () => {
suorita(tapahtumaTyypit.lisääLuokka, aste);
piirräLuokat();
}));
luokkaAsteListassa.appendChild(luoPainike('-', () => {
if (luokkaAsteet.asteet[aste].luokat().length > 1) {
suorita(tapahtumaTyypit.poistaLuokka, aste);
} else {
suorita(tapahtumaTyypit.poistaAste, aste);
}
piirräLuokat();
}));
luokkaAsteListassa.appendChild(luoLuokkaLista(aste));
luokkaAsteListassa.appendChild(luoPainike('✎', () => {
// TODO: Älä käytä prompt():ia ja alert():ia
let vastaus = prompt('Uusi luokka-aste');
if (vastaus === null) {
// Käytääjä painoi 'Peruuta'
return;
}
vastaus = parseInt(vastaus, 10);
if (vastaus === NaN) {
// Ei numero
alert('Luokka-asteen pitää olla numero');
return;
}
if (vastaus < 1) {
// Luokka-aste alkaa 1:stä
alert('Luokka-asteeen pitää olla vähintään 1');
return;
}
suorita(tapahtumaTyypit.muutaAste, aste, vastaus);
piirräLuokat();
}));
return luokkaAsteListassa;
}
function luoLuokkaLista(aste) {
let luokkaLista = document.createElement('ul');
luokkaLista.classList.add('luokat');
for (let luokka of luokkaAsteet.asteet[aste].luokat()) {
let luokkaListassa = document.createElement('li');
luokkaListassa.appendChild(document.createTextNode(`${aste}${luokka}`));
luokkaLista.appendChild(luokkaListassa);
}
return luokkaLista;
}
function luoPainike(teksti, funktio) {
let painike = document.createElement('input');
painike.type = 'button';
painike.value = teksti;
painike.addEventListener('click', funktio);
return painike;
}
// TOOD: Käyttöliittymätestit

View File

@ -4,427 +4,35 @@
<meta charset="utf-8">
<title>Lukujärjestäjä 0.1</title>
<style>
.pane {
.testit-epäonnistunut {
background-color: #e77;
}
.items-list {
#luokka-asteet {
list-style-type: none;
}
.list-element-text {
padding-left: 0.2em;
.luokat {
display: inline;
padding: 0;
}
#schedule-table td {
min-width: 10ch;
text-align: center;
border: 1px solid;
}
#unscheduled-lessons-box {
border: 1px solid;
min-height: 1;
.luokat > li {
margin: 0 0.5em;
display: inline;
}
</style>
</head>
<body lang="fi">
<details class="pane" id="classes" open>
<input id="kumoa" type="button" value="Kumoa">
<input id="tee-uudelleen" type="button" value="Tee uudelleen">
<details class="ruutu" id="luokat" open>
<summary>Luokat</summary>
<ul class="items-list" id="class-list"></ul>
<form id="add-class">
<input id="class-name" type="text">
<input type="submit" value="+">
</form>
<input id="lisää-luokka-aste" type="button" value="+ Aste">
<ul id="luokka-asteet"></ul>
</details>
<details class="pane" id="teachers">
<summary>Opettajat</summary>
<ul class="items-list" id="teacher-list"></ul>
<form id="add-teacher">
<input id="teacher-name" type="text">
<input type="submit" value="+">
</form>
</details>
<details class="pane" id="lessons">
<summary>Tunnit</summary>
<ul class="items-list" id="lesson-list"></ul>
<form id="add-lesson">
<label>Tunti
<input id="lesson-name" type="text">
</label>
<label>Luokka
<select id="lesson-class"></select>
</label>
<label>Opettaja
<select id="lesson-teacher"></select>
</label>
<input type="submit" value="+">
</form>
</details>
<details class="pane" id="schedules">
<summary>Lukujärjestykset</summary>
<select id="schedule-select">
<optgroup id="schedule-select-classes" label="Luokat"></optgroup>
<optgroup id="schedule-select-teachers" label="Opettajat"></optgroup>
</select>
<table id="schedule-table"></table>
<div class="dropzone" id="unscheduled-lessons-box">
<ul id="unscheduled-lessons"></ul>
</div>
</details>
<script>
'use strict';
const days = ['ma', 'ti', 'ke', 'to', 'pe'];
const hours = [8, 9, 10, 11, 12, 13, 14, 15];
let classes = [];
let teachers = [];
let lessons = [];
listClasses();
listTeachers();
listLessons();
function replacementElement(id) {
let oldElement = document.getElementById(id);
let newElement = document.createElement(oldElement.nodeName);
newElement.id = oldElement.id;
newElement.classList = oldElement.classList;
newElement.label = oldElement.label;
return newElement;
}
function replaceElement(id, newElement) {
let oldElement = document.getElementById(id);
oldElement.parentElement.replaceChild(newElement, oldElement);
}
function newDeleteButton(deleteFunction) {
let deleteButton = document.createElement('input');
deleteButton.type = 'button';
deleteButton.value = '-';
deleteButton.onclick = deleteFunction;
return deleteButton;
}
function newListElementText(text) {
let element = document.createElement('span');
element.className = 'list-element-text';
element.innerText = text;
return element
}
function newOption(text) {
let option = document.createElement('option');
option.value = text;
option.innerText = text;
return option;
}
document.getElementById('add-class').onsubmit = addClass;
function addClass(event) {
event.preventDefault();
let nameField = document.getElementById('class-name');
let name = nameField.value;
if (name === '') {
return;
}
if (classes.includes(name)) {
alert(`Luokka ${name} on jo listassa`);
return;
}
nameField.value = '';
classes.push(name);
classes.sort();
listClasses();
}
function deleteClass(name) {
classes = classes.filter((i) => i !== name);
listClasses();
}
function listClasses() {
let classList = replacementElement('class-list');
let classSelect = replacementElement('lesson-class');
classSelect.appendChild(document.createElement('option'));
let scheduleClassSelect = replacementElement('schedule-select-classes');
for (let name of classes) {
let listElement = document.createElement('li');
listElement.appendChild(newDeleteButton(function() { deleteClass(name); }));
listElement.appendChild(newListElementText(name));
classList.appendChild(listElement);
classSelect.appendChild(newOption(name));
scheduleClassSelect.appendChild(newOption(name));
}
replaceElement('class-list', classList);
replaceElement('lesson-class', classSelect);
replaceElement('schedule-select-classes', scheduleClassSelect);
}
document.getElementById('add-teacher').onsubmit = addTeacher;
function addTeacher(event) {
event.preventDefault();
let nameField = document.getElementById('teacher-name');
let name = nameField.value;
if (name === '') {
return;
}
if (teachers.includes(name)) {
alert(`Opettaja ${name} on jo listassa`);
return;
}
nameField.value = '';
teachers.push(name);
teachers.sort();
listTeachers();
}
function deleteTeacher(name) {
teachers = teachers.filter((i) => i !== name);
listTeachers();
}
function listTeachers() {
let teacherList = replacementElement('teacher-list');
let teacherSelect = replacementElement('lesson-teacher');
teacherSelect.appendChild(document.createElement('option'));
let scheduleTeacherSelect = replacementElement('schedule-select-teachers');
for (let name of teachers) {
let listElement = document.createElement('li');
listElement.appendChild(newDeleteButton(function() { deleteTeacher(name); }));
listElement.appendChild(newListElementText(name));
teacherList.appendChild(listElement);
teacherSelect.appendChild(newOption(name));
scheduleTeacherSelect.appendChild(newOption(name));
}
replaceElement('teacher-list', teacherList);
replaceElement('lesson-teacher', teacherSelect);
replaceElement('schedule-select-teachers', scheduleTeacherSelect);
}
document.getElementById('add-lesson').onsubmit = addLesson;
function addLesson(event) {
event.preventDefault();
let nameField = document.getElementById('lesson-name');
let name = nameField.value;
let classField = document.getElementById('lesson-class');
let className = classField.value;
let teacherField = document.getElementById('lesson-teacher');
let teacherName = teacherField.value;
if (name === '' && className === '' && teacherName === '') {
return;
}
if (className === '' && teacherName === '') {
alert('Tunti tarvitsee joko luokan, opettajan tai molemmat');
return;
}
nameField.value = '';
classField.value = '';
teacherField.value = '';
lessons.push({name: name, class: className, teacher: teacherName});
lessons.sort(function(a, b) {
if (a.class < b.class) {
return -1;
} else if (a.class > b.class) {
return 1;
} else if (a.teacher < b.teacher) {
return -1;
} else if (a.teacher > b.teacher) {
return 1;
} else if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
listLessons();
}
function deleteLesson(key) {
lessons = lessons.filter((_, i) => i !== key);
listLessons();
}
function listLessons() {
let lessonList = replacementElement('lesson-list');
for (let key of lessons.keys()) {
let lesson = lessons[key];
let listElement = document.createElement('li');
listElement.appendChild(newDeleteButton(function() { deleteLesson(key); }));
listElement.appendChild(newListElementText(lesson.name));
listElement.appendChild(newListElementText(lesson.class));
listElement.appendChild(newListElementText(lesson.teacher));
lessonList.appendChild(listElement);
}
replaceElement('lesson-list', lessonList);
showSchedule();
}
document.getElementById('schedule-select').onchange = showSchedule;
function showSchedule() {
let filter = document.getElementById('schedule-select').value;
let scheduleLessons = [];
for (let key of lessons.keys()) {
let lesson = lessons[key];
if (lesson.class === filter || lesson.teacher === filter) {
scheduleLessons.push({key: key, lesson: lesson});
}
}
let schedule = {};
for (let day of days) {
let daySchedule = {}
for (let hour of hours) {
daySchedule[hour] = null;
}
schedule[day] = daySchedule;
}
let unscheduledLessons = replacementElement('unscheduled-lessons');
for (let lesson of scheduleLessons) {
if (lesson.lesson.at) {
schedule[lesson.lesson.at.day][lesson.lesson.at.hour] = lesson;
} else {
let listElement = document.createElement('li');
listElement.id = `lesson-${lesson.key}`;
listElement.draggable = true;
listElement.appendChild(newListElementText(lesson.lesson.name));
if (lesson.lesson.class !== filter) {
listElement.appendChild(newListElementText(lesson.lesson.class));
}
if (lesson.lesson.teacher !== filter) {
listElement.appendChild(newListElementText(lesson.lesson.teacher));
}
unscheduledLessons.appendChild(listElement);
}
}
replaceElement('unscheduled-lessons', unscheduledLessons);
let scheduleTable = replacementElement('schedule-table');
let tableHeader = document.createElement('tr');
tableHeader.appendChild(document.createElement('th'));
for (let day of days) {
let header = document.createElement('th');
header.innerText = day;
tableHeader.appendChild(header);
}
scheduleTable.appendChild(tableHeader);
for (let hour of hours) {
let row = document.createElement('tr');
let header = document.createElement('th');
header.innerText = hour;
row.appendChild(header);
for (let day of days) {
let cell = document.createElement('td');
if (schedule[day][hour]) {
cell.id = `lesson-${schedule[day][hour].key}`;
cell.draggable = true;
let lesson = schedule[day][hour].lesson;
let nameField = document.createElement('div');
nameField.class = 'schedule-namefield';
nameField.innerText = lesson.name;
cell.appendChild(nameField);
let otherField = document.createElement('div');
otherField.class = 'schedule-otherfield';
if (lesson.class !== filter) {
otherField.innerText = lesson.class;
} else {
otherField.innerText = lesson.teacher;
}
cell.appendChild(otherField);
} else {
cell.id = `cell-${day}-${hour}`;
cell.className = 'dropzone';
}
row.appendChild(cell);
}
scheduleTable.appendChild(row);
}
replaceElement('schedule-table', scheduleTable);
}
let dragged;
document.addEventListener('dragstart', (event) => {
dragged = event.target;
});
document.addEventListener('dragover', (event) => {
event.preventDefault();
});
document.addEventListener('dragenter', (event) => {
if (event.target.className === 'dropzone') {
event.target.style.backgroundColor = '#ccc';
}
});
document.addEventListener('dragleave', (event) => {
if (event.target.className === 'dropzone') {
event.target.style.backgroundColor = '';
}
});
document.addEventListener('drop', (event) => {
event.preventDefault();
if (event.target.className === 'dropzone') {
event.target.style.backgroundColor = '';
let key = Number.parseInt(dragged.id.split('lesson-')[1]);
let lesson = lessons[key];
if (event.target.id === 'unscheduled-lessons-box') {
lesson.at = null;
} else {
let cell = event.target.id.split('cell-')[1];
let day = cell.split('-')[0];
let hour = Number.parseInt(cell.split('-')[1]);
for (let otherLesson of lessons) {
if (!otherLesson.at || otherLesson.at.day !== day || otherLesson.at.hour !== hour) {
continue;
}
if (otherLesson.teacher && otherLesson.teacher === lesson.teacher) {
alert(`Päällekäisyys: ${otherLesson.name} ${otherLesson.class}`);
return;
}
if (otherLesson.class && otherLesson.class === lesson.class) {
alert(`Päällekäisyys: ${otherLesson.name} ${otherLesson.teacher}`);
return;
}
}
lesson.at = {day: day, hour: hour};
}
showSchedule();
}
});
</script>
<script src="testit.js"></script>
<script src="tietotyypit.js"></script>
<script src="tietomalli.js"></script>
<script src="käyttöliittymä.js"></script>
<script>ajaTestit();</script>
</body>
</html>

81
testit.js Normal file
View File

@ -0,0 +1,81 @@
'use strict';
let testit = [];
function testi(nimi, funktio) {
testit.push([nimi, funktio]);
}
function assertEq(konteksti, a, b) {
if (a !== b) {
throw new Error(`${konteksti}: ${a}, pitäisi olla ${b}`);
}
}
function assertNe(konteksti, a, b) {
if (a === b) {
throw new Error(`${konteksti}: ei tulisi olla ${b}`);
}
}
function assertRange(konteksti, a, min, max) {
if (a < min || a > max) {
throw new Error(`${konteksti}: ${a}, pitäisi olla välillä [${min}, ${max}]`);
}
}
function assertThrow(konteksti, viesti, funktio) {
try {
funktio();
} catch(poikkeus) {
if (poikkeus.message !== viesti) {
throw new Error(`${konteksti}: poikkeus "${poikkeus}", pitäisi olla "${viesti}"`);
}
return;
}
throw new Error(`${konteksti}: pitäisi heittää "${viesti}"`);
}
function ajaTestit() {
// Luo otsikko ja lista testeistä sivulle
let otsikkoTeksti = document.createTextNode('Testit käynnissä…');
let otsikko = document.createElement('h1');
otsikko.id = 'testit-otsikko';
otsikko.appendChild(otsikkoTeksti);
let testilista = document.createElement('ul');
testilista.id = 'testit-lista';
let body = document.getElementsByTagName('body')[0];
body.appendChild(otsikko);
body.appendChild(testilista);
// Aja testit
let epäonnistuneitaTestejä = 0;
for (let [nimi, funktio] of testit) {
// Lisää testi listaan
let testiListassa = document.createElement('li');
let testiTeksti = document.createTextNode(`${nimi}`);
testiListassa.appendChild(testiTeksti);
testilista.appendChild(testiListassa);
try {
funktio();
} catch (poikkeus) {
// Virhe testiä suoritettaessa — kerro virhe käyttäjälle ja päivitä
// epäonnistuneiden testien laskuria
testiTeksti.textContent = `${nimi}: ${poikkeus}`;
testiListassa.classList.add('testit-epäonnistunut');
epäonnistuneitaTestejä++;
continue;
}
testiTeksti.textContent = `${nimi}`;
}
// Päivitä otsikko onnistuivatko testit?
if (epäonnistuneitaTestejä === 0) {
otsikko.textContent = 'Testit suoritettu onnistuneesti';
} else {
otsikko.textContent = `${epäonnistuneitaTestejä} epäonnistui`;
}
}

186
tietomalli.js Normal file
View File

@ -0,0 +1,186 @@
'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();
});

145
tietotyypit.js Normal file
View File

@ -0,0 +1,145 @@
'use strict';
class LuokkaAste {
#luokat = 1;
luokat() {
// Tee lista, jossa on #luokat ensimmäistä isoa kirjainta
let luokat = [];
for (let i = 0; i < this.#luokat; i++) {
luokat.push(String.fromCharCode(65 + i));
}
return luokat;
}
lisää() {
this.#luokat++;
}
poista() {
if (this.#luokat <= 1) {
throw new Error('viimeistä luokkaa ei voi poistaa asteelta');
}
this.#luokat--;
}
}
class LuokkaAsteet {
asteet = [];
seuraavaAste() {
// Seuraava aste on yksi suurinta listassa jo olevaa astetta isompi, tai
// 1 jos listassa ei vielä ole asteita
let aste = 1;
for (let i = 0; i < this.asteet.length; i++) {
if (this.asteet[i] !== undefined) {
aste = i + 1;
}
}
return aste;
}
lisää(aste) {
aste ??= this.seuraavaAste()
if (this.asteet[aste] !== undefined) {
throw new Error(`luokka-aste ${aste} on jo olemassa`);
}
let asteObjekti = new LuokkaAste();
this.asteet[aste] = asteObjekti;
return aste;
}
poista(aste) {
if (this.asteet[aste] === undefined) {
throw new Error(`luokka-astetta ${aste} ei ole olemassa`);
}
this.asteet[aste] = undefined;
}
muuta(vanha, uusi) {
if (vanha === uusi) {
return;
}
// Heitä poikkeus jo ennen poistoa, jotta asteet-taulukkoon ei tule
// mitään muutoksia poikkeustilanteessa
if (this.asteet[uusi] !== undefined) {
throw new Error(`luokka-aste ${uusi} on jo olemassa`);
}
let aste = this.asteet[vanha];
this.poista(vanha);
this.asteet[uusi] = aste;
}
}
testi('seuraava aste', () => {
let luokkaAsteet = new LuokkaAsteet();
assertEq('aluksi', luokkaAsteet.seuraavaAste(), 1);
luokkaAsteet.lisää();
assertEq('asteen 1 jälkeen', luokkaAsteet.seuraavaAste(), 2);
luokkaAsteet.lisää(3);
assertEq('asteiden 1 ja 3 jälkeen', luokkaAsteet.seuraavaAste(), 4);
luokkaAsteet.lisää();
assertEq('asteiden 1, 3, 4 jälkeen', luokkaAsteet.seuraavaAste(), 5);
luokkaAsteet.poista(4);
luokkaAsteet.poista(3);
assertEq('asteet 3 ja 4 poistettua', luokkaAsteet.seuraavaAste(), 2);
luokkaAsteet.muuta(1, 5);
assertEq('aste 1 muutettua 5:ksi', luokkaAsteet.seuraavaAste(), 6);
});
testi('asteiden lisääminen', () => {
let luokkaAsteet = new LuokkaAsteet();
assertEq('1. aste', luokkaAsteet.lisää(), 1);
assertEq('2. aste', luokkaAsteet.lisää(), 2);
assertEq('3. aste', luokkaAsteet.lisää(3), 3);
assertThrow('3. aste uudelleen', 'luokka-aste 3 on jo olemassa', () => {
luokkaAsteet.lisää(3);
});
});
testi('asteiden poistaminen', () => {
let luokkaAsteet = new LuokkaAsteet();
luokkaAsteet.lisää();
luokkaAsteet.lisää();
luokkaAsteet.poista(1);
luokkaAsteet.poista(2);
assertThrow('3. asteen poisto', 'luokka-astetta 3 ei ole olemassa', () => {
luokkaAsteet.poista(3);
});
});
testi('asteiden muuttaminen', () => {
let luokkaAsteet = new LuokkaAsteet();
assertEq('lisääminen', luokkaAsteet.lisää(), 1);
luokkaAsteet.muuta(1, 5);
assertThrow('poisto', 'luokka-astetta 1 ei ole olemassa', () => {
luokkaAsteet.poista(1);
});
luokkaAsteet.poista(5);
});
testi('luokkien lisääminen', () => {
let luokkaAsteet = new LuokkaAsteet();
let aste = luokkaAsteet.asteet[luokkaAsteet.lisää()];
assertEq('aluksi pituus', aste.luokat().length, 1);
assertEq('aluksi', aste.luokat()[0], 'A');
aste.lisää();
aste.lisää();
assertEq('lisättyä pituus', aste.luokat().length, 3);
assertEq('lisättyä[0]', aste.luokat()[0], 'A');
assertEq('lisättyä[1]', aste.luokat()[1], 'B');
assertEq('lisättyä[2]', aste.luokat()[2], 'C');
});
testi('luokkien poistaminen', () => {
let luokkaAsteet = new LuokkaAsteet();
let aste = luokkaAsteet.asteet[luokkaAsteet.lisää()];
aste.lisää();
aste.lisää();
aste.poista();
aste.poista();
assertThrow('viimeisen poisto', 'viimeistä luokkaa ei voi poistaa asteelta', () => {
aste.poista();
});
assertEq('poistettua pituus', aste.luokat().length, 1);
assertEq('poistettua', aste.luokat()[0], 'A');
});