Unit test to test whether icons are correctly generated.

I'm developing a package that generates author lists from tabular data for Rmarkdown and Quarto. The package allows users to append ORCID icons to authors who have an ORCID identifier.

ORCID icons are located in the inst/icons/ directory.

I'm using the following function to get the icon path:

get_icon <- function(file) {
  system.file(paste0("icons/", file), package = "pkg_name")
}

I then have a function that generates the icon and hyperlink in markdown, either as pdf or svg depending on the output format of the document (determined by knitr::is_html_output()).

# as pdf
[\\hspace{3.000000pt}![](/path_to_pkg/pkg_name/inst/icons/orcid.pdf){height=16px}\\hspace{3.000000pt}](https://orcid.org/0000-0000-0000-0001)

# as svg
[![](/path_to_pkg/pkg_name/inst/icons/orcid.svg){height=16px style='margin: 0 4px; vertical-align: baseline'}](https://orcid.org/0000-0000-0000-0001)

I'm wondering how I should test this with testthat. I guess the test would do the following:

  • generating a temporary Rmd/qmd file
  • render it
  • compare the generated image link to a character string containing a pre-made image link to ensure that: (1) the image link is properly formatted and (2) the icon format is correct depending on whether the document is rendered as HMTL or not.

Besides, I guess I need to test it in a temporary directory (or package?) so that the icon path doesn't use my local one (e.g. /Users/user_name/pkg_name/inst/icons on my system). I know that withr has some tools for this but I'm fairly new to unit tests and package development in general so I'd love to have some guidance on this one.

No complete answer, just questions/tips. :smile_cat:

How do you generate the LaTeX/svg code for the image, do you use a knitr function? (knitr::include_graphics()?) Asking as if it's knitr's responsability, you might not need to test for it.

In any case if you want to test your function, called say make_icon(icon_name, format = c("svg", "pdf")) returns the correct string, you could test it on its own outside of any Rmd/qmd file, only testing the string is equal to an expected string?

Have you heard of snapshot tests? They might be useful here, and are in any case good to know about especially as you're generating files.

Lastly, if you decide to look for the generated image in a rendered temporary HTML document, you could use {xml2} and XPath to look for the image, as is done in some of pkgdown tests. Example

I actually have the icons stored in the package as svg and pdf. I've a set of helper functions to get the appropriate icons and generate an image/link as strings using the markdown syntax. I'm not using knitr::include_graphics() which I think won't allow me to reproduce exactly what I'm trying to do.

I need the redesign make_icon(), but currently the functions that make this are:

make_link <- function(url, content = NULL) {
  if (is.null(content)) {
    content <- url
  }
  sprintf("[%s](%s)", content, url)
}

get_icon <- function(file) {
  system.file(paste0("icons/", file), package = "plume")
}

make_icon <- function(file_name, size = 16, margin = size / 4) {
  if (knitr::is_html_output()) {
    size <- size + 4
    ext <- "svg"
    style <- sprintf(" style='margin: 0 %fpx; vertical-align: baseline'", margin)
    space <- ""
  } else {
    ext <- "pdf"
    style <- ""
    space <- sprintf("\\hspace{%fpt}", margin * .75)
  }
  file <- sprintf("%s.%s", file_name, ext)
  icon <- get_icon(file)
  sprintf("%s![](%s){height=%ipx%s}%s", space, icon, size, style, space)
}

make_orcid_link <- function(orcid) {
  check_orcid(orcid)
  url <- paste0("https://orcid.org/", orcid)
  icon <- make_icon("orcid")
  out <- make_link(url, icon)
  propagate_na(out, from = orcid)
}

The package is built around a few R6 classes. I've a method get_author_list() that returns author lists as character strings. The method essentially affixes symbols or icons linking an author to the desired metadata (affiliations, corresponding author, notes and/or ORCID) as character string.

The typical output of this method for an author with two affiliations and an ORCID identifier looks like:

François Dupuis^1,2^[\\hspace{3.000000pt}![](/path_to_pkg/pkg_name/inst/icons/orcid.pdf){height=16px}\\hspace{3.000000pt}](https://orcid.org/0000-0000-0000-0001)

That's what I was thinking to compare against a pre-made string. I'm only testing public methods and exported functions, not helpers like get_icon() or make_icon(). I was actually asking myself if it wouldn't be a good idea to test helpers function as well but it's probably worth making a new post for that.

I'm already using a bunch of snapshot tests but my main issues remain:

  • since the icon format is determined automatically based on the output format, I need to reproduce the actual rendering process: making a rmd or qmd file, render it in HTML and PDF and see what the output icon (or string to be more precise) is.
  • system.file() returns the local path from my system with my user name. I'd need to run the test on a dummy system environment so that the test doesn't display my user name and work on any systems.

The code is currently on a private repo but I can give you access if you'd like to have a better look at the code.

since the icon format is determined automatically based on the output format, I need to reproduce the actual rendering process: making a rmd or qmd file, render it in HTML and PDF and see what the output icon (or string to be more precise) is.

That sounds reasonable then (what will you use for reading the PDF? pdftools? -- why not look at an intermediary tex file instead?)

system.file() returns the local path from my system with my user name. I'd need to run the test on a dummy system environment so that the test doesn't display my user name and work on any systems.

Shouldn't your package copy the image to the folder of the Rmd/qmd document to make in particular the HTML file portable (say, one could drag and drop the folder with the HTML and the image to Netlify and deploy it)?
In any case expect_snapshot has a transform argument.

I was actually asking myself if it wouldn't be a good idea to test helpers function as well but it's probably worth making a new post for that.

You are right that it's recommended to test the interface. Now as with all things it's all about trade-offs. :sweat_smile: (not a very useful answer I'm afraid)

A very small detail (that you might already know but not like!): you use

  if (is.null(content)) {
    content <- url
  }

If you import rlang's %||% operator that could be

content <- content %||% url

Last, more important, point in case you are using {rorcid} for the ORCID part. rorcid has been archived on GitHub a while ago as it's no longer maintained (not archived on CRAN yet). GitHub - ropensci-archive/rorcid: ⚠ ARCHIVED A programmatic interface the Orcid.org API

Thanks so much for the discussion. It definitely helps clear thing up in my head!

I wasn't very explicit about it but yes I'm not planning to look at the actual final PDF output. I'm not interested in comparing the icon but the link to check that the icon format and ORCID identifier for a given author are correct. I'll need to test which intermediary step is the easiest to work with (probably the tex file as you said). Working with temporary local files is totally new to me so it's still a bit fuzzy in my head but I guess I simply need to try out.

Oh, the final output is perfectly portable but before rendering anything, the function will need to get the proper ORCID icon stored in the package (not locally) in pkg_name/inst/icons/. The issue is that to get the full path to the icon, system.file() uses the path to the package from the root directory of my system. I don't think there's another way to do it but then I need to test it on a dummy/virtual system so that it doesn't use my user name in the path to the icon.
Currently the method generating author lists returns something like this (in the case I want to show ORCIDs only):

"François Dupuis[\\hspace{3.000000pt}![](/Users/my_user_name/R/plume/inst/icons/orcid.pdf){height=16px}\\hspace{3.000000pt}](https://orcid.org/0000-0000-0000-0001)"

As you can see, the path to the icon uses /Users/user_name/.... I'd like to have something generic instead.

I actually like it and use it in the package. I initially decided to use more base R code in helper functions and more tidyverse-like syntax in classes but as the package grew, the syntax has become a bit inconsistent across helpers and other functions. I'll homogenize this later. It's only internal things so it's not urgent to fix at the moment.

I'm not using rorcid :).

Working with temporary local files is totally new to me so it's still a bit fuzzy in my head but I guess I simply need to try out.

During local experimentations you can simply swap the temporary folder to a real one. I often do that, creating a subfolder inside my package (so that it's right there in the file explorer) to see things. Then I delete it. Not sure it's a standard approach, but mentioning it in case it can help.

Currently the method generating author lists returns something like this (in the case I want to show ORCIDs only):

But why can't it

  • find the local image
  • copy it to the folder where the HTML/PDF will be produced (or a subfolder of it)
  • use the relative (to the HTML/PDF) path of the image in the string? so that at the stage you test, it's not an absolute path specific to the computer it's run on?

Again, sorry if I'm misunderstanding.

In general regarding installation in a temporary folder, Library paths — with_libpaths • withr might be relevant.

It can! It's just me being a newbie… I was looking at the link in the R console, not in the actual output document :man_facepalming:

I do get a proper path /Library/Frameworks/R.framework/...

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.