htmltools/0000755000176200001440000000000015114272561012303 5ustar liggesusershtmltools/tests/0000755000176200001440000000000014600330155013436 5ustar liggesusershtmltools/tests/testthat/0000755000176200001440000000000015113345760015306 5ustar liggesusershtmltools/tests/testthat/test-template.R0000644000176200001440000002033014600330155020207 0ustar liggesuserscontext("templates") # Searches for an html dependency of format name[version], as in "d3[3.5.10]", # within the html-dependencies script tag findDep <- function(x, name, version) { deps <- sub( '.*.*', "\\1", x ) grepl(paste0(name, "[", version, "]"), deps, fixed = TRUE) } test_that("Code blocks are evaluated and rendered correctly", { template <- htmlTemplate(test_path("template-document.html"), x = div(class = "foo", "bar") ) html <- renderDocument(template) expect_true(grepl('
bar
', html)) # With text_ argument template <- htmlTemplate(text_ = "a {{ foo + 1 }} b", foo = 10) expect_identical(as.character(as.character(template)), "a 11 b") # Make char vectors are pasted together template <- htmlTemplate(text_ = c("a", "{{ foo + 1 }} b"), foo = 10) expect_identical(as.character(as.character(template)), "a\n11 b") }) test_template <- function(){ template <- htmlTemplate(test_path("template-document.html"), x = "") html <- renderDocument(template) expect_identical(Encoding(html), "UTF-8") # Create the string 'Δ★😎', making sure it's UTF-8 encoded on all platforms. # These characters are 2, 3, and 4 bytes long, respectively. pat <- rawToChar(as.raw(c(0xce, 0x94, 0xe2, 0x98, 0x85, 0xf0, 0x9f, 0x98, 0x8e))) Encoding(pat) <- "UTF-8" expect_true(grepl(pat, html)) # If template is passed text_ argument, make sure it's converted from native # to UTF-8. latin1_str <- rawToChar(as.raw(0xFF)) Encoding(latin1_str) <- "latin1" text <- as.character(htmlTemplate(text_ = latin1_str)) expect_identical(charToRaw(text), as.raw(c(0xc3, 0xbf))) } test_that("UTF-8 characters in templates with default locale", { # The default locale loc <- "" withr::with_locale(c(LC_COLLATE=loc, LC_CTYPE=loc, LC_MONETARY=loc, LC_TIME=loc), test_template()) }) test_that("UTF-8 characters in templates with Chinese locale", { # Chinese locale loc <- "Chinese" testthat::skip_if_not(is_locale_available(loc), "Chinese locale not available") withr::with_locale(c(LC_COLLATE=loc, LC_CTYPE=loc, LC_MONETARY=loc, LC_TIME=loc), test_template()) }) test_that("UTF-8 characters in template head but not body", { # On Windows, a string with "中文" will automatically be marked as UTF-8. ui <- tagList( tags$head(tags$script("alert('中文')")), "test" ) html <- htmlTemplate(test_path("template-basic.html"), body = ui) res <- renderDocument(html) expect_identical(Encoding(res), "UTF-8") expect_true(grepl("中文", res, fixed = TRUE)) # On Windows, a string with "á" will automatically be marked as latin1. ui <- tagList( tags$head(tags$script("alert('á')")), "test" ) html <- htmlTemplate(test_path("template-basic.html"), body = ui) res <- renderDocument(html) expect_identical(Encoding(res), "UTF-8") expect_true(grepl("á", res, fixed = TRUE)) }) test_that("Dependencies are added properly", { dep <- htmlDependency("d3", "3.5.10", c(href="shared"), script = "d3.js") # Add dependency by inserting a tag with a dependency template <- htmlTemplate(test_path("template-document.html"), x = attachDependencies(div(), dep) ) html <- renderDocument(template) expect_true(findDep(html, "d3", "3.5.10")) expect_true(grepl('', html, fixed = TRUE)) # Add dependency via a renderDocument template <- htmlTemplate(test_path("template-document.html"), x = "") html <- renderDocument(template, dep) expect_true(findDep(html, "d3", "3.5.10")) expect_true(grepl('', html, fixed = TRUE)) }) test_that("Dependencies can be suppressed", { # The template includes suppressDependencies("jquery"), so we shouldn't see # this dependency in the final output. dep <- htmlDependency("jquery", "1.11.3", c(href="shared"), script = "jquery.js") # Add dependency by inserting a tag with a dependency template <- htmlTemplate(test_path("template-document.html"), x = attachDependencies(div(), dep) ) html <- renderDocument(template) expect_true(findDep(html, "jquery", "9999")) expect_false(grepl(']+jquery[^>]+>', html)) # Add dependency via a renderDocument template <- htmlTemplate(test_path("template-document.html"), x = "") html <- renderDocument(template, dep) expect_true(findDep(html, "jquery", "9999")) expect_false(grepl(']+jquery[^>]+>', html)) }) test_that("Errors for mismatched brackets", { # Error if unmatched opening brackets expect_error(htmlTemplate(text_ = "text {{ code")) # No error if we didn't open a code block expect_identical( as.character(htmlTemplate(text_ = "code }} text")), "code }} text" ) # Error if unmatched brackets, when no leading or trailing space expect_error(htmlTemplate(text_ = "{{ code")) # No error if we didn't open a code block expect_identical( as.character(htmlTemplate(text_ = "code }}")), "code }}" ) }) test_that("Brackets at start or end of text", { # Code and text expect_identical( as.character(htmlTemplate(text_ = "text {{ code }} text", code = 1)), "text 1 text" ) expect_identical( as.character(htmlTemplate(text_ = "text{{code}}text", code = 1)), "text1text" ) # No brackets expect_identical( as.character(htmlTemplate(text_ = "text", code = 1)), "text" ) # No leading or trailing text expect_identical( as.character(htmlTemplate(text_ = "{{ code }}", code = 1)), "1" ) expect_identical( as.character(htmlTemplate(text_ = " {{ code }}", code = 1)), " 1" ) expect_identical( as.character(htmlTemplate(text_ = "{{ code }} ", code = 1)), "1 " ) # Edge cases expect_identical(as.character(htmlTemplate(text_ = "")), "") expect_identical(as.character(htmlTemplate(text_ = "X")), "X") expect_identical(as.character(htmlTemplate(text_ = " ")), " ") expect_identical(as.character(htmlTemplate(text_ = "{{}}")), "") expect_identical(as.character(htmlTemplate(text_ = " {{}} ")), " ") expect_identical(as.character(htmlTemplate(text_ = "{{ }}")), "") expect_identical(as.character(htmlTemplate(text_ = "{{}}{{}}")), "") expect_identical(as.character(htmlTemplate(text_ = "{{1}}{{2}}")), "12") expect_error(as.character(htmlTemplate(text_ = "{{"))) expect_error(as.character(htmlTemplate(text_ = " {{"))) expect_error(as.character(htmlTemplate(text_ = "{{ "))) expect_identical(as.character(htmlTemplate(text_ = "}}")), "}}") expect_identical(as.character(htmlTemplate(text_ = " }}")), " }}") expect_identical(as.character(htmlTemplate(text_ = "}} ")), "}} ") }) test_that("Template DFA edge cases", { # Single quotes expect_identical(as.character(htmlTemplate(text_ = "{{ '' }}")), "") expect_identical(as.character(htmlTemplate(text_ = " {{ '' }} ")), " ") expect_identical(as.character(htmlTemplate(text_ = "{{ '\\'' }}")), "'") expect_identical(as.character(htmlTemplate(text_ = "{{ '\\\\' }}")), "\\") expect_identical(as.character(htmlTemplate(text_ = "{{ '}}' }}")), "}}") # Double quotes expect_identical(as.character(htmlTemplate(text_ = '{{ "" }}')), '') expect_identical(as.character(htmlTemplate(text_ = ' {{ "" }} ')), ' ') expect_identical(as.character(htmlTemplate(text_ = '{{ "\\"" }}')), '"') expect_identical(as.character(htmlTemplate(text_ = '{{ "\\\\" }}')), '\\') expect_identical(as.character(htmlTemplate(text_ = '{{ "}}" }}')), '}}') # Backticks in code expect_identical(as.character(htmlTemplate(text_ = "{{ `}}`<-1 }}")), "1") expect_identical(as.character(htmlTemplate(text_ = "{{ `x\\`x`<-1 }}")), "1") # Percent operator - various delimiters in percent operator expect_identical( as.character(htmlTemplate(text_ = "a{{ `%'%` <- function(x, y) 1; 2 %'% 3 }}b")), "a1b" ) expect_identical( as.character(htmlTemplate(text_ = "a{{ `%}}%` <- function(x, y) 1; 2 %}}% 3 }}b")), "a1b" ) # Comments expect_identical( as.character(htmlTemplate(text_ = "a{{ 1 #2 }}b")), "a1b" ) expect_identical( as.character(htmlTemplate(text_ = "a{{ 1 #2\n3 }}b")), "a3b" ) expect_identical( as.character(htmlTemplate(text_ = "a{{ 1 #2'3 }}b")), "a1b" ) expect_identical( as.character(htmlTemplate(text_ = "a{{ 1 #2}3 }}b")), "a1b" ) }) htmltools/tests/testthat/test-tags.r0000644000176200001440000010731414600330155017402 0ustar liggesuserscontext("tags") test_that("Basic tag writing works", { expect_equal(as.character(tagList("hi")), "hi") expect_equal( as.character(tagList("one", "two", tagList("three"))), "one\ntwo\nthree") expect_equal( as.character(tags$b("one")), "one") expect_equal( as.character(tags$b("one", "two")), "\n one\n two\n") expect_equal( as.character(tagList(list("one"))), "one") expect_equal( as.character(tagList(list(tagList("one")))), "one") expect_equal( as.character(tagList(tags$br(), "one")), "
\none") }) test_that("Hanging commas don't break things", { expect_equal(as.character(tagList("hi",)), "hi") expect_equal(as.character(div("one",)), "
one
") # Multiple commas still throw err_comma_multiple <- expect_error(as.character(div("one",,))) # Non-trailing commas still throw err_comma_leading <- expect_error(as.character(div(,"one",))) # rlang > 1.0.6 changed the error message, so only run # snapshot testing of the error message with the new version skip_if_not_installed("rlang", "1.0.6.9000") local_edition(3) expect_snapshot(err_comma_multiple) expect_snapshot(err_comma_leading) }) test_that("withTags works", { output_tags <- tags$div(class = "myclass", tags$h3("header"), tags$p("text here") ) output_withhtml <- withTags( div(class = "myclass", h3("header"), p("text here") ) ) expect_identical(output_tags, output_withhtml) # Check that current environment is searched x <- 100 expect_identical(tags$p(x), withTags(p(x))) # Just to make sure, run it in a function, which has its own environment foo <- function() { y <- 100 withTags(p(y)) } expect_identical(tags$p(100), foo()) }) test_that(".noWS argument of withTags()", { get_noWS <- function(tag) tag[[".noWS"]] default <- withTags( div( class = "myclass", h3("header"), p("One", strong(span("two")), "three") ) ) expect_null(get_noWS(default)) expect_null(get_noWS(default$children[[1]])) expect_null(get_noWS(default$children[[2]])) expect_null(get_noWS(default$children[[2]]$children[[2]])) expect_null(get_noWS(default$children[[2]]$children[[2]]$children[[1]])) default_special <- withTags( div( class = "myclass", h3("header", .noWS = "after-begin"), p("One", strong(span("two")), "three", .noWS = "before-end") ) ) expect_null(get_noWS(default_special)) expect_equal(get_noWS(default_special$children[[1]]), "after-begin") expect_equal(get_noWS(default_special$children[[2]]), "before-end") expect_null(get_noWS(default_special$children[[2]]$children[[2]])) expect_null(get_noWS(default_special$children[[2]]$children[[2]]$children[[1]])) all_same_noWS <- c("outside", "inside") all_same <- withTags( div( class = "myclass", h3("header"), p("One", strong(span("two")), "three") ), .noWS = all_same_noWS ) expect_equal(get_noWS(all_same), all_same_noWS) expect_equal(get_noWS(all_same$children[[1]]), all_same_noWS) expect_equal(get_noWS(all_same$children[[2]]), all_same_noWS) expect_equal(get_noWS(all_same$children[[2]]$children[[2]]), all_same_noWS) expect_equal(get_noWS(all_same$children[[2]]$children[[2]]$children[[1]]), all_same_noWS) varied_default <- "outside" varied_special <- "inside" varied <- withTags( div( class = "myclass", h3("header"), p("One", strong(span("two"), .noWS = varied_special), "three") ), .noWS = varied_default ) expect_equal(get_noWS(varied), varied_default) expect_equal(get_noWS(varied$children[[1]]), varied_default) expect_equal(get_noWS(varied$children[[2]]), varied_default) expect_equal(get_noWS(varied$children[[2]]$children[[2]]), varied_special) expect_equal(get_noWS(varied$children[[2]]$children[[2]]$children[[1]]), varied_default) }) test_that("HTML escaping in tags", { # Regular text is escaped expect_equivalent(format(div("")), "
<a&b>
") # Text in HTML() isn't escaped expect_equivalent(format(div(HTML(""))), "
") # Text in a property is escaped expect_equivalent(format(div(class = "", "text")), '
text
') # HTML() has no effect in a property like 'class' expect_equivalent(format(div(class = HTML(""), "text")), '
text
') }) test_that("Adding child tags", { tag_list <- list(tags$p("tag1"), tags$b("tag2"), tags$i("tag3")) # Creating nested tags by calling the tag$div function and passing a list t1 <- tags$div(class="foo", tag_list) expect_equal(length(t1$children), 1) expect_equal(length(t1$children[[1]]), 3) expect_equal(t1$children[[1]][[1]]$name, "p") expect_equal(t1$children[[1]][[1]]$children[[1]], "tag1") expect_equal(t1$children[[1]][[2]]$name, "b") expect_equal(t1$children[[1]][[2]]$children[[1]], "tag2") expect_equal(t1$children[[1]][[3]]$name, "i") expect_equal(t1$children[[1]][[3]]$children[[1]], "tag3") # div tag used as starting point for tests below div_tag <- tags$div(class="foo") # Appending each child t2 <- tagAppendChild(div_tag, tag_list[[1]]) t2 <- tagAppendChild(t2, tag_list[[2]]) t2 <- tagAppendChild(t2, tag_list[[3]]) t2a <- do.call(tags$div, c(tag_list, class="foo")) expect_identical(t2a, t2) t2b <- tagAppendChildren(div_tag, `names_are_ignored` = tag_list[[1]], "ignore-this-name" = tag_list[[2]], dummyName = tag_list[[3]]) expect_identical(t2b, t2) # tagSetChildren, using list argument t2 <- tagSetChildren(div_tag, list = tag_list) expect_identical(t2a, t2) # tagSetChildren, using ... arguments t2 <- tagSetChildren(div_tag, tag_list[[1]], tag_list[[2]], tag_list[[3]]) expect_identical(t2a, t2) # tagSetChildren, using named ... arguments (names should be ignored) t2 <- tagSetChildren(div_tag, ignored = tag_list[[1]], dummy = tag_list[[2]], blah = tag_list[[3]]) expect_identical(t2a, t2) # tagSetChildren, using ... and list arguments t2 <- tagSetChildren(div_tag, tag_list[[1]], list = tag_list[2:3]) expect_identical(t2a, t2) # tagSetChildren overwrites existing children t2 <- tagAppendChild(div_tag, p("should replace this tag")) t2 <- tagSetChildren(div_tag, list = tag_list) expect_identical(t2a, t2) # tagAppendChildren, using list argument t2 <- tagAppendChild(div_tag, tag_list[[1]]) t2 <- tagAppendChildren(t2, list = tag_list[2:3]) expect_identical(t2a, t2) # tagAppendChildren, using ... arguments t2 <- tagAppendChild(div_tag, tag_list[[1]]) t2 <- tagAppendChildren(t2, tag_list[[2]], tag_list[[3]]) expect_identical(t2a, t2) # tagAppendChildren, using ... and list arguments t2 <- tagAppendChild(div_tag, tag_list[[1]]) t2 <- tagAppendChildren(t2, tag_list[[2]], list = list(tag_list[[3]])) expect_identical(t2a, t2) # tagAppendChildren can start with no children t2 <- tagAppendChildren(div_tag, list = tag_list) expect_identical(t2a, t2) # tagSetChildren preserves attributes x <- tagSetChildren(div(), HTML("text")) expect_identical(attr(x$children[[1]], "html", TRUE), TRUE) # tagAppendChildren preserves attributes x <- tagAppendChildren(div(), HTML("text")) expect_identical(attr(x$children[[1]], "html", TRUE), TRUE) }) test_that("Creating simple tags", { # Empty tag expect_identical( div(), structure( list(name = "div", attribs = dots_list(), children = list()), .Names = c("name", "attribs", "children"), class = "shiny.tag" ) ) # Tag with text expect_identical( div("text"), structure( list(name = "div", attribs = dots_list(), children = list("text")), .Names = c("name", "attribs", "children"), class = "shiny.tag" ) ) # NULL attributes are dropped expect_identical( div(a = NULL, b = "value"), div(b = "value") ) # length-0 attributes are dropped expect_identical( div(a = character(), b = "value"), div(b = "value") ) # NULL children are dropped expect_identical( renderTags(div("foo", NULL, list(NULL, list(NULL, "bar"))))$html, renderTags(div("foo", "bar"))$html ) # length-0 children are dropped expect_identical( renderTags(div("foo", character(), list(character(), list(list(), "bar"))))$html, renderTags(div("foo", "bar"))$html ) # Numbers are coerced to strings expect_identical( renderTags(div(1234))$html, renderTags(div("1234"))$html ) }) test_that("Creating nested tags", { # Simple version # Note that the $children list should not have a names attribute expect_identical( div(class="foo", list("a", "b")), structure( list(name = "div", attribs = structure(list(class = "foo"), .Names = "class"), children = list(list("a", "b"))), .Names = c("name", "attribs", "children"), class = "shiny.tag" ) ) # More complex version t1 <- withTags( div(class = "foo", p("child tag"), list( p("in-list child tag 1"), "in-list character string", p(), p("in-list child tag 2") ), "character string", 1234 ) ) # t1 should be identical to this data structure. # The nested list should be flattened, and non-tag, non-strings should be # converted to strings t1_full <- structure( list( name = "div", attribs = list(class = "foo"), children = list( structure(list(name = "p", attribs = list(), children = list("child tag")), class = "shiny.tag" ), structure(list(name = "p", attribs = list(), children = list("in-list child tag 1")), class = "shiny.tag" ), "in-list character string", structure(list(name = "p", attribs = list(), children = list()), class = "shiny.tag" ), structure(list(name = "p", attribs = list(), children = list("in-list child tag 2")), class = "shiny.tag" ), "character string", "1234" ) ), class = "shiny.tag" ) expect_identical(renderTags(t1)$html, renderTags(t1_full)$html) }) # The .noWS option was added in 0.3.6.9003; we may still encounter tags created # in an older version (perhaps saved to an RDS file and restored). They would # lack this element in their structure. test_that("Old tags without the .noWS option can still be rendered", { oldTag <- structure( list(name = "div", attribs = dots_list(), children = list("text")), .Names = c("name", "attribs", "children"), class = "shiny.tag" ) w <- WSTextWriter() tagWrite(oldTag, w) expect_identical( w$readAll(), "
text
\n" ) }) # We moved to rlang::dots_list in 0.3.6; we may still encounter tags created # in an older version (perhaps saved to an RDS file and restored). They would # use old-school lists. test_that("Old tags predating rlang::list2 can still be rendered", { oldTag <- structure( list(name = "div", attribs = list(), children = list("text")), .Names = c("name", "attribs", "children"), class = "shiny.tag" ) w <- WSTextWriter() tagWrite(oldTag, w) expect_identical( w$readAll(), "
text
\n" ) }) test_that("tag with noWS works",{ oneline <- tag("span", list(tag("strong", "Super strong", .noWS="outside"))) expect_identical(as.character(oneline), "Super strong") }) test_that("tag/s with invalid noWS fails fast", { expect_error(tag("span", .noWS="wrong")) expect_error(tags$a(.noWS="wrong")) }) test_that("Attributes are preserved", { # HTML() adds an attribute to the data structure (note that this is # different from the 'attribs' field in the list) x <- HTML("&&") expect_identical(attr(x, "html", TRUE), TRUE) expect_equivalent(format(x), "&&") # Make sure attributes are preserved when wrapped in other tags x <- div(HTML("&&")) expect_equivalent(x$children[[1]], HTML("&&")) expect_identical(attr(x$children[[1]], "html", TRUE), TRUE) expect_equivalent(format(x), "
&&
") # Deeper nesting x <- div(p(HTML("&&"))) expect_equivalent(x$children[[1]]$children[[1]], HTML("&&")) expect_identical(attr(x$children[[1]]$children[[1]], "html", TRUE), TRUE) expect_equivalent(format(x), "
\n

&&

\n
") }) test_that("Adding attributes to tags", { t1 <- tags$div("foo") # Adding attributes to empty tag expect_identical(t1$attribs, dots_list()) expect_identical( tagAppendAttributes(t1, class = "c1")$attribs, list(class = "c1") ) # Adding attribute with multiple values expect_identical( tagAppendAttributes(t1, class = "c1 c2")$attribs, list(class = "c1 c2") ) # Adding two different attributes expect_identical( tagAppendAttributes(t1, class = "c1", id = "foo")$attribs, list(class = "c1", id = "foo") ) # Adding attributes in two successive calls expect_identical( tagAppendAttributes( tagAppendAttributes(t1, class = "c1 c2"), class = "c3")$attribs, list(class = "c1 c2", class = "c3") ) # Adding empty attributes expect_identical( tagAppendAttributes(t1, class = NULL)$attribs, list() ) expect_identical( tagAppendAttributes( tagAppendAttributes(t1, class = "hidden"), class = NULL)$attribs, list(class = "hidden") ) t2 <- tags$div("foo", class = "c1") # Adding attributes on a tag with other attributes expect_identical( tagAppendAttributes(t2, id = "foo")$attribs, list(class = "c1", id = "foo") ) # Adding attributes on a tag with the same attribute expect_identical( tagAppendAttributes(t2, class = "c2")$attribs, list(class = "c1", class = "c2") ) }) test_that("Adding unnamed attributes creates a warning", { expect_error( tagAppendAttributes( tags$div(), "value" ), "include an attribute name" ) x <- div() x$attribs[[1]] <- "value" expect_error( print(x), "name all of your attribute values" ) }) test_that("Testing for attributes on tags", { t1 <- tags$div("foo", class = "c1", class = "c2", id = "foo") # Testing for attribute that does not exist expect_identical( tagHasAttribute(t1, "nope"), FALSE ) # Testing for an attribute that exists once expect_identical( tagHasAttribute(t1, "id"), TRUE ) # Testing for an attribute that exists multiple times expect_identical( tagHasAttribute(t1, "class"), TRUE ) # Testing for substring of an attribute that exists expect_identical( tagHasAttribute(t1, "clas"), FALSE ) # Testing for superstring of an attribute that exists expect_identical( tagHasAttribute(t1, "classes"), FALSE ) # Testing for attribute with empty value t2 <- tags$div("foo", foo = "") expect_identical( tagHasAttribute(t2, "foo"), TRUE ) # Testing for attribute with NULL value t3 <- tags$div("foo", foo = NULL) expect_identical( tagHasAttribute(t3, "foo"), FALSE ) }) test_that("Getting attributes from tags", { # Getting an attribute from a tag with no attributes t1 <- tags$div("foo") expect_identical( tagGetAttribute(t1, "class"), NULL ) t2 <- tags$div("foo", class = "c1") # Getting an attribute from a tag without the correct attribute expect_identical( tagGetAttribute(t2, "id"), NULL ) # Getting an attribute from a tag with the a single value for the attribute expect_identical( tagGetAttribute(t2, "class"), "c1" ) # Getting an attribute from a tag with multiple matching attributes t3 <- tags$div("foo", class = "c1", id = "foo", class = "c2") expect_identical( tagGetAttribute(t3, "class"), "c1 c2" ) # Getting an attribute from a tag where the attributes were factors t4 <- tags$div("foo", class = as.factor("c1"), class = as.factor("c2")) expect_identical( tagGetAttribute(t4, "class"), "c1 c2" ) # Getting a numeric attribute from a tag t5 <- tags$div("foo", class = 78) expect_identical( tagGetAttribute(t5, "class"), "78" ) }) test_that("NA attributes are rendered correctly", { expect_identical( as.character(tags$div("text", foo = NA)), '
text
' ) expect_identical( as.character(tags$div("text", class = "a", foo = NA)), '
text
' ) expect_identical( as.character(tags$div("text", class = "a", foo = NA, class = "b")), '
text
' ) # Multiple NA's are coalesced expect_identical( as.character(tags$div("text", class = "a", foo = NA, class = "b", foo = NA)), '
text
' ) # A non-NA value supersedes NA expect_identical( as.character(tags$div("text", class = "a", foo = NA, foo = "b")), '
text
' ) expect_identical( as.character(tags$div("text", class = "a", foo = "b", foo = NA, foo = "c")), '
text
' ) expect_identical( as.character(tags$div("text", class = "a", foo = "b", foo = NA, foo = NA, foo = "c")), '
text
' ) }) test_that("NA attributes are retrieved correctly", { expect_foo_attr <- function(y, ...) { testTag <- tags$div("text", ...) expect_identical( tagGetAttribute(testTag, "foo"), y ) } expect_foo_attr(NA, foo = NA) expect_foo_attr(NA, class = "a", foo = NA) expect_foo_attr(NA, class = "a", foo = NA, class = "b") # Multiple NA's are coalesced expect_foo_attr(NA, class = "a", foo = NA, class = "b", foo = NA) # A non-NA value supersedes NA expect_foo_attr("b", class = "a", foo = NA, foo = "b") expect_foo_attr("b c", class = "a", foo = "b", foo = NA, foo = "c") expect_foo_attr("b c", class = "a", foo = "b", foo = NA, foo = NA, foo = "c") # Non atomic value cause a list to be returned. expect_foo_attr(list(list("b")), class = "a", foo = NA, foo = list("b")) expect_foo_attr(list(list("b"), list("c")), class = "a", foo = list("b"), foo = NA, foo = list("c")) expect_foo_attr(list("b", list("c")), class = "a", foo = "b", foo = NA, foo = NA, foo = list("c")) }) test_that("Tag list tree is rendered in DOM tree order", { # Tree order is preorder, depth-first traversal # https://dom.spec.whatwg.org/#concept-tree # # Test for preordered traversal/execution of tagFunction(). This allows one to # rely on the side-effects of executing a tag, so long as those side-effects # happen "towards the top" of the tree. Shiny implicitly assumes this # behavior: execution of bootstrapLib() introduces a (temporary) side-effect # that "down-stream" UI (i.e. sliderInput() et al) can use to inform their # Sass -> CSS compilation value <- NULL lazyDiv <- div(tagFunction(function() { value })) dom <- tagList( lazyDiv, div(tagList( tagFunction(function() { value <<- 1 }) )), lazyDiv ) expect_identical( as.character(dom), "
\n
1
\n
1
" ) }) test_that("Flattening a list of tags", { # Flatten a nested list nested <- list( "a1", list( "b1", list("c1", "c2"), list(), "b2", list("d1", "d2") ), "a2" ) flat <- list("a1", "b1", "c1", "c2", "b2", "d1", "d2", "a2") expect_identical(flattenTags(nested), flat) # no-op for flat lists expect_identical(flattenTags(list(a="1", "b")), list(a="1", "b")) # numbers are coerced to character expect_identical(flattenTags(list(a=1, "b")), list(a="1", "b")) # empty list results in empty list expect_identical(flattenTags(list()), list()) # preserve attributes nested <- list("txt1", list(structure("txt2", prop="prop2"))) flat <- list("txt1", structure("txt2", prop="prop2")) expect_identical(flattenTags(nested), flat) }) test_that("Head and singleton behavior", { result <- renderTags(tagList( tags$head(singleton("hello")) )) expect_identical(result$html, HTML("")) expect_identical(result$head, HTML(" hello")) expect_identical(result$singletons, "089cce0335cf2bae2bcb08cc753ba56f8e1ea8ed") # Ensure that "hello" actually behaves like a singleton result2 <- renderTags(tagList( tags$head(singleton("hello")) ), singletons = result$singletons) expect_identical(result$singletons, result2$singletons) expect_identical(result2$head, HTML("")) expect_identical(result2$html, HTML("")) result3 <- renderTags(tagList( tags$head(singleton("hello"), singleton("hello")) )) expect_identical(result$singletons, result3$singletons) expect_identical(result3$head, HTML(" hello")) # Ensure that singleton can be applied to lists, not just tags result4 <- renderTags(list(singleton(list("hello")), singleton(list("hello")))) expect_identical(result4$singletons, "110d1f0ef6762db2c6863523a7c379a697b43ea3") expect_identical(result4$html, renderTags(HTML("hello"))$html) result5 <- renderTags(tagList(singleton(list(list("hello"))))) expect_identical(result5$html, renderTags("hello")$html) }) test_that("Factors are treated as characters, not numbers", { myfactors <- factor(LETTERS[1:3]) expect_identical( as.character(tags$option(value=myfactors[[1]], myfactors[[1]])), '' ) expect_identical( as.character(tags$option(value=myfactors[[1]], value='B', value=3, myfactors[[1]])), '' ) }) test_that("Unusual list contents are rendered correctly", { expect_identical(renderTags(list(NULL)), renderTags(HTML(""))) expect_identical(renderTags(list(100)), renderTags(HTML("100"))) expect_identical(renderTags(list(list(100))), renderTags(HTML("100"))) expect_identical(renderTags(list(list())), renderTags(HTML(""))) expect_identical(renderTags(NULL), renderTags(HTML(""))) }) test_that("Low-level singleton manipulation methods", { # Default arguments drop singleton duplicates and strips the # singletons it keeps of the singleton bit result1 <- takeSingletons(tags$div( singleton(tags$head(tags$script("foo"))), singleton(tags$head(tags$script("foo"))) )) expect_identical(result1$ui$children[[2]], NULL) expect_false(is.singleton(result1$ui$children[[1]])) # desingleton=FALSE means drop duplicates but don't strip the # singleton bit result2 <- takeSingletons(tags$div( singleton(tags$head(tags$script("foo"))), singleton(tags$head(tags$script("foo"))) ), desingleton=FALSE) expect_identical(result2$ui$children[[2]], NULL) expect_true(is.singleton(result2$ui$children[[1]])) result3 <- surroundSingletons(tags$div( singleton(tags$script("foo")), singleton(tags$script("foo")) )) expect_identical( renderTags(result3)$html, HTML("
") ) }) test_that("Indenting can be controlled/suppressed", { expect_identical( renderTags(tags$div("a", "b"))$html, HTML("
\n a\n b\n
") ) expect_identical( format(tags$div("a", "b")), "
\n a\n b\n
" ) expect_identical( renderTags(tags$div("a", "b"), indent = 2)$html, HTML("
\n a\n b\n
") ) expect_identical( format(tags$div("a", "b"), indent = 2), "
\n a\n b\n
" ) expect_identical( renderTags(tags$div("a", "b"), indent = FALSE)$html, HTML("
\na\nb\n
") ) expect_identical( format(tags$div("a", "b"), indent = FALSE), "
\na\nb\n
" ) expect_identical( renderTags(tagList(tags$div("a", "b")), indent = FALSE)$html, HTML("
\na\nb\n
") ) expect_identical( format(tagList(tags$div("a", "b")), indent = FALSE), "
\na\nb\n
" ) }) test_that("cssList tests", { expect_identical(NULL, css()) expect_identical(NULL, css()) # Regular CSS properties with conveniences expect_identical(css(font.size = "12px"), "font-size:12px;") expect_identical(css(font_size = "12px"), "font-size:12px;") expect_identical(css(fontSize = "12px"), "font-size:12px;") expect_identical(css(`font-style` = "italic"), "font-style:italic;") expect_null(css(font.variant = NULL)) expect_identical( css(.webkit.animation = "fade-in 1s"), "-webkit-animation:fade-in 1s;" ) expect_identical( css(font.family = 'Helvetica, "Segoe UI"'), "font-family:Helvetica, \"Segoe UI\";" ) expect_identical( css("font-weight!" = factor("bold")), "font-weight:bold !important;" ) expect_identical( css(padding = c("10px", "9px", "8px")), "padding:10px 9px 8px;" ) # CSS variables expect_identical(css("--_foo" = "bar"), "--_foo:bar;") expect_identical(css("--fooBar" = "baz"), "--fooBar:baz;") expect_identical(css("--foo_bar" = "baz"), "--foo_bar:baz;") expect_identical(css("--_foo!" = "bar"), "--_foo:bar !important;") expect_identical(css("--fooBar!" = "baz"), "--fooBar:baz !important;") expect_identical(css("--foo_bar!" = "baz"), "--foo_bar:baz !important;") # Mix of CSS variables and regular CSS properties expect_identical( css( "--empty" = NULL, "--_foo" = "bar", `_foo` = "bar", "--foo_bar" = "baz", foo_bar = "baz", "--fooBar" = "baz", fooBar = "baz", ), "--_foo:bar;-foo:bar;--foo_bar:baz;foo-bar:baz;--fooBar:baz;foo-bar:baz;" ) # Lists can be spliced expect_identical(css(!!!list(a = 1, b = 2)), "a:1;b:2;") # Factors are coerced to strings expect_identical(css(a = factor('a')), "a:a;") # Unnamed args not allowed expect_error(css("10")) expect_error(css(1, b=2)) # NULL and empty string are dropped expect_null(css(a="", b = NULL, "c!" = NULL, d = character())) # We are dumb about duplicated properties. Probably don't do that. expect_identical(css(a=1, a=2), "a:1;a:2;") }) test_that("Non-tag objects can be coerced", { .GlobalEnv$as.tags.testcoerce1 <- function(x) { list(singleton(list("hello"))) } on.exit(rm("as.tags.testcoerce1", pos = .GlobalEnv), add = TRUE) # Make sure tag-coerceable objects are tagified result1 <- renderTags(structure(TRUE, class = "testcoerce1")) expect_identical(result1$html, HTML("hello")) expect_identical(result1$singletons, "110d1f0ef6762db2c6863523a7c379a697b43ea3") # Make sure tag-coerceable objects are tagified before singleton handling # occurs, but that over-flattening doesn't happen result2 <- renderTags(tagList( singleton(list("hello")), structure(TRUE, class = "testcoerce1") )) expect_identical(result2$html, HTML("hello")) expect_identical(result2$singletons, "110d1f0ef6762db2c6863523a7c379a697b43ea3") }) test_that("Latin1 and system encoding are converted to UTF-8", { #Sys.setlocale(, "Chinese") latin1_str <- rawToChar(as.raw(0xFF)) Encoding(latin1_str) <- "latin1" divLatin1 <- as.character(tags$div(latin1_str)) expect_identical( charToRaw(divLatin1), as.raw(c(0x3c, 0x64, 0x69, 0x76, 0x3e, 0xc3, 0xbf, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e)) ) expect_identical(Encoding(divLatin1), "UTF-8") expect_identical(Encoding("\u4E11"), "UTF-8") divUTF8 <- as.character(tags$div("\u4E11")) expect_identical( charToRaw(divUTF8), as.raw(c(0x3c, 0x64, 0x69, 0x76, 0x3e, 0xe4, 0xb8, 0x91, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e)) ) expect_identical(Encoding(divUTF8), "UTF-8") divMixed <- format(tags$div( "\u4E11", latin1_str, tags$span(a="\u4E11", latin1_str), tags$span(b=latin1_str, HTML("\u4E11")) )) expect_identical( charToRaw(divMixed), as.raw(c(0x3c, 0x64, 0x69, 0x76, 0x3e, 0x0a, 0x20, 0x20, 0xe4, 0xb8, 0x91, 0x0a, 0x20, 0x20, 0xc3, 0xbf, 0x0a, 0x20, 0x20, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x61, 0x3d, 0x22, 0xe4, 0xb8, 0x91, 0x22, 0x3e, 0xc3, 0xbf, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x62, 0x3d, 0x22, 0xc3, 0xbf, 0x22, 0x3e, 0xe4, 0xb8, 0x91, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0a, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e )) ) expect_identical(Encoding(divMixed), "UTF-8") # Encoding(HTML(latin1_str)) is "UTF-8" on Linux; even just # paste(latin1_str) returns a UTF-8 encoded string #expect_identical(Encoding(HTML(latin1_str)), "latin1") expect_identical(Encoding(format(HTML(latin1_str))), "UTF-8") expect_identical(Encoding(format(tagList(latin1_str))), "UTF-8") # ensure the latin1 attribute returns correctly after escaping latin1_str2 <- rawToChar(as.raw(c(0xff, 0x0d, 0x0a))) Encoding(latin1_str2) <- "latin1" spanLatin <- as.character(tags$span(latin1_str2, title = latin1_str2)) expect_identical(Encoding(spanLatin), "UTF-8") expect_identical( charToRaw(spanLatin), as.raw(c(0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x3d, 0x22, 0xc3, 0xbf, 0x26, 0x23, 0x31, 0x33, 0x3b, 0x26, 0x23, 0x31, 0x30, 0x3b, 0x22, 0x3e, 0xc3, 0xbf, 0x0d, 0x0a, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e )) ) }) test_that("paste8 in Chinese locale works", { loc <- "Chinese" testthat::skip_if_not(is_locale_available(loc), "Chinese locale not available") withr::with_locale(c(LC_COLLATE=loc, LC_CTYPE=loc, LC_MONETARY=loc, LC_TIME=loc), { x <- "\377" Encoding(x) <- "latin1" expect_identical(x, "\Uff") expect_identical(Encoding(x), "latin1") y <- "\U4E2d" # Using \Uxxxx always is encoded as UTF-8 expect_identical(y, "\U4E2d") expect_identical(Encoding(y), "UTF-8") xy <- paste8(x, y) xy expect_identical(xy, "\Uff \U4E2d") expect_identical(Encoding(xy), "UTF-8") xy <- paste8(c(x, y), collapse = "") expect_identical(xy, "\Uff\U4E2d") expect_identical(Encoding(xy), "UTF-8") }) }) test_that("Printing tags works", { expect_identical( capture.output(print(tags$a(href = "#", "link"))), 'link' ) }) test_that("htmlEscape will try to coerce inputs to characters", { x <- list(a1 = "b", a2 = list("b1", "b2")) expect_identical( htmlEscape(x), as.character(x) ) }) test_that("trailing commas allowed everywhere", { expect_silent({ t1 <- div("foo",) tagList(t1,) tagSetChildren(t1, "child",) tagAppendAttributes(t1, class = "bar",) tagAppendChildren(t1, "child2",) css(style = "",) }) }) test_that("extractPreserveChunks works for emoji strings", { # solaris doesn't seem to support Unicode characters with surrogate pairs # (just by creating such a string will cause a warning) # > "\U0001F937" # [1] "\U0001f937" # Warning message: # it is not known that wchar_t is Unicode on this platform skip_on_os("solaris") x <- "chunk1\U0001F937chunk2" out <- extractPreserveChunks(x) expect_equivalent( out$chunks, c('chunk2', 'chunk1') ) }) test_that("complicated class attributes are handled", { x <- div(class = as.factor(letters)[1], class = "b c", class = c("d", "e f")) expect_equal( tagGetAttribute(x, "class"), "a b c d e f" ) expect_identical( as.character(x), "
" ) }) test_that("html render method", { local_edition(3) # Have a place holder div and return a span instead obj <- div("example", .renderHook = function(x) { x$name <- "span" x }) expect_equal(obj$name, "div") expect_snapshot(as.character(obj)) # Add a class to the tag spanExtra <- tagAddRenderHook(obj, function(x) { tagAppendAttributes(x, class = "extra") }) expect_equal(spanExtra$name, "div") expect_equal(spanExtra$attribs$class, NULL) expect_snapshot(as.character(spanExtra)) # Replace the previous render method # Should print a `div` with class `"extra"` divExtra <- tagAddRenderHook(obj, replace = TRUE, function(x) { tagAppendAttributes(x, class = "extra") }) expect_equal(divExtra$attribs$class, NULL) expect_snapshot(as.character(divExtra)) # Add more child tags spanExtended <- tagAddRenderHook(obj, function(x) { tagAppendChildren(x, tags$strong("bold text")) }) expect_equal(spanExtended$name, "div") expect_equal(spanExtended$children, obj$children) expect_snapshot(as.character(spanExtended)) tagFuncExt <- tagAddRenderHook(obj, function(x) { tagFunction(function() tagList(x, tags$p("test")) ) }) expect_equal(tagFuncExt$name, "div") expect_equal(tagFuncExt$children, obj$children) expect_snapshot(as.character(tagFuncExt)) # Add a new html dependency newDep <- tagAddRenderHook(obj, function(x) { fa <- htmlDependency( "font-awesome", "4.5.0", c(href="shared/font-awesome"), stylesheet = "css/font-awesome.min.css") attachDependencies(x, fa, append = TRUE) }) # Also add a jqueryui html dependency htmlDependencies(newDep) <- htmlDependency( "jqueryui", "1.11.4", c(href="shared/jqueryui"), script = "jquery-ui.min.js") expect_equal(newDep$name, "div") expect_length(htmlDependencies(newDep), 1) expect_snapshot(renderTags(newDep)) # Ignore the original tag and return something completely new. newObj <- tagAddRenderHook(obj, function(x) { tags$p("Something else") }) expect_equal(newObj$name, "div") expect_snapshot(as.character(newObj)) }) test_that(".cssSelector arg only applies changes to the selected elements", { html <- div( class = "outer", div(class = "inner", "text"), span("TEXT") ) expect_equal_tags( tagAppendAttributes(html, id = "test"), div(class = "outer", id = "test", div(class="inner", "text"), span("TEXT")) ) expect_equal_tags( tagAppendAttributes(html, id = "test", .cssSelector = ".inner"), div(class = "outer", div(class = "inner", id = "test", "text"), span("TEXT")) ) expect_equal_tags( tagAppendChild(html, h1()), div(class = "outer", div(class="inner", "text"), span("TEXT"), h1()) ) expect_equal_tags( tagAppendChild(html, h1(), .cssSelector = ".inner"), div(class = "outer", div(class = "inner", "text", h1()), span("TEXT")) ) expect_equal_tags( tagAppendChildren(html, h1(), h2()), div(class = "outer", div(class="inner", "text"), span("TEXT"), h1(), h2()) ) expect_equal_tags( tagAppendChildren(html, h1(), h2(), .cssSelector = ".inner"), div(class = "outer", div(class = "inner", "text", h1(), h2()), span("TEXT")) ) expect_equal_tags( tagSetChildren(html, h1(), h2()), div(class = "outer", h1(), h2()) ) expect_equal_tags( tagSetChildren(html, h1(), h2(), .cssSelector = ".inner"), div(class = "outer", div(class = "inner", h1(), h2()), span("TEXT")) ) expect_equal_tags( tagInsertChildren(html, h1(), h2(), after = 0), div(class = "outer", h1(), h2(), div(class="inner", "text"), span("TEXT")) ) expect_equal_tags( tagInsertChildren(html, h1(), h2(), after = 0, .cssSelector = ".inner"), div(class = "outer", div(class = "inner", h1(), h2(), "text"), span("TEXT")) ) }) test_that("flattenTagAttribs", { attribs <- list( b = "1", a = "2", b = "3" ) flatAttribs <- flattenTagAttribs(attribs) # alpha sorted expect_equal(names(flatAttribs), c("a", "b")) # b values are collected expect_equal(flatAttribs, list(a = "2", b = c("1", "3"))) }) test_that("htmlDependency() can be included in rmarkdown via knit_print", { skip_if_not_installed("knitr") dep <- htmlDependency( "dummytestdep", "1.0", c(href = "http://example.com/"), script = "test.js" ) dep_knitr <- knit_print.html_dependency(dep) expect_s3_class(dep_knitr, "knit_asis") expect_equal(attr(dep_knitr, "knit_meta")[[1]], dep) }) test_that("includeHTML() warns if full document is detected", { tmp_html <- withr::local_tempfile(fileext = ".html") writeLines("

test

", tmp_html) expect_warning(includeHTML(tmp_html)) save_html(p("test"), tmp_html) expect_warning(includeHTML(tmp_html)) }) htmltools/tests/testthat/test-print.R0000644000176200001440000000530314600330155017533 0ustar liggesuserstest_that("print.html preserves dependencies for HTML()", { # Regression test for issue #125 dep <- htmlDependency("dummytestdep", "1.0", c(href="http://example.com/"), script = "test.js" ) url <- NULL op <- options(viewer = function(url) { url <<- url }) on.exit(options(op), add = TRUE) print(attachDependencies(HTML("test"), list(dep) ), browse = TRUE) result_contents <- readLines(url) expect_true(any(grepl("http://example.com/test.js", result_contents))) }) test_that("CRLF is properly handled", { txt <- paste(c("x", "y", ""), collapse = "\r\n") tmp <- tempfile(fileext = ".txt") on.exit(unlink(tmp), add = TRUE) writeBin(charToRaw(txt), tmp) obj <- tagList( includeHTML(tmp), includeCSS(tmp), includeMarkdown(tmp), includeScript(tmp), includeText(tmp), txt, HTML(txt) ) out <- tempfile(fileext = ".html") on.exit(unlink(out), add = TRUE) wd <- getwd() save_html(obj, out) # Verify that save_html doesn't alter working dir expect_identical(getwd(), wd) chr <- readChar(out, file.size(out)) expect_false(grepl("\r\r\n", chr)) expect_false(grepl("\r\r\n", as.character(obj))) }) test_that("Special characters are not re-encoded", { skip_on_cran() # https://github.com/rstudio/htmltools/pull/117 f <- tempfile(fileext = ".html") withr::with_options( list(encoding = "UTF-8"), { save_html(div("brûlée"), f) expect_true(any(grepl("brûlée", readLines(f)))) } ) }) test_that("save_html() language parameter is set", { output <- tempfile(fileext = ".html") # test for default save_html("

Howdy

", output) output_read <- readLines(output) expect_true( grepl("", paste(output_read, collapse = " ")) ) # test for fr save_html("

Howdy

", output, lang = "fr") output_read <- readLines(output) expect_true( grepl("", paste(output_read, collapse = " ")) ) }) test_that("save_html() can write to subdirectories", { tmpDir <- tempfile() dir.create(tmpDir) withr::local_dir(tmpDir) dir.create("foo") save_html(tags$h2("Howdy"), "foo/bar.html") expect_true( grepl("

Howdy

", paste(readLines("foo/bar.html"), collapse = " ")) ) }) test_that("save_html() can write to a file connection", { f <- file() on.exit(close(f), add = TRUE) save_html(tags$h2("Howdy"), f) expect_true( grepl("

Howdy

", paste(readLines(f), collapse = " ")) ) }) test_that("save_html.default() throws when undefined arguments are provided", { expect_error( save_html(div(), tempfile(), foo = "bar") ) expect_error( save_html(div(), tempfile(), background = "white", libdir = "lib", lang = "en", "bar") ) }) htmltools/tests/testthat/test-images.R0000644000176200001440000000112614600330155017643 0ustar liggesuserscontext("images") test_that("capturePlot works with device functions with various signatures", { # If these run without throwing, that's success capturePlot(plot(cars), device = grDevices::png) capturePlot(plot(cars), device = function(filename, width, height) { grDevices::png(filename = filename, width = width, height = height) }) capturePlot(plot(cars), device = function(filename, ...) { grDevices::png(filename = filename, ...) }) # Ensure blank plot works plotTag({}, alt = "", device = png) # So testthat knows we didn't skip testing expect_true(TRUE) }) htmltools/tests/testthat/test-deps.r0000644000176200001440000003272414600330155017401 0ustar liggesuserscontext("dependencies") format.html_dependency <- function(x, ...) { sprintf("%s v%s @ %s", x$name, x$version, format(x$src)) } print.html_dependency <- function(x, ...) { cat(format(x), "\n") invisible(x) } expect_resolved_deps <- function(input, output) { expect_identical(resolveDependencies(input), output) } test_that("Dependency resolution works", { a1.1 <- htmlDependency("a", "1.1", c(href="/")) a1.2 <- htmlDependency("a", "1.2", c(href="/")) a1.2.1 <- htmlDependency("a", "1.2.1", c(href="/")) b1.0.0 <- htmlDependency("b", "1.0.0", c(href="/")) b1.0.1 <- htmlDependency("b", "1.0.1", c(href="/")) c1.0 <- htmlDependency("c", "1.0", c(href="/")) expect_resolved_deps( list(a1.1, b1.0.0, b1.0.1, a1.2, a1.2.1, b1.0.0, b1.0.1, c1.0), list(a1.2.1, b1.0.1, c1.0) ) expect_resolved_deps( list(tagFunction(function() { NULL })), list() ) expect_resolved_deps( list(tagFunction(function() { a1.2 })), list(a1.2) ) expect_resolved_deps( list(a1.1, tagFunction(function() { NULL })), list(a1.1) ) expect_resolved_deps( list(a1.1, tagFunction(function() { a1.2 })), list(a1.2) ) expect_resolved_deps( list(a1.1, tagFunction(function() { list(a1.2, a1.2.1) })), list(a1.2.1) ) expect_warning(expect_resolved_deps( list(a1.1, tagFunction(function() { div("foo", a1.2, a1.2.1) })), list(a1.2.1) )) expect_warning(expect_resolved_deps( list(a1.1, tagFunction(function() { tagList(a1.2, a1.2.1) })), list(a1.2.1) )) expect_warning(expect_resolved_deps( list(a1.1, tagFunction(function() { tagList(div("foo", a1.2), div("foo", a1.2.1)) })), list(a1.2.1) )) expect_warning(expect_resolved_deps( list(a1.1, tagFunction(function() { tagList(div("foo", a1.2), div("foo", tagFunction(function() { a1.2.1 }))) } )), list(a1.2.1) )) res <- subtractDependencies(list(a1.2.1, b1.0.1), list(a1.1), warnOnConflict = FALSE) expect_identical(res, list(b1.0.1)) expect_warning(subtractDependencies(list(a1.2.1, b1.0.1), list(a1.1))) }) expect_html_deps <- function(x, html, deps) { expect_identical(as.character(renderTags(x)$html), html) expect_output(print(as.tags(x)), html) expect_identical(findDependencies(x), deps) } test_that("Inline dependencies", { # Test out renderTags and findDependencies when tags are inline a1.1 <- htmlDependency("a", "1.1", c(href="/")) a1.2 <- htmlDependency("a", "1.2", c(href="/")) # tagLists ---------------------------------------------------------- x <- tagList(a1.1, div("foo"), "bar") expect_html_deps(x, "
foo
\nbar", list(a1.1)) x <- tagList(a1.1, div("foo"), a1.2, "bar") expect_html_deps(x, "
foo
\nbar", list(a1.1, a1.2)) x <- tagList(a1.1, div("foo"), "bar", tagFunction(function() { a1.2 })) expect_html_deps(x, "
foo
\nbar", list(a1.1, a1.2)) x <- tagList(a1.1, div("foo"), "bar", tagFunction(function() { div("baz", a1.2) })) expect_html_deps(x, "
foo
\nbar\n
baz
", list(a1.1, a1.2)) # Mixing inline and attribute dependencies x <- attachDependencies( tagList(a1.1, div("foo"), "bar"), a1.2, append = TRUE ) expect_html_deps(x, "
foo
\nbar", list(a1.1, a1.2)) x <- attachDependencies( tagList(a1.1, div("foo"), "bar"), tagFunction(function() { a1.2 }), append = TRUE ) expect_html_deps(x, "
foo
\nbar", list(a1.1, a1.2)) x <- attachDependencies( tagList(div("foo"), "bar", tagFunction(function() { a1.1 })), tagFunction(function() { a1.2 }), append = TRUE ) expect_html_deps(x, "
foo
\nbar", list(a1.1, a1.2)) # tags with children ------------------------------------------------ x <- div(a1.1, div("foo"), "bar") expect_html_deps(x, "
\n
foo
\n bar\n
", list(a1.1)) x <- div( tagFunction(function() { a1.1 }), tagFunction(function() { div("foo") }), tagFunction(function() { "bar" }) ) expect_html_deps(x, "
\n
foo
\n bar\n
", list(a1.1)) x <- tagFunction(function() { div(div("foo"), a1.2, tagFunction(function() { "bar"}), a1.1) }) expect_html_deps(x, "
\n
foo
\n bar\n
", list(a1.2, a1.1)) x <- attachDependencies(div(a1.1, div("foo"), "bar"), a1.2, append = TRUE) expect_html_deps(x, "
\n
foo
\n bar\n
", list(a1.1, a1.2)) x <- attachDependencies(div(a1.1, div("foo"), "bar"), tagFunction(function() { a1.2 }), append = TRUE) expect_html_deps(x, "
\n
foo
\n bar\n
", list(a1.1, a1.2)) # Passing normal lists to tagLists and tag functions --------------- x <- tagList(list(a1.1, div("foo")), "bar") expect_html_deps(x, "
foo
\nbar", list(a1.1)) x <- tagList(list(tagFunction(function() { a1.1 }), div("foo")), "bar") expect_html_deps(x, "
foo
\nbar", list(a1.1)) x <- div(list(a1.1, div("foo")), "bar") expect_html_deps(x, "
\n
foo
\n bar\n
", list(a1.1)) x <- div(list(tagFunction(function() { a1.1 }), div("foo")), "bar") expect_html_deps(x, "
\n
foo
\n bar\n
", list(a1.1)) # Top-level lists ----------------------------------- x <- list(div("ab"), "cd", a1.1) expect_html_deps(x, "
ab
\ncd", list(a1.1)) x <- structure(list(div("ab"), "cd", a1.1), class = "foo") expect_html_deps(x, "
ab
\ncd", list(a1.1)) x <- tagList(tagFunction(function() { list(div("ab"), "cd", a1.1) }), "bar") expect_html_deps(x, "
ab
\ncd\nbar", list(a1.1)) }) test_that("Modifying children using dependencies", { a1.1 <- htmlDependency("a", "1.1", c(href="/")) a1.2 <- htmlDependency("a", "1.2", c(href="/")) x <- tagAppendChild(div(a1.1), a1.2) expect_identical(findDependencies(x), list(a1.1, a1.2)) x <- tagAppendChild(div(a1.1), tagFunction(function() { a1.2 })) expect_identical(findDependencies(x), list(a1.1, a1.2)) x <- tagAppendChild(div(a1.1), list(a1.2)) expect_identical(findDependencies(x), list(a1.1, a1.2)) x <- tagAppendChildren(div(), a1.1, list(a1.2)) expect_identical(findDependencies(x), list(a1.1, a1.2)) x <- tagAppendChildren(div(), a1.1, tagFunction(function() { list(a1.2) })) expect_identical(findDependencies(x), list(a1.1, a1.2)) x <- tagSetChildren(div("foo", a1.1), a1.2) expect_identical(findDependencies(x), list(a1.2)) x <- tagSetChildren(div("foo", a1.1), tagFunction(function() { a1.2 })) expect_identical(findDependencies(x), list(a1.2)) }) test_that("able to resolve HTML scripts supplied with & without integrity", { src1 <- "https://cdn.com/libs/p1/0.1/" src2 <- "https://cdn/libs/p2/0.2/" deps <- list( htmlDependency( name = "p1", version = "0.1", src = list(href = src1), script = list( src = "p1.min.js", integrity = "longhash", crossorigin = "anonymous", defer = NA ) ), htmlDependency( "p2", version = "0.2", src = list(href = src2), script = "p2.min.js" ) ) expect1 <- paste( '', sep = '' ) expect2 <- paste( '', sep = '' ) expect <- paste(expect1, expect2, sep = '\n') class(expect) <- c("html", "character") actual <- renderDependencies(deps) expect_equal(!!strsplit(actual, "\n"), !!strsplit(expect, "\n")) }) test_that( "can render scripts given as lists of nested lists + scalar strings", { src = "https://cdn.com/libs/p1/0.1" nm <- "p1.js" d1 <- htmlDependency( "p1", "0.1", src = list(href = src), script = list(src = nm) ) deps1 <- list( d1, htmlDependency( "p1", "0.2", src = list(href = src), script = nm ), htmlDependency( "p1", "0.3", src = list(href = src), script = list(list(src = nm)) ) ) out <- renderDependencies(deps1) deps2 <- list( d1, d1, d1 ) expect_length(unique(unlist(strsplit(out, "\n"))), 1) expect_equal(renderDependencies(deps1), renderDependencies(deps2)) nm2 <- "p1-0.1.js" deps3 <- list( htmlDependency( "p1", "0.1", src = list(href = src), script = c(nm, nm2) ) ) out <- renderDependencies(deps3) src_urls <- c( file.path(src, nm), file.path(src, nm2) ) expect <- paste( '\n', '', sep = "") expect_equal(!!as.character(out), !!expect) deps4 <- list( htmlDependency( "p1", "0.1", src = list(href = src), script = list(list(src = nm, integrity = "hash"), nm2) ) ) out <- renderDependencies(deps4) expect <- paste( '\n', '', sep = "") expect_equal(!!as.character(out), !!expect) }) test_that("html escaping is carried out correctly in script rendering", { src = "https://cdn.com/libs/p1/0.1" nm <- "p1.js" funky_hash <- "" deps <- list( htmlDependency( "p1", "0.1", src = list(href = src), script = list(src = nm, integrity = funky_hash) ) ) src_url <- file.path(src, nm) expect <- paste( '', sep = "" ) out <- renderDependencies(deps) expect_equal(!!as.character(out), !!expect) }) test_that("copyDependencyToDir() doesn't create an empty directory", { tmp_dep <- tempfile("dep") dir.create(tmp_dep) on.exit(unlink(tmp_dep)) tmp_rmd <- tempfile("rmd_files") dir.create(tmp_rmd) on.exit(unlink(tmp_rmd), add = TRUE) empty <- htmltools::htmlDependency( name = "empty", version = "9.9.9", src = tmp_dep, head = "", all_files = FALSE ) copied_dep <- copyDependencyToDir(empty, tmp_rmd) # no directory is created for the empty dep expect_equal(dir(tmp_rmd), character()) # copied dependency src points to folder where files should be so that # to keep relativeTo() from throwing an error later in Rmd render process expect_match(copied_dep$src$file, normalizePath(tmp_rmd, "/", TRUE), fixed = TRUE) }) test_that("copyDependencyToDir() creates recursive output directories", { tmp_dep <- tempfile("dep") dir.create(tmp_dep) on.exit(unlink(tmp_dep, recursive = TRUE)) writeLines( c("alert('boo')"), file.path(tmp_dep, "script.js") ) dep <- htmltools::htmlDependency( name = "simple", version = "9.9.9", src = tmp_dep, script = "script.js", all_files = FALSE ) tmp_outputDir <- file.path(tempfile("outputDir"), "subdir") on.exit(unlink(tmp_outputDir, recursive = TRUE), add = TRUE) expect_silent(copyDependencyToDir(dep, tmp_outputDir)) # copyDependencyToDir() creates the nested outputDir expect_true(dir_exists(file.path(tmp_outputDir))) # it moves the dependency into this dir expect_true(dir_exists(file.path(tmp_outputDir, "simple-9.9.9"))) expect_true(file.exists(file.path(tmp_outputDir, "simple-9.9.9", "script.js"))) }) test_that("copyDependencyToDir() handles attributes", { tmp_dep <- tempfile("dep") dir.create(tmp_dep) on.exit(unlink(tmp_dep)) tmp_txt <- "temp.txt" path <- file.path(tmp_dep, tmp_txt) writeLines("Some text in the text/plain dep", path) tmp_js <- "javascript.js" path <- file.path(tmp_dep, tmp_js) writeLines('console.log("log message");', path) tmp_rmd <- tempfile("rmd_files") dir.create(tmp_rmd) on.exit(unlink(tmp_rmd), add = TRUE) dep_with_attributes <- htmltools::htmlDependency( name = "textPlain", version = "9.9.9", src = tmp_dep, script = list(src = tmp_txt, type = "text/plain"), all_files = FALSE ) dep_without <- htmltools::htmlDependency( name = "textPlain", version = "9.9.9", src = tmp_dep, script = tmp_js, all_files = FALSE ) dep_with_both <- htmltools::htmlDependency( name = "textPlain", version = "9.9.9", src = tmp_dep, script = list(tmp_js, list(src = tmp_txt, type = "text/plain")), all_files = FALSE ) dep_with_one_nested <- htmltools::htmlDependency( name = "textPlain", version = "9.9.9", src = tmp_dep, script = list(list(src = tmp_txt, type = "text/plain")), all_files = FALSE ) dep_with_missings <- htmltools::htmlDependency( name = "textPlain", version = "9.9.9", src = tmp_dep, script = list(tmp_js, "foobar1", list(src = "foobar2")), all_files = FALSE ) # None of these except the last should trigger errors as # the first two did in issue #320 copyDependencyToDir(dep_with_attributes, tmp_rmd) expect_equal(dir(tmp_rmd, recursive = TRUE), "textPlain-9.9.9/temp.txt") unlink(dir(tmp_rmd, recursive = TRUE)) copyDependencyToDir(dep_with_both, tmp_rmd) expect_equal(dir(tmp_rmd, recursive = TRUE), c("textPlain-9.9.9/javascript.js", "textPlain-9.9.9/temp.txt")) unlink(dir(tmp_rmd, recursive = TRUE)) copyDependencyToDir(dep_without, tmp_rmd) expect_equal(dir(tmp_rmd, recursive = TRUE), "textPlain-9.9.9/javascript.js") unlink(dir(tmp_rmd, recursive = TRUE)) copyDependencyToDir(dep_with_one_nested, tmp_rmd) expect_equal(dir(tmp_rmd, recursive = TRUE), "textPlain-9.9.9/temp.txt") expect_error(copyDependencyToDir(dep_with_missings, tmp_rmd)) }) htmltools/tests/testthat/test-tag-query.R0000644000176200001440000005317615113345760020340 0ustar liggesusers fakeJqueryDep <- htmlDependency("jquery", "1.11.3", c(href="shared"), script = "jquery.js") fakeTagFunction <- tagFunction(function(){ span("inner span") }) test_that("safeListToEnv and safeEnvToList undo each other", { x <- structure( list( A = 1, B = 2 ), class = "test_class", extra_dep = list(42), other_dep = "exists" ) xExpected <- x xEnv <- safeListToEnv(x, "extra_class") expect_type(xEnv, "environment") expect_s3_class(xEnv, "test_class") expect_s3_class(xEnv, "extra_class") expect_equal(names(xEnv), c("A", "B")) }) test_that("asTagEnv upgrades objects", { expect_error(asTagEnv(list()), "does not accept") expect_error(asTagEnv(tagList()), "does not accept") x <- div(class = "test_class", span(class = "inner")) xTagEnv <- asTagEnv(x) expect_s3_class(xTagEnv, "shiny.tag.env") expect_s3_class(xTagEnv, "shiny.tag") expect_null(xTagEnv$parent) expect_equal(xTagEnv$envKey, obj_address(xTagEnv)) expect_equal(xTagEnv$name, x$name) expect_equal(xTagEnv$attribs, x$attribs) expect_equal(length(xTagEnv$children), length(x$children)) lapply(xTagEnv$children, function(child) { expect_s3_class(child, "shiny.tag.env") expect_equal(child$parent$envKey, xTagEnv$envKey) }) }) ## Cycles are not tested for anymore. Keeping in case they are brought back # test_that("asTagEnv finds cycles", { # x <- div(class = "test_class", span(class = "inner")) # xTagEnv <- asTagEnv(x) # expect_error(asTagEnv(xTagEnv), NA) # testSpanEnv <- xTagEnv$children[[1]] # xTagEnv$children[[2]] <- testSpanEnv # xTagEnv$children[[3]] <- testSpanEnv # expect_error(asTagEnv(xTagEnv), "Duplicate tag environment found") # expect_equal_tags( # tagEnvToTags(xTagEnv), # div( # class = "test_class", # span(class = "inner"), # span(class = "inner"), # span(class = "inner") # ) # ) # # make a cycle # testSpanEnv$children[[1]] <- xTagEnv # expect_error(asTagEnv(xTagEnv), "Duplicate tag environment") # }) test_that("tagQuery() root values", { expect_error(tagQuery(div()), NA) expect_error(tagQuery(list()), "initial set") expect_error(tagQuery(tagList()), "initial set") expect_error(tagQuery(tagList(div())), NA) expect_error(tagQuery(5), "initial set") expect_error(tagQuery("a"), "initial set") expect_error(tagQuery(fakeJqueryDep), "initial set") expect_error(tagQuery(fakeTagFunction), "initial set") x <- tagQuery(div(span(), a()))$find("span") # expect_equal_tags(x$selectedTags(), tagListPrintAsList(span())) # expect_equal_tags(x$selectedTags(), tagListPrintAsList(div(span(), a()))) # supply a tag query object expect_equal_tags(tagQuery(x)$selectedTags(), x$selectedTags()) expect_equal_tags(tagQuery(x)$allTags(), x$allTags()) # supply a list of tag envs tagEnvs <- list() x$each(function(el, i) { tagEnvs[[length(tagEnvs) + 1]] <<- el}) expect_equal_tags(tagQuery(tagEnvs)$selectedTags(), x$selectedTags()) expect_equal_tags(tagQuery(tagEnvs)$allTags(), x$allTags()) # supply a single tag env expect_equal_tags(tagQuery(tagEnvs[[1]])$selectedTags(), x$selectedTags()) expect_equal_tags(tagQuery(tagEnvs[[1]])$allTags(), x$allTags()) }) test_that("tagQuery() structure", { x <- tagQuery(div()) expect_s3_class(x, "shiny.tag.query") lapply(x, function(xI) { expect_true(is.function(xI)) }) }) test_that("tagQuery()$find()", { x <- tagQuery(div(span("a"), span("b"))) # Make sure the found elements do not persist newX <- x$find("span") expect_condition( expect_equal_tags( x$selectedTags(), newX$selectedTags() ), class = "expectation_failure" ) x <- x$find("span") expect_equal(x$length(), 2) expect_length(x$selectedTags(), 2) expect_equal_tags( x$selectedTags(), tagListPrintAsList(span("a"), span("b")) ) ul <- tags$ul li <- tags$li x <- tagQuery(div(div(div(ul(li("a"), li("b"), li("c")))))) expect_equal(x$length(), 1) expect_length(x$selectedTags(), 1) x <- x$find("div") expect_equal(x$length(), 2) expect_length(x$selectedTags(), 2) x <- x$find("div") expect_equal(x$length(), 1) expect_length(x$selectedTags(), 1) x <- tagQuery( div( class = "outer", div(a(span(p("text1")))), div(a(p("text2"))) ) ) x <- x$find("a") expect_equal(x$length(), 2) expect_length(x$selectedTags(), 2) x <- x$resetSelected() x <- x$find("a > p") expect_equal(x$length(), 1) expect_length(x$selectedTags(), 1) expect_equal_tags(x$selectedTags(), tagListPrintAsList(p("text2"))) x <- x$resetSelected() x <- x$find("a > > p") expect_equal(x$length(), 1) expect_length(x$selectedTags(), 1) expect_equal_tags(x$selectedTags(), tagListPrintAsList(p("text1"))) x <- x$resetSelected() x <- x$find("div > *") expect_equal(x$length(), 2) expect_length(x$selectedTags(), 2) expect_equal_tags(x$selectedTags(), tagListPrintAsList(a(span(p("text1"))), a(p("text2")))) x <- x$resetSelected() x <- x$find("div>>p") expect_equal(x$length(), 1) expect_length(x$selectedTags(), 1) expect_equal_tags(x$selectedTags(), tagListPrintAsList(p("text2"))) }) test_that("tagQuery()$filter()", { x <- tagQuery(div(span(1), span(2), span(3), span(4), span(5))) x <- x$find("span") expect_length(x$selectedTags(), 5) # keep the even found elements x <- x$filter(function(item, i) { # is even (i %% 2) == 0 }) expect_length(x$selectedTags(), 2) # keep the filtered even elements. Should only have the 4th one remaining x <- x$filter(function(item, i) { # is even (i %% 2) == 0 }) expect_length(x$selectedTags(), 1) expect_equal_tags(x$selectedTags(), tagListPrintAsList(span(4))) }) test_that("tagQuery()$children() & tagQuery()$parent()", { x <- tagQuery( div(class="outer", div(class="a", span(class="A", "1"), span(class="B", "2")), div(class = "b", span(class = "C", "3"), span(class = "D", "4") ) ) ) x <- x$find("div") expect_length(x$selectedTags(), 2) x <- x$children() expect_length(x$selectedTags(), 4) expect_equal_tags( x$selectedTags(), tagListPrintAsList( span(class = "A", "1"), span(class = "B", "2"), span(class = "C", "3"), span(class = "D", "4") ) ) x <- x$parent() expect_length(x$selectedTags(), 2) x <- x$children(".C") expect_length(x$selectedTags(), 1) x <- x$parent() expect_length(x$selectedTags(), 1) secondDiv <- div(class = "b", span(class = "C", "3"), span(class = "D", "4")) expect_equal_tags(x$selectedTags(), tagListPrintAsList(secondDiv)) x <- x$resetSelected()$find("span")$parents(".b") expect_length(x$selectedTags(), 1) expect_equal_tags(x$selectedTags(), tagListPrintAsList(secondDiv)) }) test_that("tagQuery()$parents() && tagQuery()$closest()", { xTags <- div(class = "outer", div(class = "inner", p(class="p", span("a"), span("b"), span("c"), span("d"), span("e") ) ) ) x <- tagQuery(xTags) expect_length(x$selectedTags(), 1) xc <- x$find("span")$closest("div") expect_length(xc$selectedTags(), 1) expect_true(xc$hasClass("inner")) xc <- x$find("span")$closest() expect_length(xc$selectedTags(), 5) xc$each(function(el, i) { expect_equal(el$name, "span") }) xp <- x$find("span")$parents("div") expect_length(xp$selectedTags(), 2) expect_equal(xp$hasClass("outer"), c(FALSE, TRUE)) expect_equal(xp$hasClass("inner"), c(TRUE, FALSE)) x <- x$find("span")$parents() expect_length(x$selectedTags(), 3) expect_equal_tags( x$selectedTags(), tagListPrintAsList( xTags$children[[1]]$children[[1]], xTags$children[[1]], xTags ) ) x <- x$resetSelected()$find("span")$parents(".outer") expect_length(x$selectedTags(), 1) expect_equal_tags( x$selectedTags(), tagListPrintAsList(xTags) ) }) test_that("tagQuery()$siblings()", { xTags <- tagList( span("a"), span("b"), span("c"), span("d"), span("e") ) x <- tagQuery(xTags) expect_length(x$selectedTags(), 5) x <- x$siblings() expect_length(x$selectedTags(), 5) xTags <- tagList( span("a"), span("b"), span("c", class = "middle"), span("d"), span("e") ) x <- tagQuery(xTags) expect_length(x$selectedTags(), 5) x <- x$filter(".middle") expect_length(x$selectedTags(), 1) x <- x$siblings() expect_length(x$selectedTags(), 4) }) test_that("tagQuery()$addClass()", { xTags <- div(class = "outer", div(class = "inner", span("a"), span("b"), span("c"), span("d"), span("e") ) ) x <- tagQuery(xTags) expect_length(x$selectedTags(), 1) x <- x$find("div.inner")$addClass("test-class") expect_length(x$selectedTags(), 1) expect_equal(x$selectedTags()[[1]]$attribs$class, "inner test-class") expect_silent({ x$addClass(NULL) x$removeClass(NULL) x$toggleClass(NULL) expect_equal(x$hasClass(NULL), c(FALSE)) }) expect_silent({ x$addClass(character(0)) x$removeClass(character(0)) x$toggleClass(character(0)) expect_equal(x$hasClass(character(0)), c(FALSE)) }) expect_equal_tags( tagQuery( div(class="A", class="B", "text") )$ addClass("C")$ removeClass("B")$ allTags(), div(class = "A C", "text") ) }) test_that("tagQuery()$hasClass(), $toggleClass(), $removeClass()", { xTags <- div(class = "outer", div(class = "A B", span(class = "odd", "a"), span(class = "even", "b"), span(class = "odd", "c"), span(class = "even", "d"), span(class = "odd", "e") ) ) x <- tagQuery(xTags) x <- x$find("div.A") expect_length(x$selectedTags(), 1) expect_equal(x$hasClass("B A"), TRUE) expect_equal(x$hasClass("A B"), TRUE) expect_equal(x$hasClass("B"), TRUE) expect_equal(x$hasClass("A"), TRUE) expect_equal(x$hasClass("C"), FALSE) x <- x$resetSelected()$find("span") expect_equal(x$hasClass("even"), c(FALSE, TRUE, FALSE, TRUE, FALSE)) expect_equal(x$hasClass("odd"), c(TRUE, FALSE, TRUE, FALSE, TRUE)) x$toggleClass("even odd") expect_equal(x$hasClass("even"), c(TRUE, FALSE, TRUE, FALSE, TRUE)) expect_equal(x$hasClass("odd"), c(FALSE, TRUE, FALSE, TRUE, FALSE)) x$removeClass("even") expect_equal(x$hasClass("even"), c(FALSE, FALSE, FALSE, FALSE, FALSE)) expect_equal(x$hasClass("odd"), c(FALSE, TRUE, FALSE, TRUE, FALSE)) x$removeClass("other odd") expect_equal(x$hasClass("odd"), c(FALSE, FALSE, FALSE, FALSE, FALSE)) }) test_that("tagQuery()$addAttrs(), $removeAttrs(), $s", { xTags <- tagList( span(key = "value - a", "a"), span(key = "value - b", "b"), span( "c"), span( "d"), span(key = "value - e", "e") ) x <- tagQuery(xTags) expect_length(x$selectedTags(), 5) expect_equal(x$hasAttrs("key"), c(TRUE, TRUE, FALSE, FALSE, TRUE)) x$addAttrs(key2 = "val2", key3 = "val3") expect_equal(x$hasAttrs("key"), c(TRUE, TRUE, FALSE, FALSE, TRUE)) expect_equal(x$hasAttrs("key2"), c(TRUE, TRUE, TRUE, TRUE, TRUE)) expect_equal(x$hasAttrs("key3"), c(TRUE, TRUE, TRUE, TRUE, TRUE)) x$removeAttrs(c("key", "key3")) expect_equal(x$hasAttrs("key"), c(FALSE, FALSE, FALSE, FALSE, FALSE)) expect_equal(x$hasAttrs("key2"), c(TRUE, TRUE, TRUE, TRUE, TRUE)) expect_equal(x$hasAttrs("key3"), c(FALSE, FALSE, FALSE, FALSE, FALSE)) }) test_that("tagQuery()$append()", { xTags <- div(span("child")) x <- tagQuery(xTags) newa <- span("a") x$append(newa) expect_equal_tags( x$allTags(), div(span("child"), newa) ) new1 <- div("new1") new2 <- div("new2") x$append(new1, new2) expect_equal_tags( x$allTags(), div(span("child"), newa, new1, new2) ) }) test_that("tagQuery()$prepend()", { xTags <- div(span("child")) x <- tagQuery(xTags) newa <- span("a") x$prepend(newa) expect_equal_tags( x$allTags(), div(newa, span("child")) ) new1 <- div("new1") new2 <- div("new2") x$prepend(new1, new2) expect_equal_tags( x$allTags(), div(new1, new2, newa, span("child")) ) }) test_that("tagQuery()$each()", { xTags <- div(span("a"), h1("title"), span("b")) x <- tagQuery(xTags) x <- x$find("span") expect_error(x$each("4"), "function") expect_error(x$each(function(item) {}), "two") expect_error(x$each(function(...) {}), NA) x$each(function(el, i) { el$children <- lapply(el$children, toupper) "ignored" }) expect_equal_tags( x$allTags(), div(span("A"), h1("title"), span("B")) ) }) test_that("tagQuery()$allTags() & tagQuery()$rebuild()", { xTags <- div(span("a"), h1("title"), span("b")) x <- tagQuery(xTags) x$each(function(root, i) { # add a child to the root root$children[[length(root$children) + 1]] <- div("test") }) # retrieve the root (and direct children) from graph rootChildren <- x$allTags()$children lastChild <- rootChildren[[length(rootChildren)]] # make sure the last child is a tag env (not a standard tag) expect_false(isTagEnv(lastChild)) # make sure it equals what was manually added expect_equal_tags(lastChild, div("test")) }) test_that("tagQuery()$remove()", { xTags <- div( span("a"), span("b", class = "A"), span("c"), span("d", class = "A"), span("e") ) x <- tagQuery(xTags)$find("span") expect_equal(x$length(), 5) expect_length(x$selectedTags(), 5) x <- x$filter(".A")$remove() expect_equal(x$length(), 0) expect_length(x$selectedTags(), 0) expect_equal_tags( x$allTags(), div(span("a"), span("c"), span("e")) ) x <- x$resetSelected()$find("span") expect_equal(x$length(), 3) expect_length(x$selectedTags(), 3) x <- x$remove() expect_equal_tags( x$allTags(), div() ) # https://github.com/rstudio/htmltools/issues/346 # `isTagEnv("Barret")` is `FALSE` html <- div(tags$label("Carson"), "Barret") # Remove the label x <- tagQuery(html)$find("label")$remove() expect_equal_tags(x$allTags(), div("Barret")) }) test_that("tagQuery()$after()", { xTags <- div() x <- tagQuery(xTags) newa <- span("a") x$after(newa) expect_equal_tags( x$allTags(), tagList(xTags, newa) ) new1 <- div("new1") new2 <- div("new2") x$after(new1, new2) expect_equal_tags( x$allTags(), tagList(xTags, new1, new2, newa) ) }) test_that("tagQuery()$before()", { xTags <- div() x <- tagQuery(xTags) newa <- span("a") x$before(newa) expect_equal_tags( x$allTags(), tagList(newa, xTags) ) new1 <- div("new1") new2 <- div("new2") x$before(new1, new2) expect_equal_tags( x$allTags(), tagList(newa, new1, new2, xTags) ) }) test_that("tagQuery(x)$allTags()", { xTags <- tagList( fakeJqueryDep, div( fakeTagFunction ) ) x <- tagQuery(xTags) expect_equal_tags( x$allTags(), tagList(!!!xTags) ) }) test_that("tagQuery() objects inherit from each other objects", { xTags <- div(span("text")) x <- tagQuery(xTags)$find("span") y <- tagQuery(x) zEnv <- NULL wEnvs <- NULL x$each(function(el, i) { zEnv <<- el wEnvs <<- append(wEnvs, list(el)) }) z <- tagQuery(zEnv) w <- tagQuery(wEnvs) y$addClass("extra") expected <- div(span(class="extra", "text")) expect_equal_tags(x$selectedTags(), tagListPrintAsList(!!!expected$children)) expect_equal_tags(y$selectedTags(), tagListPrintAsList(!!!expected$children)) expect_equal_tags(z$selectedTags(), tagListPrintAsList(!!!expected$children)) expect_equal_tags(w$selectedTags(), tagListPrintAsList(!!!expected$children)) expect_equal_tags(x$allTags(), expected) expect_equal_tags(y$allTags(), expected) expect_equal_tags(z$allTags(), expected) expect_equal_tags(w$allTags(), expected) }) test_that("tagQuery() objects can not inherit from mixed objects", { xTags <- div(span("text"), span("extra")) x <- tagQuery(xTags)$find("span") y <- tagQuery(xTags)$find("span") xEnv <- NULL x$each(function(el, i) { xEnv <<- el }) yEnv <- NULL y$each(function(el, i) { yEnv <<- el }) expect_error( tagQuery(tagList( div(), xEnv )), "not be a mix" ) expect_error( tagQuery(tagList( xEnv, yEnv )), "share the same root" ) }) test_that("rebuilding tag envs after inserting children is done", { xTags <- div(div(), div()) expect_equal_tags( tagQuery(xTags)$find("div")$before(span())$allTags(), div(span(), div(), span(), div()) ) expect_equal_tags( tagQuery(xTags)$find("div")$replaceWith(span())$allTags(), div(span(), span()) ) expect_equal_tags( tagQuery(xTags)$find("div")$after(span())$allTags(), div(div(), span(), div(), span()) ) }) test_that("tagQuery() print method displays custom output for selected tags", { local_edition(3) expect_snapshot_output(print( tagQuery(div(span())) )) expect_snapshot_output(print( tagQuery(div(span()))$find("span") )) expect_snapshot_output(print( tagQuery(div(span()))$find("empty") )) }) test_that("tagQuery() allows for tags with extra top level items and will preserve them", { html <- div(span()) html$test <- "extra" html <- c(list(first = TRUE), html) class(html) <- "shiny.tag" # Test different removal types: setting the value to NULL and removing the value from the envir completely. for (removeType in c("set", "rm")) { expect_error( tagQuery(html)$each(function(el, i) { switch(removeType, set = { el$name <- NULL }, rm = { rm(list = "name", envir = el) } ) })$allTags(), "lost its `$name`", fixed = TRUE ) for (missing_key in c("__not_a_match__", "attribs", "children")) { htmlQ <- tagQuery(html) if (missing_key %in% names(html)) { htmlQ$each(function(el, i) { switch(removeType, set = { el[[missing_key]] <- NULL }, rm = { rm(list = missing_key, envir = el) } ) el[[missing_key]] <- NULL }) } htmlPostQ <- htmlQ$allTags() html_out <- html if (missing_key == "attribs") html_out$attribs <- dots_list() if (missing_key == "children") html_out$children <- list() # expect first three names to be standard tag names expect_equal(names(htmlPostQ)[1:3], names(div())) # expect all other names to be included somewhere expect_setequal(names(htmlPostQ), names(html_out)) # If done in the same order, it should be equal back_to_orig <- htmlPostQ[names(html_out)] class(back_to_orig) <- "shiny.tag" expect_equal(back_to_orig, html_out) } } }) test_that("tag methods do not unexpectedly alter tag envs", { expect_equal_tags( tagEnvToTags(tagAppendAttributes(asTagEnv(div()), key = "a")), tagAppendAttributes(div(), key = "a") ) expect_equal_tags( tagHasAttribute(asTagEnv(div(key = "a")), "key"), tagHasAttribute(div(key = "a"), "key") ) expect_equal_tags( tagGetAttribute(asTagEnv(div(key = "a")), "key"), tagGetAttribute(div(key = "a"), "key") ) expect_equal_tags( tagEnvToTags(tagAppendChild(asTagEnv(div()), span())), tagAppendChild(div(), span()) ) expect_equal_tags( tagEnvToTags(tagAppendChildren(asTagEnv(div()), span(), h1())), tagAppendChildren(div(), span(), h1()) ) expect_equal_tags( tagEnvToTags(tagSetChildren(asTagEnv(div()), span(), h1())), tagSetChildren(div(), span(), h1()) ) expect_equal_tags( tagEnvToTags(tagInsertChildren(asTagEnv(div()), span(), h1(), after = 12)), tagInsertChildren(div(), span(), h1(), after = 12) ) }) test_that("adding a class does not reorder attribs", { # No class expect_equal_tags( tagQuery(div(test = "A", "text"))$addClass("foo")$allTags(), div(test = "A", class = "foo", "text") ) # One class expect_equal_tags( tagQuery(div(class = "bar", test = "A", "text"))$addClass("foo")$allTags(), div(class="bar foo", test = "A", "text") ) # Multiple classes expect_equal_tags( tagQuery(div(class = "bar", test = "A", class = "baz", "text"))$addClass("foo")$allTags(), div(class = "bar baz foo", test = "A", "text") ) }) test_that("flattenTagsRaw() and flattenTags() do not drop html deps", { emptyDiv <- div() emptySpan <- span() testSpan <- span("test") otherObj <- HTML("test") fakeDep <- function(i) { ret <- fakeJqueryDep ret$i <- i ret } # `flattenTagsRaw()` moves html deps on tag lists to children htmlRaw <- tagList( emptyDiv, tagAppendChild(emptySpan, fakeDep(1)), tagAppendChild(testSpan, fakeDep(2)), otherObj, fakeDep(3) ) htmlDependencies(emptySpan) <- list(fakeDep(1)) htmlDependencies(testSpan) <- list(fakeDep(2)) html <- tagList( emptyDiv, emptySpan, testSpan, otherObj ) htmlDependencies(html) <- list(fakeDep(3)) expect_equal(flattenTags(html), html) expect_equal(flattenTagsRaw(html), htmlRaw) }) test_that("flattenTagsRaw(): tag list html deps are not lost when tag children are squashed", { # https://github.com/rstudio/htmltools/issues/301 a_dep <- htmlDependency(name = "A", version = 1, src = "a.js") b_dep <- htmlDependency(name = "B", version = 2, src = "b.js") c_dep <- htmlDependency(name = "C", version = 3, src = "c.js") d_dep <- htmlDependency(name = "D", version = 4, src = "d.js") z <- div("Z") z$children <- list(attachDependencies(list("z1"), d_dep)) children <- attachDependencies( list( attachDependencies(list("X", "Y"), a_dep), z ), list(b_dep, c_dep) ) html <- div("test", children) tq_html <- tagQuery(html)$allTags() tq_deps <- findDependencies(tq_html$children) expect_length(tq_deps, 4) expect_equal(tq_deps, list(a_dep, d_dep, b_dep, c_dep)) }) # htmltools/tests/testthat/test-colors.R0000644000176200001440000001034114600330155017676 0ustar liggesuserscontext("test-colors") colors_bad <- readLines(test_path("colors-bad.txt")) colors_good <- readLines(test_path("colors-good.txt")) # To update the test results: # writeLines(parseCssColors(readLines(test_path("colors-good.txt"))), test_path("colors-good-expected.txt")) colors_good_expected <- readLines(test_path("colors-good-expected.txt")) test_that("parseCssColors", { # Invalid colors for (color in colors_bad) { expect_error(parseCssColors(color)) } expect_true(all(is.na(parseCssColors(colors_bad, mustWork = FALSE)))) # Both valid and invalid expect_identical( parseCssColors(c("black", "hello"), mustWork = FALSE), c("#000000", NA) ) # Valid colors expect_identical(parseCssColors(colors_good), colors_good_expected) }) test_that("decode_hex", { expect_identical(decode_hex(sprintf("%02X", 0:255)), 0:255) expect_identical(decode_hex(sprintf("%02x", 0:255)), 0:255) expect_identical(decode_hex(sprintf("%1X", 0:15)), as.integer(0:15*16 + 0:15)) expect_identical(decode_hex(sprintf("%1x", 0:15)), as.integer(0:15*16 + 0:15)) expect_identical(decode_hex(character(0)), integer(0)) # Too many digits expect_error(decode_hex("000")) # Too few digits expect_error(decode_hex("")) # Not a valid number expect_error(decode_hex("z")) }) test_that("decode_float_255", { expect_identical(decode_float_255(character(0)), integer(0)) expect_identical(decode_float_255("-1"), 0L) expect_identical(decode_float_255("-0"), 0L) expect_identical(decode_float_255("-1.2"), 0L) expect_identical(decode_float_255("-.3"), 0L) expect_identical(decode_float_255("-.300"), 0L) expect_identical(decode_float_255("-300.300"), 0L) expect_identical(decode_float_255("125"), 125L) expect_identical(decode_float_255("125.5"), 126L) # Rounding goes to nearest even number. :shrug: expect_identical(decode_float_255("0.5"), 0L) expect_identical(decode_float_255(".5"), 0L) expect_identical(decode_float_255("1.5"), 2L) expect_identical(decode_float_255(300), 255L) expect_identical(decode_float_255(as.character(0:255)), 0:255) expect_identical(decode_float_255(as.character(0:255 + 0.2)), 0:255) expect_error(decode_float_255("-")) expect_error(decode_float_255("")) expect_error(decode_float_255("1.")) expect_error(decode_float_255(".")) expect_error(decode_float_255(" 1 ")) expect_error(decode_float_255(" ")) expect_error(decode_float_255("aa")) }) test_that("decode_float_1", { expect_identical( decode_float_1(as.character(0:1000/1000)), as.integer(round((0:1000/1000)*255)) ) # Without leading 0 expect_identical( decode_float_1( gsub("^0+\\.", ".", as.character(0:1000/1000)) ), as.integer(round((0:1000/1000)*255)) ) # Clamp expect_identical(decode_float_1(-1), 0L) expect_identical(decode_float_1(2), 255L) expect_identical(decode_float_1(character(0)), integer(0)) expect_error(decode_float_1("a")) expect_error(decode_float_1("")) expect_error(decode_float_1(" ")) expect_error(decode_float_1(" 0 ")) }) test_that("decode_float_identity", { rand <- runif(1000, min = -1000, max = 1000) rand <- c(rand, 0) expect_equal(decode_float_identity(as.character(rand)), rand) expect_identical(decode_float_identity(character(0)), numeric(0)) expect_error(decode_float_identity("0.")) expect_error(decode_float_identity(".")) expect_error(decode_float_identity("")) expect_error(decode_float_identity(NA)) }) test_that("decode_color_keyword", { expect_identical( encode_hex(decode_color_keyword(c("cornflowerblue", "orange"))), c("#6495ED", "#FFA500") ) expect_error(decode_color_keyword("")) expect_error(decode_color_keyword("notvalid")) expect_error(decode_color_keyword(" orange ")) expect_error(decode_color_keyword(NA)) }) test_that("parseCssColors() handles incoming NA values sensibly", { expect_error(parseCssColors(NA)) expect_identical( parseCssColors(NA, mustWork = FALSE), NA_character_ ) expect_identical( parseCssColors(rep(NA, 2), mustWork = FALSE), rep(NA_character_, 2) ) expect_identical( parseCssColors(c(NA, "red"), mustWork = FALSE), c(NA, "#FF0000") ) expect_identical( parseCssColors(c(NA, "blue", "notacolor"), mustWork = FALSE), c(NA, "#0000FF", NA) ) }) htmltools/tests/testthat/colors-good.txt0000644000176200001440000000075314600330155020273 0ustar liggesusers#AABBCC #0099AA #AAA #11223344 #ABCD rgb(100,100,100) rgb( 100 , 200 , 300 ) rgba(100,100,100) rgba( 100 , 200 , 300 ) rgb(100,100,100,0.5) rgb( 100 , 200 , 300 , 0.5 ) rgba(100,100,100,0.5) rgba( 100 , 200 , 300 , 0.5 ) hsl(0,30%,40%) hsl( 360 , 30% , 40% ) hsla(0,100%,100%) hsla( 360 , 100% , 100% ) hsl(-200.5,23.2%,70%,0.5) hsla(-200.5,23.2%,70%,0.5) hsl( -200.5 , 23.2% , 70% , 0.5) hsla( -200.5 , 23.2% , 70% , 0.5) white BLACK Orange tRaNsPaReNt fuchsia CornflowerBlue RebeccaPurple htmltools/tests/testthat/colors-good-expected.txt0000644000176200001440000000036614600330155022072 0ustar liggesusers#AABBCC #0099AA #AAAAAA #11223344 #AABBCCDD #646464 #64C8FF #646464 #64C8FF #64646480 #64C8FF80 #64646480 #64C8FF80 #854747 #854747 #FFFFFF #FFFFFF #A1C4B880 #A1C4B880 #A1C4B880 #A1C4B880 #FFFFFF #000000 #FFA500 #00000000 #FF00FF #6495ED #663399 htmltools/tests/testthat/colors-bad.txt0000644000176200001440000000015714600330155020067 0ustar liggesusersrgb(100, 100, 10 0) rgba (100, 100, 100, 0.5) hsl(50, 50 %, 50%) hsl(50, 50%, 50 %) #FFZZFF #FF FF FF # FFFFFF htmltools/tests/testthat/template-basic.html0000644000176200001440000000011414600330155021052 0ustar liggesusers {{ headContent() }} {{ body }} htmltools/tests/testthat/test-fill.R0000644000176200001440000000320314600330155017322 0ustar liggesusers# Some basic test coverage of asFillContainer() and asFillItem(). # Note that these expectations aren't as important as the e2e test coverage # we'll have via bslib::card(), shiny::plotOutput(), shiny::uiOutput() # (those will also be testing the client-side CSS) test_that("asFillContainer() and asFillItem()", { x <- bindFillRole(div(), container = TRUE) expect_true( doRenderTags(x) == "
" ) expect_equal(htmlDependencies(x), list(fillDependencies())) x <- bindFillRole(div(), item = TRUE) expect_true( doRenderTags(x) == "
" ) expect_equal(htmlDependencies(x), list(fillDependencies())) x <- bindFillRole(x, container = TRUE, overwrite = TRUE) expect_true( doRenderTags(x) == "
" ) x <- bindFillRole( div(span()), .cssSelector = "span", container = TRUE, item = TRUE ) expect_true( doRenderTags(x) == "
\n \n
" ) x <- bindFillRole(x, .cssSelector = "span", container = FALSE, item = FALSE, overwrite = TRUE) expect_true( doRenderTags(x) == "
\n \n
" ) x <- bindFillRole( tagList(div(span())), .cssSelector = "span", container = TRUE ) expect_true( doRenderTags(x) == "
\n \n
" ) expect_warning( bindFillRole(tagList()), "htmltools::tag" ) expect_warning( bindFillRole(tagList()), "htmltools::tag" ) expect_warning( bindFillRole(div(span()), .cssSelector = "foo"), "cssSelector" ) }) htmltools/tests/testthat/helper-tags.R0000644000176200001440000000136714600330155017643 0ustar liggesusers # Needed to compare tags that go from lists to envs and back to lists. expect_equal_tags <- function(x, y) { expect_equal_tags_ <- function(x, y) { if (isTag(x)) { expect_true(isTag(y)) expect_equal(x$parent, NULL) expect_equal(y$parent, NULL) expect_equal(x$envKey, NULL) expect_equal(y$envKey, NULL) # Recurse through children expect_equal_tags_(x$children, y$children) } else if (is.list(x)) { expect_true(is.list(y)) expect_equal(length(x), length(y)) Map(x, y, f = expect_equal_tags_) } else { # no tags to recurse } } # Should be fully equal. expect_equal(x, y) # Do custom checks to make sure tagQuery undid any internal changes expect_equal_tags_(x, y) } htmltools/tests/testthat/test-textwriter.r0000644000176200001440000000520714600330155020663 0ustar liggesuserscontext("textwriter") describe("WSTextWriter", { it("basically works", { wsw <- WSTextWriter() expect_identical(wsw$readAll(), "") wsw$write("") expect_identical(wsw$readAll(), "") wsw$write("line one") expect_identical(wsw$readAll(), "line one") wsw$write("\nanother line") expect_identical(wsw$readAll(), "line one\nanother line") wsw$write("more content") expect_identical(wsw$readAll(), "line one\nanother linemore content") # Non-character writes expect_error(wsw$write(1)) expect_error(wsw$write(letters[1:2])) expect_error(WSTextWriter(bufferSize=2)) }) it("eats past and future whitespace", { wtw <- WSTextWriter() expect_identical(wtw$readAll(), "") wtw$writeWS(" ") expect_identical(wtw$readAll(), " ") wtw$writeWS(" ") wtw$writeWS(" ") wtw$eatWS() expect_identical(wtw$readAll(), "") wtw$writeWS(" ") wtw$writeWS(" ") wtw$writeWS(" ") expect_identical(wtw$readAll(), "") wtw$write("Hello") expect_identical(wtw$readAll(), "Hello") wtw$writeWS(" ") expect_identical(wtw$readAll(), "Hello ") wtw$eatWS() expect_identical(wtw$readAll(), "Hello") wtw$writeWS(" ") expect_identical(wtw$readAll(), "Hello") }) it("handles full buffers of non-WS writes", { wtw <- WSTextWriter(bufferSize = 3) wtw$write("a") wtw$write("b") wtw$write("c") wtw$write("d") wtw$write("e") wtw$write("f") expect_identical(wtw$readAll(), "abcdef") wtw$eatWS() expect_identical(wtw$readAll(), "abcdef") wtw$write("g") wtw$writeWS(" ") expect_identical(wtw$readAll(), "abcdefg ") wtw$eatWS() expect_identical(wtw$readAll(), "abcdefg") }) it("handles full buffers of whitespace writeWS's", { wtw <- WSTextWriter(bufferSize = 3) # fill the buffer with whitespace that it will need to accumulate wtw$writeWS(" ") wtw$writeWS(" ") wtw$writeWS(" ") wtw$writeWS(" ") expect_identical(wtw$readAll(), " ") wtw$eatWS() expect_identical(wtw$readAll(), "") wtw$write("b") wtw$writeWS(" ") wtw$writeWS(" ") wtw$writeWS(" ") wtw$writeWS(" ") expect_identical(wtw$readAll(), "b ") wtw$eatWS() expect_identical(wtw$readAll(), "b") }) }) describe("validateNoWS",{ it("basically works", { validateNoWS(NULL) validateNoWS(noWSOptions[1]) validateNoWS(noWSOptions[1:2]) validateNoWS(noWSOptions) expect_error(validateNoWS("badOption")) expect_error(validateNoWS(c(noWSOptions, "badOption"))) # capitalization matters expect_error(validateNoWS(toupper(noWSOptions[1]))) }) }) htmltools/tests/testthat/_snaps/0000755000176200001440000000000014600330155016561 5ustar liggesusershtmltools/tests/testthat/_snaps/tags.md0000644000176200001440000000376314600330155020052 0ustar liggesusers# Hanging commas don't break things Code err_comma_multiple Output Error in `dots_list()`: ! Argument 2 can't be empty. --- Code err_comma_leading Output Error in `dots_list()`: ! Argument 1 can't be empty. # html render method Code as.character(obj) Output [1] "example" --- Code as.character(spanExtra) Output [1] "example" --- Code as.character(divExtra) Output [1] "
example
" --- Code as.character(spanExtended) Output [1] "\n example\n bold text\n" --- Code as.character(tagFuncExt) Output [1] "example\n

test

" --- Code renderTags(newDep) Output $head $singletons character(0) $dependencies $dependencies[[1]] List of 10 $ name : chr "jqueryui" $ version : chr "1.11.4" $ src :List of 1 ..$ href: chr "shared/jqueryui" $ meta : NULL $ script : chr "jquery-ui.min.js" $ stylesheet: NULL $ head : NULL $ attachment: NULL $ package : NULL $ all_files : logi TRUE - attr(*, "class")= chr "html_dependency" $dependencies[[2]] List of 10 $ name : chr "font-awesome" $ version : chr "4.5.0" $ src :List of 1 ..$ href: chr "shared/font-awesome" $ meta : NULL $ script : NULL $ stylesheet: chr "css/font-awesome.min.css" $ head : NULL $ attachment: NULL $ package : NULL $ all_files : logi TRUE - attr(*, "class")= chr "html_dependency" $html example --- Code as.character(newObj) Output [1] "

Something else

" htmltools/tests/testthat/_snaps/tag-query.md0000644000176200001440000000062614600330155021025 0ustar liggesusers# tagQuery() print method displays custom output for selected tags `$allTags()`:
`$selectedTags()`: `$allTags()` --- `$allTags()`:
`$selectedTags()`: [[1]] --- `$allTags()`:
`$selectedTags()`: (Empty selection) htmltools/tests/testthat/test-whitespace.r0000644000176200001440000000636014600330155020577 0ustar liggesuserscontext("whitespace") with(tags, { test_that("Whitespace directives basic tests", { # Default expect_identical( as.character( div( span( strong() ) ) ), paste(collapse = "\n", c( "
", " ", " ", " ", "
" )) ) expect_identical( as.character( div( span(.noWS = "before", strong() ) ) ), paste(collapse = "\n", c( "
", " ", " ", "
" )) ) expect_identical( as.character( div( span(.noWS = "after", strong() ) ) ), paste(collapse = "\n", c( "
", " ", " ", "
" )) ) expect_identical( as.character( div( span(.noWS =c("before", "after"), strong() ) ) ), paste(collapse = "\n", c( "
", " ", "
" )) ) expect_identical( as.character( div( span(.noWS = c("after-begin", "before-end"), strong() ) ) ), paste(collapse = "\n", c( "
", " ", "
" )) ) expect_identical( as.character( div(.noWS = c("after-begin", "before-end"), span(.noWS = "before", strong() ) ) ), paste(collapse = "\n", c( "
", " ", "
" )) ) expect_identical( as.character( div(.noWS = "inside", span( strong() ) ) ), paste(collapse = "\n", c( "
", " ", "
" )) ) expect_identical( as.character( div( span(.noWS = c("outside", "inside"), strong() ) ) ), paste(collapse = "\n", c( "
" )) ) expect_identical( as.character( div( HTML("one", .noWS = "before"), HTML("two") ) ), paste(collapse = "\n", c( "
one", " two", "
" )) ) expect_identical( as.character( div( HTML("one", .noWS = c("before", "after")), HTML("two") ) ), paste(collapse = "\n", c( "
onetwo", "
" )) ) expect_identical( as.character( div(.noWS = c("after-begin", "before-end"), HTML("one"), HTML("two") ) ), paste(collapse = "\n", c( "
one", " two
" )) ) expect_identical( as.character( div( HTML("one", .noWS = "outside"), HTML("two", .noWS = "outside"), ) ), paste(collapse = "\n", c( "
onetwo
" )) ) }) }) htmltools/tests/testthat/helper-locale.R0000644000176200001440000000032114600330155020131 0ustar liggesusersis_locale_available <- function(loc){ set_locale_failed <- FALSE tryCatch( withr::with_locale(c(LC_COLLATE=loc), {}), warning = function(e){ set_locale_failed <<- TRUE } ) !set_locale_failed } htmltools/tests/testthat/template-document.html0000644000176200001440000000030014600330155021604 0ustar liggesusers {{ suppressDependencies("jquery") }} {{ headContent() }}
{{ x }}
UTF-8 chars:Δ★😎 htmltools/tests/testthat/test-selector.R0000644000176200001440000001240514600330155020220 0ustar liggesusers makeTags <- function(text1, text2) { inputTags <- div( class = "outer", tagList( div( class = "inner", a(href="example.com", "`a` ", list(strong(text1), em(p(text2))), " text."), ), span( class = "sibling", "sibling text" ) ) ) inputTags <- span(list(inputTags, inputTags)) inputTags } test_that("error checks", { expect_error(asSelector("div, span"), "contain `,`") expect_error(asSelector("div[foo]"), "contain `[`", fixed = TRUE) expect_error(asSelector("div:text"), "Pseudo CSS selectors") }) test_that("selector parses string", { selector <- asSelector("h1#myId-value.class-name .child-class#child-id_value") expect_equal( format(selector), "h1#myId-value.class-name #child-id_value.child-class" ) }) test_that("selector parses valid names", { verbose <- FALSE wrapped_expect_equal <- function (object, expected, label) { if (verbose) message(label) expect_equal(object, expected, label = label) } valid_names <- list( single_character = "a", single_CHARACTER = "A", multiple_characters = "abc", multiple_cHaRaCtErS = "aBcD", just_an_underscore = "_", starts_with_an_underscore = "_abc", contains_underscore = "a_abc", contains_hyphen_and_underscore = "a_abc-def" ) # check elements parse for (key in names(valid_names)) { this_time <- valid_names[[key]] wrapped_expect_equal( format(asSelector(this_time)), this_time, label = paste0("checking element ", key, " with content ", this_time) ) } # check ids parse for (key in names(valid_names)) { this_time <- paste0("#", valid_names[[key]]) wrapped_expect_equal( format(asSelector(this_time)), this_time, label = paste0("checking id ", key, " with content ", this_time) ) } # check classes parse for (key in names(valid_names)) { this_time <- paste0(".", valid_names[[key]]) wrapped_expect_equal( format(asSelector(this_time)), this_time, label = paste0("checking class name ", key, " with content ", this_time) ) } # check compounds parse for (element_key in names(valid_names)) { for (id_key in names(valid_names)) { for (class_key_1 in names(valid_names)) { for (class_key_2 in names(valid_names)) { this_time <- paste0( valid_names[[element_key]], "#", valid_names[[id_key]], ".", valid_names[[class_key_1]], ".", valid_names[[class_key_2]] ) wrapped_expect_equal( format(asSelector(this_time)), this_time, label = paste0("checking compound element ", paste(element_key, id_key, class_key_1, class_key_2, sep="|"), " with content ", this_time) ) reordered_this_time <- paste0( valid_names[[element_key]], ".", valid_names[[class_key_1]], "#", valid_names[[id_key]], ".", valid_names[[class_key_2]] ) wrapped_expect_equal( format(asSelector(reordered_this_time)), this_time, label = paste0("checking reordered compound element ", paste(element_key, id_key, class_key_1, class_key_2, sep="|"), " with content ", reordered_this_time) ) } } } } }) test_that("* checks", { expect_equal(format(asSelector(" * ")), "*") expect_equal(format(asSelector(" *.class-name")), ".class-name") expect_s3_class(asSelector(" * "), selectorClass) }) test_that("> checks", { expect_error(asSelector("> div"), "first element") expect_error(asSelector("div >"), "last element") expect_error(asSelectorList("> div"), "first element") expect_error(asSelectorList("div >"), "last element") expect_equal(format(asSelector("div>span")), "div > span") expect_equal(format(asSelector("div>>span")), "div > * > span") }) # x <- div(class = "foo", "text 1", # div(class = "bar", "text 2"), # div(class = "bar", "text 3", # span("more text") # ) # ) # y <- x # x <- x[1:length(x)] # mutate_in_place(x) # x %>% find(".bar", function(item) { toupper(x$children); item }) # y <- x %>% el_find(".bar") %>% el_find(".x") %>% el_mutate(function(x) toupper(x$children)) # x %>% find(".bar") %>% addClass("abc") # x %>% find(".bar") %>% removeClass("abc") # x %>% find(".bar") %>% attr(`data-x` = 123) # x %>% find(".bar") %>% css(color = "red") # actionButton("x", "X") %>% removeClass("btn-default") %>% addClass("btn-primary") # x <- actionButton("x", "X") # x$attribs$class <- sub("btn-default", "btn-primary", x$attribs$class) # removeClass.shiny.tag <- function(x, class) { # ... # } # x <- div( # actionButton("x", "X") # ) # # Selection types: # # - tags # # - classes # # - ID # # - descendent ".btn .x" # # Future: # # - direct child ".btn > .x" # # # # Tag functions: # # - delay execution, wrap them somehow # # - need to know we have a way forward # x %>% el_select(".btn") %>% el_remove_class("btn-default") %>% el_add_class("btn-primary") # x %>% find(".btn") %>% { . %>% removeClass("btn") %>% addClass("btn-primary") } # x$select(".btn")$removeClass("btn-default")$addClass("btn-primary") # `$.shiny.tag` <- function(x, name, ...) { # el_fns[[name]](x, ...) # } htmltools/tests/test-all.R0000644000176200001440000000007614600330155015311 0ustar liggesuserslibrary(testthat) library(htmltools) test_check("htmltools") htmltools/MD50000644000176200001440000001152615114272561012620 0ustar liggesusersf2321bd1e8ea8a05f8783847a5399854 *DESCRIPTION 691f06b13a7452793fd9fc1581346b88 *NAMESPACE 4770c2d61d7dbc1816dbec77d725567d *NEWS.md 5f044fb2e9680b6c1625d182bdeda8d1 *R/colors.R 8aa66c36a0c428b1eaeaab7ae0238eb7 *R/fill.R 41d3d5c49e5ebc09d3696abdb53682ce *R/html_dependency.R 3264d8cfd71d9150f3b08b473a28847b *R/html_escape.R 99ad091935c8c7290163a264a7163f6c *R/html_print.R 2f07d7435562b3865ec234823b834bc7 *R/htmltools-package.R 3f94c4f4858f13cc7af9a59e758700e5 *R/images.R 0bddcd4bb9b19a209d1078b9ee7ef5b1 *R/known_tags.R f093a7c50e7ee932b50e9429549edf1e *R/selector.R 5b5faeac9c5718857c41c856f84b3609 *R/staticimports.R f313557f25e51070e8e1e2a7ca02637c *R/tag_query.R 2ae89508c5da854d891de5b882e96661 *R/tags.R 8754e24109c264ad919c72f969223085 *R/template.R 4d55ba4751614e631d3a0d5f54fb91e4 *R/utils.R 0c307b2ce7a3fa7f260a0451457c809f *inst/fill/fill.css 66c25a9fbb39e38f9d00f70e388b51ca *man/HTML.Rd 0f17d772c2a8b09646c35575510f58a9 *man/as.tags.Rd 88755a18c0ce2033778d9209cdfcd58c *man/bindFillRole.Rd ce6cec95e6f96f39a06724bb0c04c781 *man/browsable.Rd f4c60f8981bebc3eab34050a8f5a8d21 *man/builder.Rd 03c957ef3c77d9020c6badeb36267912 *man/capturePlot.Rd f8a468bd5662dae651fd5320a00ce0f2 *man/copyDependencyToDir.Rd 399fc5b9debe9b58ebd9e4d20767339e *man/css.Rd dc524c0301930e93a4efe84878ad37a7 *man/defaultPngDevice.Rd a1b8c987c676c16af790f563f96cbb1f *man/figures/lifecycle-deprecated.svg c3978703d8f40f2679795335715e98f4 *man/figures/lifecycle-experimental.svg 27b879bf3677ea76e3991d56ab324081 *man/figures/lifecycle-questioning.svg 1c1fe7a759b86dc6dbcbe7797ab8246c *man/figures/lifecycle-superseded.svg 3535309b2acb4eef2e33ee1d24d96081 *man/figures/logo.png 58529e9e64ce0f2e4582f758d2c2c525 *man/figures/plotly-taglist.png 1d408ec004842bf68e2014a0d2c9062e *man/findDependencies.Rd d9ebaf094bf5ac589977d39993ce5176 *man/htmlDependencies.Rd e97a1da2af5a9bf58a896fef0c4288b2 *man/htmlDependency.Rd 91c1824deb08f57376108765fd29dbaa *man/htmlEscape.Rd e4ef52ee52d417ed12e90a188d291ffc *man/htmlPreserve.Rd 1ed6ea95e97e8e11f1bf305c9df05921 *man/htmlTemplate.Rd 6af07c0c22f52e3290cfafcde67eb29d *man/html_print.Rd 8196cb6861fed087df5bf86f48b83acd *man/htmltools-package.Rd b20713b9e3b5208cf1df9db9561b740e *man/include.Rd d5fe567bcd73afd46d8a6871cd8fa995 *man/knitr_methods.Rd a10d13a4b646e7d0caaaa95a7954f344 *man/makeDependencyRelative.Rd 2d10f3203fc76c606392cc280124bf8c *man/parseCssColors.Rd 1614e94f6ef7ec32b51645faff9c2333 *man/plotTag.Rd 16159aa45a251fb364e5fd1fd144d1d7 *man/print.html.Rd 7a2cffca91021e5c404498652430fcf0 *man/renderDependencies.Rd f701da695ed57753e86a13a2e7b60d9c *man/renderDocument.Rd 3dde546fb9ab551279ba11af4b504a2f *man/renderTags.Rd b671fc7559090c18c3c077ae44695ad4 *man/resolveDependencies.Rd dd23ebdf78b207176c4bdf18673866a9 *man/save_html.Rd a8b4b505f02377daaccef0066f8e7051 *man/singleton.Rd 67133c0b221c410ce6fa114454b6b850 *man/singleton_tools.Rd 03558ea3689843ad60e5af74ce9713b8 *man/subtractDependencies.Rd 1e4baacda4939aa34b4e0cff3e570375 *man/suppressDependencies.Rd 4ce6f9bcee698e62ba04bd4d695b8903 *man/tagAddRenderHook.Rd b588bd0eb452a44e4d228fde1fe67990 *man/tagAppendAttributes.Rd b450d680e9744c44714f98b700245df0 *man/tagAppendChild.Rd a492dd899372845a6159d2cd215bdcfe *man/tagFunction.Rd 4c1b47a1b4ea9c41edd40197aee66af1 *man/tagList.Rd 5eea18a9ada5591aacac72fdf737e084 *man/tagQuery.Rd 537d844bb9ca6ee662850bc84becfb5f *man/urlEncodePath.Rd 91696588d899d600bea3b1f78a991915 *man/validateCssUnit.Rd ffda5c8f5a5c36c04d313bea6e6de0da *man/withTags.Rd 5b6c540dad8faa887f50925c167ce6e3 *src/init.c c5b39bfd45eb9e331566c62d6c8637fb *src/template.c d5386f261693f9f4a5dda7b6fe0aa9f0 *tests/test-all.R 81f65654f00a9808c699aa51b5c753d2 *tests/testthat/_snaps/tag-query.md bc4287ae148918c070619d1a786f66af *tests/testthat/_snaps/tags.md 037397d065ae81eade043f3da91b9abb *tests/testthat/colors-bad.txt 8986597ca4b30709e3cc989c02439d95 *tests/testthat/colors-good-expected.txt 7d0d443e1159f7efc66cadaf467f69e7 *tests/testthat/colors-good.txt fe57ef256876fb7bf2d9e0d293af40ee *tests/testthat/helper-locale.R 11075ccb13fe28458cf1d33923d058be *tests/testthat/helper-tags.R 4de059d582d96a7c86907beb670b819d *tests/testthat/template-basic.html ce9c101bbebef449d432567b9a29e9f9 *tests/testthat/template-document.html c6cc88d0969658803fe2b87090f46c9e *tests/testthat/test-colors.R 21f9cb771663db3b946d75ef7fd43c86 *tests/testthat/test-deps.r b0eaee0845742888bccb11f95cdf7275 *tests/testthat/test-fill.R 76e589e51de5fbb218c876eb67dcc6ef *tests/testthat/test-images.R 01002f5157da1cdc6708d84a1c14e3f7 *tests/testthat/test-print.R 00e215b63d00f05bc12717510fd3cfc2 *tests/testthat/test-selector.R c01cb9d5593ed09ecc56ec6a98ef7b9f *tests/testthat/test-tag-query.R 027ebcc332f905231a429b3154781f95 *tests/testthat/test-tags.r 236dc86c477edb3a95f882ced8d34447 *tests/testthat/test-template.R e60a118b02ff97b60c7f250b325052dc *tests/testthat/test-textwriter.r 1d7b7c72f3937e644bca05b5272f1b39 *tests/testthat/test-whitespace.r htmltools/R/0000755000176200001440000000000015113345760012505 5ustar liggesusershtmltools/R/tags.R0000644000176200001440000017044515113345760013601 0ustar liggesusers# Like base::paste, but converts all string args to UTF-8 first. paste8 <- function(..., sep = " ", collapse = NULL) { args <- c( lapply(list(...), enc2utf8), list( sep = if (is.null(sep)) sep else enc2utf8(sep), collapse = if (is.null(collapse)) collapse else enc2utf8(collapse) ) ) do.call(paste, args) } # A special case of paste8 that employs paste0. Avoids the overhead of lapply. concat8 <- function(...) { enc2utf8(paste0(...)) } # Reusable function for registering a set of methods with S3 manually. The # methods argument is a list of character vectors, each of which has the form # c(package, genname, class). registerMethods <- function(methods) { lapply(methods, function(method) { pkg <- method[[1]] generic <- method[[2]] class <- method[[3]] func <- get(paste(generic, class, sep=".")) if (pkg %in% loadedNamespaces()) { registerS3method(generic, class, func, envir = asNamespace(pkg)) } setHook( packageEvent(pkg, "onLoad"), function(...) { registerS3method(generic, class, func, envir = asNamespace(pkg)) } ) }) } .onLoad <- function(...) { # htmltools provides methods for knitr::knit_print, but knitr isn't a Depends or # Imports of htmltools, only an Enhances. Therefore, the NAMESPACE file has to # declare it as an export, not an S3method. That means that R will only know to # use our methods if htmltools is actually attached, i.e., you have to use # library(htmltools) in a knitr document or else you'll get escaped HTML in your # document. This code snippet manually registers our methods with S3 once both # htmltools and knitr are loaded. registerMethods(list( # c(package, genname, class) c("knitr", "knit_print", "html"), c("knitr", "knit_print", "shiny.tag"), c("knitr", "knit_print", "shiny.tag.list"), c("knitr", "knit_print", "html_dependency") )) } depListToNamedDepList <- function(dependencies) { if (inherits(dependencies, "html_dependency")) dependencies <- list(dependencies) if (is.null(names(dependencies))) { names(dependencies) <- sapply(dependencies, `[[`, "name") } return(dependencies) } #' Resolve a list of dependencies #' #' Given a list of dependencies, removes any redundant dependencies (based on #' name equality). If multiple versions of a dependency are found, the copy with #' the latest version number is used. #' #' @param dependencies A list of [htmlDependency()] objects. #' @param resolvePackageDir Whether to resolve the relative path to an absolute #' path via [system.file()] when the `package` attribute is #' present in a dependency object. #' @return dependencies A list of [htmlDependency()] objects with #' redundancies removed. #' #' @export resolveDependencies <- function(dependencies, resolvePackageDir = TRUE) { deps <- resolveFunctionalDependencies(dependencies) # Get names and numeric versions in vector/list form depnames <- vapply(deps, function(x) x$name, character(1)) depvers <- numeric_version(vapply(deps, function(x) x$version, character(1))) # Get latest version of each dependency. `unique` uses the first occurrence of # each dependency name, which is important for inter-dependent libraries. return(lapply(unique(depnames), function(depname) { # Sort by depname equality, then by version. Since na.last=NA, all elements # whose names do not match will not be included in the sorted vector. sorted <- order(ifelse(depnames == depname, TRUE, NA), depvers, na.last = NA, decreasing = TRUE) # The first element in the list is the one with the largest version. dep <- deps[[sorted[[1]]]] if (resolvePackageDir && !is.null(dep$package)) { dir <- dep$src$file if (!is.null(dir)) dep$src$file <- system_file(dir, package = dep$package) dep$package <- NULL } dep })) } # Remove `remove` from `dependencies` if the name matches. # dependencies is a named list of dependencies. # remove is a named list of dependencies that take priority. # If warnOnConflict, then warn when a dependency is being removed because of an # older version already being loaded. #' Subtract dependencies #' #' Remove a set of dependencies from another list of dependencies. The set of #' dependencies to remove can be expressed as either a character vector or a #' list; if the latter, a warning can be emitted if the version of the #' dependency being removed is later than the version of the dependency object #' that is causing the removal. #' #' @param dependencies A list of [htmlDependency()] objects from which #' dependencies should be removed. #' @param remove A list of [htmlDependency()] objects indicating which #' dependencies should be removed, or a character vector indicating dependency #' names. #' @param warnOnConflict If `TRUE`, a warning is emitted for each #' dependency that is removed if the corresponding dependency in `remove` #' has a lower version number. Has no effect if `remove` is provided as a #' character vector. #' #' @return A list of [htmlDependency()] objects that don't intersect #' with `remove`. #' #' @export subtractDependencies <- function(dependencies, remove, warnOnConflict = TRUE) { dependencies <- resolveFunctionalDependencies(dependencies) depnames <- vapply(dependencies, function(x) x$name, character(1)) if (is.character(remove)) { rmnames <- remove } else { remove <- resolveFunctionalDependencies(remove) rmnames <- vapply(remove, function(x) x$name, character(1)) } matches <- depnames %in% rmnames if (warnOnConflict && !is.character(remove)) { for (loser in dependencies[matches]) { winner <- remove[[head(rmnames == loser$name, 1)]] if (compareVersion(loser$version, winner$version) > 0) { warning(sprintf(paste("The dependency %s %s conflicts with", "version %s"), loser$name, loser$version, winner$version )) } } } # Return only deps that weren't in remove return(dependencies[!matches]) } # Given a vector or list, drop all the NULL items in it dropNulls <- function(x) { x[!vapply(x, is.null, FUN.VALUE=logical(1))] } nullOrEmpty <- function(x) { length(x) == 0 } # Given a vector or list, drop all the NULL or length-0 items in it dropNullsOrEmpty <- function(x) { x[!vapply(x, nullOrEmpty, FUN.VALUE=logical(1))] } isResolvedTag <- function(x) { inherits(x, "shiny.tag") && length(x$.renderHooks) == 0 } isTag <- function(x) { inherits(x, "shiny.tag") } #' @rdname print.html #' @export print.shiny.tag <- function(x, browse = is.browsable(x), ...) { if (browse) html_print(x) else print(HTML(as.character(x)), ...) invisible(x) } # indent can be numeric to indicate an initial indent level, # or FALSE to suppress #' @export format.shiny.tag <- function(x, ..., singletons = character(0), indent = 0) { as.character(renderTags(x, singletons = singletons, indent = indent)$html) } #' @export as.character.shiny.tag <- function(x, ...) { as.character(renderTags(x)$html) } #' @export as.character.html <- function(x, ...) { as.vector(enc2utf8(x)) } #' @export print.shiny.tag.list <- function(x, ...) { if (isTRUE(attr(x, "print.as.list", exact = TRUE))) { attr(x, "print.as.list") <- NULL class(x) <- setdiff(class(x), "shiny.tag.list") return(print(x)) } print.shiny.tag(x, ...) } #' @export format.shiny.tag.list <- format.shiny.tag #' @export as.character.shiny.tag.list <- as.character.shiny.tag #' Print method for HTML/tags #' #' S3 method for printing HTML that prints markup or renders HTML in a web #' browser. #' #' @param x The value to print. #' @param browse If `TRUE`, the HTML will be rendered and displayed in a #' browser (or possibly another HTML viewer supplied by the environment via #' the `viewer` option). If `FALSE` then the HTML object's markup #' will be rendered at the console. #' @param ... Additional arguments passed to print. #' #' @export print.html <- function(x, ..., browse = is.browsable(x)) { if (browse) html_print(x) else cat(x, "\n", sep = "") invisible(x) } #' @export format.html <- function(x, ...) { as.character(x) } normalizeText <- function(text) { if (!is.null(attr(text, "html", TRUE))) text else htmlEscape(text, attribute=FALSE) } #' Create a list of tags #' #' Create a `list()` of [tag]s with methods for [print()], [as.character()], #' etc. #' #' @param ... A collection of [tag]s. #' @export #' @examples #' tagList( #' h1("Title"), #' h2("Header text", style = "color: red;"), #' p("Text here") #' ) tagList <- function(...) { lst <- dots_list(...) class(lst) <- c("shiny.tag.list", "list") return(lst) } #' Tag function #' #' Create 'lazily' rendered HTML [tags] (and/or [htmlDependencies()]). #' #' When possible, use [`tagAddRenderHook()`] to provide both a tag #' structure and utilize a render function. #' #' @param func a function with no arguments that returns HTML tags and/or #' dependencies. #' @seealso [`tagAddRenderHook()`] #' @export #' @examples #' myDivDep <- tagFunction(function() { #' if (isTRUE(getOption("useDep", TRUE))) { #' htmlDependency( #' name = "lazy-dependency", #' version = "1.0", src = "" #' ) #' } #' }) #' myDiv <- attachDependencies(div(), myDivDep) #' renderTags(myDiv) #' withr::with_options(list(useDep = FALSE), renderTags(myDiv)) #' tagFunction <- function(func) { if (!is.function(func) || length(formals(func)) != 0) { stop("`func` must be a function with no arguments") } structure(func, class = "shiny.tag.function") } #' Modify a tag prior to rendering #' #' Adds a hook to call on a [tag()] object when it is is rendered as HTML (with, #' for example, [print()], [renderTags()], [as.tags()], etc). #' #' The primary motivation for [tagAddRenderHook()] is to create tags that can #' change their attributes (e.g., change CSS classes) depending upon the context #' in which they're rendered (e.g., use one set of CSS classes in one a page #' layout, but a different set in another page layout). In this situation, #' [tagAddRenderHook()] is preferable to [tagFunction()] since the latter is more a #' "black box" in the sense that you don't know anything about the tag structure #' until it's rendered. #' #' @param tag A [`tag()`] object. #' @param func A function (_hook_) to call when the `tag` is rendered. This function #' should have at least one argument (the `tag`) and return anything that can #' be converted into tags via [as.tags()]. #' @param replace If `TRUE`, the previous hooks will be removed. If `FALSE`, #' `func` is appended to the previous hooks. #' @return A [tag()] object with a `.renderHooks` field containing a list of functions #' (e.g. `func`). When the return value is _rendered_ (such as with [`as.tags()`]), #' these functions will be called just prior to writing the HTML. #' @export #' @seealso [tagFunction()] #' @examples #' # Have a place holder div and return a span instead #' obj <- div("example", .renderHook = function(x) { #' x$name <- "span" #' x #' }) #' obj$name # "div" #' print(obj) # Prints as a `span` #' #' # Add a class to the tag #' # Should print a `span` with class `"extra"` #' spanExtra <- tagAddRenderHook(obj, function(x) { #' tagAppendAttributes(x, class = "extra") #' }) #' spanExtra #' #' # Replace the previous render method #' # Should print a `div` with class `"extra"` #' divExtra <- tagAddRenderHook(obj, replace = TRUE, function(x) { #' tagAppendAttributes(x, class = "extra") #' }) #' divExtra #' #' # Add more child tags #' spanExtended <- tagAddRenderHook(obj, function(x) { #' tagAppendChildren(x, " ", tags$strong("bold text")) #' }) #' spanExtended #' #' # Add a new html dependency #' newDep <- tagAddRenderHook(obj, function(x) { #' fa <- htmlDependency( #' "font-awesome", "4.5.0", c(href="shared/font-awesome"), #' stylesheet = "css/font-awesome.min.css") #' attachDependencies(x, fa, append = TRUE) #' }) #' # Also add a jqueryui html dependency #' htmlDependencies(newDep) <- htmlDependency( #' "jqueryui", "1.11.4", c(href="shared/jqueryui"), #' script = "jquery-ui.min.js") #' # At render time, both dependencies will be found #' renderTags(newDep)$dependencies #' #' # Ignore the original tag and return something completely new. #' newObj <- tagAddRenderHook(obj, function(x) { #' tags$p("Something else") #' }) #' newObj tagAddRenderHook <- function(tag, func, replace = FALSE) { if (!is.function(func) || length(formals(func)) == 0) { stop("`func` must be a function that accepts at least 1 argument") } tag$.renderHooks <- if (isTRUE(replace)) { list(func) } else { append(tag$.renderHooks, list(func)) } tag } #' Append tag attributes #' #' Append (`tagAppendAttributes()`), check existence (`tagHasAttribute()`), #' and obtain the value (`tagGetAttribute()`) of HTML attribute(s). #' #' @export #' @param tag a [tag] object. #' @param ... Attributes to append as named argument-value pairs. A named #' argument with an `NA` value is rendered as a boolean attribute (see #' example). #' @param .cssSelector A character string containing a [CSS #' selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors) #' for targeting particular (inner) tags of interest. At the moment, only a #' combination of #' [type](https://www.w3.org/TR/CSS22/selector.html#type-selectors) (e.g, #' `div`), [class](https://www.w3.org/TR/CSS22/selector.html#class-html) #' (e.g., `.my-class`), #' [id](https://www.w3.org/TR/CSS22/selector.html#id-selectors) (e.g., #' `#myID`), and #' [universal](https://www.w3.org/TR/CSS22/selector.html#universal-selector) #' (`*`) selectors within a given [simple #' selector](https://www.w3.org/TR/CSS22/selector.html#selector-syntax) is #' supported. Note, if `.cssSelector` is used, the returned tags will have #' their `$children` fields flattened to a single `list()` via [`tagQuery()`]. #' @seealso [tagAppendChildren()], [tagQuery()] #' @examples #' html <- div(a()) #' tagAppendAttributes(html, class = "foo") #' tagAppendAttributes(html, .cssSelector = "a", class = "bar") #' tagAppendAttributes(html, contenteditable = NA) #' #' tagHasAttribute(div(foo = "bar"), "foo") #' tagGetAttribute(div(foo = "bar"), "foo") #' tagAppendAttributes <- function(tag, ..., .cssSelector = NULL) { throw_if_tag_function(tag) if (!is.null(.cssSelector)) { return( tagQuery(tag)$ find(.cssSelector)$ addAttrs(...)$ allTags() ) } newAttribs <- dropNullsOrEmpty(dots_list(...)) if (any(!nzchar(names2(newAttribs)))) { stop( "At least one of the new attribute values did not have a name.\n", "Did you forget to include an attribute name?" ) } tag$attribs <- c(tag$attribs, newAttribs) tag } #' @rdname tagAppendAttributes #' @param attr The name of an attribute. #' @export tagHasAttribute <- function(tag, attr) { throw_if_tag_function(tag) result <- attr %in% names(tag$attribs) result } #' @rdname tagAppendAttributes #' @export tagGetAttribute <- function(tag, attr) { throw_if_tag_function(tag) # Find out which positions in the attributes list correspond to the given attr attribs <- tag$attribs attrIdx <- which(attr == names(attribs)) if (length(attrIdx) == 0) { return (NULL) } result <- attribs[attrIdx] # Remove NA values or return a single NA value if (anyNA(result)) { na_idx <- is.na(result) if (all(na_idx)) { return(NA) } result <- result[!na_idx] } if (all(vapply(result, is.atomic, logical(1)))) { # Convert all attribs to chars explicitly; prevents us from messing up factors # Separate multiple attributes with the same name vals <- vapply(result, function(val) { val <- as.character(val) # Combine vector values if they exist if (length(val) > 1) { val <- paste0(val, collapse = " ") } val }, character(1)) result <- paste0(vals, collapse = " ") } else { # When retrieving values that are not atomic, return a list of values names(result) <- NULL } result } #' Modify tag contents #' #' Modify the contents (aka children) of a [tag] object. #' #' @inheritParams tagAppendAttributes #' @param child A child element to append to a parent tag. #' @export #' @seealso [tagAppendAttributes()], [tagQuery()] #' @examples #' html <- div(a(), h1()) #' tagAppendChild(html, span()) #' tagAppendChild(html, .cssSelector = "a", span()) #' #' tagAppendChildren(html, span(), p()) #' tagAppendChildren(html, .cssSelector = "a", span(), p()) #' #' tagSetChildren(html, span(), p()) #' #' tagInsertChildren(html, after = 1, span(), p()) #' tagAppendChild <- function(tag, child, .cssSelector = NULL) { throw_if_tag_function(tag) if (!is.null(.cssSelector)) { return( tagAppendChildren(tag, child, .cssSelector = .cssSelector) ) } tag$children[[length(tag$children)+1]] <- child tag } #' @rdname tagAppendChild #' @param ... a collection of `child` elements. #' @param list Deprecated. Use `!!!` instead to splice into `...`. #' @export tagAppendChildren <- function(tag, ..., .cssSelector = NULL, list = NULL) { throw_if_tag_function(tag) children <- unname(c(dots_list(...), list)) if (!is.null(.cssSelector)) { return( tagQuery(tag)$ find(.cssSelector)$ append(!!!children)$ allTags() ) } tag$children <- unname(c(tag$children, children)) tag } #' @rdname tagAppendChild #' @export tagSetChildren <- function(tag, ..., .cssSelector = NULL, list = NULL) { throw_if_tag_function(tag) children <- unname(c(dots_list(...), list)) if (!is.null(.cssSelector)) { return( tagQuery(tag)$ find(.cssSelector)$ empty()$ append(!!!children)$ allTags() ) } tag$children <- children tag } #' @rdname tagAppendChild #' @param after an integer value (i.e., subscript) referring to the child position to append after. #' @export tagInsertChildren <- function(tag, after, ..., .cssSelector = NULL, list = NULL) { throw_if_tag_function(tag) children <- unname(c(dots_list(...), list)) if (!is.null(.cssSelector)) { return( tagQuery(tag)$ find(.cssSelector)$ each(function(x, i) { tagInsertChildren(x, after = after, !!!children) })$ allTags() ) } tag$children <- unname(append(tag$children, children, after)) tag } throw_if_tag_function <- function(tag) { if (is_tag_function(tag)) stop("`tag` can not be a `tagFunction()`") } # Use `known_tags` from `known_tags.R` # Then remove `known_tags` once done creating tag functions #' @include known_tags.R names(known_tags) <- known_tags #' Create HTML tags #' #' Create an R object that represents an HTML tag. For convenience, common HTML #' tags (e.g., `
`) can be created by calling for their tag name directly #' (e.g., `div()`). To create less common HTML5 (or SVG) tags (e.g., #' `
`), use the `tags` list collection (e.g., `tags$article()`). To #' create other non HTML/SVG tags, use the lower-level `tag()` constructor. #' #' @name builder #' @param ... Tag attributes (named arguments) and children (unnamed arguments). #' A named argument with an `NA` value is rendered as a boolean attributes #' (see example). Children may include any combination of: #' * Other tags objects #' * [HTML()] strings #' * [htmlDependency()]s #' * Single-element atomic vectors #' * `list()`s containing any combination of the above #' @return A `list()` with a `shiny.tag` class that can be converted into an #' HTML string via `as.character()` and saved to a file with `save_html()`. #' @seealso [tagList()], [withTags()], [tagAppendAttributes()], [tagQuery()] #' @examples #' tags$html( #' tags$head( #' tags$title('My first page') #' ), #' tags$body( #' h1('My first heading'), #' p('My first paragraph, with some ', strong('bold'), ' text.'), #' div( #' id = 'myDiv', class = 'simpleDiv', #' 'Here is a div with some attributes.' #' ) #' ) #' ) #' #' # html5