From b46a3611af5d3bb29dc123cea076b448ff028e31 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Thu, 29 Oct 2026 01:43:48 +0100 Subject: [PATCH] fix: final review findings Co-Authored-By: Claude Opus 5.5 --- .gitignore | 4 ++++ README.md | 6 +++--- course/00-setup.md | 2 ++ course/06-rendering-as-data.md | 2 +- course/09-wiring-it-together.md | 10 +++++----- fsharp/tests/Purity.fs | 8 +++++++- scripts/serve.mjs | 2 +- 7 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 35b071a..d41d86f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ obj/ .scala-build/ .bsp/ */web/dist/ +.bloop/ +.metals/ +.idea/ +.vscode/ # build outputs .fable/ diff --git a/README.md b/README.md index b1503b7..01ab28a 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ To play the game in any track you also need a modern browser. [Module 0](course/ Run the tests (from the repository root): ```sh -cd js && node --test "test/m03-*.test.js" # JavaScript, one module (all: ./test.sh) -cd scala && ./test.sh M03 # Scala, one module (all: ./test.sh) -cd fsharp && ./test.sh M03 # F#, one module (all: ./test.sh) +(cd js && node --test "test/m03-*.test.js") # JavaScript, one module (all: ./test.sh) +(cd scala && ./test.sh M03) # Scala, one module (all: ./test.sh) +(cd fsharp && ./test.sh M03) # F#, one module (all: ./test.sh) ``` In a fresh checkout, Module 0's tests pass and every other module's tests fail: the files in `src/` are stubs for you to fill in. diff --git a/course/00-setup.md b/course/00-setup.md index fbaed0f..5ec75dd 100644 --- a/course/00-setup.md +++ b/course/00-setup.md @@ -189,6 +189,8 @@ cd fsharp ./test.sh # everything ``` +If `scala-cli` prints a hint that the munit dependency is outdated, ignore it: the course pins munit on purpose, for reproducibility. + Try it now with Module 0, which has a few sanity tests that already pass. They check that your toolchain works and that the fixtures folder can be read. If you see three green tests (Scala and F#: a short summary with no failures), you are set up. ### What a red test means diff --git a/course/06-rendering-as-data.md b/course/06-rendering-as-data.md index 96a06f7..8462082 100644 --- a/course/06-rendering-as-data.md +++ b/course/06-rendering-as-data.md @@ -195,7 +195,7 @@ Our `view : GameState → Scene` is exactly this, for a canvas instead of HTML. ## Exercises -> **About test names.** The test titles quoted under "Proved by" are the JavaScript ones. The Scala and F# suites test the same things one-to-one, but their titles use each track's vocabulary: "events" instead of "inputs", `None` instead of `null`, `()` instead of `undefined`, `bind`/`choose` instead of `flatMap`/`filterMap`, `IO.map throwingF io` instead of `map(io, throwingF)`, and so on. Failing tests are reported by name when you run the module's tests; to read all titles, open `scala/test/M06View.test.scala` or `fsharp/tests/M06View.fs`. Fixture-based tests have the same case names in every track. +> **About test names.** The test titles quoted under "Proved by" are the JavaScript ones, which carry an `m06` prefix (e.g. `m06 CELL is 20 pixels`). The Scala and F# suites test the same things one-to-one, without that prefix: `CELL is 20 pixels` is identical, and the fixture-driven cases appear as `view: ` with the same case names in every track. Failing tests are reported by name when you run the module's tests; to read all titles, open `scala/test/M06View.test.scala` or `fsharp/tests/M06View.fs`. ### Exercise 6.1 — `view` diff --git a/course/09-wiring-it-together.md b/course/09-wiring-it-together.md index 407a4b5..ac851bf 100644 --- a/course/09-wiring-it-together.md +++ b/course/09-wiring-it-together.md @@ -229,11 +229,11 @@ Remember section 12 of Module 7: the loop only helps if each continuation does a That's the whole runtime. Go back to the stateful code at the start of Module 8, with its `let state` and logic in callbacks, and compare. There, the mutable variable held the *game state*, and game logic lived in the callbacks. Here, the variable holds the *current stream* — the runtime doesn't even know there's a game. It has no idea what a snake, a tick or a score is. All the game logic is in pure values; the runtime is a few lines of plumbing. That is what "pushing effects to the edge" means in practice. ``` - ┌──────────────────────── runtime (impure, ~20 lines) ─────────────────────────┐ - browser ──key──▶│ feed(KeyDown key): [ios, next] = stream.step(ev); stream = next; ios.forEach(runIO) │──▶ canvas - ──time─▶│ feed(Time t) │ - └───────────────────────────────────────────────────────────────────────────────┘ - ▲ the one `let` / `var` / ref cell: stream + ┌────────────────────────────── runtime (impure, ~20 lines) ─────────────────────────────────┐ + browser ──key──▶│ feed(KeyDown key): [ios, next] = stream.step(ev); stream = next; ios.forEach(runIO) │ + ──time─▶│ feed(Time t) │ + └────────────────────────────────────────────────────────────────────────────────────────────┘ + ▲ the one `let` / `var` / ref cell: stream ``` The tests describe the behaviour precisely; read them before writing the code: diff --git a/fsharp/tests/Purity.fs b/fsharp/tests/Purity.fs index c037e56..7f03389 100644 --- a/fsharp/tests/Purity.fs +++ b/fsharp/tests/Purity.fs @@ -29,7 +29,13 @@ let private violations (code: string) = let private fsFiles () = if Directory.Exists srcDir then - Directory.GetFiles(srcDir, "*.fs", SearchOption.AllDirectories) |> Array.sort |> List.ofArray + Directory.GetFiles(srcDir, "*.fs", SearchOption.AllDirectories) + |> Array.filter (fun f -> + // skip generated output: any path component named bin or obj + let rel = Path.GetRelativePath(srcDir, f) + not (rel.Split([| '/'; '\\' |]) |> Array.exists (fun d -> d = "bin" || d = "obj"))) + |> Array.sort + |> List.ofArray else [] diff --git a/scripts/serve.mjs b/scripts/serve.mjs index ca56502..0ffc098 100644 --- a/scripts/serve.mjs +++ b/scripts/serve.mjs @@ -27,4 +27,4 @@ createServer(async (req, res) => { res.writeHead(e.code === 'EACCES' ? 403 : 404, { 'content-type': 'text/plain' }) res.end(e.code === 'EACCES' ? 'forbidden' : 'not found') } -}).listen(port, () => console.log(`Serving ${root} on http://localhost:${port}`)) +}).listen(port, '127.0.0.1', () => console.log(`Serving ${root} on http://localhost:${port}`))