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 "S"<br>te&v'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) == "<>>"); 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