use alloc::vec::Vec;
use vize_l0::{Allocator, SourceRoot, Span, String};
use super::super::{Refusal, construct_as};
use crate::markup::document::{DocumentTokenKind, NativeDocument};
use crate::markup::{Component, Document};
const ORIGINAL: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/document/head-text.html"
));
fn envelope(head: &str, body: &str) -> String {
let mut source = String::from("<!DOCTYPE html><html><head>");
source.push_str(head);
source.push_str("</head><body>");
source.push_str(body);
source.push_str("</body></html>");
source
}
#[test]
fn head_text_ancestry_borrows_the_original_single_lexer_owner() {
let arena = Allocator::default();
let owner = NativeDocument::lex_in(&arena, SourceRoot::new(ORIGINAL).expect("small source"));
let before: Vec<_> = owner
.tokens()
.map(|token| (token.kind(), token.span()))
.collect();
let tree = owner.html_structure().expect("explicit head text");
let elements: Vec<_> = tree.elements().collect();
assert_eq!(
elements.iter().map(|node| node.name()).collect::<Vec<_>>(),
["html", "head", "meta", "title", "style", "body", "div"]
);
let head = elements.get(1).expect("original head");
assert_eq!(
head.children().map(|node| node.name()).collect::<Vec<_>>(),
["meta", "title", "style"]
);
for (index, opening, closing) in [
(3, "<TITLE data-label=\"&\">", "</TiTlE>"),
(4, "<STYLE>", "</sTyLe>"),
] {
let node = elements.get(index).expect("original head child");
assert!(core::ptr::eq(node.owner(), &owner));
assert_eq!(node.opening(), opening);
assert_eq!(
node.closing_span().expect("authored end").slice(ORIGINAL),
closing
);
assert_eq!(
node.parent().expect("actual head").opening_span(),
head.opening_span()
);
assert_eq!(node.children().count(), 0);
assert_eq!(
node.child_nodes().unwrap_err(),
Refusal::UnsupportedNodeParent(node.opening_span())
);
let offset = ORIGINAL.find(opening).expect("authored opening");
assert!(core::ptr::eq(
node.opening().as_ptr(),
ORIGINAL[offset..].as_ptr()
));
}
assert_eq!(
owner
.tokens()
.map(|token| (token.kind(), token.span()))
.collect::<Vec<_>>(),
before
);
let entities: Vec<_> = owner
.tokens()
.filter(|token| token.kind() == DocumentTokenKind::TextEntity)
.collect();
assert_eq!(entities.len(), 1);
assert_eq!(entities[0].source(), Some("&"));
let mut decoded = String::default();
entities[0]
.decoded_entity()
.expect("title entity")
.for_each(|ch| decoded.push(ch));
assert_eq!(decoded, "&");
assert!(
owner
.tokens()
.any(|token| token.kind() == DocumentTokenKind::Text
&& token.source() == Some("p::before { content: \"& {{ literal }} <div>\"; }"))
);
}
#[test]
fn explicit_unslashed_head_text_preserves_both_profiles_name_rules() {
let source = envelope(
"<title>a & <b></title><style>& <b></style>",
"<span></span>",
);
let arena = Allocator::default();
let owner = NativeDocument::lex_in(&arena, SourceRoot::new(&source).expect("small source"));
for tree in [
construct_as::<Document>(&owner),
construct_as::<Component>(&owner),
] {
assert_eq!(
tree.expect("explicit nesting")
.elements()
.map(|node| node.name())
.collect::<Vec<_>>(),
["html", "head", "title", "style", "body", "span"]
);
}
let source = envelope("<TITLE></TITLE>", "");
let arena = Allocator::default();
let owner = NativeDocument::lex_in(&arena, SourceRoot::new(&source).expect("small source"));
assert!(construct_as::<Document>(&owner).is_ok());
assert_eq!(
construct_as::<Component>(&owner).unwrap_err(),
Refusal::UnsupportedElement(Span::new(28, 33))
);
}
#[test]
fn slashed_starts_and_body_raw_text_refuse_instead_of_borrowing_html_ownership() {
for (head, body) in [
("<title/>x</title>", ""),
("<style/>x</style>", ""),
("", "<title>x</title>"),
("", "<style>x</style>"),
] {
let source = envelope(head, body);
let arena = Allocator::default();
let owner = NativeDocument::lex_in(&arena, SourceRoot::new(&source).expect("small source"));
let raw = if source.contains("title") {
"title"
} else {
"style"
};
let start = source.find(raw).expect("authored element") as u32;
assert_eq!(
owner.html_structure().unwrap_err(),
Refusal::UnsupportedElement(Span::new(start, start + raw.len() as u32)),
"{source}"
);
}
}
#[test]
fn missing_head_text_ends_and_vue_interpolation_cannot_mint_completion() {
for head in ["<title>unfinished", "<style>unfinished"] {
let source = envelope(head, "");
let arena = Allocator::default();
let owner = NativeDocument::lex_in(&arena, SourceRoot::new(&source).expect("small source"));
assert!(owner.normal_completion().is_ok());
assert_eq!(
owner.html_structure().unwrap_err(),
Refusal::PendingStructure
);
}
let source = envelope("<title>{{ value }}</title>", "");
let arena = Allocator::default();
let owner = NativeDocument::lex_in(&arena, SourceRoot::new(&source).expect("small source"));
assert_eq!(owner.html_structure().unwrap_err(), Refusal::VueSyntax);
}
#[test]
fn other_head_and_foreign_features_keep_their_existing_refusal() {
for head in [
"<script></script>",
"<svg></svg>",
"<b></b>",
"<template></template>",
] {
let source = envelope(head, "");
let arena = Allocator::default();
let owner = NativeDocument::lex_in(&arena, SourceRoot::new(&source).expect("small source"));
assert!(
matches!(owner.html_structure(), Err(Refusal::UnsupportedElement(_))),
"{source}"
);
}
}