# ============================================================================= # Tests for show_when (Conditional Visibility) System # ============================================================================= # # These tests verify: # 1. show_when_open() / show_when_close() helpers produce correct HTML # 2. .parse_show_when() converts R formulas to correct JSON conditions # 3. .generate_single_viz() emits show_when wrapper calls correctly # 4. .generate_global_setup_chunk() includes fallback definitions when needed # 5. show_when.js integration expectations (HTML structure) # ============================================================================= library(testthat) library(dashboardr) # ============================================================================= # SECTION 1: show_when_open() / show_when_close() Helpers # ============================================================================= describe("show_when_open()", { it("emits a div with data-show-when attribute", { json <- '{"var":"time_period","op":"in","val":["2022","2024"]}' out <- capture.output(show_when_open(json)) expect_length(out, 1) expect_match(out, '
"), fixed = TRUE) }) it("handles complex nested JSON conditions", { json <- '{"op":"and","conditions":[{"var":"a","op":"eq","val":"1"},{"var":"b","op":"neq","val":"2"}]}' out <- capture.output(show_when_open(json)) expect_match(out, json, fixed = TRUE) }) }) describe("show_when_close()", { it("emits a closing div tag", { out <- capture.output(show_when_close()) expect_length(out, 1) expect_equal(trimws(out), "
") }) }) describe("show_when_open / show_when_close round-trip", { it("produces well-formed HTML when used together", { json <- '{"var":"wave","op":"eq","val":"1"}' out <- capture.output({ show_when_open(json) cat("

content

\n") show_when_close() }) html <- paste(out, collapse = "\n") # Opening tag, some content, closing tag expect_match(html, '
content

", fixed = TRUE) expect_match(html, "
", fixed = TRUE) }) }) # ============================================================================= # SECTION 2: .parse_show_when() (Formula → JSON) # ============================================================================= describe(".parse_show_when()", { parse <- dashboardr:::.parse_show_when it("returns NULL for NULL input", { expect_null(parse(NULL)) }) it("errors on non-formula input", { expect_error(parse("not a formula"), "formula") }) it("parses equality operator", { json <- as.character(parse(~ status == "active")) parsed <- jsonlite::fromJSON(json) expect_equal(parsed$var, "status") expect_equal(parsed$op, "eq") expect_equal(parsed$val, "active") }) it("parses inequality operator", { json <- as.character(parse(~ status != "deleted")) parsed <- jsonlite::fromJSON(json) expect_equal(parsed$var, "status") expect_equal(parsed$op, "neq") expect_equal(parsed$val, "deleted") }) it("parses %in% operator", { json <- as.character(parse(~ time_period %in% c("2022", "2024"))) parsed <- jsonlite::fromJSON(json) expect_equal(parsed$var, "time_period") expect_equal(parsed$op, "in") expect_equal(parsed$val, c("2022", "2024")) }) it("parses AND conditions", { json <- as.character(parse(~ status == "active" & wave == "1")) parsed <- jsonlite::fromJSON(json) expect_equal(parsed$op, "and") # jsonlite converts the conditions array to a data.frame (cols = var, op, val) expect_equal(nrow(parsed$conditions), 2) expect_true("status" %in% parsed$conditions$var) expect_true("wave" %in% parsed$conditions$var) }) it("parses OR conditions", { json <- as.character(parse(~ status == "a" | status == "b")) parsed <- jsonlite::fromJSON(json) expect_equal(parsed$op, "or") expect_equal(nrow(parsed$conditions), 2) }) it("parses comparison operators (>, <, >=, <=)", { json_gt <- as.character(parse(~ score > 50)) parsed_gt <- jsonlite::fromJSON(json_gt) expect_equal(parsed_gt$op, "gt") expect_equal(parsed_gt$val, 50) json_lt <- as.character(parse(~ year < 2020)) parsed_lt <- jsonlite::fromJSON(json_lt) expect_equal(parsed_lt$op, "lt") expect_equal(parsed_lt$val, 2020) json_gte <- as.character(parse(~ age >= 18)) parsed_gte <- jsonlite::fromJSON(json_gte) expect_equal(parsed_gte$op, "gte") expect_equal(parsed_gte$val, 18) json_lte <- as.character(parse(~ score <= 100)) parsed_lte <- jsonlite::fromJSON(json_lte) expect_equal(parsed_lte$op, "lte") expect_equal(parsed_lte$val, 100) }) it("errors on truly unsupported operators", { expect_error(parse(~ x %% 5), "Unsupported operator") }) }) # ============================================================================= # SECTION 3: .generate_single_viz() show_when wrapper emission # ============================================================================= describe("viz chunk generation with show_when", { gen_viz <- dashboardr:::.generate_single_viz it("does not add results: asis or show_when calls when show_when is NULL", { spec <- list( viz_type = "stackedbar", title = "Test Chart", x_var = "x", y_var = "y" ) lines <- gen_viz("test_viz", spec) combined <- paste(lines, collapse = "\n") expect_false(grepl("results.*asis", combined)) expect_false(grepl("show_when_open", combined)) expect_false(grepl("show_when_close", combined)) }) it("adds results: asis and show_when_open/close when show_when is set", { spec <- list( viz_type = "stackedbar", title = "Test Chart", x_var = "x", y_var = "y", show_when = ~ wave %in% c("1", "2") ) lines <- gen_viz("test_viz", spec) combined <- paste(lines, collapse = "\n") expect_match(combined, "results.*asis", perl = TRUE) expect_match(combined, "show_when_open(", fixed = TRUE) expect_match(combined, "show_when_close()", fixed = TRUE) }) it("embeds correct JSON inside show_when_open call", { spec <- list( viz_type = "stackedbar", title = "Test Chart", x_var = "x", y_var = "y", show_when = ~ time_period == "Over Time" ) lines <- gen_viz("test_viz", spec) combined <- paste(lines, collapse = "\n") # The JSON should be single-quoted in the generated R code expect_match(combined, "show_when_open('", fixed = TRUE) # Verify the JSON content is valid open_line <- lines[grep("show_when_open", lines)] json_str <- sub(".*show_when_open\\('(.+)'\\).*", "\\1", open_line) parsed <- jsonlite::fromJSON(json_str) expect_equal(parsed$var, "time_period") expect_equal(parsed$op, "eq") expect_equal(parsed$val, "Over Time") }) it("show_when_open appears before viz code and show_when_close after", { spec <- list( viz_type = "stackedbar", title = "Test Chart", x_var = "x", y_var = "y", show_when = ~ wave == "1" ) lines <- gen_viz("test_viz", spec) open_idx <- grep("show_when_open", lines) close_idx <- grep("show_when_close", lines) viz_idx <- grep("viz_stackedbar", lines) expect_length(open_idx, 1) expect_length(close_idx, 1) expect_true(length(viz_idx) > 0) # open before viz, close after expect_true(open_idx[1] < min(viz_idx)) expect_true(close_idx[1] > max(viz_idx)) }) it("does not pass show_when to the viz function itself", { spec <- list( viz_type = "stackedbar", title = "Test Chart", x_var = "x", y_var = "y", show_when = ~ wave == "1" ) lines <- gen_viz("test_viz", spec) # The show_when param should NOT appear as an argument to viz_stackedbar() # (it's listed in the excluded params) viz_lines <- lines[grep("viz_stackedbar", lines):length(lines)] viz_call <- paste(viz_lines, collapse = "\n") # show_when should only appear in show_when_open(), not as a parameter matches <- gregexpr("show_when", viz_call)[[1]] open_matches <- gregexpr("show_when_open|show_when_close", viz_call) # Every occurrence of "show_when" should be part of show_when_open/close # i.e. show_when should not appear as a standalone parameter # Simplest check: "show_when =" should not appear expect_false(grepl("show_when\\s*=", viz_call)) }) }) # ============================================================================= # SECTION 4: Setup Chunk Fallback Definitions # ============================================================================= describe("global setup chunk show_when fallback", { gen_setup <- dashboardr:::.generate_global_setup_chunk it("includes show_when helper fallback when page needs_show_when = TRUE", { page <- list(needs_show_when = TRUE) lines <- gen_setup(page) combined <- paste(lines, collapse = "\n") expect_match(combined, "show_when_open", fixed = TRUE) expect_match(combined, "show_when_close", fixed = TRUE) expect_match(combined, "if (!exists(", fixed = TRUE) }) it("does NOT include show_when fallback when needs_show_when is FALSE", { page <- list(needs_show_when = FALSE) lines <- gen_setup(page) combined <- paste(lines, collapse = "\n") expect_false(grepl("show_when_open", combined)) }) it("does NOT include show_when fallback when needs_show_when is NULL", { page <- list() lines <- gen_setup(page) combined <- paste(lines, collapse = "\n") expect_false(grepl("show_when_open", combined)) }) it("fallback defines both show_when_open and show_when_close", { page <- list(needs_show_when = TRUE) lines <- gen_setup(page) combined <- paste(lines, collapse = "\n") # Both functions should be defined in the fallback block expect_match(combined, "show_when_open", fixed = TRUE) expect_match(combined, "show_when_close", fixed = TRUE) expect_match(combined, "function", fixed = TRUE) }) }) # ============================================================================= # SECTION 5: HTML Structure Contract (for show_when.js) # ============================================================================= describe("show_when HTML structure contract", { it("uses the CSS class 'viz-show-when' that show_when.js queries", { json <- '{"var":"x","op":"eq","val":"1"}' out <- capture.output(show_when_open(json)) # show_when.js does: document.querySelectorAll('[data-show-when]') expect_match(out, 'class="viz-show-when"', fixed = TRUE) expect_match(out, "data-show-when=", fixed = TRUE) }) it("data-show-when attribute contains valid JSON", { json <- '{"var":"time_period","op":"in","val":["Wave 1","Wave 2"]}' out <- capture.output(show_when_open(json)) # Extract the JSON from the attribute attr_val <- sub(".*data-show-when='([^']+)'.*", "\\1", out) parsed <- jsonlite::fromJSON(attr_val) expect_equal(parsed$var, "time_period") expect_equal(parsed$op, "in") expect_equal(parsed$val, c("Wave 1", "Wave 2")) }) }) # ============================================================================= # SECTION 6: Edge Cases # ============================================================================= describe("show_when edge cases", { it("handles single-value %in% correctly", { json <- as.character(dashboardr:::.parse_show_when(~ x %in% c("only_one"))) parsed <- jsonlite::fromJSON(json) expect_equal(parsed$op, "in") expect_equal(parsed$val, "only_one") }) it("handles numeric values in conditions", { json <- as.character(dashboardr:::.parse_show_when(~ year == 2024)) parsed <- jsonlite::fromJSON(json) expect_equal(parsed$val, 2024) }) it("handles values with spaces", { json <- as.character(dashboardr:::.parse_show_when(~ category %in% c("Wave 1", "Over Time"))) parsed <- jsonlite::fromJSON(json) expect_equal(parsed$val, c("Wave 1", "Over Time")) # Also verify the generated HTML handles spaces correctly out <- capture.output(show_when_open(json)) expect_match(out, "Wave 1", fixed = TRUE) expect_match(out, "Over Time", fixed = TRUE) }) it("multiple viz chunks with different show_when conditions stay independent", { gen_viz <- dashboardr:::.generate_single_viz spec1 <- list( viz_type = "stackedbar", title = "A", x_var = "x", y_var = "y", show_when = ~ wave == "1" ) spec2 <- list( viz_type = "timeline", title = "B", time_var = "t", y_var = "y", show_when = ~ wave == "Over Time" ) lines1 <- gen_viz("viz1", spec1) lines2 <- gen_viz("viz2", spec2) # Each should have exactly one open and one close expect_equal(sum(grepl("show_when_open", lines1)), 1) expect_equal(sum(grepl("show_when_close", lines1)), 1) expect_equal(sum(grepl("show_when_open", lines2)), 1) expect_equal(sum(grepl("show_when_close", lines2)), 1) # They should contain different JSON open1 <- lines1[grep("show_when_open", lines1)] open2 <- lines2[grep("show_when_open", lines2)] expect_false(identical(open1, open2)) }) })