diff --git a/DESCRIPTION b/DESCRIPTION
index 3b87379..bd77f56 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,7 +1,7 @@
Package: lt
Type: Package
Title: Lightweight Tables via JSON Specs and JavaScript
-Version: 0.1.4
+Version: 0.1.5
Authors@R: person('Yihui', 'Xie', email = 'xie@yihui.name', role = c('aut', 'cre', 'cph'),
comment = c(ORCID = '0000-0003-0645-5666', URL = 'https://yihui.org'))
Description: A lightweight grammar of tables. Build a table by declaring a JSON
@@ -20,6 +20,7 @@ Suggests:
htmltools,
knitr,
litedown,
+ magick,
repr,
shiny,
testit
diff --git a/NAMESPACE b/NAMESPACE
index 6c0e547..e0e70b5 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -8,11 +8,11 @@ export(lt)
export(lt_align)
export(lt_css)
export(lt_date)
+export(lt_export)
export(lt_footnote)
export(lt_format)
export(lt_group)
export(lt_header)
-export(lt_html)
export(lt_indent)
export(lt_label)
export(lt_merge)
diff --git a/NEWS.md b/NEWS.md
index f53ca74..0d3e8d5 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,8 +1,6 @@
# CHANGES IN lt VERSION 0.2
-- Added `lt_html()` to render an lt table to a static HTML `
` via
- Node.js or a headless browser. This is useful for output formats that support
- raw HTML but cannot run JavaScript (e.g., GFM).
+- Added `lt_export()` to save an lt table to a file: `.html` (an HTML table, optionally baked to a static `` via Node.js or a headless browser so it needs no JavaScript to view), `.pdf` (a vector PDF), or `.png` (a raster image). PDF and PNG are rendered in a headless Chromium browser and cropped tightly to the table by default.
# CHANGES IN lt VERSION 0.1
diff --git a/R/render.R b/R/render.R
index 4dd1c14..8e8b67f 100644
--- a/R/render.R
+++ b/R/render.R
@@ -131,7 +131,7 @@ format.lt_tbl = function(x, fragment = TRUE, inline_assets = TRUE, assets = TRUE
if ('js' %in% assets) js_block(inline_assets)
)
if (!fragment) body = html_doc(body)
- paste(body, collapse = '\n')
+ xfun::raw_string(paste(body, collapse = '\n'))
}
#' Print an `lt_tbl` (Opens in the Viewer or Browser)
@@ -208,35 +208,21 @@ register_s3 = function(pkgs, generics) {
}
-#' Render an lt table to a static HTML table
-#'
-#' Execute the JavaScript runtime to produce a rendered `` element. Two
-#' methods are available: `"node"` uses Node.js; `"browser"` uses a headless
-#' Chromium browser (via [xfun::browser_dom()]). The default `"auto"` tries
-#' Node first (faster), then falls back to the browser.
-#'
-#' @param x An `lt_tbl` object.
-#' @param method One of `"auto"`, `"browser"`, or `"node"`.
-#' @param css Whether to include the lt.css runtime stylesheet in the output.
-#' User CSS from [lt_css()] is always included.
-#' @param fragment If `FALSE` (default), wrap in a full HTML document. If
-#' `TRUE`, return only the rendered fragment.
-#' @return A character vector of the rendered HTML.
-#' @section Global option:
-#' When the option `lt.lt_html` is set to a list of arguments (e.g.,
-#' `options(lt.lt_html = list(fragment = TRUE, css = FALSE))`), the
-#' [knit_print][knitr::knit_print] and [record_print][xfun::record_print]
-#' methods will call `lt_html()` with these arguments instead of emitting the
-#' default JavaScript-based spec. This is useful for output formats that
-#' support raw HTML but cannot run JavaScript (e.g., GitHub Flavored
-#' Markdown).
-#' @export
-#' @examples
-#' if (interactive()) lt_html(lt(head(mtcars)))
+# Build the HTML for lt_export()'s .html output. `method`:
+# "raw" -> the JavaScript-spec HTML (the table is built client-side by
+# lt.js at view time); no external tool runs.
+# "node" -> run lt.js in Node.js to bake a static .
+# "browser" -> run lt.js in a headless Chromium browser (via browser_dom).
+# "auto" -> node if available, else browser.
+# `css` includes the lt.css runtime stylesheet (user CSS from lt_css() always
+# is); `fragment = FALSE` wraps the result in a full HTML document.
lt_html = function(
- x, method = c('auto', 'node', 'browser'), css = TRUE, fragment = FALSE
+ x, method = c('auto', 'node', 'browser', 'raw'), css = TRUE, fragment = FALSE
) {
method = match.arg(method)
+ if (method == 'raw') return(format(
+ x, fragment = fragment, assets = c(if (css) 'css', 'js')
+ ))
if (method == 'auto') method = if (has_node()) 'node' else if (has_browser()) 'browser'
if (is.null(method)) stop(
'No rendering method available. Install a Chromium-based browser or Node.js.'
@@ -265,6 +251,187 @@ lt_html_node = function(x, css = TRUE) {
c(if (css) css_block(TRUE), user_css_block(x$css), rules_block(x$rules), out)
}
+# Write `html` to a temp file, run it through headless Chromium via
+# `fun`, and clean up. Shared by the measure pass (browser_dom) and the
+# render pass (browser_print).
+with_temp_html = function(html, fun) {
+ f = tempfile(fileext = '.html')
+ on.exit(unlink(f), add = TRUE)
+ xfun::write_utf8(html, f)
+ fun(f)
+}
+
+# Layout CSS shared by the measure and render passes: zero the page margins
+# so the table sits flush at the top-left, add the crop padding, and set the
+# body width. Both passes MUST use identical layout so the size measured in
+# pass 1 matches what pass 2 renders. `pad` is c(vertical, horizontal) in CSS
+# pixels; `width` is the outer body width in pixels (NULL = shrink to the
+# table's natural width). box-sizing keeps `padding` inside `width`.
+crop_layout = function(pad, width = NULL) sprintf(paste0(
+ 'html,body{margin:0!important}',
+ 'body{box-sizing:border-box;padding:%dpx %dpx!important;width:%s}'
+), pad[1L], pad[2L], if (is.null(width)) 'max-content' else paste0(width, 'px'))
+
+# Measure the rendered table's full pixel size (including the crop padding).
+# Chromium runs lt.js, so the box is only known after the JS builds the
+# table; inject a load handler that stamps body.scrollWidth/scrollHeight onto
+# as data attributes, dump the DOM, and parse them back. We measure
+# the body's scroll size rather than the table's bounding box because parts
+# of the table (caption border, footer spacing) extend beyond the table's
+# own border-box; using the table box alone undercounts the height and makes
+# the PDF spill onto a second page. Returns integer c(width, height).
+lt_measure = function(html, pad, width = NULL, browser = NULL) {
+ inject = paste0(
+ '',
+ ''
+ )
+ html = sub('', paste0(inject, ''), html, fixed = TRUE)
+ dom = with_temp_html(html, function(f) xfun::browser_dom(f, browser = browser))
+ m = regmatches(dom, regexec('data-ltw="([0-9]+)" data-lth="([0-9]+)"', dom))[[1]]
+ if (length(m) != 3L) stop('Failed to measure the table dimensions.')
+ as.integer(m[-1L])
+}
+
+#' Export an lt table to a file
+#'
+#' Save a table to disk. The output format is chosen from the file extension
+#' of `output`: `.html` writes an HTML table, `.pdf` writes a vector PDF, and
+#' any other extension writes a PNG. PDF and PNG are produced by rendering the
+#' table in a headless Chromium browser (via [xfun::browser_print()]).
+#'
+#' For `.html` output, `method` controls how the `` is produced:
+#' `"raw"` writes the JavaScript-spec HTML, so the table is built in the
+#' browser by the lt.js runtime when the file is viewed; the other methods
+#' bake a static `` up front by running lt.js once (via `"node"` in
+#' Node.js or `"browser"` in a headless Chromium browser; `"auto"` picks Node
+#' if available, else the browser), so the saved file needs no JavaScript to
+#' view. `method`, `css`, and `fragment` apply only to `.html` output.
+#'
+#' @param x An `lt_tbl` object.
+#' @param output Output file path. Its extension selects the format: `.html`,
+#' `.pdf`, or (otherwise) PNG. If `NA`, the HTML is returned as a string
+#' instead of being written to a file.
+#' @param method How to produce the HTML ``: `"auto"`, `"node"`,
+#' `"browser"`, or `"raw"` (see Details). Applies only to `.html` output.
+#' @param css Whether to include the lt.css runtime stylesheet in the HTML
+#' output. User CSS from [lt_css()] is always included. Applies only to
+#' `.html` output.
+#' @param fragment If `FALSE` (default), wrap the HTML in a full HTML document;
+#' if `TRUE`, return only the table fragment. Applies only to `.html`
+#' output.
+#' @param crop Whether to crop the PDF/PNG tightly to the table, removing the
+#' surrounding page whitespace. This adds a preliminary browser pass to
+#' measure the rendered table. Set to `FALSE` for the default full page.
+#' Cropping PNG output requires the \pkg{magick} package; without it, PNG
+#' falls back to the full page (with a warning).
+#' @param width The width of the table in CSS pixels. By default (`NULL`) it
+#' shrinks to the table's natural width. A smaller `width` wraps cell
+#' content; a larger one pads the table.
+#' @param padding Padding in CSS pixels to keep around the table when
+#' cropping. A single value (all sides) or a length-two vector
+#' `c(vertical, horizontal)`.
+#' @param browser Path to the Chromium-based browser; passed to
+#' [xfun::browser_print()]. `NULL` (default) auto-detects.
+#' @param ... Passed to [xfun::browser_print()] for PDF/PNG output.
+#' @return The `output` path, or (when `output` is `NA`) the HTML as a string.
+#' @section Global option:
+#' When the option `lt.lt_html` is set to a list of arguments (e.g.,
+#' `options(lt.lt_html = list(css = FALSE))`), the
+#' [knit_print][knitr::knit_print] and [record_print][xfun::record_print]
+#' methods emit the same static HTML table as `lt_export(x, "*.html")` (using
+#' those arguments as `method`/`css`/`fragment`) instead of the default
+#' JavaScript-based spec. This is useful for output formats that support raw
+#' HTML but cannot run JavaScript (e.g., GitHub Flavored Markdown).
+#' @export
+#' @examples
+#' tbl = lt(head(mtcars))
+#'
+#' # HTML with the JavaScript spec (table built by lt.js when viewed)
+#' lt_export(tbl, NA, method = 'raw') # character output
+#' f1 = tempfile(fileext = '.html')
+#' lt_export(tbl, f1, method = 'raw') # file output
+#'
+#' # Bake a static (needs Node.js or a headless browser).
+#' if (lt:::has_node() || lt:::has_browser())
+#' lt_export(tbl, NA, method = 'auto', fragment = TRUE, css = FALSE)
+#'
+#' # PDF / PNG are rendered in a headless browser and cropped to the table.
+#' f2 = tempfile(fileext = '.pdf')
+#' f3 = tempfile(fileext = '.png')
+#' if (lt:::has_browser()) {
+#' lt_export(tbl, f2)
+#' lt_export(tbl, f3, width = 400)
+#' }
+#'
+#' unlink(c(f1, f2, f3))
+lt_export = function(
+ x, output = 'lt.html', method = c('auto', 'node', 'browser', 'raw'),
+ css = TRUE, fragment = FALSE, crop = TRUE, width = NULL, padding = 8,
+ browser = NULL, ...
+) {
+ # HTML output (also the target when `output` is NA, since there's no
+ # extension to infer a format from): no browser needed at view time.
+ if (is.na(output) || tolower(xfun::file_ext(output)) == 'html') {
+ html = lt_html(x, method, css, fragment)
+ if (is.na(output)) return(html)
+ xfun::write_utf8(html, output)
+ return(output)
+ }
+ html = format(x, fragment = FALSE)
+ is_pdf = tolower(xfun::file_ext(output)) == 'pdf'
+ # PNG cropping needs magick to trim Chromium's screenshot (its --screenshot
+ # size is the window size, which we can't shrink below Chromium's minimums).
+ # PDF cropping needs no extra package: an @page rule sets the page box.
+ if (crop && !is_pdf && !xfun::loadable('magick')) {
+ warning('Cropping PNG output requires the magick package; ',
+ 'exporting the full page instead.')
+ crop = FALSE
+ }
+ pad = rep_len(padding, 2L) # c(vertical, horizontal)
+ # When cropping or when the user fixes a width, measure the rendered size
+ # (at that width); `w`/`h` are the outer box including padding.
+ if (crop || !is.null(width)) {
+ d = lt_measure(html, pad, width, browser)
+ w = width %||% d[1L]; h = d[2L]
+ layout = crop_layout(pad, w)
+ }
+ if (crop && is_pdf) {
+ # @page size drives the PDF page box exactly; no image post-processing.
+ style = sprintf('', w, h, layout)
+ html = sub('', paste0(style, ''), html, fixed = TRUE)
+ with_temp_html(html, function(f)
+ xfun::browser_print(f, output, browser = browser, ...))
+ return(output)
+ }
+ if (crop) {
+ # PNG: render onto a window large enough that the table is drawn
+ # unscaled at the top-left (Chromium clamps the window to a minimum size
+ # and reserves some height, and scales content that overflows the
+ # viewport), then crop the screenshot to the exact table box with magick.
+ html = sub('', paste0(''), html, fixed = TRUE)
+ png = tempfile(fileext = '.png')
+ on.exit(unlink(png), add = TRUE)
+ with_temp_html(html, function(f) xfun::browser_print(
+ f, png, browser = browser, window_size = c(max(500L, w), h + 120L)
+ ))
+ img = magick::image_crop(magick::image_read(png), sprintf('%dx%d+0+0', w, h))
+ magick::image_write(img, output, format = 'png')
+ return(output)
+ }
+ # No crop: honor an explicit width via body layout + window_size, else fall
+ # back to browser_print's default full page.
+ args = list(browser = browser, ...)
+ if (!is.null(width)) {
+ html = sub('', paste0(''), html, fixed = TRUE)
+ args$window_size = c(w, h)
+ }
+ with_temp_html(html, function(f)
+ do.call(xfun::browser_print, c(list(f, output), args)))
+ output
+}
+
has_browser = function() {
tryCatch({xfun:::check_browser(NULL); TRUE}, error = function(e) FALSE)
}
diff --git a/man/lt_export.Rd b/man/lt_export.Rd
new file mode 100644
index 0000000..75a5270
--- /dev/null
+++ b/man/lt_export.Rd
@@ -0,0 +1,107 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/render.R
+\name{lt_export}
+\alias{lt_export}
+\title{Export an lt table to a file}
+\usage{
+lt_export(
+ x,
+ output = "lt.html",
+ method = c("auto", "node", "browser", "raw"),
+ css = TRUE,
+ fragment = FALSE,
+ crop = TRUE,
+ width = NULL,
+ padding = 8,
+ browser = NULL,
+ ...
+)
+}
+\arguments{
+\item{x}{An \code{lt_tbl} object.}
+
+\item{output}{Output file path. Its extension selects the format: \code{.html},
+\code{.pdf}, or (otherwise) PNG. If \code{NA}, the HTML is returned as a string
+instead of being written to a file.}
+
+\item{method}{How to produce the HTML \verb{}: \code{"auto"}, \code{"node"},
+\code{"browser"}, or \code{"raw"} (see Details). Applies only to \code{.html} output.}
+
+\item{css}{Whether to include the lt.css runtime stylesheet in the HTML
+output. User CSS from \code{\link[=lt_css]{lt_css()}} is always included. Applies only to
+\code{.html} output.}
+
+\item{fragment}{If \code{FALSE} (default), wrap the HTML in a full HTML document;
+if \code{TRUE}, return only the table fragment. Applies only to \code{.html}
+output.}
+
+\item{crop}{Whether to crop the PDF/PNG tightly to the table, removing the
+surrounding page whitespace. This adds a preliminary browser pass to
+measure the rendered table. Set to \code{FALSE} for the default full page.
+Cropping PNG output requires the \pkg{magick} package; without it, PNG
+falls back to the full page (with a warning).}
+
+\item{width}{The width of the table in CSS pixels. By default (\code{NULL}) it
+shrinks to the table's natural width. A smaller \code{width} wraps cell
+content; a larger one pads the table.}
+
+\item{padding}{Padding in CSS pixels to keep around the table when
+cropping. A single value (all sides) or a length-two vector
+\code{c(vertical, horizontal)}.}
+
+\item{browser}{Path to the Chromium-based browser; passed to
+\code{\link[xfun:browser_print]{xfun::browser_print()}}. \code{NULL} (default) auto-detects.}
+
+\item{...}{Passed to \code{\link[xfun:browser_print]{xfun::browser_print()}} for PDF/PNG output.}
+}
+\value{
+The \code{output} path, or (when \code{output} is \code{NA}) the HTML as a string.
+}
+\description{
+Save a table to disk. The output format is chosen from the file extension
+of \code{output}: \code{.html} writes an HTML table, \code{.pdf} writes a vector PDF, and
+any other extension writes a PNG. PDF and PNG are produced by rendering the
+table in a headless Chromium browser (via \code{\link[xfun:browser_print]{xfun::browser_print()}}).
+}
+\details{
+For \code{.html} output, \code{method} controls how the \verb{} is produced:
+\code{"raw"} writes the JavaScript-spec HTML, so the table is built in the
+browser by the lt.js runtime when the file is viewed; the other methods
+bake a static \verb{} up front by running lt.js once (via \code{"node"} in
+Node.js or \code{"browser"} in a headless Chromium browser; \code{"auto"} picks Node
+if available, else the browser), so the saved file needs no JavaScript to
+view. \code{method}, \code{css}, and \code{fragment} apply only to \code{.html} output.
+}
+\section{Global option}{
+
+When the option \code{lt.lt_html} is set to a list of arguments (e.g.,
+\code{options(lt.lt_html = list(css = FALSE))}), the
+\link[knitr:knit_print]{knit_print} and \link[xfun:record_print]{record_print}
+methods emit the same static HTML table as \code{lt_export(x, "*.html")} (using
+those arguments as \code{method}/\code{css}/\code{fragment}) instead of the default
+JavaScript-based spec. This is useful for output formats that support raw
+HTML but cannot run JavaScript (e.g., GitHub Flavored Markdown).
+}
+
+\examples{
+tbl = lt(head(mtcars))
+
+# HTML with the JavaScript spec (table built by lt.js when viewed)
+lt_export(tbl, NA, method = 'raw') # character output
+f1 = tempfile(fileext = '.html')
+lt_export(tbl, f1, method = 'raw') # file output
+
+# Bake a static (needs Node.js or a headless browser).
+if (lt:::has_node() || lt:::has_browser())
+ lt_export(tbl, NA, method = 'auto', fragment = TRUE, css = FALSE)
+
+# PDF / PNG are rendered in a headless browser and cropped to the table.
+f2 = tempfile(fileext = '.pdf')
+f3 = tempfile(fileext = '.png')
+if (lt:::has_browser()) {
+ lt_export(tbl, f2)
+ lt_export(tbl, f3, width = 400)
+}
+
+unlink(c(f1, f2, f3))
+}
diff --git a/man/lt_html.Rd b/man/lt_html.Rd
deleted file mode 100644
index 8256b09..0000000
--- a/man/lt_html.Rd
+++ /dev/null
@@ -1,42 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/render.R
-\name{lt_html}
-\alias{lt_html}
-\title{Render an lt table to a static HTML table}
-\usage{
-lt_html(x, method = c("auto", "node", "browser"), css = TRUE, fragment = FALSE)
-}
-\arguments{
-\item{x}{An \code{lt_tbl} object.}
-
-\item{method}{One of \code{"auto"}, \code{"browser"}, or \code{"node"}.}
-
-\item{css}{Whether to include the lt.css runtime stylesheet in the output.
-User CSS from \code{\link[=lt_css]{lt_css()}} is always included.}
-
-\item{fragment}{If \code{FALSE} (default), wrap in a full HTML document. If
-\code{TRUE}, return only the rendered fragment.}
-}
-\value{
-A character vector of the rendered HTML.
-}
-\description{
-Execute the JavaScript runtime to produce a rendered \verb{} element. Two
-methods are available: \code{"node"} uses Node.js; \code{"browser"} uses a headless
-Chromium browser (via \code{\link[xfun:browser_dom]{xfun::browser_dom()}}). The default \code{"auto"} tries
-Node first (faster), then falls back to the browser.
-}
-\section{Global option}{
-
-When the option \code{lt.lt_html} is set to a list of arguments (e.g.,
-\code{options(lt.lt_html = list(fragment = TRUE, css = FALSE))}), the
-\link[knitr:knit_print]{knit_print} and \link[xfun:record_print]{record_print}
-methods will call \code{lt_html()} with these arguments instead of emitting the
-default JavaScript-based spec. This is useful for output formats that
-support raw HTML but cannot run JavaScript (e.g., GitHub Flavored
-Markdown).
-}
-
-\examples{
-if (interactive()) lt_html(lt(head(mtcars)))
-}
diff --git a/tests/test-ci/test-js.R b/tests/test-ci/test-js.R
index f1a6d4b..f11911f 100644
--- a/tests/test-ci/test-js.R
+++ b/tests/test-ci/test-js.R
@@ -241,3 +241,92 @@ assert("merge drops a conditional block when its references are empty", {
(matches(html, ".*>x.*") %==% "")
(matches(html, ".*\\(/\\).*") %==% html)
})
+
+# Count the pages in a PDF file (each page object is "/Type /Page" not
+# followed by "s", which would be "/Pages").
+pdf_pages = function(f) {
+ raw = readChar(f, file.info(f)$size, useBytes = TRUE)
+ length(gregexpr("/Type\\s*/Page[^s]", raw)[[1]])
+}
+
+if (has_browser()) assert("lt_export() writes PDF and PNG by extension", {
+ x = lt(data.frame(a = 1:2, b = c("x", "y")))
+ pdf = tempfile(fileext = ".pdf")
+ png = tempfile(fileext = ".png")
+ on.exit(unlink(c(pdf, png)), add = TRUE)
+
+ out = lt_export(x, pdf)
+ (out %==% pdf)
+ (file.exists(pdf))
+ # PDF magic bytes: "%PDF"
+ (rawToChar(readBin(pdf, "raw", 4L)) %==% "%PDF")
+ # A cropped table fits on a single page.
+ (pdf_pages(pdf) %==% 1L)
+
+ lt_export(x, png)
+ (file.exists(png))
+ # PNG magic bytes: 0x89 "PNG"
+ (readBin(png, "raw", 4L) %==% as.raw(c(0x89, 0x50, 0x4e, 0x47)))
+})
+
+# A taller/wider table (more rows than the PDF measurement used to account
+# for) must still crop to one page: the table's caption border and footer
+# spacing extend past the table's own border-box, so we measure the body's
+# scroll size, not the table box.
+if (has_browser()) assert("lt_export() crops a large table to one PDF page", {
+ x = lt(head(iris, 20))
+ pdf = tempfile(fileext = ".pdf")
+ on.exit(unlink(pdf), add = TRUE)
+ lt_export(x, pdf)
+ (pdf_pages(pdf) %==% 1L)
+})
+
+if (has_browser() && xfun::loadable("magick"))
+ assert("lt_export() crops PNG tightly to the table size", {
+ x = lt(head(mtcars))
+ d = lt_measure(format(x, fragment = FALSE), c(8L, 8L), NULL)
+ png = tempfile(fileext = ".png")
+ on.exit(unlink(png), add = TRUE)
+ lt_export(x, png, padding = 8)
+ info = magick::image_info(magick::image_read(png))
+ # The cropped image matches the measured content box exactly.
+ (info$width %==% d[1L])
+ (info$height %==% d[2L])
+ })
+
+# An explicit width overrides the measured width for both PDF and PNG,
+# regardless of crop.
+if (has_browser() && xfun::loadable("magick"))
+ assert("lt_export() honors an explicit width", {
+ x = lt(head(mtcars))
+ png = tempfile(fileext = ".png")
+ on.exit(unlink(png), add = TRUE)
+ lt_export(x, png, width = 400)
+ (magick::image_info(magick::image_read(png))$width %==% 400L)
+ })
+
+# .html output bakes a static table via lt_html().
+if (has_node() || has_browser())
+ assert("lt_export() writes a static HTML file", {
+ html = tempfile(fileext = ".html")
+ on.exit(unlink(html), add = TRUE)
+ out = lt_export(lt(data.frame(a = 1:2, b = c("x", "y"))), html)
+ (out %==% html)
+ txt = readLines(html)
+ (matches(txt, ".*b.*>1.*>x.*") %==% "")
+ })
+
+# output = NA returns the HTML string instead of writing a file.
+if (has_node() || has_browser())
+ assert("lt_export(output = NA) returns the HTML string", {
+ html = lt_export(lt(data.frame(a = 1:2, b = c("x", "y"))), NA)
+ (is.character(html))
+ (matches(html, ".*1.*>x.*") %==% "")
+ })
+
+# method = "raw" writes the JavaScript-spec HTML (table built client-side),
+# so the file carries the lt.js runtime and the spec, not a baked .
+assert('lt_export(method = "raw") emits the JS spec, no external tool', {
+ html = lt_export(lt(data.frame(a = 1:2)), NA, method = "raw")
+ (matches(html, ".*.*") %==% "")
+})
diff --git a/tests/testit/test-render.R b/tests/testit/test-render.R
index 625d082..dd3cffa 100644
--- a/tests/testit/test-render.R
+++ b/tests/testit/test-render.R
@@ -13,7 +13,7 @@ assert("format(fragment = FALSE) wraps in DOCTYPE", {
})
assert("format(assets = FALSE) omits runtime", {
- html = format(x, assets = FALSE)
+ html = unclass(format(x, assets = FALSE))
(matches(html, ".*