tests: migrate most tests from test_templater.rs

test_templater_alias(), test_templater_alias_override(), and
test_templater_bad_alias_decl() aren't moved since they also test config loading
and error formatting. The first test in test_templater_parse_error() is left for
the same reason. test_templater_upper_lower() depends on the commit templater.
This commit is contained in:
Yuya Nishihara 2023-10-26 16:41:29 +09:00
parent 957360dc83
commit 2874a69faf
2 changed files with 660 additions and 619 deletions

View file

@ -940,6 +940,14 @@ mod tests {
}
}
fn new_signature(name: &str, email: &str) -> Signature {
Signature {
name: name.to_owned(),
email: email.to_owned(),
timestamp: new_timestamp(0, 0),
}
}
fn new_timestamp(msec: i64, tz_offset: i32) -> Timestamp {
Timestamp {
timestamp: MillisSinceEpoch(msec),
@ -947,6 +955,436 @@ mod tests {
}
}
#[test]
fn test_parsed_tree() {
let mut env = TestTemplateEnv::default();
env.add_keyword("divergent", |language| {
language.wrap_boolean(Literal(false))
});
env.add_keyword("empty", |language| language.wrap_boolean(Literal(true)));
env.add_keyword("hello", |language| {
language.wrap_string(Literal("Hello".to_owned()))
});
// Empty
insta::assert_snapshot!(env.render_ok(r#" "#), @"");
// Single term with whitespace
insta::assert_snapshot!(env.render_ok(r#" hello.upper() "#), @"HELLO");
// Multiple terms
insta::assert_snapshot!(env.render_ok(r#" hello.upper() ++ true "#), @"HELLOtrue");
// Parenthesized single term
insta::assert_snapshot!(env.render_ok(r#"(hello.upper())"#), @"HELLO");
// Parenthesized multiple terms and concatenation
insta::assert_snapshot!(env.render_ok(r#"(hello.upper() ++ " ") ++ empty"#), @"HELLO true");
// Parenthesized "if" condition
insta::assert_snapshot!(env.render_ok(r#"if((divergent), "t", "f")"#), @"f");
// Parenthesized method chaining
insta::assert_snapshot!(env.render_ok(r#"(hello).upper()"#), @"HELLO");
}
#[test]
fn test_parse_error() {
let mut env = TestTemplateEnv::default();
env.add_keyword("description", |language| {
language.wrap_string(Literal("".to_owned()))
});
env.add_keyword("empty", |language| language.wrap_boolean(Literal(true)));
insta::assert_snapshot!(env.parse_err(r#"description ()"#), @r###"
--> 1:13
|
1 | description ()
| ^---
|
= expected EOI
"###);
insta::assert_snapshot!(env.parse_err(r#"foo"#), @r###"
--> 1:1
|
1 | foo
| ^-^
|
= Keyword "foo" doesn't exist
"###);
insta::assert_snapshot!(env.parse_err(r#"foo()"#), @r###"
--> 1:1
|
1 | foo()
| ^-^
|
= Function "foo" doesn't exist
"###);
insta::assert_snapshot!(env.parse_err(r#"false()"#), @r###"
--> 1:1
|
1 | false()
| ^---^
|
= Expected identifier
"###);
insta::assert_snapshot!(env.parse_err(r#"description.first_line().foo()"#), @r###"
--> 1:26
|
1 | description.first_line().foo()
| ^-^
|
= Method "foo" doesn't exist for type "String"
"###);
insta::assert_snapshot!(env.parse_err(r#"10000000000000000000"#), @r###"
--> 1:1
|
1 | 10000000000000000000
| ^------------------^
|
= Invalid integer literal: number too large to fit in target type
"###);
insta::assert_snapshot!(env.parse_err(r#"42.foo()"#), @r###"
--> 1:4
|
1 | 42.foo()
| ^-^
|
= Method "foo" doesn't exist for type "Integer"
"###);
insta::assert_snapshot!(env.parse_err(r#"(-empty)"#), @r###"
--> 1:2
|
1 | (-empty)
| ^---
|
= expected template
"###);
insta::assert_snapshot!(env.parse_err(r#"("foo" ++ "bar").baz()"#), @r###"
--> 1:18
|
1 | ("foo" ++ "bar").baz()
| ^-^
|
= Method "baz" doesn't exist for type "Template"
"###);
insta::assert_snapshot!(env.parse_err(r#"description.contains()"#), @r###"
--> 1:22
|
1 | description.contains()
| ^
|
= Function "contains": Expected 1 arguments
"###);
insta::assert_snapshot!(env.parse_err(r#"description.first_line("foo")"#), @r###"
--> 1:24
|
1 | description.first_line("foo")
| ^---^
|
= Function "first_line": Expected 0 arguments
"###);
insta::assert_snapshot!(env.parse_err(r#"label()"#), @r###"
--> 1:7
|
1 | label()
| ^
|
= Function "label": Expected 2 arguments
"###);
insta::assert_snapshot!(env.parse_err(r#"label("foo", "bar", "baz")"#), @r###"
--> 1:7
|
1 | label("foo", "bar", "baz")
| ^-----------------^
|
= Function "label": Expected 2 arguments
"###);
insta::assert_snapshot!(env.parse_err(r#"if()"#), @r###"
--> 1:4
|
1 | if()
| ^
|
= Function "if": Expected 2 to 3 arguments
"###);
insta::assert_snapshot!(env.parse_err(r#"if("foo", "bar", "baz", "quux")"#), @r###"
--> 1:4
|
1 | if("foo", "bar", "baz", "quux")
| ^-------------------------^
|
= Function "if": Expected 2 to 3 arguments
"###);
insta::assert_snapshot!(env.parse_err(r#"if(label("foo", "bar"), "baz")"#), @r###"
--> 1:4
|
1 | if(label("foo", "bar"), "baz")
| ^-----------------^
|
= Expected expression of type "Boolean"
"###);
insta::assert_snapshot!(env.parse_err(r#"|x| description"#), @r###"
--> 1:1
|
1 | |x| description
| ^-------------^
|
= Lambda cannot be defined here
"###);
}
#[test]
fn test_list_method() {
let mut env = TestTemplateEnv::default();
env.add_keyword("empty", |language| language.wrap_boolean(Literal(true)));
env.add_keyword("sep", |language| {
language.wrap_string(Literal("sep".to_owned()))
});
insta::assert_snapshot!(env.render_ok(r#""".lines().join("|")"#), @"");
insta::assert_snapshot!(env.render_ok(r#""a\nb\nc".lines().join("|")"#), @"a|b|c");
// Null separator
insta::assert_snapshot!(env.render_ok(r#""a\nb\nc".lines().join("\0")"#), @"a\0b\0c");
// Keyword as separator
insta::assert_snapshot!(
env.render_ok(r#""a\nb\nc".lines().join(sep.upper())"#),
@"aSEPbSEPc");
insta::assert_snapshot!(
env.render_ok(r#""a\nb\nc".lines().map(|s| s ++ s)"#),
@"aa bb cc");
// Global keyword in item template
insta::assert_snapshot!(
env.render_ok(r#""a\nb\nc".lines().map(|s| s ++ empty)"#),
@"atrue btrue ctrue");
// Override global keyword 'empty'
insta::assert_snapshot!(
env.render_ok(r#""a\nb\nc".lines().map(|empty| empty)"#),
@"a b c");
// Nested map operations
insta::assert_snapshot!(
env.render_ok(r#""a\nb\nc".lines().map(|s| "x\ny".lines().map(|t| s ++ t))"#),
@"ax ay bx by cx cy");
// Nested map/join operations
insta::assert_snapshot!(
env.render_ok(r#""a\nb\nc".lines().map(|s| "x\ny".lines().map(|t| s ++ t).join(",")).join(";")"#),
@"ax,ay;bx,by;cx,cy");
// Nested string operations
insta::assert_snapshot!(
env.render_ok(r#""!a\n!b\nc\nend".remove_suffix("end").lines().map(|s| s.remove_prefix("!"))"#),
@"a b c");
// Lambda expression in alias
env.add_alias("identity", "|x| x");
insta::assert_snapshot!(env.render_ok(r#""a\nb\nc".lines().map(identity)"#), @"a b c");
// Not a lambda expression
insta::assert_snapshot!(env.parse_err(r#""a".lines().map(empty)"#), @r###"
--> 1:17
|
1 | "a".lines().map(empty)
| ^---^
|
= Expected lambda expression
"###);
// Bad lambda parameter count
insta::assert_snapshot!(env.parse_err(r#""a".lines().map(|| "")"#), @r###"
--> 1:18
|
1 | "a".lines().map(|| "")
| ^
|
= Expected 1 lambda parameters
"###);
insta::assert_snapshot!(env.parse_err(r#""a".lines().map(|a, b| "")"#), @r###"
--> 1:18
|
1 | "a".lines().map(|a, b| "")
| ^--^
|
= Expected 1 lambda parameters
"###);
// Error in lambda expression
insta::assert_snapshot!(env.parse_err(r#""a".lines().map(|s| s.unknown())"#), @r###"
--> 1:23
|
1 | "a".lines().map(|s| s.unknown())
| ^-----^
|
= Method "unknown" doesn't exist for type "String"
"###);
// Error in lambda alias
env.add_alias("too_many_params", "|x, y| x");
insta::assert_snapshot!(env.parse_err(r#""a".lines().map(too_many_params)"#), @r###"
--> 1:17
|
1 | "a".lines().map(too_many_params)
| ^-------------^
|
= Alias "too_many_params" cannot be expanded
--> 1:2
|
1 | |x, y| x
| ^--^
|
= Expected 1 lambda parameters
"###);
}
#[test]
fn test_string_method() {
let mut env = TestTemplateEnv::default();
env.add_keyword("description", |language| {
language.wrap_string(Literal("description 1".to_owned()))
});
insta::assert_snapshot!(env.render_ok(r#""fooo".contains("foo")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""foo".contains("fooo")"#), @"false");
insta::assert_snapshot!(env.render_ok(r#"description.contains("description")"#), @"true");
insta::assert_snapshot!(
env.render_ok(r#""description 123".contains(description.first_line())"#),
@"true");
insta::assert_snapshot!(env.render_ok(r#""".first_line()"#), @"");
insta::assert_snapshot!(env.render_ok(r#""foo\nbar".first_line()"#), @"foo");
insta::assert_snapshot!(env.render_ok(r#""".lines()"#), @"");
insta::assert_snapshot!(env.render_ok(r#""a\nb\nc\n".lines()"#), @"a b c");
insta::assert_snapshot!(env.render_ok(r#""".starts_with("")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""everything".starts_with("")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""".starts_with("foo")"#), @"false");
insta::assert_snapshot!(env.render_ok(r#""foo".starts_with("foo")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""foobar".starts_with("foo")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""foobar".starts_with("bar")"#), @"false");
insta::assert_snapshot!(env.render_ok(r#""".ends_with("")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""everything".ends_with("")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""".ends_with("foo")"#), @"false");
insta::assert_snapshot!(env.render_ok(r#""foo".ends_with("foo")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""foobar".ends_with("foo")"#), @"false");
insta::assert_snapshot!(env.render_ok(r#""foobar".ends_with("bar")"#), @"true");
insta::assert_snapshot!(env.render_ok(r#""".remove_prefix("wip: ")"#), @"");
insta::assert_snapshot!(
env.render_ok(r#""wip: testing".remove_prefix("wip: ")"#),
@"testing");
insta::assert_snapshot!(
env.render_ok(r#""bar@my.example.com".remove_suffix("@other.example.com")"#),
@"bar@my.example.com");
insta::assert_snapshot!(
env.render_ok(r#""bar@other.example.com".remove_suffix("@other.example.com")"#),
@"bar");
insta::assert_snapshot!(env.render_ok(r#""foo".substr(0, 0)"#), @"");
insta::assert_snapshot!(env.render_ok(r#""foo".substr(0, 1)"#), @"f");
insta::assert_snapshot!(env.render_ok(r#""foo".substr(0, 99)"#), @"foo");
insta::assert_snapshot!(env.render_ok(r#""abcdef".substr(2, -1)"#), @"cde");
insta::assert_snapshot!(env.render_ok(r#""abcdef".substr(-3, 99)"#), @"def");
insta::assert_snapshot!(env.render_ok(r#""abc💩".substr(2, -1)"#), @"c");
insta::assert_snapshot!(env.render_ok(r#""abc💩".substr(-1, 99)"#), @"💩");
// ranges with end > start are empty
insta::assert_snapshot!(env.render_ok(r#""abcdef".substr(4, 2)"#), @"");
insta::assert_snapshot!(env.render_ok(r#""abcdef".substr(-2, -4)"#), @"");
}
#[test]
fn test_signature() {
let mut env = TestTemplateEnv::default();
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature("Test User", "test.user@example.com")))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <test.user@example.com>");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"Test User");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"test.user@example.com");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature(
"Another Test User",
"test.user@example.com",
)))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Another Test User <test.user@example.com>");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"Another Test User");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"test.user@example.com");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature(
"Test User",
"test.user@invalid@example.com",
)))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <test.user@invalid@example.com>");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"Test User");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"test.user@invalid@example.com");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature("Test User", "test.user")))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <test.user>");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"test.user");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature(
"Test User",
"test.user+tag@example.com",
)))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <test.user+tag@example.com>");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"test.user+tag@example.com");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user+tag");
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature("Test User", "x@y")))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <x@y>");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"x@y");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"x");
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature("", "test.user@example.com")))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"<test.user@example.com>");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"test.user@example.com");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature("Test User", "")))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"Test User");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"");
env.add_keyword("author", |language| {
language.wrap_signature(Literal(new_signature("", "")))
});
insta::assert_snapshot!(env.render_ok(r#"author"#), @"");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"");
}
#[test]
fn test_timestamp_method() {
let mut env = TestTemplateEnv::default();
@ -1008,6 +1446,152 @@ mod tests {
"###);
}
#[test]
fn test_fill_function() {
let mut env = TestTemplateEnv::default();
env.add_color("error", crossterm::style::Color::DarkRed);
insta::assert_snapshot!(
env.render_ok(r#"fill(20, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The quick fox jumps
over the lazy dog
"###);
// A low value will not chop words, but can chop a label by words
insta::assert_snapshot!(
env.render_ok(r#"fill(9, "Longlonglongword an some short words " ++
label("error", "longlonglongword and short words") ++
" back out\n")"#),
@r###"
Longlonglongword
an some
short
words
longlonglongword
and short
words
back out
"###);
// Filling to 0 means breaking at every word
insta::assert_snapshot!(
env.render_ok(r#"fill(0, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The
quick
fox
jumps
over
the
lazy
dog
"###);
// Filling to -0 is the same as 0
insta::assert_snapshot!(
env.render_ok(r#"fill(-0, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The
quick
fox
jumps
over
the
lazy
dog
"###);
// Filling to negatives are clamped to the same as zero
insta::assert_snapshot!(
env.render_ok(r#"fill(-10, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The
quick
fox
jumps
over
the
lazy
dog
"###);
// Word-wrap, then indent
insta::assert_snapshot!(
env.render_ok(r#""START marker to help insta\n" ++
indent(" ", fill(20, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n"))"#),
@r###"
START marker to help insta
The quick fox jumps
over the lazy dog
"###);
// Word-wrap indented (no special handling for leading spaces)
insta::assert_snapshot!(
env.render_ok(r#""START marker to help insta\n" ++
fill(20, indent(" ", "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n"))"#),
@r###"
START marker to help insta
The quick fox
jumps over the lazy
dog
"###);
}
#[test]
fn test_indent_function() {
let mut env = TestTemplateEnv::default();
env.add_color("error", crossterm::style::Color::DarkRed);
env.add_color("warning", crossterm::style::Color::DarkYellow);
env.add_color("hint", crossterm::style::Color::DarkCyan);
// Empty line shouldn't be indented. Not using insta here because we test
// whitespace existence.
assert_eq!(env.render_ok(r#"indent("__", "")"#), "");
assert_eq!(env.render_ok(r#"indent("__", "\n")"#), "\n");
assert_eq!(env.render_ok(r#"indent("__", "a\n\nb")"#), "__a\n\n__b");
// "\n" at end of labeled text
insta::assert_snapshot!(
env.render_ok(r#"indent("__", label("error", "a\n") ++ label("warning", "b\n"))"#),
@r###"
__a
__b
"###);
// "\n" in labeled text
insta::assert_snapshot!(
env.render_ok(r#"indent("__", label("error", "a") ++ label("warning", "b\nc"))"#),
@r###"
__ab
__c
"###);
// Labeled prefix + unlabeled content
insta::assert_snapshot!(
env.render_ok(r#"indent(label("error", "XX"), "a\nb\n")"#),
@r###"
XXa
XXb
"###);
// Nested indent, silly but works
insta::assert_snapshot!(
env.render_ok(r#"indent(label("hint", "A"),
label("warning", indent(label("hint", "B"),
label("error", "x\n") ++ "y")))"#),
@r###"
ABx
ABy
"###);
}
#[test]
fn test_label_function() {
let mut env = TestTemplateEnv::default();
@ -1030,4 +1614,80 @@ mod tests {
env.render_ok(r#"label(if(empty, "error", "warning"), "text")"#),
@"text");
}
#[test]
fn test_concat_function() {
let mut env = TestTemplateEnv::default();
env.add_keyword("empty", |language| language.wrap_boolean(Literal(true)));
env.add_keyword("hidden", |language| language.wrap_boolean(Literal(false)));
env.add_color("empty", crossterm::style::Color::DarkGreen);
env.add_color("error", crossterm::style::Color::DarkRed);
env.add_color("warning", crossterm::style::Color::DarkYellow);
insta::assert_snapshot!(env.render_ok(r#"concat()"#), @"");
insta::assert_snapshot!(
env.render_ok(r#"concat(hidden, empty)"#),
@"falsetrue");
insta::assert_snapshot!(
env.render_ok(r#"concat(label("error", ""), label("warning", "a"), "b")"#),
@"ab");
}
#[test]
fn test_separate_function() {
let mut env = TestTemplateEnv::default();
env.add_keyword("description", |language| {
language.wrap_string(Literal("".to_owned()))
});
env.add_keyword("empty", |language| language.wrap_boolean(Literal(true)));
env.add_keyword("hidden", |language| language.wrap_boolean(Literal(false)));
env.add_color("empty", crossterm::style::Color::DarkGreen);
env.add_color("error", crossterm::style::Color::DarkRed);
env.add_color("warning", crossterm::style::Color::DarkYellow);
insta::assert_snapshot!(env.render_ok(r#"separate(" ")"#), @"");
insta::assert_snapshot!(env.render_ok(r#"separate(" ", "")"#), @"");
insta::assert_snapshot!(env.render_ok(r#"separate(" ", "a")"#), @"a");
insta::assert_snapshot!(env.render_ok(r#"separate(" ", "a", "b")"#), @"a b");
insta::assert_snapshot!(env.render_ok(r#"separate(" ", "a", "", "b")"#), @"a b");
insta::assert_snapshot!(env.render_ok(r#"separate(" ", "a", "b", "")"#), @"a b");
insta::assert_snapshot!(env.render_ok(r#"separate(" ", "", "a", "b")"#), @"a b");
// Labeled
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", label("error", ""), label("warning", "a"), "b")"#),
@"a b");
// List template
insta::assert_snapshot!(env.render_ok(r#"separate(" ", "a", ("" ++ ""))"#), @"a");
insta::assert_snapshot!(env.render_ok(r#"separate(" ", "a", ("" ++ "b"))"#), @"a b");
// Nested separate
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", "a", separate("|", "", ""))"#), @"a");
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", "a", separate("|", "b", ""))"#), @"a b");
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", "a", separate("|", "b", "c"))"#), @"a b|c");
// Conditional template
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", "a", if(true, ""))"#), @"a");
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", "a", if(true, "", "f"))"#), @"a");
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", "a", if(false, "t", ""))"#), @"a");
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", "a", if(true, "t", "f"))"#), @"a t");
// Separate keywords
insta::assert_snapshot!(
env.render_ok(r#"separate(" ", hidden, description, empty)"#),
@"false true");
// Keyword as separator
insta::assert_snapshot!(
env.render_ok(r#"separate(hidden, "X", "Y", "Z")"#),
@"XfalseYfalseZ");
}
}

View file

@ -18,35 +18,6 @@ use crate::common::TestEnvironment;
pub mod common;
#[test]
fn test_templater_parsed_tree() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render = |template| get_template_output(&test_env, &repo_path, "@-", template);
// Empty
insta::assert_snapshot!(render(r#" "#), @"");
// Single term with whitespace
insta::assert_snapshot!(render(r#" commit_id.short() "#), @"000000000000");
// Multiple terms
insta::assert_snapshot!(render(r#" commit_id.short() ++ empty "#), @"000000000000true");
// Parenthesized single term
insta::assert_snapshot!(render(r#"(commit_id.short())"#), @"000000000000");
// Parenthesized multiple terms and concatenation
insta::assert_snapshot!(render(r#"(commit_id.short() ++ " ") ++ empty"#), @"000000000000 true");
// Parenthesized "if" condition
insta::assert_snapshot!(render(r#"if((divergent), "t", "f")"#), @"f");
// Parenthesized method chaining
insta::assert_snapshot!(render(r#"(commit_id).short()"#), @"000000000000");
}
#[test]
fn test_templater_parse_error() {
let test_env = TestEnvironment::default();
@ -62,596 +33,6 @@ fn test_templater_parse_error() {
|
= expected EOI
"###);
insta::assert_snapshot!(render_err(r#"foo"#), @r###"
Error: Failed to parse template: --> 1:1
|
1 | foo
| ^-^
|
= Keyword "foo" doesn't exist
"###);
insta::assert_snapshot!(render_err(r#"foo()"#), @r###"
Error: Failed to parse template: --> 1:1
|
1 | foo()
| ^-^
|
= Function "foo" doesn't exist
"###);
insta::assert_snapshot!(render_err(r#"false()"#), @r###"
Error: Failed to parse template: --> 1:1
|
1 | false()
| ^---^
|
= Expected identifier
"###);
insta::assert_snapshot!(render_err(r#"description.first_line().foo()"#), @r###"
Error: Failed to parse template: --> 1:26
|
1 | description.first_line().foo()
| ^-^
|
= Method "foo" doesn't exist for type "String"
"###);
insta::assert_snapshot!(render_err(r#"10000000000000000000"#), @r###"
Error: Failed to parse template: --> 1:1
|
1 | 10000000000000000000
| ^------------------^
|
= Invalid integer literal: number too large to fit in target type
"###);
insta::assert_snapshot!(render_err(r#"42.foo()"#), @r###"
Error: Failed to parse template: --> 1:4
|
1 | 42.foo()
| ^-^
|
= Method "foo" doesn't exist for type "Integer"
"###);
insta::assert_snapshot!(render_err(r#"(-empty)"#), @r###"
Error: Failed to parse template: --> 1:2
|
1 | (-empty)
| ^---
|
= expected template
"###);
insta::assert_snapshot!(render_err(r#"("foo" ++ "bar").baz()"#), @r###"
Error: Failed to parse template: --> 1:18
|
1 | ("foo" ++ "bar").baz()
| ^-^
|
= Method "baz" doesn't exist for type "Template"
"###);
insta::assert_snapshot!(render_err(r#"description.contains()"#), @r###"
Error: Failed to parse template: --> 1:22
|
1 | description.contains()
| ^
|
= Function "contains": Expected 1 arguments
"###);
insta::assert_snapshot!(render_err(r#"description.first_line("foo")"#), @r###"
Error: Failed to parse template: --> 1:24
|
1 | description.first_line("foo")
| ^---^
|
= Function "first_line": Expected 0 arguments
"###);
insta::assert_snapshot!(render_err(r#"label()"#), @r###"
Error: Failed to parse template: --> 1:7
|
1 | label()
| ^
|
= Function "label": Expected 2 arguments
"###);
insta::assert_snapshot!(render_err(r#"label("foo", "bar", "baz")"#), @r###"
Error: Failed to parse template: --> 1:7
|
1 | label("foo", "bar", "baz")
| ^-----------------^
|
= Function "label": Expected 2 arguments
"###);
insta::assert_snapshot!(render_err(r#"if()"#), @r###"
Error: Failed to parse template: --> 1:4
|
1 | if()
| ^
|
= Function "if": Expected 2 to 3 arguments
"###);
insta::assert_snapshot!(render_err(r#"if("foo", "bar", "baz", "quux")"#), @r###"
Error: Failed to parse template: --> 1:4
|
1 | if("foo", "bar", "baz", "quux")
| ^-------------------------^
|
= Function "if": Expected 2 to 3 arguments
"###);
insta::assert_snapshot!(render_err(r#"if(label("foo", "bar"), "baz")"#), @r###"
Error: Failed to parse template: --> 1:4
|
1 | if(label("foo", "bar"), "baz")
| ^-----------------^
|
= Expected expression of type "Boolean"
"###);
insta::assert_snapshot!(render_err(r#"|x| description"#), @r###"
Error: Failed to parse template: --> 1:1
|
1 | |x| description
| ^-------------^
|
= Lambda cannot be defined here
"###);
}
#[test]
fn test_templater_list_method() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render = |template| get_template_output(&test_env, &repo_path, "@-", template);
let render_err = |template| test_env.jj_cmd_failure(&repo_path, &["log", "-T", template]);
test_env.add_config(
r###"
[template-aliases]
'identity' = '|x| x'
'too_many_params' = '|x, y| x'
"###,
);
insta::assert_snapshot!(render(r#""".lines().join("|")"#), @"");
insta::assert_snapshot!(render(r#""a\nb\nc".lines().join("|")"#), @"a|b|c");
// Null separator
insta::assert_snapshot!(render(r#""a\nb\nc".lines().join("\0")"#), @"a\0b\0c");
// Keyword as separator
insta::assert_snapshot!(render(r#""a\nb\nc".lines().join(commit_id.short(2))"#), @"a00b00c");
insta::assert_snapshot!(render(r#""a\nb\nc".lines().map(|s| s ++ s)"#), @"aa bb cc");
// Global keyword in item template
insta::assert_snapshot!(
render(r#""a\nb\nc".lines().map(|s| s ++ empty)"#), @"atrue btrue ctrue");
// Override global keyword 'empty'
insta::assert_snapshot!(
render(r#""a\nb\nc".lines().map(|empty| empty)"#), @"a b c");
// Nested map operations
insta::assert_snapshot!(
render(r#""a\nb\nc".lines().map(|s| "x\ny".lines().map(|t| s ++ t))"#),
@"ax ay bx by cx cy");
// Nested map/join operations
insta::assert_snapshot!(
render(r#""a\nb\nc".lines().map(|s| "x\ny".lines().map(|t| s ++ t).join(",")).join(";")"#),
@"ax,ay;bx,by;cx,cy");
// Nested string operations
insta::assert_snapshot!(render(r#""!a\n!b\nc\nend".remove_suffix("end").lines().map(|s| s.remove_prefix("!"))"#), @"a b c");
// Lambda expression in alias
insta::assert_snapshot!(render(r#""a\nb\nc".lines().map(identity)"#), @"a b c");
// Not a lambda expression
insta::assert_snapshot!(render_err(r#""a".lines().map(empty)"#), @r###"
Error: Failed to parse template: --> 1:17
|
1 | "a".lines().map(empty)
| ^---^
|
= Expected lambda expression
"###);
// Bad lambda parameter count
insta::assert_snapshot!(render_err(r#""a".lines().map(|| "")"#), @r###"
Error: Failed to parse template: --> 1:18
|
1 | "a".lines().map(|| "")
| ^
|
= Expected 1 lambda parameters
"###);
insta::assert_snapshot!(render_err(r#""a".lines().map(|a, b| "")"#), @r###"
Error: Failed to parse template: --> 1:18
|
1 | "a".lines().map(|a, b| "")
| ^--^
|
= Expected 1 lambda parameters
"###);
// Error in lambda expression
insta::assert_snapshot!(render_err(r#""a".lines().map(|s| s.unknown())"#), @r###"
Error: Failed to parse template: --> 1:23
|
1 | "a".lines().map(|s| s.unknown())
| ^-----^
|
= Method "unknown" doesn't exist for type "String"
"###);
// Error in lambda alias
insta::assert_snapshot!(render_err(r#""a".lines().map(too_many_params)"#), @r###"
Error: Failed to parse template: --> 1:17
|
1 | "a".lines().map(too_many_params)
| ^-------------^
|
= Alias "too_many_params" cannot be expanded
--> 1:2
|
1 | |x, y| x
| ^--^
|
= Expected 1 lambda parameters
"###);
}
#[test]
fn test_templater_string_method() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["commit", "-m=description 1"]);
let render = |template| get_template_output(&test_env, &repo_path, "@-", template);
insta::assert_snapshot!(render(r#""fooo".contains("foo")"#), @"true");
insta::assert_snapshot!(render(r#""foo".contains("fooo")"#), @"false");
insta::assert_snapshot!(render(r#"description.contains("description")"#), @"true");
insta::assert_snapshot!(
render(r#""description 123".contains(description.first_line())"#), @"true");
insta::assert_snapshot!(render(r#""".first_line()"#), @"");
insta::assert_snapshot!(render(r#""foo\nbar".first_line()"#), @"foo");
insta::assert_snapshot!(render(r#""".lines()"#), @"");
insta::assert_snapshot!(render(r#""a\nb\nc\n".lines()"#), @"a b c");
insta::assert_snapshot!(render(r#""".starts_with("")"#), @"true");
insta::assert_snapshot!(render(r#""everything".starts_with("")"#), @"true");
insta::assert_snapshot!(render(r#""".starts_with("foo")"#), @"false");
insta::assert_snapshot!(render(r#""foo".starts_with("foo")"#), @"true");
insta::assert_snapshot!(render(r#""foobar".starts_with("foo")"#), @"true");
insta::assert_snapshot!(render(r#""foobar".starts_with("bar")"#), @"false");
insta::assert_snapshot!(render(r#""".ends_with("")"#), @"true");
insta::assert_snapshot!(render(r#""everything".ends_with("")"#), @"true");
insta::assert_snapshot!(render(r#""".ends_with("foo")"#), @"false");
insta::assert_snapshot!(render(r#""foo".ends_with("foo")"#), @"true");
insta::assert_snapshot!(render(r#""foobar".ends_with("foo")"#), @"false");
insta::assert_snapshot!(render(r#""foobar".ends_with("bar")"#), @"true");
insta::assert_snapshot!(render(r#""".remove_prefix("wip: ")"#), @"");
insta::assert_snapshot!(render(r#""wip: testing".remove_prefix("wip: ")"#), @"testing");
insta::assert_snapshot!(render(r#""bar@my.example.com".remove_suffix("@other.example.com")"#), @"bar@my.example.com");
insta::assert_snapshot!(render(r#""bar@other.example.com".remove_suffix("@other.example.com")"#), @"bar");
insta::assert_snapshot!(render(r#""foo".substr(0, 0)"#), @"");
insta::assert_snapshot!(render(r#""foo".substr(0, 1)"#), @"f");
insta::assert_snapshot!(render(r#""foo".substr(0, 99)"#), @"foo");
insta::assert_snapshot!(render(r#""abcdef".substr(2, -1)"#), @"cde");
insta::assert_snapshot!(render(r#""abcdef".substr(-3, 99)"#), @"def");
insta::assert_snapshot!(render(r#""abc💩".substr(2, -1)"#), @"c");
insta::assert_snapshot!(render(r#""abc💩".substr(-1, 99)"#), @"💩");
// ranges with end > start are empty
insta::assert_snapshot!(render(r#""abcdef".substr(4, 2)"#), @"");
insta::assert_snapshot!(render(r#""abcdef".substr(-2, -4)"#), @"");
}
#[test]
fn test_templater_signature() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render = |template| get_template_output(&test_env, &repo_path, "@", template);
test_env.jj_cmd_ok(&repo_path, &["new"]);
insta::assert_snapshot!(render(r#"author"#), @"Test User <test.user@example.com>");
insta::assert_snapshot!(render(r#"author.name()"#), @"Test User");
insta::assert_snapshot!(render(r#"author.email()"#), @"test.user@example.com");
insta::assert_snapshot!(render(r#"author.username()"#), @"test.user");
test_env.jj_cmd_ok(
&repo_path,
&["--config-toml=user.name='Another Test User'", "new"],
);
insta::assert_snapshot!(render(r#"author"#), @"Another Test User <test.user@example.com>");
insta::assert_snapshot!(render(r#"author.name()"#), @"Another Test User");
insta::assert_snapshot!(render(r#"author.email()"#), @"test.user@example.com");
insta::assert_snapshot!(render(r#"author.username()"#), @"test.user");
test_env.jj_cmd_ok(
&repo_path,
&[
"--config-toml=user.email='test.user@invalid@example.com'",
"new",
],
);
insta::assert_snapshot!(render(r#"author"#), @"Test User <test.user@invalid@example.com>");
insta::assert_snapshot!(render(r#"author.name()"#), @"Test User");
insta::assert_snapshot!(render(r#"author.email()"#), @"test.user@invalid@example.com");
insta::assert_snapshot!(render(r#"author.username()"#), @"test.user");
test_env.jj_cmd_ok(&repo_path, &["--config-toml=user.email='test.user'", "new"]);
insta::assert_snapshot!(render(r#"author"#), @"Test User <test.user>");
insta::assert_snapshot!(render(r#"author.email()"#), @"test.user");
insta::assert_snapshot!(render(r#"author.username()"#), @"test.user");
test_env.jj_cmd_ok(
&repo_path,
&[
"--config-toml=user.email='test.user+tag@example.com'",
"new",
],
);
insta::assert_snapshot!(render(r#"author"#), @"Test User <test.user+tag@example.com>");
insta::assert_snapshot!(render(r#"author.email()"#), @"test.user+tag@example.com");
insta::assert_snapshot!(render(r#"author.username()"#), @"test.user+tag");
test_env.jj_cmd_ok(&repo_path, &["--config-toml=user.email='x@y'", "new"]);
insta::assert_snapshot!(render(r#"author"#), @"Test User <x@y>");
insta::assert_snapshot!(render(r#"author.email()"#), @"x@y");
insta::assert_snapshot!(render(r#"author.username()"#), @"x");
test_env.jj_cmd_ok(&repo_path, &["--config-toml=user.name=''", "new"]);
insta::assert_snapshot!(render(r#"author"#), @"<test.user@example.com>");
insta::assert_snapshot!(render(r#"author.name()"#), @"");
insta::assert_snapshot!(render(r#"author.email()"#), @"test.user@example.com");
insta::assert_snapshot!(render(r#"author.username()"#), @"test.user");
test_env.jj_cmd_ok(&repo_path, &["--config-toml=user.email=''", "new"]);
insta::assert_snapshot!(render(r#"author"#), @"Test User");
insta::assert_snapshot!(render(r#"author.name()"#), @"Test User");
insta::assert_snapshot!(render(r#"author.email()"#), @"");
insta::assert_snapshot!(render(r#"author.username()"#), @"");
test_env.jj_cmd_ok(
&repo_path,
&[
"--config-toml=user.email=''",
"--config-toml=user.name=''",
"new",
],
);
insta::assert_snapshot!(render(r#"author"#), @"");
insta::assert_snapshot!(render(r#"author.name()"#), @"");
insta::assert_snapshot!(render(r#"author.email()"#), @"");
insta::assert_snapshot!(render(r#"author.username()"#), @"");
}
#[test]
fn test_templater_fill_function() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render = |template| get_colored_template_output(&test_env, &repo_path, "@-", template);
insta::assert_snapshot!(
render(r#"fill(20, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The quick fox jumps
over the lazy dog
"###);
// A low value will not chop words, but can chop a label by words
insta::assert_snapshot!(
render(r#"fill(9, "Longlonglongword an some short words " ++
label("error", "longlonglongword and short words") ++ " back out\n")"#),
@r###"
Longlonglongword
an some
short
words
longlonglongword
and short
words
back out
"###);
// Filling to 0 means breaking at every word
insta::assert_snapshot!(
render(r#"fill(0, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The
quick
fox
jumps
over
the
lazy
dog
"###);
// Filling to -0 is the same as 0
insta::assert_snapshot!(
render(r#"fill(-0, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The
quick
fox
jumps
over
the
lazy
dog
"###);
// Filling to negatives are clamped to the same as zero
insta::assert_snapshot!(
render(r#"fill(-10, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The
quick
fox
jumps
over
the
lazy
dog
"###);
// Word-wrap, then indent
insta::assert_snapshot!(
render(r#""START marker to help insta\n" ++
indent(" ", fill(20, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n"))"#),
@r###"
START marker to help insta
The quick fox jumps
over the lazy dog
"###);
// Word-wrap indented (no special handling for leading spaces)
insta::assert_snapshot!(
render(r#""START marker to help insta\n" ++
fill(20, indent(" ", "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n"))"#),
@r###"
START marker to help insta
The quick fox
jumps over the lazy
dog
"###);
}
#[test]
fn test_templater_indent_function() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render = |template| get_colored_template_output(&test_env, &repo_path, "@-", template);
// Empty line shouldn't be indented. Not using insta here because we test
// whitespace existence.
assert_eq!(render(r#"indent("__", "")"#), "");
assert_eq!(render(r#"indent("__", "\n")"#), "\n");
assert_eq!(render(r#"indent("__", "a\n\nb")"#), "__a\n\n__b");
// "\n" at end of labeled text
insta::assert_snapshot!(
render(r#"indent("__", label("error", "a\n") ++ label("warning", "b\n"))"#),
@r###"
__a
__b
"###);
// "\n" in labeled text
insta::assert_snapshot!(
render(r#"indent("__", label("error", "a") ++ label("warning", "b\nc"))"#),
@r###"
__ab
__c
"###);
// Labeled prefix + unlabeled content
insta::assert_snapshot!(
render(r#"indent(label("error", "XX"), "a\nb\n")"#),
@r###"
XXa
XXb
"###);
// Nested indent, silly but works
insta::assert_snapshot!(
render(r#"indent(label("hint", "A"),
label("warning", indent(label("hint", "B"),
label("error", "x\n") ++ "y")))"#),
@r###"
ABx
ABy
"###);
}
#[test]
fn test_templater_concat_function() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render = |template| get_colored_template_output(&test_env, &repo_path, "@-", template);
insta::assert_snapshot!(render(r#"concat()"#), @"");
insta::assert_snapshot!(render(r#"concat(hidden, empty)"#), @"falsetrue");
insta::assert_snapshot!(
render(r#"concat(label("error", ""), label("warning", "a"), "b")"#),
@"ab");
}
#[test]
fn test_templater_separate_function() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render = |template| get_colored_template_output(&test_env, &repo_path, "@-", template);
insta::assert_snapshot!(render(r#"separate(" ")"#), @"");
insta::assert_snapshot!(render(r#"separate(" ", "")"#), @"");
insta::assert_snapshot!(render(r#"separate(" ", "a")"#), @"a");
insta::assert_snapshot!(render(r#"separate(" ", "a", "b")"#), @"a b");
insta::assert_snapshot!(render(r#"separate(" ", "a", "", "b")"#), @"a b");
insta::assert_snapshot!(render(r#"separate(" ", "a", "b", "")"#), @"a b");
insta::assert_snapshot!(render(r#"separate(" ", "", "a", "b")"#), @"a b");
// Labeled
insta::assert_snapshot!(
render(r#"separate(" ", label("error", ""), label("warning", "a"), "b")"#),
@"a b");
// List template
insta::assert_snapshot!(render(r#"separate(" ", "a", ("" ++ ""))"#), @"a");
insta::assert_snapshot!(render(r#"separate(" ", "a", ("" ++ "b"))"#), @"a b");
// Nested separate
insta::assert_snapshot!(
render(r#"separate(" ", "a", separate("|", "", ""))"#), @"a");
insta::assert_snapshot!(
render(r#"separate(" ", "a", separate("|", "b", ""))"#), @"a b");
insta::assert_snapshot!(
render(r#"separate(" ", "a", separate("|", "b", "c"))"#), @"a b|c");
// Conditional template
insta::assert_snapshot!(
render(r#"separate(" ", "a", if(true, ""))"#), @"a");
insta::assert_snapshot!(
render(r#"separate(" ", "a", if(true, "", "f"))"#), @"a");
insta::assert_snapshot!(
render(r#"separate(" ", "a", if(false, "t", ""))"#), @"a");
insta::assert_snapshot!(
render(r#"separate(" ", "a", if(true, "t", "f"))"#), @"a t");
// Separate keywords
insta::assert_snapshot!(
render(r#"separate(" ", hidden, description, empty)"#), @"false true");
// Keyword as separator
insta::assert_snapshot!(
render(r#"separate(hidden, "X", "Y", "Z")"#), @"XfalseYfalseZ");
}
#[test]