1context("tags") 2 3test_that("Basic tag writing works", { 4 expect_equal(as.character(tagList("hi")), "hi") 5 expect_equal( 6 as.character(tagList("one", "two", tagList("three"))), 7 "one\ntwo\nthree") 8 expect_equal( 9 as.character(tags$b("one")), 10 "<b>one</b>") 11 expect_equal( 12 as.character(tags$b("one", "two")), 13 "<b>\n one\n two\n</b>") 14 expect_equal( 15 as.character(tagList(list("one"))), 16 "one") 17 expect_equal( 18 as.character(tagList(list(tagList("one")))), 19 "one") 20 expect_equal( 21 as.character(tagList(tags$br(), "one")), 22 "<br/>\none") 23}) 24 25test_that("Hanging commas don't break things", { 26 expect_equal(as.character(tagList("hi",)), "hi") 27 expect_equal(as.character(div("one",)), "<div>one</div>") 28 # Multiple commas still throw 29 expect_error(as.character(div("one",,)), "is empty") 30 # Non-trailing commas still throw 31 expect_error(as.character(div(,"one",)), "is empty") 32}) 33 34 35test_that("withTags works", { 36 output_tags <- tags$div(class = "myclass", 37 tags$h3("header"), 38 tags$p("text here") 39 ) 40 output_withhtml <- withTags( 41 div(class = "myclass", 42 h3("header"), 43 p("text here") 44 ) 45 ) 46 expect_identical(output_tags, output_withhtml) 47 48 49 # Check that current environment is searched 50 x <- 100 51 expect_identical(tags$p(x), withTags(p(x))) 52 53 # Just to make sure, run it in a function, which has its own environment 54 foo <- function() { 55 y <- 100 56 withTags(p(y)) 57 } 58 expect_identical(tags$p(100), foo()) 59}) 60 61test_that(".noWS argument of withTags()", { 62 get_noWS <- function(tag) tag[[".noWS"]] 63 64 default <- withTags( 65 div( 66 class = "myclass", 67 h3("header"), 68 p("One", strong(span("two")), "three") 69 ) 70 ) 71 72 expect_null(get_noWS(default)) 73 expect_null(get_noWS(default$children[[1]])) 74 expect_null(get_noWS(default$children[[2]])) 75 expect_null(get_noWS(default$children[[2]]$children[[2]])) 76 expect_null(get_noWS(default$children[[2]]$children[[2]]$children[[1]])) 77 78 default_special <- withTags( 79 div( 80 class = "myclass", 81 h3("header", .noWS = "after-begin"), 82 p("One", strong(span("two")), "three", .noWS = "before-end") 83 ) 84 ) 85 86 expect_null(get_noWS(default_special)) 87 expect_equal(get_noWS(default_special$children[[1]]), "after-begin") 88 expect_equal(get_noWS(default_special$children[[2]]), "before-end") 89 expect_null(get_noWS(default_special$children[[2]]$children[[2]])) 90 expect_null(get_noWS(default_special$children[[2]]$children[[2]]$children[[1]])) 91 92 all_same_noWS <- c("outside", "inside") 93 all_same <- withTags( 94 div( 95 class = "myclass", 96 h3("header"), 97 p("One", strong(span("two")), "three") 98 ), 99 .noWS = all_same_noWS 100 ) 101 102 expect_equal(get_noWS(all_same), all_same_noWS) 103 expect_equal(get_noWS(all_same$children[[1]]), all_same_noWS) 104 expect_equal(get_noWS(all_same$children[[2]]), all_same_noWS) 105 expect_equal(get_noWS(all_same$children[[2]]$children[[2]]), all_same_noWS) 106 expect_equal(get_noWS(all_same$children[[2]]$children[[2]]$children[[1]]), all_same_noWS) 107 108 varied_default <- "outside" 109 varied_special <- "inside" 110 varied <- withTags( 111 div( 112 class = "myclass", 113 h3("header"), 114 p("One", strong(span("two"), .noWS = varied_special), "three") 115 ), 116 .noWS = varied_default 117 ) 118 119 expect_equal(get_noWS(varied), varied_default) 120 expect_equal(get_noWS(varied$children[[1]]), varied_default) 121 expect_equal(get_noWS(varied$children[[2]]), varied_default) 122 expect_equal(get_noWS(varied$children[[2]]$children[[2]]), varied_special) 123 expect_equal(get_noWS(varied$children[[2]]$children[[2]]$children[[1]]), varied_default) 124}) 125 126test_that("HTML escaping in tags", { 127 # Regular text is escaped 128 expect_equivalent(format(div("<a&b>")), "<div><a&b></div>") 129 130 # Text in HTML() isn't escaped 131 expect_equivalent(format(div(HTML("<a&b>"))), "<div><a&b></div>") 132 133 # Text in a property is escaped 134 expect_equivalent(format(div(class = "<a&b>", "text")), 135 '<div class="<a&b>">text</div>') 136 137 # HTML() has no effect in a property like 'class' 138 expect_equivalent(format(div(class = HTML("<a&b>"), "text")), 139 '<div class="<a&b>">text</div>') 140}) 141 142 143test_that("Adding child tags", { 144 tag_list <- list(tags$p("tag1"), tags$b("tag2"), tags$i("tag3")) 145 146 # Creating nested tags by calling the tag$div function and passing a list 147 t1 <- tags$div(class="foo", tag_list) 148 expect_equal(length(t1$children), 1) 149 expect_equal(length(t1$children[[1]]), 3) 150 expect_equal(t1$children[[1]][[1]]$name, "p") 151 expect_equal(t1$children[[1]][[1]]$children[[1]], "tag1") 152 expect_equal(t1$children[[1]][[2]]$name, "b") 153 expect_equal(t1$children[[1]][[2]]$children[[1]], "tag2") 154 expect_equal(t1$children[[1]][[3]]$name, "i") 155 expect_equal(t1$children[[1]][[3]]$children[[1]], "tag3") 156 157 158 # div tag used as starting point for tests below 159 div_tag <- tags$div(class="foo") 160 161 # Appending each child 162 t2 <- tagAppendChild(div_tag, tag_list[[1]]) 163 t2 <- tagAppendChild(t2, tag_list[[2]]) 164 t2 <- tagAppendChild(t2, tag_list[[3]]) 165 t2a <- do.call(tags$div, c(tag_list, class="foo")) 166 expect_identical(t2a, t2) 167 t2b <- tagAppendChildren(div_tag, `names_are_ignored` = tag_list[[1]], 168 "ignore-this-name" = tag_list[[2]], 169 dummyName = tag_list[[3]]) 170 expect_identical(t2b, t2) 171 172 # tagSetChildren, using list argument 173 t2 <- tagSetChildren(div_tag, list = tag_list) 174 expect_identical(t2a, t2) 175 176 # tagSetChildren, using ... arguments 177 t2 <- tagSetChildren(div_tag, tag_list[[1]], tag_list[[2]], tag_list[[3]]) 178 expect_identical(t2a, t2) 179 180 # tagSetChildren, using named ... arguments (names should be ignored) 181 t2 <- tagSetChildren(div_tag, ignored = tag_list[[1]], dummy = tag_list[[2]], blah = tag_list[[3]]) 182 expect_identical(t2a, t2) 183 184 # tagSetChildren, using ... and list arguments 185 t2 <- tagSetChildren(div_tag, tag_list[[1]], list = tag_list[2:3]) 186 expect_identical(t2a, t2) 187 188 # tagSetChildren overwrites existing children 189 t2 <- tagAppendChild(div_tag, p("should replace this tag")) 190 t2 <- tagSetChildren(div_tag, list = tag_list) 191 expect_identical(t2a, t2) 192 193 194 # tagAppendChildren, using list argument 195 t2 <- tagAppendChild(div_tag, tag_list[[1]]) 196 t2 <- tagAppendChildren(t2, list = tag_list[2:3]) 197 expect_identical(t2a, t2) 198 199 # tagAppendChildren, using ... arguments 200 t2 <- tagAppendChild(div_tag, tag_list[[1]]) 201 t2 <- tagAppendChildren(t2, tag_list[[2]], tag_list[[3]]) 202 expect_identical(t2a, t2) 203 204 # tagAppendChildren, using ... and list arguments 205 t2 <- tagAppendChild(div_tag, tag_list[[1]]) 206 t2 <- tagAppendChildren(t2, tag_list[[2]], list = list(tag_list[[3]])) 207 expect_identical(t2a, t2) 208 209 # tagAppendChildren can start with no children 210 t2 <- tagAppendChildren(div_tag, list = tag_list) 211 expect_identical(t2a, t2) 212 213 214 # tagSetChildren preserves attributes 215 x <- tagSetChildren(div(), HTML("text")) 216 expect_identical(attr(x$children[[1]], "html", TRUE), TRUE) 217 218 # tagAppendChildren preserves attributes 219 x <- tagAppendChildren(div(), HTML("text")) 220 expect_identical(attr(x$children[[1]], "html", TRUE), TRUE) 221}) 222 223 224test_that("Creating simple tags", { 225 # Empty tag 226 expect_identical( 227 div(), 228 structure( 229 list(name = "div", attribs = dots_list(), children = list()), 230 .Names = c("name", "attribs", "children"), 231 class = "shiny.tag" 232 ) 233 ) 234 235 # Tag with text 236 expect_identical( 237 div("text"), 238 structure( 239 list(name = "div", attribs = dots_list(), children = list("text")), 240 .Names = c("name", "attribs", "children"), 241 class = "shiny.tag" 242 ) 243 ) 244 245 # NULL attributes are dropped 246 expect_identical( 247 div(a = NULL, b = "value"), 248 div(b = "value") 249 ) 250 251 # length-0 attributes are dropped 252 expect_identical( 253 div(a = character(), b = "value"), 254 div(b = "value") 255 ) 256 257 # NULL children are dropped 258 expect_identical( 259 renderTags(div("foo", NULL, list(NULL, list(NULL, "bar"))))$html, 260 renderTags(div("foo", "bar"))$html 261 ) 262 263 # length-0 children are dropped 264 expect_identical( 265 renderTags(div("foo", character(), list(character(), list(list(), "bar"))))$html, 266 renderTags(div("foo", "bar"))$html 267 ) 268 269 # Numbers are coerced to strings 270 expect_identical( 271 renderTags(div(1234))$html, 272 renderTags(div("1234"))$html 273 ) 274}) 275 276 277test_that("Creating nested tags", { 278 # Simple version 279 # Note that the $children list should not have a names attribute 280 expect_identical( 281 div(class="foo", list("a", "b")), 282 structure( 283 list(name = "div", 284 attribs = structure(list(class = "foo"), .Names = "class"), 285 children = list(list("a", "b"))), 286 .Names = c("name", "attribs", "children"), 287 class = "shiny.tag" 288 ) 289 ) 290 291 # More complex version 292 t1 <- withTags( 293 div(class = "foo", 294 p("child tag"), 295 list( 296 p("in-list child tag 1"), 297 "in-list character string", 298 p(), 299 p("in-list child tag 2") 300 ), 301 "character string", 302 1234 303 ) 304 ) 305 306 # t1 should be identical to this data structure. 307 # The nested list should be flattened, and non-tag, non-strings should be 308 # converted to strings 309 t1_full <- structure( 310 list( 311 name = "div", 312 attribs = list(class = "foo"), 313 children = list( 314 structure(list(name = "p", 315 attribs = list(), 316 children = list("child tag")), 317 class = "shiny.tag" 318 ), 319 structure(list(name = "p", 320 attribs = list(), 321 children = list("in-list child tag 1")), 322 class = "shiny.tag" 323 ), 324 "in-list character string", 325 structure(list(name = "p", 326 attribs = list(), 327 children = list()), 328 class = "shiny.tag" 329 ), 330 structure(list(name = "p", 331 attribs = list(), 332 children = list("in-list child tag 2")), 333 class = "shiny.tag" 334 ), 335 "character string", 336 "1234" 337 ) 338 ), 339 class = "shiny.tag" 340 ) 341 342 expect_identical(renderTags(t1)$html, renderTags(t1_full)$html) 343}) 344 345# The .noWS option was added in 0.3.6.9003; we may still encounter tags created 346# in an older version (perhaps saved to an RDS file and restored). They would 347# lack this element in their structure. 348test_that("Old tags without the .noWS option can still be rendered", { 349 oldTag <- structure( 350 list(name = "div", attribs = dots_list(), children = list("text")), 351 .Names = c("name", "attribs", "children"), 352 class = "shiny.tag" 353 ) 354 w <- WSTextWriter() 355 tagWrite(oldTag, w) 356 357 expect_identical( 358 w$readAll(), 359 "<div>text</div>\n" 360 ) 361}) 362 363# We moved to rlang::dots_list in 0.3.6; we may still encounter tags created 364# in an older version (perhaps saved to an RDS file and restored). They would 365# use old-school lists. 366test_that("Old tags predating rlang::list2 can still be rendered", { 367 oldTag <- structure( 368 list(name = "div", attribs = list(), children = list("text")), 369 .Names = c("name", "attribs", "children"), 370 class = "shiny.tag" 371 ) 372 w <- WSTextWriter() 373 tagWrite(oldTag, w) 374 375 expect_identical( 376 w$readAll(), 377 "<div>text</div>\n" 378 ) 379}) 380 381test_that("tag with noWS works",{ 382 oneline <- tag("span", list(tag("strong", "Super strong", .noWS="outside"))) 383 expect_identical(as.character(oneline), "<span><strong>Super strong</strong></span>") 384}) 385 386test_that("tag/s with invalid noWS fails fast", { 387 expect_error(tag("span", .noWS="wrong")) 388 expect_error(tags$a(.noWS="wrong")) 389}) 390 391test_that("Attributes are preserved", { 392 # HTML() adds an attribute to the data structure (note that this is 393 # different from the 'attribs' field in the list) 394 x <- HTML("<tag>&&</tag>") 395 expect_identical(attr(x, "html", TRUE), TRUE) 396 expect_equivalent(format(x), "<tag>&&</tag>") 397 398 # Make sure attributes are preserved when wrapped in other tags 399 x <- div(HTML("<tag>&&</tag>")) 400 expect_equivalent(x$children[[1]], HTML("<tag>&&</tag>")) 401 expect_identical(attr(x$children[[1]], "html", TRUE), TRUE) 402 expect_equivalent(format(x), "<div><tag>&&</tag></div>") 403 404 # Deeper nesting 405 x <- div(p(HTML("<tag>&&</tag>"))) 406 expect_equivalent(x$children[[1]]$children[[1]], HTML("<tag>&&</tag>")) 407 expect_identical(attr(x$children[[1]]$children[[1]], "html", TRUE), TRUE) 408 expect_equivalent(format(x), "<div>\n <p><tag>&&</tag></p>\n</div>") 409}) 410 411test_that("Adding attributes to tags", { 412 t1 <- tags$div("foo") 413 414 # Adding attributes to empty tag 415 expect_identical(t1$attribs, dots_list()) 416 expect_identical( 417 tagAppendAttributes(t1, class = "c1")$attribs, 418 list(class = "c1") 419 ) 420 421 # Adding attribute with multiple values 422 expect_identical( 423 tagAppendAttributes(t1, class = "c1 c2")$attribs, 424 list(class = "c1 c2") 425 ) 426 427 # Adding two different attributes 428 expect_identical( 429 tagAppendAttributes(t1, class = "c1", id = "foo")$attribs, 430 list(class = "c1", id = "foo") 431 ) 432 433 # Adding attributes in two successive calls 434 expect_identical( 435 tagAppendAttributes( 436 tagAppendAttributes(t1, class = "c1 c2"), class = "c3")$attribs, 437 list(class = "c1 c2", class = "c3") 438 ) 439 440 # Adding empty attributes 441 expect_identical( 442 tagAppendAttributes(t1, class = NULL)$attribs, 443 list() 444 ) 445 expect_identical( 446 tagAppendAttributes( 447 tagAppendAttributes(t1, class = "hidden"), class = NULL)$attribs, 448 list(class = "hidden") 449 ) 450 451 t2 <- tags$div("foo", class = "c1") 452 453 # Adding attributes on a tag with other attributes 454 expect_identical( 455 tagAppendAttributes(t2, id = "foo")$attribs, 456 list(class = "c1", id = "foo") 457 ) 458 459 # Adding attributes on a tag with the same attribute 460 expect_identical( 461 tagAppendAttributes(t2, class = "c2")$attribs, 462 list(class = "c1", class = "c2") 463 ) 464}) 465 466test_that("Adding unnamed attributes creates a warning", { 467 expect_error( 468 tagAppendAttributes( 469 tags$div(), 470 "value" 471 ), 472 "include an attribute name" 473 ) 474 475 x <- div() 476 x$attribs[[1]] <- "value" 477 expect_error( 478 print(x), 479 "name all of your attribute values" 480 ) 481}) 482 483 484 485test_that("Testing for attributes on tags", { 486 t1 <- tags$div("foo", class = "c1", class = "c2", id = "foo") 487 488 # Testing for attribute that does not exist 489 expect_identical( 490 tagHasAttribute(t1, "nope"), 491 FALSE 492 ) 493 494 # Testing for an attribute that exists once 495 expect_identical( 496 tagHasAttribute(t1, "id"), 497 TRUE 498 ) 499 500 # Testing for an attribute that exists multiple times 501 expect_identical( 502 tagHasAttribute(t1, "class"), 503 TRUE 504 ) 505 506 # Testing for substring of an attribute that exists 507 expect_identical( 508 tagHasAttribute(t1, "clas"), 509 FALSE 510 ) 511 512 # Testing for superstring of an attribute that exists 513 expect_identical( 514 tagHasAttribute(t1, "classes"), 515 FALSE 516 ) 517 518 # Testing for attribute with empty value 519 t2 <- tags$div("foo", foo = "") 520 expect_identical( 521 tagHasAttribute(t2, "foo"), 522 TRUE 523 ) 524 525 # Testing for attribute with NULL value 526 t3 <- tags$div("foo", foo = NULL) 527 expect_identical( 528 tagHasAttribute(t3, "foo"), 529 FALSE 530 ) 531}) 532 533test_that("Getting attributes from tags", { 534 # Getting an attribute from a tag with no attributes 535 t1 <- tags$div("foo") 536 expect_identical( 537 tagGetAttribute(t1, "class"), 538 NULL 539 ) 540 541 t2 <- tags$div("foo", class = "c1") 542 543 # Getting an attribute from a tag without the correct attribute 544 expect_identical( 545 tagGetAttribute(t2, "id"), 546 NULL 547 ) 548 549 # Getting an attribute from a tag with the a single value for the attribute 550 expect_identical( 551 tagGetAttribute(t2, "class"), 552 "c1" 553 ) 554 555 # Getting an attribute from a tag with multiple matching attributes 556 t3 <- tags$div("foo", class = "c1", id = "foo", class = "c2") 557 expect_identical( 558 tagGetAttribute(t3, "class"), 559 "c1 c2" 560 ) 561 562 # Getting an attribute from a tag where the attributes were factors 563 t4 <- tags$div("foo", class = as.factor("c1"), class = as.factor("c2")) 564 expect_identical( 565 tagGetAttribute(t4, "class"), 566 "c1 c2" 567 ) 568 569 # Getting a numeric attribute from a tag 570 t5 <- tags$div("foo", class = 78) 571 expect_identical( 572 tagGetAttribute(t5, "class"), 573 "78" 574 ) 575}) 576 577test_that("NA attributes are rendered correctly", { 578 expect_identical( 579 as.character(tags$div("text", foo = NA)), 580 '<div foo>text</div>' 581 ) 582 expect_identical( 583 as.character(tags$div("text", class = "a", foo = NA)), 584 '<div class="a" foo>text</div>' 585 ) 586 expect_identical( 587 as.character(tags$div("text", class = "a", foo = NA, class = "b")), 588 '<div class="a b" foo>text</div>' 589 ) 590 591 # Multiple NA's are coalesced 592 expect_identical( 593 as.character(tags$div("text", class = "a", foo = NA, class = "b", foo = NA)), 594 '<div class="a b" foo>text</div>' 595 ) 596 597 # A non-NA value supersedes NA 598 expect_identical( 599 as.character(tags$div("text", class = "a", foo = NA, foo = "b")), 600 '<div class="a" foo="b">text</div>' 601 ) 602 expect_identical( 603 as.character(tags$div("text", class = "a", foo = "b", foo = NA, foo = "c")), 604 '<div class="a" foo="b c">text</div>' 605 ) 606 expect_identical( 607 as.character(tags$div("text", class = "a", foo = "b", foo = NA, foo = NA, foo = "c")), 608 '<div class="a" foo="b c">text</div>' 609 ) 610}) 611 612test_that("NA attributes are retrieved correctly", { 613 expect_foo_attr <- function(y, ...) { 614 testTag <- tags$div("text", ...) 615 expect_identical( 616 tagGetAttribute(testTag, "foo"), 617 y 618 ) 619 } 620 expect_foo_attr(NA, foo = NA) 621 expect_foo_attr(NA, class = "a", foo = NA) 622 expect_foo_attr(NA, class = "a", foo = NA, class = "b") 623 624 # Multiple NA's are coalesced 625 expect_foo_attr(NA, class = "a", foo = NA, class = "b", foo = NA) 626 627 # A non-NA value supersedes NA 628 expect_foo_attr("b", class = "a", foo = NA, foo = "b") 629 expect_foo_attr("b c", class = "a", foo = "b", foo = NA, foo = "c") 630 expect_foo_attr("b c", class = "a", foo = "b", foo = NA, foo = NA, foo = "c") 631 632 # Non atomic value cause a list to be returned. 633 expect_foo_attr(list(list("b")), class = "a", foo = NA, foo = list("b")) 634 expect_foo_attr(list(list("b"), list("c")), class = "a", foo = list("b"), foo = NA, foo = list("c")) 635 expect_foo_attr(list("b", list("c")), class = "a", foo = "b", foo = NA, foo = NA, foo = list("c")) 636}) 637 638test_that("Tag list tree is rendered in DOM tree order", { 639 # Tree order is preorder, depth-first traversal 640 # https://dom.spec.whatwg.org/#concept-tree 641 # 642 # Test for preordered traversal/execution of tagFunction(). This allows one to 643 # rely on the side-effects of executing a tag, so long as those side-effects 644 # happen "towards the top" of the tree. Shiny implicitly assumes this 645 # behavior: execution of bootstrapLib() introduces a (temporary) side-effect 646 # that "down-stream" UI (i.e. sliderInput() et al) can use to inform their 647 # Sass -> CSS compilation 648 value <- NULL 649 lazyDiv <- div(tagFunction(function() { value })) 650 dom <- tagList( 651 lazyDiv, 652 div(tagList( 653 tagFunction(function() { value <<- 1 }) 654 )), 655 lazyDiv 656 ) 657 expect_identical( 658 as.character(dom), 659 "<div></div>\n<div>1</div>\n<div>1</div>" 660 ) 661}) 662 663 664test_that("Flattening a list of tags", { 665 # Flatten a nested list 666 nested <- list( 667 "a1", 668 list( 669 "b1", 670 list("c1", "c2"), 671 list(), 672 "b2", 673 list("d1", "d2") 674 ), 675 "a2" 676 ) 677 678 flat <- list("a1", "b1", "c1", "c2", "b2", "d1", "d2", "a2") 679 expect_identical(flattenTags(nested), flat) 680 681 # no-op for flat lists 682 expect_identical(flattenTags(list(a="1", "b")), list(a="1", "b")) 683 684 # numbers are coerced to character 685 expect_identical(flattenTags(list(a=1, "b")), list(a="1", "b")) 686 687 # empty list results in empty list 688 expect_identical(flattenTags(list()), list()) 689 690 # preserve attributes 691 nested <- list("txt1", list(structure("txt2", prop="prop2"))) 692 flat <- list("txt1", 693 structure("txt2", prop="prop2")) 694 expect_identical(flattenTags(nested), flat) 695}) 696 697test_that("Head and singleton behavior", { 698 result <- renderTags(tagList( 699 tags$head(singleton("hello")) 700 )) 701 702 expect_identical(result$html, HTML("")) 703 expect_identical(result$head, HTML(" hello")) 704 expect_identical(result$singletons, "089cce0335cf2bae2bcb08cc753ba56f8e1ea8ed") 705 706 # Ensure that "hello" actually behaves like a singleton 707 result2 <- renderTags(tagList( 708 tags$head(singleton("hello")) 709 ), singletons = result$singletons) 710 711 expect_identical(result$singletons, result2$singletons) 712 expect_identical(result2$head, HTML("")) 713 expect_identical(result2$html, HTML("")) 714 715 result3 <- renderTags(tagList( 716 tags$head(singleton("hello"), singleton("hello")) 717 )) 718 expect_identical(result$singletons, result3$singletons) 719 expect_identical(result3$head, HTML(" hello")) 720 721 # Ensure that singleton can be applied to lists, not just tags 722 result4 <- renderTags(list(singleton(list("hello")), singleton(list("hello")))) 723 expect_identical(result4$singletons, "110d1f0ef6762db2c6863523a7c379a697b43ea3") 724 expect_identical(result4$html, renderTags(HTML("hello"))$html) 725 726 result5 <- renderTags(tagList(singleton(list(list("hello"))))) 727 expect_identical(result5$html, renderTags("hello")$html) 728}) 729 730test_that("Factors are treated as characters, not numbers", { 731 myfactors <- factor(LETTERS[1:3]) 732 expect_identical( 733 as.character(tags$option(value=myfactors[[1]], myfactors[[1]])), 734 '<option value="A">A</option>' 735 ) 736 737 expect_identical( 738 as.character(tags$option(value=myfactors[[1]], value='B', value=3, myfactors[[1]])), 739 '<option value="A B 3">A</option>' 740 ) 741}) 742 743test_that("Unusual list contents are rendered correctly", { 744 expect_identical(renderTags(list(NULL)), renderTags(HTML(""))) 745 expect_identical(renderTags(list(100)), renderTags(HTML("100"))) 746 expect_identical(renderTags(list(list(100))), renderTags(HTML("100"))) 747 expect_identical(renderTags(list(list())), renderTags(HTML(""))) 748 expect_identical(renderTags(NULL), renderTags(HTML(""))) 749}) 750 751test_that("Low-level singleton manipulation methods", { 752 # Default arguments drop singleton duplicates and strips the 753 # singletons it keeps of the singleton bit 754 result1 <- takeSingletons(tags$div( 755 singleton(tags$head(tags$script("foo"))), 756 singleton(tags$head(tags$script("foo"))) 757 )) 758 759 expect_identical(result1$ui$children[[2]], NULL) 760 expect_false(is.singleton(result1$ui$children[[1]])) 761 762 # desingleton=FALSE means drop duplicates but don't strip the 763 # singleton bit 764 result2 <- takeSingletons(tags$div( 765 singleton(tags$head(tags$script("foo"))), 766 singleton(tags$head(tags$script("foo"))) 767 ), desingleton=FALSE) 768 769 expect_identical(result2$ui$children[[2]], NULL) 770 expect_true(is.singleton(result2$ui$children[[1]])) 771 772 result3 <- surroundSingletons(tags$div( 773 singleton(tags$script("foo")), 774 singleton(tags$script("foo")) 775 )) 776 777 expect_identical( 778 renderTags(result3)$html, 779 HTML("<div> 780 <!--SHINY.SINGLETON[98eee9ba1f9e4ab3db75f33036bf91d4e214342b]--> 781 <script>foo</script> 782 <!--/SHINY.SINGLETON[98eee9ba1f9e4ab3db75f33036bf91d4e214342b]--> 783 <!--SHINY.SINGLETON[98eee9ba1f9e4ab3db75f33036bf91d4e214342b]--> 784 <script>foo</script> 785 <!--/SHINY.SINGLETON[98eee9ba1f9e4ab3db75f33036bf91d4e214342b]--> 786</div>") 787 ) 788}) 789 790test_that("Indenting can be controlled/suppressed", { 791 expect_identical( 792 renderTags(tags$div("a", "b"))$html, 793 HTML("<div>\n a\n b\n</div>") 794 ) 795 expect_identical( 796 format(tags$div("a", "b")), 797 "<div>\n a\n b\n</div>" 798 ) 799 800 expect_identical( 801 renderTags(tags$div("a", "b"), indent = 2)$html, 802 HTML(" <div>\n a\n b\n </div>") 803 ) 804 expect_identical( 805 format(tags$div("a", "b"), indent = 2), 806 " <div>\n a\n b\n </div>" 807 ) 808 809 expect_identical( 810 renderTags(tags$div("a", "b"), indent = FALSE)$html, 811 HTML("<div>\na\nb\n</div>") 812 ) 813 expect_identical( 814 format(tags$div("a", "b"), indent = FALSE), 815 "<div>\na\nb\n</div>" 816 ) 817 818 expect_identical( 819 renderTags(tagList(tags$div("a", "b")), indent = FALSE)$html, 820 HTML("<div>\na\nb\n</div>") 821 ) 822 expect_identical( 823 format(tagList(tags$div("a", "b")), indent = FALSE), 824 "<div>\na\nb\n</div>" 825 ) 826}) 827 828test_that("cssList tests", { 829 expect_identical(NULL, css()) 830 expect_identical(NULL, css()) 831 expect_identical( 832 css( 833 font.family = 'Helvetica, "Segoe UI"', 834 font_size = "12px", 835 `font-style` = "italic", 836 font.variant = NULL, 837 "font-weight!" = factor("bold"), 838 padding = c("10px", "9px", "8px") 839 ), 840 "font-family:Helvetica, \"Segoe UI\";font-size:12px;font-style:italic;font-weight:bold !important;padding:10px 9px 8px;" 841 ) 842 843 # Unnamed args not allowed 844 expect_error(css("10")) 845 expect_error(css(1, b=2)) 846 847 # NULL and empty string are dropped 848 expect_null(css(a="", b = NULL, "c!" = NULL, d = character())) 849 850 # We are dumb about duplicated properties. Probably don't do that. 851 expect_identical(css(a=1, a=2), "a:1;a:2;") 852}) 853 854test_that("Non-tag objects can be coerced", { 855 856 .GlobalEnv$as.tags.testcoerce1 <- function(x) { 857 list(singleton(list("hello"))) 858 } 859 on.exit(rm("as.tags.testcoerce1", pos = .GlobalEnv), add = TRUE) 860 861 # Make sure tag-coerceable objects are tagified 862 result1 <- renderTags(structure(TRUE, class = "testcoerce1")) 863 expect_identical(result1$html, HTML("hello")) 864 expect_identical(result1$singletons, "110d1f0ef6762db2c6863523a7c379a697b43ea3") 865 866 # Make sure tag-coerceable objects are tagified before singleton handling 867 # occurs, but that over-flattening doesn't happen 868 result2 <- renderTags(tagList( 869 singleton(list("hello")), 870 structure(TRUE, class = "testcoerce1") 871 )) 872 expect_identical(result2$html, HTML("hello")) 873 expect_identical(result2$singletons, "110d1f0ef6762db2c6863523a7c379a697b43ea3") 874 875}) 876 877test_that("Latin1 and system encoding are converted to UTF-8", { 878 #Sys.setlocale(, "Chinese") 879 latin1_str <- rawToChar(as.raw(0xFF)) 880 Encoding(latin1_str) <- "latin1" 881 882 divLatin1 <- as.character(tags$div(latin1_str)) 883 expect_identical( 884 charToRaw(divLatin1), 885 as.raw(c(0x3c, 0x64, 0x69, 0x76, 0x3e, 0xc3, 0xbf, 0x3c, 0x2f, 886 0x64, 0x69, 0x76, 0x3e)) 887 ) 888 expect_identical(Encoding(divLatin1), "UTF-8") 889 890 expect_identical(Encoding("\u4E11"), "UTF-8") 891 divUTF8 <- as.character(tags$div("\u4E11")) 892 expect_identical( 893 charToRaw(divUTF8), 894 as.raw(c(0x3c, 0x64, 0x69, 0x76, 0x3e, 0xe4, 0xb8, 0x91, 0x3c, 895 0x2f, 0x64, 0x69, 0x76, 0x3e)) 896 ) 897 expect_identical(Encoding(divUTF8), "UTF-8") 898 899 divMixed <- format(tags$div( 900 "\u4E11", latin1_str, 901 tags$span(a="\u4E11", latin1_str), 902 tags$span(b=latin1_str, HTML("\u4E11")) 903 )) 904 expect_identical( 905 charToRaw(divMixed), 906 as.raw(c(0x3c, 0x64, 0x69, 0x76, 0x3e, 0x0a, 0x20, 0x20, 0xe4, 907 0xb8, 0x91, 0x0a, 0x20, 0x20, 0xc3, 0xbf, 0x0a, 0x20, 0x20, 0x3c, 908 0x73, 0x70, 0x61, 0x6e, 0x20, 0x61, 0x3d, 0x22, 0xe4, 0xb8, 0x91, 909 0x22, 0x3e, 0xc3, 0xbf, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 910 0x0a, 0x20, 0x20, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x62, 0x3d, 911 0x22, 0xc3, 0xbf, 0x22, 0x3e, 0xe4, 0xb8, 0x91, 0x3c, 0x2f, 0x73, 912 0x70, 0x61, 0x6e, 0x3e, 0x0a, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e 913 )) 914 ) 915 expect_identical(Encoding(divMixed), "UTF-8") 916 917 # Encoding(HTML(latin1_str)) is "UTF-8" on Linux; even just 918 # paste(latin1_str) returns a UTF-8 encoded string 919 #expect_identical(Encoding(HTML(latin1_str)), "latin1") 920 921 expect_identical(Encoding(format(HTML(latin1_str))), "UTF-8") 922 expect_identical(Encoding(format(tagList(latin1_str))), "UTF-8") 923 924 # ensure the latin1 attribute returns correctly after escaping 925 latin1_str2 <- rawToChar(as.raw(c(0xff, 0x0d, 0x0a))) 926 Encoding(latin1_str2) <- "latin1" 927 spanLatin <- as.character(tags$span(latin1_str2, title = latin1_str2)) 928 expect_identical(Encoding(spanLatin), "UTF-8") 929 expect_identical( 930 charToRaw(spanLatin), 931 as.raw(c(0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x74, 0x69, 0x74, 932 0x6c, 0x65, 0x3d, 0x22, 0xc3, 0xbf, 0x26, 0x23, 0x31, 0x33, 0x3b, 933 0x26, 0x23, 0x31, 0x30, 0x3b, 0x22, 0x3e, 0xc3, 0xbf, 0x0d, 0x0a, 934 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e 935 )) 936 ) 937}) 938 939test_that("paste8 in Chinese locale works", { 940 loc <- "Chinese" 941 testthat::skip_if_not(is_locale_available(loc), "Chinese locale not available") 942 943 withr::with_locale(c(LC_COLLATE=loc, LC_CTYPE=loc, LC_MONETARY=loc, LC_TIME=loc), { 944 x <- "\377" 945 Encoding(x) <- "latin1" 946 expect_identical(x, "\Uff") 947 expect_identical(Encoding(x), "latin1") 948 949 y <- "\U4E2d" # Using \Uxxxx always is encoded as UTF-8 950 expect_identical(y, "\U4E2d") 951 expect_identical(Encoding(y), "UTF-8") 952 953 xy <- paste8(x, y) 954 xy 955 expect_identical(xy, "\Uff \U4E2d") 956 expect_identical(Encoding(xy), "UTF-8") 957 958 xy <- paste8(c(x, y), collapse = "") 959 expect_identical(xy, "\Uff\U4E2d") 960 expect_identical(Encoding(xy), "UTF-8") 961 }) 962}) 963 964test_that("Printing tags works", { 965 expect_identical( 966 capture.output(print(tags$a(href = "#", "link"))), 967 '<a href="#">link</a>' 968 ) 969}) 970 971test_that("htmlEscape will try to coerce inputs to characters", { 972 x <- list(a1 = "b", a2 = list("b1", "b2")) 973 expect_identical( 974 htmlEscape(x), 975 as.character(x) 976 ) 977}) 978 979test_that("trailing commas allowed everywhere", { 980 expect_silent({ 981 t1 <- div("foo",) 982 tagList(t1,) 983 tagSetChildren(t1, "child",) 984 tagAppendAttributes(t1, class = "bar",) 985 tagAppendChildren(t1, "child2",) 986 css(style = "",) 987 }) 988}) 989 990test_that("extractPreserveChunks works for emoji strings", { 991 # solaris doesn't seem to support Unicode characters with surrogate pairs 992 # (just by creating such a string will cause a warning) 993 # > "\U0001F937" 994 # [1] "\U0001f937" 995 # Warning message: 996 # it is not known that wchar_t is Unicode on this platform 997 skip_on_os("solaris") 998 x <- "<!--html_preserve-->chunk1<!--/html_preserve-->\U0001F937<!--html_preserve-->chunk2<!--/html_preserve-->" 999 out <- extractPreserveChunks(x) 1000 expect_equivalent( 1001 out$chunks, 1002 c('chunk2', 'chunk1') 1003 ) 1004}) 1005 1006 1007test_that("complicated class attributes are handled", { 1008 x <- div(class = as.factor(letters)[1], class = "b c", class = c("d", "e f")) 1009 expect_equal( 1010 tagGetAttribute(x, "class"), 1011 "a b c d e f" 1012 ) 1013 expect_identical( 1014 as.character(x), 1015 "<div class=\"a b c d e f\"></div>" 1016 ) 1017}) 1018 1019 1020test_that("html render method", { 1021 local_edition(3) 1022 1023 # Have a place holder div and return a span instead 1024 obj <- div("example", .renderHook = function(x) { 1025 x$name <- "span" 1026 x 1027 }) 1028 expect_equal(obj$name, "div") 1029 expect_snapshot(as.character(obj)) 1030 1031 # Add a class to the tag 1032 spanExtra <- tagAddRenderHook(obj, function(x) { 1033 tagAppendAttributes(x, class = "extra") 1034 }) 1035 expect_equal(spanExtra$name, "div") 1036 expect_equal(spanExtra$attribs$class, NULL) 1037 expect_snapshot(as.character(spanExtra)) 1038 1039 # Replace the previous render method 1040 # Should print a `div` with class `"extra"` 1041 divExtra <- tagAddRenderHook(obj, replace = TRUE, function(x) { 1042 tagAppendAttributes(x, class = "extra") 1043 }) 1044 expect_equal(divExtra$attribs$class, NULL) 1045 expect_snapshot(as.character(divExtra)) 1046 1047 # Add more child tags 1048 spanExtended <- tagAddRenderHook(obj, function(x) { 1049 tagAppendChildren(x, tags$strong("bold text")) 1050 }) 1051 expect_equal(spanExtended$name, "div") 1052 expect_equal(spanExtended$children, obj$children) 1053 expect_snapshot(as.character(spanExtended)) 1054 1055 tagFuncExt <- tagAddRenderHook(obj, function(x) { 1056 tagFunction(function() tagList(x, tags$p("test")) ) 1057 }) 1058 expect_equal(tagFuncExt$name, "div") 1059 expect_equal(tagFuncExt$children, obj$children) 1060 expect_snapshot(as.character(tagFuncExt)) 1061 1062 # Add a new html dependency 1063 newDep <- tagAddRenderHook(obj, function(x) { 1064 fa <- htmlDependency( 1065 "font-awesome", "4.5.0", c(href="shared/font-awesome"), 1066 stylesheet = "css/font-awesome.min.css") 1067 attachDependencies(x, fa, append = TRUE) 1068 }) 1069 # Also add a jqueryui html dependency 1070 htmlDependencies(newDep) <- htmlDependency( 1071 "jqueryui", "1.11.4", c(href="shared/jqueryui"), 1072 script = "jquery-ui.min.js") 1073 expect_equal(newDep$name, "div") 1074 expect_length(htmlDependencies(newDep), 1) 1075 expect_snapshot(renderTags(newDep)) 1076 1077 # Ignore the original tag and return something completely new. 1078 newObj <- tagAddRenderHook(obj, function(x) { 1079 tags$p("Something else") 1080 }) 1081 expect_equal(newObj$name, "div") 1082 expect_snapshot(as.character(newObj)) 1083}) 1084 1085 1086test_that(".cssSelector arg only applies changes to the selected elements", { 1087 html <- 1088 div( 1089 class = "outer", 1090 div(class = "inner", "text"), 1091 span("TEXT") 1092 ) 1093 1094 expect_equal_tags( 1095 tagAppendAttributes(html, id = "test"), 1096 div(class = "outer", id = "test", div(class="inner", "text"), span("TEXT")) 1097 ) 1098 expect_equal_tags( 1099 tagAppendAttributes(html, id = "test", .cssSelector = ".inner"), 1100 div(class = "outer", div(class = "inner", id = "test", "text"), span("TEXT")) 1101 ) 1102 1103 expect_equal_tags( 1104 tagAppendChild(html, h1()), 1105 div(class = "outer", div(class="inner", "text"), span("TEXT"), h1()) 1106 ) 1107 expect_equal_tags( 1108 tagAppendChild(html, h1(), .cssSelector = ".inner"), 1109 div(class = "outer", div(class = "inner", "text", h1()), span("TEXT")) 1110 ) 1111 1112 expect_equal_tags( 1113 tagAppendChildren(html, h1(), h2()), 1114 div(class = "outer", div(class="inner", "text"), span("TEXT"), h1(), h2()) 1115 ) 1116 expect_equal_tags( 1117 tagAppendChildren(html, h1(), h2(), .cssSelector = ".inner"), 1118 div(class = "outer", div(class = "inner", "text", h1(), h2()), span("TEXT")) 1119 ) 1120 1121 expect_equal_tags( 1122 tagSetChildren(html, h1(), h2()), 1123 div(class = "outer", h1(), h2()) 1124 ) 1125 expect_equal_tags( 1126 tagSetChildren(html, h1(), h2(), .cssSelector = ".inner"), 1127 div(class = "outer", div(class = "inner", h1(), h2()), span("TEXT")) 1128 ) 1129 1130 expect_equal_tags( 1131 tagInsertChildren(html, h1(), h2(), after = 0), 1132 div(class = "outer", h1(), h2(), div(class="inner", "text"), span("TEXT")) 1133 ) 1134 expect_equal_tags( 1135 tagInsertChildren(html, h1(), h2(), after = 0, .cssSelector = ".inner"), 1136 div(class = "outer", div(class = "inner", h1(), h2(), "text"), span("TEXT")) 1137 ) 1138}) 1139 1140 1141 1142 1143test_that("flattenTagAttribs", { 1144 attribs <- list( 1145 b = "1", 1146 a = "2", 1147 b = "3" 1148 ) 1149 1150 flatAttribs <- flattenTagAttribs(attribs) 1151 # alpha sorted 1152 expect_equal(names(flatAttribs), c("a", "b")) 1153 # b values are collected 1154 expect_equal(flatAttribs, list(a = "2", b = c("1", "3"))) 1155}) 1156