mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 07:20:55 +00:00
Add tests for if (not) defined
feature
This commit is contained in:
parent
0372dac003
commit
fcf1a97d9d
@ -8,98 +8,106 @@ use similar::{Algorithm, ChangeTag, TextDiffConfig};
|
||||
|
||||
use crate::build_template;
|
||||
|
||||
// This function makes it much easier to compare expected code by adding the wrapping around
|
||||
// the code we want to check.
|
||||
#[track_caller]
|
||||
fn compare(jinja: &str, expected: &str, fields: &[(&str, &str)], size_hint: usize) {
|
||||
let jinja = format!(
|
||||
r##"#[template(source = {jinja:?}, ext = "txt")]
|
||||
struct Foo {{ {} }}"##,
|
||||
fields
|
||||
.iter()
|
||||
.map(|(name, type_)| format!("{name}: {type_}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
);
|
||||
let generated = build_template(&syn::parse_str::<syn::DeriveInput>(&jinja).unwrap())
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
let generated: syn::File = syn::parse2(generated).unwrap();
|
||||
|
||||
let size_hint = proc_macro2::Literal::usize_unsuffixed(size_hint);
|
||||
let expected: proc_macro2::TokenStream = expected.parse().unwrap();
|
||||
let expected: syn::File = syn::parse_quote! {
|
||||
impl ::rinja::Template for Foo {
|
||||
fn render_into<RinjaW>(&self, writer: &mut RinjaW) -> ::rinja::Result<()>
|
||||
where
|
||||
RinjaW: ::core::fmt::Write + ?::core::marker::Sized,
|
||||
{
|
||||
use ::rinja::filters::AutoEscape as _;
|
||||
use ::core::fmt::Write as _;
|
||||
#expected
|
||||
::rinja::Result::Ok(())
|
||||
}
|
||||
const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = Some("txt");
|
||||
const SIZE_HINT: ::std::primitive::usize = #size_hint;
|
||||
const MIME_TYPE: &'static ::std::primitive::str = "text/plain; charset=utf-8";
|
||||
}
|
||||
impl ::std::fmt::Display for Foo {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
::rinja::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if expected != generated {
|
||||
let expected = prettyplease::unparse(&expected);
|
||||
let generated = prettyplease::unparse(&generated);
|
||||
|
||||
struct Diff<'a>(&'a str, &'a str);
|
||||
|
||||
impl fmt::Display for Diff<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let diff = TextDiffConfig::default()
|
||||
.algorithm(Algorithm::Patience)
|
||||
.diff_lines(self.0, self.1);
|
||||
for change in diff.iter_all_changes() {
|
||||
let (change, line) = match change.tag() {
|
||||
ChangeTag::Equal => (
|
||||
style(" ").dim().bold(),
|
||||
style(change.to_string_lossy()).dim(),
|
||||
),
|
||||
ChangeTag::Delete => (
|
||||
style("-").red().bold(),
|
||||
style(change.to_string_lossy()).red(),
|
||||
),
|
||||
ChangeTag::Insert => (
|
||||
style("+").green().bold(),
|
||||
style(change.to_string_lossy()).green(),
|
||||
),
|
||||
};
|
||||
write!(f, "{change}{line}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
panic!(
|
||||
"\n\
|
||||
=== Expected ===\n\
|
||||
\n\
|
||||
{expected}\n\
|
||||
\n\
|
||||
=== Generated ===\n\
|
||||
\n\
|
||||
{generated}\n\
|
||||
\n\
|
||||
=== Diff ===\n\
|
||||
\n\
|
||||
{diff}\n\
|
||||
\n\
|
||||
=== FAILURE ===",
|
||||
expected = style(&expected).red(),
|
||||
generated = style(&generated).green(),
|
||||
diff = Diff(&expected, &generated),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_if_let() {
|
||||
// This function makes it much easier to compare expected code by adding the wrapping around
|
||||
// the code we want to check.
|
||||
#[track_caller]
|
||||
fn compare(jinja: &str, expected: &str, size_hint: usize) {
|
||||
let jinja = format!(r#"#[template(source = {jinja:?}, ext = "txt")] struct Foo;"#);
|
||||
let generated = build_template(&syn::parse_str::<syn::DeriveInput>(&jinja).unwrap())
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
let generated: syn::File = syn::parse2(generated).unwrap();
|
||||
|
||||
let size_hint = proc_macro2::Literal::usize_unsuffixed(size_hint);
|
||||
let expected: proc_macro2::TokenStream = expected.parse().unwrap();
|
||||
let expected: syn::File = syn::parse_quote! {
|
||||
impl ::rinja::Template for Foo {
|
||||
fn render_into<RinjaW>(&self, writer: &mut RinjaW) -> ::rinja::Result<()>
|
||||
where
|
||||
RinjaW: ::core::fmt::Write + ?::core::marker::Sized,
|
||||
{
|
||||
use ::rinja::filters::AutoEscape as _;
|
||||
use ::core::fmt::Write as _;
|
||||
#expected
|
||||
::rinja::Result::Ok(())
|
||||
}
|
||||
const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = Some("txt");
|
||||
const SIZE_HINT: ::std::primitive::usize = #size_hint;
|
||||
const MIME_TYPE: &'static ::std::primitive::str = "text/plain; charset=utf-8";
|
||||
}
|
||||
impl ::std::fmt::Display for Foo {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
::rinja::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if expected != generated {
|
||||
let expected = prettyplease::unparse(&expected);
|
||||
let generated = prettyplease::unparse(&generated);
|
||||
|
||||
struct Diff<'a>(&'a str, &'a str);
|
||||
|
||||
impl fmt::Display for Diff<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let diff = TextDiffConfig::default()
|
||||
.algorithm(Algorithm::Patience)
|
||||
.diff_lines(self.0, self.1);
|
||||
for change in diff.iter_all_changes() {
|
||||
let (change, line) = match change.tag() {
|
||||
ChangeTag::Equal => (
|
||||
style(" ").dim().bold(),
|
||||
style(change.to_string_lossy()).dim(),
|
||||
),
|
||||
ChangeTag::Delete => (
|
||||
style("-").red().bold(),
|
||||
style(change.to_string_lossy()).red(),
|
||||
),
|
||||
ChangeTag::Insert => (
|
||||
style("+").green().bold(),
|
||||
style(change.to_string_lossy()).green(),
|
||||
),
|
||||
};
|
||||
write!(f, "{change}{line}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
panic!(
|
||||
"\n\
|
||||
=== Expected ===\n\
|
||||
\n\
|
||||
{expected}\n\
|
||||
\n\
|
||||
=== Generated ===\n\
|
||||
\n\
|
||||
{generated}\n\
|
||||
\n\
|
||||
=== Diff ===\n\
|
||||
\n\
|
||||
{diff}\n\
|
||||
\n\
|
||||
=== FAILURE ===",
|
||||
expected = style(&expected).red(),
|
||||
generated = style(&generated).green(),
|
||||
diff = Diff(&expected, &generated),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// In this test, we ensure that `query` never is `self.query`.
|
||||
compare(
|
||||
"{% if let Some(query) = s && !query.is_empty() %}{{query}}{% endif %}",
|
||||
@ -110,6 +118,7 @@ fn check_if_let() {
|
||||
expr0 = &(&&::rinja::filters::AutoEscaper::new(&(query), ::rinja::filters::Text)).rinja_auto_escape()?,
|
||||
)?;
|
||||
}"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
|
||||
@ -124,6 +133,7 @@ fn check_if_let() {
|
||||
expr0 = &(&&::rinja::filters::AutoEscaper::new(&(s), ::rinja::filters::Text)).rinja_auto_escape()?,
|
||||
)?;
|
||||
}"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
|
||||
@ -138,6 +148,7 @@ fn check_if_let() {
|
||||
expr0 = &(&&::rinja::filters::AutoEscaper::new(&(s), ::rinja::filters::Text)).rinja_auto_escape()?,
|
||||
)?;
|
||||
}"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
|
||||
@ -157,6 +168,161 @@ fn check_if_let() {
|
||||
writer.write_str("3")?;
|
||||
writer.write_str("3")?;"#
|
||||
),
|
||||
&[],
|
||||
4,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_is_defined() {
|
||||
// Checks that it removes conditions if we know at compile-time that they always return false.
|
||||
//
|
||||
// We're forced to add `bla` otherwise `compare` assert fails in weird ways...
|
||||
compare(
|
||||
"{% if y is defined %}{{query}}{% endif %}bla",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
compare(
|
||||
"{% if x is not defined %}{{query}}{% endif %}bla",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[("x", "u32")],
|
||||
3,
|
||||
);
|
||||
compare(
|
||||
"{% if y is defined && x is not defined %}{{query}}{% endif %}bla",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[("x", "u32")],
|
||||
3,
|
||||
);
|
||||
|
||||
// Same with declared variables.
|
||||
compare(
|
||||
"{% set y = 12 %}
|
||||
{%- if y is not defined %}{{query}}{% endif %}bla",
|
||||
r#"let y = 12;
|
||||
writer.write_str("bla")?;"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
compare(
|
||||
"{% set y = 12 %}
|
||||
{%- if y is not defined && x is defined %}{{query}}{% endif %}bla",
|
||||
r#"let y = 12;
|
||||
writer.write_str("bla")?;"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
|
||||
// Checks that if the condition is always `true` at compile-time, then we keep the code but
|
||||
// remove the condition.
|
||||
compare(
|
||||
"{% if y is defined %}bla{% endif %}",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[("y", "u32")],
|
||||
3,
|
||||
);
|
||||
compare(
|
||||
"{% if x is not defined %}bla{% endif %}",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
// Same with declared variables.
|
||||
compare(
|
||||
"{% set y = 12 %}
|
||||
{%- if y is defined %}bla{% endif %}",
|
||||
r#"let y = 12;
|
||||
writer.write_str("bla")?;"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
|
||||
// If the always `true` condition is followed by more `else if`/`else`, check that they are
|
||||
// removed as well.
|
||||
compare(
|
||||
"{% if x is defined %}bli
|
||||
{%- else if x == 12 %}12{% endif %}bla",
|
||||
r#"writer.write_str("bli")?;
|
||||
writer.write_str("bla")?;"#,
|
||||
&[("x", "u32")],
|
||||
6,
|
||||
);
|
||||
compare(
|
||||
"{% if x is defined %}bli
|
||||
{%- else if x == 12 %}12
|
||||
{%- else %}nope{% endif %}bla",
|
||||
r#"writer.write_str("bli")?;
|
||||
writer.write_str("bla")?;"#,
|
||||
&[("x", "u32")],
|
||||
6,
|
||||
);
|
||||
// If it's not the first one.
|
||||
compare(
|
||||
"{% if x == 12 %}bli
|
||||
{%- else if x is defined %}12
|
||||
{%- else %}nope{% endif %}",
|
||||
r#"if *(&(self.x == 12) as &bool) {
|
||||
writer.write_str("bli")?;
|
||||
} else {
|
||||
writer.write_str("12")?;
|
||||
}"#,
|
||||
&[("x", "u32")],
|
||||
5,
|
||||
);
|
||||
|
||||
// Checking that it doesn't remove the condition if other non-"if (not) defined" checks
|
||||
// are present.
|
||||
compare(
|
||||
"{% if y is defined || x == 12 %}{{x}}{% endif %}",
|
||||
r#"if *(&(false || self.x == 12) as &bool) {
|
||||
::std::write!(writer, "{expr0}",
|
||||
expr0 = &(&&::rinja::filters::AutoEscaper::new(&(self.x), ::rinja::filters::Text)).rinja_auto_escape()?,
|
||||
)?;
|
||||
}
|
||||
"#,
|
||||
&[("x", "u32")],
|
||||
3,
|
||||
);
|
||||
compare(
|
||||
"{% if y is defined || x == 12 %}{{x}}{% endif %}",
|
||||
r#"if *(&(true || self.x == 12) as &bool) {
|
||||
::std::write!(writer, "{expr0}",
|
||||
expr0 = &(&&::rinja::filters::AutoEscaper::new(&(self.x), ::rinja::filters::Text)).rinja_auto_escape()?,
|
||||
)?;
|
||||
}
|
||||
"#,
|
||||
&[("y", "u32"), ("x", "u32")],
|
||||
3,
|
||||
);
|
||||
|
||||
// Checking some funny cases.
|
||||
|
||||
// This one is a bit useless because you can use `is not defined` but I suppose it's possible
|
||||
// to encounter cases like that in the wild so better have a check.
|
||||
compare(
|
||||
"{% if !(y is defined) %}bla{% endif %}",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
compare(
|
||||
"{% if !(y is not defined) %}bli{% endif %}bla",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[],
|
||||
3,
|
||||
);
|
||||
compare(
|
||||
"{% if !(y is defined) %}bli{% endif %}bla",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[("y", "u32")],
|
||||
3,
|
||||
);
|
||||
compare(
|
||||
"{% if !(y is not defined) %}bla{% endif %}",
|
||||
r#"writer.write_str("bla")?;"#,
|
||||
&[("y", "u32")],
|
||||
3,
|
||||
);
|
||||
}
|
||||
|
33
testing/tests/is_defined.rs
Normal file
33
testing/tests/is_defined.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use rinja::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"<script>
|
||||
const x = {{ x is defined }};
|
||||
const y = {{ y is not defined }};
|
||||
const z = {{ y is defined }};
|
||||
const w = {{ x is not defined }};
|
||||
const v = {{ y }};
|
||||
</script>"#,
|
||||
ext = "html"
|
||||
)]
|
||||
struct IsDefined {
|
||||
y: u32,
|
||||
}
|
||||
|
||||
// This test ensures that `include` are correctly working inside filter blocks and that external
|
||||
// variables are used correctly.
|
||||
#[test]
|
||||
fn is_defined_in_expr() {
|
||||
let s = IsDefined { y: 0 };
|
||||
assert_eq!(
|
||||
s.render().unwrap(),
|
||||
r#"<script>
|
||||
const x = false;
|
||||
const y = false;
|
||||
const z = true;
|
||||
const w = true;
|
||||
const v = 0;
|
||||
</script>"#
|
||||
);
|
||||
}
|
46
testing/tests/ui/is_defined.rs
Normal file
46
testing/tests/ui/is_defined.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use rinja::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
source = r#"{% if x.y is defined %}{% endif %}"#,
|
||||
)]
|
||||
struct A;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
source = r#"{% if true is defined %}{% endif %}"#,
|
||||
)]
|
||||
struct B;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
source = r#"{% if true is %}{% endif %}"#,
|
||||
)]
|
||||
struct C;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
source = r#"{% if x is %}{% endif %}"#,
|
||||
)]
|
||||
struct D;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
source = r#"{% if x is blue %}{% endif %}"#,
|
||||
)]
|
||||
struct E;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
source = r#"{% if x is blue.red %}{% endif %}"#,
|
||||
)]
|
||||
struct F;
|
||||
|
||||
fn main() {
|
||||
}
|
59
testing/tests/ui/is_defined.stderr
Normal file
59
testing/tests/ui/is_defined.stderr
Normal file
@ -0,0 +1,59 @@
|
||||
error: `is defined` operator can only be used on variables, not on their fields
|
||||
failed to parse template source at row 1, column 6 near:
|
||||
"x.y is defined %}{% endif %}"
|
||||
--> tests/ui/is_defined.rs:3:10
|
||||
|
|
||||
3 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: `is defined` operator can only be used on variables
|
||||
failed to parse template source at row 1, column 6 near:
|
||||
"true is defined %}{% endif %}"
|
||||
--> tests/ui/is_defined.rs:10:10
|
||||
|
|
||||
10 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected `defined` or `not defined` after `is`
|
||||
failed to parse template source at row 1, column 6 near:
|
||||
"true is %}{% endif %}"
|
||||
--> tests/ui/is_defined.rs:17:10
|
||||
|
|
||||
17 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected `defined` or `not defined` after `is`
|
||||
failed to parse template source at row 1, column 6 near:
|
||||
"x is %}{% endif %}"
|
||||
--> tests/ui/is_defined.rs:24:10
|
||||
|
|
||||
24 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected `defined` or `not defined` after `is`
|
||||
failed to parse template source at row 1, column 6 near:
|
||||
"x is blue %}{% endif %}"
|
||||
--> tests/ui/is_defined.rs:31:10
|
||||
|
|
||||
31 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected `defined` or `not defined` after `is`
|
||||
failed to parse template source at row 1, column 6 near:
|
||||
"x is blue.red %}{% endif %}"
|
||||
--> tests/ui/is_defined.rs:38:10
|
||||
|
|
||||
38 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
Loading…
x
Reference in New Issue
Block a user