From 4a21ae5af30239ca672552befe770c99ff24d5ba Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 25 May 2020 15:40:42 -0400 Subject: [PATCH] Add tests for explicit context setting that check failure conditions and implement a helper function to generate a new breadcrumb stack. --- .../explicit_context_setting/README.md | 22 ++++- .../explicit_context_setting/input1.json | 3 +- .../explicit_context_setting/main.dust | 98 +++++++++++++++++++ src/parser/mod.rs | 1 + src/renderer/renderer.rs | 56 +++++++++++ src/renderer/walking.rs | 6 ++ 6 files changed, 184 insertions(+), 2 deletions(-) diff --git a/js/test_cases/explicit_context_setting/README.md b/js/test_cases/explicit_context_setting/README.md index f72b503..8be51bf 100644 --- a/js/test_cases/explicit_context_setting/README.md +++ b/js/test_cases/explicit_context_setting/README.md @@ -37,4 +37,24 @@ Explicitly setting a context does not work on a reference, but it has revealed t Paths ----- -Explicit contexts do support deep paths +Explicit contexts do support deep paths. + +Else Blocks +----------- + +Else blocks also use an explicit context. + +Complete Failure +---------------- + +If the walk destination does not exist, and the explicit context does not exist, then you end up with absolutely no context. + +Regular Path Failure +-------------------- + +If the regular path fails but the explicit path succeeds then the context is set to the explicit path. + +Falsey Explicit Path +-------------------- + +Since dust would not walk to a falsey path, theres no difference between a falsey path and a non-existent path. diff --git a/js/test_cases/explicit_context_setting/input1.json b/js/test_cases/explicit_context_setting/input1.json index cc71db1..20dd998 100644 --- a/js/test_cases/explicit_context_setting/input1.json +++ b/js/test_cases/explicit_context_setting/input1.json @@ -24,5 +24,6 @@ "explicit": { "pet": "cat" } - } + }, + "empty_array": [] } diff --git a/js/test_cases/explicit_context_setting/main.dust b/js/test_cases/explicit_context_setting/main.dust index 9354ec3..fbaa3ff 100644 --- a/js/test_cases/explicit_context_setting/main.dust +++ b/js/test_cases/explicit_context_setting/main.dust @@ -157,3 +157,101 @@ Variable Explicit{~n} {/person} {/loop} +{! What context is set on else blocks? !} +Else Block Regular{~n} +=================={~n} +{#loop} + {#empty_array} + MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {:else} + ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {/empty_array} +{/loop} + +Else Block Explicit{~n} +==================={~n} +{#loop} + {#empty_array:explicit} + MAIN {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n} + {:else} + ELSE {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n} + {/empty_array} +{/loop} + +{! What context is set when the explicit context path does not exist? !} +Failed Explicit Regular{~n} +======================={~n} +{#loop} + {#person} + {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {/person} +{/loop} + +Failed Explicit Explicit{~n} +========================{~n} +{#loop} + {#person:foobar} + {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {/person} +{/loop} + +{! What context is set when the regular path and explicit context path does not exist? !} +Failed Everything Regular{~n} +========================={~n} +BEFORE {.|js}{~n} +{#foo} + MAIN {.|js}{~n} +{:else} + ELSE {.|js}{~n} +{/foo} + +Failed Everything Explicit{~n} +=========================={~n} +{#foo:bar} + MAIN {.|js}{~n} +{:else} + ELSE {.|js}{~n} +{/foo} + +{! What context is set when the regular context path does not exist? !} +Failed Regular Path Regular{~n} +==========================={~n} +{#loop} + {#foo} + MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {:else} + ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {/foo} +{/loop} + +Failed Regular Path Explicit{~n} +============================{~n} +{#loop} + {#foo:explicit} + MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {:else} + ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {/foo} +{/loop} + +{! What context is set when the explicit path is falsey? !} +Falsey Explicit Path Regular{~n} +============================{~n} +{#loop} + {#foo} + MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {:else} + ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {/foo} +{/loop} + +Falsey Explicit Path Explicit{~n} +============================={~n} +{#loop} + {#foo:empty_array} + MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {:else} + ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n} + {.|js}{~n} + {/foo} +{/loop} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 30184b2..6efae74 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9,6 +9,7 @@ pub use parser::Filter; pub use parser::KVPair; pub use parser::OwnedLiteral; pub use parser::PartialNameElement; +pub use parser::Path; pub use parser::RValue; pub use parser::Special; pub use parser::Template; diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 6a76535..b82e683 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -3,6 +3,7 @@ use crate::parser::Body; use crate::parser::DustTag; use crate::parser::KVPair; use crate::parser::PartialNameElement; +use crate::parser::Path; use crate::parser::RValue; use crate::parser::Special; use crate::parser::Template; @@ -551,6 +552,61 @@ impl<'a> DustRenderer<'a> { } final_filters } + + /// Generate a new breadcrumbs object + /// + /// This function generates a new breadcrumbs object based on the + /// new context information provided. + /// + /// explicit_context is for contexts specified with a `:path` + /// inside a dust tag. + /// + /// injected_context is for any generated context. This includes + /// both parameters on a tag and also the handling of $idx and + /// $len. + /// + /// New context element is the element is an element to append to + /// the end, generally for use in section tags which walk to a new + /// context. + /// + /// If explicit_context is not None, then the final breadcrumb stack will be: + /// + /// ``` + /// breadcrumbs + /// injected_context + /// new_context_element + /// ``` + /// + /// However, if explicit_context is not None, then the old + /// breadcrumbs are omitted, leading to the new breadcrumb stack + /// as: + /// + /// ``` + /// injected_context + /// explicit_context + /// new_context_element + /// ``` + fn new_breadcrumbs<'b>( + breadcrumbs: &'b Vec<&'b dyn ContextElement>, + injected_context: Option<&'b dyn ContextElement>, + explicit_context: &Option>, + new_context_element: Option<&'b dyn ContextElement>, + ) -> Vec<&'b dyn ContextElement> { + let mut new_stack = match explicit_context { + Some(_) => Vec::with_capacity(3), + None => breadcrumbs.clone(), + }; + injected_context.map(|ctx| new_stack.push(ctx)); + explicit_context.as_ref().map(|path| { + walk_path(breadcrumbs, &path.keys).map(|val| { + if val.is_truthy() { + new_stack.push(val) + } + }); + }); + new_context_element.map(|ctx| new_stack.push(ctx)); + new_stack + } } #[cfg(test)] diff --git a/src/renderer/walking.rs b/src/renderer/walking.rs index 5a38458..49dff7f 100644 --- a/src/renderer/walking.rs +++ b/src/renderer/walking.rs @@ -42,6 +42,12 @@ where B: Borrow, P: Borrow, { + if breadcrumbs.is_empty() { + // This happens when you use a section with an explicit + // context path, where both the path and the explicit context + // path fail, leaving you with absolutely no context. + return Err(WalkError::CantWalk); + } if path.is_empty() { return Ok(breadcrumbs .last()