Add a regression test for the newest change winning

The existing tests cover the scheduling: serial-batches asserts that only
one rebuild runs at a time, and the watcher tests cover event filtering and
shutdown flushing. Neither asserts the outcome the bug was reported as —
that what ends up written is the newest change.

This drives the watcher wiring with an older change whose rebuild is slow
and a newer change whose rebuild is fast, and asserts the newer result is
written last. Against the previous fire-and-forget behaviour the two
rebuilds overlap and the stale one lands on top, so the test fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lcj4iQ3fBxMwAu2rf4zLbC
This commit is contained in:
Michael Glass 2026-08-31 15:24:27 +02:00
parent 35a16706c7
commit 2b9a5c4112
No known key found for this signature in database
GPG key ID: 25FC9DFE17C802D3

View file

@ -76,3 +76,41 @@ it('waits for an entered watcher callback before shutdown flushes changes', asyn
await queue.close()
expect(calls).toEqual([['delayed-change']])
})
it('writes the newest change last when an earlier rebuild is slower', async () => {
// The reported bug: a rebuild for an older change finished *after* the rebuild
// for a newer one and overwrote it, leaving stale CSS on disk. The serial
// batches tests pin the scheduling on its own; this pins the outcome through
// the watcher wiring, which is the shape the bug was reported in.
let written: string[] = []
let releaseSlowRebuild!: () => void
let slowRebuildCanFinish = new Promise<void>((resolve) => (releaseSlowRebuild = resolve))
let queue = serializeBatches<string>(async (files) => {
// Make the *first* rebuild the slow one. Without serialization the second
// rebuild finishes first and this stale result lands on top of it.
if (written.length === 0) await slowRebuildCanFinish
written.push(files.at(-1)!)
})
let fake = fakeWatcher()
await createWatchers(
['/watch'],
async () => {},
queue,
fake.watcher,
async () => ({
isFile: () => true,
isSymbolicLink: () => false,
}),
)
await fake.callbacks[0](null, [{ type: 'update', path: 'older-change' }])
await nextTask()
await fake.callbacks[0](null, [{ type: 'update', path: 'newer-change' }])
await nextTask()
releaseSlowRebuild()
await queue.close()
expect(written).toEqual(['older-change', 'newer-change'])
})