use super::super::tsgen::declare;
use super::core::HostState;
use crate::engine::{ScenarioInfo, ScenarioResult};
use rquickjs::function::Opt;
use rquickjs::{Ctx as JsCtx, Error as JsError, Function, Object, Persistent, Value};
#[allow(dead_code)] #[derive(ringo_flow_macros::TsInterface)]
struct ScenarioOptions {
tags: Option<Vec<String>>,
#[jsdoc(type = "boolean | string", optional)]
skip: Option<bool>,
only: Option<bool>,
}
declare!("type ScenarioBody = (ctx: any) => void | Promise<void>;");
declare!("type ScenarioEachBody<T> = (ctx: any, param: T) => void | Promise<void>;");
declare!(
"interface ScenarioEachFactory<T> {\n (name: string, body: ScenarioEachBody<T>): void;\n (name: string, opts: ScenarioOptions, body: ScenarioEachBody<T>): void;\n}"
);
declare!(
"declare namespace scenario {\n function each<T>(table: T[]): ScenarioEachFactory<T>;\n}"
);
#[ringo_flow_macros::ts_global(name = "skip")]
pub(in crate::script::js) fn skip_global(
cx: JsCtx<'_>,
reason: Opt<String>,
) -> rquickjs::Result<()> {
let msg = format!("{SKIP_PREFIX}{}", reason.0.unwrap_or_default());
Err(super::super::convert::throw(&cx, &msg))
}
#[ringo_flow_macros::ts_global(name = "setup")]
pub(in crate::script::js) fn setup_global<'js>(#[jsdoc(type = "() => any")] body: Function<'js>) {
let reg = body
.ctx()
.userdata::<HostState>()
.expect("host state stored at install")
.reg
.clone();
reg.set_setup(Persistent::save(&body.ctx().clone(), body));
}
#[ringo_flow_macros::ts_global(name = "teardown")]
pub(in crate::script::js) fn teardown_global<'js>(
#[jsdoc(type = "(ctx: any) => void")] body: Function<'js>,
) {
let reg = body
.ctx()
.userdata::<HostState>()
.expect("host state stored at install")
.reg
.clone();
reg.set_teardown(Persistent::save(&body.ctx().clone(), body));
}
#[ringo_flow_macros::ts_global(name = "scenario")]
#[jsdoc(sig = "scenario(name: string, body: ScenarioBody): void")]
#[jsdoc(sig = "scenario(name: string, opts: ScenarioOptions, body: ScenarioBody): void")]
pub(in crate::script::js) fn register_scenario<'a, 'b>(
name: String,
a: Value<'a>,
b: Opt<Function<'b>>,
) -> rquickjs::Result<()> {
let reg = a
.ctx()
.userdata::<HostState>()
.expect("host state stored at install")
.reg
.clone();
match b.0 {
Some(body) => {
let info = scenario_info(name, a.as_object());
let saved = Persistent::save(&body.ctx().clone(), body);
reg.add(info, saved);
}
None => {
let cx = a.ctx().clone();
let body = a.into_function().ok_or_else(|| {
super::super::convert::throw(&cx, "scenario body must be a function")
})?;
let saved = Persistent::save(&body.ctx().clone(), body);
reg.add(
ScenarioInfo {
name,
..Default::default()
},
saved,
);
}
}
Ok(())
}
fn scenario_info(name: String, opts: Option<&Object<'_>>) -> ScenarioInfo {
let mut info = ScenarioInfo {
name,
..Default::default()
};
let Some(opts) = opts else { return info };
if let Ok(Some(tags)) = opts.get::<_, Option<Vec<String>>>("tags") {
info.tags = tags;
}
if let Ok(v) = opts.get::<_, Value>("skip") {
if let Some(b) = v.as_bool() {
info.skip = b;
} else if let Some(s) = v.as_string() {
info.skip = true;
info.skip_reason = s.to_string().ok();
}
}
if let Ok(Some(only)) = opts.get::<_, Option<bool>>("only") {
info.only = only;
}
info
}
const SKIP_PREFIX: &str = "\u{1f}RINGO_SKIP\u{1f}";
pub fn classify_scenario_error(ctx: &JsCtx<'_>, err: JsError, label: &str) -> ScenarioResult {
match err {
JsError::Exception => {
let msg = super::core::exception_text(ctx);
match msg.strip_prefix(SKIP_PREFIX) {
Some(reason) => {
ScenarioResult::Skipped((!reason.is_empty()).then(|| reason.into()))
}
None => ScenarioResult::Failed(format!("in {label}: {msg}")),
}
}
other => ScenarioResult::Failed(format!("in {label}: {other}")),
}
}
pub(in crate::script::js) const EACH_SHIM: &str = r#"
scenario.each = function (table) {
if (!Array.isArray(table)) throw new Error("scenario.each: table must be an array");
return function (name, opts, body) {
if (arguments.length === 2) { body = opts; opts = undefined; }
if (typeof name !== "string") throw new Error("scenario.each: name must be a string");
if (typeof body !== "function") throw new Error("scenario.each: body must be a function");
for (var i = 0; i < table.length; i++) {
var row = table[i];
var label = name.replace(/\$(\w+)/g, function (_, k) {
var v = row[k];
return v == null ? "" : String(v);
});
scenario(label, opts, function (ctx) { return body(ctx, row); });
}
};
};
"#;