diff --git a/R/ace.R b/R/ace.R
index 454944bc3..5c7087386 100644
--- a/R/ace.R
+++ b/R/ace.R
@@ -1,3 +1,48 @@
# This file was autogenerated by 'tools/update-ace.R'
ACE_VERSION <- "1.10.1"
-ACE_THEMES <- c("ambiance", "chaos", "chrome", "cloud9_day", "cloud9_night_low_color", "cloud9_night", "clouds_midnight", "clouds", "cobalt", "crimson_editor", "dawn", "dracula", "dreamweaver", "eclipse", "github", "gob", "gruvbox_dark_hard", "gruvbox_light_hard", "gruvbox", "idle_fingers", "iplastic", "katzenmilch", "kr_theme", "kuroir", "merbivore_soft", "merbivore", "mono_industrial", "monokai", "nord_dark", "one_dark", "pastel_on_dark", "solarized_dark", "solarized_light", "sqlserver", "terminal", "textmate", "tomorrow_night_blue", "tomorrow_night_bright", "tomorrow_night_eighties", "tomorrow_night", "tomorrow", "twilight", "vibrant_ink", "xcode")
+ACE_THEMES <- c(
+ "ambiance",
+ "chaos",
+ "chrome",
+ "cloud9_day",
+ "cloud9_night_low_color",
+ "cloud9_night",
+ "clouds_midnight",
+ "clouds",
+ "cobalt",
+ "crimson_editor",
+ "dawn",
+ "dracula",
+ "dreamweaver",
+ "eclipse",
+ "github",
+ "gob",
+ "gruvbox_dark_hard",
+ "gruvbox_light_hard",
+ "gruvbox",
+ "idle_fingers",
+ "iplastic",
+ "katzenmilch",
+ "kr_theme",
+ "kuroir",
+ "merbivore_soft",
+ "merbivore",
+ "mono_industrial",
+ "monokai",
+ "nord_dark",
+ "one_dark",
+ "pastel_on_dark",
+ "solarized_dark",
+ "solarized_light",
+ "sqlserver",
+ "terminal",
+ "textmate",
+ "tomorrow_night_blue",
+ "tomorrow_night_bright",
+ "tomorrow_night_eighties",
+ "tomorrow_night",
+ "tomorrow",
+ "twilight",
+ "vibrant_ink",
+ "xcode"
+)
diff --git a/R/auto-complete.R b/R/auto-complete.R
index a6c3b6d4e..b58fae83b 100644
--- a/R/auto-complete.R
+++ b/R/auto-complete.R
@@ -1,8 +1,6 @@
-
# Given a line buffer, return a list of possible auto completions.
# If there is a valid label, then attach the server env to allow for local overrides of functions
auto_complete_r <- function(line, label = NULL, server_env = NULL) {
-
# If the last line includes comments then we don't return any completions.
# It's okay to consider only the last line for comments: Comment detection
# takes into account quotes on the same line, but `quotes = FALSE` in the
@@ -17,18 +15,31 @@ auto_complete_r <- function(line, label = NULL, server_env = NULL) {
# set completion settings
options <- utils::rc.options()
- utils::rc.options(package.suffix = "::",
- funarg.suffix = " = ",
- function.suffix = "(")
+ utils::rc.options(
+ package.suffix = "::",
+ funarg.suffix = " = ",
+ function.suffix = "("
+ )
on.exit(do.call(utils::rc.options, as.list(options)), add = TRUE)
# If and when exercises gain access to files, then we should evaluate this
# code in the exercise dir with `quotes = TRUE` (and sanitize to keep
# filename lookup local to exercise dir)
settings <- utils::rc.settings()
- utils::rc.settings(ops = TRUE, ns = TRUE, args = TRUE, func = FALSE,
- ipck = TRUE, S3 = TRUE, data = TRUE, help = TRUE,
- argdb = TRUE, fuzzy = FALSE, files = FALSE, quotes = FALSE)
+ utils::rc.settings(
+ ops = TRUE,
+ ns = TRUE,
+ args = TRUE,
+ func = FALSE,
+ ipck = TRUE,
+ S3 = TRUE,
+ data = TRUE,
+ help = TRUE,
+ argdb = TRUE,
+ fuzzy = FALSE,
+ files = FALSE,
+ quotes = FALSE
+ )
on.exit(do.call(utils::rc.settings, as.list(settings)), add = TRUE)
# temporarily attach global setup to search path
@@ -55,15 +66,19 @@ auto_complete_r <- function(line, label = NULL, server_env = NULL) {
# detect functions
splat <- strsplit(completions, ":{2,3}")
- fn <- vapply(splat, function(el) {
- n <- length(el)
- envir <- if (n == 1) .GlobalEnv else asNamespace(el[[1]])
- symbol <- if (n == 2) el[[2]] else el[[1]]
- tryCatch(
- is.function(get(symbol, envir = envir)),
- error = function(e) FALSE
- )
- }, logical(1))
+ fn <- vapply(
+ splat,
+ function(el) {
+ n <- length(el)
+ envir <- if (n == 1) .GlobalEnv else asNamespace(el[[1]])
+ symbol <- if (n == 2) el[[2]] else el[[1]]
+ tryCatch(
+ is.function(get(symbol, envir = envir)),
+ error = function(e) FALSE
+ )
+ },
+ logical(1)
+ )
# remove a leading '::', ':::' from autocompletion results, as
# those won't be inserted as expected in Ace
@@ -102,8 +117,12 @@ detect_comment <- function(line = "") {
next
}
in_escape <- FALSE
- if (!identical(char, "#")) next
- if (in_quote) next
+ if (!identical(char, "#")) {
+ next
+ }
+ if (in_quote) {
+ next
+ }
return(TRUE)
}
diff --git a/R/available_tutorials.R b/R/available_tutorials.R
index 8809fa8ab..cf2257649 100644
--- a/R/available_tutorials.R
+++ b/R/available_tutorials.R
@@ -1,4 +1,3 @@
-
#' List available tutorials
#'
#' List the tutorials that are currently available via installed R packages.
@@ -15,7 +14,6 @@
#'
#' @export
available_tutorials <- function(package = NULL) {
-
info <-
if (is.null(package)) {
all_available_tutorials()
@@ -45,7 +43,6 @@ available_tutorials <- function(package = NULL) {
#' "yaml_front_matter": list column of all yaml header info; [list()]
#' @noRd
available_tutorials_for_package <- function(package) {
-
an_error <- function(...) {
list(
tutorials = NULL,
@@ -53,22 +50,32 @@ available_tutorials_for_package <- function(package) {
)
}
- if (!file.exists(
- system.file(package = package)
- )) {
+ if (
+ !file.exists(
+ system.file(package = package)
+ )
+ ) {
return(an_error(
- "No package found with name: \"", package, "\""
+ "No package found with name: \"",
+ package,
+ "\""
))
}
tutorials_dir <- system.file("tutorials", package = package)
if (!file.exists(tutorials_dir)) {
return(an_error(
- "No tutorials found for package: \"", package, "\""
+ "No tutorials found for package: \"",
+ package,
+ "\""
))
}
- tutorial_folders <- list.dirs(tutorials_dir, full.names = TRUE, recursive = FALSE)
+ tutorial_folders <- list.dirs(
+ tutorials_dir,
+ full.names = TRUE,
+ recursive = FALSE
+ )
names(tutorial_folders) <- basename(tutorial_folders)
rmd_info <- lapply(tutorial_folders, function(tut_dir) {
dir_rmd_file <- run_find_tutorial_rmd(tut_dir)
@@ -83,7 +90,9 @@ available_tutorials_for_package <- function(package) {
title = yaml_front_matter$title %||% NA,
description = yaml_front_matter$description %||% NA,
private = yaml_front_matter$private %||% FALSE,
- package_dependencies = I(list(tutorial_dir_package_dependencies(tut_dir))),
+ package_dependencies = I(list(tutorial_dir_package_dependencies(
+ tut_dir
+ ))),
yaml_front_matter = I(list(yaml_front_matter)),
stringsAsFactors = FALSE,
row.names = FALSE
@@ -93,7 +102,9 @@ available_tutorials_for_package <- function(package) {
has_no_rmd <- vapply(rmd_info, is.null, logical(1))
if (all(has_no_rmd)) {
return(an_error(
- "No tutorial .Rmd files found for package: \"", package, "\""
+ "No tutorial .Rmd files found for package: \"",
+ package,
+ "\""
))
}
@@ -115,7 +126,7 @@ available_tutorials_for_package <- function(package) {
#' @noRd
all_available_tutorials <- function() {
ret <- list()
- all_pkgs <- installed.packages()[,"Package"]
+ all_pkgs <- installed.packages()[, "Package"]
for (pkg in all_pkgs) {
info <- available_tutorials_for_package(pkg)
@@ -136,21 +147,29 @@ all_available_tutorials <- function() {
get_tutorial_path <- function(name, package) {
-
tutorial_path <- system.file("tutorials", name, package = package)
# validate that it's a direcotry
if (!utils::file_test("-d", tutorial_path)) {
tutorials <- available_tutorials(package)
possible_tutorials <- tutorials$name
- msg <- paste0("Tutorial \"", name, "\" was not found in the \"", package, "\" package.")
+ msg <- paste0(
+ "Tutorial \"",
+ name,
+ "\" was not found in the \"",
+ package,
+ "\" package."
+ )
# if any tutorial names are _close_ tell the user
adist_vals <- utils::adist(possible_tutorials, name, ignore.case = TRUE)
if (any(adist_vals <= 3)) {
best_match <- possible_tutorials[which.min(adist_vals)]
msg <- paste0(
- msg, "\n",
- "Did you mean \"", best_match, "\"?"
+ msg,
+ "\n",
+ "Did you mean \"",
+ best_match,
+ "\"?"
)
}
stop.(msg, "\n", format(tutorials))
@@ -188,8 +207,11 @@ format.learnr_available_tutorials <- function(x, ...) {
)
ret <- paste0(
- ret, "\n",
- "* ", pkg, "\n",
+ ret,
+ "\n",
+ "* ",
+ pkg,
+ "\n",
paste0(txts, collapse = "\n")
)
}
diff --git a/R/debug_exercise_checker.R b/R/debug_exercise_checker.R
index fedd30c3d..592dc5d37 100644
--- a/R/debug_exercise_checker.R
+++ b/R/debug_exercise_checker.R
@@ -1,4 +1,3 @@
-
#' An Exercise Checker for Debugging
#'
#' An exercise checker for debugging that renders all of the expected arguments
@@ -23,16 +22,16 @@
#'
#' @keywords internal
debug_exercise_checker <- function(
- label,
- user_code,
- solution_code,
- check_code,
- envir_result,
- evaluate_result,
- envir_prep,
- last_value,
- engine,
- ...
+ label,
+ user_code,
+ solution_code,
+ check_code,
+ envir_result,
+ evaluate_result,
+ envir_prep,
+ last_value,
+ engine,
+ ...
) {
# Use I() around check_code to indicate that we want to evaluate the check code
checker_result <- if (is_AsIs(check_code)) {
@@ -109,16 +108,16 @@ debug_exercise_checker <- function(
location = "replace",
checker_result = checker_result,
checker_args = list(
- label = label,
- user_code = user_code,
- solution_code = solution_code,
- check_code = check_code,
- envir_result = envir_result,
+ label = label,
+ user_code = user_code,
+ solution_code = solution_code,
+ check_code = check_code,
+ envir_result = envir_result,
evaluate_result = evaluate_result,
- envir_prep = envir_prep,
- last_value = last_value,
- engine = engine,
- "..." = list(...)
+ envir_prep = envir_prep,
+ last_value = last_value,
+ engine = engine,
+ "..." = list(...)
)
)
}
diff --git a/R/evaluators.R b/R/evaluators.R
index 6f35cd9aa..10b2feb18 100644
--- a/R/evaluators.R
+++ b/R/evaluators.R
@@ -1,16 +1,16 @@
-
# inline execution evaluator
inline_evaluator <- function(expr, timelimit, ...) {
-
result <- NULL
list(
start = function() {
-
# setTimeLimit -- if the timelimit is exceeeded an error will occur
# during knit which we will catch and format within evaluate_exercise
- setTimeLimit(elapsed=timelimit, transient=TRUE);
- on.exit(setTimeLimit(cpu=Inf, elapsed=Inf, transient=FALSE), add = TRUE);
+ setTimeLimit(elapsed = timelimit, transient = TRUE)
+ on.exit(
+ setTimeLimit(cpu = Inf, elapsed = Inf, transient = FALSE),
+ add = TRUE
+ )
# execute and capture result
result <<- tryCatch(
@@ -34,7 +34,7 @@ inline_evaluator <- function(expr, timelimit, ...) {
}
# forked execution evaluator
-setup_forked_evaluator_factory <- function(max_forked_procs){
+setup_forked_evaluator_factory <- function(max_forked_procs) {
running_exercises <- 0
function(expr, timelimit, ...) {
@@ -63,11 +63,10 @@ setup_forked_evaluator_factory <- function(max_forked_procs){
}
list(
-
start = function() {
self$start_time <<- Sys.time()
- doStart <- function(){
+ doStart <- function() {
if (running_exercises >= max_forked_procs) {
# Then we can't start this job yet.
print("Delaying exercise execution due to forked proc limits")
@@ -79,7 +78,6 @@ setup_forked_evaluator_factory <- function(max_forked_procs){
running_exercises <<- running_exercises + 1
self$job <<- parallel::mcparallel(mc.interactive = FALSE, {
-
# close all connections
closeAllConnections()
@@ -105,7 +103,11 @@ setup_forked_evaluator_factory <- function(max_forked_procs){
}
# attempt to collect the result
- collect <- parallel::mccollect(jobs = self$job, wait = FALSE, timeout = 0.01)
+ collect <- parallel::mccollect(
+ jobs = self$job,
+ wait = FALSE,
+ timeout = 0.01
+ )
# got result
if (!is.null(collect)) {
@@ -120,15 +122,19 @@ setup_forked_evaluator_factory <- function(max_forked_procs){
# check if it's an error and convert it to an html error if it is
if (inherits(self$result, "try-error")) {
- self$result <<- exercise_result_error(self$result, timeout_exceeded = FALSE)
+ self$result <<- exercise_result_error(
+ self$result,
+ timeout_exceeded = FALSE
+ )
}
return(TRUE)
}
# hit timeout
- if (difftime(Sys.time(), self$start_time, units="secs") >= timelimit) {
-
+ if (
+ difftime(Sys.time(), self$start_time, units = "secs") >= timelimit
+ ) {
# call cleanup hook
call_hook("oncleanup", default = default_cleanup)
@@ -151,7 +157,12 @@ setup_forked_evaluator_factory <- function(max_forked_procs){
}
}
-forked_evaluator_factory <- setup_forked_evaluator_factory(max_forked_procs = getOption("tutorial.max.forked.procs", Sys.getenv("TUTORIAL_MAX_FORKED_PROCS", 3)))
+forked_evaluator_factory <- setup_forked_evaluator_factory(
+ max_forked_procs = getOption(
+ "tutorial.max.forked.procs",
+ Sys.getenv("TUTORIAL_MAX_FORKED_PROCS", 3)
+ )
+)
# Maintain for backwards-compatibility with original implementation in which
# forked_evaluator was uncapped
forked_evaluator <- setup_forked_evaluator_factory(max_forked_procs = Inf)
@@ -168,9 +179,12 @@ forked_evaluator <- setup_forked_evaluator_factory(max_forked_procs = Inf)
#' and `session`.
#' @export
external_evaluator <- function(
- endpoint = getOption("tutorial.external.host", Sys.getenv("TUTORIAL_EXTERNAL_EVALUATOR_HOST", NA)),
+ endpoint = getOption(
+ "tutorial.external.host",
+ Sys.getenv("TUTORIAL_EXTERNAL_EVALUATOR_HOST", NA)
+ ),
max_curl_conns = 50
-){
+) {
rlang::check_installed("curl", "to use an external evaluator.")
internal_external_evaluator(endpoint, max_curl_conns)
}
@@ -186,10 +200,12 @@ external_evaluator <- function(
internal_external_evaluator <- function(
endpoint,
max_curl_conns,
- initiate = initiate_external_session){
-
- if (is.na(endpoint)){
- stop("You must specify an endpoint explicitly as a parameter, or via the `tutorial.external.host` option, or the `TUTORIAL_EXTERNAL_EVALUATOR_HOST` environment variable")
+ initiate = initiate_external_session
+) {
+ if (is.na(endpoint)) {
+ stop(
+ "You must specify an endpoint explicitly as a parameter, or via the `tutorial.external.host` option, or the `TUTORIAL_EXTERNAL_EVALUATOR_HOST` environment variable"
+ )
}
# Trim trailing slash
@@ -197,38 +213,61 @@ internal_external_evaluator <- function(
function(expr, timelimit, exercise, session, ...) {
result <- NULL
- pool <- curl::new_pool(total_con = max_curl_conns, host_con = max_curl_conns)
+ pool <- curl::new_pool(
+ total_con = max_curl_conns,
+ host_con = max_curl_conns
+ )
list(
start = function() {
-
# The actual workhorse here -- called once we have a session ID on the external evaluator
- submit_req <- function(sess_id, cookiejar){
+ submit_req <- function(sess_id, cookiejar) {
# Work around a few edge cases on the exercise that don't serialize well
- if (identical(exercise$options$exercise.checker, "NULL")){
+ if (identical(exercise$options$exercise.checker, "NULL")) {
exercise$options$exercise.checker <- c()
}
- json <- jsonlite::toJSON(exercise, auto_unbox = TRUE, null = "null", force = TRUE)
+ json <- jsonlite::toJSON(
+ exercise,
+ auto_unbox = TRUE,
+ null = "null",
+ force = TRUE
+ )
if (
- identical(tolower(Sys.getenv("TUTORIAL_DEBUG_EXTERNAL_EVALUATOR_EVENT_SUBMISSION", "")), "true") ||
- "submission" %in% getOption("tutorial.debug.external_evaluator_event", "")
+ identical(
+ tolower(Sys.getenv(
+ "TUTORIAL_DEBUG_EXTERNAL_EVALUATOR_EVENT_SUBMISSION",
+ ""
+ )),
+ "true"
+ ) ||
+ "submission" %in%
+ getOption("tutorial.debug.external_evaluator_event", "")
) {
- event_trigger(session, "external_evaluator_submission", as.character(json))
+ event_trigger(
+ session,
+ "external_evaluator_submission",
+ as.character(json)
+ )
}
- if (is.null(exercise$options$exercise.timelimit) || exercise$options$exercise.timelimit == 0){
+ if (
+ is.null(exercise$options$exercise.timelimit) ||
+ exercise$options$exercise.timelimit == 0
+ ) {
timeout_s <- 30 * 1000
} else {
timeout_s <- exercise$options$exercise.timelimit * 1000
}
# Create curl request
- handle <- curl::new_handle(customrequest = "POST",
- copypostfields = json,
- # add 15 seconds for application startup
- timeout_ms = timeout_s + 15000,
- cookiefile=cookiejar)
+ handle <- curl::new_handle(
+ customrequest = "POST",
+ copypostfields = json,
+ # add 15 seconds for application startup
+ timeout_ms = timeout_s + 15000,
+ cookiefile = cookiejar
+ )
curl::handle_setheaders(handle, "Content-Type" = "application/json")
url <- paste0(endpoint, "/learnr/", sess_id)
@@ -238,48 +277,66 @@ internal_external_evaluator <- function(
# requests in the pool to resolve.
pending <- TRUE
- done_cb <- function(res){
+ done_cb <- function(res) {
pending <<- FALSE
- tryCatch({
- if (res$status != 200){
+ tryCatch(
+ {
+ if (res$status != 200) {
+ fail_cb(response_to_error(res))
+ return()
+ }
+
+ r <- rawToChar(res$content)
+ if (
+ identical(
+ tolower(Sys.getenv(
+ "TUTORIAL_DEBUG_EXTERNAL_EVALUATOR_EVENT_RESULT",
+ ""
+ )),
+ "true"
+ ) ||
+ "result" %in%
+ getOption("tutorial.debug.external_evaluator_event", "")
+ ) {
+ event_trigger(session, "external_evaluator_result", r)
+ }
+
+ valid_json <- jsonlite::validate(r)
+ if (!valid_json) {
+ stop(attr(valid_json, "err"))
+ }
+
+ result <<- r
+ },
+ error = function(e) {
+ print(e)
fail_cb(response_to_error(res))
- return()
- }
-
- r <- rawToChar(res$content)
- if (
- identical(tolower(Sys.getenv("TUTORIAL_DEBUG_EXTERNAL_EVALUATOR_EVENT_RESULT", "")), "true") ||
- "result" %in% getOption("tutorial.debug.external_evaluator_event", "")
- ) {
- event_trigger(session, "external_evaluator_result", r)
- }
-
- valid_json <- jsonlite::validate(r)
- if (!valid_json) {
- stop(attr(valid_json, "err"))
}
-
- result <<- r
- }, error = function(e){
- print(e)
- fail_cb(response_to_error(res))
- })
+ )
}
- fail_cb <- function(err){
+ fail_cb <- function(err) {
pending <<- FALSE
print("Error submitting external exercise:")
print(err)
- result <<- exercise_result_error("Error submitting external exercise. Please try again later")
+ result <<- exercise_result_error(
+ "Error submitting external exercise. Please try again later"
+ )
}
- curl::curl_fetch_multi(url, handle = handle, done = done_cb, fail = fail_cb, pool = pool)
+ curl::curl_fetch_multi(
+ url,
+ handle = handle,
+ done = done_cb,
+ fail = fail_cb,
+ pool = pool
+ )
- poll <- function(){
+ poll <- function() {
curl::multi_run(timeout = 0, pool = pool)
- if (pending){
+ if (pending) {
later::later(poll, delay = 0.1)
}
}
@@ -287,11 +344,15 @@ internal_external_evaluator <- function(
}
# Initiate a session
- if (is.null(session$userData$.external_evaluator_session_id)){
+ if (is.null(session$userData$.external_evaluator_session_id)) {
session$userData$.external_evaluator_session_id <-
- initiate(pool, paste0(endpoint, "/learnr/"), exercise$global_setup) %>%
- then(onFulfilled = function(extsess){
- session$onSessionEnded(function(){
+ initiate(
+ pool,
+ paste0(endpoint, "/learnr/"),
+ exercise$global_setup
+ ) %>%
+ then(onFulfilled = function(extsess) {
+ session$onSessionEnded(function() {
# Cleanup session cookiefile
# Because of https://github.com/rstudio/shiny/pull/2757, we can't
# trust that the reactive context will be provided here. So just
@@ -302,15 +363,18 @@ internal_external_evaluator <- function(
})
}
- session$userData$.external_evaluator_session_id %>% then(
- onFulfilled = function(extsess){
- submit_req(extsess$id, extsess$cookieFile)
- },
- onRejected = function(err){
- print(err)
- result <<- exercise_result_error("Error initiating session for external requests. Please try again later")
- }
- )
+ session$userData$.external_evaluator_session_id %>%
+ then(
+ onFulfilled = function(extsess) {
+ submit_req(extsess$id, extsess$cookieFile)
+ },
+ onRejected = function(err) {
+ print(err)
+ result <<- exercise_result_error(
+ "Error initiating session for external requests. Please try again later"
+ )
+ }
+ )
},
completed = function() {
@@ -321,18 +385,21 @@ internal_external_evaluator <- function(
if (is_exercise_result(result)) {
return(result)
}
- tryCatch({
- if (length(result) > 1) {
- result <- paste(result, collapse = "\n")
+ tryCatch(
+ {
+ if (length(result) > 1) {
+ result <- paste(result, collapse = "\n")
+ }
+ exercise_result_from_json(result)
+ },
+ error = function(e) {
+ exercise_result_error_internal(
+ exercise = exercise,
+ error = e,
+ task_internal = "converting result from external evaluator into a learnr exercise result"
+ )
}
- exercise_result_from_json(result)
- }, error = function(e) {
- exercise_result_error_internal(
- exercise = exercise,
- error = e,
- task_internal = "converting result from external evaluator into a learnr exercise result"
- )
- })
+ )
}
)
}
@@ -385,23 +452,37 @@ exercise_result_from_json <- function(json) {
#' @importFrom promises promise
#' @importFrom promises %>%
#' @noRd
-initiate_external_session <- function(pool, url, global_setup, retry_count = 0){
- promises::promise(function(resolve, reject){
- json <- jsonlite::toJSON(list(global_setup = global_setup), auto_unbox = TRUE, null = "null")
- handle <- curl::new_handle(customrequest = "POST",
- copypostfields = json)
+initiate_external_session <- function(
+ pool,
+ url,
+ global_setup,
+ retry_count = 0
+) {
+ promises::promise(function(resolve, reject) {
+ json <- jsonlite::toJSON(
+ list(global_setup = global_setup),
+ auto_unbox = TRUE,
+ null = "null"
+ )
+ handle <- curl::new_handle(customrequest = "POST", copypostfields = json)
# Track whether or not the current request is still active.
# We cannot use multi_run()$pending because it waits for ALL pending
# requests in the pool to resolve.
pending <- TRUE
- err_cb <- function(res){
+ err_cb <- function(res) {
pending <<- FALSE
# may just have hit a temporarily overloaded server. Retry
- if (res$status == 503 && retry_count < 2) { # three total tries
- resolve(initiate_external_session(pool, url, global_setup, retry_count+1))
+ if (res$status == 503 && retry_count < 2) {
+ # three total tries
+ resolve(initiate_external_session(
+ pool,
+ url,
+ global_setup,
+ retry_count + 1
+ ))
return()
} else {
# invoke the given error callback
@@ -410,26 +491,29 @@ initiate_external_session <- function(pool, url, global_setup, retry_count = 0){
}
}
- done_cb <- function(res){
+ done_cb <- function(res) {
pending <<- FALSE
id <- NULL
failed <- FALSE
- if (res$status != 200){
+ if (res$status != 200) {
reject(response_to_error(res))
return()
}
- tryCatch({
- r <- rawToChar(res$content)
- p <- jsonlite::fromJSON(r)
- id <- p$id
- }, error = function(e) {
- print(e)
- reject(response_to_error(res))
- return()
- })
+ tryCatch(
+ {
+ r <- rawToChar(res$content)
+ p <- jsonlite::fromJSON(r)
+ id <- p$id
+ },
+ error = function(e) {
+ print(e)
+ reject(response_to_error(res))
+ return()
+ }
+ )
cookies <- curl::handle_cookies(handle)
cookieFile <- tempfile("cookies")
@@ -437,11 +521,17 @@ initiate_external_session <- function(pool, url, global_setup, retry_count = 0){
resolve(list(id = id, cookieFile = cookieFile))
}
- curl::curl_fetch_multi(url, handle = handle, done = done_cb, fail = reject, pool = pool)
+ curl::curl_fetch_multi(
+ url,
+ handle = handle,
+ done = done_cb,
+ fail = reject,
+ pool = pool
+ )
- poll <- function(){
+ poll <- function() {
curl::multi_run(timeout = 0, pool = pool)
- if (pending){
+ if (pending) {
later::later(poll, delay = 0.1)
}
}
@@ -449,14 +539,14 @@ initiate_external_session <- function(pool, url, global_setup, retry_count = 0){
})
}
-response_to_error <- function(res){
+response_to_error <- function(res) {
headers <- res$headers
- if (is.raw(headers)){
+ if (is.raw(headers)) {
headers <- rawToChar(headers)
}
content <- res$content
- if (is.raw(content)){
+ if (is.raw(content)) {
content <- rawToChar(content)
}
@@ -484,9 +574,18 @@ response_to_error <- function(res){
# So we settled on this approach -- persisting the cookies off the connection
# ourselves in a format that can be read in by curl using the COOKIEFILE option.
#' @importFrom utils write.table
-write_cookies <- function(cookies, cookieFile){
+write_cookies <- function(cookies, cookieFile) {
cookies$expiration <- as.numeric(cookies$expiration)
- cookies$expiration[is.infinite(cookies$expiration) | is.na(cookies$expiration)] <- 0
+ cookies$expiration[
+ is.infinite(cookies$expiration) | is.na(cookies$expiration)
+ ] <- 0
cookies$expiration <- as.integer(cookies$expiration)
- write.table(cookies, cookieFile, row.names=FALSE, col.names=FALSE, sep="\t", quote = FALSE)
+ write.table(
+ cookies,
+ cookieFile,
+ row.names = FALSE,
+ col.names = FALSE,
+ sep = "\t",
+ quote = FALSE
+ )
}
diff --git a/R/events.R b/R/events.R
index bfbec6799..02e0b57f4 100644
--- a/R/events.R
+++ b/R/events.R
@@ -26,10 +26,13 @@ event_handlers <- new.env(parent = emptyenv())
#'
#' @export
event_register_handler <- function(event, callback) {
- if (!is.function(callback) ||
- !identical(names(formals(callback)), c("session", "event", "data")))
- {
- stop("`callback` must be a function that takes three arguments, `session`, `event`, and `data`.")
+ if (
+ !is.function(callback) ||
+ !identical(names(formals(callback)), c("session", "event", "data"))
+ ) {
+ stop(
+ "`callback` must be a function that takes three arguments, `session`, `event`, and `data`."
+ )
}
if (is.null(event_handlers[[event]])) {
@@ -77,9 +80,10 @@ create_event_handler_remover <- function(event, id) {
# Remove an event handler.
event_remove_handler <- function(event, id) {
- if (is.null(event_handlers[[event]]) ||
- is.null(event_handlers[[event]][[id]]))
- {
+ if (
+ is.null(event_handlers[[event]]) ||
+ is.null(event_handlers[[event]][[id]])
+ ) {
return(invisible(FALSE))
}
diff --git a/R/events_default.R b/R/events_default.R
index 1b0b29297..45e9424cb 100644
--- a/R/events_default.R
+++ b/R/events_default.R
@@ -1,20 +1,23 @@
-
broadcast_progress_event_to_client <- function(session, event, data) {
- session$sendCustomMessage("tutorial.progress_event", list(
- event = event,
- data = data
- ))
+ session$sendCustomMessage(
+ "tutorial.progress_event",
+ list(
+ event = event,
+ data = data
+ )
+ )
}
broadcast_question_event_to_client <- function(session, label, answer) {
- broadcast_progress_event_to_client(session = session,
- event = "question_submission",
- data = list(label = label, answer = answer))
+ broadcast_progress_event_to_client(
+ session = session,
+ event = "question_submission",
+ data = list(label = label, answer = answer)
+ )
}
register_default_event_handlers <- function() {
-
event_register_handler(
"session_start",
function(session, event, data) {
@@ -82,16 +85,16 @@ register_default_event_handlers <- function() {
# notify client side listeners
broadcast_question_event_to_client(
session = session,
- label = data$label,
- answer = data$answer
+ label = data$label,
+ answer = data$answer
)
# store submission for later replay
save_question_submission(
- session = session,
- label = data$label,
+ session = session,
+ label = data$label,
question = data$question,
- answer = data$answer
+ answer = data$answer
)
}
)
@@ -106,11 +109,10 @@ register_default_event_handlers <- function() {
list(label = data$label, answer = NULL)
)
-
# store submission for later replay
save_reset_question_submission(
- session = session,
- label = data$label,
+ session = session,
+ label = data$label,
question = data$question
)
}
@@ -131,7 +133,6 @@ register_default_event_handlers <- function() {
}
)
-
event_register_handler(
"exercise_result",
function(session, event, data) {
@@ -144,29 +145,33 @@ register_default_event_handlers <- function() {
# 1. Some code is submitted
# 2. A check is not required OR the submission was checked
# (correctness does not affect completion)
- completed <- nzchar(trimws(data$code)) && (!requires_check || data$checked)
+ completed <- nzchar(trimws(data$code)) &&
+ (!requires_check || data$checked)
# notify client side listeners
broadcast_progress_event_to_client(
session = session,
event = "exercise_submission",
- data = list(label = data$label, correct = correct, completed = completed)
+ data = list(
+ label = data$label,
+ correct = correct,
+ completed = completed
+ )
)
# save submission for later replay
save_exercise_submission(
- session = session,
- label = data$label,
- code = data$code,
- output = data$output,
+ session = session,
+ label = data$label,
+ code = data$code,
+ output = data$output,
error_message = data$error_message,
- checked = data$checked,
- feedback = data$feedback
+ checked = data$checked,
+ feedback = data$feedback
)
}
)
-
event_register_handler(
"video_progress",
function(session, event, data) {
diff --git a/R/events_record.R b/R/events_record.R
index 78e18920e..8a8696333 100644
--- a/R/events_record.R
+++ b/R/events_record.R
@@ -1,11 +1,13 @@
record_event <- function(session, event, data) {
recorder <- getOption("tutorial.event_recorder", default = NULL)
if (!is.null(recorder)) {
- recorder(tutorial_id = read_request(session, "tutorial.tutorial_id"),
- tutorial_version = read_request(session, "tutorial.tutorial_version"),
- user_id = read_request(session, "tutorial.user_id"),
- event = event,
- data = data)
+ recorder(
+ tutorial_id = read_request(session, "tutorial.tutorial_id"),
+ tutorial_version = read_request(session, "tutorial.tutorial_version"),
+ user_id = read_request(session, "tutorial.user_id"),
+ event = event,
+ data = data
+ )
}
invisible(NULL)
}
@@ -20,6 +22,10 @@ debug_event_recorder <- function(
) {
cat(tutorial_id, " (", tutorial_version, "): ", user_id, "\n", sep = "")
cat("event: ", event, "\n", sep = "")
- if (is.character(data)) cat(data) else utils::str(data)
+ if (is.character(data)) {
+ cat(data)
+ } else {
+ utils::str(data)
+ }
cat("\n")
}
diff --git a/R/exercise.R b/R/exercise.R
index fbf280ffc..ee97ade8b 100644
--- a/R/exercise.R
+++ b/R/exercise.R
@@ -11,17 +11,22 @@ cache_complete_exercise <- function(exercise) {
get_opt_quick_restore <- function() {
env <- Sys.getenv("TUTORIAL_QUICK_RESTORE", NA)
- if (!is.na(env)) return(env)
+ if (!is.na(env)) {
+ return(env)
+ }
opt <- getOption("tutorial.quick_restore", NA)
- if (isTRUE(opt)) return("1")
- if (isFALSE(opt)) return("0")
+ if (isTRUE(opt)) {
+ return("1")
+ }
+ if (isFALSE(opt)) {
+ return("0")
+ }
if (!is.na(opt)) as.character(opt) else "0"
}
# run an exercise and return HTML UI
setup_exercise_handler <- function(exercise_rx, session) {
-
# get the environment where shared setup and data is located. one environment up
# includes all of the shiny housekeeping (e.g. inputs, output, etc.); two
# environments up will be an empty environment
@@ -56,7 +61,10 @@ setup_exercise_handler <- function(exercise_rx, session) {
return()
}
- object <- get_exercise_submission(session = session, label = exercise$label)
+ object <- get_exercise_submission(
+ session = session,
+ label = exercise$label
+ )
if (!is.null(object) && !is.null(object$data$output)) {
# restore user state, but don't report correct
# since the user's code wasn't re-evaluated
@@ -72,8 +80,11 @@ setup_exercise_handler <- function(exercise_rx, session) {
# ensure that html dependencies only reference package files
dependencies <- htmltools::htmlDependencies(output)
- if (!is.null(dependencies))
- htmltools::htmlDependencies(output) <- filter_dependencies(dependencies)
+ if (!is.null(dependencies)) {
+ htmltools::htmlDependencies(output) <- filter_dependencies(
+ dependencies
+ )
+ }
# assign to rv and return
rv$result <- output
@@ -88,15 +99,22 @@ setup_exercise_handler <- function(exercise_rx, session) {
}
# get exercise evaluator factory function (allow replacement via global option)
- evaluator_factory <- getOption("tutorial.exercise.evaluator", default = NULL)
+ evaluator_factory <- getOption(
+ "tutorial.exercise.evaluator",
+ default = NULL
+ )
if (is.null(evaluator_factory)) {
- remote_host <- getOption("tutorial.external.host", Sys.getenv("TUTORIAL_EXTERNAL_EVALUATOR_HOST", NA))
- if (!is.na(remote_host)){
+ remote_host <- getOption(
+ "tutorial.external.host",
+ Sys.getenv("TUTORIAL_EXTERNAL_EVALUATOR_HOST", NA)
+ )
+ if (!is.na(remote_host)) {
evaluator_factory <- external_evaluator(remote_host)
- } else if (!is_windows() && !is_mac())
+ } else if (!is_windows() && !is_mac()) {
evaluator_factory <- forked_evaluator_factory
- else
+ } else {
evaluator_factory <- inline_evaluator
+ }
}
# retrieve exercise cache information:
@@ -118,8 +136,9 @@ setup_exercise_handler <- function(exercise_rx, session) {
# get timelimit option (either from chunk option or from global option)
timelimit <- exercise$options$exercise.timelimit
- if (is.null(timelimit))
+ if (is.null(timelimit)) {
timelimit <- getOption("tutorial.exercise.timelimit", default = 30)
+ }
# placeholder for current learnr version to deal with exercise structure differences
# with other learnr versions
@@ -133,8 +152,12 @@ setup_exercise_handler <- function(exercise_rx, session) {
}
# create exercise evaluator
- evaluator <- evaluator_factory(evaluate_exercise(exercise, envir),
- timelimit, exercise, session)
+ evaluator <- evaluator_factory(
+ evaluate_exercise(exercise, envir),
+ timelimit,
+ exercise,
+ session
+ )
# Create exercise ID to map the associated events.
ex_id <- random_id("lnr_ex")
@@ -144,9 +167,9 @@ setup_exercise_handler <- function(exercise_rx, session) {
session,
"exercise_submitted",
data = list(
- label = exercise$label,
- id = ex_id,
- code = exercise$code,
+ label = exercise$label,
+ id = ex_id,
+ code = exercise$code,
restore = exercise$restore
)
)
@@ -158,9 +181,7 @@ setup_exercise_handler <- function(exercise_rx, session) {
# poll for completion
o <- observe({
-
if (evaluator$completed()) {
-
# get the result
result <- evaluator$result()
@@ -169,20 +190,26 @@ setup_exercise_handler <- function(exercise_rx, session) {
session,
"exercise_result",
data = list(
- label = exercise$label,
- id = ex_id,
- code = exercise$code,
- output = result$html_output,
+ label = exercise$label,
+ id = ex_id,
+ code = exercise$code,
+ output = result$html_output,
timeout_exceeded = result$timeout_exceeded,
- time_elapsed = as.numeric(difftime(Sys.time(), start, units="secs")),
- error_message = result$error_message,
- checked = check_was_requested,
- feedback = result$feedback
+ time_elapsed = as.numeric(difftime(
+ Sys.time(),
+ start,
+ units = "secs"
+ )),
+ error_message = result$error_message,
+ checked = check_was_requested,
+ feedback = result$feedback
)
)
# assign reactive result to be sent to the UI
- rv$triggered <- isolate({ rv$triggered + 1})
+ rv$triggered <- isolate({
+ rv$triggered + 1
+ })
rv$result <- exercise_result_as_html(result)
isolate({
@@ -199,10 +226,8 @@ setup_exercise_handler <- function(exercise_rx, session) {
}
})
-
# destroy the observer
o$destroy()
-
} else {
invalidateLater(100, session)
}
@@ -255,7 +280,9 @@ upgrade_exercise <- function(exercise, require_items = NULL) {
}
stop(
- "Received an exercise with ", v, ", most likely because it's ",
+ "Received an exercise with ",
+ v,
+ ", most likely because it's ",
"from an older version of {learnr}. This is {learnr} version ",
utils::packageVersion("learnr")
)
@@ -304,18 +331,28 @@ upgrade_exercise <- function(exercise, require_items = NULL) {
# this exercise will work out. Or at least won't result in surfacing an
# internal learnr error as the culprit.
warning(
- "Expected exercise version ", current_version, ", but received version ",
- exercise$version, ". This version of {learnr} is likely able to evaluate ",
- "version ", exercise$version, " exercises, but there may be differences. ",
+ "Expected exercise version ",
+ current_version,
+ ", but received version ",
+ exercise$version,
+ ". This version of {learnr} is likely able to evaluate ",
+ "version ",
+ exercise$version,
+ " exercises, but there may be differences. ",
"Please upgrade {learnr}; this version is ",
- utils::packageVersion("learnr"), "."
+ utils::packageVersion("learnr"),
+ "."
)
return(exercise)
}
stop(
- "Expected exercise version ", current_version, ", but received version ",
- exercise$version, ". These versions are incompatible. ", exercise_problem
+ "Expected exercise version ",
+ current_version,
+ ", but received version ",
+ exercise$version,
+ ". These versions are incompatible. ",
+ exercise_problem
)
}
@@ -327,7 +364,10 @@ validate_exercise <- function(exercise, require_items = NULL) {
required_names <- c("code", "label", "options", "chunks", require_items)
missing_names <- setdiff(required_names, names(exercise))
if (length(missing_names)) {
- return(paste("Missing exercise items:", paste(missing_names, collapse = ", ")))
+ return(paste(
+ "Missing exercise items:",
+ paste(missing_names, collapse = ", ")
+ ))
}
NULL
@@ -346,7 +386,15 @@ standardize_code <- function(code) {
}
standardize_exercise_code <- function(exercise) {
- ex_code_items <- c("error_check", "code_check", "check", "code", "global_setup", "solution", "tests")
+ ex_code_items <- c(
+ "error_check",
+ "code_check",
+ "check",
+ "code",
+ "global_setup",
+ "solution",
+ "tests"
+ )
exercise[ex_code_items] <- lapply(exercise[ex_code_items], standardize_code)
exercise
}
@@ -364,9 +412,11 @@ standardize_exercise_code <- function(exercise) {
# evaluators, if they choose to use this function, might want to include the
# global setup.
evaluate_exercise <- function(
- exercise, envir, evaluate_global_setup = FALSE, data_dir = NULL
+ exercise,
+ envir,
+ evaluate_global_setup = FALSE,
+ data_dir = NULL
) {
-
# Exercise Prep and Standardization ---------------------------------------
# Protect global options and environment vars from permanent modification
local_restore_options_and_envvars()
@@ -392,17 +442,20 @@ evaluate_exercise <- function(
# Evaluate Global Setup ---------------------------------------------------
if (evaluate_global_setup) {
res_global <-
- tryCatch({
- eval(parse(text = exercise$global_setup), envir = envir)
- NULL
- }, error = function(err) {
- exercise_result_error_internal(
- exercise,
- err,
- task_internal = "evaluating the global setup",
- task_external = "setting up the tutorial"
- )
- })
+ tryCatch(
+ {
+ eval(parse(text = exercise$global_setup), envir = envir)
+ NULL
+ },
+ error = function(err) {
+ exercise_result_error_internal(
+ exercise,
+ err,
+ task_internal = "evaluating the global setup",
+ task_external = "setting up the tutorial"
+ )
+ }
+ )
if (is_exercise_result(res_global)) {
return(res_global)
@@ -470,15 +523,23 @@ evaluate_exercise <- function(
if (!inherits(err_render, "learnr_render_exercise_error")) {
# render exercise errors are expected, but something really went wrong
return(
- exercise_result_error_internal(exercise, err_render, "evaluating your exercise", "inside render_exercise()")
+ exercise_result_error_internal(
+ exercise,
+ err_render,
+ "evaluating your exercise",
+ "inside render_exercise()"
+ )
)
}
error_feedback <- NULL
error_check_code <- exercise$error_check
- error_should_check <- nzchar(exercise$check) || nzchar(exercise$code_check)
+ error_should_check <- nzchar(exercise$check) ||
+ nzchar(exercise$code_check)
if (error_should_check && !nzchar(error_check_code)) {
# If there is no locally defined error check code, look for globally defined error check option
- error_check_code <- standardize_code(exercise$options$exercise.error.check.code)
+ error_check_code <- standardize_code(
+ exercise$options$exercise.error.check.code
+ )
}
if (nzchar(error_check_code)) {
# Error check -------------------------------------------------------
@@ -536,9 +597,14 @@ evaluate_exercise <- function(
try_checker <- function(
- exercise, stage,
- name = "exercise.checker", check_code = NULL, envir_result = NULL,
- evaluate_result = NULL, envir_prep, last_value = NULL,
+ exercise,
+ stage,
+ name = "exercise.checker",
+ check_code = NULL,
+ envir_result = NULL,
+ evaluate_result = NULL,
+ envir_prep,
+ last_value = NULL,
engine = exercise$engine
) {
checker_func <- tryCatch(
@@ -576,7 +642,8 @@ try_checker <- function(
} else {
msg <- sprintf(
"Either add ... or the following arguments to the '%s' function: '%s'",
- name, paste(missing_args, collapse = "', '")
+ name,
+ paste(missing_args, collapse = "', '")
)
message(msg)
rlang::return_from(rlang::caller_env(), exercise_result_error(msg))
@@ -612,8 +679,13 @@ get_checker_func <- function(exercise, name, envir) {
if (is.function(checker)) {
environment(checker) <- envir
return(checker)
- } else if(!is.null(checker)) {
- warning("Ignoring the ", name, " option since it isn't a function", call. = FALSE)
+ } else if (!is.null(checker)) {
+ warning(
+ "Ignoring the ",
+ name,
+ " option since it isn't a function",
+ call. = FALSE
+ )
}
function(...) NULL
}
@@ -646,7 +718,10 @@ render_exercise <- function(exercise, envir) {
if (isTRUE(user)) {
knitr_options$knit_hooks$evaluate <- function(
- code, envir, ..., output_handler # knitr's output_handler
+ code,
+ envir,
+ ...,
+ output_handler # knitr's output_handler
) {
has_visible_arg <- length(formals(output_handler$value)) > 1
# wrap `output_handler$value` to be able to capture the `last_value`
@@ -667,7 +742,9 @@ render_exercise <- function(exercise, envir) {
}
}
evaluate_result <<- evaluate::evaluate(
- code, envir, ...,
+ code,
+ envir,
+ ...,
output_handler = output_handler
)
evaluate_result
@@ -689,70 +766,75 @@ render_exercise <- function(exercise, envir) {
envir_result <- envir_prep
# First, Rmd to markdown (and exit early if any error)
- output_file <- tryCatch({
- # — Render Exercise Stage: Prep ----
- # TODO: The render stage and everything associated with it should really be
- # named "setup", e.g. `envir_setup`, etc. The stage here is called
- # "prep" to avoid confusion with the current naming.
- render_stage <- "prep"
-
- render_exercise_evaluate_prep(
- exercise = exercise,
- envir_prep = envir_prep,
- output_format_exercise(user = FALSE)
- )
-
- # Create exercise.Rmd after running setup so it isn't accidentally overwritten
- if (file.exists("exercise.Rmd")) {
- warning(
- "Evaluating user code in exercise '", exercise$label, "' created ",
- "'exercise.Rmd'. If the setup code for this exercise creates a file ",
- "with that name, please choose another name.",
- immediate. = TRUE
+ output_file <- tryCatch(
+ {
+ # — Render Exercise Stage: Prep ----
+ # TODO: The render stage and everything associated with it should really be
+ # named "setup", e.g. `envir_setup`, etc. The stage here is called
+ # "prep" to avoid confusion with the current naming.
+ render_stage <- "prep"
+
+ render_exercise_evaluate_prep(
+ exercise = exercise,
+ envir_prep = envir_prep,
+ output_format_exercise(user = FALSE)
)
- }
- # — Render Exercise Stage: User ----
- render_stage <- "user"
- # Copy in a full clone `envir_prep` before running user code in `envir_result`
- # By being a sibling to `envir_prep` (rather than a dependency),
- # alterations to `envir_prep` from eval'ing code in `envir_result`
- # are much more difficult
- envir_result <- duplicate_env(envir_prep)
-
- render_exercise_evaluate_user(
- exercise = exercise,
- envir_result = envir_result,
- output_format_exercise(user = TRUE)
- )
- }, error = function(e) {
- msg <- conditionMessage(e)
- # make the time limit error message a bit more friendly
- pattern <- gettext("reached elapsed time limit", domain = "R")
- if (grepl(pattern, msg, fixed = TRUE)) {
- return(exercise_result_timeout())
- }
+ # Create exercise.Rmd after running setup so it isn't accidentally overwritten
+ if (file.exists("exercise.Rmd")) {
+ warning(
+ "Evaluating user code in exercise '",
+ exercise$label,
+ "' created ",
+ "'exercise.Rmd'. If the setup code for this exercise creates a file ",
+ "with that name, please choose another name.",
+ immediate. = TRUE
+ )
+ }
- if (render_stage == "prep") {
- # errors in setup (prep) code should be returned as internal error results
- return(
- exercise_result_error_internal(
- exercise = exercise,
- error = e,
- task_external = "setting up the exercise",
- task_internal = "rendering exercise setup"
+ # — Render Exercise Stage: User ----
+ render_stage <- "user"
+ # Copy in a full clone `envir_prep` before running user code in `envir_result`
+ # By being a sibling to `envir_prep` (rather than a dependency),
+ # alterations to `envir_prep` from eval'ing code in `envir_result`
+ # are much more difficult
+ envir_result <- duplicate_env(envir_prep)
+
+ render_exercise_evaluate_user(
+ exercise = exercise,
+ envir_result = envir_result,
+ output_format_exercise(user = TRUE)
+ )
+ },
+ error = function(e) {
+ msg <- conditionMessage(e)
+ # make the time limit error message a bit more friendly
+ pattern <- gettext("reached elapsed time limit", domain = "R")
+ if (grepl(pattern, msg, fixed = TRUE)) {
+ return(exercise_result_timeout())
+ }
+
+ if (render_stage == "prep") {
+ # errors in setup (prep) code should be returned as internal error results
+ return(
+ exercise_result_error_internal(
+ exercise = exercise,
+ error = e,
+ task_external = "setting up the exercise",
+ task_internal = "rendering exercise setup"
+ )
)
+ }
+
+ rlang::abort(
+ class = "learnr_render_exercise_error",
+ envir_result = envir_result,
+ evaluate_result = evaluate_result,
+ envir_prep = envir_prep,
+ parent = e
)
}
-
- rlang::abort(
- class = "learnr_render_exercise_error",
- envir_result = envir_result,
- evaluate_result = evaluate_result,
- envir_prep = envir_prep,
- parent = e
- )
- })
+ )
if (is_exercise_result(output_file)) {
# this only happens when the render result is a timeout error or setup error
@@ -762,8 +844,11 @@ render_exercise <- function(exercise, envir) {
# Render markdown to HTML
dependencies <- filter_dependencies(attr(output_file, "knit_meta"))
output_file <- rmarkdown::render(
- input = output_file, output_format = output_format_exercise(user = TRUE),
- envir = envir_result, quiet = TRUE, clean = FALSE
+ input = output_file,
+ output_format = output_format_exercise(user = TRUE),
+ envir = envir_result,
+ quiet = TRUE,
+ clean = FALSE
)
output <- readLines(output_file, warn = FALSE, encoding = "UTF-8")
html_output <- htmltools::attachDependencies(
@@ -771,10 +856,13 @@ render_exercise <- function(exercise, envir) {
dependencies
)
- if (!last_value_is_visible && isTRUE(exercise$options$exercise.warn_invisible)) {
+ if (
+ !last_value_is_visible && isTRUE(exercise$options$exercise.warn_invisible)
+ ) {
invisible_feedback <- list(
message = "The submitted code didn't produce a visible value, so exercise checking may not work correctly.",
- type = "warning", correct = FALSE
+ type = "warning",
+ correct = FALSE
)
html_output <- htmltools::tagList(
feedback_as_html(invisible_feedback),
@@ -815,7 +903,11 @@ render_exercise_evaluate_prep <- function(exercise, envir_prep, output_format) {
}
}
-render_exercise_evaluate_user <- function(exercise, envir_result, output_format) {
+render_exercise_evaluate_user <- function(
+ exercise,
+ envir_result,
+ output_format
+) {
withr::defer(render_exercise_post_stage_hook(exercise, "user", envir_result))
rmd_src_user <- render_exercise_rmd_user(exercise)
@@ -864,19 +956,26 @@ exercise_code_chunks_user <- function(exercise) {
}
exercise_code_chunks <- function(chunks, engine = "r") {
- vapply(chunks, function(chunk) {
- opts <- chunk$opts[setdiff(names(chunk$opts), "label")]
- opts <- paste(names(opts), unname(opts), sep = "=")
- chunk_engine <- chunk$engine %||% engine %||% "r"
- chunk_opts <- paste0(c(dput_to_string(chunk$label), opts), collapse = ", ")
- paste(
- sep = "\n",
- # we quote the label to ensure that it is treated as a label and not a symbol for instance
- sprintf("```{%s %s}", chunk_engine, chunk_opts),
- paste0(chunk$code, collapse = "\n"),
- "```"
- )
- }, character(1))
+ vapply(
+ chunks,
+ function(chunk) {
+ opts <- chunk$opts[setdiff(names(chunk$opts), "label")]
+ opts <- paste(names(opts), unname(opts), sep = "=")
+ chunk_engine <- chunk$engine %||% engine %||% "r"
+ chunk_opts <- paste0(
+ c(dput_to_string(chunk$label), opts),
+ collapse = ", "
+ )
+ paste(
+ sep = "\n",
+ # we quote the label to ensure that it is treated as a label and not a symbol for instance
+ sprintf("```{%s %s}", chunk_engine, chunk_opts),
+ paste0(chunk$code, collapse = "\n"),
+ "```"
+ )
+ },
+ character(1)
+ )
}
exercise_get_blanks_pattern <- function(exercise) {
@@ -940,14 +1039,23 @@ exercise_check_code_for_blanks <- function(exercise) {
key = "text.pleasereplaceblank",
opts = list(
count = length(blanks),
- blank = i18n_combine_words(unique(blanks), before = "", after = ""),
+ blank = i18n_combine_words(
+ unique(blanks),
+ before = "",
+ after = ""
+ ),
interpolation = list(escapeValue = FALSE)
)
)
)
exercise_result(
- list(message = HTML(msg), correct = FALSE, location = "prepend", type = "error")
+ list(
+ message = HTML(msg),
+ correct = FALSE,
+ location = "prepend",
+ type = "error"
+ )
)
}
@@ -1023,7 +1131,13 @@ exercise_check_unparsable_unicode <- function(exercise, error_message) {
names(replacement_pattern) <- c(single_quote_pattern, double_quote_pattern)
return(
- unparsable_unicode_message("unparsablequotes", code, line, quote_pattern, replacement_pattern)
+ unparsable_unicode_message(
+ "unparsablequotes",
+ code,
+ line,
+ quote_pattern,
+ replacement_pattern
+ )
)
}
@@ -1042,7 +1156,13 @@ exercise_check_unparsable_unicode <- function(exercise, error_message) {
names(replacement_pattern) <- dash_pattern
return(
- unparsable_unicode_message("unparsableunicodesuggestion", code, line, dash_pattern, replacement_pattern)
+ unparsable_unicode_message(
+ "unparsableunicodesuggestion",
+ code,
+ line,
+ dash_pattern,
+ replacement_pattern
+ )
)
}
@@ -1050,12 +1170,21 @@ exercise_check_unparsable_unicode <- function(exercise, error_message) {
# Regex searches for any codepoints not in the ASCII range (00-7F)
non_ascii_pattern <- "[^\u01-\u7f]"
return(
- unparsable_unicode_message("unparsableunicode", code, line, non_ascii_pattern)
+ unparsable_unicode_message(
+ "unparsableunicode",
+ code,
+ line,
+ non_ascii_pattern
+ )
)
}
unparsable_unicode_message <- function(
- i18n_key, code, line, pattern, replacement_pattern = NULL
+ i18n_key,
+ code,
+ line,
+ pattern,
+ replacement_pattern = NULL
) {
code <- unlist(strsplit(code, "\n"))[[line]]
@@ -1111,7 +1240,12 @@ exercise_result_timeout <- function() {
# @param timeout_exceeded represents whether or not the error was triggered
# because the exercise exceeded the timeout. Use NA if unknown
-exercise_result_error <- function(error_message, feedback = NULL, timeout_exceeded = NA, style = "code") {
+exercise_result_error <- function(
+ error_message,
+ feedback = NULL,
+ timeout_exceeded = NA,
+ style = "code"
+) {
exercise_result(
feedback = feedback,
timeout_exceeded = timeout_exceeded,
@@ -1126,8 +1260,14 @@ exercise_result_error_internal <- function(
task_external = "",
task_internal = task_external
) {
- task_external <- paste0(if (nzchar(task_external %||% "")) " while ", task_external)
- task_internal <- paste0(if (nzchar(task_internal %||% "")) " while ", task_internal)
+ task_external <- paste0(
+ if (nzchar(task_external %||% "")) " while ",
+ task_external
+ )
+ task_internal <- paste0(
+ if (nzchar(task_internal %||% "")) " while ",
+ task_internal
+ )
msg_internal <- sprintf(
"An error occurred%s for exercise '%s'",
@@ -1164,7 +1304,11 @@ exercise_result <- function(
feedback$html <- feedback_as_html(feedback)
}
- if (!inherits(html_output, "html") && is.character(html_output) && any(nzchar(html_output))) {
+ if (
+ !inherits(html_output, "html") &&
+ is.character(html_output) &&
+ any(nzchar(html_output))
+ ) {
html_output <- htmltools::HTML(html_output)
} else if (length(html_output) == 0) {
html_output <- NULL
@@ -1222,9 +1366,11 @@ filter_dependencies <- function(dependencies) {
} else if (!is.null(dependency$package)) {
TRUE
} else {
- ! is.null(tryCatch(
- rprojroot::find_root(rprojroot::is_r_package,
- path = dependency$src$file),
+ !is.null(tryCatch(
+ rprojroot::find_root(
+ rprojroot::is_r_package,
+ path = dependency$src$file
+ ),
error = function(e) NULL
))
}
@@ -1259,7 +1405,6 @@ render_exercise_prepare.default <- function(exercise, ...) {
}
exercise$chunks <- lapply(exercise[["chunks"]], function(chunk) {
-
if (identical(chunk[["label"]], exercise[["label"]])) {
# Exercise Chunk ----
chunk[["opts"]] <- discard_forced_opts(chunk[["opts"]])
@@ -1269,8 +1414,13 @@ render_exercise_prepare.default <- function(exercise, ...) {
inherited = I(exercise[["opts_chunk"]])
)
# keep only unique options that we over-rode when prepping specific ex type (e.g. sql)
- different_ex_opt <- function(opt, name) !identical(opt, exercise[["opts_chunk"]][[name]])
- chunk[["opts"]] <- chunk[["opts"]][imap_lgl(chunk[["opts"]], different_ex_opt)]
+ different_ex_opt <- function(opt, name) {
+ !identical(opt, exercise[["opts_chunk"]][[name]])
+ }
+ chunk[["opts"]] <- chunk[["opts"]][imap_lgl(
+ chunk[["opts"]],
+ different_ex_opt
+ )]
# move user submission code into the exercise chunk
chunk[["code"]] <- exercise[["code"]]
} else {
@@ -1341,7 +1491,7 @@ merge_chunk_options <- function(
option_names <- unique(c(names(chunk), names(inherited), names(forced)))
opts <- lapply(option_names, function(option_name) {
# first we want manually set options, then user's, then exercise
- forced[[option_name]] %||%
+ forced[[option_name]] %||%
chunk[[option_name]] %||%
inherited[[option_name]]
})
@@ -1376,7 +1526,12 @@ render_exercise_rmd_user <- function(exercise, ...) {
#' @export
render_exercise_rmd_user.default <- function(exercise, ...) {
c(
- readLines(system.file("internals", "templates", "exercise-setup.Rmd", package = "learnr")),
+ readLines(system.file(
+ "internals",
+ "templates",
+ "exercise-setup.Rmd",
+ package = "learnr"
+ )),
"",
exercise_code_chunks_user(exercise)
)
@@ -1429,7 +1584,12 @@ render_exercise_post_stage_hook.default <- function(exercise, ...) {
}
#' @export
-render_exercise_post_stage_hook.python <- function(exercise, stage, envir, ...) {
+render_exercise_post_stage_hook.python <- function(
+ exercise,
+ stage,
+ envir,
+ ...
+) {
# Add copy of python environment into the prep/result environment
assign(".__py__", py_copy_global_env(), envir = envir)
invisible()
@@ -1482,7 +1642,11 @@ render_exercise_result.sql <- function(
if (exists("___sql_result", envir = envir_result)) {
if (!is.null(exercise[["options"]][["output.var"]])) {
# the author expected the sql results in a specific variable
- assign(exercise[["options"]][["output.var"]], last_value, envir = envir_result)
+ assign(
+ exercise[["options"]][["output.var"]],
+ last_value,
+ envir = envir_result
+ )
}
rm("___sql_result", envir = envir_result)
}
@@ -1539,15 +1703,15 @@ local_restore_envvars <- function(.local_envir = parent.frame()) {
}
restore_options <- function(old) {
- current <- options()
- nulls <- setdiff(names(current), names(old))
+ current <- options()
+ nulls <- setdiff(names(current), names(old))
old[nulls] <- list(NULL)
options(old)
}
restore_envvars <- function(old) {
current <- Sys.getenv()
- nulls <- setdiff(names(current), names(old))
+ nulls <- setdiff(names(current), names(old))
Sys.unsetenv(nulls)
do.call(Sys.setenv, as.list(old))
}
@@ -1555,11 +1719,19 @@ restore_envvars <- function(old) {
# Print Methods -----------------------------------------------------------
#' @export
-format.tutorial_exercise <- function (x, ..., setup_chunk_only = FALSE) {
+format.tutorial_exercise <- function(x, ..., setup_chunk_only = FALSE) {
label <- x$label
if (!isTRUE(setup_chunk_only)) {
- for (chunk in c("solution", "code_check", "check", "error_check", "tests")) {
- if (is.null(x[[chunk]]) || !nzchar(x[[chunk]])) next
+ for (chunk in c(
+ "solution",
+ "code_check",
+ "check",
+ "error_check",
+ "tests"
+ )) {
+ if (is.null(x[[chunk]]) || !nzchar(x[[chunk]])) {
+ next
+ }
support_chunk <- mock_chunk(
label = paste0(label, "-", sub("_", "-", chunk)),
code = x[[chunk]],
diff --git a/R/feedback.R b/R/feedback.R
index 6849cea5d..003acdd94 100644
--- a/R/feedback.R
+++ b/R/feedback.R
@@ -13,27 +13,47 @@ feedback_validated <- function(feedback) {
if (!length(feedback)) {
return(feedback)
}
- if (!(is.list(feedback) && all(c("message", "correct") %in% names(feedback)))) {
- stop("Feedback must be a list with 'message' and 'correct' fields", call. = FALSE)
+ if (
+ !(is.list(feedback) && all(c("message", "correct") %in% names(feedback)))
+ ) {
+ stop(
+ "Feedback must be a list with 'message' and 'correct' fields",
+ call. = FALSE
+ )
}
- if (!(is.character(feedback$message) || inherits(feedback$message, c("shiny.tag", "shiny.tag.list")))) {
- stop("The 'message' field of feedback must be a character vector or an htmltools tag or tagList", call. = FALSE)
+ if (
+ !(is.character(feedback$message) ||
+ inherits(feedback$message, c("shiny.tag", "shiny.tag.list")))
+ ) {
+ stop(
+ "The 'message' field of feedback must be a character vector or an htmltools tag or tagList",
+ call. = FALSE
+ )
}
if (!is.logical(feedback$correct)) {
- stop("The 'correct' field of feedback must be a logical (i.e., boolean) value", call. = FALSE)
+ stop(
+ "The 'correct' field of feedback must be a logical (i.e., boolean) value",
+ call. = FALSE
+ )
}
# Fill in type/location defaults and check their value
feedback$type <- feedback$type[1] %||% "auto"
feedback$location <- feedback$location[1] %||% "append"
feedback_types <- c("auto", "success", "info", "warning", "error", "custom")
if (!feedback$type %in% feedback_types) {
- stop("Feedback 'type' field must be one of these values: ",
- paste(feedback_types, collapse = ", "), call. = FALSE)
+ stop(
+ "Feedback 'type' field must be one of these values: ",
+ paste(feedback_types, collapse = ", "),
+ call. = FALSE
+ )
}
feedback_locations <- c("append", "prepend", "replace")
if (!feedback$location %in% feedback_locations) {
- stop("Feedback 'location' field must be one of these values: ",
- paste(feedback_locations, collapse = ", "), call. = FALSE)
+ stop(
+ "Feedback 'location' field must be one of these values: ",
+ paste(feedback_locations, collapse = ", "),
+ call. = FALSE
+ )
}
if (feedback$type %in% "auto") {
feedback$type <- if (feedback$correct) "success" else "error"
diff --git a/R/html-dependencies.R b/R/html-dependencies.R
index e32cb9c8c..d0db1c440 100644
--- a/R/html-dependencies.R
+++ b/R/html-dependencies.R
@@ -1,4 +1,3 @@
-
#' Tutorial HTML dependency
#'
#' HTML dependency for core tutorial JS and CSS. This should be included as a
@@ -25,8 +24,7 @@ html_dependency_src <- function(...) {
r_dir <- utils::getSrcDirectory(html_dependency_src, unique = TRUE)
pkg_dir <- dirname(r_dir)
file.path(pkg_dir, "inst", ...)
- }
- else {
+ } else {
system.file(..., package = "learnr")
}
}
diff --git a/R/html_selector.R b/R/html_selector.R
index c86f8f55d..90f4785e0 100644
--- a/R/html_selector.R
+++ b/R/html_selector.R
@@ -1,7 +1,9 @@
-
# only handles id and classes
as_selector <- function(selector) {
- if (inherits(selector, "shiny_selector") || inherits(selector, "shiny_selector_list")) {
+ if (
+ inherits(selector, "shiny_selector") ||
+ inherits(selector, "shiny_selector_list")
+ ) {
return(selector)
}
@@ -30,12 +32,15 @@ as_selector <- function(selector) {
classes <- str_remove(str_match_all(selector, "\\.([^.]+)"), "^\\.")
- structure(class = "shiny_selector", list(
- element = element,
- id = id,
- classes = classes,
- match_everything = match_everything
- ))
+ structure(
+ class = "shiny_selector",
+ list(
+ element = element,
+ id = id,
+ classes = classes,
+ match_everything = match_everything
+ )
+ )
}
as_selector_list <- function(selector) {
@@ -51,7 +56,11 @@ format.shiny_selector <- function(x, ...) {
if (x$match_everything) {
paste0("* // match everything")
} else {
- paste0(x$element, if (!is.null(x$id)) paste0("#", x$id), paste0(".", x$classes, collapse = ""))
+ paste0(
+ x$element,
+ if (!is.null(x$id)) paste0("#", x$id),
+ paste0(".", x$classes, collapse = "")
+ )
}
}
diff --git a/R/http-handlers.R b/R/http-handlers.R
index bb9ff9eae..e2e1be307 100644
--- a/R/http-handlers.R
+++ b/R/http-handlers.R
@@ -1,5 +1,3 @@
-
-
register_http_handlers <- function(session, metadata) {
session$userData$learnr_state <- reactiveVal("start")
@@ -11,8 +9,7 @@ register_http_handlers <- function(session, metadata) {
state <- new.env(parent = emptyenv())
# initialize handler
- session$registerDataObj("initialize", NULL, function(data, req) {
-
+ session$registerDataObj("initialize", NULL, function(data, req) {
# parameters
location <- json_rpc_input(req)$location
@@ -39,52 +36,62 @@ register_http_handlers <- function(session, metadata) {
})
# restore state handler
- session$registerDataObj("restore_state", NULL, rpc_handler(function(input) {
-
- # forward any client stored objects into our own storage
- if (!is.null(input))
- initialize_objects_from_client(session, input)
-
- # get state objects
- state_objects <- get_all_state_objects(session, exercise_output = FALSE)
-
- # create submissions from state objects
- submissions <- submissions_from_state_objects(state_objects)
-
- # create video progress from state objects
- video_progress <- video_progress_from_state_objects(state_objects)
-
- # create progress events from state objects
- progress_events <- progress_events_from_state_objects(state_objects)
-
- # get client state
- client_state <- get_client_state(session)
-
- session$userData$learnr_state("restored")
-
- # return data
- list(
- client_state = client_state,
- submissions = submissions,
- video_progress = video_progress,
- progress_events = progress_events
- )
- }))
+ session$registerDataObj(
+ "restore_state",
+ NULL,
+ rpc_handler(function(input) {
+ # forward any client stored objects into our own storage
+ if (!is.null(input)) {
+ initialize_objects_from_client(session, input)
+ }
+
+ # get state objects
+ state_objects <- get_all_state_objects(session, exercise_output = FALSE)
+
+ # create submissions from state objects
+ submissions <- submissions_from_state_objects(state_objects)
+
+ # create video progress from state objects
+ video_progress <- video_progress_from_state_objects(state_objects)
+
+ # create progress events from state objects
+ progress_events <- progress_events_from_state_objects(state_objects)
+
+ # get client state
+ client_state <- get_client_state(session)
+
+ session$userData$learnr_state("restored")
+
+ # return data
+ list(
+ client_state = client_state,
+ submissions = submissions,
+ video_progress = video_progress,
+ progress_events = progress_events
+ )
+ })
+ )
# remove state handler
- session$registerDataObj("remove_state", NULL, rpc_handler(function(input) {
- remove_all_objects(session)
- }))
+ session$registerDataObj(
+ "remove_state",
+ NULL,
+ rpc_handler(function(input) {
+ remove_all_objects(session)
+ })
+ )
# event recording
- session$registerDataObj("record_event", NULL, rpc_handler(function(input) {
- # Augment the data with the label
- input$data$label <- input$label
+ session$registerDataObj(
+ "record_event",
+ NULL,
+ rpc_handler(function(input) {
+ # Augment the data with the label
+ input$data$label <- input$label
- record_event(session = session,
- event = input$event,
- data = input$data)
- }))
+ record_event(session = session, event = input$event, data = input$data)
+ })
+ )
# # question submission handler
# session$registerDataObj("question_submission", NULL, rpc_handler(function(input) {
@@ -105,85 +112,97 @@ register_http_handlers <- function(session, metadata) {
# }))
# video progress handler
- session$registerDataObj("video_progress", NULL, rpc_handler(function(input) {
-
- # extract inputs
- video_url <- input$video_url
- time <- input$time
- total_time <- input$total_time
-
- # fire event
- event_trigger(
- session,
- "video_progress",
- data = list(
- video_url = video_url,
- time = time,
- total_time = total_time
+ session$registerDataObj(
+ "video_progress",
+ NULL,
+ rpc_handler(function(input) {
+ # extract inputs
+ video_url <- input$video_url
+ time <- input$time
+ total_time <- input$total_time
+
+ # fire event
+ event_trigger(
+ session,
+ "video_progress",
+ data = list(
+ video_url = video_url,
+ time = time,
+ total_time = total_time
+ )
)
- )
- }))
+ })
+ )
# exercise skipped event
- session$registerDataObj("section_skipped", NULL, rpc_handler(function(input) {
-
- # extract inputs
- sectionId <- input$sectionId
-
- # fire event
- event_trigger(
- session,
- "section_skipped",
- data = list(sectionId = sectionId)
- )
-
- }))
+ session$registerDataObj(
+ "section_skipped",
+ NULL,
+ rpc_handler(function(input) {
+ # extract inputs
+ sectionId <- input$sectionId
+
+ # fire event
+ event_trigger(
+ session,
+ "section_skipped",
+ data = list(sectionId = sectionId)
+ )
+ })
+ )
# client state handler
- session$registerDataObj("set_client_state", NULL, rpc_handler(function(input) {
- save_client_state(session, input)
- }))
+ session$registerDataObj(
+ "set_client_state",
+ NULL,
+ rpc_handler(function(input) {
+ save_client_state(session, input)
+ })
+ )
# help handler
- session$registerDataObj("help", NULL, rpc_handler(function(input) {
-
- }))
+ session$registerDataObj("help", NULL, rpc_handler(function(input) {}))
# setup chunk handler
- session$registerDataObj("initialize_chunk", NULL, rpc_handler(function(input) {
- params <- input
-
- # evaluate setup code to prime environment
- label <- as.character(params$label)
- code <- paste(params$setup_code, collapse = "\n")
-
- # no setup chunk / label? nothing to do
- if (!(nzchar(label) && nzchar(code)))
- return()
-
- # evaluate code in environment to prime
- Encoding(code) <- "UTF-8"
- state[[label]] <- new.env()
- eval(parse(text = code, encoding = "UTF-8"), envir = state[[label]])
-
- }))
+ session$registerDataObj(
+ "initialize_chunk",
+ NULL,
+ rpc_handler(function(input) {
+ params <- input
+
+ # evaluate setup code to prime environment
+ label <- as.character(params$label)
+ code <- paste(params$setup_code, collapse = "\n")
+
+ # no setup chunk / label? nothing to do
+ if (!(nzchar(label) && nzchar(code))) {
+ return()
+ }
+
+ # evaluate code in environment to prime
+ Encoding(code) <- "UTF-8"
+ state[[label]] <- new.env()
+ eval(parse(text = code, encoding = "UTF-8"), envir = state[[label]])
+ })
+ )
# completion handler
- session$registerDataObj("completion", NULL, rpc_handler(function(input) {
-
- # read params
- line <- as.character(input$contents)
- label <- as.character(input$label)
+ session$registerDataObj(
+ "completion",
+ NULL,
+ rpc_handler(function(input) {
+ # read params
+ line <- as.character(input$contents)
+ label <- as.character(input$label)
- Encoding(line) <- "UTF-8"
+ Encoding(line) <- "UTF-8"
- auto_complete_r(line, label, state)
- }))
+ auto_complete_r(line, label, state)
+ })
+ )
# diagnostics handler
- session$registerDataObj("diagnotics", NULL, rpc_handler(function(input) {
-
- }))
+ session$registerDataObj("diagnotics", NULL, rpc_handler(function(input) {}))
# this is a "bat signal" to let the JS side know that the Shiny
# server is ready to handle http requests
@@ -194,9 +213,7 @@ register_http_handlers <- function(session, metadata) {
# return a rook wrapper for a function that takes a list and returns a list
# (list contents are automatically converted to/from JSON for rook as required)
rpc_handler <- function(handler) {
-
function(data, req) {
-
# get the input
input <- json_rpc_input(req)
diff --git a/R/i18n.R b/R/i18n.R
index a366f9f70..160ffb803 100644
--- a/R/i18n.R
+++ b/R/i18n.R
@@ -50,7 +50,9 @@ i18n_process_language_options <- function(language = NULL) {
language[[lng]] <- i18n_validate_customization(language[[lng]])
- if (is.null(language[[lng]])) next
+ if (is.null(language[[lng]])) {
+ next
+ }
custom[[lng]] <- list(custom = language[[lng]])
}
}
@@ -68,7 +70,11 @@ i18n_process_language_options <- function(language = NULL) {
i18n_read_json <- function(path) {
tryCatch(
- jsonlite::read_json(path, simplifyDataFrame = FALSE, simplifyMatrix = FALSE),
+ jsonlite::read_json(
+ path,
+ simplifyDataFrame = FALSE,
+ simplifyMatrix = FALSE
+ ),
error = function(e) {
message("Unable to read custom language JSON file at: ", path)
NULL
@@ -100,7 +106,8 @@ i18n_validate_customization <- function(lng) {
extra_group_keys <- setdiff(names(lng), group_keys)
if (length(extra_group_keys)) {
warning(
- "Ignoring extra customization groups ", paste(extra_group_keys, collapse = ", "),
+ "Ignoring extra customization groups ",
+ paste(extra_group_keys, collapse = ", "),
immediate. = TRUE
)
}
@@ -109,7 +116,9 @@ i18n_validate_customization <- function(lng) {
extra_keys <- setdiff(names(lng[[group]]), names(default[[group]]))
if (length(extra_keys)) {
warning(
- "Ignoring extra ", group, " language customizations: ",
+ "Ignoring extra ",
+ group,
+ " language customizations: ",
paste(extra_keys, collapse = ", "),
immediate. = TRUE
)
@@ -137,10 +146,14 @@ i18n_div <- function(key, ..., opts = NULL) {
}
i18n_combine_words <- function(
- words, and = c("and", "or"), before = "", after = before, oxford_comma = TRUE
+ words,
+ and = c("and", "or"),
+ before = "",
+ after = before,
+ oxford_comma = TRUE
) {
- and <- match.arg(and)
- and <- sprintf(" $t(text.%s) ", and)
+ and <- match.arg(and)
+ and <- sprintf(" $t(text.%s) ", and)
words <- paste0(before, words, after)
n <- length(words)
@@ -151,7 +164,8 @@ i18n_combine_words <- function(
knitr::combine_words(
words,
sep = "$t(text.listcomma) ",
- and = and, oxford_comma = FALSE
+ and = and,
+ oxford_comma = FALSE
)
}
diff --git a/R/identifiers.R b/R/identifiers.R
index 3c031255a..f4218044a 100644
--- a/R/identifiers.R
+++ b/R/identifiers.R
@@ -1,23 +1,25 @@
-
initialize_session_state <- function(session, metadata, location, request) {
-
# helper to read rook headers
as_rook_header <- function(name) {
- if (!is.null(name))
+ if (!is.null(name)) {
paste0("HTTP_", toupper(gsub("-", "_", name, fixed = TRUE)))
- else
+ } else {
NULL
+ }
}
# function to initialize an identifier (read from http header or take default)
initialize_identifier <- function(identifier, default) {
-
# determine whether a custom header provides the value (fallback to default)
- header <- as_rook_header(getOption(sprintf("tutorial.http_header_%s", identifier)))
- if (!is.null(header) && exists(header, envir = request))
+ header <- as_rook_header(getOption(sprintf(
+ "tutorial.http_header_%s",
+ identifier
+ )))
+ if (!is.null(header) && exists(header, envir = request)) {
value <- get(header, envir = request)
- else
+ } else {
value <- default
+ }
# write it into the request for reading later on
write_request(session, sprintf("tutorial.%s", identifier), value)
@@ -64,10 +66,16 @@ package_info <- function() {
}
}
-default_tutorial_id <- function(id = NULL, location = NULL, pkg = package_info()) {
+default_tutorial_id <- function(
+ id = NULL,
+ location = NULL,
+ pkg = package_info()
+) {
# determine default tutorial id (metadata first then filesystem-based for
# localhost and remote URL based for other configurations)
- if (!is.null(id)) return(id)
+ if (!is.null(id)) {
+ return(id)
+ }
if (!is_localhost(location)) {
return(paste0(location$host, location$pathname))
@@ -86,7 +94,9 @@ default_tutorial_id <- function(id = NULL, location = NULL, pkg = package_info()
default_tutorial_version <- function(version = NULL, pkg = package_info()) {
# determine default version (if in a package use the package version)
- if (!is.null(version)) return(version)
+ if (!is.null(version)) {
+ return(version)
+ }
if (!is.null(pkg$dir)) {
return(pkg$info$Version)
@@ -107,10 +117,13 @@ default_language <- function() {
read_request <- function(session, name, default = NULL) {
if (!is.null(name)) {
- if (is.environment(session$request) && exists(name, envir = session$request))
+ if (
+ is.environment(session$request) && exists(name, envir = session$request)
+ ) {
get(name, envir = session$request)
- else
+ } else {
default
+ }
} else {
default
}
@@ -121,4 +134,3 @@ write_request <- function(session, name, value) {
session$request[[name]] <- value
do.call("lockBinding", list("request", session))
}
-
diff --git a/R/initialize.R b/R/initialize.R
index b1ecdd9b4..651d664aa 100644
--- a/R/initialize.R
+++ b/R/initialize.R
@@ -1,6 +1,3 @@
-
-
-
#' Initialize tutorial R Markdown extensions
#'
#' One time initialization of R Markdown extensions required by the
@@ -12,11 +9,11 @@
#'
#' @export
initialize_tutorial <- function() {
-
# helper function for one time initialization
- if (isTRUE(getOption("knitr.in.progress")) &&
- !isTRUE(knitr::opts_knit$get("tutorial.initialized"))) {
-
+ if (
+ isTRUE(getOption("knitr.in.progress")) &&
+ !isTRUE(knitr::opts_knit$get("tutorial.initialized"))
+ ) {
# html dependencies
knitr::knit_meta_add(list(
rmarkdown::html_dependency_jquery(),
@@ -29,8 +26,10 @@ initialize_tutorial <- function() {
# session initialization (forward tutorial metadata)
rmarkdown::shiny_prerendered_chunk(
'server',
- sprintf('learnr:::register_http_handlers(session, metadata = %s)',
- dput_to_string(rmarkdown::metadata$tutorial)),
+ sprintf(
+ 'learnr:::register_http_handlers(session, metadata = %s)',
+ dput_to_string(rmarkdown::metadata$tutorial)
+ ),
singleton = TRUE
)
@@ -50,9 +49,11 @@ initialize_tutorial <- function() {
# Register session stop handler
rmarkdown::shiny_prerendered_chunk(
'server',
- sprintf('session$onSessionEnded(function() {
+ sprintf(
+ 'session$onSessionEnded(function() {
learnr:::event_trigger(session, "session_stop")
- })'),
+ })'
+ ),
singleton = TRUE
)
@@ -64,7 +65,9 @@ initialize_tutorial <- function() {
dput_to_string <- function(x) {
conn <- textConnection("dput_to_string", "w")
- on.exit({close(conn)})
+ on.exit({
+ close(conn)
+ })
dput(x, file = conn)
# Must use a `"\n"` if `dput()`ing a function
paste0(textConnectionValue(conn), collapse = "\n")
diff --git a/R/knitr-hooks.R b/R/knitr-hooks.R
index 94a0f121d..3b62e7285 100644
--- a/R/knitr-hooks.R
+++ b/R/knitr-hooks.R
@@ -19,7 +19,7 @@ detect_installed_knitr_hooks <- function() {
tutorial_knitr_options <- function() {
# helper to check for runtime: shiny_prerendered being active
is_shiny_prerendered_active <- function() {
- identical(knitr::opts_knit$get("rmarkdown.runtime"),"shiny_prerendered")
+ identical(knitr::opts_knit$get("rmarkdown.runtime"), "shiny_prerendered")
}
# helper to check for an exercise chunk
@@ -34,7 +34,9 @@ tutorial_knitr_options <- function() {
return(TRUE)
}
- chunk_opt_exercise <- attr(get_knitr_chunk(label), "chunk_opts")[["exercise"]]
+ chunk_opt_exercise <- attr(get_knitr_chunk(label), "chunk_opts")[[
+ "exercise"
+ ]]
if (is.symbol(chunk_opt_exercise)) {
# original chunk options might not be evaluated yet, see #757
chunk_opt_exercise <- eval(chunk_opt_exercise, knitr::knit_global())
@@ -46,7 +48,10 @@ tutorial_knitr_options <- function() {
# is an empty chunk that didn't trigger knitr's duplicate chunk error).
# Note that we can't rely on knit_code$get() or options$code since they
# both report the code for the non-exercise chunk.
- msg <- sprintf("Cannot create exercise '%s': duplicate chunk label", label)
+ msg <- sprintf(
+ "Cannot create exercise '%s': duplicate chunk label",
+ label
+ )
rlang::abort(msg)
}
@@ -117,7 +122,8 @@ tutorial_knitr_options <- function() {
# look for another chunk which names this as its setup chunk or if it has `exercise.setup`
# this second condition is for support chunks that isn't referenced by an exercise yet
# but is part of a chain and should be stored as a setup chunk
- is_referenced <- length(exercise_chunks_for_setup_chunk(options$label)) > 0
+ is_referenced <- length(exercise_chunks_for_setup_chunk(options$label)) >
+ 0
if (is_referenced) {
find_parent_setup_chunks(options) # only used to check for cycles; the return value is not useful here
return(TRUE)
@@ -126,8 +132,16 @@ tutorial_knitr_options <- function() {
# if this looks like a setup chunk, but no one references it, error
if (is.null(options[["exercise"]]) && !is.null(options$exercise.setup)) {
stop(
- "Chunk '", options$label, "' is not being used by any exercise or exercise setup chunk.\n",
- "Please remove chunk '", options$label, "' or reference '", options$label, "' with `exercise.setup = '", options$label, "'`",
+ "Chunk '",
+ options$label,
+ "' is not being used by any exercise or exercise setup chunk.\n",
+ "Please remove chunk '",
+ options$label,
+ "' or reference '",
+ options$label,
+ "' with `exercise.setup = '",
+ options$label,
+ "'`",
call. = FALSE
)
}
@@ -137,7 +151,8 @@ tutorial_knitr_options <- function() {
}
is_exercise_setup_chunk <- function(label) {
- grepl("-setup$", label) || (length(exercise_chunks_for_setup_chunk(label)) > 0)
+ grepl("-setup$", label) ||
+ (length(exercise_chunks_for_setup_chunk(label)) > 0)
}
# helper function to grab the raw knitr chunk associated with a chunk label
@@ -159,22 +174,34 @@ tutorial_knitr_options <- function() {
# it goes up the chain of setup dependencies and returns a list of raw knitr chunks (if any)
find_parent_setup_chunks <- function(options, visited = NULL) {
# base case: when options are null, there are no more setup references
- if (is.null(options))
+ if (is.null(options)) {
return(NULL)
+ }
has_visited <- options$label %in% visited
# update visited set
visited <- append(visited, options$label)
# error out if there is a cycle
if (has_visited) {
- stop("Chained setup chunks form a cycle!\nCycle: ", paste0(visited, collapse = " => "), call. = FALSE)
+ stop(
+ "Chained setup chunks form a cycle!\nCycle: ",
+ paste0(visited, collapse = " => "),
+ call. = FALSE
+ )
}
# check if the chunk with label has another setup chunk associated with it
setup_label <- options$exercise.setup
setup_chunk <- get_knitr_chunk(setup_label)
# if the setup_label is mispelled, throw an error to user instead of silently ignoring
# which would cause other issues when data dependencies can't be found
- if (!is.null(setup_label) && is.null(setup_chunk))
- stop(paste0("exercise.setup label '", setup_label, "' not found for exercise '", options$label, "'"))
+ if (!is.null(setup_label) && is.null(setup_chunk)) {
+ stop(paste0(
+ "exercise.setup label '",
+ setup_label,
+ "' not found for exercise '",
+ options$label,
+ "'"
+ ))
+ }
setup_options <- attr(setup_chunk, "chunk_opts")
# serialize the options here so that the values are not evaluated when retrieved from learnr cache
@@ -191,7 +218,10 @@ tutorial_knitr_options <- function() {
)
}
# recurse
- append(find_parent_setup_chunks(setup_options, visited), current_setup_chunks)
+ append(
+ find_parent_setup_chunks(setup_options, visited),
+ current_setup_chunks
+ )
}
# helper function to return a list of exercise chunk and its setup chunks
@@ -214,13 +244,29 @@ tutorial_knitr_options <- function() {
} else {
NULL
}
- append(setup_chunks, list(list(label = options$label, code = exercise_chunk, opts = chunk_opts, engine = knitr_engine(options$engine))))
+ append(
+ setup_chunks,
+ list(list(
+ label = options$label,
+ code = exercise_chunk,
+ opts = chunk_opts,
+ engine = knitr_engine(options$engine)
+ ))
+ )
}
get_reveal_solution_option <- function(solution_opts) {
- exercise_chunk <- get_knitr_chunk(sub("-solution$", "", solution_opts$label))
+ exercise_chunk <- get_knitr_chunk(sub(
+ "-solution$",
+ "",
+ solution_opts$label
+ ))
if (is.null(exercise_chunk)) {
- stop("Can not find exercise chunk for solution: `", solution_opts$label, "`")
+ stop(
+ "Can not find exercise chunk for solution: `",
+ solution_opts$label,
+ "`"
+ )
}
# these are unevaluated options at this point
@@ -232,9 +278,15 @@ tutorial_knitr_options <- function() {
# Determine if we should reveal the solution using...
reveal_solution <-
# 1. the option explicitly set on the solution chunk
- eval(sol_opts_user$exercise.reveal_solution, envir = knitr::knit_global()) %||%
+ eval(
+ sol_opts_user$exercise.reveal_solution,
+ envir = knitr::knit_global()
+ ) %||%
# 2. the option explicitly set on the exercise chunk
- eval(exercise_opts$exercise.reveal_solution, envir = knitr::knit_global()) %||%
+ eval(
+ exercise_opts$exercise.reveal_solution,
+ envir = knitr::knit_global()
+ ) %||%
# 3. the global knitr chunk option
solution_opts$exercise.reveal_solution %||%
# 4. the global R option
@@ -244,7 +296,7 @@ tutorial_knitr_options <- function() {
}
# hook to turn off evaluation/highlighting for exercise related chunks
- tutorial_opts_hook <- function(options) {
+ tutorial_opts_hook <- function(options) {
# ensure label is an unnamed string (yihui/knitr#2280)
options$label <- unname(options$label)
@@ -254,9 +306,14 @@ tutorial_knitr_options <- function() {
exercise_setup_chunk <- is_exercise_support_chunk(options, type = "setup")
# validate that we have runtime: shiny_prerendered
- if ((exercise_chunk || exercise_support_chunk) && !is_shiny_prerendered_active()) {
- stop("Tutorial exercises require the use of 'runtime: shiny_prerendered'",
- call. = FALSE)
+ if (
+ (exercise_chunk || exercise_support_chunk) &&
+ !is_shiny_prerendered_active()
+ ) {
+ stop(
+ "Tutorial exercises require the use of 'runtime: shiny_prerendered'",
+ call. = FALSE
+ )
}
# validate or ensure that the exercise chunk is 'defined'
@@ -266,7 +323,6 @@ tutorial_knitr_options <- function() {
# if this is an exercise chunk then set various options
if (exercise_chunk) {
-
# one time tutor initialization
initialize_tutorial()
@@ -274,10 +330,11 @@ tutorial_knitr_options <- function() {
options$include <- TRUE
options$highlight <- FALSE
options$comment <- NA
- if (!is.null(options$exercise.eval))
+ if (!is.null(options$exercise.eval)) {
options$eval <- options$exercise.eval
- else
+ } else {
options$eval <- FALSE
+ }
# exercises can be support chunks, but if it's an exercise it should be treated that way
return(options)
}
@@ -291,7 +348,12 @@ tutorial_knitr_options <- function() {
options$highlight <- FALSE
}
- if (is_exercise_support_chunk(options, type = c("code-check", "error-check", "check", "tests"))) {
+ if (
+ is_exercise_support_chunk(
+ options,
+ type = c("code-check", "error-check", "check", "tests")
+ )
+ ) {
# completely suppress behind-the-scenes support chunks
options$include <- FALSE
}
@@ -299,7 +361,9 @@ tutorial_knitr_options <- function() {
if (is_exercise_support_chunk(options, type = "check")) {
if (is.null(knitr::opts_chunk$get("exercise.checker"))) {
stop(
- "An exercise check chunk exists ('", options$label, "') but an ",
+ "An exercise check chunk exists ('",
+ options$label,
+ "') but an ",
"exercise checker function is not configured for this tutorial. ",
"Please use `tutorial_options()` to define an `exercise.checker`."
)
@@ -316,21 +380,30 @@ tutorial_knitr_options <- function() {
if (exercise_setup_chunk) {
# figure out the default behavior
exercise_eval <- knitr::opts_chunk$get('exercise.eval')
- if (is.null(exercise_eval))
+ if (is.null(exercise_eval)) {
exercise_eval <- FALSE
+ }
# look for chunks that name this as their setup chunk
labels <- exercise_chunks_for_setup_chunk(options$label)
- if (grepl("-setup$", options$label))
+ if (grepl("-setup$", options$label)) {
labels <- c(labels, sub("-setup$", "", options$label))
+ }
labels <- paste0('"', labels, '"')
- labels <- paste0('c(', paste(labels, collapse = ', ') ,')')
- label_query <- paste0("knitr::all_labels(label %in% ", labels, ", ",
- "identical(exercise.eval, ", !exercise_eval, "))")
+ labels <- paste0('c(', paste(labels, collapse = ', '), ')')
+ label_query <- paste0(
+ "knitr::all_labels(label %in% ",
+ labels,
+ ", ",
+ "identical(exercise.eval, ",
+ !exercise_eval,
+ "))"
+ )
default_reversed <- length(eval(parse(text = label_query))) > 0
- if (default_reversed)
+ if (default_reversed) {
exercise_eval <- !exercise_eval
+ }
# set the eval property as appropriate
options$eval <- exercise_eval
@@ -343,9 +416,13 @@ tutorial_knitr_options <- function() {
is.null(get_knitr_chunk(sub("-error", "", options$label)))
) {
stop(
- "Exercise '", sub("-error-check", "", options$label), "': ",
+ "Exercise '",
+ sub("-error-check", "", options$label),
+ "': ",
"a *-check chunk is required when using an *-error-check chunk, but",
- " '", sub("-error", "", options$label), "' was not found in the tutorial.",
+ " '",
+ sub("-error", "", options$label),
+ "' was not found in the tutorial.",
call. = FALSE
)
}
@@ -366,38 +443,46 @@ tutorial_knitr_options <- function() {
exercise_wrapper_div <- function(suffix = NULL, extra_html = NULL) {
# before exercise
if (before) {
- if (!is.null(suffix))
+ if (!is.null(suffix)) {
suffix <- paste0("-", suffix)
+ }
class <- paste0("exercise", suffix)
- lines <- ifelse(is.numeric(options$exercise.lines),
- options$exercise.lines, 0)
- completion <- as.numeric(options$exercise.completion %||% 1 > 0)
+ lines <- ifelse(
+ is.numeric(options$exercise.lines),
+ options$exercise.lines,
+ 0
+ )
+ completion <- as.numeric(options$exercise.completion %||% 1 > 0)
diagnostics <- as.numeric(options$exercise.diagnostics %||% 1 > 0)
startover <- as.numeric(options$exercise.startover %||% 1 > 0)
paste0(
- '
____ with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ____ with valid code."
+ )
- ex <- mock_exercise(user_code = '____(____)')
+ ex <- mock_exercise(user_code = '____(____)')
result <- evaluate_exercise(ex, new.env())
expect_equal(result$feedback, exercise_check_code_for_blanks(ex)$feedback)
expect_match(result$feedback$message, ""count":2")
expect_match(result$feedback$message, "This exercise contains 2 blanks.")
- expect_match(result$feedback$message, "Please replace ____ with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ____ with valid code."
+ )
- ex <- mock_exercise(user_code = '____("____")')
+ ex <- mock_exercise(user_code = '____("____")')
result <- evaluate_exercise(ex, new.env())
expect_equal(result$feedback, exercise_check_code_for_blanks(ex)$feedback)
expect_match(result$feedback$message, ""count":2")
expect_match(result$feedback$message, "This exercise contains 2 blanks.")
- expect_match(result$feedback$message, "Please replace ____ with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ____ with valid code."
+ )
})
test_that("setting a different blank for the blank checker", {
skip_if_not_pandoc("1.14")
- ex <- mock_exercise(user_code = '####("test")', exercise.blanks = "###")
+ ex <- mock_exercise(user_code = '####("test")', exercise.blanks = "###")
result <- evaluate_exercise(ex, new.env())
expect_equal(result$feedback, exercise_check_code_for_blanks(ex)$feedback)
expect_match(result$feedback$message, ""count":1")
expect_match(result$feedback$message, "This exercise contains 1 blank.")
- expect_match(result$feedback$message, "Please replace ### with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ### with valid code."
+ )
- ex <- mock_exercise(user_code = '####(####)', exercise.blanks = "###")
+ ex <- mock_exercise(user_code = '####(####)', exercise.blanks = "###")
result <- evaluate_exercise(ex, new.env())
expect_equal(result$feedback, exercise_check_code_for_blanks(ex)$feedback)
expect_match(result$feedback$message, ""count":2")
expect_match(result$feedback$message, "This exercise contains 2 blanks.")
- expect_match(result$feedback$message, "Please replace ### with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ### with valid code."
+ )
- ex <- mock_exercise(user_code = '####("####")', exercise.blanks = "###")
+ ex <- mock_exercise(user_code = '####("####")', exercise.blanks = "###")
result <- evaluate_exercise(ex, new.env())
expect_equal(result$feedback, exercise_check_code_for_blanks(ex)$feedback)
expect_match(result$feedback$message, ""count":2")
expect_match(result$feedback$message, "This exercise contains 2 blanks.")
- expect_match(result$feedback$message, "Please replace ### with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ### with valid code."
+ )
})
test_that("setting a different blank for the blank checker in global setup", {
@@ -1017,7 +1055,7 @@ test_that("setting a different blank for the blank checker in global setup", {
withr::defer(knitr::opts_chunk$set(exercise.blanks = NULL))
ex <- mock_exercise(
- user_code = '####("test")',
+ user_code = '####("test")',
global_setup = 'knitr::opts_chunk$set(exercise.blanks = "###")'
)
result <- evaluate_exercise(ex, new.env(), evaluate_global_setup = TRUE)
@@ -1025,14 +1063,17 @@ test_that("setting a different blank for the blank checker in global setup", {
expect_match(result$feedback$message, ""count":1")
expect_match(result$feedback$message, "This exercise contains 1 blank.")
- expect_match(result$feedback$message, "Please replace ### with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ### with valid code."
+ )
})
test_that("setting a regex blank for the blank checker", {
skip_if_not_pandoc("1.14")
ex <- mock_exercise(
- user_code = '..function..("..string..")',
+ user_code = '..function..("..string..")',
exercise.blanks = "\\.\\.\\S+?\\.\\."
)
result <- evaluate_exercise(ex, new.env(), evaluate_global_setup = TRUE)
@@ -1040,49 +1081,63 @@ test_that("setting a regex blank for the blank checker", {
expect_match(result$feedback$message, ""count":2")
expect_match(result$feedback$message, "This exercise contains 2 blanks.")
- expect_match(result$feedback$message, "Please replace ..function.. and ..string.. with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ..function.. and ..string.. with valid code."
+ )
})
test_that("use underscores as blanks if exercise.blanks is TRUE", {
skip_if_not_pandoc("1.14")
ex <- mock_exercise(
- user_code = 'print("____")', exercise.blanks = TRUE
+ user_code = 'print("____")',
+ exercise.blanks = TRUE
)
result <- evaluate_exercise(ex, new.env())
expect_equal(result$feedback, exercise_check_code_for_blanks(ex)$feedback)
expect_match(result$feedback$message, ""count":1")
expect_match(result$feedback$message, "This exercise contains 1 blank.")
- expect_match(result$feedback$message, "Please replace ____ with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ____ with valid code."
+ )
ex <- mock_exercise(
- user_code = '____("test")', exercise.blanks = TRUE
+ user_code = '____("test")',
+ exercise.blanks = TRUE
)
result <- evaluate_exercise(ex, new.env())
expect_equal(result$feedback, exercise_check_code_for_blanks(ex)$feedback)
expect_match(result$feedback$message, ""count":1")
expect_match(result$feedback$message, "This exercise contains 1 blank.")
- expect_match(result$feedback$message, "Please replace ____ with valid code.")
+ expect_match(
+ result$feedback$message,
+ "Please replace ____ with valid code."
+ )
})
test_that("default message if exercise.blanks is FALSE", {
skip_if_not_pandoc("1.14")
ex <- mock_exercise(
- user_code = 'print("____")', exercise.blanks = FALSE
+ user_code = 'print("____")',
+ exercise.blanks = FALSE
)
result <- evaluate_exercise(ex, new.env())
expect_null(result$feedback$message)
expect_null(exercise_check_code_for_blanks(ex))
ex <- mock_exercise(
- user_code = '____("test")', exercise.blanks = FALSE
+ user_code = '____("test")',
+ exercise.blanks = FALSE
)
result <- evaluate_exercise(ex, new.env())
expect_null(exercise_check_code_for_blanks(ex))
expect_match(result$feedback$message, "text.unparsable")
expect_match(
- result$feedback$message, i18n_translations()$en$translation$text$unparsable,
+ result$feedback$message,
+ i18n_translations()$en$translation$text$unparsable,
fixed = TRUE
)
expect_equal(result$feedback, exercise_check_code_is_parsable(ex)$feedback)
@@ -1099,7 +1154,8 @@ test_that("evaluate_exercise() returns a message if code is unparsable", {
expect_equal(result$feedback, exercise_check_code_is_parsable(ex)$feedback)
expect_match(result$feedback$message, "text.unparsable")
expect_match(
- result$feedback$message, i18n_translations()$en$translation$text$unparsable,
+ result$feedback$message,
+ i18n_translations()$en$translation$text$unparsable,
fixed = TRUE
)
expect_match(result$error_message, "unexpected end of input")
@@ -1109,7 +1165,8 @@ test_that("evaluate_exercise() returns a message if code is unparsable", {
expect_equal(result$feedback, exercise_check_code_is_parsable(ex)$feedback)
expect_match(result$feedback$message, "text.unparsable")
expect_match(
- result$feedback$message, i18n_translations()$en$translation$text$unparsable,
+ result$feedback$message,
+ i18n_translations()$en$translation$text$unparsable,
fixed = TRUE
)
expect_match(result$error_message, "unexpected INCOMPLETE_STRING")
@@ -1119,7 +1176,8 @@ test_that("evaluate_exercise() returns a message if code is unparsable", {
expect_equal(result$feedback, exercise_check_code_is_parsable(ex)$feedback)
expect_match(result$feedback$message, "text.unparsable")
expect_match(
- result$feedback$message, i18n_translations()$en$translation$text$unparsable,
+ result$feedback$message,
+ i18n_translations()$en$translation$text$unparsable,
fixed = TRUE
)
expect_match(result$error_message, "unexpected symbol")
@@ -1148,7 +1206,10 @@ test_that("exericse_check_code_is_parsable() gives error checker a 'parse_error'
ex <- mock_exercise(user_code = 'print("test"', error_check = I("last_value"))
result <- evaluate_exercise(ex, new.env())
- expect_s3_class(result$feedback$checker_result, class = c("parse_error", "condition"))
+ expect_s3_class(
+ result$feedback$checker_result,
+ class = c("parse_error", "condition")
+ )
})
test_that("Errors with global setup code result in an internal error", {
@@ -1161,7 +1222,10 @@ test_that("Errors with global setup code result in an internal error", {
)
expect_null(res$error_message)
- expect_match(res$feedback$message, "internal error occurred while setting up the tutorial")
+ expect_match(
+ res$feedback$message,
+ "internal error occurred while setting up the tutorial"
+ )
expect_s3_class(res$feedback$error, "error")
expect_match(conditionMessage(res$feedback$error), "boom")
})
@@ -1224,8 +1288,7 @@ test_that("evaluate_exercise() does not return a message for parsable non-ASCII
# Non-ASCII variable name
ex <- mock_exercise(
- user_code =
- '\u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae <- "What?"'
+ user_code = '\u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae <- "What?"'
)
result <- evaluate_exercise(ex, new.env())
expect_null(result$feedback)
@@ -1260,7 +1323,6 @@ test_that("Exercise timelimit error is returned when exercise takes too long", {
})
-
# Sensitive env vars and options are masked from user -----------------------
test_that("Shiny session is diabled", {
@@ -1280,14 +1342,16 @@ test_that("Shiny session is diabled", {
test_that("Sensitive env vars and options are masked", {
skip_if_not_pandoc("1.14")
- ex <- mock_exercise(user_code = paste(
- "list(",
- " Sys.getenv('CONNECT_API_KEY', 'USER_LOCAL_CONNECT_API_KEY'),",
- " Sys.getenv('CONNECT_SERVER', 'USER_LOCAL_CONNECT_SERVER'),",
- " getOption('shiny.sharedSecret', 'USER_LOCAL_sharedSecret')",
- ")",
- sep = "\n"
- ))
+ ex <- mock_exercise(
+ user_code = paste(
+ "list(",
+ " Sys.getenv('CONNECT_API_KEY', 'USER_LOCAL_CONNECT_API_KEY'),",
+ " Sys.getenv('CONNECT_SERVER', 'USER_LOCAL_CONNECT_SERVER'),",
+ " getOption('shiny.sharedSecret', 'USER_LOCAL_sharedSecret')",
+ ")",
+ sep = "\n"
+ )
+ )
env_connect <- list(
CONNECT_API_KEY = "T_CONNECT_API_KEY",
@@ -1374,7 +1438,9 @@ test_that("SQL exercises - without explicit `output.var`", {
res <- res_sql_engine$feedback$checker_args
# snapshots
- expect_snapshot(writeLines(render_exercise_rmd_user(render_exercise_prepare(ex_sql_engine))))
+ expect_snapshot(writeLines(render_exercise_rmd_user(render_exercise_prepare(
+ ex_sql_engine
+ ))))
# connection exists in envir_prep
expect_true(exists("db_con", res$envir_prep, inherits = FALSE))
@@ -1423,7 +1489,9 @@ test_that("SQL exercises - with explicit `output.var`", {
res <- res_sql_engine$feedback$checker_args
# snapshots
- expect_snapshot(writeLines(render_exercise_rmd_user(render_exercise_prepare(ex_sql_engine))))
+ expect_snapshot(writeLines(render_exercise_rmd_user(render_exercise_prepare(
+ ex_sql_engine
+ ))))
# connection exists in envir_prep
expect_true(exists("db_con", res$envir_prep, inherits = FALSE))
@@ -1462,9 +1530,16 @@ test_that("Python exercises - simple example", {
# envir_prep and envir_result should be different objects
envir_prep_py <- get0(".__py__", envir = res$envir_prep, ifnotfound = NULL)
- envir_result_py <- get0(".__py__", envir = res$envir_result, ifnotfound = NULL)
+ envir_result_py <- get0(
+ ".__py__",
+ envir = res$envir_result,
+ ifnotfound = NULL
+ )
expect_false(
- identical(reticulate::py_id(envir_prep_py), reticulate::py_id(envir_result_py))
+ identical(
+ reticulate::py_id(envir_prep_py),
+ reticulate::py_id(envir_result_py)
+ )
)
})
@@ -1484,7 +1559,10 @@ test_that("Python exercises - assignment example", {
res <- withr::with_tempdir(render_exercise(ex_py, new.env()))
# TODO: invisible values should be more explicit
- expect_equal(reticulate::py_to_r(res$last_value), "__reticulate_placeholder__")
+ expect_equal(
+ reticulate::py_to_r(res$last_value),
+ "__reticulate_placeholder__"
+ )
expect_null(res$evaluate_result)
expect_true(exists('.__py__', res$envir_prep))
expect_true(exists('.__py__', res$envir_result))
@@ -1492,9 +1570,16 @@ test_that("Python exercises - assignment example", {
expect_equal(result$x, 6)
envir_prep_py <- get0(".__py__", envir = res$envir_prep, ifnotfound = NULL)
- envir_result_py <- get0(".__py__", envir = res$envir_result, ifnotfound = NULL)
+ envir_result_py <- get0(
+ ".__py__",
+ envir = res$envir_result,
+ ifnotfound = NULL
+ )
expect_false(
- identical(reticulate::py_id(envir_prep_py), reticulate::py_id(envir_result_py))
+ identical(
+ reticulate::py_id(envir_prep_py),
+ reticulate::py_id(envir_result_py)
+ )
)
})
diff --git a/tests/testthat/test-feedback.R b/tests/testthat/test-feedback.R
index 683a6faa7..7e66d79c2 100644
--- a/tests/testthat/test-feedback.R
+++ b/tests/testthat/test-feedback.R
@@ -21,7 +21,9 @@ test_that("feedback message must be character or tag or tagList", {
expect_silent(feedback_validated(fdbck("good")))
expect_silent(feedback_validated(fdbck(htmltools::HTML("good"))))
expect_silent(feedback_validated(fdbck(htmltools::p("good"))))
- expect_silent(feedback_validated(fdbck(htmltools::tagList(htmltools::p("good")))))
+ expect_silent(feedback_validated(fdbck(htmltools::tagList(htmltools::p(
+ "good"
+ )))))
})
test_that("feedback type must be one of the acceptable values", {
@@ -29,12 +31,18 @@ test_that("feedback type must be one of the acceptable values", {
expect_equal(feedback_validated(fdbck(correct = TRUE))$type, "success")
expect_equal(feedback_validated(fdbck(correct = FALSE))$type, "error")
- expect_equal(feedback_validated(fdbck(type = c("info", "error")))$type, "info")
+ expect_equal(
+ feedback_validated(fdbck(type = c("info", "error")))$type,
+ "info"
+ )
})
test_that("feedback location must be one of the acceptable values", {
expect_error(feedback_validated(fdbck(location = "--bad--")), "location")
expect_equal(feedback_validated(fdbck())$location, "append")
- expect_equal(feedback_validated(fdbck(location = c("replace", "prepend")))$location, "replace")
+ expect_equal(
+ feedback_validated(fdbck(location = c("replace", "prepend")))$location,
+ "replace"
+ )
})
diff --git a/tests/testthat/test-i18n.R b/tests/testthat/test-i18n.R
index fba314f96..bbc40e5df 100644
--- a/tests/testthat/test-i18n.R
+++ b/tests/testthat/test-i18n.R
@@ -55,7 +55,7 @@ test_that("i18n_process_language_options() multiple customizations", {
),
en = list(
button = list(runcode = "EN run"),
- text= list(areyousure = "EN sure")
+ text = list(areyousure = "EN sure")
)
))
@@ -169,7 +169,9 @@ test_that("i18n_process_language_options() warns if a language is not a single c
test_that("i18n_process_language_options() warns unexpected keys are present", {
expect_warning(
- i18n_process_language_options(list(en = list(foo = list(), button = list()))),
+ i18n_process_language_options(list(
+ en = list(foo = list(), button = list())
+ )),
"foo"
)
@@ -200,7 +202,11 @@ test_that("i18n_span() returns an i18n span", {
expect_s3_class(span, "character")
expect_match(span, 'data-i18n="KEY"')
expect_match(span, ">DEFAULT")
- expect_match(span, 'data-i18n-opts="{"interp":"STRING"}"', fixed = TRUE)
+ expect_match(
+ span,
+ 'data-i18n-opts="{"interp":"STRING"}"',
+ fixed = TRUE
+ )
})
test_that("i18n_set_language_option() changes message language", {
@@ -253,14 +259,20 @@ test_that("i18n_set_language_option() sets up language inheritance", {
ex <- mock_exercise(user_code = "mean$x")
ex$tutorial$language <- "pt"
result <- evaluate_exercise(ex, new.env())
- expect_equal(result$error_message, "objeto de tipo 'closure' não possível dividir em subconjuntos")
+ expect_equal(
+ result$error_message,
+ "objeto de tipo 'closure' não possível dividir em subconjuntos"
+ )
ex <- mock_exercise(
user_code = "mean$x",
global_setup = "i18n_set_language_option('pt')"
)
result <- evaluate_exercise(ex, new.env(), evaluate_global_setup = TRUE)
- expect_equal(result$error_message, "objeto de tipo 'closure' não possível dividir em subconjuntos")
+ expect_equal(
+ result$error_message,
+ "objeto de tipo 'closure' não possível dividir em subconjuntos"
+ )
ex <- mock_exercise(
user_code = c(
diff --git a/tests/testthat/test-install-dependencies.R b/tests/testthat/test-install-dependencies.R
index b8f81c42b..104d96eae 100644
--- a/tests/testthat/test-install-dependencies.R
+++ b/tests/testthat/test-install-dependencies.R
@@ -3,9 +3,11 @@ context("install tutorial dependencies")
create_test_tutorial <- function(code) {
tutorial_dir <- file.path(tempdir(), "tutorial-deps")
dir.create(tutorial_dir)
- tutorial_path <- tempfile("tutorial-deps",
- tmpdir = tutorial_dir,
- fileext = ".R")
+ tutorial_path <- tempfile(
+ "tutorial-deps",
+ tmpdir = tutorial_dir,
+ fileext = ".R"
+ )
writeLines(code, con = tutorial_path)
invisible(tutorial_dir)
}
@@ -29,20 +31,6 @@ test_that("tutorial dependency check returns NULL for no dependencies", {
expect_silent(install_tutorial_dependencies(tutorial_dir))
})
-# test_that("tutorial dependency check works (interactive)", {
-# skip_if_not(interactive())
-#
-# tutorial_dir <- create_test_tutorial("library(pkg1)\npkg2::n()")
-# on.exit(unlink(tutorial_dir, recursive = TRUE), add = TRUE)
-#
-# expect_error(
-# with_mock(
-# ask_pkgs_install = function(x) 2,
-# install_tutorial_dependencies(tutorial_dir)
-# )
-# )
-# })
-
test_that("tutorial dependency check works (not interactive)", {
skip_if(interactive())
diff --git a/tests/testthat/test-knitr-hooks.R b/tests/testthat/test-knitr-hooks.R
index efa4ecb23..563b5c777 100644
--- a/tests/testthat/test-knitr-hooks.R
+++ b/tests/testthat/test-knitr-hooks.R
@@ -18,13 +18,21 @@ test_that("*-error-check chunks require *-check chunks", {
on.exit(unlink(tmpfile))
expect_error(
- rmarkdown::render(test_path("setup-chunks", "error-check-chunk_bad.Rmd"), output_file = tmpfile, quiet = TRUE),
+ rmarkdown::render(
+ test_path("setup-chunks", "error-check-chunk_bad.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ ),
"ex-check",
fixed = TRUE
)
expect_silent(
- rmarkdown::render(test_path("setup-chunks", "error-check-chunk_good.Rmd"), output_file = tmpfile, quiet = TRUE)
+ rmarkdown::render(
+ test_path("setup-chunks", "error-check-chunk_good.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ )
)
})
@@ -35,37 +43,65 @@ test_that("Detection of chained setup cycle works", {
on.exit(unlink(tmpfile))
expect_error(
- rmarkdown::render(test_path("setup-chunks", "setup-cycle.Rmd"), output_file = tmpfile, quiet = TRUE),
+ rmarkdown::render(
+ test_path("setup-chunks", "setup-cycle.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ ),
"dataA => dataC => dataB => dataA",
fixed = TRUE
)
expect_error(
- rmarkdown::render(test_path("setup-chunks", "setup-cycle-self.Rmd"), output_file = tmpfile, quiet = TRUE),
+ rmarkdown::render(
+ test_path("setup-chunks", "setup-cycle-self.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ ),
"dataA => dataA",
fixed = TRUE
)
expect_error(
- rmarkdown::render(test_path("setup-chunks", "setup-cycle-two.Rmd"), output_file = tmpfile, quiet = TRUE),
+ rmarkdown::render(
+ test_path("setup-chunks", "setup-cycle-two.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ ),
"dataA => dataB => dataA",
fixed = TRUE
)
expect_error(
- rmarkdown::render(test_path("setup-chunks", "exercise-cycle-default-setup.Rmd"), output_file = tmpfile, quiet = TRUE),
+ rmarkdown::render(
+ test_path("setup-chunks", "exercise-cycle-default-setup.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ ),
"data1 => data1-setup => data1",
fixed = TRUE
)
expect_error(
- rmarkdown::render(test_path("setup-chunks", "exercise-cycle.Rmd"), output_file = tmpfile, quiet = TRUE),
+ rmarkdown::render(
+ test_path("setup-chunks", "exercise-cycle.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ ),
"data1 => data3 => data2 => data1",
fixed = TRUE
)
expect_error(
- rmarkdown::render(test_path("setup-chunks", "exercise-cycle-self.Rmd"), output_file = tmpfile, quiet = TRUE),
+ rmarkdown::render(
+ test_path("setup-chunks", "exercise-cycle-self.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ ),
"data1 => data1",
fixed = TRUE
)
expect_error(
- rmarkdown::render(test_path("setup-chunks", "exercise-cycle-two.Rmd"), output_file = tmpfile, quiet = TRUE),
+ rmarkdown::render(
+ test_path("setup-chunks", "exercise-cycle-two.Rmd"),
+ output_file = tmpfile,
+ quiet = TRUE
+ ),
"data1 => data2 => data1",
fixed = TRUE
)
@@ -77,8 +113,16 @@ test_that("Empty exercise code still creates an exercise", {
# empty and full exercises are the same, except that "full" has empty lines
# in the exercise chunk. They should result in identical exercises.
- rmd_empty <- test_path("tutorials", "knitr-hooks_empty-exercise", "empty-exercise.Rmd")
- rmd_full <- test_path("tutorials", "knitr-hooks_empty-exercise", "full-exercise.Rmd")
+ rmd_empty <- test_path(
+ "tutorials",
+ "knitr-hooks_empty-exercise",
+ "empty-exercise.Rmd"
+ )
+ rmd_full <- test_path(
+ "tutorials",
+ "knitr-hooks_empty-exercise",
+ "full-exercise.Rmd"
+ )
ex_empty <- get_tutorial_exercises(rmd_empty)
ex_full <- get_tutorial_exercises(rmd_full)
@@ -93,7 +137,11 @@ test_that("Empty exercises with duplicate labels throw an error", {
skip_if_not_pandoc("1.14")
local_edition(3)
- rmd <- test_path("tutorials", "knitr-hooks_empty-exercise", "duplicate-label.Rmd")
+ rmd <- test_path(
+ "tutorials",
+ "knitr-hooks_empty-exercise",
+ "duplicate-label.Rmd"
+ )
expect_error(expect_message(get_tutorial_exercises(rmd), "duplicate"))
})
diff --git a/tests/testthat/test-mock_exercise.R b/tests/testthat/test-mock_exercise.R
index 4dc41b939..5b672f2f9 100644
--- a/tests/testthat/test-mock_exercise.R
+++ b/tests/testthat/test-mock_exercise.R
@@ -9,10 +9,22 @@ test_that("exercise mocks: mock_prep_setup()", {
expect_equal(mock_prep_setup(chunks, "setup-1"), "x <- 1")
# random order
- expect_equal(mock_prep_setup(chunks[3:1], "setup-3"), "x <- 1\ny <- 2\nz <- 3")
- expect_equal(mock_prep_setup(chunks[c(1, 3, 2)], "setup-3"), "x <- 1\ny <- 2\nz <- 3")
- expect_equal(mock_prep_setup(chunks[c(2, 3, 1)], "setup-3"), "x <- 1\ny <- 2\nz <- 3")
- expect_equal(mock_prep_setup(chunks[c(2, 1, 3)], "setup-3"), "x <- 1\ny <- 2\nz <- 3")
+ expect_equal(
+ mock_prep_setup(chunks[3:1], "setup-3"),
+ "x <- 1\ny <- 2\nz <- 3"
+ )
+ expect_equal(
+ mock_prep_setup(chunks[c(1, 3, 2)], "setup-3"),
+ "x <- 1\ny <- 2\nz <- 3"
+ )
+ expect_equal(
+ mock_prep_setup(chunks[c(2, 3, 1)], "setup-3"),
+ "x <- 1\ny <- 2\nz <- 3"
+ )
+ expect_equal(
+ mock_prep_setup(chunks[c(2, 1, 3)], "setup-3"),
+ "x <- 1\ny <- 2\nz <- 3"
+ )
# checks that setup chunk is in chunks
expect_error(mock_prep_setup(chunks, "setup-Z"), "setup-Z")
diff --git a/tests/testthat/test-mutate_tags.R b/tests/testthat/test-mutate_tags.R
index 8638d9ee7..1a3af163e 100644
--- a/tests/testthat/test-mutate_tags.R
+++ b/tests/testthat/test-mutate_tags.R
@@ -1,7 +1,13 @@
has_class <- function(el, .class, ...) {
class_idx <- which(names(el$attribs) == "class")
- if (!length(class_idx)) return(FALSE)
- el_class <- vapply(class_idx, function(i) el$attribs[[i]], FUN.VALUE = character(1))
+ if (!length(class_idx)) {
+ return(FALSE)
+ }
+ el_class <- vapply(
+ class_idx,
+ function(i) el$attribs[[i]],
+ FUN.VALUE = character(1)
+ )
grepl(.class, paste(el_class, collapse = " "), ...)
}
@@ -74,4 +80,4 @@ test_that("finalize_question() works with a shiny.tag.list, too", {
expect_true(has_class(q_ui_final[[1]], "disabled"))
expect_true(has_class(q_ui_final[[2]], "question-final"))
expect_true(has_class(q_ui_final[[2]], "disabled"))
-})
\ No newline at end of file
+})
diff --git a/tests/testthat/test-options-reveal_solution.R b/tests/testthat/test-options-reveal_solution.R
index 03ec56bbc..322a40a7d 100644
--- a/tests/testthat/test-options-reveal_solution.R
+++ b/tests/testthat/test-options-reveal_solution.R
@@ -13,11 +13,14 @@ render_tutorial_with_reveal_solution <- function(opt_string) {
tut_html <- rmarkdown::render(tut_rmd, quiet = TRUE)
# overwrite exit handler to remove all tutorial files
- on.exit({
- rmarkdown::shiny_prerendered_clean(tut_rmd)
- unlink(tut_html)
- unlink(tut_rmd)
- }, add = FALSE)
+ on.exit(
+ {
+ rmarkdown::shiny_prerendered_clean(tut_rmd)
+ unlink(tut_html)
+ unlink(tut_rmd)
+ },
+ add = FALSE
+ )
paste(readLines(tut_html), collapse = "\n")
}
@@ -32,7 +35,9 @@ var_show_solution <- "# SHOWN VAR SOLUTION aba888"
test_that("Solutions are revealed or hidden with tutorial_options()", {
skip_if_not(rmarkdown::pandoc_available())
- ex_show <- render_tutorial_with_reveal_solution("tutorial_options(exercise.reveal_solution = TRUE)")
+ ex_show <- render_tutorial_with_reveal_solution(
+ "tutorial_options(exercise.reveal_solution = TRUE)"
+ )
expect_match(ex_show, default_solution, fixed = TRUE)
expect_failure(expect_match(ex_show, hidden_solution, fixed = TRUE))
expect_match(ex_show, shown_solution, fixed = TRUE)
@@ -40,7 +45,9 @@ test_that("Solutions are revealed or hidden with tutorial_options()", {
expect_failure(expect_match(ex_show, var_hide_solution, fixed = TRUE))
expect_match(ex_show, var_show_solution, fixed = TRUE)
- ex_hide <- render_tutorial_with_reveal_solution("tutorial_options(exercise.reveal_solution = FALSE)")
+ ex_hide <- render_tutorial_with_reveal_solution(
+ "tutorial_options(exercise.reveal_solution = FALSE)"
+ )
expect_failure(expect_match(ex_hide, default_solution, fixed = TRUE))
expect_failure(expect_match(ex_hide, hidden_solution, fixed = TRUE))
expect_match(ex_hide, shown_solution, fixed = TRUE)
@@ -52,7 +59,9 @@ test_that("Solutions are revealed or hidden with tutorial_options()", {
test_that("Solutions are revealed or hidden with global option", {
skip_if_not(rmarkdown::pandoc_available())
- ex_show <- render_tutorial_with_reveal_solution("options(tutorial.exercise.reveal_solution = TRUE)")
+ ex_show <- render_tutorial_with_reveal_solution(
+ "options(tutorial.exercise.reveal_solution = TRUE)"
+ )
expect_match(ex_show, default_solution, fixed = TRUE)
expect_failure(expect_match(ex_show, hidden_solution, fixed = TRUE))
expect_match(ex_show, shown_solution, fixed = TRUE)
@@ -60,7 +69,9 @@ test_that("Solutions are revealed or hidden with global option", {
expect_failure(expect_match(ex_show, var_hide_solution, fixed = TRUE))
expect_match(ex_show, var_show_solution, fixed = TRUE)
- ex_hide <- render_tutorial_with_reveal_solution("options(tutorial.exercise.reveal_solution = FALSE)")
+ ex_hide <- render_tutorial_with_reveal_solution(
+ "options(tutorial.exercise.reveal_solution = FALSE)"
+ )
expect_failure(expect_match(ex_hide, default_solution, fixed = TRUE))
expect_failure(expect_match(ex_hide, hidden_solution, fixed = TRUE))
expect_match(ex_hide, shown_solution, fixed = TRUE)
diff --git a/tests/testthat/test-options.R b/tests/testthat/test-options.R
index 62117cf5c..3c27d7f7f 100644
--- a/tests/testthat/test-options.R
+++ b/tests/testthat/test-options.R
@@ -1,4 +1,3 @@
-
context("options")
test_that("tutor options set knitr options", {
@@ -10,4 +9,3 @@ test_that("tutor options don't set knitr options when excluded from the call", {
tutorial_options(exercise.cap = "Caption")
expect_equal(knitr::opts_chunk$get("exercise.eval"), NULL)
})
-
diff --git a/tests/testthat/test-praise.R b/tests/testthat/test-praise.R
index c7047620a..f89acff6d 100644
--- a/tests/testthat/test-praise.R
+++ b/tests/testthat/test-praise.R
@@ -1,16 +1,25 @@
test_that("random_phrases()", {
expect_error(random_phrases("foo"), "should be one of")
expect_warning(
- expect_equal(random_phrases("praise", "foo"), random_phrases("praise", "en"))
+ expect_equal(
+ random_phrases("praise", "foo"),
+ random_phrases("praise", "en")
+ )
)
knitr::opts_knit$set("tutorial.language" = "en")
expect_equal(random_phrases("praise"), random_phrases("praise", "en"))
- expect_equal(random_phrases("encouragement"), random_phrases("encouragement", "en"))
+ expect_equal(
+ random_phrases("encouragement"),
+ random_phrases("encouragement", "en")
+ )
knitr::opts_knit$set("tutorial.language" = NULL)
expect_equal(random_phrases("praise", "testing"), "RANDOM PRAISE.")
- expect_equal(random_phrases("encouragement", "testing"), "RANDOM ENCOURAGEMENT.")
+ expect_equal(
+ random_phrases("encouragement", "testing"),
+ "RANDOM ENCOURAGEMENT."
+ )
})
test_that("random_phrases_add()", {
@@ -24,7 +33,10 @@ test_that("random_phrases_add()", {
expect_equal(random_phrases("encouragement", "bogus"), c("Go 1", "Go 2"))
random_phrases_add("bogus", encouragement = "Go 3")
- expect_equal(random_phrases("encouragement", "bogus"), c("Go 1", "Go 2", "Go 3"))
+ expect_equal(
+ random_phrases("encouragement", "bogus"),
+ c("Go 1", "Go 2", "Go 3")
+ )
expect_error(random_phrases_add("bogus", list("bad")))
expect_error(random_phrases_add("bogus", 1:4))
diff --git a/tests/testthat/test-question_answers.R b/tests/testthat/test-question_answers.R
index c095ab55d..8a097a1b3 100644
--- a/tests/testthat/test-question_answers.R
+++ b/tests/testthat/test-question_answers.R
@@ -1,5 +1,4 @@
test_that("no message-correct", {
-
q <- question(
"test",
answer("A", correct = TRUE),
@@ -10,18 +9,21 @@ test_that("no message-correct", {
ans <- question_is_correct(q, c("A"))
- out <- question_messages(q, ans$messages, ans$correct, (!isTRUE(q$allow_retry)) || ans$correct)
+ out <- question_messages(
+ q,
+ ans$messages,
+ ans$correct,
+ (!isTRUE(q$allow_retry)) || ans$correct
+ )
expect_s3_class(out, "shiny.tag.list")
expect_equivalent(as.character(out[[1]]$children[[1]]), "test-correct")
expect_true(is.null(out$children[[2]]))
expect_true(is.null(out$children[[3]]))
-
})
test_that("no message-incorrect", {
-
q <- question(
"test",
answer("A", correct = TRUE),
@@ -32,18 +34,21 @@ test_that("no message-incorrect", {
ans <- question_is_correct(q, c("B"))
- out <- question_messages(q, ans$messages, ans$correct, (!isTRUE(q$allow_retry)) || ans$correct)
+ out <- question_messages(
+ q,
+ ans$messages,
+ ans$correct,
+ (!isTRUE(q$allow_retry)) || ans$correct
+ )
expect_s3_class(out, "shiny.tag.list")
expect_equivalent(as.character(out[[1]]$children[[1]]), "test-incorrect")
expect_true(is.null(out$children[[2]]))
expect_true(is.null(out$children[[3]]))
-
})
test_that("all messages-correct", {
-
q <- question(
"test",
answer("A", correct = TRUE, message = "msg **1**"),
@@ -56,18 +61,27 @@ test_that("all messages-correct", {
ans <- question_is_correct(q, c("A"))
- out <- question_messages(q, ans$messages, ans$correct, (!isTRUE(q$allow_retry)) || ans$correct)
+ out <- question_messages(
+ q,
+ ans$messages,
+ ans$correct,
+ (!isTRUE(q$allow_retry)) || ans$correct
+ )
expect_s3_class(out, "shiny.tag.list")
- expect_equivalent(as.character(out[[1]]$children[[1]][[1]][[1]]), "test-correct")
- expect_equivalent(as.character(out[[1]]$children[[1]][[1]][[3]]), "msg 1")
+ expect_equivalent(
+ as.character(out[[1]]$children[[1]][[1]][[1]]),
+ "test-correct"
+ )
+ expect_equivalent(
+ as.character(out[[1]]$children[[1]][[1]][[3]]),
+ "msg 1"
+ )
expect_equivalent(as.character(out[[2]]$children[[1]]), "test-message")
expect_equivalent(as.character(out[[3]]$children[[1]]), "test-post")
-
})
test_that("all messages-incorrect", {
-
q <- question(
"test",
answer("A", correct = TRUE, message = "msg **1**"),
@@ -80,20 +94,28 @@ test_that("all messages-incorrect", {
ans <- question_is_correct(q, c("B"))
- out <- question_messages(q, ans$messages, ans$correct, (!isTRUE(q$allow_retry)) || ans$correct)
+ out <- question_messages(
+ q,
+ ans$messages,
+ ans$correct,
+ (!isTRUE(q$allow_retry)) || ans$correct
+ )
expect_s3_class(out, "shiny.tag.list")
- expect_equivalent(as.character(out[[1]]$children[[1]][[1]][[1]]), "test-incorrect")
- expect_equivalent(as.character(out[[1]]$children[[1]][[1]][[3]]), "msg 2")
+ expect_equivalent(
+ as.character(out[[1]]$children[[1]][[1]][[1]]),
+ "test-incorrect"
+ )
+ expect_equivalent(
+ as.character(out[[1]]$children[[1]][[1]][[3]]),
+ "msg 2"
+ )
expect_equivalent(as.character(out[[2]]$children[[1]]), "test-message")
expect_equivalent(as.character(out[[3]]$children[[1]]), "test-post")
-
})
-
test_that("custom message", {
-
q <- question(
"test",
answer("A", correct = TRUE, message = htmltools::tags$div("_Test_")),
@@ -104,22 +126,34 @@ test_that("custom message", {
ans <- question_is_correct(q, c("A"))
- out <- question_messages(q, ans$messages, ans$correct, (!isTRUE(q$allow_retry)) || ans$correct)
+ out <- question_messages(
+ q,
+ ans$messages,
+ ans$correct,
+ (!isTRUE(q$allow_retry)) || ans$correct
+ )
expect_s3_class(out, "shiny.tag.list")
- expect_equivalent(as.character(out[[1]]$children[[1]][[1]][[1]]), "test-correct")
- expect_equivalent(as.character(out[[1]]$children[[1]][[1]][[3]]$children), "_Test_")
+ expect_equivalent(
+ as.character(out[[1]]$children[[1]][[1]][[1]]),
+ "test-correct"
+ )
+ expect_equivalent(
+ as.character(out[[1]]$children[[1]][[1]][[3]]$children),
+ "_Test_"
+ )
expect_true(is.null(out$children[[2]]))
expect_true(is.null(out$children[[3]]))
-
})
test_that("answer options must have unique values (option)", {
expect_error(
- answer_values(list(answers = list(
- answer("same"),
- answer("same")
- )))
+ answer_values(list(
+ answers = list(
+ answer("same"),
+ answer("same")
+ )
+ ))
)
})
@@ -161,7 +195,7 @@ test_that("answer functions: filtering and splitting", {
answer("apple", TRUE, "correct"),
answer_fn(function(x) "F1", "f1"),
answer("banana", FALSE, "incorrect"),
- answer_fn(~ "F2", "f2"),
+ answer_fn(~"F2", "f2"),
answer("mango", FALSE, "also incorrect")
)
diff --git a/tests/testthat/test-question_checkbox.R b/tests/testthat/test-question_checkbox.R
index 49386ce27..5c88854d7 100644
--- a/tests/testthat/test-question_checkbox.R
+++ b/tests/testthat/test-question_checkbox.R
@@ -1,6 +1,4 @@
-
test_that("question_checkbox() does not include correct messages for incorrect answer", {
-
q <- question_checkbox(
"test",
answer("A", correct = TRUE, message = "msg **1**"),
@@ -36,7 +34,6 @@ test_that("question_checkbox() does not include correct messages for incorrect a
})
test_that("question_checkbox() message depends on whether allow_retry = TRUE", {
-
incorrect_message <- "incorrect"
try_again_message <- "try_again"
diff --git a/tests/testthat/test-question_radio.R b/tests/testthat/test-question_radio.R
index 5b659c12f..0d32b5ac8 100644
--- a/tests/testthat/test-question_radio.R
+++ b/tests/testthat/test-question_radio.R
@@ -2,8 +2,8 @@ test_that("question_radio() throws an error when using only answer_fn() answers"
expect_error(
question_radio(
"test",
- answer_fn(~ "one"),
- answer_fn(~ "two")
+ answer_fn(~"one"),
+ answer_fn(~"two")
)
)
})
@@ -12,7 +12,7 @@ test_that("question_radio() throws an error if it doesn't include a correct answ
expect_error(
question_radio(
"test",
- answer_fn(~ "one"),
+ answer_fn(~"one"),
answer("two", correct = FALSE)
)
)
@@ -29,7 +29,7 @@ test_that("question_radio() warns when using answer_fn() answers", {
expect_warning(
q_fn <- question_radio(
"test",
- answer_fn(~ "one"),
+ answer_fn(~"one"),
answer("two", correct = TRUE)
)
)
diff --git a/tests/testthat/test-question_text.R b/tests/testthat/test-question_text.R
index 5ff8fde12..4b2838c65 100644
--- a/tests/testthat/test-question_text.R
+++ b/tests/testthat/test-question_text.R
@@ -1,15 +1,24 @@
test_that("question text uses textAreaInput if rows or cols are provided", {
ans <- answer("", TRUE)
- q_textArea_rows <- question_ui_initialize(question_text("A", ans, rows = 3), "")
+ q_textArea_rows <- question_ui_initialize(
+ question_text("A", ans, rows = 3),
+ ""
+ )
expect_equal(q_textArea_rows$children[[2]]$name, "textarea")
expect_equal(q_textArea_rows$children[[2]]$attribs$rows, 3)
- q_textArea_cols <- question_ui_initialize(question_text("A", ans, cols = 40), "")
+ q_textArea_cols <- question_ui_initialize(
+ question_text("A", ans, cols = 40),
+ ""
+ )
expect_equal(q_textArea_cols$children[[2]]$name, "textarea")
expect_equal(q_textArea_cols$children[[2]]$attribs$cols, 40)
- q_textArea <- question_ui_initialize(question_text("A", ans, rows = 4, cols = 30), "")
+ q_textArea <- question_ui_initialize(
+ question_text("A", ans, rows = 4, cols = 30),
+ ""
+ )
expect_equal(q_textArea$children[[2]]$name, "textarea")
expect_equal(q_textArea$children[[2]]$attribs$rows, 4)
expect_equal(q_textArea$children[[2]]$attribs$cols, 30)
@@ -101,5 +110,3 @@ test_that("question_text() requires some text input", {
expect_error(question_is_correct(q, ""), "text")
})
})
-
-
diff --git a/tests/testthat/test-quiz.R b/tests/testthat/test-quiz.R
index 4a361e515..f93330bda 100644
--- a/tests/testthat/test-quiz.R
+++ b/tests/testthat/test-quiz.R
@@ -1,4 +1,3 @@
-
# Test quiz() -------------------------------------------------------------
create_question <- function() {
@@ -22,7 +21,6 @@ test_that("quiz questions can be created", {
expect_type(a$correct, "logical")
expect_type(a$message, "NULL")
-
expect_s3_class(q, "learnr_radio")
expect_s3_class(q, "tutorial_question")
@@ -76,7 +74,6 @@ test_that("questions can be aggregated via quiz", {
# Test question() ---------------------------------------------------------
-
test_that("bad ellipses are found", {
expect_silent(
question("title", answer("5", correct = TRUE))
@@ -88,11 +85,17 @@ test_that("bad ellipses are found", {
test_that("loading placeholder is correctly generated for HTML question texts", {
expect_silent(
- q1 <- question(htmltools::tags$p("Did this work?"), answer("yes", correct = TRUE))
+ q1 <- question(
+ htmltools::tags$p("Did this work?"),
+ answer("yes", correct = TRUE)
+ )
)
expect_silent(
- q2 <- question(htmltools::HTML("Did this work?
"), answer("yes", correct = TRUE)) + q2 <- question( + htmltools::HTML("Did this work?
"), + answer("yes", correct = TRUE) + ) ) expect_equal(q1$loading, q2$loading) @@ -102,16 +105,20 @@ test_that("loading placeholder is correctly generated for HTML question texts", 'Does this equal two?1 + 1
-', answer("yes", correct = TRUE)
+',
+ answer("yes", correct = TRUE)
)
)
expect_silent(
question(
- htmltools::HTML('Does this equal two?
+ htmltools::HTML( + 'Does this equal two?
1 + 1
-'), answer("yes", correct = TRUE)
+'
+ ),
+ answer("yes", correct = TRUE)
)
)
@@ -125,13 +132,12 @@ test_that("loading placeholder is correctly generated for HTML question texts",
"```",
sep = "\n"
),
- answer(2, correct =TRUE)
+ answer(2, correct = TRUE)
)
)
})
test_that("question() message depends on whether type is checkbox", {
-
q_radio <- question(
"test",
answer("A", correct = TRUE),
diff --git a/tests/testthat/test-run.R b/tests/testthat/test-run.R
index fb7240cfc..0605d4cb4 100644
--- a/tests/testthat/test-run.R
+++ b/tests/testthat/test-run.R
@@ -19,13 +19,28 @@ test_that("run_tutorial() with bad inputs", {
test_that("validating and finding tutorials", {
# value when nothing is provided
expect_equal(run_validate_tutorial_dir(NULL), list(valid = FALSE, dir = NULL))
- expect_equal(run_validate_tutorial_file(NULL), list(valid = FALSE, dir = NULL))
- expect_equal(run_validate_tutorial_path(NULL), list(valid = FALSE, dir = NULL))
+ expect_equal(
+ run_validate_tutorial_file(NULL),
+ list(valid = FALSE, dir = NULL)
+ )
+ expect_equal(
+ run_validate_tutorial_path(NULL),
+ list(valid = FALSE, dir = NULL)
+ )
# returns name if not an existing file
- expect_equal(run_validate_tutorial_dir("foo"), list(valid = FALSE, dir = "foo"))
- expect_equal(run_validate_tutorial_file("foo"), list(valid = FALSE, dir = "foo"))
- expect_equal(run_validate_tutorial_path("foo"), list(valid = FALSE, dir = "foo"))
+ expect_equal(
+ run_validate_tutorial_dir("foo"),
+ list(valid = FALSE, dir = "foo")
+ )
+ expect_equal(
+ run_validate_tutorial_file("foo"),
+ list(valid = FALSE, dir = "foo")
+ )
+ expect_equal(
+ run_validate_tutorial_path("foo"),
+ list(valid = FALSE, dir = "foo")
+ )
expect_null(run_find_tutorial_rmd("foo"))
tmpdir <- tempfile()
@@ -162,4 +177,3 @@ test_that("safe() executes code expression directly and programmatically", {
safe(cat(!!x, file = !!file))
expect_equal(readLines(file), "3")
})
-
diff --git a/tests/testthat/test-shinytest2-hints.R b/tests/testthat/test-shinytest2-hints.R
index 48d5be8d4..f53c5d8c4 100644
--- a/tests/testthat/test-shinytest2-hints.R
+++ b/tests/testthat/test-shinytest2-hints.R
@@ -33,39 +33,31 @@ describe("sequential hints", {
id <- "one"
it("shows hint when hint button is clicked", {
- app$
- wait_for_js(selector_exists(exercise_selector_hint_btn(id)))$
- click(selector = exercise_selector_hint_btn(id))$
- wait_for_js(check_popover_exists(id))$
- expect("succeed")
+ app$wait_for_js(selector_exists(exercise_selector_hint_btn(id)))$click(
+ selector = exercise_selector_hint_btn(id)
+ )$wait_for_js(check_popover_exists(id))$expect("succeed")
})
it("doesn't have a next hint button", {
- app$
- wait_for_js(
- selector_doesnt_exist(
- exercise_selector_hint_popover(id),
- ".btn-tutorial-hint"
- )
- )$
- expect("succeed")
+ app$wait_for_js(
+ selector_doesnt_exist(
+ exercise_selector_hint_popover(id),
+ ".btn-tutorial-hint"
+ )
+ )$expect("succeed")
})
it("shows the correct hint in the editor", {
- app$
- expect("equal", get_js(get_popover_editor_value(id)), "# one hint")
-
+ app$expect("equal", get_js(get_popover_editor_value(id)), "# one hint")
hint_text <- app$get_js(get_popover_editor_value(id))
expect_equal(hint_text, "# one hint")
})
it("hides the popover when clicking on the hint button again", {
- app$
- wait_for_js(check_popover_exists(id))$
- click(selector = exercise_selector_hint_btn(id))$
- wait_for_js(check_popover_closed(id))$
- expect("succeed")
+ app$wait_for_js(check_popover_exists(id))$click(
+ selector = exercise_selector_hint_btn(id)
+ )$wait_for_js(check_popover_closed(id))$expect("succeed")
})
})
@@ -73,11 +65,9 @@ describe("sequential hints", {
id <- "two"
it("shows hints when hint button is clicked", {
- app$
- wait_for_js(selector_exists(exercise_selector_hint_btn(id)))$
- click(selector = exercise_selector_hint_btn(id))$
- wait_for_js(check_popover_exists(id))$
- expect("succeed")
+ app$wait_for_js(selector_exists(exercise_selector_hint_btn(id)))$click(
+ selector = exercise_selector_hint_btn(id)
+ )$wait_for_js(check_popover_exists(id))$expect("succeed")
})
next_hint_button <- paste(
@@ -86,9 +76,7 @@ describe("sequential hints", {
)
it("has a next hint button", {
- app$
- wait_for_js(selector_exists(next_hint_button))$
- expect("succeed")
+ app$wait_for_js(selector_exists(next_hint_button))$expect("succeed")
})
it("shows the first hint in the editor", {
@@ -96,36 +84,36 @@ describe("sequential hints", {
})
it("shows the next hint when clicking on the next hint button", {
- app$
- click(selector = next_hint_button)$
- expect("equal", get_js(get_popover_editor_value(id)), "# second hint")
+ app$click(selector = next_hint_button)$expect(
+ "equal",
+ get_js(get_popover_editor_value(id)),
+ "# second hint"
+ )
})
it("disables the next hint button when the last hint is shown", {
- app$
- expect(
- "true",
- "disabled" %in% unlist(get_js(selector_classlist(next_hint_button)))
- )$
- expect(
- "true",
- get_js(selector_attributes(next_hint_button))$disabled %in%
+ app$expect(
+ "true",
+ "disabled" %in% unlist(get_js(selector_classlist(next_hint_button)))
+ )$expect(
+ "true",
+ get_js(selector_attributes(next_hint_button))$disabled %in%
c("true", "disabled", "")
- )
+ )
})
it("doesn't do anything when disabled hint button is clicked", {
- app$
- click(selector = next_hint_button)$
- expect("equal", get_js(get_popover_editor_value(id)), "# second hint")
+ app$click(selector = next_hint_button)$expect(
+ "equal",
+ get_js(get_popover_editor_value(id)),
+ "# second hint"
+ )
})
it("hides the hints when clicking on the hint button again", {
- app$
- wait_for_js(check_popover_exists(id))$
- click(selector = exercise_selector_hint_btn(id))$
- wait_for_js(check_popover_closed(id))$
- expect("succeed")
+ app$wait_for_js(check_popover_exists(id))$click(
+ selector = exercise_selector_hint_btn(id)
+ )$wait_for_js(check_popover_closed(id))$expect("succeed")
})
})
@@ -133,11 +121,9 @@ describe("sequential hints", {
id <- "three"
it("shows hints when hint button is clicked", {
- app$
- wait_for_js(selector_exists(exercise_selector_hint_btn(id)))$
- click(selector = exercise_selector_hint_btn(id))$
- wait_for_js(check_popover_exists(id))$
- expect("succeed")
+ app$wait_for_js(selector_exists(exercise_selector_hint_btn(id)))$click(
+ selector = exercise_selector_hint_btn(id)
+ )$wait_for_js(check_popover_exists(id))$expect("succeed")
})
next_hint_button <- paste(
@@ -146,57 +132,62 @@ describe("sequential hints", {
)
it("has a next hint button", {
- app$
- wait_for_js(selector_exists(next_hint_button))$
- expect("succeed")
+ app$wait_for_js(selector_exists(next_hint_button))$expect("succeed")
})
it("shows the first hint in the editor", {
- app$expect("equal", get_js(get_popover_editor_value(id)), "# 3 - first hint")
+ app$expect(
+ "equal",
+ get_js(get_popover_editor_value(id)),
+ "# 3 - first hint"
+ )
})
it("shows the next hint when clicking on the next hint button", {
- app$
- click(selector = next_hint_button)$
- wait_for_js(check_popover_exists(id))$
- expect("succeed")$
- expect("equal", get_js(get_popover_editor_value(id)), "# 3 - second hint")
+ app$click(selector = next_hint_button)$wait_for_js(check_popover_exists(
+ id
+ ))$expect("succeed")$expect(
+ "equal",
+ get_js(get_popover_editor_value(id)),
+ "# 3 - second hint"
+ )
})
it("shows the solution after the last hint", {
- app$
- click(selector = next_hint_button)$
- wait_for_js(check_popover_exists(id))$
- expect("succeed")$
- expect("equal", get_js(get_popover_editor_value(id)), "2 + 2")
+ app$click(selector = next_hint_button)$wait_for_js(check_popover_exists(
+ id
+ ))$expect("succeed")$expect(
+ "equal",
+ get_js(get_popover_editor_value(id)),
+ "2 + 2"
+ )
})
it("adds disabled class and attribute when the next hint button is shown", {
- app$
- expect(
- "true",
- "disabled" %in% unlist(get_js(selector_classlist(next_hint_button)))
- )$
- expect(
- "true",
- get_js(selector_attributes(next_hint_button))$disabled %in% c("true", "disabled", "")
- )
+ app$expect(
+ "true",
+ "disabled" %in% unlist(get_js(selector_classlist(next_hint_button)))
+ )$expect(
+ "true",
+ get_js(selector_attributes(next_hint_button))$disabled %in%
+ c("true", "disabled", "")
+ )
})
it("doesn't do anything when disabled hint button is clicked", {
- app$
- click(selector = next_hint_button)$
- wait_for_js(check_popover_exists(id))$
- expect("succeed")$
- expect("equal", get_js(get_popover_editor_value(id)), "2 + 2")
+ app$click(selector = next_hint_button)$wait_for_js(check_popover_exists(
+ id
+ ))$expect("succeed")$expect(
+ "equal",
+ get_js(get_popover_editor_value(id)),
+ "2 + 2"
+ )
})
it("hides the hints when clicking on the hint button again", {
- app$
- wait_for_js(check_popover_exists(id))$
- click(selector = exercise_selector_hint_btn(id))$
- wait_for_js(check_popover_closed(id))$
- expect("succeed")
+ app$wait_for_js(check_popover_exists(id))$click(
+ selector = exercise_selector_hint_btn(id)
+ )$wait_for_js(check_popover_closed(id))$expect("succeed")
})
})
})
@@ -214,10 +205,9 @@ describe("copy button", {
}
# Reset tutorial via "Start Over" button
- app$
- click(selector = ".resetButton")$
- wait_for_js(selector_exists(".bootbox .bootbox-accept"))$
- click(selector = ".bootbox .bootbox-accept")
+ app$click(selector = ".resetButton")$wait_for_js(selector_exists(
+ ".bootbox .bootbox-accept"
+ ))$click(selector = ".bootbox .bootbox-accept")
# Wait for page reload to complete
chrome$Page$loadEventFired()
@@ -236,26 +226,26 @@ describe("copy button", {
)
it("clicks hint button to open hint popover", {
- app$
- wait_for_js(check_popover_closed(id), timeout = 5000)$
- click(selector = exercise_selector_hint_btn(id))$
- wait_for_js(check_popover_exists(id))$
- expect("succeed", "hint popover exists")$
- wait_for_js(
- selector_exists(
- exercise_selector_hint_popover(id),
- ".btn-tutorial-copy-solution"
- )
- )$
- expect("succeed", "hint popover has copy solution button")$
- wait_for_js(
- selector_exists(exercise_selector_hint_popover(id), ".ace_editor")
- )$
- expect("succeed", "popover has editor with hint")
+ app$wait_for_js(check_popover_closed(id), timeout = 5000)$click(
+ selector = exercise_selector_hint_btn(id)
+ )$wait_for_js(check_popover_exists(id))$expect(
+ "succeed",
+ "hint popover exists"
+ )$wait_for_js(
+ selector_exists(
+ exercise_selector_hint_popover(id),
+ ".btn-tutorial-copy-solution"
+ )
+ )$expect("succeed", "hint popover has copy solution button")$wait_for_js(
+ selector_exists(exercise_selector_hint_popover(id), ".ace_editor")
+ )$expect("succeed", "popover has editor with hint")
})
it("hint text in editor matches expectations", {
- app$expect("true", get_js(get_popover_editor_value(id)) %in% hint_text_expected)
+ app$expect(
+ "true",
+ get_js(get_popover_editor_value(id)) %in% hint_text_expected
+ )
})
it("clicks copy solution button to copy hint and close popover", {
@@ -264,9 +254,12 @@ describe("copy button", {
".btn-tutorial-copy-solution"
)
- app_real_click(app, copy_btn)$
- wait_for_js(check_popover_closed(id))$
- expect("true", get_js('navigator.clipboard.readText()') %in% hint_text_expected)
+ app_real_click(app, copy_btn)$wait_for_js(check_popover_closed(
+ id
+ ))$expect(
+ "true",
+ get_js('navigator.clipboard.readText()') %in% hint_text_expected
+ )
})
it("pastes the copied text into the editor", {
@@ -278,29 +271,27 @@ describe("copy button", {
",
exercise_selector_editor(id)
)
- )$
- expect(
- "true",
- trimws(app$get_js(get_editor_value(exercise_selector_editor(id)))) %in%
- hint_text_expected
- )
+ )$expect(
+ "true",
+ trimws(app$get_js(get_editor_value(exercise_selector_editor(id)))) %in%
+ hint_text_expected
+ )
})
it("evaluates the pasted hint text correctly", {
- app$
- click(selector = exercise_selector_run_btn(id))$
- wait_for_js(exercise_has_output(id))$
- expect(
- "equal",
- get_html(
- selector = paste(exercise_selector_output(id), "pre code"),
- outer_html = FALSE
- ),
- get_html(
- selector = "#section-ex1-expected-output pre code",
- outer_html = FALSE
- )
+ app$click(
+ selector = exercise_selector_run_btn(id)
+ )$wait_for_js(exercise_has_output(id))$expect(
+ "equal",
+ get_html(
+ selector = paste(exercise_selector_output(id), "pre code"),
+ outer_html = FALSE
+ ),
+ get_html(
+ selector = "#section-ex1-expected-output pre code",
+ outer_html = FALSE
)
+ )
})
})
@@ -312,26 +303,26 @@ describe("copy button", {
)
it("clicks hint button to open hint popover", {
- app$
- wait_for_js(check_popover_closed(id), timeout = 5000)$
- click(selector = exercise_selector_hint_btn(id))$
- wait_for_js(check_popover_exists(id))$
- expect("succeed", "hint popover exists")$
- wait_for_js(
- selector_exists(
- exercise_selector_hint_popover(id),
- ".btn-tutorial-copy-solution"
- )
- )$
- expect("succeed", "hint popover has copy solution button")$
- wait_for_js(
- selector_exists(exercise_selector_hint_popover(id), ".ace_editor")
- )$
- expect("succeed", "popover has editor with hint")
+ app$wait_for_js(check_popover_closed(id), timeout = 5000)$click(
+ selector = exercise_selector_hint_btn(id)
+ )$wait_for_js(check_popover_exists(id))$expect(
+ "succeed",
+ "hint popover exists"
+ )$wait_for_js(
+ selector_exists(
+ exercise_selector_hint_popover(id),
+ ".btn-tutorial-copy-solution"
+ )
+ )$expect("succeed", "hint popover has copy solution button")$wait_for_js(
+ selector_exists(exercise_selector_hint_popover(id), ".ace_editor")
+ )$expect("succeed", "popover has editor with hint")
})
it("hint text in editor matches expectations", {
- app$expect("true", get_js(get_popover_editor_value(id)) %in% solution_text_expected)
+ app$expect(
+ "true",
+ get_js(get_popover_editor_value(id)) %in% solution_text_expected
+ )
})
it("clicks copy solution button to copy hint and close popover", {
@@ -340,9 +331,12 @@ describe("copy button", {
".btn-tutorial-copy-solution"
)
- app_real_click(app, copy_btn)$
- wait_for_js(check_popover_closed(id))$
- expect("true", get_js('navigator.clipboard.readText()') %in% solution_text_expected)
+ app_real_click(app, copy_btn)$wait_for_js(check_popover_closed(
+ id
+ ))$expect(
+ "true",
+ get_js('navigator.clipboard.readText()') %in% solution_text_expected
+ )
})
it("pastes the copied text into the editor", {
@@ -354,29 +348,27 @@ describe("copy button", {
",
exercise_selector_editor(id)
)
- )$
- expect(
- "true",
- trimws(app$get_js(get_editor_value(exercise_selector_editor(id)))) %in%
- solution_text_expected
- )
+ )$expect(
+ "true",
+ trimws(app$get_js(get_editor_value(exercise_selector_editor(id)))) %in%
+ solution_text_expected
+ )
})
it("evaluates the pasted hint text correctly", {
- app$
- click(selector = exercise_selector_run_btn(id))$
- wait_for_js(exercise_has_output(id))$
- expect(
- "equal",
- get_html(
- selector = paste(exercise_selector_output(id), "pre code"),
- outer_html = FALSE
- ),
- get_html(
- selector = "#section-ex2-expected-output pre code",
- outer_html = FALSE
- )
+ app$click(
+ selector = exercise_selector_run_btn(id)
+ )$wait_for_js(exercise_has_output(id))$expect(
+ "equal",
+ get_html(
+ selector = paste(exercise_selector_output(id), "pre code"),
+ outer_html = FALSE
+ ),
+ get_html(
+ selector = "#section-ex2-expected-output pre code",
+ outer_html = FALSE
)
+ )
})
})
})
@@ -398,30 +390,32 @@ describe("div hints", {
})
it("has a hidden hint div", {
- app$
- wait_for_js(selector_exists(div))$
- expect(
- "equal",
- get_js(selector_computed_style(div))$display,
- "none"
- )
+ app$wait_for_js(selector_exists(div))$expect(
+ "equal",
+ get_js(selector_computed_style(div))$display,
+ "none"
+ )
})
it("reveals hint when hint button is clicked", {
- app$
- click(selector = btn)$
- wait_for_js(selector_exists(hint_panel, ".tutorial-hint"))$
- expect(
- "equal",
- trimws(get_html(paste(hint_panel, ".tutorial-hint"), outer_html = FALSE)),
- "This is the HTML hint.
" - ) + app$click(selector = btn)$wait_for_js(selector_exists( + hint_panel, + ".tutorial-hint" + ))$expect( + "equal", + trimws(get_html( + paste(hint_panel, ".tutorial-hint"), + outer_html = FALSE + )), + "This is the HTML hint.
" + ) }) it("hides the hint when the button is clicked again", { - app$ - click(selector = btn)$ - wait_for_js(selector_doesnt_exist(hint_panel, ".tutorial-hint")) + app$click(selector = btn)$wait_for_js(selector_doesnt_exist( + hint_panel, + ".tutorial-hint" + )) }) }) @@ -435,30 +429,32 @@ describe("div hints", { }) it("has a hidden hint div", { - app$ - wait_for_js(selector_exists(div))$ - expect( - "equal", - get_js(selector_computed_style(div))$display, - "none" - ) + app$wait_for_js(selector_exists(div))$expect( + "equal", + get_js(selector_computed_style(div))$display, + "none" + ) }) it("reveals hint when hint button is clicked", { - app$ - click(selector = btn)$ - wait_for_js(selector_exists(hint_panel, ".tutorial-hint"))$ - expect( - "equal", - trimws(get_html(paste(hint_panel, ".tutorial-hint"), outer_html = FALSE)), - "This is the md hint.
" - ) + app$click(selector = btn)$wait_for_js(selector_exists( + hint_panel, + ".tutorial-hint" + ))$expect( + "equal", + trimws(get_html( + paste(hint_panel, ".tutorial-hint"), + outer_html = FALSE + )), + "This is the md hint.
" + ) }) it("hides the hint when the button is clicked again", { - app$ - click(selector = btn)$ - wait_for_js(selector_doesnt_exist(hint_panel, ".tutorial-hint")) + app$click(selector = btn)$wait_for_js(selector_doesnt_exist( + hint_panel, + ".tutorial-hint" + )) }) }) }) diff --git a/tests/testthat/test-storage.R b/tests/testthat/test-storage.R index 4d43eb106..2ca091c1e 100644 --- a/tests/testthat/test-storage.R +++ b/tests/testthat/test-storage.R @@ -1,4 +1,3 @@ - context("storage") test_that("filesystem storage can be created", { @@ -8,8 +7,19 @@ test_that("filesystem storage can be created", { test_that("objects cna be saved into filesystem storage", { fs <- filesystem_storage(tempfile()) - fs$save_object("tutorial_id", "tutorial_version", "user_id", "object_id", "data") - obj <- fs$get_object("tutorial_id", "tutorial_version", "user_id", "object_id") + fs$save_object( + "tutorial_id", + "tutorial_version", + "user_id", + "object_id", + "data" + ) + obj <- fs$get_object( + "tutorial_id", + "tutorial_version", + "user_id", + "object_id" + ) expect_equal(obj, "data") fs$remove_all_objects("tutorial_id", "tutorial_version", "user_id") -}) \ No newline at end of file +}) diff --git a/tests/testthat/test-tutorial-state.R b/tests/testthat/test-tutorial-state.R index 48fa76b20..d8f341494 100644 --- a/tests/testthat/test-tutorial-state.R +++ b/tests/testthat/test-tutorial-state.R @@ -1,11 +1,13 @@ - test_that("store works", { # First write works expect_equal(store_tutorial_cache("myName", c("code", "here"), FALSE), TRUE) expect_equal(tutorial_cache_env$objects[["myName"]], c("code", "here")) # Second write without overwrite is a no-op - expect_equal(store_tutorial_cache("myName", c("updated", "code"), FALSE), FALSE) + expect_equal( + store_tutorial_cache("myName", c("updated", "code"), FALSE), + FALSE + ) expect_equal(tutorial_cache_env$objects[["myName"]], c("code", "here")) # Overwrite returns true @@ -49,11 +51,21 @@ test_that("get_tutorial_info() returns structured tutorial cache", { expect_equal(all[exercises], get_exercise_cache()) expect_equal(all$`two-plus-two`, get_exercise_cache("two-plus-two")) - expect_true(all(vapply(all[exercises], inherits, logical(1), "tutorial_exercise"))) + expect_true(all(vapply( + all[exercises], + inherits, + logical(1), + "tutorial_exercise" + ))) expect_equal(all[questions], get_question_cache()) expect_equal(all$`quiz-2`, get_question_cache("quiz-2")) - expect_true(all(vapply(all[questions], inherits, logical(1), "tutorial_question"))) + expect_true(all(vapply( + all[questions], + inherits, + logical(1), + "tutorial_question" + ))) # exercises have the same `global_setup` expect_equal(all$`two-plus-two`$global_setup, all$`add-function`$global_setup) @@ -65,7 +77,10 @@ test_that("get_tutorial_info() returns structured tutorial cache", { test_that("setup-global-exercise chunk is used for global_setup", { skip_if_not_pandoc("1.14") - prepare_tutorial_cache_from_source(test_path("setup-chunks", "exercise-global-setup.Rmd")) + prepare_tutorial_cache_from_source(test_path( + "setup-chunks", + "exercise-global-setup.Rmd" + )) withr::defer(clear_tutorial_cache()) all <- get_tutorial_cache() @@ -73,9 +88,16 @@ test_that("setup-global-exercise chunk is used for global_setup", { expect_equal(as.character(all$data1$global_setup), "global <- 0") # check that the correct chunk was used for the `global_setup` # NOTE: this may change if the knitr hooks are refactored - expect_equal(attr(all$data1$global_setup, "chunk_opts")$label, "setup-global-exercise") - - ex <- mock_exercise(user_code = "global", label = "data1", check = I("global")) + expect_equal( + attr(all$data1$global_setup, "chunk_opts")$label, + "setup-global-exercise" + ) + + ex <- mock_exercise( + user_code = "global", + label = "data1", + check = I("global") + ) ex$chunks <- all$data1$chunks ex$global_setup <- all$data1$global_setup ex$setup <- all$data1$setup diff --git a/tools/deploy_tutorials.R b/tools/deploy_tutorials.R index fc7de7304..ab86e2b76 100644 --- a/tools/deploy_tutorials.R +++ b/tools/deploy_tutorials.R @@ -1,5 +1,6 @@ - -if (!requireNamespace("remotes")) install.packages("remotes") +if (!requireNamespace("remotes")) { + install.packages("remotes") +} # install rsconnect remotes::install_cran("rsconnect") @@ -13,7 +14,7 @@ remotes::install_cran("renv") remotes::install_cran( setdiff( unique(renv::dependencies("inst/tutorials/")$Package), - unname(installed.packages()[,"Package"]) + unname(installed.packages()[, "Package"]) ) ) diff --git a/tools/deploy_tutorials_on_ci.R b/tools/deploy_tutorials_on_ci.R index 5b3344fca..a646e7c0a 100644 --- a/tools/deploy_tutorials_on_ci.R +++ b/tools/deploy_tutorials_on_ci.R @@ -1,4 +1,3 @@ - if (!requireNamespace("remotes")) { install.packages("remotes") } @@ -6,8 +5,8 @@ remotes::install_cran("rsconnect") # Set the account info for deployment. rsconnect::setAccountInfo( - name = Sys.getenv("SHINYAPPS_NAME"), # learnr-examples - token = Sys.getenv("SHINYAPPS_TOKEN"), + name = Sys.getenv("SHINYAPPS_NAME"), # learnr-examples + token = Sys.getenv("SHINYAPPS_TOKEN"), secret = Sys.getenv("SHINYAPPS_SECRET") ) diff --git a/tools/deploy_tutorials_on_local.R b/tools/deploy_tutorials_on_local.R index db7f6552a..76c257f34 100644 --- a/tools/deploy_tutorials_on_local.R +++ b/tools/deploy_tutorials_on_local.R @@ -1,5 +1,3 @@ - - if (!requireNamespace("remotes")) { install.packages("remotes") } diff --git a/tools/update-ace.R b/tools/update-ace.R index 95a2a8bc4..3d6ae392a 100644 --- a/tools/update-ace.R +++ b/tools/update-ace.R @@ -1,67 +1,82 @@ - (function() { -owd <- getwd() -on.exit({setwd(owd)}) - - -ROOT <- rprojroot::find_package_root_file() -ACE_VERSION <- "1.10.1" -ACE_FILES <- c( - "ace.js", - "ext-language_tools.js", - "mode-css.js", - "mode-html.js", - "mode-javascript.js", - "mode-julia.js", - "mode-plain_text.js", - "mode-python.js", - "mode-r.js", - "mode-rdoc.js", - "mode-rhtml.js", - "mode-sql.js", - "mode-text.js", - "mode-xml.js" -) -ACE_THEME_PREFIX <- "theme-" - -url <- sprintf( - "https://github.com/ajaxorg/ace-builds/archive/v%s.tar.gz", - ACE_VERSION -) + owd <- getwd() + on.exit({ + setwd(owd) + }) -destfile <- tempfile("ace-tarball-") -on.exit({unlink(destfile)}, add = TRUE) -download.file(url, destfile = destfile) + ROOT <- rprojroot::find_package_root_file() + ACE_VERSION <- "1.10.1" + ACE_FILES <- c( + "ace.js", + "ext-language_tools.js", + "mode-css.js", + "mode-html.js", + "mode-javascript.js", + "mode-julia.js", + "mode-plain_text.js", + "mode-python.js", + "mode-r.js", + "mode-rdoc.js", + "mode-rhtml.js", + "mode-sql.js", + "mode-text.js", + "mode-xml.js" + ) + ACE_THEME_PREFIX <- "theme-" + url <- sprintf( + "https://github.com/ajaxorg/ace-builds/archive/v%s.tar.gz", + ACE_VERSION + ) -exdir <- tempfile("ace-") -on.exit({unlink(exdir, recursive = TRUE)}, add = TRUE) -dir.create(exdir) -untar(tarfile = destfile, exdir = exdir) + destfile <- tempfile("ace-tarball-") + on.exit( + { + unlink(destfile) + }, + add = TRUE + ) + download.file(url, destfile = destfile) -setwd(exdir) -setwd(sprintf("ace-builds-%s", ACE_VERSION)) + exdir <- tempfile("ace-") + on.exit( + { + unlink(exdir, recursive = TRUE) + }, + add = TRUE + ) + dir.create(exdir) + untar(tarfile = destfile, exdir = exdir) + setwd(exdir) + setwd(sprintf("ace-builds-%s", ACE_VERSION)) -source <- c( - file.path("src-min", ACE_FILES), - dir("src-min", pattern = ACE_THEME_PREFIX, full.names = TRUE) -) -contents <- paste(lapply(source, function(file) { - readChar(file, file.info(file)$size, TRUE) -}), collapse = "\n") + source <- c( + file.path("src-min", ACE_FILES), + dir("src-min", pattern = ACE_THEME_PREFIX, full.names = TRUE) + ) + contents <- paste( + lapply(source, function(file) { + readChar(file, file.info(file)$size, TRUE) + }), + collapse = "\n" + ) -target <- file.path(ROOT, "inst/lib/ace/ace.js") -writeLines(contents, con = target, sep = "\n", useBytes = TRUE) + target <- file.path(ROOT, "inst/lib/ace/ace.js") + writeLines(contents, con = target, sep = "\n", useBytes = TRUE) -themes <- sub("^theme-", "", sub("\\.js$", "", dir("src", "^theme-"))) + themes <- sub("^theme-", "", sub("\\.js$", "", dir("src", "^theme-"))) -metadata <- c( - "# This file was autogenerated by 'tools/update-ace.R'", - paste0("ACE_VERSION <- ", shQuote(ACE_VERSION, "cmd")), - paste0("ACE_THEMES <- c(", paste0(shQuote(themes, "cmd"), collapse = ", "), ")") -) + metadata <- c( + "# This file was autogenerated by 'tools/update-ace.R'", + paste0("ACE_VERSION <- ", shQuote(ACE_VERSION, "cmd")), + paste0( + "ACE_THEMES <- c(", + paste0(shQuote(themes, "cmd"), collapse = ", "), + ")" + ) + ) -cat("Saving metadata:\n\n", paste0(metadata, collapse = '\n'), "\n") -writeLines(metadata, con = file.path(ROOT, "R/ace.R")) + cat("Saving metadata:\n\n", paste0(metadata, collapse = '\n'), "\n") + writeLines(metadata, con = file.path(ROOT, "R/ace.R")) })()