ferrijs-std 0.1.0

Node and web standard library for the ferrijs QuickJS runtime: WHATWG Streams, Events, AbortController, Buffer, crypto, fs, os, url, zlib and the capability model they enforce (partly derived from awslabs/llrt, Apache-2.0).
Documentation
use crate::test::test_sync_with;

#[tokio::test]
async fn high_water_mark_uses_javascript_number_conversion() {
    test_sync_with(|ctx| {
        crate::stream_web::init(&ctx)?;
        ctx.eval::<(), _>(
            r#"
            const converted = {
                valueOf() {
                    return "7.5";
                },
            };

            const observed = [];
            new ReadableStream({
                start(controller) {
                    observed.push(controller.desiredSize);
                },
            }, { highWaterMark: converted });
            observed.push(
                new WritableStream({}, { highWaterMark: converted })
                    .getWriter().desiredSize,
            );

            const transform = new TransformStream({
                start(controller) {
                    observed.push(controller.desiredSize);
                },
            }, { highWaterMark: converted }, { highWaterMark: converted });
            observed.push(transform.writable.getWriter().desiredSize);

            observed.push(
                new CountQueuingStrategy({ highWaterMark: converted }).highWaterMark,
                new ByteLengthQueuingStrategy({ highWaterMark: converted }).highWaterMark,
            );

            if (observed.length !== 6 || observed.some(value => value !== 7.5)) {
                throw new Error(`Unexpected highWaterMark values: ${observed}`);
            }

            const primitiveConversions = [
                [false, 0],
                [true, 1],
                ["2.25", 2.25],
            ];
            for (const [input, expected] of primitiveConversions) {
                const strategy = new CountQueuingStrategy({ highWaterMark: input });
                if (strategy.highWaterMark !== expected) {
                    throw new Error(`${String(input)} converted to ${strategy.highWaterMark}`);
                }
            }
            "#,
        )
    })
    .await;
}

#[tokio::test]
async fn high_water_mark_conversion_order_and_errors_are_observable() {
    test_sync_with(|ctx| {
        crate::stream_web::init(&ctx)?;
        ctx.eval::<(), _>(
            r#"
            const order = [];
            new WritableStream({}, {
                get highWaterMark() {
                    order.push("get highWaterMark");
                    return {
                        valueOf() {
                            order.push("convert highWaterMark");
                            return 1;
                        },
                    };
                },
                get size() {
                    order.push("get size");
                    return undefined;
                },
            });

            const expectedOrder =
                "get highWaterMark,convert highWaterMark,get size";
            if (order.join() !== expectedOrder) {
                throw new Error(`Unexpected conversion order: ${order}`);
            }

            const expectedError = new Error("number conversion failed");
            const throwingValue = {
                valueOf() {
                    throw expectedError;
                },
            };
            const factories = [
                () => new ReadableStream({}, { highWaterMark: throwingValue }),
                () => new WritableStream({}, { highWaterMark: throwingValue }),
                () => new TransformStream({}, { highWaterMark: throwingValue }),
                () => new TransformStream({}, {}, { highWaterMark: throwingValue }),
                () => new CountQueuingStrategy({ highWaterMark: throwingValue }),
                () => new ByteLengthQueuingStrategy({ highWaterMark: throwingValue }),
            ];

            for (const factory of factories) {
                try {
                    factory();
                    throw new Error("Expected number conversion to throw");
                } catch (error) {
                    if (error !== expectedError) {
                        throw new Error(`Unexpected conversion error: ${error}`);
                    }
                }
            }

            for (const input of [1n, Symbol("highWaterMark")]) {
                try {
                    new CountQueuingStrategy({ highWaterMark: input });
                    throw new Error("Expected ToNumber to reject the value");
                } catch (error) {
                    if (!(error instanceof TypeError)) {
                        throw new Error(`Expected TypeError, got ${error}`);
                    }
                }
            }
            "#,
        )
    })
    .await;
}

#[tokio::test]
async fn invalid_converted_stream_high_water_marks_throw_range_error() {
    test_sync_with(|ctx| {
        crate::stream_web::init(&ctx)?;
        ctx.eval::<(), _>(
            r#"
            for (const highWaterMark of ["-1", "not a number"]) {
                const factories = [
                    () => new ReadableStream({}, { highWaterMark }),
                    () => new WritableStream({}, { highWaterMark }),
                    () => new TransformStream({}, { highWaterMark }),
                    () => new TransformStream({}, {}, { highWaterMark }),
                ];
                for (const factory of factories) {
                    try {
                        factory();
                        throw new Error("Expected invalid highWaterMark to throw");
                    } catch (error) {
                        if (!(error instanceof RangeError)) {
                            throw new Error(`Expected RangeError, got ${error}`);
                        }
                    }
                }
            }
            "#,
        )
    })
    .await;
}