"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ObservableStream = void 0;
const tslib_1 = require("tslib");
const web_1 = require("node:stream/web");
const expect_utils_1 = require("@jest/expect-utils");
const globals_1 = require("@jest/globals");
const matcherUtils = tslib_1.__importStar(require("jest-matcher-utils"));
class ObservableStream {
    reader;
    subscription;
    readerQueue = [];
    constructor(observable) {
        this.unsubscribe = this.unsubscribe.bind(this);
        this.reader = new web_1.ReadableStream({
            start: (controller) => {
                this.subscription = observable.subscribe({
                    next: (value) => controller.enqueue({ type: "next", value }),
                    error: (error) => controller.enqueue({ type: "error", error }),
                    complete: () => controller.enqueue({ type: "complete" }),
                });
            },
        }).getReader();
    }
    peek({ timeout = 100 } = {}) {
        // Calling `peek` multiple times in a row should not advance the reader
        // multiple times until this value has been consumed.
        let readerPromise = this.readerQueue[0];
        if (!readerPromise) {
            // Since this.reader.read() advances the reader in the stream, we don't
            // want to consume this promise entirely, otherwise we will miss it when
            // calling `take`. Instead, we push it into a queue that can be consumed
            // by `take` the next time its called so that we avoid advancing the
            // reader until we are finished processing all peeked values.
            readerPromise = this.readNextValue();
            this.readerQueue.push(readerPromise);
        }
        return Promise.race([
            readerPromise,
            new Promise((_, reject) => {
                setTimeout(reject, timeout, new Error("Timeout waiting for next event"));
            }),
        ]);
    }
    take({ timeout = 100 } = {}) {
        return Promise.race([
            this.readerQueue.shift() || this.readNextValue(),
            new Promise((_, reject) => {
                setTimeout(reject, timeout, new Error("Timeout waiting for next event"));
            }),
        ]);
    }
    unsubscribe() {
        this.subscription.unsubscribe();
    }
    async takeNext(options) {
        const event = await this.take(options);
        validateEquals(event, { type: "next", value: globals_1.expect.anything() });
        return event.value;
    }
    async takeError(options) {
        const event = await this.take(options);
        validateEquals(event, { type: "error", error: globals_1.expect.anything() });
        return event.error;
    }
    async takeComplete(options) {
        const event = await this.take(options);
        validateEquals(event, { type: "complete" });
    }
    async readNextValue() {
        return this.reader.read().then((result) => result.value);
    }
}
exports.ObservableStream = ObservableStream;
// Lightweight expect(...).toEqual(...) check that avoids using `expect` so that
// `expect.assertions(num)` does not double count assertions when using the take*
// functions inside of expect(stream).toEmit* matchers.
function validateEquals(actualEvent, expectedEvent) {
    // Uses the same matchers as expect(...).toEqual(...)
    // https://github.com/jestjs/jest/blob/611d1a4ba0008d67b5dcda485177f0813b2b573e/packages/expect/src/matchers.ts#L626-L629
    const isEqual = (0, expect_utils_1.equals)(actualEvent, expectedEvent, [
        ...getCustomMatchers(),
        expect_utils_1.iterableEquality,
    ]);
    if (isEqual) {
        return;
    }
    const hint = matcherUtils.matcherHint("toEqual", "stream", "expected");
    throw new Error(hint +
        "\n\n" +
        matcherUtils.printDiffOrStringify(expectedEvent, actualEvent, "Expected", "Received", true));
}
function getCustomMatchers() {
    // https://github.com/jestjs/jest/blob/611d1a4ba0008d67b5dcda485177f0813b2b573e/packages/expect/src/jestMatchersObject.ts#L141-L143
    const JEST_MATCHERS_OBJECT = Symbol.for("$$jest-matchers-object");
    return globalThis[JEST_MATCHERS_OBJECT].customEqualityTesters;
}
//# sourceMappingURL=ObservableStream.cjs.map
