diff --git a/DESCRIPTION b/DESCRIPTION index eb4f7b1e..02730005 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: htmltools Type: Package Title: Tools for HTML -Version: 0.5.1.9005 +Version: 0.5.1.9006 Authors@R: c( person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"), person("Carson", "Sievert", role = c("aut", "cre"), email = "carson@rstudio.com", comment = c(ORCID = "0000-0002-4958-2844")), @@ -20,7 +20,8 @@ Imports: grDevices, base64enc, rlang (>= 0.4.11.9000), - fastmap + fastmap, + withr Suggests: markdown, testthat, diff --git a/NAMESPACE b/NAMESPACE index 65f3724a..39d230d2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -90,6 +90,7 @@ export(subtractDependencies) export(suppressDependencies) export(surroundSingletons) export(tag) +export(tagAddPostRenderHook) export(tagAddRenderHook) export(tagAppendAttributes) export(tagAppendChild) diff --git a/R/tags.R b/R/tags.R index 3e448785..e21dc18b 100644 --- a/R/tags.R +++ b/R/tags.R @@ -175,10 +175,6 @@ 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") } @@ -266,6 +262,7 @@ normalizeText <- function(text) { #' etc. #' #' @param ... A collection of [tag]s. +#' @inheritParams tag #' @export #' @examples #' tagList( @@ -273,10 +270,15 @@ normalizeText <- function(text) { #' h2("Header text"), #' p("Text here") #' ) -tagList <- function(...) { +tagList <- function(..., .renderHook = NULL, .postRenderHook = NULL) { + lst <- dots_list(...) class(lst) <- c("shiny.tag.list", "list") - return(lst) + + lst <- tagAddHooks(lst, .renderHook, tagAddRenderHook) + lst <- tagAddHooks(lst, .postRenderHook, tagAddPostRenderHook) + + lst } #' Tag function @@ -310,30 +312,24 @@ tagFunction <- function(func) { structure(func, class = "shiny.tag.function") } -#' Modify a tag prior to rendering +#' Modify a tag during the render phase #' -#' Adds a hook to call on a [tag()] object when it is is rendered as HTML (with, -#' for example, [print()], [renderTags()], [as.tags()], etc). +#' Add hook(s) to modify [tag()] (or [tagList()]) object(s) during the render +#' phase (i.e., when [renderTags()] / [print()] / [as.character()] / etc. happens). #' -#' 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. +#' These hooks allow tags to change their attributes (e.g., change CSS classes) +#' and/or change their entire HTML structure, depending upon the context in +#' which they're rendered. For example, you may want to an HTML widget to emit +#' different HTML depending on what HTML dependencies are being included on the +#' page. #' -#' @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 tag A [tag()] or [tagList()]. +#' @param func A function (_hook_) to call when the `tag` is rendered. This +#' function should have at least one argument (the `tag`). #' @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. +#' @return A [tag()] object. #' @export -#' @seealso [tagFunction()] #' @examples #' # Have a place holder div and return a span instead #' obj <- div("example", .renderHook = function(x) { @@ -383,21 +379,32 @@ tagFunction <- function(func) { #' }) #' 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") - } + addRenderHook(tag, func, replace, post = FALSE) +} - tag$.renderHooks <- - if (isTRUE(replace)) { - list(func) - } else { - append(tag$.renderHooks, list(func)) - } +#' @export +#' @rdname tagAddRenderHook +tagAddPostRenderHook <- function(tag, func, replace = FALSE) { + addRenderHook(tag, func, replace, post = TRUE) +} +addRenderHook <- function(tag, func, replace, post = FALSE) { + # TODO: can postRender hooks have an arg? + #if (!is.function(func) || length(formals(func)) == 0) { + # stop("`func` must be a function that accepts at least 1 argument") + #} + if (!(isTag(tag) || isTagList(tag))) { + stop("Can't set a renderHook on non tag/tagList objects", call. = FALSE) + } + name <- if (isTRUE(post)) "postRenderHooks" else "renderHooks" + hooks <- list(func) + if (!isTRUE(replace)) { + hooks <- append(attr(tag, name), hooks) + } + attr(tag, name) <- hooks tag } - #' Append tag attributes #' #' Append (`tagAppendAttributes()`), check existence (`tagHasAttribute()`), @@ -652,11 +659,11 @@ NULL tags <- lapply(known_tags, function(tagname) { # Overwrite the body with the `tagname` value injected into the body new_function( - args = exprs(... = , .noWS = NULL, .renderHook = NULL), + args = exprs(... = , .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL), expr({ validateNoWS(.noWS) contents <- dots_list(...) - tag(!!tagname, contents, .noWS = .noWS, .renderHook = .renderHook) + tag(!!tagname, contents, .noWS = .noWS, .renderHook = .renderHook, .postRenderHook = .postRenderHook) }), env = asNamespace("htmltools") ) @@ -742,12 +749,17 @@ hr <- tags$hr #' normally be written around this tag. Valid options include `before`, #' `after`, `outside`, `after-begin`, and `before-end`. #' Any number of these options can be specified. -#' @param .renderHook A function (or list of functions) 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()]. Additional hooks may also be -#' added to a particular `tag` via [tagAddRenderHook()]. -#' @export -tag <- function(`_tag_name`, varArgs, .noWS = NULL, .renderHook = NULL) { +#' @param .renderHook A function (or list of functions) to call when the `tag` +#' is rendered. Each function should have at least one argument (the `tag`). +#' Additional hooks may also be added to a particular `tag` via +#' [tagAddRenderHook()] (see there for more details and examples). +#' @param .postRenderHook A function (or list of functions) to call after the +#' entire HTML tree has rendered. Each function should have at least one +#' argument (the `tag`). Additional hooks may also be added to a particular +#' `tag` via [tagAddPostRenderHook()] (see there for more details and +#' examples). +#' @export +tag <- function(`_tag_name`, varArgs, .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) { validateNoWS(.noWS) # Get arg names; if not a named list, use vector of empty strings varArgsNames <- names2(varArgs) @@ -765,22 +777,27 @@ tag <- function(`_tag_name`, varArgs, .noWS = NULL, .renderHook = NULL) { attribs = attribs, children = children) + class(st) <- "shiny.tag" + # Conditionally include the `.noWS` field. # We do this to avoid breaking the hashes of existing tags that weren't leveraging .noWS. if (!is.null(.noWS)) { st$.noWS <- .noWS } - # Conditionally include the `.renderHooks` field. - # We do this to avoid breaking the hashes of existing tags that weren't leveraging .renderHooks. - if (!is.null(.renderHook)) { - if (!is.list(.renderHook)) { - .renderHook <- list(.renderHook) - } - st$.renderHooks <- .renderHook - } - # Return tag data structure - structure(st, class = "shiny.tag") + st <- tagAddHooks(st, funcs = .renderHook, addFunc = tagAddRenderHook) + tagAddHooks(st, funcs = .postRenderHook, addFunc = tagAddPostRenderHook) +} + +tagAddHooks <- function(tag, funcs = NULL, addFunc = tagAddRenderHook) { + if (is.null(funcs)) return(tag) + if (is.function(funcs)) { + funcs <- list(funcs) + } + for (func in funcs) { + tag <- addFunc(tag, func) + } + tag } isTagList <- function(x) { @@ -1203,11 +1220,35 @@ withTags <- function(code, .noWS = NULL) { # Make sure any objects in the tree that can be converted to tags, have been tagify <- function(x) { - rewriteTags(x, function(uiObj) { - if (isResolvedTag(uiObj) || isTagList(uiObj) || is.character(uiObj)) - return(uiObj) - else - tagify(as.tags(uiObj)) + rewriteTags(x, function(ui) { + if (is.character(ui)) return(ui) + + pre <- attr(ui, "renderHooks") + post <- attr(ui, "postRenderHooks") + attr(ui, "renderHooks") <- NULL + attr(ui, "postRenderHooks") <- NULL + + for (hook in pre) { + ui <- tryCatch({ hook(ui) }, error = function(e) { + warning(conditionMessage(e), call. = FALSE) + ui + }) + } + + # Since tagify() is called recursively within this anonymous function (which + # is applied in a preorder=F fashion), I don't think we can simply schedule + # post hooks with an on.exit() since both tagify() and this anonymous + # function both exit before we've walked the entire tree. + if (length(post)) { + withr::defer( + for (hook in post) + tryCatch(hook(), error = function(e) warning(conditionMessage(e), call. = FALSE)), + envir = parent.frame(2L), + priority = "last" + ) + } + + if (isTag(ui) || isTagList(ui)) ui else tagify(as.tags(ui)) }, FALSE) } @@ -1324,17 +1365,7 @@ as.tags.html <- function(x, ...) { #' @export as.tags.shiny.tag <- function(x, ...) { - if (isResolvedTag(x)) { - return(x) - } - - hook <- x$.renderHooks[[1]] - # remove first hook - x$.renderHooks[[1]] <- NULL - # Recursively call as.tags on the updated object - # (Perform in two lines to avoid lazy arg evaluation issues) - y <- hook(x) - as.tags(y) + x } #' @export diff --git a/man/builder.Rd b/man/builder.Rd index fe0515fd..4fd48f58 100644 --- a/man/builder.Rd +++ b/man/builder.Rd @@ -25,41 +25,47 @@ \usage{ tags -p(..., .noWS = NULL, .renderHook = NULL) +p(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -h1(..., .noWS = NULL, .renderHook = NULL) +h1(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -h2(..., .noWS = NULL, .renderHook = NULL) +h2(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -h3(..., .noWS = NULL, .renderHook = NULL) +h3(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -h4(..., .noWS = NULL, .renderHook = NULL) +h4(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -h5(..., .noWS = NULL, .renderHook = NULL) +h5(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -h6(..., .noWS = NULL, .renderHook = NULL) +h6(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -a(..., .noWS = NULL, .renderHook = NULL) +a(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -br(..., .noWS = NULL, .renderHook = NULL) +br(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -div(..., .noWS = NULL, .renderHook = NULL) +div(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -span(..., .noWS = NULL, .renderHook = NULL) +span(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -pre(..., .noWS = NULL, .renderHook = NULL) +pre(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -code(..., .noWS = NULL, .renderHook = NULL) +code(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -img(..., .noWS = NULL, .renderHook = NULL) +img(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -strong(..., .noWS = NULL, .renderHook = NULL) +strong(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -em(..., .noWS = NULL, .renderHook = NULL) +em(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -hr(..., .noWS = NULL, .renderHook = NULL) +hr(..., .noWS = NULL, .renderHook = NULL, .postRenderHook = NULL) -tag(`_tag_name`, varArgs, .noWS = NULL, .renderHook = NULL) +tag( + `_tag_name`, + varArgs, + .noWS = NULL, + .renderHook = NULL, + .postRenderHook = NULL +) } \arguments{ \item{...}{Tag attributes (named arguments) and children (unnamed arguments). @@ -78,10 +84,16 @@ normally be written around this tag. Valid options include \code{before}, \code{after}, \code{outside}, \code{after-begin}, and \code{before-end}. Any number of these options can be specified.} -\item{.renderHook}{A function (or list of functions) to call when the \code{tag} is rendered. This -function should have at least one argument (the \code{tag}) and return anything -that can be converted into tags via \code{\link[=as.tags]{as.tags()}}. Additional hooks may also be -added to a particular \code{tag} via \code{\link[=tagAddRenderHook]{tagAddRenderHook()}}.} +\item{.renderHook}{A function (or list of functions) to call when the \code{tag} +is rendered. Each function should have at least one argument (the \code{tag}). +Additional hooks may also be added to a particular \code{tag} via +\code{\link[=tagAddRenderHook]{tagAddRenderHook()}} (see there for more details and examples).} + +\item{.postRenderHook}{A function (or list of functions) to call after the +entire HTML tree has rendered. Each function should have at least one +argument (the \code{tag}). Additional hooks may also be added to a particular +\code{tag} via \code{\link[=tagAddPostRenderHook]{tagAddPostRenderHook()}} (see there for more details and +examples).} \item{_tag_name}{A character string to use for the tag name.} diff --git a/man/tagAddRenderHook.Rd b/man/tagAddRenderHook.Rd index baeb3647..d37518ff 100644 --- a/man/tagAddRenderHook.Rd +++ b/man/tagAddRenderHook.Rd @@ -2,37 +2,35 @@ % Please edit documentation in R/tags.R \name{tagAddRenderHook} \alias{tagAddRenderHook} -\title{Modify a tag prior to rendering} +\alias{tagAddPostRenderHook} +\title{Modify a tag during the render phase} \usage{ tagAddRenderHook(tag, func, replace = FALSE) + +tagAddPostRenderHook(tag, func, replace = FALSE) } \arguments{ -\item{tag}{A \code{\link[=tag]{tag()}} object.} +\item{tag}{A \code{\link[=tag]{tag()}} or \code{\link[=tagList]{tagList()}}.} -\item{func}{A function (\emph{hook}) to call when the \code{tag} is rendered. This function -should have at least one argument (the \code{tag}) and return anything that can -be converted into tags via \code{\link[=as.tags]{as.tags()}}.} +\item{func}{A function (\emph{hook}) to call when the \code{tag} is rendered. This +function should have at least one argument (the \code{tag}).} \item{replace}{If \code{TRUE}, the previous hooks will be removed. If \code{FALSE}, \code{func} is appended to the previous hooks.} } \value{ -A \code{\link[=tag]{tag()}} object with a \code{.renderHooks} field containing a list of functions -(e.g. \code{func}). When the return value is \emph{rendered} (such as with \code{\link[=as.tags]{as.tags()}}), -these functions will be called just prior to writing the HTML. +A \code{\link[=tag]{tag()}} object. } \description{ -Adds a hook to call on a \code{\link[=tag]{tag()}} object when it is is rendered as HTML (with, -for example, \code{\link[=print]{print()}}, \code{\link[=renderTags]{renderTags()}}, \code{\link[=as.tags]{as.tags()}}, etc). +Add hook(s) to modify \code{\link[=tag]{tag()}} (or \code{\link[=tagList]{tagList()}}) object(s) during the render +phase (i.e., when \code{\link[=renderTags]{renderTags()}} / \code{\link[=print]{print()}} / \code{\link[=as.character]{as.character()}} / etc. happens). } \details{ -The primary motivation for \code{\link[=tagAddRenderHook]{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, -\code{\link[=tagAddRenderHook]{tagAddRenderHook()}} is preferable to \code{\link[=tagFunction]{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. +These hooks allow tags to change their attributes (e.g., change CSS classes) +and/or change their entire HTML structure, depending upon the context in +which they're rendered. For example, you may want to an HTML widget to emit +different HTML depending on what HTML dependencies are being included on the +page. } \examples{ # Have a place holder div and return a span instead @@ -83,6 +81,3 @@ newObj <- tagAddRenderHook(obj, function(x) { }) newObj } -\seealso{ -\code{\link[=tagFunction]{tagFunction()}} -} diff --git a/man/tagList.Rd b/man/tagList.Rd index 71e4ed69..968b4ca0 100644 --- a/man/tagList.Rd +++ b/man/tagList.Rd @@ -4,10 +4,21 @@ \alias{tagList} \title{Create a list of tags} \usage{ -tagList(...) +tagList(..., .renderHook = NULL, .postRenderHook = NULL) } \arguments{ \item{...}{A collection of \link{tag}s.} + +\item{.renderHook}{A function (or list of functions) to call when the \code{tag} +is rendered. Each function should have at least one argument (the \code{tag}). +Additional hooks may also be added to a particular \code{tag} via +\code{\link[=tagAddRenderHook]{tagAddRenderHook()}} (see there for more details and examples).} + +\item{.postRenderHook}{A function (or list of functions) to call after the +entire HTML tree has rendered. Each function should have at least one +argument (the \code{tag}). Additional hooks may also be added to a particular +\code{tag} via \code{\link[=tagAddPostRenderHook]{tagAddPostRenderHook()}} (see there for more details and +examples).} } \description{ Create a \code{list()} of \link{tag}s with methods for \code{\link[=print]{print()}}, \code{\link[=as.character]{as.character()}}, diff --git a/tests/testthat/_snaps/tag-hooks.md b/tests/testthat/_snaps/tag-hooks.md new file mode 100644 index 00000000..b6150d9f --- /dev/null +++ b/tests/testthat/_snaps/tag-hooks.md @@ -0,0 +1,39 @@ +# render hooks can be used to + + Code + bar_widget + Output +
+ +--- + + Code + renderTags(html) + Output + $head + + + $singletons + character(0) + + $dependencies + $dependencies[[1]] + List of 10 + $ name : chr "bar" + $ version : chr "1.0" + $ src :List of 1 + ..$ file: chr "" + $ meta : NULL + $ script : NULL + $ stylesheet: NULL + $ head : NULL + $ attachment: NULL + $ package : NULL + $ all_files : logi TRUE + - attr(*, "class")= chr "html_dependency" + + + $html + + + diff --git a/tests/testthat/_snaps/tags.md b/tests/testthat/_snaps/tags.md index 9908eb21..e672c962 100644 --- a/tests/testthat/_snaps/tags.md +++ b/tests/testthat/_snaps/tags.md @@ -26,13 +26,6 @@ Output [1] "\n example\n bold text\n" ---- - - Code - as.character(tagFuncExt) - Output - [1] "example\ntest
" - --- Code diff --git a/tests/testthat/test-tag-hooks.R b/tests/testthat/test-tag-hooks.R new file mode 100644 index 00000000..d21527af --- /dev/null +++ b/tests/testthat/test-tag-hooks.R @@ -0,0 +1,198 @@ +# expect_tag_hooks <- function(tagFunc, ..., .render = NULL, .postRender = NULL) { +# x <- tagFunc(..., .renderHook = .render, .postRenderHook = .postRender) +# +# y <- tagFunc(...) +# y <- tagAddHooks(y, .render, tagAddRenderHook) +# y <- tagAddHooks(y, .postRender, tagAddPostRenderHook) +# +# expect_same_html(x, y) +# } +# +# expect_same_html <- function(x, y, equal = TRUE) { +# local_edition(3) +# if (equal) expect_equal(x, y) +# expect_snapshot( +# renderTags(x)[c("dependencies", "html")], +# cran = TRUE +# ) +# } +# +# test_that("tag(.renderHook, .postRenderHook) basics", { +# expect_tag_hooks(div, .render = span) +# expect_tag_hooks(div, .postRender = span) +# expect_tag_hooks(div, .render = span, .postRender = span) +# expect_tag_hooks( +# div, .render = function(x) stop("boom"), +# .postRender = function(x) stop("boom2") +# ) +# +# # Adding accumulates by default +# expect_tag_hooks(div, .render = list(span, span)) +# expect_tag_hooks(div, .postRender = list(span, span)) +# expect_tag_hooks(div, .render = list(span, span), .postRender = list(span, span)) +# +# # But can be also be replaced +# expect_same_html( +# tagAddRenderHook( +# tagAddRenderHook(div(), span), +# h1, replace = TRUE +# ), +# div(.renderHook = h1) +# ) +# expect_same_html( +# tagAddPostRenderHook( +# tagAddPostRenderHook(div(), span), +# h1, replace = TRUE +# ), +# div(.postRenderHook = h1) +# ) +# }) +# +# +# test_that("tagList(.renderHook, .postRenderHook) basics", { +# expect_tag_hooks(tagList, "a", .render = div) +# expect_tag_hooks(tagList, "a", .postRender = div) +# expect_tag_hooks(tagList, "a", .render = span, .postRender = span) +# expect_tag_hooks(tagList, "a", .render = list(span, span)) +# expect_tag_hooks(tagList, "a", .postRender = list(span, span)) +# expect_tag_hooks(tagList, "a", .postRender = list(span, span)) +# }) +# +# +# test_that("Can return various types of output in render hooks", { +# +# # Strings +# hook <- function(x) "foo" +# +# expect_tag_hooks(div, .render = hook) +# expect_tag_hooks(div, .postRender = hook) +# expect_tag_hooks(tagList, .render = hook) +# expect_tag_hooks(tagList, .postRender = hook) +# +# # HTML dependencies +# hook <- function(x) { +# attachDependencies(x, htmlDependency("foo", "1.0", "")) +# } +# +# expect_tag_hooks(div, .render = hook) +# expect_tag_hooks(div, .postRender = hook) +# expect_tag_hooks(tagList, .render = hook) +# expect_tag_hooks(tagList, .postRender = hook) +# +# # Unresolved tags +# hook <- function(x) span(x, .renderHook = h1) +# +# expect_tag_hooks(div, .render = hook) +# expect_tag_hooks(div, .postRender = hook) +# expect_tag_hooks(tagList, .render = hook) +# expect_tag_hooks(tagList, .postRender = hook) +# +# # Unresolved tagList()s +# hook <- function(x) tagList(span(), x, .renderHook = h1) +# +# expect_tag_hooks(div, .render = hook) +# expect_tag_hooks(div, .postRender = hook) +# expect_tag_hooks(tagList, .render = hook) +# expect_tag_hooks(tagList, .postRender = hook) +# +# # List of unresolved tags +# hook <- function(x) list(span(x, .renderHook = h1), span(x, .renderHook = h1)) +# +# expect_tag_hooks(div, .render = hook) +# expect_tag_hooks(div, .postRender = hook) +# expect_tag_hooks(tagList, .render = hook) +# expect_tag_hooks(tagList, .postRender = hook) +# +# # Nothing +# hook <- function(x) NULL +# +# expect_tag_hooks(div, .render = hook) +# expect_tag_hooks(div, .postRender = hook) +# expect_tag_hooks(tagList, .render = hook) +# expect_tag_hooks(tagList, .postRender = hook) +# }) + +test_that("Pre hooks render in order", { + # Note that, unlike tagFunction(), .renderHook's order of execution + # doesn't follow DOM tree order (preorder, depth-first traversal), + # but that seems like a feature, not a bug, since if you need to control + # state of html, you can do tagList(myTag, html) for "guaranteed control" + # over the state + state <- 0 + renderTags(tagList( + div( + .renderHook = function(x) { + expect_equal(state, 0) + state <<- state + 1 + }, + .postRenderHook = function() { + expect_equal(state, 2) + state <<- state + 1 + } + ), + div( + .renderHook = function(x) { + expect_equal(state, 1) + state <<- state + 1 + }, + .postRenderHook = function() { + expect_equal(state, 3) + state <<- state + 1 + } + ) + )) + expect_equal(state, 4) + + # post render hook still executes on failure + state <- 0 + expect_warning(renderTags(tagList( + div( + .renderHook = function(x) { + state <<- state + 1 + stop("boom") + }, + .postRenderHook = function() { + expect_equal(state, 2) + } + ), + div( + .renderHook = function(x) { + state <<- state + 1 + stop("boom") + }, + .postRenderHook = function() { + expect_equal(state, 2) + } + ) + ))) + expect_equal(state, 2) +}) + +test_that("render hooks can be used to ", { + local_edition(3) + + bar_widget <- div( + .renderHook = function(x) { + if (isTRUE(getOption("bar"))) tagQuery(x)$addClass("bar")$allTags() else x + } + ) + + expect_snapshot(bar_widget, cran = TRUE) + + bar_framework <- tagList( + htmlDependency("bar", "1.0", ""), + .renderHook = function(x) { + options("bar" = TRUE) + x + }, + .postRenderHook = function() { + options("bar" = NULL) + } + ) + + html <- tagList(bar_framework, bar_widget) + + expect_null(getOption("bar")) + expect_snapshot(renderTags(html), cran = TRUE) + expect_null(getOption("bar")) +}) diff --git a/tests/testthat/test-tags.r b/tests/testthat/test-tags.r index 9c3283d4..d6d80b83 100644 --- a/tests/testthat/test-tags.r +++ b/tests/testthat/test-tags.r @@ -1014,13 +1014,6 @@ test_that("html render method", { 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(