// `make dev` runs `node build.js --watch` // (https://git.eeqj.de/sneak/AutistMask/issues/332). These drive build.js's // watch() over a temp directory with a stand-in for build(), so nothing here // builds or writes dist/. const fs = require("fs"); const os = require("os"); const path = require("path"); const { watch } = require("../build"); let dir; let watchers = []; beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), "autistmask-watch-")); fs.mkdirSync(path.join(dir, "nested")); jest.spyOn(console, "log").mockImplementation(() => {}); jest.spyOn(console, "error").mockImplementation(() => {}); }); afterEach(() => { for (const watcher of watchers) watcher.close(); watchers = []; fs.rmSync(dir, { recursive: true, force: true }); jest.restoreAllMocks(); }); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // Wait until `condition()` holds; fail the test if it has not within 3s. async function until(condition) { const deadline = Date.now() + 3000; while (!condition()) { if (Date.now() > deadline) throw new Error("timed out waiting"); await sleep(10); } } // Save the way many editors do: write a new copy, then rename it over the // original, so the file at that path is a different one afterwards. function saveByRename(file, contents) { fs.writeFileSync(`${file}.tmp`, contents); fs.renameSync(`${file}.tmp`, file); } test("builds at start, then once per change to a file in a subdirectory", async () => { const file = path.join(dir, "nested", "a.js"); fs.writeFileSync(file, "1"); let builds = 0; watchers = watch([dir], async () => { builds++; }); await until(() => builds === 1); fs.writeFileSync(file, "2"); await until(() => builds === 2); await sleep(300); expect(builds).toBe(2); }); test("keeps seeing a file that is saved by renaming a new copy over it", async () => { const file = path.join(dir, "nested", "a.js"); fs.writeFileSync(file, "1"); let builds = 0; watchers = watch([dir], async () => { builds++; }); await until(() => builds === 1); saveByRename(file, "2"); await until(() => builds === 2); saveByRename(file, "3"); await until(() => builds === 3); }); test("a failed build is reported and watching carries on", async () => { let builds = 0; watchers = watch([dir], async () => { builds++; if (builds === 1) throw new Error("unexpected token"); }); await until(() => console.error.mock.calls.length === 1); expect(console.error).toHaveBeenCalledWith( "Build failed: unexpected token", ); fs.writeFileSync(path.join(dir, "a.js"), "1"); await until(() => builds === 2); }); test("a change made while a build runs causes one more build after it", async () => { let builds = 0; watchers = watch([dir], async () => { builds++; if (builds === 1) { fs.writeFileSync(path.join(dir, "a.js"), "1"); await sleep(200); } }); await until(() => builds === 2); await sleep(300); expect(builds).toBe(2); });