Files
next.js/test/lib/test-log.ts
Arian Tron 61f56f997c
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
first commit
2026-03-10 19:37:31 +03:30

155 lines
4.4 KiB
TypeScript

// Creates an event log. You can write to this during testing and then assert
// on the result.
//
// The main use case is for asynchronous e2e tests. It provides a `waitFor`
// method that resolves when the log matches some expected asynchronous sequence
// of events. This is an alternative to setting up a timer loop. It helps catch
// subtle mistakes where the order of events is not expected, or the same
// event happens more than it should.
//
// Based on the Scheduler.log pattern used in the React repo.
export function createTestLog() {
let events: unknown[] = []
// Represents a pending waitFor call.
let pendingExpectation: null | {
resolve: () => void
reject: (error: Error) => void
expectedEvents: Array<any>
error: Error
} = null
function log(value: unknown) {
// Add to the event log.
events.push(value)
// Check if we've reached the end of the expected log. If there's a
// pending waitFor, and we've reached the last of the expected events, this
// will resolve the promise.
pingExpectation()
}
function assert(expectedEvents: any[]) {
if (pendingExpectation !== null) {
const error = new Error('Cannot assert while a waitFor() is pending.')
Error.captureStackTrace(error, assert)
throw error
}
const actualEvents = events
events = []
if (!areLogsEqual(expectedEvents, actualEvents)) {
// Capture the stack trace of `assert` so that Jest will report the
// error as originating from the `assert` call instead of here.
const error = new Error(
'Expected sequence of events did not occur.\n\n' +
createDiff(expectedEvents, actualEvents)
)
Error.captureStackTrace(error, assert)
throw error
}
}
function waitFor(expectedEvents: any[], timeout: number = 5000) {
// Returns a promise that resolves when the event log matches the
// expected sequence.
// Capture the stack trace of `waitFor` so that if an inner assertion fails,
// Jest will report the error as originating from the `waitFor` call instead
// of inside this module's implementation.
const error = new Error()
Error.captureStackTrace(error, waitFor)
if (pendingExpectation !== null) {
error.message = 'A previous waitFor() is still pending.'
throw error
}
let resolve
let reject
const promise = new Promise<void>((res, rej) => {
resolve = res
reject = rej
})
const thisExpectation = {
resolve,
reject,
expectedEvents,
error,
}
pendingExpectation = thisExpectation
setTimeout(() => {
if (pendingExpectation === thisExpectation) {
error.message = `waitFor timed out after ${timeout}ms`
reject(error)
}
}, timeout)
pingExpectation()
return promise
}
function pingExpectation() {
if (pendingExpectation !== null) {
const expectedEvents = pendingExpectation.expectedEvents
if (events.length < expectedEvents.length) {
return
}
if (areLogsEqual(expectedEvents, events)) {
// We've reached the end of the expected log. Resolve the promise and
// reset the log.
events = []
pendingExpectation.resolve()
pendingExpectation = null
} else {
// The log does not match what was expected by the test. Reject the
// promise and reset the log.
// Use the error object that we captured at the start of the `waitFor`
// call. Jest will show that the error originated from `waitFor` call
// instead of inside this internal function.
const error = pendingExpectation.error
error.message =
'Expected sequence of events did not occur.\n\n' +
createDiff(expectedEvents, events)
events = []
pendingExpectation.reject(error)
pendingExpectation = null
}
}
}
function createDiff(expected, actual) {
// TODO: Jest exposes the diffing utility that it uses for `expect`.
// We could use that here for nicer output.
return `
Expected: ${JSON.stringify(expected)}
Actual: ${JSON.stringify(actual)}
`
}
function areLogsEqual(a, b) {
if (a.length !== b.length) {
return false
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false
}
}
return true
}
return {
log,
waitFor,
assert,
}
}