From 4e945a756c207d146e5c5c4c53a0fd14231c9924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:39:34 +0100 Subject: [PATCH 01/19] feat: ordering and add missing assertions --- R/tm_a_pca.R | 46 ++++++++++++--------- R/tm_data_table.R | 10 ++++- R/tm_file_viewer.R | 18 ++++----- R/tm_front_page.R | 6 ++- R/tm_g_association.R | 13 ++++++ R/tm_g_bivariate.R | 79 +++++++++++++++++++++---------------- R/tm_g_distribution.R | 36 +++++++++++++---- R/tm_g_response.R | 23 +++++++---- R/tm_g_scatterplot.R | 45 ++++++++++++--------- R/tm_g_scatterplotmatrix.R | 12 ++++++ R/tm_missing_data.R | 16 +++++++- R/tm_outliers.R | 21 +++++++++- R/tm_t_crosstable.R | 15 +++++-- R/tm_variable_browser.R | 8 ++++ R/utils.R | 12 ++++++ tests/testthat/test-utils.R | 22 +++++++++++ 16 files changed, 277 insertions(+), 105 deletions(-) create mode 100644 tests/testthat/test-utils.R diff --git a/R/tm_a_pca.R b/R/tm_a_pca.R index f5cf694ff..4e341bb41 100644 --- a/R/tm_a_pca.R +++ b/R/tm_a_pca.R @@ -110,14 +110,38 @@ tm_a_pca <- function(label = "Principal Component Analysis", pre_output = NULL, post_output = NULL) { logger::log_info("Initializing tm_a_pca") + + # Normalizing data types if (inherits(dat, "data_extract_spec")) dat <- list(dat) if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) + # Start of assertions checkmate::assert_string(label) checkmate::assert_list(dat, types = "data_extract_spec") + + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") + checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) + checkmate::assert_numeric( + plot_width[1], + lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" + ) + ggtheme <- match.arg(ggtheme) + + plot_choices <- c("Elbow plot", "Circle plot", "Biplot", "Eigenvector plot") + checkmate::assert_list(ggplot2_args, types = "ggplot2_args") + checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices)) + checkmate::assert_flag(rotate_xaxis_labels) + if (length(font_size) == 1) { + checkmate::assert_numeric(font_size, any.missing = FALSE, finite = TRUE, lower = 8, upper = 20) + } else { + checkmate::assert_numeric(font_size, len = 3, any.missing = FALSE, finite = TRUE, lower = 8, upper = 20) + checkmate::assert_numeric(font_size[1], lower = font_size[2], upper = font_size[3], .var.name = "font_size") + } + if (length(alpha) == 1) { checkmate::assert_numeric(alpha, any.missing = FALSE, finite = TRUE, lower = 0, upper = 1) } else { @@ -132,25 +156,11 @@ tm_a_pca <- function(label = "Principal Component Analysis", checkmate::assert_numeric(size[1], lower = size[2], upper = size[3], .var.name = "size") } - if (length(font_size) == 1) { - checkmate::assert_numeric(font_size, any.missing = FALSE, finite = TRUE, lower = 8, upper = 20) - } else { - checkmate::assert_numeric(font_size, len = 3, any.missing = FALSE, finite = TRUE, lower = 8, upper = 20) - checkmate::assert_numeric(font_size[1], lower = font_size[2], upper = font_size[3], .var.name = "font_size") - } - - checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) - checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") - checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) - checkmate::assert_numeric( - plot_width[1], - lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" - ) - - plot_choices <- c("Elbow plot", "Circle plot", "Biplot", "Eigenvector plot") - checkmate::assert_list(ggplot2_args, types = "ggplot2_args") - checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices)) + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + # End of assertions + # Send ui args args <- as.list(environment()) data_extract_list <- list(dat = dat) diff --git a/R/tm_data_table.R b/R/tm_data_table.R index 9d6cd866c..3a5f47180 100644 --- a/R/tm_data_table.R +++ b/R/tm_data_table.R @@ -89,7 +89,10 @@ tm_data_table <- function(label = "Data Table", pre_output = NULL, post_output = NULL) { logger::log_info("Initializing tm_data_table") + + # Start of assertions checkmate::assert_string(label) + checkmate::assert_list(variables_selected, min.len = 0, types = "character", names = "named") if (length(variables_selected) > 0) { lapply(seq_along(variables_selected), function(i) { @@ -99,14 +102,17 @@ tm_data_table <- function(label = "Data Table", } }) } + checkmate::assert_character(datasets_selected, min.len = 0, min.chars = 1) - checkmate::assert_list(dt_options, names = "named") checkmate::assert( checkmate::check_list(dt_args, len = 0), checkmate::check_subset(names(dt_args), choices = names(formals(DT::datatable))) ) - + checkmate::assert_list(dt_options, names = "named") checkmate::assert_flag(server_rendering) + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + # End of assertions module( label, diff --git a/R/tm_file_viewer.R b/R/tm_file_viewer.R index 8be167330..e46d6154f 100644 --- a/R/tm_file_viewer.R +++ b/R/tm_file_viewer.R @@ -39,25 +39,24 @@ tm_file_viewer <- function(label = "File Viewer Module", input_path = list("Current Working Directory" = ".")) { logger::log_info("Initializing tm_file_viewer") - if (length(label) == 0 || identical(label, "")) { - label <- " " - } - if (length(input_path) == 0 || identical(input_path, "")) { - input_path <- list() - } + # Normalize the parameters + if (length(label) == 0 || identical(label, "")) label <- " " + if (length(input_path) == 0 || identical(input_path, "")) input_path <- list() + + # Start of assertions checkmate::assert_string(label) + checkmate::assert( checkmate::check_list(input_path, types = "character", min.len = 0), checkmate::check_character(input_path, min.len = 1) ) - if (length(input_path) > 0) { valid_url <- function(url_input, timeout = 2) { con <- try(url(url_input), silent = TRUE) check <- suppressWarnings(try(open.connection(con, open = "rt", timeout = timeout), silent = TRUE)[1]) try(close.connection(con), silent = TRUE) - ifelse(is.null(check), TRUE, FALSE) + is.null(check) } idx <- vapply(input_path, function(x) file.exists(x) || valid_url(x), logical(1)) @@ -75,8 +74,9 @@ tm_file_viewer <- function(label = "File Viewer Module", "No file or url paths were provided." ) } + # End of assertions - + # Send ui args args <- as.list(environment()) module( diff --git a/R/tm_front_page.R b/R/tm_front_page.R index c10ef2bd0..54ada504e 100644 --- a/R/tm_front_page.R +++ b/R/tm_front_page.R @@ -67,14 +67,18 @@ tm_front_page <- function(label = "Front page", additional_tags = tagList(), footnotes = character(0), show_metadata = FALSE) { + logger::log_info("Initializing tm_front_page") + + # Start of assertions checkmate::assert_string(label) checkmate::assert_character(header_text, min.len = 0, any.missing = FALSE) checkmate::assert_list(tables, types = "data.frame", names = "named", any.missing = FALSE) checkmate::assert_multi_class(additional_tags, classes = c("shiny.tag.list", "html")) checkmate::assert_character(footnotes, min.len = 0, any.missing = FALSE) checkmate::assert_flag(show_metadata) + # End of assertions - logger::log_info("Initializing tm_front_page") + # Send ui args args <- as.list(environment()) module( diff --git a/R/tm_g_association.R b/R/tm_g_association.R index 1ee28d45a..8768ec8ed 100644 --- a/R/tm_g_association.R +++ b/R/tm_g_association.R @@ -131,17 +131,23 @@ tm_g_association <- function(label = "Association", post_output = NULL, ggplot2_args = teal.widgets::ggplot2_args()) { logger::log_info("Initializing tm_g_association") + + # Normalize the parameters if (inherits(ref, "data_extract_spec")) ref <- list(ref) if (inherits(vars, "data_extract_spec")) vars <- list(vars) if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) + # Start of assertions checkmate::assert_string(label) + checkmate::assert_list(ref, types = "data_extract_spec") if (!all(vapply(ref, function(x) !x$select$multiple, logical(1)))) { stop("'ref' should not allow multiple selection") } + checkmate::assert_list(vars, types = "data_extract_spec") checkmate::assert_flag(show_association) + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) @@ -149,12 +155,19 @@ tm_g_association <- function(label = "Association", plot_width[1], lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" ) + distribution_theme <- match.arg(distribution_theme) association_theme <- match.arg(association_theme) + + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + plot_choices <- c("Bivariate1", "Bivariate2") checkmate::assert_list(ggplot2_args, types = "ggplot2_args") checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices)) + # End of assertions + # Send ui args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_bivariate.R b/R/tm_g_bivariate.R index 99fa167f4..1d2d7d62a 100644 --- a/R/tm_g_bivariate.R +++ b/R/tm_g_bivariate.R @@ -192,6 +192,8 @@ tm_g_bivariate <- function(label = "Bivariate Plots", pre_output = NULL, post_output = NULL) { logger::log_info("Initializing tm_g_bivariate") + + # Normalize the parameters if (inherits(x, "data_extract_spec")) x <- list(x) if (inherits(y, "data_extract_spec")) y <- list(y) if (inherits(row_facet, "data_extract_spec")) row_facet <- list(row_facet) @@ -200,52 +202,37 @@ tm_g_bivariate <- function(label = "Bivariate Plots", if (inherits(fill, "data_extract_spec")) fill <- list(fill) if (inherits(size, "data_extract_spec")) size <- list(size) + # Start of assertions + checkmate::assert_string(label) + checkmate::assert_list(x, types = "data_extract_spec") - if (!all(vapply(x, function(x) !x$select$multiple, logical(1)))) { - stop("'x' should not allow multiple selection") - } + assert_single_selection(x) + checkmate::assert_list(y, types = "data_extract_spec") - if (!all(vapply(y, function(x) !x$select$multiple, logical(1)))) { - stop("'y' should not allow multiple selection") - } + assert_single_selection(y) + checkmate::assert_list(row_facet, types = "data_extract_spec", null.ok = TRUE) - if (!all(vapply(row_facet, function(x) !x$select$multiple, logical(1)))) { - stop("'row_facet' should not allow multiple selection") - } + assert_single_selection(row_facet) + checkmate::assert_list(col_facet, types = "data_extract_spec", null.ok = TRUE) - if (!all(vapply(col_facet, function(x) !x$select$multiple, logical(1)))) { - stop("'col_facet' should not allow multiple selection") - } + assert_single_selection(col_facet) + checkmate::assert_list(color, types = "data_extract_spec", null.ok = TRUE) - if (!all(vapply(color, function(x) !x$select$multiple, logical(1)))) { - stop("'color' should not allow multiple selection") - } + assert_single_selection(color) + checkmate::assert_list(fill, types = "data_extract_spec", null.ok = TRUE) - if (!all(vapply(fill, function(x) !x$select$multiple, logical(1)))) { - stop("'fill' should not allow multiple selection") - } + assert_single_selection(fill) + checkmate::assert_list(size, types = "data_extract_spec", null.ok = TRUE) - if (!all(vapply(size, function(x) !x$select$multiple, logical(1)))) { - stop("'size' should not allow multiple selection") - } + assert_single_selection(size) - ggtheme <- match.arg(ggtheme) - checkmate::assert_string(label) - checkmate::assert_flag(use_density) - checkmate::assert_flag(color_settings) - checkmate::assert_flag(free_x_scales) - checkmate::assert_flag(free_y_scales) + checkmate::assert_flag(facet) checkmate::assert_flag(rotate_xaxis_labels) checkmate::assert_flag(swap_axes) - checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) - checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") - checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) - checkmate::assert_numeric( - plot_width[1], - lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" - ) - checkmate::assert_class(ggplot2_args, "ggplot2_args") + checkmate::assert_flag(use_density) + # Determines color, fill & size if they are not explicitly set + checkmate::assert_flag(color_settings) if (color_settings) { if (is.null(color)) { color <- x @@ -265,6 +252,28 @@ tm_g_bivariate <- function(label = "Bivariate Plots", } } + checkmate::assert_flag(free_x_scales) + checkmate::assert_flag(free_y_scales) + + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") + checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) + checkmate::assert_numeric( + plot_width[1], + lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" + ) + + checkmate::assert_flag(rotate_xaxis_labels) + checkmate::assert_flag(swap_axes) + + ggtheme <- match.arg(ggtheme) + checkmate::assert_class(ggplot2_args, "ggplot2_args") + + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + # End of assertions + + # Send ui args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_distribution.R b/R/tm_g_distribution.R index a60d6465f..3cb0345f9 100644 --- a/R/tm_g_distribution.R +++ b/R/tm_g_distribution.R @@ -119,6 +119,7 @@ tm_g_distribution <- function(label = "Distribution Module", post_output = NULL) { logger::log_info("Initializing tm_g_distribution") + # Requires Suggested packages extra_packages <- c("ggpmisc", "ggpp", "goftest", "MASS", "broom") missing_packages <- Filter(function(x) !requireNamespace(x, quietly = TRUE), extra_packages) if (length(missing_packages) > 0L) { @@ -128,28 +129,47 @@ tm_g_distribution <- function(label = "Distribution Module", )) } + # Normalize the parameters if (inherits(dist_var, "data_extract_spec")) dist_var <- list(dist_var) if (inherits(strata_var, "data_extract_spec")) strata_var <- list(strata_var) if (inherits(group_var, "data_extract_spec")) group_var <- list(group_var) if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) - ggtheme <- match.arg(ggtheme) - if (length(bins) == 1) { - checkmate::assert_numeric(bins, any.missing = FALSE, lower = 1) - } else { - checkmate::assert_numeric(bins, len = 3, any.missing = FALSE, lower = 1) - checkmate::assert_numeric(bins[1], lower = bins[2], upper = bins[3], .var.name = "bins") - } + # Start of assertions checkmate::assert_string(label) + checkmate::assert_list(dist_var, "data_extract_spec") - checkmate::assert_false(dist_var[[1]]$select$multiple) + checkmate::assert_false(dist_var[[1L]]$select$multiple) + checkmate::assert_list(strata_var, types = "data_extract_spec", null.ok = TRUE) checkmate::assert_list(group_var, types = "data_extract_spec", null.ok = TRUE) checkmate::assert_flag(freq) + ggtheme <- match.arg(ggtheme) + plot_choices <- c("Histogram", "QQplot") checkmate::assert_list(ggplot2_args, types = "ggplot2_args") checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices)) + if (length(bins) == 1) { + checkmate::assert_numeric(bins, any.missing = FALSE, lower = 1) + } else { + checkmate::assert_numeric(bins, len = 3, any.missing = FALSE, lower = 1) + checkmate::assert_numeric(bins[1], lower = bins[2], upper = bins[3], .var.name = "bins") + } + + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") + checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) + checkmate::assert_numeric( + plot_width[1], + lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" + ) + + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + # End of assertions + + # Send ui args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_response.R b/R/tm_g_response.R index 52c221e7e..55dcebcd5 100644 --- a/R/tm_g_response.R +++ b/R/tm_g_response.R @@ -152,32 +152,35 @@ tm_g_response <- function(label = "Response Plot", pre_output = NULL, post_output = NULL) { logger::log_info("Initializing tm_g_response") + + # Normalize the parameters if (inherits(response, "data_extract_spec")) response <- list(response) if (inherits(x, "data_extract_spec")) x <- list(x) if (inherits(row_facet, "data_extract_spec")) row_facet <- list(row_facet) if (inherits(col_facet, "data_extract_spec")) col_facet <- list(col_facet) + + # Start of assertions checkmate::assert_string(label) - ggtheme <- match.arg(ggtheme) + checkmate::assert_list(response, types = "data_extract_spec") if (!all(vapply(response, function(x) !("" %in% x$select$choices), logical(1)))) { stop("'response' should not allow empty values") } - if (!all(vapply(response, function(x) !x$select$multiple, logical(1)))) { - stop("'response' should not allow multiple selection") - } + assert_single_selection(response) + checkmate::assert_list(x, types = "data_extract_spec") if (!all(vapply(x, function(x) !("" %in% x$select$choices), logical(1)))) { stop("'x' should not allow empty values") } - if (!all(vapply(x, function(x) !x$select$multiple, logical(1)))) { - stop("'x' should not allow multiple selection") - } + assert_single_selection(x) + checkmate::assert_list(row_facet, types = "data_extract_spec", null.ok = TRUE) checkmate::assert_list(col_facet, types = "data_extract_spec", null.ok = TRUE) checkmate::assert_flag(coord_flip) checkmate::assert_flag(count_labels) checkmate::assert_flag(rotate_xaxis_labels) checkmate::assert_flag(freq) + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) @@ -186,8 +189,14 @@ tm_g_response <- function(label = "Response Plot", lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" ) + ggtheme <- match.arg(ggtheme) checkmate::assert_class(ggplot2_args, "ggplot2_args") + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + # End of assertions + + # Send ui args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_scatterplot.R b/R/tm_g_scatterplot.R index 2bed23a61..0e96eab2c 100644 --- a/R/tm_g_scatterplot.R +++ b/R/tm_g_scatterplot.R @@ -233,6 +233,7 @@ tm_g_scatterplot <- function(label = "Scatterplot", ggplot2_args = teal.widgets::ggplot2_args()) { logger::log_info("Initializing tm_g_scatterplot") + # Requires Suggested packages extra_packages <- c("ggpmisc", "ggExtra", "colourpicker") missing_packages <- Filter(function(x) !requireNamespace(x, quietly = TRUE), extra_packages) if (length(missing_packages) > 0L) { @@ -242,6 +243,7 @@ tm_g_scatterplot <- function(label = "Scatterplot", )) } + # Normalize the parameters if (inherits(x, "data_extract_spec")) x <- list(x) if (inherits(y, "data_extract_spec")) y <- list(y) if (inherits(color_by, "data_extract_spec")) color_by <- list(color_by) @@ -250,26 +252,27 @@ tm_g_scatterplot <- function(label = "Scatterplot", if (inherits(col_facet, "data_extract_spec")) col_facet <- list(col_facet) if (is.double(max_deg)) max_deg <- as.integer(max_deg) - ggtheme <- match.arg(ggtheme) + # Start of assertions checkmate::assert_string(label) checkmate::assert_list(x, types = "data_extract_spec") checkmate::assert_list(y, types = "data_extract_spec") checkmate::assert_list(color_by, types = "data_extract_spec", null.ok = TRUE) checkmate::assert_list(size_by, types = "data_extract_spec", null.ok = TRUE) + checkmate::assert_list(row_facet, types = "data_extract_spec", null.ok = TRUE) + assert_single_selection(row_facet) + checkmate::assert_list(col_facet, types = "data_extract_spec", null.ok = TRUE) - checkmate::assert_list(row_facet, types = "data_extract_spec", null.ok = TRUE) - if (!all(vapply(row_facet, function(x) !x$select$multiple, logical(1)))) { - stop("'row_facet' should not allow multiple selection") - } - if (!all(vapply(col_facet, function(x) !x$select$multiple, logical(1)))) { - stop("'col_facet' should not allow multiple selection") - } - checkmate::assert_character(shape) + assert_single_selection(col_facet) + + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") + checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) + checkmate::assert_numeric( + plot_width[1], + lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" + ) - checkmate::assert_int(max_deg, lower = 1L) - checkmate::assert_scalar(table_dec) - checkmate::assert_flag(rotate_xaxis_labels) if (length(alpha) == 1) { checkmate::assert_numeric(alpha, any.missing = FALSE, finite = TRUE) } else { @@ -277,6 +280,8 @@ tm_g_scatterplot <- function(label = "Scatterplot", checkmate::assert_numeric(alpha[1], lower = alpha[2], upper = alpha[3], .var.name = "alpha") } + checkmate::assert_character(shape) + if (length(size) == 1) { checkmate::assert_numeric(size, any.missing = FALSE, finite = TRUE) } else { @@ -284,16 +289,18 @@ tm_g_scatterplot <- function(label = "Scatterplot", checkmate::assert_numeric(size[1], lower = size[2], upper = size[3], .var.name = "size") } - checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) - checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") - checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) - checkmate::assert_numeric( - plot_width[1], - lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" - ) + checkmate::assert_int(max_deg, lower = 1L) + checkmate::assert_flag(rotate_xaxis_labels) + ggtheme <- match.arg(ggtheme) + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + + checkmate::assert_scalar(table_dec) checkmate::assert_class(ggplot2_args, "ggplot2_args") + # End of assertions + # Send ui args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_scatterplotmatrix.R b/R/tm_g_scatterplotmatrix.R index a334686cd..01a19fda9 100644 --- a/R/tm_g_scatterplotmatrix.R +++ b/R/tm_g_scatterplotmatrix.R @@ -161,13 +161,19 @@ tm_g_scatterplotmatrix <- function(label = "Scatterplot Matrix", pre_output = NULL, post_output = NULL) { logger::log_info("Initializing tm_g_scatterplotmatrix") + + # Requires Suggested packages if (!requireNamespace("lattice", quietly = TRUE)) { stop("Cannot load lattice - please install the package or restart your session.") } + + # Normalize the parameters if (inherits(variables, "data_extract_spec")) variables <- list(variables) + # Start of assertions checkmate::assert_string(label) checkmate::assert_list(variables, types = "data_extract_spec") + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) @@ -176,7 +182,13 @@ tm_g_scatterplotmatrix <- function(label = "Scatterplot Matrix", lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" ) + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + # End of assertions + + # Send ui args args <- as.list(environment()) + module( label = label, server = srv_g_scatterplotmatrix, diff --git a/R/tm_missing_data.R b/R/tm_missing_data.R index ce4240ded..8c2235583 100644 --- a/R/tm_missing_data.R +++ b/R/tm_missing_data.R @@ -87,17 +87,22 @@ tm_missing_data <- function(label = "Missing data", ), pre_output = NULL, post_output = NULL) { + logger::log_info("Initializing tm_missing_data") + + # Requires Suggested packages if (!requireNamespace("gridExtra", quietly = TRUE)) { stop("Cannot load gridExtra - please install the package or restart your session.") } if (!requireNamespace("rlang", quietly = TRUE)) { stop("Cannot load rlang - please install the package or restart your session.") } - logger::log_info("Initializing tm_missing_data") + + # Normalize the parameters if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) + # Start of assertions checkmate::assert_string(label) - checkmate::assert_character(parent_dataname, min.len = 0, max.len = 1) + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) @@ -105,11 +110,18 @@ tm_missing_data <- function(label = "Missing data", plot_width[1], lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" ) + + checkmate::assert_character(parent_dataname, min.len = 0, max.len = 1) ggtheme <- match.arg(ggtheme) + plot_choices <- c("Summary Obs", "Summary Patients", "Combinations Main", "Combinations Hist", "By Subject") checkmate::assert_list(ggplot2_args, types = "ggplot2_args") checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices)) + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + # End of assertions + module( label, server = srv_page_missing_data, diff --git a/R/tm_outliers.R b/R/tm_outliers.R index 34cccf2b2..bbbf95797 100644 --- a/R/tm_outliers.R +++ b/R/tm_outliers.R @@ -130,13 +130,16 @@ tm_outliers <- function(label = "Outliers Module", pre_output = NULL, post_output = NULL) { logger::log_info("Initializing tm_outliers") + + # Normalize the parameters if (inherits(outlier_var, "data_extract_spec")) outlier_var <- list(outlier_var) if (inherits(categorical_var, "data_extract_spec")) categorical_var <- list(categorical_var) if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) - ggtheme <- match.arg(ggtheme) + # Start of assertions checkmate::assert_string(label) checkmate::assert_list(outlier_var, types = "data_extract_spec") + checkmate::assert_list(categorical_var, types = "data_extract_spec", null.ok = TRUE) if (is.list(categorical_var)) { lapply(categorical_var, function(x) { @@ -145,10 +148,26 @@ tm_outliers <- function(label = "Outliers Module", } }) } + + ggtheme <- match.arg(ggtheme) + plot_choices <- c("Boxplot", "Density Plot", "Cumulative Distribution Plot") checkmate::assert_list(ggplot2_args, types = "ggplot2_args") checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices)) + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") + checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) + checkmate::assert_numeric( + plot_width[1], + lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" + ) + + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + # End of assertions + + # Send ui args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_t_crosstable.R b/R/tm_t_crosstable.R index 8fdc090e6..a41ba1133 100644 --- a/R/tm_t_crosstable.R +++ b/R/tm_t_crosstable.R @@ -139,22 +139,31 @@ tm_t_crosstable <- function(label = "Cross Table", post_output = NULL, basic_table_args = teal.widgets::basic_table_args()) { logger::log_info("Initializing tm_t_crosstable") + + # Requires Suggested packages if (!requireNamespace("rtables", quietly = TRUE)) { stop("Cannot load rtables - please install the package or restart your session.") } + + # Normalize the parameters if (inherits(x, "data_extract_spec")) x <- list(x) if (inherits(y, "data_extract_spec")) y <- list(y) + # Start of assertions checkmate::assert_string(label) checkmate::assert_list(x, types = "data_extract_spec") + checkmate::assert_list(y, types = "data_extract_spec") - if (any(vapply(y, function(x) x$select$multiple, logical(1)))) { - stop("'y' should not allow multiple selection") - } + assert_single_selection(y) + checkmate::assert_flag(show_percentage) checkmate::assert_flag(show_total) + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) checkmate::assert_class(basic_table_args, classes = "basic_table_args") + # End of assertions + # Send ui args ui_args <- as.list(environment()) server_args <- list( diff --git a/R/tm_variable_browser.R b/R/tm_variable_browser.R index bc8bd03d7..930c37005 100644 --- a/R/tm_variable_browser.R +++ b/R/tm_variable_browser.R @@ -75,6 +75,8 @@ tm_variable_browser <- function(label = "Variable Browser", post_output = NULL, ggplot2_args = teal.widgets::ggplot2_args()) { logger::log_info("Initializing tm_variable_browser") + + # Requires Suggested packages if (!requireNamespace("sparkline", quietly = TRUE)) { stop("Cannot load sparkline - please install the package or restart your session.") } @@ -84,10 +86,16 @@ tm_variable_browser <- function(label = "Variable Browser", if (!requireNamespace("jsonlite", quietly = TRUE)) { stop("Cannot load jsonlite - please install the package or restart your session.") } + + # Start of assertions checkmate::assert_string(label) checkmate::assert_character(datasets_selected) checkmate::assert_character(parent_dataname, min.len = 0, max.len = 1) + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) checkmate::assert_class(ggplot2_args, "ggplot2_args") + # End of assertions + datasets_selected <- unique(datasets_selected) module( diff --git a/R/utils.R b/R/utils.R index cdcb4ece7..b4c94da6f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -285,3 +285,15 @@ is_tab_active_js <- function(id, name) { id, name ) } + +#' Assert single selection on `data_extract_spec` object +#' Helper to reduce code in assertions +#' @noRd +#' +assert_single_selection <- function(x, + .var.name = checkmate::vname(x)) { # nolint: object_name. + if (any(vapply(x, function(.x) .x$select$multiple, logical(1)))) { + stop("'", .var.name, "' should not allow multiple selection") + } + invisible(TRUE) +} diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R new file mode 100644 index 000000000..fe402f4ee --- /dev/null +++ b/tests/testthat/test-utils.R @@ -0,0 +1,22 @@ +testthat::test_that("assert_single_selection will pass if all elements have multiple choices disabled", { + mock_spec <- list(select = list(multiple = FALSE)) + + testthat::expect_true(assert_single_selection(list(mock_spec))) + testthat::expect_true(assert_single_selection(list(mock_spec, mock_spec))) +}) + +testthat::test_that("assert_single_selection fails when multiple selection is selected in any of the specs", { + mock_spec <- list(select = list(multiple = TRUE)) + mock_spec2 <- list(select = list(multiple = FALSE)) + + x <- list(mock_spec) + + testthat::expect_error( + assert_single_selection(x), + "'x' should not allow multiple selection" + ) + testthat::expect_error( + assert_single_selection(list(mock_spec2, mock_spec), .var.name = "x"), + "'x' should not allow multiple selection" + ) +}) From cdf949d2a1ddffa209f6c6c616a5aa870d1e55af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:25:27 +0100 Subject: [PATCH 02/19] chore: replace "Send ui args" with "Make UI args" --- R/tm_a_pca.R | 2 +- R/tm_file_viewer.R | 2 +- R/tm_front_page.R | 2 +- R/tm_g_association.R | 2 +- R/tm_g_bivariate.R | 2 +- R/tm_g_distribution.R | 2 +- R/tm_g_response.R | 2 +- R/tm_g_scatterplot.R | 2 +- R/tm_g_scatterplotmatrix.R | 2 +- R/tm_outliers.R | 2 +- R/tm_t_crosstable.R | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/R/tm_a_pca.R b/R/tm_a_pca.R index 4e341bb41..134ea6556 100644 --- a/R/tm_a_pca.R +++ b/R/tm_a_pca.R @@ -160,7 +160,7 @@ tm_a_pca <- function(label = "Principal Component Analysis", checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) data_extract_list <- list(dat = dat) diff --git a/R/tm_file_viewer.R b/R/tm_file_viewer.R index e46d6154f..bf16a287c 100644 --- a/R/tm_file_viewer.R +++ b/R/tm_file_viewer.R @@ -76,7 +76,7 @@ tm_file_viewer <- function(label = "File Viewer Module", } # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) module( diff --git a/R/tm_front_page.R b/R/tm_front_page.R index 54ada504e..f8bf83d0f 100644 --- a/R/tm_front_page.R +++ b/R/tm_front_page.R @@ -78,7 +78,7 @@ tm_front_page <- function(label = "Front page", checkmate::assert_flag(show_metadata) # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) module( diff --git a/R/tm_g_association.R b/R/tm_g_association.R index 8768ec8ed..009654212 100644 --- a/R/tm_g_association.R +++ b/R/tm_g_association.R @@ -167,7 +167,7 @@ tm_g_association <- function(label = "Association", checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices)) # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_bivariate.R b/R/tm_g_bivariate.R index 1d2d7d62a..6aa7d6b22 100644 --- a/R/tm_g_bivariate.R +++ b/R/tm_g_bivariate.R @@ -273,7 +273,7 @@ tm_g_bivariate <- function(label = "Bivariate Plots", checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_distribution.R b/R/tm_g_distribution.R index 3cb0345f9..624de3cd9 100644 --- a/R/tm_g_distribution.R +++ b/R/tm_g_distribution.R @@ -169,7 +169,7 @@ tm_g_distribution <- function(label = "Distribution Module", checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_response.R b/R/tm_g_response.R index 55dcebcd5..64640640a 100644 --- a/R/tm_g_response.R +++ b/R/tm_g_response.R @@ -196,7 +196,7 @@ tm_g_response <- function(label = "Response Plot", checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_scatterplot.R b/R/tm_g_scatterplot.R index 0e96eab2c..f739453ca 100644 --- a/R/tm_g_scatterplot.R +++ b/R/tm_g_scatterplot.R @@ -300,7 +300,7 @@ tm_g_scatterplot <- function(label = "Scatterplot", checkmate::assert_class(ggplot2_args, "ggplot2_args") # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_g_scatterplotmatrix.R b/R/tm_g_scatterplotmatrix.R index 01a19fda9..3cbf05e4f 100644 --- a/R/tm_g_scatterplotmatrix.R +++ b/R/tm_g_scatterplotmatrix.R @@ -186,7 +186,7 @@ tm_g_scatterplotmatrix <- function(label = "Scatterplot Matrix", checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) module( diff --git a/R/tm_outliers.R b/R/tm_outliers.R index bbbf95797..6ca76f986 100644 --- a/R/tm_outliers.R +++ b/R/tm_outliers.R @@ -167,7 +167,7 @@ tm_outliers <- function(label = "Outliers Module", checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) # End of assertions - # Send ui args + # Make UI args args <- as.list(environment()) data_extract_list <- list( diff --git a/R/tm_t_crosstable.R b/R/tm_t_crosstable.R index a41ba1133..f7ad03136 100644 --- a/R/tm_t_crosstable.R +++ b/R/tm_t_crosstable.R @@ -163,7 +163,7 @@ tm_t_crosstable <- function(label = "Cross Table", checkmate::assert_class(basic_table_args, classes = "basic_table_args") # End of assertions - # Send ui args + # Make UI args ui_args <- as.list(environment()) server_args <- list( From 928eca25ac5a0b28019ef06c866d6b5fbd92c6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:26:05 +0100 Subject: [PATCH 03/19] feat: reorder assertions and add necessary --- R/tm_a_regression.R | 47 +++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/R/tm_a_regression.R b/R/tm_a_regression.R index e3dd0bfd3..6e0dbc1fa 100644 --- a/R/tm_a_regression.R +++ b/R/tm_a_regression.R @@ -143,33 +143,56 @@ tm_a_regression <- function(label = "Regression Analysis", default_plot_type = 1, default_outlier_label = "USUBJID") { logger::log_info("Initializing tm_a_regression") + + # Normalizing data types if (inherits(regressor, "data_extract_spec")) regressor <- list(regressor) if (inherits(response, "data_extract_spec")) response <- list(response) if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) + # Start of assertions checkmate::assert_string(label) + checkmate::assert_list(regressor, types = "data_extract_spec") checkmate::assert_list(response, types = "data_extract_spec") - if (!all(vapply(response, function(x) !(x$select$multiple), logical(1)))) { - stop("'response' should not allow multiple selection") + assert_single_selection(response) + + checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") + checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) + checkmate::assert_numeric( + plot_width[1], + lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" + ) + + if (length(alpha) == 1) { + checkmate::assert_numeric(alpha, any.missing = FALSE, finite = TRUE) + } else { + checkmate::assert_numeric(alpha, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric(alpha[1], lower = alpha[2], upper = alpha[3], .var.name = "alpha") } - checkmate::assert_list(regressor, types = "data_extract_spec") + + if (length(size) == 1) { + checkmate::assert_numeric(size, any.missing = FALSE, finite = TRUE) + } else { + checkmate::assert_numeric(size, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric(size[1], lower = size[2], upper = size[3], .var.name = "size") + } + ggtheme <- match.arg(ggtheme) - checkmate::assert_string(default_outlier_label) + plot_choices <- c( "Response vs Regressor", "Residuals vs Fitted", "Normal Q-Q", "Scale-Location", "Cook's distance", "Residuals vs Leverage", "Cook's dist vs Leverage" ) checkmate::assert_list(ggplot2_args, types = "ggplot2_args") checkmate::assert_subset(names(ggplot2_args), c("default", plot_choices)) - checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) - checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") - checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) - checkmate::assert_numeric( - plot_width[1], - lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" - ) - # Send ui args + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_choice(default_plot_type, seq.int(1, length(plot_choices))) + checkmate::assert_string(default_outlier_label) + # End of assertions + + # Make UI args args <- as.list(environment()) args[["plot_choices"]] <- plot_choices data_extract_list <- list( From 48c96c3e80e18062c9aeda48714d53b01fe1b5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:28:49 +0100 Subject: [PATCH 04/19] fix: remove duplicated assertion --- R/tm_g_bivariate.R | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/R/tm_g_bivariate.R b/R/tm_g_bivariate.R index 6aa7d6b22..5e72316b2 100644 --- a/R/tm_g_bivariate.R +++ b/R/tm_g_bivariate.R @@ -217,6 +217,8 @@ tm_g_bivariate <- function(label = "Bivariate Plots", checkmate::assert_list(col_facet, types = "data_extract_spec", null.ok = TRUE) assert_single_selection(col_facet) + checkmate::assert_flag(facet) + checkmate::assert_list(color, types = "data_extract_spec", null.ok = TRUE) assert_single_selection(color) @@ -226,9 +228,6 @@ tm_g_bivariate <- function(label = "Bivariate Plots", checkmate::assert_list(size, types = "data_extract_spec", null.ok = TRUE) assert_single_selection(size) - checkmate::assert_flag(facet) - checkmate::assert_flag(rotate_xaxis_labels) - checkmate::assert_flag(swap_axes) checkmate::assert_flag(use_density) # Determines color, fill & size if they are not explicitly set From b43aecf06a6c19cce5b20f910fcc8d58a0628536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:58:13 +0100 Subject: [PATCH 05/19] tests: expand tests of assert_single_selection to include public API --- tests/testthat/test-utils.R | 62 ++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index fe402f4ee..4e29bf409 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1,13 +1,22 @@ -testthat::test_that("assert_single_selection will pass if all elements have multiple choices disabled", { - mock_spec <- list(select = list(multiple = FALSE)) +testthat::test_that("assert_single_selection succeeds if all elements have multiple choices disabled", { + mock_spec <- data_extract_spec( + dataname = "MOCK_DATASET", + select = teal.transform::select_spec(choices = c("A", "B"), multiple = FALSE) + ) testthat::expect_true(assert_single_selection(list(mock_spec))) testthat::expect_true(assert_single_selection(list(mock_spec, mock_spec))) }) testthat::test_that("assert_single_selection fails when multiple selection is selected in any of the specs", { - mock_spec <- list(select = list(multiple = TRUE)) - mock_spec2 <- list(select = list(multiple = FALSE)) + mock_spec <- data_extract_spec( + dataname = "MOCK_DATASET", + select = teal.transform::select_spec(choices = c("A", "B"), multiple = TRUE) + ) + mock_spec2 <- data_extract_spec( + dataname = "MOCK_DATASET", + select = teal.transform::select_spec(choices = c("A", "B"), multiple = FALSE) + ) x <- list(mock_spec) @@ -20,3 +29,48 @@ testthat::test_that("assert_single_selection fails when multiple selection is se "'x' should not allow multiple selection" ) }) + +testthat::test_that("assert_single_selection fails when multiple selection is enabled", { + mock_spec <- data_extract_spec( + dataname = "MOCK_DATASET", + select = teal.transform::select_spec(choices = c("A", "B"), multiple = TRUE) + ) + + # Suppress logger messages + logger::with_log_threshold( + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_spec, + mock_spec + ), + "'x' should not allow multiple selection" + ), + threshold = logger::FATAL, + namespace = "teal.modules.general" + ) +}) + +testthat::test_that("assert_single_selection succeeds when multiple selection is disabled", { + mock_spec <- data_extract_spec( + dataname = "ADSL", + select = teal.transform::select_spec( + choices = c("USUBJID", "AGE"), + multiple = FALSE + ) + ) + + # Suppress logger messages + logger::with_log_threshold( + testthat::expect_s3_class( + tm_g_bivariate( + "a label", + mock_spec, + mock_spec + ), + "teal_module" + ), + threshold = logger::FATAL, + namespace = "teal.modules.general" + ) +}) From c5294bbc4133d128d0d60e3c57b3ea9428cf6e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:14:10 +0100 Subject: [PATCH 06/19] tests: only use public functions --- tests/testthat/helper-functions.R | 9 +++ tests/testthat/test-utils.R | 92 +++++++++++++++---------------- 2 files changed, 52 insertions(+), 49 deletions(-) create mode 100644 tests/testthat/helper-functions.R diff --git a/tests/testthat/helper-functions.R b/tests/testthat/helper-functions.R new file mode 100644 index 000000000..5887b60f2 --- /dev/null +++ b/tests/testthat/helper-functions.R @@ -0,0 +1,9 @@ +local_logger_threshold <- function(threshold, envir = parent.frame()) { + old <- logger::log_threshold(namespace = "teal.modules.general") + if (!requireNamespace("withr", quietly = FALSE)) { + return(invisible(old)) + } + withr::defer(logger::log_threshold(old, namespace = "teal.modules.general"), envir = envir) + logger::log_threshold(threshold, namespace = "teal.modules.general") + invisible(old) +} diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 4e29bf409..8d0a14ce8 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1,14 +1,44 @@ testthat::test_that("assert_single_selection succeeds if all elements have multiple choices disabled", { + # Suppress logger messages + local_logger_threshold(logger::FATAL) + mock_spec <- data_extract_spec( dataname = "MOCK_DATASET", select = teal.transform::select_spec(choices = c("A", "B"), multiple = FALSE) ) - testthat::expect_true(assert_single_selection(list(mock_spec))) - testthat::expect_true(assert_single_selection(list(mock_spec, mock_spec))) + testthat::expect_s3_class( + tm_g_bivariate( + "a label", + mock_spec, + mock_spec + ), + "teal_module" + ) + + testthat::expect_s3_class( + tm_g_bivariate( + "a label", + list(mock_spec), + mock_spec + ), + "teal_module" + ) + + testthat::expect_s3_class( + tm_g_bivariate( + "a label", + list(mock_spec, mock_spec), + mock_spec + ), + "teal_module" + ) }) testthat::test_that("assert_single_selection fails when multiple selection is selected in any of the specs", { + # Suppress logger messages + local_logger_threshold(logger::FATAL) + mock_spec <- data_extract_spec( dataname = "MOCK_DATASET", select = teal.transform::select_spec(choices = c("A", "B"), multiple = TRUE) @@ -21,56 +51,20 @@ testthat::test_that("assert_single_selection fails when multiple selection is se x <- list(mock_spec) testthat::expect_error( - assert_single_selection(x), - "'x' should not allow multiple selection" - ) - testthat::expect_error( - assert_single_selection(list(mock_spec2, mock_spec), .var.name = "x"), - "'x' should not allow multiple selection" - ) -}) - -testthat::test_that("assert_single_selection fails when multiple selection is enabled", { - mock_spec <- data_extract_spec( - dataname = "MOCK_DATASET", - select = teal.transform::select_spec(choices = c("A", "B"), multiple = TRUE) - ) - - # Suppress logger messages - logger::with_log_threshold( - testthat::expect_error( - tm_g_bivariate( - "a label", - mock_spec, - mock_spec - ), - "'x' should not allow multiple selection" + tm_g_bivariate( + "a label", + x, + x ), - threshold = logger::FATAL, - namespace = "teal.modules.general" - ) -}) - -testthat::test_that("assert_single_selection succeeds when multiple selection is disabled", { - mock_spec <- data_extract_spec( - dataname = "ADSL", - select = teal.transform::select_spec( - choices = c("USUBJID", "AGE"), - multiple = FALSE - ) + "'x' should not allow multiple selection" ) - # Suppress logger messages - logger::with_log_threshold( - testthat::expect_s3_class( - tm_g_bivariate( - "a label", - mock_spec, - mock_spec - ), - "teal_module" + testthat::expect_error( + tm_g_bivariate( + "a label", + list(mock_spec2, mock_spec, data_extract_spec("EMPTY")), + x ), - threshold = logger::FATAL, - namespace = "teal.modules.general" + "'x' should not allow multiple selection" ) }) From edc1d275922dd2b2e9edf6e43279d8212a3ece40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:17:34 +0100 Subject: [PATCH 07/19] tests: add default spec failure test --- tests/testthat/test-utils.R | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 8d0a14ce8..810df7fb7 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -54,7 +54,7 @@ testthat::test_that("assert_single_selection fails when multiple selection is se tm_g_bivariate( "a label", x, - x + mock_spec2 ), "'x' should not allow multiple selection" ) @@ -62,8 +62,25 @@ testthat::test_that("assert_single_selection fails when multiple selection is se testthat::expect_error( tm_g_bivariate( "a label", - list(mock_spec2, mock_spec, data_extract_spec("EMPTY")), - x + list(mock_spec2, mock_spec), + mock_spec2 + ), + "'x' should not allow multiple selection" + ) +}) + +testthat::test_that("assert_single_selection fails when with default spec", { + # Suppress logger messages + local_logger_threshold(logger::FATAL) + + testthat::expect_error( + tm_g_bivariate( + "a label", + data_extract_spec("DEFAULT"), + data_extract_spec( + "VALID", + teal.transform::select_spec(choices = c("A", "B"), multiple = FALSE) + ) ), "'x' should not allow multiple selection" ) From 4e3f7fcf8074c7b6e23da61a0cd74de58699c902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:23:59 +0100 Subject: [PATCH 08/19] Merge pre-release --- R/tm_a_pca.R | 2 +- R/tm_a_regression.R | 103 +++++++++++++++++++++++++++++++++-------- man/tm_a_regression.Rd | 38 ++++++++------- 3 files changed, 107 insertions(+), 36 deletions(-) diff --git a/R/tm_a_pca.R b/R/tm_a_pca.R index 134ea6556..4da38b6d6 100644 --- a/R/tm_a_pca.R +++ b/R/tm_a_pca.R @@ -111,7 +111,7 @@ tm_a_pca <- function(label = "Principal Component Analysis", post_output = NULL) { logger::log_info("Initializing tm_a_pca") - # Normalizing data types + # Normalize the parameters if (inherits(dat, "data_extract_spec")) dat <- list(dat) if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) diff --git a/R/tm_a_regression.R b/R/tm_a_regression.R index 6e0dbc1fa..3f2a45e5d 100644 --- a/R/tm_a_regression.R +++ b/R/tm_a_regression.R @@ -1,4 +1,4 @@ -#' Scatterplot and regression model +#' `teal` module: Scatterplot and regression analysis #' #' Module for visualizing regression analysis, including scatterplots and #' various regression diagnostics plots. @@ -14,14 +14,6 @@ #' Regressor variables from an incoming dataset with filtering and selecting. #' @param response (`data_extract_spec` or `list` of multiple `data_extract_spec`) #' Response variables from an incoming dataset with filtering and selecting. -#' @param alpha (`integer(1)` or `integer(3)`, optional) Specifies point opacity. -#' - When the length of `alpha` is one: the plot points will have a fixed opacity. -#' - When the length of `alpha` is three: the plot points opacity are dynamically adjusted based on -#' vector of `value`, `min`, and `max`. -#' @param size (`integer(1)` or `integer(3)`, optional) Specifies point size. -#' - When the length of `size` is one: the plot point sizes will have a fixed size. -#' - When the length of `size` is three: the plot points size are dynamically adjusted based on -#' vector of `value`, `min`, and `max`. #' @param default_outlier_label (`character`, optional) The default column selected to label outliers. #' @param default_plot_type (`numeric`, optional) Defaults to Response vs Regressor. #' 1. Response vs Regressor @@ -31,10 +23,25 @@ #' 5. Cook's distance #' 6. Residuals vs Leverage #' 7. Cook's dist vs Leverage +#' @param label_segment_threshold (`numeric(1)` or `numeric(3)`) +#' Minimum distance between label and point on the plot that triggers the creation of +#' a line segment between the two. +#' This may happen when the label cannot be placed next to the point as it overlaps another +#' label or point. +#' The value is used as the `min.segment.length` parameter to the [ggrepel::geom_text_repel()] function. +#' +#' It can take the following forms: +#' - `numeric(1)`: Fixed value used for the minimum distance and the slider is not presented in the UI. +#' - `numeric(3)`: A slider is presented in the UI (under "Plot settings") to adjust the minimum distance dynamically. +#' +#' It takes the form of `c(value, min, max)` and it is passed to the `value_min_max` +#' argument in `teal.widgets::optionalSliderInputValMinMax`. #' #' @templateVar ggnames `r regression_names` #' @template ggplot2_args_multi #' +#' @inherit shared_params return +#' #' @examples #' # general data example #' library(teal.widgets) @@ -141,10 +148,11 @@ tm_a_regression <- function(label = "Regression Analysis", pre_output = NULL, post_output = NULL, default_plot_type = 1, - default_outlier_label = "USUBJID") { + default_outlier_label = "USUBJID", + label_segment_threshold = c(0.5, 0, 10)) { logger::log_info("Initializing tm_a_regression") - # Normalizing data types + # Normalize the parameters if (inherits(regressor, "data_extract_spec")) regressor <- list(regressor) if (inherits(response, "data_extract_spec")) response <- list(response) if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args) @@ -152,15 +160,20 @@ tm_a_regression <- function(label = "Regression Analysis", # Start of assertions checkmate::assert_string(label) checkmate::assert_list(regressor, types = "data_extract_spec") + checkmate::assert_list(response, types = "data_extract_spec") assert_single_selection(response) checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") + checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) checkmate::assert_numeric( plot_width[1], - lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" + lower = plot_width[2], + upper = plot_width[3], + null.ok = TRUE, + .var.name = "plot_width" ) if (length(alpha) == 1) { @@ -188,8 +201,20 @@ tm_a_regression <- function(label = "Regression Analysis", checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) - checkmate::assert_choice(default_plot_type, seq.int(1, length(plot_choices))) + checkmate::assert_choice(default_plot_type, seq.int(1L, length(plot_choices))) checkmate::assert_string(default_outlier_label) + + if (length(label_segment_threshold) == 1) { + checkmate::assert_numeric(label_segment_threshold, any.missing = FALSE, finite = TRUE) + } else { + checkmate::assert_numeric(label_segment_threshold, len = 3, any.missing = FALSE, finite = TRUE) + checkmate::assert_numeric( + label_segment_threshold[1], + lower = label_segment_threshold[2], + upper = label_segment_threshold[3], + .var.name = "label_segment_threshold" + ) + } # End of assertions # Make UI args @@ -287,6 +312,29 @@ ui_a_regression <- function(id, ...) { title = "Plot settings", teal.widgets::optionalSliderInputValMinMax(ns("alpha"), "Opacity:", args$alpha, ticks = FALSE), teal.widgets::optionalSliderInputValMinMax(ns("size"), "Points size:", args$size, ticks = FALSE), + teal.widgets::optionalSliderInputValMinMax( + inputId = ns("label_min_segment"), + label = div( + class = "teal-tooltip", + tagList( + "Label min. segment:", + icon("circle-info"), + span( + class = "tooltiptext", + paste( + "Use the slider to choose the cut-off value to define minimum distance between label and point", + "that generates a line segment.", + "It's only valid when 'Display outlier labels' is checked." + ) + ) + ) + ), + value_min_max = args$label_segment_threshold, + # Extra parameters to sliderInput + ticks = FALSE, + step = .1, + round = FALSE + ), selectInput( inputId = ns("ggtheme"), label = "Theme (by ggplot):", @@ -467,10 +515,23 @@ srv_a_regression <- function(id, ) }) + label_min_segment <- reactive({ + input$label_min_segment + }) + outlier_label <- reactive({ substitute( - expr = geom_text(label = label_col, hjust = 0, vjust = 1, color = "red"), - env = list(label_col = label_col()) + expr = ggrepel::geom_text_repel( + label = label_col, + color = "red", + hjust = 0, + vjust = 1, + max.overlaps = Inf, + min.segment.length = label_min_segment, + segment.alpha = 0.5, + seed = 123 + ), + env = list(label_col = label_col(), label_min_segment = label_min_segment()) ) }) @@ -638,16 +699,20 @@ srv_a_regression <- function(id, plot <- substitute( expr = plot + stat_qq( - geom = "text", + geom = ggrepel::GeomTextRepel, label = label_col %>% data.frame(label = .) %>% dplyr::filter(label != "cooksd == NaN") %>% unlist(), + color = "red", hjust = 0, - vjust = 1, - color = "red" + vjust = 0, + max.overlaps = Inf, + min.segment.length = label_min_segment, + segment.alpha = .5, + seed = 123 ), - env = list(plot = plot, label_col = label_col()) + env = list(plot = plot, label_col = label_col(), label_min_segment = label_min_segment()) ) } diff --git a/man/tm_a_regression.Rd b/man/tm_a_regression.Rd index 5d539d163..99aed7d27 100644 --- a/man/tm_a_regression.Rd +++ b/man/tm_a_regression.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/tm_a_regression.R \name{tm_a_regression} \alias{tm_a_regression} -\title{Scatterplot and regression model} +\title{\code{teal} module: Scatterplot and regression analysis} \usage{ tm_a_regression( label = "Regression Analysis", @@ -17,7 +17,8 @@ tm_a_regression( pre_output = NULL, post_output = NULL, default_plot_type = 1, - default_outlier_label = "USUBJID" + default_outlier_label = "USUBJID", + label_segment_threshold = c(0.5, 0, 10) ) } \arguments{ @@ -36,20 +37,6 @@ Response variables from an incoming dataset with filtering and selecting.} \item{plot_width}{optional, (\code{numeric}) Specifies the plot width as a three-element vector of \code{value}, \code{min}, and \code{max} for a slider encoding the plot width.} -\item{alpha}{(\code{integer(1)} or \code{integer(3)}, optional) Specifies point opacity. -\itemize{ -\item When the length of \code{alpha} is one: the plot points will have a fixed opacity. -\item When the length of \code{alpha} is three: the plot points opacity are dynamically adjusted based on -vector of \code{value}, \code{min}, and \code{max}. -}} - -\item{size}{(\code{integer(1)} or \code{integer(3)}, optional) Specifies point size. -\itemize{ -\item When the length of \code{size} is one: the plot point sizes will have a fixed size. -\item When the length of \code{size} is three: the plot points size are dynamically adjusted based on -vector of \code{value}, \code{min}, and \code{max}. -}} - \item{ggtheme}{optional, (\code{character}) \code{ggplot2} theme to be used by default. Defaults to \code{"gray"}.} \item{ggplot2_args}{optional, (\code{ggplot2_args}) object created by \code{\link[teal.widgets:ggplot2_args]{teal.widgets::ggplot2_args()}} @@ -79,6 +66,25 @@ adding context or further instructions. Elements like \code{shiny::helpText()} a }} \item{default_outlier_label}{(\code{character}, optional) The default column selected to label outliers.} + +\item{label_segment_threshold}{(\code{numeric(1)} or \code{numeric(3)}) +Minimum distance between label and point on the plot that triggers the creation of +a line segment between the two. +This may happen when the label cannot be placed next to the point as it overlaps another +label or point. +The value is used as the \code{min.segment.length} parameter to the \code{\link[ggrepel:geom_text_repel]{ggrepel::geom_text_repel()}} function. + +It can take the following forms: +\itemize{ +\item \code{numeric(1)}: Fixed value used for the minimum distance and the slider is not presented in the UI. +\item \code{numeric(3)}: A slider is presented in the UI (under "Plot settings") to adjust the minimum distance dynamically. + +It takes the form of \code{c(value, min, max)} and it is passed to the \code{value_min_max} +argument in \code{teal.widgets::optionalSliderInputValMinMax}. +}} +} +\value{ +Object of class \code{teal_module} to be used in \code{teal} applications. } \description{ Module for visualizing regression analysis, including scatterplots and From 8af721c1d518f0aa8c7ce64333806ae1572de476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 13:15:40 +0100 Subject: [PATCH 09/19] chore: rename tests file --- tests/testthat/{test-utils.R => test-tm_g_bivariate.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/{test-utils.R => test-tm_g_bivariate.R} (100%) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-tm_g_bivariate.R similarity index 100% rename from tests/testthat/test-utils.R rename to tests/testthat/test-tm_g_bivariate.R From e98f7b0b124087f20c2ba69daab9fe297d276528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:04:23 +0100 Subject: [PATCH 10/19] tests: testing tm_g_bivariate --- tests/testthat/helper-functions.R | 13 ++ tests/testthat/test-tm_g_bivariate.R | 211 ++++++++++++++++++++++----- 2 files changed, 188 insertions(+), 36 deletions(-) diff --git a/tests/testthat/helper-functions.R b/tests/testthat/helper-functions.R index 5887b60f2..8992327f4 100644 --- a/tests/testthat/helper-functions.R +++ b/tests/testthat/helper-functions.R @@ -7,3 +7,16 @@ local_logger_threshold <- function(threshold, envir = parent.frame()) { logger::log_threshold(threshold, namespace = "teal.modules.general") invisible(old) } + +# Create a mock data extact spec for tests +mock_data_extract_spec <- function(dataname = "MOCK_DATASET", + select_choices = sample(LETTERS, sample(2:10, 1)), + select_multiple = FALSE) { + teal.transform::data_extract_spec( + dataname = dataname, + select = teal.transform::select_spec( + choices = select_choices, + multiple = select_multiple + ) + ) +} diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index 810df7fb7..5d84e936d 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -1,87 +1,226 @@ -testthat::test_that("assert_single_selection succeeds if all elements have multiple choices disabled", { - # Suppress logger messages - local_logger_threshold(logger::FATAL) - - mock_spec <- data_extract_spec( - dataname = "MOCK_DATASET", - select = teal.transform::select_spec(choices = c("A", "B"), multiple = FALSE) - ) +testthat::test_that("tm_g_bivariate creates a `teal_module` object", { + local_logger_threshold(logger::FATAL) # Suppress logger messages testthat::expect_s3_class( tm_g_bivariate( "a label", - mock_spec, - mock_spec + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + plot_height = c(400, 100, 600), + plot_width = c(600, 100, 600) ), "teal_module" ) +}) + +testthat::test_that("tm_g_bivariate creates a `teal_module` object with default options", { + local_logger_threshold(logger::FATAL) # Suppress logger messages testthat::expect_s3_class( tm_g_bivariate( "a label", - list(mock_spec), - mock_spec + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE) ), "teal_module" ) +}) + +testthat::test_that("tm_g_bivariate creates a `teal_module` object with multiple data extract specs", { + local_logger_threshold(logger::FATAL) # Suppress logger messages testthat::expect_s3_class( tm_g_bivariate( "a label", - list(mock_spec, mock_spec), - mock_spec + list(mock_data_extract_spec(select_multiple = FALSE), mock_data_extract_spec(select_multiple = FALSE)), + list(mock_data_extract_spec(select_multiple = FALSE), mock_data_extract_spec(select_multiple = FALSE)), + plot_height = c(400, 100, 600), + plot_width = c(600, 100, 600) ), "teal_module" ) }) -testthat::test_that("assert_single_selection fails when multiple selection is selected in any of the specs", { - # Suppress logger messages - local_logger_threshold(logger::FATAL) +testthat::test_that("tm_g_bivariate creates a module with `ui_args` that have all arguments of function", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + mod <- tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE) + ) + + expect_contains( + names(mod$ui_args), + names(formals(tm_g_bivariate)) + ) +}) + +testthat::test_that("tm_g_bivariate creates a module with `ui_args` that have all arguments of function", { + local_logger_threshold(logger::FATAL) # Suppress logger messages - mock_spec <- data_extract_spec( - dataname = "MOCK_DATASET", - select = teal.transform::select_spec(choices = c("A", "B"), multiple = TRUE) + mod <- tm_g_bivariate( + "a label", + list( + mock_data_extract_spec(dataname = "A", select_multiple = FALSE), + mock_data_extract_spec(dataname = "B", select_multiple = FALSE) + ), + mock_data_extract_spec(dataname = "C", select_multiple = FALSE) ) - mock_spec2 <- data_extract_spec( - dataname = "MOCK_DATASET", - select = teal.transform::select_spec(choices = c("A", "B"), multiple = FALSE) + + expect_setequal( + mod$datanames, + c("A", "B", "C") ) +}) - x <- list(mock_spec) +# Test `x` and `y` arguments with invalid data_extract_spec + +testthat::test_that("tm_g_bivariate fails when `x` contains a spec with multiple selection", { + local_logger_threshold(logger::FATAL) # Suppress logger messages testthat::expect_error( tm_g_bivariate( "a label", - x, - mock_spec2 + list( + mock_data_extract_spec(select_multiple = TRUE) + ), + list() ), "'x' should not allow multiple selection" ) +}) + +testthat::test_that("tm_g_bivariate fails when `x` contains multiple spec with (at least one ) multiple selection", { + local_logger_threshold(logger::FATAL) # Suppress logger messages testthat::expect_error( tm_g_bivariate( "a label", - list(mock_spec2, mock_spec), - mock_spec2 + list( + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = TRUE) + ), + list(mock_data_extract_spec(select_multiple = FALSE)) ), "'x' should not allow multiple selection" ) }) -testthat::test_that("assert_single_selection fails when with default spec", { - # Suppress logger messages - local_logger_threshold(logger::FATAL) +testthat::test_that("tm_g_bivariate fails when `x` contains multiple spec with (at least one ) multiple selection", { + local_logger_threshold(logger::FATAL) # Suppress logger messages testthat::expect_error( tm_g_bivariate( "a label", - data_extract_spec("DEFAULT"), - data_extract_spec( - "VALID", - teal.transform::select_spec(choices = c("A", "B"), multiple = FALSE) + mock_data_extract_spec(select_multiple = FALSE), + list( + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = TRUE) ) ), - "'x' should not allow multiple selection" + "'y' should not allow multiple selection" + ) +}) + +testthat::test_that("tm_g_bivariate fails when `y` contains multiple spec with (at least one ) multiple selection", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + list( + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = TRUE) + ) + ), + "'y' should not allow multiple selection" + ) +}) + +# Test `plot_height` and `plot_width` arguments + +testthat::test_that("tm_g_bivariate fails when `plot_height` is not valid", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + plot_height = c(100, 10, 20) + ), + "Assertion on 'plot_height' failed: Element 1 is not <= 20" + ) +}) + +testthat::test_that("tm_g_bivariate fails when `plot_height` is not valid", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + plot_height = c(1, 10, 20) + ), + "Assertion on 'plot_height' failed: Element 1 is not >= 10" + ) +}) + +testthat::test_that("tm_g_bivariate fails when `plot_height` is not valid", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + plot_height = 100 + ), + "Assertion on 'plot_height' failed: Must have length 3, but has length 1" + ) +}) + +testthat::test_that("tm_g_bivariate fails when `plot_width` is not valid", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + plot_width = c(100, 10, 20) + ), + "Assertion on 'plot_width' failed: Element 1 is not <= 20" + ) +}) + +testthat::test_that("tm_g_bivariate fails when `plot_width` is not valid", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + plot_width = c(1, 10, 20) + ), + "Assertion on 'plot_width' failed: Element 1 is not >= 10" + ) +}) + +testthat::test_that("tm_g_bivariate fails when `plot_width` is not valid", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + plot_width = 100 + ), + "Assertion on 'plot_width' failed: Must have length 3, but has length 1" ) }) From 9483f20122d48957b4f71d39e6663eccb8c8e402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:27:13 +0100 Subject: [PATCH 11/19] tests: color_settings --- tests/testthat/test-tm_g_bivariate.R | 67 ++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index 5d84e936d..c347442b0 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -224,3 +224,70 @@ testthat::test_that("tm_g_bivariate fails when `plot_width` is not valid", { "Assertion on 'plot_width' failed: Must have length 3, but has length 1" ) }) + +# Test `color_settings` argument + +testthat::test_that("tm_g_bivariate fails when `color_setting` is FALSE and `color` is supplied", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + color_setting = FALSE, + color = mock_data_extract_spec() + ), + "'color_settings' argument needs to be set to TRUE if 'color', 'fill', and/or 'size' is/are supplied." + ) +}) + +testthat::test_that("tm_g_bivariate fails when `color_setting` is FALSE and `size` is supplied", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + color_setting = FALSE, + size = mock_data_extract_spec() + ), + "'color_settings' argument needs to be set to TRUE if 'color', 'fill', and/or 'size' is/are supplied." + ) +}) + +testthat::test_that("tm_g_bivariate fails when `color_setting` is FALSE and `fill` is supplied", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + testthat::expect_error( + tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + color_setting = FALSE, + fill = mock_data_extract_spec() + ), + "'color_settings' argument needs to be set to TRUE if 'color', 'fill', and/or 'size' is/are supplied." + ) +}) + +testthat::test_that("tm_g_bivariate determines `color`, `size` and `fill` when `color_setting` is TRUE", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + mod <- tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE), + color_setting = TRUE + ) + + expect_contains( + vapply( + unlist(mod$ui_args[c("color", "size", "fill")], recursive = FALSE), + class, + character(1) + ), + "data_extract_spec" + ) +}) From 9b78c18622c70eb1b7ac1e4c90ba4cef92de91cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:30:12 +0100 Subject: [PATCH 12/19] chore: missing testthat prefix --- tests/testthat/test-tm_g_bivariate.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index c347442b0..928ee69ba 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -282,7 +282,7 @@ testthat::test_that("tm_g_bivariate determines `color`, `size` and `fill` when ` color_setting = TRUE ) - expect_contains( + testthat::expect_contains( vapply( unlist(mod$ui_args[c("color", "size", "fill")], recursive = FALSE), class, From 407df25df17625dd088ef73fa065fc0c1e6d54d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:30:40 +0100 Subject: [PATCH 13/19] chore: missing testthat prefix --- tests/testthat/test-tm_g_bivariate.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index 928ee69ba..f86c8279a 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -50,7 +50,7 @@ testthat::test_that("tm_g_bivariate creates a module with `ui_args` that have al mock_data_extract_spec(select_multiple = FALSE) ) - expect_contains( + testthat::expect_contains( names(mod$ui_args), names(formals(tm_g_bivariate)) ) From b71252d64c0ea72cc79862c4a9209f0a2b73c24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:36:27 +0100 Subject: [PATCH 14/19] tests: add extra arguments to non-default test --- tests/testthat/test-tm_g_bivariate.R | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index f86c8279a..5babd384a 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -6,8 +6,17 @@ testthat::test_that("tm_g_bivariate creates a `teal_module` object", { "a label", mock_data_extract_spec(select_multiple = FALSE), mock_data_extract_spec(select_multiple = FALSE), + row_facet = mock_data_extract_spec(select_multiple = FALSE), + col_facet = mock_data_extract_spec(select_multiple = FALSE), + facet = TRUE, + color_setting = TRUE, + use_density = TRUE, + free_x_scales = TRUE, + free_y_scales = TRUE, plot_height = c(400, 100, 600), - plot_width = c(600, 100, 600) + plot_width = c(600, 100, 600), + rotate_xaxis_labels = TRUE, + swap_axes = TRUE ), "teal_module" ) From 6bc23002e7eba35c3ede18cee2830c8dd013e90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:51:44 +0100 Subject: [PATCH 15/19] tests: adds superficial tests on UI and server merged dataset --- tests/testthat/test-tm_g_bivariate.R | 85 ++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index 5babd384a..eab907bf3 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -83,6 +83,91 @@ testthat::test_that("tm_g_bivariate creates a module with `ui_args` that have al ) }) +testthat::test_that("tm_g_bivariate module UI function creates a shiny tag", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + mod <- tm_g_bivariate( + "a label", + mock_data_extract_spec(select_multiple = FALSE), + mock_data_extract_spec(select_multiple = FALSE) + ) + + testthat::expect_class( + do.call( + mod$ui, + c(list("a_id"), mod$ui_args) + ), + "shiny.tag" + ) +}) + +testthat::test_that("tm_g_bivariate module server", { + local_logger_threshold(logger::FATAL) # Suppress logger messages + + data <- teal_data() + data <- within(data, { + require(nestcolor) + CO2 <- data.frame(CO2) + }) + datanames(data) <- c("CO2") + join_keys(data) <- default_cdisc_join_keys[datanames(data)] + + mod <- tm_g_bivariate( + x = data_extract_spec( + dataname = "CO2", + select = select_spec( + label = "Select variable:", + choices = variable_choices(data[["CO2"]]), + selected = "conc", + multiple = FALSE, + fixed = FALSE + ) + ), + y = data_extract_spec( + dataname = "CO2", + select = select_spec( + label = "Select variable:", + choices = variable_choices(data[["CO2"]]), + selected = "uptake", + multiple = FALSE, + fixed = FALSE + ) + ), + row_facet = data_extract_spec( + dataname = "CO2", + select = select_spec( + label = "Select variable:", + choices = variable_choices(data[["CO2"]]), + selected = "Type", + fixed = FALSE + ) + ), + col_facet = data_extract_spec( + dataname = "CO2", + select = select_spec( + label = "Select variable:", + choices = variable_choices(data[["CO2"]]), + selected = "Treatment", + fixed = FALSE + ) + ), + ggplot2_args = ggplot2_args( + labs = list(subtitle = "Plot generated by Bivariate Module") + ) + ) + + shiny::testServer( + mod$server, + args = c(mod$server_args, data = shiny::reactive(data)), + expr = { + checkmate::expect_data_frame( + anl_merged_q()[["ANL"]], + min.rows = 1 + ) + } + ) +}) + # Test `x` and `y` arguments with invalid data_extract_spec testthat::test_that("tm_g_bivariate fails when `x` contains a spec with multiple selection", { From 30457d8df91461387362a681a01c5eaa4419eb55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:52:29 +0100 Subject: [PATCH 16/19] tests: minor correction --- tests/testthat/test-tm_g_bivariate.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index eab907bf3..7470d7cbf 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -92,7 +92,7 @@ testthat::test_that("tm_g_bivariate module UI function creates a shiny tag", { mock_data_extract_spec(select_multiple = FALSE) ) - testthat::expect_class( + testthat::expect_s3_class( do.call( mod$ui, c(list("a_id"), mod$ui_args) From edbef9064557f09a62513824ae7da63b0663d20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:54:06 +0100 Subject: [PATCH 17/19] tests: simplify server test --- tests/testthat/test-tm_g_bivariate.R | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index 7470d7cbf..4806e761b 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -101,16 +101,14 @@ testthat::test_that("tm_g_bivariate module UI function creates a shiny tag", { ) }) -testthat::test_that("tm_g_bivariate module server", { +testthat::test_that("tm_g_bivariate module server builds merged datasets", { local_logger_threshold(logger::FATAL) # Suppress logger messages - data <- teal_data() - data <- within(data, { + data <- within(teal_data(), { require(nestcolor) CO2 <- data.frame(CO2) }) datanames(data) <- c("CO2") - join_keys(data) <- default_cdisc_join_keys[datanames(data)] mod <- tm_g_bivariate( x = data_extract_spec( @@ -132,27 +130,6 @@ testthat::test_that("tm_g_bivariate module server", { multiple = FALSE, fixed = FALSE ) - ), - row_facet = data_extract_spec( - dataname = "CO2", - select = select_spec( - label = "Select variable:", - choices = variable_choices(data[["CO2"]]), - selected = "Type", - fixed = FALSE - ) - ), - col_facet = data_extract_spec( - dataname = "CO2", - select = select_spec( - label = "Select variable:", - choices = variable_choices(data[["CO2"]]), - selected = "Treatment", - fixed = FALSE - ) - ), - ggplot2_args = ggplot2_args( - labs = list(subtitle = "Plot generated by Bivariate Module") ) ) From da72fc77f7106ee67d39ddbb9cbd543d11624139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:35:10 +0100 Subject: [PATCH 18/19] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dawid Kałędkowski <6959016+gogonzo@users.noreply.github.com> Signed-off-by: André Veríssimo <211358+averissimo@users.noreply.github.com> --- tests/testthat/test-tm_g_bivariate.R | 79 +--------------------------- 1 file changed, 1 insertion(+), 78 deletions(-) diff --git a/tests/testthat/test-tm_g_bivariate.R b/tests/testthat/test-tm_g_bivariate.R index 4806e761b..1d97a37fc 100644 --- a/tests/testthat/test-tm_g_bivariate.R +++ b/tests/testthat/test-tm_g_bivariate.R @@ -50,22 +50,7 @@ testthat::test_that("tm_g_bivariate creates a `teal_module` object with multiple ) }) -testthat::test_that("tm_g_bivariate creates a module with `ui_args` that have all arguments of function", { - local_logger_threshold(logger::FATAL) # Suppress logger messages - - mod <- tm_g_bivariate( - "a label", - mock_data_extract_spec(select_multiple = FALSE), - mock_data_extract_spec(select_multiple = FALSE) - ) - - testthat::expect_contains( - names(mod$ui_args), - names(formals(tm_g_bivariate)) - ) -}) - -testthat::test_that("tm_g_bivariate creates a module with `ui_args` that have all arguments of function", { +testthat::test_that("tm_g_bivariate creates a module with datanames taken from data extracts", { local_logger_threshold(logger::FATAL) # Suppress logger messages mod <- tm_g_bivariate( @@ -83,68 +68,6 @@ testthat::test_that("tm_g_bivariate creates a module with `ui_args` that have al ) }) -testthat::test_that("tm_g_bivariate module UI function creates a shiny tag", { - local_logger_threshold(logger::FATAL) # Suppress logger messages - - mod <- tm_g_bivariate( - "a label", - mock_data_extract_spec(select_multiple = FALSE), - mock_data_extract_spec(select_multiple = FALSE) - ) - - testthat::expect_s3_class( - do.call( - mod$ui, - c(list("a_id"), mod$ui_args) - ), - "shiny.tag" - ) -}) - -testthat::test_that("tm_g_bivariate module server builds merged datasets", { - local_logger_threshold(logger::FATAL) # Suppress logger messages - - data <- within(teal_data(), { - require(nestcolor) - CO2 <- data.frame(CO2) - }) - datanames(data) <- c("CO2") - - mod <- tm_g_bivariate( - x = data_extract_spec( - dataname = "CO2", - select = select_spec( - label = "Select variable:", - choices = variable_choices(data[["CO2"]]), - selected = "conc", - multiple = FALSE, - fixed = FALSE - ) - ), - y = data_extract_spec( - dataname = "CO2", - select = select_spec( - label = "Select variable:", - choices = variable_choices(data[["CO2"]]), - selected = "uptake", - multiple = FALSE, - fixed = FALSE - ) - ) - ) - - shiny::testServer( - mod$server, - args = c(mod$server_args, data = shiny::reactive(data)), - expr = { - checkmate::expect_data_frame( - anl_merged_q()[["ANL"]], - min.rows = 1 - ) - } - ) -}) - # Test `x` and `y` arguments with invalid data_extract_spec testthat::test_that("tm_g_bivariate fails when `x` contains a spec with multiple selection", { From c0b46c75f73f4b08f98bcd4aa4fd03439dbcb686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:50:26 +0100 Subject: [PATCH 19/19] chore: refactor code to avoid adding withr to suggests --- tests/testthat/helper-functions.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testthat/helper-functions.R b/tests/testthat/helper-functions.R index 8992327f4..8ae39cb2a 100644 --- a/tests/testthat/helper-functions.R +++ b/tests/testthat/helper-functions.R @@ -1,9 +1,9 @@ local_logger_threshold <- function(threshold, envir = parent.frame()) { old <- logger::log_threshold(namespace = "teal.modules.general") - if (!requireNamespace("withr", quietly = FALSE)) { - return(invisible(old)) - } - withr::defer(logger::log_threshold(old, namespace = "teal.modules.general"), envir = envir) + + # Equivalent to withr::defer + thunk <- as.call(list(function() logger::log_threshold(old, namespace = "teal.modules.general"))) + do.call(base::on.exit, list(thunk, TRUE, FALSE), envir = envir) logger::log_threshold(threshold, namespace = "teal.modules.general") invisible(old) }