1 /*
2  * Boost Software License - Version 1.0
3  *
4  * Copyright 2015-2020 Kevin Wojniak
5  *
6  * Permission is hereby granted, free of charge, to any person or organization
7  * obtaining a copy of the software and accompanying documentation covered by
8  * this license (the "Software") to use, reproduce, display, distribute,
9  * execute, and transmit the Software, and to prepare derivative works of the
10  * Software, and to permit third-parties to whom the Software is furnished to
11  * do so, all subject to the following:
12  *
13  * The copyright notices in the Software and this entire statement, including
14  * the above license grant, this restriction and the following disclaimer,
15  * must be included in all copies of the Software, in whole or in part, and
16  * all derivative works of the Software, unless such copies or derivative
17  * works are solely in the form of machine-executable object code generated by
18  * a source language processor.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23  * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24  * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  */
28 
29 #include "mustache.hpp"
30 
31 #define CATCH_CONFIG_MAIN
32 #include "catch.hpp"
33 
34 using namespace kainjow::mustache;
35 
36 #ifndef MUSTACHE_VS2013
37     #if defined(_MSC_VER) && _MSC_VER == 1800
38         #define MUSTACHE_VS2013 1
39     #else
40         #define MUSTACHE_VS2013 0
41     #endif
42 #endif
43 
44 TEST_CASE("split") {
45 
46     std::vector<std::string> names;
47     names = split<std::string>("", '.');
48     REQUIRE(names.size() == 0);
49     names = split<std::string>("test", '.');
50     REQUIRE(names.size() == 1);
51     CHECK(names[0] == "test");
52     names = split<std::string>("a.b", '.');
53     REQUIRE(names.size() == 2);
54     CHECK(names[0] == "a");
55     CHECK(names[1] == "b");
56     names = split<std::string>(".", '.');
57     REQUIRE(names.size() == 1);
58     CHECK(names[0] == "");
59     names = split<std::string>("a.", '.');
60     REQUIRE(names.size() == 1);
61     CHECK(names[0] == "a");
62 
63 }
64 
65 TEST_CASE("variables") {
66 
67     SECTION("empty") {
68         mustache tmpl("");
69         data data;
70         CHECK(tmpl.render(data).empty());
71     }
72 
73     SECTION("none") {
74         mustache tmpl("Hello");
75         data data;
76         CHECK(tmpl.render(data) == "Hello");
77     }
78 
79     SECTION("single_miss") {
80         mustache tmpl("Hello {{name}}");
81         data data;
82         CHECK(tmpl.render(data) == "Hello ");
83     }
84 
85     SECTION("single_exist") {
86         mustache tmpl("Hello {{name}}");
87         data data;
88         data.set("name", "Steve");
89         CHECK(tmpl.render(data) == "Hello Steve");
90     }
91 
92     SECTION("single_exist_wide") {
93         mustachew tmpl(L"Hello {{name}}");
94         dataw data;
95         data.set(L"name", L"Steve");
96         CHECK(tmpl.render(data) == L"Hello Steve");
97     }
98 
99     SECTION("escape") {
100         mustache tmpl("Hello {{name}}");
101         data data;
102         data.set("name", "\"S\"<br>te&v\'e");
103         CHECK(tmpl.render(data) == "Hello &quot;S&quot;&lt;br&gt;te&amp;v&apos;e");
104     }
105 
106     SECTION("unescaped1") {
107         mustache tmpl("Hello {{{name}}}");
108         data data;
109         data.set("name", "\"S\"<br>te&v\'e");
110         CHECK(tmpl.render(data) == "Hello \"S\"<br>te&v\'e");
111     }
112 
113     SECTION("unescaped2") {
114         mustache tmpl("Hello {{&name}}");
115         data data;
116         data.set("name", "\"S\"<br>te&v\'e");
117         CHECK(tmpl.render(data) == "Hello \"S\"<br>te&v\'e");
118     }
119 
120     SECTION("unescaped2_spaces") {
121         mustache tmpl("Hello {{   &      name  }}");
122         data data;
123         data.set("name", "\"S\"<br>te&v\'e");
124         CHECK(tmpl.render(data) == "Hello \"S\"<br>te&v\'e");
125     }
126 
127     SECTION("empty_name") {
128         mustache tmpl("Hello {{}}");
129         data data;
130         data.set("", "Steve");
131         CHECK(tmpl.render(data) == "Hello Steve");
132     }
133 
134     SECTION("braces") {
135         mustache tmpl("my {{var}}");
136         data data;
137         data.set("var", "{{te}}st");
138         CHECK(tmpl.render(data) == "my {{te}}st");
139     }
140 
141 }
142 
143 TEST_CASE("comments") {
144 
145     SECTION("simple") {
146         mustache tmpl("<h1>Today{{! ignore me }}.</h1>");
147         data data;
148         CHECK(tmpl.render(data) == "<h1>Today.</h1>");
149     }
150 
151     SECTION("newlines") {
152         mustache tmpl("Hello\n{{! ignore me }}\nWorld\n");
153         data data;
154         CHECK(tmpl.render(data) == "Hello\n\nWorld\n");
155     }
156 
157 }
158 
159 TEST_CASE("set_delimiter") {
160 
161     SECTION("basic") {
162         mustache tmpl("{{name}}{{=<% %>=}}<% name %><%={{ }}=%>{{ name }}");
163         data data;
164         data.set("name", "Steve");
165         CHECK(tmpl.render(data) == "SteveSteveSteve");
166     }
167 
168     SECTION("small") {
169         mustache tmpl("{{n}}{{=a b=}}anba={{ }}=b{{n}}");
170         data data;
171         data.set("n", "s");
172         CHECK(tmpl.render(data) == "sss");
173     }
174 
175     SECTION("noreset") {
176         mustache tmpl("{{=[ ]=}}[name] [x] + [y] = [sum]");
177         data data;
178         data.set("name", "Steve");
179         data.set("x", "1");
180         data.set("y", "2");
181         data.set("sum", "3");
182         CHECK(tmpl.render(data) == "Steve 1 + 2 = 3");
183     }
184 
185     SECTION("whitespace") {
186         mustache tmpl("|{{= @   @ =}}|");
187         data data;
188         REQUIRE(tmpl.is_valid());
189         CHECK(tmpl.render(data) == "||");
190     }
191 
192 }
193 
194 TEST_CASE("sections") {
195 
196     SECTION("nonexistant") {
197         mustache tmpl("{{#var}}not shown{{/var}}");
198         data data;
199         CHECK(tmpl.render(data) == "");
200     }
201 
202     SECTION("false") {
203         mustache tmpl("{{#var}}not shown{{/var}}");
204         data dat;
205         dat.set("var", data(data::type::bool_false));
206         CHECK(tmpl.render(dat) == "");
207     }
208 
209     SECTION("emptylist") {
210         mustache tmpl("{{#var}}not shown{{/var}}");
211         data dat;
212         dat.set("var", data(data::type::list));
213         CHECK(tmpl.render(dat) == "");
214     }
215 
216     SECTION("nested") {
217         mustache tmpl("{{#var1}}hello{{#var2}}world{{/var2}}{{/var1}}");
218         data data;
219         data.set("var1", data::type::bool_true);
220         data.set("var2", data::type::bool_true);
221         CHECK(tmpl.render(data) == "helloworld");
222     }
223 
224 }
225 
226 TEST_CASE("sections_inverted") {
227 
228     SECTION("nonexistant") {
229         mustache tmpl("{{^var}}shown{{/var}}");
230         CHECK(tmpl.render(data()) == "shown");
231     }
232 
233     SECTION("false") {
234         mustache tmpl("{{^var}}shown{{/var}}");
235         data dat("var", data(data::type::bool_false));
236         CHECK(tmpl.render(dat) == "shown");
237     }
238 
239     SECTION("emptylist") {
240         mustache tmpl("{{^var}}shown{{/var}}");
241         data dat("var", data(data::type::list));
242         CHECK(tmpl.render(dat) == "shown");
243     }
244 
245 }
246 
247 TEST_CASE("section_lists") {
248 
249     SECTION("list") {
250         mustache tmpl("{{#people}}Hello {{name}}, {{/people}}");
251         data people = data::type::list;
252         for (const auto& name : {"Steve", "Bill", "Tim"}) {
253             people.push_back(data("name", name));
254         }
255         data data("people", people);
256         CHECK(tmpl.render(data) == "Hello Steve, Hello Bill, Hello Tim, ");
257     }
258 
259     SECTION("nested") {
260         mustache tmpl("{{#families}}surname={{surname}}, members={{#members}}{{given}},{{/members}}|{{/families}}");
261         data families = data::type::list;
262         data family1;
263         family1.set("surname", "Smith");
264         data members1 = data::type::list;
265         data member1a; member1a.set("given", "Steve"); members1.push_back(member1a);
266         data member1b; member1b.set("given", "Joe"); members1.push_back(member1b);
267         family1.set("members", members1);
268         data family2;
269         family2.set("surname", "Lee");
270         data members2 = data::type::list;
271         data member2a; member2a.set("given", "Bill"); members2.push_back(member2a);
272         data member2b; member2b.set("given", "Peter"); members2.push_back(member2b);
273         family2.set("members", members2);
274         families.push_back(family1);
275         families.push_back(family2);
276         data data;
277         data.set("families", families);
278         CHECK(tmpl.render(data) == "surname=Smith, members=Steve,Joe,|surname=Lee, members=Bill,Peter,|");
279     }
280 
281     SECTION("dot") {
282         mustache tmpl("{{#names}}Hello {{.}}, {{/names}}");
283         data names = data::type::list;
284         names.push_back(data("Steve"));
285         names.push_back(data("Bill"));
286         names.push_back(data("Tim"));
287         data data("names", names);
288         CHECK(tmpl.render(data) == "Hello Steve, Hello Bill, Hello Tim, ");
289     }
290 
291     SECTION("dot2") {
292         mustache tmpl("{{#names}}Hello {{.}}{{/names}}{{#friends}} and {{.}}{{/friends}}");
293         data friends = data::type::list;
294         friends.push_back("Bill");
295         friends.push_back("Tim");
296         data data;
297         data.set("friends", friends);
298         CHECK(tmpl.render(data) == " and Bill and Tim");
299         data.set("names", "Steve");
300         CHECK(tmpl.render(data) == "Hello Steve and Bill and Tim");
301     }
302 
303 }
304 
305 TEST_CASE("section_object") {
306 
307     SECTION("basic") {
308         mustache tmpl("{{#employee}}name={{name}}, age={{age}}{{/employee}}");
309         data person;
310         person.set("name", "Steve");
311         person.set("age", "42");
312         data data;
313         data.set("employee", person);
314         CHECK(tmpl.render(data) == "name=Steve, age=42");
315     }
316 
317     SECTION("basic_parent") {
318         mustache tmpl("({{subject}}) {{#employee}}name={{name}}, age={{age}} - {{subject}}{{/employee}}");
319         data person;
320         person.set("name", "Steve");
321         person.set("age", "42");
322         person.set("subject", "email");
323         data data;
324         data.set("subject", "test");
325         data.set("employee", person);
326         CHECK(tmpl.render(data) == "(test) name=Steve, age=42 - email");
327     }
328 
329 }
330 
331 TEST_CASE("examples") {
332 
333     SECTION("one") {
334         mustache tmpl{"Hello {{what}}!"};
335         std::cout << tmpl.render({"what", "World"}) << std::endl;
336         CHECK(tmpl.is_valid());
337         CHECK(tmpl.error_message() == "");
338         CHECK(tmpl.render({"what", "World"}) == "Hello World!");
339     }
340 
341     SECTION("two") {
342         mustache tmpl{"{{#employees}}{{name}}, {{/employees}}"};
343         data employees{data::type::list};
344         employees << data{"name", "Steve"} << data{"name", "Bill"};
345         std::ostream& stream = tmpl.render({"employees", employees}, std::cout) << std::endl;
346         CHECK(tmpl.is_valid());
347         CHECK(tmpl.error_message() == "");
348         CHECK(tmpl.render({"employees", employees}) == "Steve, Bill, ");
349         CHECK(&stream == &std::cout);
350     }
351 
352     SECTION("three") {
353         mustache tmpl("Hello {{what}}!");
354         std::stringstream ss;
__anon3f70779f0102(const std::string& str) 355         tmpl.render({"what", "World"}, [&ss](const std::string& str) {
356             ss << str;
357         });
358         CHECK(tmpl.is_valid());
359         CHECK(tmpl.error_message() == "");
360         CHECK(ss.str() == "Hello World!");
361     }
362 
363 }
364 
365 TEST_CASE("data") {
366 
367     SECTION("types") {
368         data dat("age", "42");
369         data emptyStr = data::type::string;
370         dat["name"] = "Steve";
371         dat["is_human"] = data::type::bool_true;
372         dat["is_dog"] = false;
373         dat["is_organic"] = true;
374         const data* name;
375         const data* age;
376         const data* is_human;
377         name = dat.get("name");
378         age = dat.get("age");
379         is_human = dat.get("is_human");
380         CHECK(name != (const data*)0);
381         CHECK(age != (const data*)0);
382         CHECK(is_human != (const data*)0);
383         CHECK(dat.get("miss") == (const data*)0);
384         REQUIRE(name->is_string());
385         CHECK(name->string_value() == "Steve");
386         REQUIRE(age->is_string());
387         CHECK(age->string_value() == "42");
388         CHECK(is_human->is_true());
389         CHECK(is_human->is_bool());
390         CHECK(emptyStr.is_string());
391         CHECK(emptyStr.string_value() == "");
392         CHECK(dat["is_dog"].is_bool());
393         CHECK(dat["is_dog"].is_false());
394         CHECK(dat["is_organic"].is_bool());
395         CHECK(dat["is_organic"].is_true());
396 
397         data emptyData;
398         CHECK(emptyData.is_empty_object() == true);
399         CHECK(emptyData.is_non_empty_object() == false);
400 
401         data nonEmptyData("name", "foo");
402         CHECK(nonEmptyData.is_empty_object() == false);
403         CHECK(nonEmptyData.is_non_empty_object() == true);
404     }
405 
406     SECTION("move_ctor") {
407         data obj1{data::type::list};
408         CHECK(obj1.is_list());
409         data obj2{std::move(obj1)};
410         CHECK(obj2.is_list());
411         CHECK(obj1.is_invalid());
412         obj2.push_back({"name", "Steve"}); // this should puke if the internal data isn't setup correctly
413     }
414 
415     SECTION("move_assign") {
416         data obj1{data::type::list};
417         CHECK(obj1.is_list());
418         data obj2 = std::move(obj1);
419         CHECK(obj2.is_list());
420         CHECK(obj1.is_invalid());
421         obj2.push_back({"name", "Steve"}); // this should puke if the internal data isn't setup correctly
422 
__anon3f70779f0202()423         data lambda0{lambda{[](const std::string&){ return ""; }}};
424         CHECK(lambda0.is_lambda());
425         data lambda1 = std::move(lambda0);
426         CHECK(lambda1.is_lambda());
427         CHECK(lambda0.is_invalid());
428 
__anon3f70779f0302()429         data lambda3{lambda2{[](const std::string&, const renderer&){ return ""; }}};
430         CHECK(lambda3.is_lambda2());
431         data lambda4 = std::move(lambda3);
432         CHECK(lambda4.is_lambda2());
433         CHECK(lambda3.is_invalid());
434     }
435 
436     SECTION("lambda_copy_ctor") {
__anon3f70779f0402()437         data l1{lambda{[](const std::string&){ return ""; }}};
438         data l2{l1};
439         CHECK(l1.is_lambda());
440         CHECK(l2.is_lambda());
441         CHECK(l1.lambda_value()("") == l2.lambda_value()(""));
442     }
443 
444     SECTION("lambda2_copy_ctor") {
__anon3f70779f0502()445         data l1{lambda2{[](const std::string&, const renderer& r){ return r(""); }}};
446         data l2{l1};
447         CHECK(l1.is_lambda2());
448         CHECK(l2.is_lambda2());
449     }
450 
451     SECTION("data_set") {
452         data data;
453         data.set("var", data::type::bool_true);
454         CHECK(data.get("var")->is_bool());
455         CHECK(data.get("var")->is_true());
456         data.set("var", "hello");
457         CHECK(data.get("var")->is_string());
458         CHECK(data.get("var")->string_value() == "hello");
459     }
460 
461 }
462 
463 TEST_CASE("errors") {
464 
465     SECTION("unclosed_section") {
466         mustache tmpl("test {{#employees}}");
467         CHECK_FALSE(tmpl.is_valid());
468         CHECK(tmpl.error_message() == "Unclosed section \"employees\" at 5");
469     }
470 
471     SECTION("unclosed_section_nested") {
472         mustache tmpl("{{#var1}}hello{{#var2}}world");
473         data data;
474         data.set("var1", data::type::bool_true);
475         data.set("var2", data::type::bool_true);
476         CHECK(tmpl.render(data) == "");
477         CHECK(tmpl.is_valid() == false);
478         CHECK(tmpl.error_message() == "Unclosed section \"var1\" at 0");
479     }
480 
481     SECTION("unclosed_section_nested2") {
482         mustache tmpl("{{#var1}}hello{{#var2}}world{{/var1}}");
483         data data;
484         data.set("var1", data::type::bool_true);
485         data.set("var2", data::type::bool_true);
486         CHECK(tmpl.render(data) == "");
487         CHECK(tmpl.is_valid() == false);
488         CHECK(tmpl.error_message() == "Unclosed section \"var1\" at 0");
489     }
490 
491     SECTION("unclosed_section_in_section") {
492         mustache tmpl("{{#a}}{{^b}}{{/c}}{{/a}}");
493         CHECK_FALSE(tmpl.is_valid());
494         CHECK(tmpl.error_message() == "Unclosed section \"b\" at 6");
495     }
496 
497     SECTION("unclosed_tag") {
498         mustache tmpl("test {{employees");
499         CHECK_FALSE(tmpl.is_valid());
500         CHECK(tmpl.error_message() == "Unclosed tag at 5");
501     }
502 
503     SECTION("unopened_section") {
504         mustache tmpl("test {{/employees}}");
505         CHECK_FALSE(tmpl.is_valid());
506         CHECK(tmpl.error_message() == "Unopened section \"employees\" at 5");
507     }
508 
509     SECTION("invalid_set_delimiter") {
510         std::vector<std::string> invalids;
511         invalids.push_back("test {{=< =}}");  // not 5 characters
512         invalids.push_back("test {{=....}}"); // not ending with =
513         invalids.push_back("test {{=...=}}"); // does not contain space
514         invalids.push_back("test {{=.  ==}}"); // can't contain equal sign
515         invalids.push_back("test {{==  .=}}"); // can't contain equal sign
516         invalids.push_back("test {{=[ ] ] ]=}}"); // can't contain space
517         invalids.push_back("test {{=[ [ ]=}}"); // can't contain space
518         std::vector<std::string>::size_type total = 0;
519         for (const auto& str: invalids) {
520             mustache tmpl(str);
521             CHECK_FALSE(tmpl.is_valid());
522             CHECK(tmpl.error_message() == "Invalid set delimiter tag at 5");
523             ++total;
524         }
525         CHECK(total == invalids.size());
526         CHECK(total == 7);
527     }
528 
529     SECTION("lambda") {
530         mustache tmpl{"Hello {{lambda}}!"};
__anon3f70779f0602(const std::string&)531         data dat("lambda", data{lambda{[](const std::string&){
532             return "{{#what}}";
533         }}});
534         CHECK(tmpl.is_valid() == true);
535         CHECK(tmpl.error_message() == "");
536         CHECK(tmpl.render(dat) == "Hello ");
537         CHECK(tmpl.is_valid() == false);
538         CHECK(tmpl.error_message() == "Unclosed section \"what\" at 0");
539     }
540 
541     SECTION("lambda2") {
542         mustache tmpl{"Hello {{lambda}}!"};
__anon3f70779f0702(const std::string&)543         data dat("lambda", data{lambda{[](const std::string&){
544             return "{{what}}";
545         }}});
__anon3f70779f0802()546         dat["what"] = data{lambda{[](const std::string&){
547             return "{{#blah}}";
548         }}};
549         CHECK(tmpl.is_valid() == true);
550         CHECK(tmpl.error_message() == "");
551         CHECK(tmpl.render(dat) == "Hello ");
552         CHECK(tmpl.is_valid() == false);
553         CHECK(tmpl.error_message() == "Unclosed section \"blah\" at 0");
554     }
555 
556     SECTION("partial") {
557         mustache tmpl{"Hello {{>partial}}!"};
__anon3f70779f0902()558         data dat("partial", data{partial{[](){
559             return "{{#what}}";
560         }}});
561         CHECK(tmpl.is_valid() == true);
562         CHECK(tmpl.error_message() == "");
563         CHECK(tmpl.render(dat) == "Hello ");
564         CHECK(tmpl.is_valid() == false);
565         CHECK(tmpl.error_message() == "Unclosed section \"what\" at 0");
566     }
567 
568     SECTION("partial2") {
569         mustache tmpl{"Hello {{>partial}}!"};
__anon3f70779f0a02()570         data data("partial", {partial{[](){
571             return "{{what}}";
572         }}});
__anon3f70779f0b02(const std::string&)573         data["what"] = lambda{[](const std::string&){
574             return "{{#blah}}";
575         }};
576         CHECK(tmpl.is_valid() == true);
577         CHECK(tmpl.error_message() == "");
578         CHECK(tmpl.render(data) == "Hello ");
579         CHECK(tmpl.is_valid() == false);
580         CHECK(tmpl.error_message() == "Unclosed section \"blah\" at 0");
581     }
582 
583     SECTION("section_lambda") {
584         mustache tmpl{"{{#what}}asdf{{/what}}"};
__anon3f70779f0c02(const std::string&)585         data data("what", lambda{[](const std::string&){
586             return "{{blah";
587         }});
588         CHECK(tmpl.is_valid() == true);
589         CHECK(tmpl.error_message() == "");
590         CHECK(tmpl.render(data) == "");
591         CHECK(tmpl.is_valid() == false);
592         CHECK(tmpl.error_message() == "Unclosed tag at 0");
593     }
594 
595 }
596 
597 TEST_CASE("partials") {
598 
599     SECTION("empty") {
600         mustache tmpl{"{{>header}}"};
601         data data;
602         CHECK(tmpl.render(data) == "");
603     }
604 
605     SECTION("basic") {
606         mustache tmpl{"{{>header}}"};
__anon3f70779f0d02() 607         partial part = []() {
608             return "Hello World";
609         };
610         data dat("header", data{part});
611         CHECK(tmpl.render(dat) == "Hello World");
612     }
613 
614     SECTION("context") {
615         mustache tmpl{"{{>header}}"};
__anon3f70779f0e02() 616         partial part{[]() {
617             return "Hello {{name}}";
618         }};
619         data dat("header", data{part});
620         dat["name"] = "Steve";
621         CHECK(tmpl.render(dat) == "Hello Steve");
622     }
623 
624     SECTION("nested") {
625         mustache tmpl{"{{>header}}"};
__anon3f70779f0f02() 626         partial header{[]() {
627             return "Hello {{name}} {{>footer}}";
628         }};
__anon3f70779f1002() 629         partial footer{[]() {
630             return "Goodbye {{#names}}{{.}}|{{/names}}";
631         }};
632         data names{data::type::list};
633         names.push_back("Jack");
634         names.push_back("Jill");
635         data dat("header", header);
636         dat["name"] = "Steve";
637         dat["footer"] = data{footer};
638         dat["names"] = data{names};
639         CHECK(tmpl.render(dat) == "Hello Steve Goodbye Jack|Jill|");
640     }
641 
642     SECTION("dotted") {
643         mustache tmpl{"{{>a.b}}"};
__anon3f70779f1102() 644         partial a_b{[]() {
645             return "test";
646         }};
647         data data("a.b", a_b);
648         CHECK(tmpl.render(data) == "test");
649     }
650 }
651 
652 TEST_CASE("lambdas") {
653 
654     SECTION("basic") {
655         mustache tmpl{"{{lambda}}"};
__anon3f70779f1202(const std::string&)656         data dat("lambda", data{lambda{[](const std::string&){
657             return "Hello {{planet}}";
658         }}});
659         dat["planet"] = "world";
660         CHECK(tmpl.render(dat) == "Hello world");
661     }
662 
663     SECTION("basic_t") {
664         mustache tmpl{"{{lambda}}"};
665 #if MUSTACHE_VS2013
__anon3f70779f1302(const std::string&)666         data dat("lambda", data{lambda_t{lambda_t::type1{[](const std::string&){
667 #else
668         data dat("lambda", data{lambda_t{{[](const std::string&){
669 #endif
670             return "Hello {{planet}}";
671         }}}});
672         dat["planet"] = "world";
673         CHECK(tmpl.render(dat) == "Hello world");
674     }
675 
676     SECTION("delimiters") {
677         mustache tmpl{"{{= | | =}}Hello, (|&lambda|)!"};
678         data dat("lambda", data{lambda{[](const std::string&){
679             return "|planet| => {{planet}}";
680         }}});
681         dat["planet"] = "world";
682         CHECK(tmpl.render(dat) == "Hello, (|planet| => world)!");
683     }
684 
685     SECTION("nocaching") {
686         mustache tmpl{"{{lambda}} == {{{lambda}}} == {{lambda}}"};
687         int calls = 0;
688         data dat("lambda", data{lambda{[&calls](const std::string&){
689             ++calls;
690             return std::to_string(calls);
691         }}});
692         CHECK(tmpl.render(dat) == "1 == 2 == 3");
693     }
694 
695     SECTION("escape") {
696         mustache tmpl{"<{{lambda}}{{{lambda}}}"};
697         data dat("lambda", data{lambda{[](const std::string&){
698             return ">";
699         }}});
700         CHECK(tmpl.render(dat) == "<&gt;>");
701     }
702 
703     SECTION("section") {
704         mustache tmpl{"<{{#lambda}}{{x}}{{/lambda}}>"};
705         data dat("lambda", data{lambda{[](const std::string& text){
706             return text == "{{x}}" ? "yes" : "no";
707         }}});
708         CHECK(tmpl.render(dat) == "<yes>");
709     }
710 
711     SECTION("section_expansion") {
712         mustache tmpl{"<{{#lambda}}-{{/lambda}}>"};
713         data dat("lambda", data{lambda{[](const std::string& text){
714             return text + "{{planet}}" + text;
715         }}});
716         dat["planet"] = "Earth";
717         CHECK(tmpl.render(dat) == "<-Earth->");
718     }
719 
720     SECTION("section_alternate_delimiters") {
721         mustache tmpl{"{{= | | =}}<|#lambda|-|/lambda|>"};
722         data dat("lambda", data{lambda{[](const std::string& text){
723             return text + "{{planet}} => |planet|" + text;
724         }}});
725         dat["planet"] = "Earth";
726         CHECK(tmpl.render(dat) == "<-{{planet}} => Earth->");
727     }
728 
729     const lambda sectionLambda{[](const std::string& text){
730         return "__" + text + "__";
731     }};
732 
733     SECTION("section_multiple_calls") {
734         mustache tmpl{"{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}"};
735         data data("lambda", sectionLambda);
736         CHECK(tmpl.render(data) == "__FILE__ != __LINE__");
737     }
738 
739     SECTION("section_inverted") {
740         mustache tmpl{"<{{^lambda}}{{static}}{{/lambda}}>"};
741         data data("lambda", sectionLambda);
742         data["static"] = "static";
743         CHECK(tmpl.render(data) == "<>");
744     }
745 
746 }
747 
748 TEST_CASE("dotted_names") {
749 
750     SECTION("basic") {
751         mustache tmpl{"\"{{person.name}}\" == \"{{#person}}{{name}}{{/person}}\""};
752         data person{"name", "Joe"};
753         CHECK(tmpl.render({"person", person}) == "\"Joe\" == \"Joe\"");
754     }
755 
756     SECTION("triple_mustache") {
757         mustache tmpl{"\"{{{person.name}}}\" == \"{{#person}}{{name}}{{/person}}\""};
758         data person{"name", "Joe"};
759         CHECK(tmpl.render({"person", person}) == "\"Joe\" == \"Joe\"");
760     }
761 
762     SECTION("ampersand") {
763         mustache tmpl{"\"{{&person.name}}\" == \"{{#person}}{{&name}}{{/person}}\""};
764         data person{"name", "Joe"};
765         CHECK(tmpl.render({"person", person}) == "\"Joe\" == \"Joe\"");
766     }
767 
768     SECTION("depth") {
769         mustache tmpl{"\"{{a.b.c.d.e.name}}\" == \"Phil\""};
770         data data{"a", {"b", {"c", {"d", {"e", {"name", "Phil"}}}}}};
771         CHECK(tmpl.render(data) == "\"Phil\" == \"Phil\"");
772     }
773 
774     SECTION("broken_chains1") {
775         mustache tmpl{"\"{{a.b.c}}\" == \"\""};
776         data data{"a", data::type::list};
777         CHECK(tmpl.render(data) == "\"\" == \"\"");
778     }
779 
780     SECTION("broken_chains2") {
781         mustache tmpl{"\"{{a.b.c.name}}\" == \"\""};
782         data data;
783         data["a"] = {"b", data::type::list};
784         data["c"] = {"name", "Jim"};
785         CHECK(tmpl.render(data) == "\"\" == \"\"");
786     }
787 
788     SECTION("depth2") {
789         mustache tmpl{"\"{{#a}}{{b.c.d.e.name}}{{/a}}\" == \"Phil\""};
790         data data;
791         data["a"] = {"b", {"c", {"d", {"e", {"name", "Phil"}}}}};
792         data["b"] = {"c", {"d", {"e", {"name", "Wrong"}}}};
793         CHECK(tmpl.render(data) == "\"Phil\" == \"Phil\"");
794     }
795 
796     SECTION("scope") {
797         mustache tmpl{"\"{{#a}}{{b.name}}{{/a}}\" == \"Phil\""};
798         data data;
799         data["a"] = {"x", "y"};
800         data["b"] = {"name", "Phil"};
801         CHECK(tmpl.render(data) == "\"Phil\" == \"Phil\"");
802     }
803 }
804 
805 TEST_CASE("bustache_benchmark") {
806 
807     // https://github.com/jamboree/bustache/blob/master/test/benchmark.cpp
808     int n = 0;
809     object dat
810     {
811         {"header", "Colors"},
812         {"items",
813             list
814             {
815                 object
816                 {
817                     {"name", "red"},
818                     {"first", true},
819                     {"url", "#Red"}
820                 },
821                 object
822                 {
823                     {"name", "green"},
824                     {"link", true},
825                     {"url", "#Green"}
826                 },
827                 object
828                 {
829                     {"name", "blue"},
830                     {"link", true},
831                     {"url", "#Blue"}
832                 }
833             }
834         },
835         {"empty", false},
836         {"count", lambda{[&n](const std::string&) { return std::to_string(++n); }}},
837         {"array", list{"1", "2", "3"}},
838         {"a", object{{"b", object{{"c", true}}}}},
839         {"comments",
840             list
841             {
842                 object
843                 {
844                     {"name", "Joe"},
845                     {"body", "<html> should be escaped"}
846                 },
847                 object
848                 {
849                     {"name", "Sam"},
850                     {"body", "{{mustache}} can be seen"}
851                 },
852                 object
853                 {
854                     {"name", "New"},
855                     {"body", "break\nup"}
856                 }
857             }
858         }
859     };
860 
861 }
862 
863 TEST_CASE("lambda_render") {
864 
865     SECTION("auto-render") {
866         mustache tmpl{"{{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
867         data data;
868         data["name"] = "Willy";
869         data["wrapped"] = lambda{[](const std::string& text) {
870             CHECK(text == "{{name}} is awesome.");
871             return "<b>" + text + "</b>";
872         }};
873         CHECK(tmpl.render(data) == "<b>Willy is awesome.</b>");
874     }
875 
876     SECTION("no-render") {
877         mustache tmpl{"{{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
878         data data;
879         data["name"] = "Willy";
880         data["wrapped"] = lambda2{[](const std::string& text, const renderer&) {
881             CHECK(text == "{{name}} is awesome.");
882             return "<b>" + text + "</b>";
883         }};
884         CHECK(tmpl.render(data) == "<b>{{name}} is awesome.</b>");
885     }
886 
887     SECTION("no-render-lambda_t") {
888         mustache tmpl{"{{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
889         data data;
890         data["name"] = "Willy";
891 #if MUSTACHE_VS2013
892         data["wrapped"] = lambda_t{lambda_t::type2{[](const std::string& text, const renderer&) {
893 #else
894         data["wrapped"] = lambda_t{{[](const std::string& text, const renderer&) {
895 #endif
896             CHECK(text == "{{name}} is awesome.");
897             return "<b>" + text + "</b>";
898         }}};
899         CHECK(tmpl.render(data) == "<b>{{name}} is awesome.</b>");
900     }
901 
902     SECTION("manual-render") {
903         mustache tmpl{"{{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
904         data data;
905         data["name"] = "Willy";
906         data["wrapped"] = lambda2{[](const std::string& text, const renderer& render) {
907             CHECK(text == "{{name}} is awesome.");
908             const auto renderedText = render(text);
909             CHECK(renderedText == "Willy is awesome.");
910             return "<b>" + renderedText + "</b>";
911         }};
912         CHECK(tmpl.render(data) == "<b>Willy is awesome.</b>");
913     }
914 
915     SECTION("manual-render-lambda_t") {
916         mustache tmpl{"{{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
917         data data;
918         data["name"] = "Willy";
919 #if MUSTACHE_VS2013
920         data["wrapped"] = lambda_t{lambda_t::type2{[](const std::string& text, const renderer& render) {
921 #else
922         data["wrapped"] = lambda_t{{[](const std::string& text, const renderer& render) {
923 #endif
924             CHECK(text == "{{name}} is awesome.");
925             const auto renderedText = render(text);
926             CHECK(renderedText == "Willy is awesome.");
927             return "<b>" + renderedText + "</b>";
928         }}};
929         CHECK(tmpl.render(data) == "<b>Willy is awesome.</b>");
930     }
931 
932     SECTION("manual-render-append-tag") {
933         // When using the render lambda, any text returned should not be itself rendered.
934         mustache tmpl{"{{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
935         data data;
936         data["name"] = "Willy";
937         data["wrapped"] = lambda2{[](const std::string& text, const renderer& render) {
938             CHECK(text == "{{name}} is awesome.");
939             const auto renderedText = render(text);
940             CHECK(renderedText == "Willy is awesome.");
941             return "<b>" + renderedText + "</b>Hello {{name}}.";
942         }};
943         CHECK(tmpl.render(data) == "<b>Willy is awesome.</b>Hello {{name}}.");
944     }
945 
946     SECTION("manual-render-error") {
947         mustache tmpl{"{{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
948         data data;
949         data["name"] = "Willy";
950         data["wrapped"] = lambda2{[](const std::string& text, const renderer& render) -> mustache::string_type {
951             CHECK(text == "{{name}} is awesome.");
952             const auto renderedText = render("{{name is awesome");
953             CHECK(renderedText == "");
954             return {};
955         }};
956         CHECK(tmpl.render(data) == "");
957         CHECK_FALSE(tmpl.is_valid());
958         CHECK(tmpl.error_message() == "Unclosed tag at 0");
959     }
960 
961     SECTION("lambda-render-variable") {
962         mustache tmpl{"{{name}} is awesome."};
963         data data;
964         data["name"] = lambda2{[](const std::string&, const renderer&) -> mustache::string_type {
965             return {};
966         }};
967         CHECK(tmpl.render(data) == "");
968         CHECK_FALSE(tmpl.is_valid());
969         CHECK(tmpl.error_message() == "Lambda with render argument is not allowed for regular variables");
970     }
971 
972     SECTION("lambda-render-bug38") {
973         mustache tmpl{"It is true that {{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
974         data data;
975         data["name"] = "Willy";
976         data["wrapped"] = lambda2{[](const std::string& text, const renderer& render) {
977             const auto renderedText = render(text);
978             return "<b>" + renderedText + "</b>";
979         }};
980         CHECK(tmpl.render(data) == "It is true that <b>Willy is awesome.</b>");
981     }
982 
983     SECTION("lambda-render-multiple-times") {
984         mustache tmpl{"It is true that {{#wrapped}}{{name}} is awesome.{{/wrapped}}"};
985         data data;
986         data["name"] = "Willy";
987         data["wrapped"] = lambda2{[](const std::string& text, const renderer& render) {
988             CHECK(text == "{{name}} is awesome.");
989             const auto pre_lambda_text = render("");
990             CHECK(pre_lambda_text.empty());
991             const auto renderedText = render(text);
992             CHECK(renderedText == "Willy is awesome.");
993             CHECK(render("").empty());
994             CHECK(render(text) == "Willy is awesome.");
995             return pre_lambda_text + "<b>" + renderedText + "</b>";
996         }};
997         CHECK(tmpl.render(data) == "It is true that <b>Willy is awesome.</b>");
998     }
999 
1000 }
1001 
1002 TEST_CASE("custom_escape") {
1003 
1004     SECTION("basic") {
1005         mustache tmpl("printf(\"Say {{password}} and enter\");{{&newline}}");
1006         tmpl.set_custom_escape([](const std::string& s) {
1007             std::string ret; ret.reserve(s.size());
1008             for (const auto ch: s) {
1009                 switch (ch) {
1010                     case '\"':
1011                     case '\n':
1012                         ret.append({'\\', ch});
1013                         break;
1014                     default:
1015                         ret.append(1, ch);
1016                         break;
1017                 }
1018             }
1019             return ret;
1020         });
1021         object data{ { "password", "\"friend\"" }, { "newline", "\n" } };
1022         CHECK(tmpl.render(data) == "printf(\"Say \\\"friend\\\" and enter\");\n");
1023     }
1024 
1025     SECTION("no_html_escape") {
1026         // make sure when using a custom escape that HTML is not escaped
1027         mustache tmpl("hello {{world}}");
1028         tmpl.set_custom_escape([](const std::string& s) {
1029             // doing nothing here
1030             return s;
1031         });
1032         object data{ { "world", "<world>" } };
1033         CHECK(tmpl.render(data) == "hello <world>");
1034     }
1035 
1036     SECTION("lambda") {
1037         mustache tmpl{"hello {{lambda}}"};
1038         data dat("lambda", data{lambda{[](const std::string&){
1039             return "\"friend\"";
1040         }}});
1041         tmpl.set_custom_escape([](const std::string& s) {
1042             std::string ret; ret.reserve(s.size());
1043             for (const auto ch: s) {
1044                 switch (ch) {
1045                     case '\"':
1046                     case '\n':
1047                         ret.append({'\\', ch});
1048                         break;
1049                     default:
1050                         ret.append(1, ch);
1051                         break;
1052                 }
1053             }
1054             return ret;
1055         });
1056         CHECK(tmpl.render(dat) == "hello \\\"friend\\\"");
1057     }
1058 
1059     SECTION("#lambda") {
1060         mustache tmpl{"hello {{#quote}}friend{{/quote}}"};
1061 #if MUSTACHE_VS2013
1062         data dat1("quote", data{lambda_t{lambda_t::type2{[](const std::string& s, const renderer& r){
1063 #else
1064         data dat1("quote", data{lambda_t{{[](const std::string& s, const renderer& r){
1065 #endif
1066             return r("<\"" + s + "\">", true);
1067         }}}});
1068 #if MUSTACHE_VS2013
1069         data dat2("quote", data{lambda_t{lambda_t::type2{[](const std::string& s, const renderer& r){
1070 #else
1071         data dat2("quote", data{lambda_t{{[](const std::string& s, const renderer& r){
1072 #endif
1073             return r("<\"" + s + "\">", false);
1074         }}}});
1075         tmpl.set_custom_escape([](const std::string& s) {
1076             std::string ret; ret.reserve(s.size());
1077             for (const auto ch: s) {
1078                 switch (ch) {
1079                     case '\"':
1080                     case '\n':
1081                         ret.append({'\\', ch});
1082                         break;
1083                     default:
1084                         ret.append(1, ch);
1085                         break;
1086                 }
1087             }
1088             return ret;
1089         });
1090         CHECK(tmpl.render(dat1) == "hello <\\\"friend\\\">");
1091         CHECK(tmpl.render(dat2) == "hello <\"friend\">");
1092     }
1093 
1094     SECTION("partial") {
1095         mustache tmpl{"hello {{>partial}}"};
1096         object dat({{"what", "\"friend\""}, {"partial", data{partial{[](){
1097             return "{{what}}";
1098         }}}}});
1099         tmpl.set_custom_escape([](const std::string& s) {
1100             std::string ret; ret.reserve(s.size());
1101             for (const auto ch: s) {
1102                 switch (ch) {
1103                     case '\"':
1104                     case '\n':
1105                         ret.append({'\\', ch});
1106                         break;
1107                     default:
1108                         ret.append(1, ch);
1109                         break;
1110                 }
1111             }
1112             return ret;
1113         });
1114         CHECK(tmpl.render(dat) == "hello \\\"friend\\\"");
1115     }
1116 
1117     SECTION("none") {
1118         mustache tmpl("hello {{what}}");
1119         mustache::escape_handler esc;
1120         tmpl.set_custom_escape(esc);
1121         object dat({ {"what", "\"friend\""} });
1122         CHECK_THROWS_AS(tmpl.render(dat), std::bad_function_call&);
1123     }
1124 
1125 }
1126 
1127 template <typename string_type>
1128 class my_context : public basic_context<string_type> {
1129 public:
1130     my_context()
1131         : value_("Steve")
1132     {
1133     }
1134 
1135     virtual void push(const basic_data<string_type>* /*data*/) override {
1136     }
1137 
1138     virtual void pop() override {
1139     }
1140 
1141     virtual const basic_data<string_type>* get(const string_type& name) const override {
1142         if (name == "what") {
1143             return &value_;
1144         }
1145         return nullptr;
1146     }
1147 
1148     virtual const basic_data<string_type>* get_partial(const string_type& /*name*/) const override {
1149         return nullptr;
1150     }
1151 
1152 private:
1153     basic_data<string_type> value_;
1154 };
1155 
1156 TEST_CASE("custom_context") {
1157 
1158     SECTION("basic") {
1159         my_context<mustache::string_type> ctx;
1160         mustache tmpl("Hello {{what}}");
1161         std::ostream& stream = tmpl.render(ctx, std::cout) << std::endl;
1162         CHECK(tmpl.is_valid());
1163         CHECK(tmpl.error_message() == "");
1164         CHECK(tmpl.render(ctx) == "Hello Steve");
1165     }
1166 
1167     SECTION("empty") {
1168         my_context<mustache::string_type> ctx;
1169         mustache tmpl("Hello {{world}}");
1170         CHECK(tmpl.render(ctx) == "Hello ");
1171     }
1172 
1173 }
1174 
1175 template <typename string_type>
1176 class file_partial_context : public context<string_type> {
1177 public:
1178     file_partial_context(const basic_data<string_type>* data)
1179         : context<string_type>(data)
1180     {
1181     }
1182 
1183     virtual const basic_data<string_type>* get_partial(const string_type& name) const override {
1184         const auto cached = cached_files_.find(name);
1185         REQUIRE(cached == cached_files_.end());
1186         string_type result;
1187         REQUIRE(read_file(name, result));
1188         return &cached_files_.insert(std::make_pair(name, basic_data<string_type>(result))).first->second;
1189     }
1190 
1191 private:
1192     bool read_file(const string_type& name, string_type& file_contents) const {
1193         // read from file [name].mustache (fake the data for the test)
1194         REQUIRE(name == "what");
1195         file_contents = "World";
1196         return true;
1197     }
1198 
1199     mutable std::unordered_map<string_type, basic_data<string_type>> cached_files_;
1200 };
1201 
1202 TEST_CASE("file_partial_context") {
1203 
1204     data dat("punctuation", "!");
1205     file_partial_context<mustache::string_type> ctx{&dat};
1206     mustache tmpl("Hello {{>what}}{{punctuation}}");
1207     CHECK(tmpl.render(ctx) == "Hello World!");
1208 
1209 }
1210 
1211 TEST_CASE("standalone_lines") {
1212 
1213     SECTION("parse_whitespace_basic") {
1214         const mustache::string_type input = "\n\r\n\t \n\n\r";
1215         component<mustache::string_type> root_component;
1216         mustache::string_type error_message;
1217         context<mustache::string_type> ctx;
1218         context_internal<mustache::string_type> context{ctx};
1219         parser<mustache::string_type>{input, context, root_component, error_message};
1220         CHECK(error_message.empty());
1221         const auto& root_children = root_component.children;
1222         const std::vector<mustache::string_type> text_components{"\n", "\r\n", "\t", " ", "\n", "\n", "\r"};
1223         REQUIRE(root_component.children.size() == 7);
1224         REQUIRE(root_component.children.size() == text_components.size());
1225         std::vector<mustache::string_type>::size_type i = 0;
1226         for (const auto& child : root_component.children) {
1227             CHECK(child.text == text_components[i++]);
1228             CHECK(child.tag.type == tag_type::text);
1229             CHECK(child.children.empty());
1230         }
1231         CHECK(root_component.children[0].is_newline());
1232         CHECK_FALSE(root_component.children[0].is_non_newline_whitespace());
1233         CHECK(root_component.children[1].is_newline());
1234         CHECK_FALSE(root_component.children[1].is_non_newline_whitespace());
1235         CHECK_FALSE(root_component.children[2].is_newline());
1236         CHECK(root_component.children[2].is_non_newline_whitespace());
1237         CHECK_FALSE(root_component.children[3].is_newline());
1238         CHECK(root_component.children[3].is_non_newline_whitespace());
1239         CHECK(root_component.children[4].is_newline());
1240         CHECK_FALSE(root_component.children[4].is_non_newline_whitespace());
1241         CHECK(root_component.children[5].is_newline());
1242         CHECK_FALSE(root_component.children[5].is_non_newline_whitespace());
1243         CHECK(root_component.children[6].is_newline());
1244         CHECK_FALSE(root_component.children[6].is_non_newline_whitespace());
1245     }
1246 
1247     SECTION("parse_whitespace") {
1248         const mustache::string_type input =
1249         "|\n"
1250         "| This Is\n"
1251         "{{#boolean}}\n"
1252         "|\n"
1253         "{{/boolean}}\n"
1254         "| A Line";
1255         component<mustache::string_type> root_component;
1256         mustache::string_type error_message;
1257         context<mustache::string_type> ctx;
1258         context_internal<mustache::string_type> context{ctx};
1259         parser<mustache::string_type>{input, context, root_component, error_message};
1260         CHECK(error_message.empty());
1261         const auto& root_children = root_component.children;
1262         REQUIRE(root_children.size() == 15);
1263         CHECK(root_children[0].text == "|");
1264         CHECK(root_children[0].tag.type == tag_type::text);
1265         CHECK(root_children[0].children.empty());
1266         CHECK(root_children[1].text == "\n");
1267         CHECK(root_children[1].tag.type == tag_type::text);
1268         CHECK(root_children[1].children.empty());
1269         CHECK(root_children[2].text == "|");
1270         CHECK(root_children[2].tag.type == tag_type::text);
1271         CHECK(root_children[2].children.empty());
1272         CHECK(root_children[3].text == " ");
1273         CHECK(root_children[3].tag.type == tag_type::text);
1274         CHECK(root_children[3].children.empty());
1275         CHECK(root_children[4].text == "This");
1276         CHECK(root_children[4].tag.type == tag_type::text);
1277         CHECK(root_children[4].children.empty());
1278         CHECK(root_children[5].text == " ");
1279         CHECK(root_children[5].tag.type == tag_type::text);
1280         CHECK(root_children[5].children.empty());
1281         CHECK(root_children[6].text == "Is");
1282         CHECK(root_children[6].tag.type == tag_type::text);
1283         CHECK(root_children[6].children.empty());
1284         CHECK(root_children[7].text == "\n");
1285         CHECK(root_children[7].tag.type == tag_type::text);
1286         CHECK(root_children[7].children.empty());
1287         CHECK(root_children[8].text.empty());
1288         CHECK(root_children[8].tag.type == tag_type::section_begin);
1289         REQUIRE(root_children[8].children.size() == 3);
1290         CHECK(root_children[8].children[0].text == "\n");
1291         CHECK(root_children[8].children[0].tag.type == tag_type::text);
1292         CHECK(root_children[8].children[0].children.empty());
1293         CHECK(root_children[8].children[1].text == "|");
1294         CHECK(root_children[8].children[1].tag.type == tag_type::text);
1295         CHECK(root_children[8].children[1].children.empty());
1296         CHECK(root_children[8].children[2].text == "\n");
1297         CHECK(root_children[8].children[2].tag.type == tag_type::text);
1298         CHECK(root_children[8].children[2].children.empty());
1299         CHECK(root_children[9].text == "\n");
1300         CHECK(root_children[9].tag.type == tag_type::text);
1301         CHECK(root_children[9].children.empty());
1302         CHECK(root_children[10].text == "|");
1303         CHECK(root_children[10].tag.type == tag_type::text);
1304         CHECK(root_children[10].children.empty());
1305         CHECK(root_children[11].text == " ");
1306         CHECK(root_children[11].tag.type == tag_type::text);
1307         CHECK(root_children[11].children.empty());
1308         CHECK(root_children[12].text == "A");
1309         CHECK(root_children[12].tag.type == tag_type::text);
1310         CHECK(root_children[12].children.empty());
1311         CHECK(root_children[13].text == " ");
1312         CHECK(root_children[13].tag.type == tag_type::text);
1313         CHECK(root_children[13].children.empty());
1314         CHECK(root_children[14].text == "Line");
1315         CHECK(root_children[14].tag.type == tag_type::text);
1316         CHECK(root_children[14].children.empty());
1317     }
1318 
1319     SECTION("remove_standalone_lines") {
1320         mustache tmpl{
1321             "|\n"
1322             "| This Is\n"
1323             "{{#boolean}}\n"
1324             "|\n"
1325             "{{/boolean}}\n"
1326             "| A Line"
1327         };
1328         data data("boolean", true);
1329         CHECK(tmpl.render(data) ==
1330             "|\n"
1331             "| This Is\n"
1332             "|\n"
1333             "| A Line"
1334         );
1335     }
1336 
1337     SECTION("remove_indented_standalone_lines") {
1338         mustache tmpl{
1339             "|\n"
1340             "| This Is\n"
1341             "  {{#boolean}}\n"
1342             "|\n"
1343             "  {{/boolean}}\n"
1344             "| A Line"
1345         };
1346         data data("boolean", true);
1347         CHECK(tmpl.render(data) ==
1348             "|\n"
1349             "| This Is\n"
1350             "|\n"
1351             "| A Line"
1352         );
1353     }
1354 
1355     SECTION("crlf") {
1356         mustache tmpl{"|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|"};
1357         data data("boolean", true);
1358         CHECK(tmpl.render(data) == "|\r\n|");
1359     }
1360 
1361     SECTION("without_previous_line") {
1362         mustache tmpl{"  {{#boolean}}\n#{{/boolean}}\n/"};
1363         data data("boolean", true);
1364         CHECK(tmpl.render(data) == "#\n/");
1365     }
1366 
1367     SECTION("without_next_newline") {
1368         mustache tmpl{"#{{#boolean}}\n/\n  {{/boolean}}"};
1369         data data("boolean", true);
1370         CHECK(tmpl.render(data) == "#\n/\n");
1371     }
1372 
1373     SECTION("section_list") {
1374         mustache tmpl{
1375             "Text1\n"
1376             "{{#section}}\n"
1377             "Text2\n"
1378             "{{/section}}\n"
1379             "Text3\n"
1380         };
1381         const list section{
1382             "Text2",
1383             "Text2",
1384             "Text2",
1385         };
1386         CHECK(tmpl.render(data{"section", section}) ==
1387             "Text1\n"
1388             "Text2\n"
1389             "Text2\n"
1390             "Text2\n"
1391             "Text3\n"
1392         );
1393     }
1394 
1395     SECTION("section_list_partial_inline") {
1396         mustache tmpl{
1397             "Text1\n"
1398             "{{#section}}blah\n"
1399             "{{/section}}\n"
1400             "Text3\n"
1401         };
1402         const list section{
1403             "Text2",
1404             "Text2",
1405             "Text2",
1406         };
1407         CHECK(tmpl.render(data{"section", section}) ==
1408             "Text1\n"
1409             "blah\n"
1410             "blah\n"
1411             "blah\n"
1412             "Text3\n"
1413         );
1414     }
1415 
1416     SECTION("section_list_full_inline") {
1417         mustache tmpl{
1418             "Text1\n"
1419             "{{#section}}blah{{/section}}\n"
1420             "Text3\n"
1421         };
1422         const list section{
1423             "Text2",
1424             "Text2",
1425             "Text2",
1426         };
1427         CHECK(tmpl.render(data{"section", section}) ==
1428             "Text1\n"
1429             "blahblahblah\n"
1430             "Text3\n"
1431         );
1432     }
1433 
1434     SECTION("section_list_not_empty_lines") {
1435         mustache tmpl{
1436             "Text1\n"
1437             "{{#section}}a\n"
1438             "test {{.}}\n"
1439             "{{/section}}b\n"
1440             "Text3\n"
1441         };
1442         const list section{
1443             "a",
1444             "b",
1445             "c",
1446         };
1447         CHECK(tmpl.render(data{"section", section}) ==
1448             "Text1\n"
1449             "a\n"
1450             "test a\n"
1451             "a\n"
1452             "test b\n"
1453             "a\n"
1454             "test c\n"
1455             "b\n"
1456             "Text3\n"
1457         );
1458     }
1459 
1460     SECTION("partial_indent_bug") {
1461         mustache tmpl{
1462             "No indent\n"
1463             "    Indent\n"
1464             "    {{>partial1}}Indent\n"
1465             "    {{>partial2}}Indent\n"
1466             "    {{>partial3}}    Indent\n"
1467             "    {{>partial4}}    Indent\n"
1468             "    {{>partial5}}\n"
1469             "No indent\n"
1470         };
1471         data dat;
1472         dat.set("partial1", partial{[]{
1473             return "{{#section}}{{/section}}";
1474         }});
1475         dat.set("partial2", partial{[]{
1476             return "{{#invalidSection}}{{/invalidSection}}";
1477         }});
1478         dat.set("partial3", partial{[]{
1479             return "    {{#section}}Indent more{{/section}}\n";
1480         }});
1481         dat.set("partial4", partial{[]{
1482             // produces a whitespace-only line, which gets removed, then a "Hello" line
1483             return "    {{#section}}{{/section}}\n    Hello\n";
1484         }});
1485         dat.set("partial5", partial{[]{
1486             // produces whitespace-only, which gets removed because parent line is also whitespace only
1487             return "    {{#section}}{{/section}}";
1488         }});
1489         dat.set("section", data::type::bool_true);
1490         CHECK(tmpl.render(dat) ==
1491             "No indent\n"
1492             "    Indent\n"
1493             "    Indent\n"
1494             "    Indent\n"
1495             "        Indent more\n"
1496             "    Indent\n"
1497             "    Hello\n"
1498             "    Indent\n"
1499             "No indent\n"
1500         );
1501     }
1502 
1503 }
1504 
1505