#![doc = include_str!("../ABOUT.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg",
html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg"
)]
#![cfg_attr(test, allow(clippy::needless_raw_string_hashes))] #![cfg_attr(not(test), forbid(clippy::unwrap_used))]
#![allow(unused_crate_dependencies)]
#![allow(
clippy::module_name_repetitions,
clippy::redundant_pub_crate,
clippy::let_unit_value
)]
pub mod console;
#[doc(inline)]
pub use console::{Console, ConsoleState, DefaultLogger, Logger, NullLogger};
pub mod clone;
#[cfg(feature = "fetch")]
pub mod fetch;
pub mod interval;
pub mod message;
pub mod microtask;
pub mod store;
pub mod text;
#[cfg(feature = "url")]
pub mod url;
pub mod extensions;
use crate::extensions::{
EncodingExtension, MicrotaskExtension, StructuredCloneExtension, TimeoutExtension,
};
pub use extensions::RuntimeExtension;
pub fn register(
extensions: impl RuntimeExtension,
realm: Option<boa_engine::realm::Realm>,
ctx: &mut boa_engine::Context,
) -> boa_engine::JsResult<()> {
(
TimeoutExtension,
EncodingExtension,
MicrotaskExtension,
StructuredCloneExtension,
#[cfg(feature = "url")]
extensions::UrlExtension,
extensions,
)
.register(realm, ctx)?;
Ok(())
}
pub fn register_extensions(
extensions: impl RuntimeExtension,
realm: Option<boa_engine::realm::Realm>,
ctx: &mut boa_engine::Context,
) -> boa_engine::JsResult<()> {
extensions.register(realm, ctx)?;
Ok(())
}
#[cfg(test)]
pub(crate) mod test {
use crate::extensions::ConsoleExtension;
use crate::register;
use boa_engine::{Context, JsError, JsResult, JsValue, Source, builtins};
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::pin::Pin;
#[allow(missing_debug_implementations)]
pub(crate) struct TestAction(Inner);
#[allow(dead_code)]
#[allow(clippy::type_complexity)]
enum Inner {
RunHarness,
Run {
source: Cow<'static, str>,
},
RunFile {
path: PathBuf,
},
RunJobs,
InspectContext {
op: Box<dyn FnOnce(&mut Context)>,
},
InspectContextAsync {
op: Box<dyn for<'a> FnOnce(&'a mut Context) -> Pin<Box<dyn Future<Output = ()> + 'a>>>,
},
Assert {
source: Cow<'static, str>,
},
AssertEq {
source: Cow<'static, str>,
expected: JsValue,
},
AssertWithOp {
source: Cow<'static, str>,
op: fn(JsValue, &mut Context) -> bool,
},
AssertOpaqueError {
source: Cow<'static, str>,
expected: JsValue,
},
AssertNativeError {
source: Cow<'static, str>,
kind: builtins::error::ErrorKind,
message: &'static str,
},
AssertContext {
op: fn(&mut Context) -> bool,
},
}
impl TestAction {
#[allow(unused)]
pub(crate) fn harness() -> Self {
Self(Inner::RunHarness)
}
pub(crate) fn run(source: impl Into<Cow<'static, str>>) -> Self {
Self(Inner::Run {
source: source.into(),
})
}
pub(crate) fn inspect_context(op: impl FnOnce(&mut Context) + 'static) -> Self {
Self(Inner::InspectContext { op: Box::new(op) })
}
pub(crate) fn inspect_context_async(op: impl AsyncFnOnce(&mut Context) + 'static) -> Self {
Self(Inner::InspectContextAsync {
op: Box::new(move |ctx| Box::pin(op(ctx))),
})
}
}
#[track_caller]
pub(crate) fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
let context = &mut Context::default();
register(ConsoleExtension::default(), None, context)
.expect("failed to register WebAPI objects");
run_test_actions_with(actions, context);
}
#[track_caller]
#[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
pub(crate) fn run_test_actions_with(
actions: impl IntoIterator<Item = TestAction>,
context: &mut Context,
) {
#[track_caller]
fn forward_val(context: &mut Context, source: &str) -> JsResult<JsValue> {
context.eval(Source::from_bytes(source))
}
#[track_caller]
fn forward_file(context: &mut Context, path: impl AsRef<Path>) -> JsResult<JsValue> {
let p = path.as_ref();
context.eval(Source::from_filepath(p).map_err(JsError::from_rust)?)
}
#[track_caller]
fn fmt_test(source: &str, test: usize) -> String {
format!(
"\n\nTest case {test}: \n```\n{}\n```",
textwrap::indent(source, " ")
)
}
let mut i = 1;
for action in actions.into_iter().map(|a| a.0) {
match action {
Inner::RunHarness => {
if let Err(e) = forward_file(context, "./assets/harness.js") {
panic!("Uncaught {e} in the test harness");
}
}
Inner::Run { source } => {
if let Err(e) = forward_val(context, &source) {
panic!("{}\nUncaught {e}", fmt_test(&source, i));
}
}
Inner::RunFile { path } => {
if let Err(e) = forward_file(context, &path) {
panic!("Uncaught {e} in file {path:?}");
}
forward_file(context, &path).expect("failed to run file");
}
Inner::RunJobs => {
if let Err(e) = context.run_jobs() {
panic!("Uncaught {e} in a job");
}
}
Inner::InspectContext { op } => {
op(context);
}
Inner::InspectContextAsync { op } => futures_lite::future::block_on(op(context)),
Inner::Assert { source } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
let Some(val) = val.as_boolean() else {
panic!(
"{}\nTried to assert with the non-boolean value `{}`",
fmt_test(&source, i),
val.display()
)
};
assert!(val, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertEq { source, expected } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
assert_eq!(val, expected, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertWithOp { source, op } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
assert!(op(val, context), "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertOpaqueError { source, expected } => {
let err = match forward_val(context, &source) {
Ok(v) => panic!(
"{}\nExpected error, got value `{}`",
fmt_test(&source, i),
v.display()
),
Err(e) => e,
};
let Some(err) = err.as_opaque() else {
panic!(
"{}\nExpected opaque error, got native error `{}`",
fmt_test(&source, i),
err
)
};
assert_eq!(err, &expected, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertNativeError {
source,
kind,
message,
} => {
let err = match forward_val(context, &source) {
Ok(v) => panic!(
"{}\nExpected error, got value `{}`",
fmt_test(&source, i),
v.display()
),
Err(e) => e,
};
let native = match err.try_native(context) {
Ok(err) => err,
Err(e) => panic!(
"{}\nCouldn't obtain a native error: {e}",
fmt_test(&source, i)
),
};
assert_eq!(&native.kind, &kind, "{}", fmt_test(&source, i));
assert_eq!(native.message(), message, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertContext { op } => {
assert!(op(context), "Test case {i}");
i += 1;
}
}
}
}
}