use indexmap::IndexMap;
use super::{FormatOptions, MAX_FORMAT_DEPTH, format, format_value, format_with};
use crate::ast::{Function, Node, NodeType, PathSegment, Value, Variable};
fn tag(name: &str, attributes: &[(&str, Value)]) -> Node<'static> {
let mut map = IndexMap::new();
for (key, value) in attributes {
map.insert((*key).to_owned(), value.clone());
}
Node::with(NodeType::Tag, map, Vec::new(), Some(name.to_owned()))
}
#[test]
fn a_null_value_formats_as_nothing() {
assert_eq!(format_value(&Value::Null), "");
}
#[test]
fn a_variable_reprints_each_step_in_its_shortest_spelling() {
let variable = Value::Variable(Variable::new(vec![
PathSegment::Key("gates".to_owned()),
PathSegment::Key("a-b".to_owned()),
PathSegment::Key("with space".to_owned()),
PathSegment::Index(5.0),
]));
assert_eq!(format_value(&variable), "$gates.a-b[\"with space\"][5]");
}
#[test]
fn a_function_drops_its_parameter_names() {
let mut parameters = IndexMap::new();
parameters.insert("0".to_owned(), Value::String("test".to_owned()));
parameters.insert("x".to_owned(), Value::Number(1.0));
let function = Value::Function(Function::new("default".to_owned(), parameters));
assert_eq!(format_value(&function), "default(\"test\", 1)");
}
#[test]
fn a_hash_key_is_quoted_only_when_it_is_not_an_identifier() {
let mut inner = IndexMap::new();
inner.insert("with space".to_owned(), Value::Number(5.0));
let mut outer = IndexMap::new();
outer.insert("e".to_owned(), Value::Hash(inner));
let node = tag("x", &[("b", Value::Hash(outer))]);
assert_eq!(format(&node).trim(), "{% x b={e: {\"with space\": 5}} /%}");
}
#[test]
fn a_string_attribute_is_json_encoded() {
let node = tag(
"x",
&[("a", Value::String("quote \" slash \\ tab \t".to_owned()))],
);
assert_eq!(
format(&node).trim(),
"{% x a=\"quote \\\" slash \\\\ tab \\t\" /%}"
);
}
#[test]
fn an_id_attribute_contracts_only_when_its_value_is_an_identifier() {
let identifier = tag("x", &[("id", Value::String("intro".to_owned()))]);
assert_eq!(format(&identifier).trim(), "{% x #intro /%}");
let with_space = tag("x", &[("id", Value::String("in tro".to_owned()))]);
assert_eq!(format(&with_space).trim(), "{% x id=\"in tro\" /%}");
}
#[test]
fn a_class_whose_name_is_not_an_identifier_prints_the_whole_hash() {
let mut classes = IndexMap::new();
classes.insert("ok".to_owned(), Value::Boolean(true));
classes.insert("not ok".to_owned(), Value::Boolean(true));
let node = tag("x", &[("class", Value::Hash(classes))]);
assert_eq!(
format(&node).trim(),
"{% x .ok not ok={ok: true, \"not ok\": true} /%}"
);
}
#[test]
fn a_class_attribute_that_is_not_a_hash_stays_an_ordinary_attribute() {
let node = tag(
"x",
&[("class", Value::String("class with space".to_owned()))],
);
assert_eq!(format(&node).trim(), "{% x class=\"class with space\" /%}");
}
#[test]
fn the_tag_opening_width_is_a_setting() {
let node = tag(
"tag",
&[
("a", Value::Boolean(true)),
(
"b",
Value::String("My very long text well over 80 characters in total".to_owned()),
),
],
);
assert!(format(&node).contains('\n'));
let wide = FormatOptions::new().max_tag_opening_width(usize::MAX);
assert_eq!(format_with(&node, &wide).lines().count(), 1);
}
#[test]
fn a_heading_level_is_bounded_rather_than_allocated() {
let mut node = Node::new(NodeType::Heading);
node.set("level", Value::Number(1e30));
let formatted = format(&node);
assert_eq!(formatted.trim().len(), super::MAX_HEADING_LEVEL);
for level in [Value::Number(0.0), Value::Boolean(true), Value::Null] {
let mut node = Node::new(NodeType::Heading);
node.set("level", level);
assert_eq!(format(&node), "# \n");
}
}
#[test]
fn a_deeply_nested_tree_formats_without_exhausting_the_stack() {
let mut node = Node::new(NodeType::Paragraph);
for _ in 0..50_000 {
node = Node::with(
NodeType::Tag,
IndexMap::new(),
vec![node],
Some("a".to_owned()),
);
}
let formatted = format(&node);
assert_eq!(formatted.matches("{% a %}").count(), MAX_FORMAT_DEPTH - 1);
assert_eq!(formatted.matches("{% /a %}").count(), MAX_FORMAT_DEPTH - 1);
assert_eq!(formatted.matches("{% a /%}").count(), 1);
}
#[test]
fn a_deeply_nested_value_formats_without_exhausting_the_stack() {
let mut value = Value::Number(1.0);
for _ in 0..MAX_FORMAT_DEPTH * 4 {
value = Value::Array(vec![value]);
}
let node = tag("x", &[("a", value)]);
let formatted = format(&node);
assert_eq!(formatted.matches('[').count(), MAX_FORMAT_DEPTH);
assert_eq!(formatted.matches(']').count(), MAX_FORMAT_DEPTH);
assert!(formatted.contains("a=["));
}
#[test]
fn an_error_node_prints_nothing() {
assert_eq!(format(&Node::new(NodeType::Error)), "");
assert_eq!(format(&Node::new(NodeType::Node)), "");
}