Typing lessons like duolingo & musicmouse cleanup
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { FIRST_LESSON_ID, LESSONS, WORLDS, lessonById, lessonsOfWorld, nextLesson } from "../curriculum";
|
||||
import { ELIGIBLE_MODES, FIRST_LESSON_ID, LESSONS, WORLDS, lessonById, lessonsOfWorld, nextLesson } from "../curriculum";
|
||||
import { fingerOf, keyForChar, needsShift } from "../fingers";
|
||||
|
||||
describe("LESSONS", () => {
|
||||
@@ -125,15 +125,52 @@ describe("LESSONS", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("only offers word modes where words exist", () => {
|
||||
it("plays a mode that actually fits its kind", () => {
|
||||
for (const lesson of LESSONS) {
|
||||
const wordModes = lesson.modes.some((mode) => mode === "feed" || mode === "race");
|
||||
expect(wordModes).toBe(lesson.words.length > 0);
|
||||
expect(ELIGIBLE_MODES[lesson.kind], `${lesson.number}: ${lesson.title}`).toContain(lesson.primaryMode);
|
||||
for (const mode of lesson.bonusModes) {
|
||||
expect(ELIGIBLE_MODES[lesson.kind], `${lesson.number}: ${lesson.title}`).toContain(mode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("always offers the pressure-free pearl-diving mode", () => {
|
||||
for (const lesson of LESSONS) expect(lesson.modes).toContain("pearls");
|
||||
it("never plays the same arcade game twice in a row", () => {
|
||||
const arcade = LESSONS.filter((lesson) => lesson.kind === "letters");
|
||||
for (let i = 1; i < arcade.length; i++) {
|
||||
expect(arcade[i]!.primaryMode, `${arcade[i]!.number}: ${arcade[i]!.title}`).not.toBe(arcade[i - 1]!.primaryMode);
|
||||
}
|
||||
});
|
||||
|
||||
it("offers every eligible mode this lesson isn't gated on, as a bonus", () => {
|
||||
for (const lesson of LESSONS) {
|
||||
const expected = ELIGIBLE_MODES[lesson.kind].filter(
|
||||
(mode) => (mode === "feed" || mode === "race") && mode !== lesson.primaryMode,
|
||||
);
|
||||
expect([...lesson.bonusModes].sort()).toEqual([...expected].sort());
|
||||
}
|
||||
});
|
||||
|
||||
it("mixes the games instead of always diving - feed and race take a turn as the required mode too", () => {
|
||||
const words = LESSONS.filter((l) => l.kind === "words");
|
||||
const sentences = LESSONS.filter((l) => l.kind === "sentences");
|
||||
expect(words.some((l) => l.primaryMode === "feed")).toBe(true);
|
||||
expect(words.some((l) => l.primaryMode === "race")).toBe(true);
|
||||
expect(sentences.some((l) => l.primaryMode === "race")).toBe(true);
|
||||
});
|
||||
|
||||
it("distinguishes a mixed round from a drill - both have no new keys, only one is a review", () => {
|
||||
const mixed = LESSONS.filter((lesson) => lesson.emphasis === "mixed");
|
||||
expect(mixed.length).toBeGreaterThan(0);
|
||||
for (const lesson of mixed) {
|
||||
expect(lesson.newKeys).toEqual([]);
|
||||
expect(lesson.isDrill).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it("gives every isolated round something new to drill", () => {
|
||||
for (const lesson of LESSONS) {
|
||||
if (lesson.emphasis === "isolated") expect(lesson.newKeys.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("teaches capitals only once shift exists", () => {
|
||||
@@ -154,7 +191,7 @@ describe("LESSONS", () => {
|
||||
|
||||
it("follows every pair of new keys with a drill", () => {
|
||||
const drills = LESSONS.filter((lesson) => lesson.isDrill);
|
||||
expect(drills.length).toBeGreaterThanOrEqual(12);
|
||||
expect(drills.length).toBeGreaterThanOrEqual(15);
|
||||
// A drill never introduces anything, and always has something to practise.
|
||||
for (const lesson of drills) {
|
||||
expect(lesson.newKeys).toEqual([]);
|
||||
@@ -162,8 +199,8 @@ describe("LESSONS", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("is long enough to be a real course", () => {
|
||||
expect(LESSONS.length).toBeGreaterThanOrEqual(40);
|
||||
it("is long enough to be a real, Duolingo-length course", () => {
|
||||
expect(LESSONS.length).toBeGreaterThanOrEqual(80);
|
||||
expect(WORLDS.length).toBe(5);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user