tests/testthat/test-as_flextable.R

test_that("Can create flextable object that works with different styles", {
  analysisfun <- function(x, ...) {
    in_rows(
      row1 = 5,
      row2 = c(1, 2),
      .row_footnotes = list(row1 = "row 1 - row footnote"),
      .cell_footnotes = list(row2 = "row 2 - cell footnote")
    )
  }

  lyt <- basic_table() %>%
    split_cols_by("ARM") %>%
    split_cols_by("SEX", split_fun = keep_split_levels(c("M", "F"))) %>%
    split_rows_by("STRATA1") %>%
    summarize_row_groups() %>%
    split_rows_by("RACE", split_fun = keep_split_levels(c("WHITE", "ASIAN"))) %>%
    analyze("AGE", afun = analysisfun)


  tbl <- build_table(lyt, ex_adsl)
  ft <- tt_to_flextable(tbl, total_page_width = 20)
  expect_equal(sum(unlist(nrow(ft))), 20)

  expect_silent(ft3 <- tt_to_flextable(tbl, theme = NULL))

  # Custom theme
  special_bold <- list(
    "header" = list("i" = c(1, 2), "j" = c(1, 3)),
    "body" = list("i" = c(1, 2), "j" = 1)
  )
  custom_theme <- theme_docx_default(
    font_size = 10,
    font = "Brush Script MT",
    border = officer::fp_border(color = "pink", width = 2),
    bold = NULL,
    bold_manual = special_bold
  )
  expect_silent(tt_to_flextable(tbl, theme = custom_theme))

  # Custom theme error
  special_bold <- list(
    "header" = list("asdai" = c(1, 2), "j" = c(1, 3)),
    "body" = list("i" = c(1, 2), "j" = 1)
  )
  custom_theme <- theme_docx_default(
    font_size = 10,
    font = "Brush Script MT",
    bold = NULL,
    bold_manual = special_bold
  )
  expect_error(tt_to_flextable(tbl, theme = custom_theme), regexp = "header")

  # header colcounts not in a newline works
  topleft_t1 <- topleft_t2 <- basic_table(show_colcounts = TRUE) %>%
    split_rows_by("ARM", label_pos = "topleft") %>%
    split_cols_by("STRATA1")

  topleft_t1 <- topleft_t1 %>%
    analyze("BMRKR1") %>%
    build_table(DM)
  topleft_t1a <- tt_to_flextable(topleft_t1, counts_in_newline = FALSE)
  topleft_t1b <- tt_to_flextable(topleft_t1, counts_in_newline = TRUE)

  topleft_t2 <- topleft_t2 %>%
    split_rows_by("SEX", label_pos = "topleft") %>%
    analyze("BMRKR1") %>%
    build_table(DM) %>%
    tt_to_flextable(counts_in_newline = FALSE)

  expect_equal(flextable::nrow_part(topleft_t2, part = "header"), 2L)
  expect_equal(flextable::nrow_part(topleft_t1a, part = "header"), 1L)
  expect_equal(flextable::nrow_part(topleft_t1b, part = "header"), 1L)
})

test_that("tt_to_flextable does not create different cells when colcounts (or multiple) on different lines", {
  lyt <- basic_table(show_colcounts = TRUE) %>%
    split_rows_by("ARM", label_pos = "topleft") %>%
    split_rows_by("STRATA1", label_pos = "topleft") %>%
    split_cols_by("STRATA1", split_fun = keep_split_levels("B"), show_colcounts = TRUE) %>%
    split_cols_by("SEX", split_fun = keep_split_levels(c("F", "M"))) %>%
    split_cols_by("COUNTRY", split_fun = keep_split_levels("CHN")) %>%
    analyze("AGE")

  tbl <- build_table(lyt, ex_adsl)
  ft1 <- tt_to_flextable(tbl, counts_in_newline = FALSE)
  ft2 <- tt_to_flextable(tbl, counts_in_newline = TRUE)

  expect_equal(flextable::nrow_part(ft1, "header"), flextable::nrow_part(ft2, "header"))
})

test_that("check titles bold and html theme", {
  lyt <- basic_table(show_colcounts = TRUE) %>%
    split_rows_by("ARM", label_pos = "topleft") %>%
    split_rows_by("STRATA1", label_pos = "topleft") %>%
    split_cols_by("STRATA1", split_fun = keep_split_levels("B"), show_colcounts = TRUE) %>%
    split_cols_by("SEX", split_fun = keep_split_levels(c("F", "M"))) %>%
    split_cols_by("COUNTRY", split_fun = keep_split_levels("CHN")) %>%
    analyze("AGE")

  tbl <- build_table(lyt, ex_adsl)
  main_title(tbl) <- "Main title"
  subtitles(tbl) <- c("Some Many", "Subtitles")
  main_footer(tbl) <- c("Some Footer", "Mehr")

  expect_silent(ft1 <- tt_to_flextable(tbl, theme = theme_html_default(), bold_titles = FALSE))
  expect_silent(ft1 <- tt_to_flextable(tbl, theme = theme_html_default(), bold_titles = c(2, 3)))
  expect_error(ft1 <- tt_to_flextable(tbl, theme = theme_html_default(), bold_titles = c(2, 3, 5)))
})

test_that("check pagination", {
  lyt <- basic_table(show_colcounts = TRUE) %>%
    split_rows_by("ARM", label_pos = "topleft", page_by = TRUE) %>%
    split_rows_by("STRATA1", label_pos = "topleft") %>%
    split_cols_by("STRATA1", split_fun = keep_split_levels("B"), show_colcounts = TRUE) %>%
    split_cols_by("SEX", split_fun = keep_split_levels(c("F", "M"))) %>%
    split_cols_by("COUNTRY", split_fun = keep_split_levels("CHN")) %>%
    analyze("AGE")

  tbl <- build_table(lyt, ex_adsl)

  main_title(tbl) <- "Main title"
  subtitles(tbl) <- c("Some Many", "Subtitles")
  main_footer(tbl) <- c("Some Footer", "Mehr")
  prov_footer(tbl) <- "Some prov Footer"

  expect_no_error(out <- tt_to_flextable(tbl, paginate = TRUE, lpp = 100))
  expect_equal(length(out), 3L)
})

test_that("check colwidths in flextable object", {
  lyt <- basic_table(show_colcounts = TRUE) %>%
    split_rows_by("ARM", label_pos = "topleft", page_by = TRUE) %>%
    split_rows_by("STRATA1", label_pos = "topleft") %>%
    split_cols_by("STRATA1", split_fun = keep_split_levels("B"), show_colcounts = TRUE) %>%
    split_cols_by("SEX", split_fun = keep_split_levels(c("F", "M"))) %>%
    split_cols_by("COUNTRY", split_fun = keep_split_levels("CHN")) %>%
    analyze("AGE")

  tbl <- build_table(lyt, ex_adsl)

  main_title(tbl) <- "Main title"
  subtitles(tbl) <- c("Some Many", "Subtitles")
  main_footer(tbl) <- c("Some Footer", "Mehr")
  prov_footer(tbl) <- "Some prov Footer"

  cw <- c(0.9, 0.05, 0.05)
  spd <- section_properties_default(orientation = "landscape")
  fin_cw <- cw * spd$page_size$width / 2 / sum(cw)

  # Fixed total width is / 2
  flx_res <- tt_to_flextable(tbl,
    total_page_width = spd$page_size$width / 2,
    counts_in_newline = TRUE,
    autofit_to_page = TRUE,
    bold_titles = TRUE,
    colwidths = cw
  ) # if you add cw then autofit_to_page = FALSE
  dflx <- dim(flx_res)
  testthat::expect_equal(fin_cw, unname(dflx$widths))
})

test_that("tt_to_flextable works with {rlistings}' objects", {
  lsting <- as_listing(
    df = head(formatters::ex_adae, n = 50),
    key_cols = c("USUBJID", "ARM"),
    disp_cols = c("AETOXGR", "AEDECOD", "AESEV"),
    main_title = "Listing of Adverse Events (First 50 Records)",
    main_footer = "Source: formatters::ex_adae example dataset"
  )

  expect_no_error(out <- tt_to_flextable(lsting))
  expect_equal(flextable::nrow_part(out), nrow(lsting))
  expect_equal(flextable::nrow_part(out, part = "header"), length(all_titles(lsting)) + 1)
})
test_that("tt_to_flextable handles basic rlistings object correctly", {
  # --- Setup ---
  test_data <- head(formatters::ex_adae, n = 50)
  listing_title <- "Listing of Adverse Events (First 50 Records)"
  listing_footer <- "Source: formatters::ex_adae example dataset"
  key_cols <- c("USUBJID", "ARM")
  disp_cols <- c("AETOXGR", "AEDECOD", "AESEV")

  lsting <- as_listing(
    df = test_data,
    key_cols = key_cols,
    disp_cols = disp_cols,
    main_title = listing_title,
    main_footer = listing_footer
  )
  # Add a subtitle for header row count check robustness
  subtitles(lsting) <- "A subtitle for testing"

  expected_titles <- all_titles(lsting) # Main title + subtitle
  expected_col_keys <- c(key_cols, disp_cols)
  expected_data_rows <- nrow(test_data) # 50

  # --- Action ---
  # Use expect_no_error to ensure it runs and capture output
  out <- NULL # Initialize to avoid potential issues if expect_no_error fails early
  expect_no_error(out <- tt_to_flextable(lsting))

  # --- Checks ---
  # Check 1: Output Type
  expect_s3_class(out, "flextable")

  # Check 2: Dimensions
  expect_equal(flextable::nrow_part(out, part = "body"), expected_data_rows,
    label = "Number of body rows should match input data rows (assuming no separator rows added)"
  )

  # Column count (based on keys in flextable)
  expect_equal(length(out$col_keys), length(expected_col_keys),
    label = "Number of columns in flextable"
  )

  # Check 3: Header Structure and Content
  # Number of header rows = titles + column names row
  expect_equal(flextable::nrow_part(out, part = "header"), length(expected_titles) + 1,
    label = "Number of header rows (titles + colnames)"
  )

  # Column keys/names (order matters)
  expect_equal(out$col_keys, expected_col_keys,
    label = "Column keys/names in flextable"
  )

  # Check 4: Footer Content
  # This might need adjustment based on how tt_to_flextable handles footers
  expect_match(out$footer$dataset[[1]], listing_footer,
    fixed = TRUE,
    label = "Flextable footer should contain the listing's main footer text"
  )
})

test_that("tt_to_flextable handles rlistings with active separators", {
  # Create data where the separator column *changes*
  test_data_sep <- data.frame(
    USUBJID = paste0("S", rep(1:2, each = 3)),
    ARM = rep(c("A", "A", "B"), times = 2), # ARM changes within Subject 1 and 2
    AETOXGR = rep(1:3, times = 2),
    AEDECOD = LETTERS[1:6],
    AESEV = rep(c("MILD", "MOD", "SEVERE"), 2)
  )
  # Expected separators: After row 2 (A->B), After row 5 (A->B) = 2 separators

  lsting_sep <- as_listing(
    df = test_data_sep,
    key_cols = c("USUBJID"),
    disp_cols = c("ARM", "AETOXGR", "AEDECOD", "AESEV"),
    add_trailing_sep = "ARM"
  )

  out_sep <- NULL
  expect_no_error(out_sep <- tt_to_flextable(lsting_sep))
  expect_s3_class(out_sep, "flextable")

  # *** Crucial Check: Row count should NOT change ***
  # This assumes tt_to_flextable uses hline or padding, not adding physical rows
  expect_equal(
    flextable::nrow_part(out_sep, part = "body"),
    nrow(test_data_sep), # Should equal original data rows (6)
    label = "Body row count should match original data rows"
  )
})

Try the rtables.officer package in your browser

Any scripts or data that you put into this service are public.

rtables.officer documentation built on June 8, 2025, 12:44 p.m.