#![expect(clippy::expect_used, reason = "tests assert by panicking")]
use vize_l0::dump::{Dump, Mode as DumpMode};
use vize_l0::{Allocator, Box, Span, Vec as ArenaVec, pool};
use vize_l2::dump::{
Expr as DumpExpr, Interpolation as DumpInterpolation, Op as DumpOp, Page as L2Page,
};
use vize_l2::expr::{ExprRef, JsExpr, OpaqueExpr, OpaqueReason};
use vize_l2::op::{InterpolationOp, Op};
fn arena_built<'a>(allocator: &'a Allocator) -> ArenaVec<'a, Op<'a>> {
let retained = JsExpr::parse_in(allocator, "items.filter(Boolean).length", Span::new(2, 30))
.expect("the replay payload is admitted");
let escape = allocator.alloc(OpaqueExpr {
reason: OpaqueReason::MultiStatement,
source: "a++; b++",
span: Span::new(36, 44),
});
ArenaVec::from_iter_in(
[
Op::Interpolation(Box::new_in(
InterpolationOp {
expression: ExprRef::Js(retained),
span: Span::new(0, 32),
},
&allocator,
)),
Op::Interpolation(Box::new_in(
InterpolationOp {
expression: ExprRef::Opaque(escape),
span: Span::new(34, 46),
},
&allocator,
)),
],
&allocator,
)
}
const CANONICAL: &str = "\
[l2-dump-v2]
ops=2
[l2-dump-v2.ops]
ui.interpolation js(\"items.filter(Boolean).length\" @2:30) @0:32
ui.interpolation opaque(multi-statement \"a++; b++\" @36:44) @34:46
";
#[test]
fn a_printed_folio_replays_across_an_arena_reset() {
let (printed, mirrored) = {
let guard = pool::acquire();
let ops = arena_built(&guard);
let mirrored = L2Page::of(&ops);
(mirrored.print_to_string(DumpMode::Full), mirrored)
};
assert_eq!(printed.as_str(), CANONICAL);
let replayed = L2Page::parse(printed.as_str()).expect("printed text parses");
assert_eq!(replayed, mirrored);
let guard = pool::acquire();
let allocator: &Allocator = &guard;
let DumpOp::Interpolation(DumpInterpolation {
expression: DumpExpr::Js { source, span },
..
}) = &replayed.ops[0]
else {
panic!("the first replayed op must be the retained-js interpolation");
};
let source = allocator.alloc_str(source.as_str());
let loaded = ExprRef::parse_js_in(allocator, source, *span);
let ExprRef::Js(loaded) = loaded else {
panic!("an admitted payload must reload as ExprRef::Js");
};
assert_eq!(loaded.source, "items.filter(Boolean).length");
assert_eq!(loaded.span, Span::new(2, 30));
let ops = ArenaVec::from_iter_in(
[
Op::Interpolation(Box::new_in(
InterpolationOp {
expression: ExprRef::Js(loaded),
span: Span::new(0, 32),
},
&allocator,
)),
Op::Interpolation(Box::new_in(
InterpolationOp {
expression: ExprRef::Opaque(allocator.alloc(OpaqueExpr {
reason: OpaqueReason::MultiStatement,
source: "a++; b++",
span: Span::new(36, 44),
})),
span: Span::new(34, 46),
},
&allocator,
)),
],
&allocator,
);
assert_eq!(
L2Page::of(&ops).print_to_string(DumpMode::Full).as_str(),
CANONICAL
);
}
#[test]
fn the_load_path_falls_back_to_the_escape_variant() {
let arena = Allocator::default();
let allocator = &arena;
let rejected = ExprRef::parse_js_in(allocator, "a++; b++", Span::new(0, 8));
let ExprRef::Opaque(rejected) = rejected else {
panic!("non-covering text must load as ExprRef::Opaque");
};
assert_eq!(rejected.reason, OpaqueReason::ParseRejected);
assert_eq!(rejected.source, "a++; b++");
assert_eq!(rejected.span, Span::new(0, 8));
let invalid = ExprRef::parse_js_in(allocator, "%", Span::new(0, 1));
let ExprRef::Opaque(invalid) = invalid else {
panic!("invalid text must load as ExprRef::Opaque");
};
assert_eq!(invalid.reason, OpaqueReason::ParseRejected);
let deep = "((((((((((((((((((((((((((((((((((((((((x))))))))))))))))))))))))))))))))))))))))";
let refused = ExprRef::parse_js_in(allocator, deep, Span::new(0, 81));
let ExprRef::Opaque(refused) = refused else {
panic!("guard-refused text must load as ExprRef::Opaque");
};
assert_eq!(refused.reason, OpaqueReason::NestingRefused);
let admitted = ExprRef::parse_js_in(allocator, "a + b", Span::new(0, 5));
let ExprRef::Js(admitted) = admitted else {
panic!("admitted text must load as ExprRef::Js");
};
assert_eq!(admitted.source, "a + b");
}