use vize_l0::dump::{Dump, Error, Mode};
use vize_l0::key::{ArtifactKey, KeyedArtifact};
use vize_l0::{Span, String, cstr};
use vize_l2::dump::historical::v1::Page as HistoricalPage;
use vize_l2::dump::provenance::{Page as ProvenancePage, Record};
use vize_l2::dump::{Attribute, Comment, Element, Expr, Interpolation, Op, Page, Text};
use vize_l2::expr::OpaqueReason;
use vize_l2::op::Namespace;
const CURRENT: &str = r#"[l2-dump-v2]
ops=6
[l2-dump-v2.ops]
ui.element div @0:120
attr title="[disegno]" @1:12
ui.text "[disegno.ops]" @12:27
ui.interpolation js("[l2-dump-v2]" @27:42) @27:42
ui.comment "[l2-dump-v2.ops]" @42:60
ui.interpolation foreign(md "[disegno]" @60:78) @60:78
ui.interpolation opaque(multi-statement "[s2-provenance-folio]" @78:100) @78:100
"#;
const HISTORICAL: &str = r#"[disegno]
ops=6
[disegno.ops]
ui.element div @0:120
attr title="[disegno]" @1:12
ui.text "[disegno.ops]" @12:27
ui.interpolation js("[l2-dump-v2]" @27:42) @27:42
ui.comment "[l2-dump-v2.ops]" @42:60
ui.interpolation foreign(md "[disegno]" @60:78) @60:78
ui.interpolation opaque(multi-statement "[s2-provenance-folio]" @78:100) @78:100
"#;
fn tree(base: u32) -> Page {
let span = |start, end| Span::new(base + start, base + end);
Page {
ops: vec![Op::Element(Element {
tag: String::from("div"),
namespace: Namespace::Html,
attributes: vec![Attribute {
name: String::from("title"),
value: Some(String::from("[disegno]")),
span: span(1, 12),
}],
bindings: vec![],
children: vec![
Op::Text(Text {
content: String::from("[disegno.ops]"),
span: span(12, 27),
}),
Op::Interpolation(Interpolation {
expression: Expr::Js {
source: String::from("[l2-dump-v2]"),
span: span(27, 42),
},
span: span(27, 42),
}),
Op::Comment(Comment {
content: String::from("[l2-dump-v2.ops]"),
span: span(42, 60),
}),
Op::Interpolation(Interpolation {
expression: Expr::Foreign {
dialect: String::from("md"),
source: String::from("[disegno]"),
span: span(60, 78),
},
span: span(60, 78),
}),
Op::Interpolation(Interpolation {
expression: Expr::Opaque {
reason: OpaqueReason::MultiStatement,
source: String::from("[s2-provenance-folio]"),
span: span(78, 100),
},
span: span(78, 100),
}),
],
span: span(0, 120),
})],
}
}
#[test]
fn both_explicit_codecs_preserve_header_looking_payloads_and_spans() {
let page = tree(0);
assert_eq!(page.print_to_string(Mode::Full).as_str(), CURRENT);
assert_eq!(Page::parse(CURRENT), Ok(page.clone()));
let old = HistoricalPage::from_current(page.clone());
assert_eq!(old.print_to_string(Mode::Full).as_str(), HISTORICAL);
assert_eq!(HistoricalPage::parse(HISTORICAL), Ok(old.clone()));
assert_eq!(old.into_current(), page);
}
#[test]
fn current_and_historical_headers_never_select_each_other() {
assert_eq!(
Page::parse(HISTORICAL),
Err(Error::new(1, cstr!("first section must be [l2-dump-v2]")))
);
assert_eq!(
HistoricalPage::parse(CURRENT),
Err(Error::new(1, cstr!("first section must be [disegno]")))
);
for (input, line, message) in [
(
"[l2-dump-v2]\nops=0\n[disegno.ops]\n",
3,
"unknown section [disegno.ops]",
),
(
"[l2-dump-v2]\nops=0\n[l2-dump-v2]\n",
3,
"duplicate section [l2-dump-v2]",
),
(
"[l2-dump-v2]\nops=0\n[l2-dump-v2.ops]\n[l2-dump-v2.ops]\n",
4,
"duplicate section [l2-dump-v2.ops]",
),
("", 0, "missing [l2-dump-v2] header"),
] {
assert_eq!(
Page::parse(input),
Err(Error::new(line, String::from(message)))
);
}
assert_eq!(
HistoricalPage::parse("[disegno]\nops=0\n[l2-dump-v2.ops]\n"),
Err(Error::new(3, cstr!("unknown section [l2-dump-v2.ops]")))
);
}
#[test]
fn provenance_v2_preserves_quoted_headers_and_record_spans() {
let page = ProvenancePage {
records: vec![Record {
rule: String::from("lower.text"),
node: Some(7),
before: String::from("[s2-provenance-folio]\n[disegno]"),
after: String::from("[l2-provenance-dump-v2]"),
span: Span::new(3, 42),
}],
};
let expected = r#"[l2-provenance-dump-v2]
[l2-provenance-dump-v2.records]
rule=lower.text node=7 before="[s2-provenance-folio]\n[disegno]" after="[l2-provenance-dump-v2]" @3:42
"#;
assert_eq!(page.print_to_string(Mode::Full).as_str(), expected);
assert_eq!(page.print_to_string(Mode::Display).as_str(), expected);
assert_eq!(ProvenancePage::parse(expected), Ok(page));
assert!(ProvenancePage::parse("[s2-provenance-folio]\n").is_err());
}
fn text_page(value: &str, span: Span) -> Page {
Page {
ops: vec![Op::Text(Text {
content: String::from(value),
span,
})],
}
}
#[test]
fn recipe3_preserves_relative_offsets_and_before_base_injectivity() {
assert_eq!(<Page as KeyedArtifact>::SCHEMA_VERSION, 3);
assert_eq!(
ArtifactKey::of(&tree(0), 0),
ArtifactKey::of(&tree(128), 128)
);
assert_ne!(
ArtifactKey::of(&text_page("same", Span::new(3, 5)), 10),
ArtifactKey::of(&text_page("same", Span::new(0, 0)), 10)
);
assert_ne!(
ArtifactKey::of(&text_page("same", Span::new(3, 5)), 0),
ArtifactKey::of(&text_page("changed", Span::new(3, 5)), 0)
);
assert_ne!(
ArtifactKey::of(&text_page("same", Span::new(3, 5)), 0),
ArtifactKey::of(&text_page("same", Span::new(3, 6)), 0)
);
}
#[test]
fn typed_slots_project_only_to_the_identical_frozen_legacy_wire_record() {
use vize_l2::dump::{Binding, Component, SlotsObject, VueDirective};
let value = Expr::Js {
source: String::from(r#"pick("猫&")"#),
span: Span::new(9, 21),
};
let span = Span::new(0, 31);
let page = |binding| Page {
ops: vec![Op::Component(Component {
name: String::from("B"),
attributes: vec![],
bindings: vec![binding],
children: vec![],
span: Span::new(0, 32),
})],
};
let typed = page(Binding::SlotsObject(SlotsObject {
value: value.clone(),
span,
}));
let legacy = page(Binding::VueDirective(VueDirective {
name: String::from("slots"),
argument: None,
modifiers: vec![],
value: Some(value),
span,
}));
let old = r#"[disegno]
ops=2
[disegno.ops]
ui.component B @0:32
vue.directive "slots" value=js("pick(\"猫&\")" @9:21) @0:31
"#;
let current = r#"[l2-dump-v2]
ops=2
[l2-dump-v2.ops]
ui.component B @0:32
ui.slots-object value=js("pick(\"猫&\")" @9:21) @0:31
"#;
assert_eq!(typed.print_to_string(Mode::Full).as_str(), current);
assert_eq!(Page::parse(current), Ok(typed.clone()));
assert_eq!(
HistoricalPage::from_current(typed)
.print_to_string(Mode::Full)
.as_str(),
old
);
assert_eq!(
HistoricalPage::from_current(legacy.clone())
.print_to_string(Mode::Full)
.as_str(),
old
);
assert_eq!(
HistoricalPage::parse(old)
.expect("old grammar")
.into_current(),
legacy
);
let forged = old.replace("vue.directive \"slots\"", "ui.slots-object");
assert!(
HistoricalPage::parse(&forged).is_err(),
"old header does not grant the typed opcode"
);
}