Files
Tiago Valente 08c9abb573 CheatSpecter 0.4.1
A quality pass after 0.4.0 was tried on a real console.

- Fix the Learn page crashing in current browsers when changing lesson steps
- Clear feedback for every search outcome; real progress with time left and Cancel
- Dialogs scroll inside the screen with Save and Cancel always in view
- Fix a console error on every closed connection to the payload
- Copy buttons work when the page is served from the PS5
- The practice game sits beside the lesson's tools and lasts between steps
- Damaged cheat files, vanishing error messages, unreadable memory shown as zeros
- Phone layout: one-line header, larger switches, readable rows
- Browser tests under test/ui and a guard against effects that return values
2026-10-10 15:19:56 -07:00

36 lines
1.6 KiB
JavaScript

// A React effect may return a cleanup function and nothing else. An effect written as a bare
// expression (`useEffect(() => window.scrollTo(0, 0))`) returns whatever the call returns, and
// newer browsers return a promise from scroll methods: React then tries to call that promise as
// the cleanup and the page breaks. So effects are written with a block body, or return a function.
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'src');
function* sources(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) yield* sources(full);
else if (/\.tsx?$/.test(entry.name)) yield full;
}
}
test('no effect returns the value of an expression', () => {
const bad = [];
for (const file of sources(root)) {
const text = fs.readFileSync(file, 'utf8');
// `() => {` (a block) and `() => () =>` (a cleanup function) are the two allowed openings.
for (const m of text.matchAll(/use(?:Layout|Insertion)?Effect\(\s*(?:async\s*)?\(\)\s*=>\s*(?=\S)(?!\{|\(\)\s*=>)/g)) {
bad.push(`${path.relative(root, file)}:${text.slice(0, m.index).split('\n').length}`);
}
for (const m of text.matchAll(/use(?:Layout|Insertion)?Effect\(\s*async\b/g)) {
bad.push(`${path.relative(root, file)}:${text.slice(0, m.index).split('\n').length} (async effect)`);
}
}
assert.deepEqual(bad, [], 'write these effects with a block body');
});