context("Pagination") test_that("Page by splitting works", { lyt <- basic_table(title = "big title") %>% split_rows_by("SEX", page_by = TRUE) %>% analyze("AGE") tt <- build_table(lyt, DM) ttlst <- paginate_table(tt) expect_identical( names(ttlst), levels(DM$SEX) ) expect_error( { basic_table(title = "big title") %>% analyze("AGE") %>% split_rows_by("SEX", page_by = TRUE) %>% analyze("AGE") }, "page_by splits cannot have top-level siblings" ) expect_error( { basic_table() %>% split_rows_by("SEX") %>% split_rows_by("ARM", page_by = TRUE) %>% analyze("AGE") }, "page_by splits cannot be nested within non-page_by splits" ) lyt2 <- basic_table( title = "main title", subtitles = "subtitle", main_footer = "main footer", prov_footer = "provenance footer" ) %>% split_cols_by("ARM") %>% split_cols_by("SEX", split_fun = keep_split_levels(c("F", "M"))) %>% split_rows_by("STRATA1", split_fun = keep_split_levels(c("A", "B")), page_by = TRUE, page_prefix = "Stratum") %>% split_rows_by("RACE", split_fun = keep_split_levels(c("ASIAN", "WHITE"))) %>% summarize_row_groups() %>% analyze("AGE", afun = function(x, ...) { in_rows( "mean (sd)" = rcell( c(mean(x), sd(x)), format = "xx.x (xx.x)" ), "range" = rcell(range(x), format = "xx.x - xx.x") ) }) tbl2 <- build_table(lyt2, ex_adsl) ttlst2 <- paginate_table(tbl2, lpp = 16, cpp = 55) expect_equal( length(ttlst2), 8 ) txt <- export_as_txt(tbl2, lpp = 16, cpp = 55) expect_true(grepl("Stratum: B", txt)) }) test_that("export_as_txt prints split level header correctly when using page_by", { # single level in page_by split tbl <- basic_table() %>% split_rows_by("PARAMCD", labels_var = "PARAMCD", split_label = "aaa", label_pos = "topleft", split_fun = drop_split_levels, page_by = TRUE ) %>% split_rows_by("AVISIT", label_pos = "topleft", split_fun = keep_split_levels("SCREENING") ) %>% analyze("AVAL") %>% build_table(ex_adlb[ex_adlb$PARAMCD == "ALT", ]) tbl_txt <- tbl %>% export_as_txt(lpp = 100) expect_true(grepl("^\naaa: ALT", tbl_txt)) # multiple levels in page_by split tbl <- basic_table() %>% split_rows_by("PARAMCD", labels_var = "PARAMCD", split_label = "aaa", label_pos = "topleft", split_fun = drop_split_levels, page_by = TRUE ) %>% split_rows_by("AVISIT", label_pos = "topleft", split_fun = keep_split_levels("SCREENING") ) %>% analyze("AVAL") %>% build_table(ex_adlb[ex_adlb$PARAMCD != "IGA", ]) tbl_txt <- tbl %>% export_as_txt(lpp = 100) expect_true(grepl("^\naaa: ALT", tbl_txt)) expect_true(grepl("\naaa: CRP", tbl_txt)) }) test_that("vertical and horizontal pagination work", { spoof_df <- data.frame( arm = factor(c("a", "b", "c", "d", "e", "f")), var1 = 6 ) simple_lyt <- basic_table() %>% split_cols_by("arm") %>% analyze("var1", function(x, ...) { in_rows( .list = replicate(30, list(1234)), .names = paste0("mynameis", 1:30) ) }) simple_tbl <- build_table(simple_lyt, spoof_df) ## rownames take up 10 char, repeated across horiz pag ## colheader takes up 2, repeated across vert pag ## all columns take up 7 (4 for content + 3 for cols sep) ## should be one col per page, i.e. 6 pages hpag1 <- paginate_table(simple_tbl, lpp = 80, cpp = 17) expect_equal( rep(1, 6), sapply(hpag1, ncol) ) ## 16 is too small to fit any columns (After col divider) ## onto a page with the row names, so this is an error expect_error(paginate_table(simple_tbl, lpp = 80, cpp = 16)) ## 23 is just barely still only enough to fit 1 col per page hpag2 <- paginate_table(simple_tbl, lpp = 80, cpp = 23) expect_identical(hpag1, hpag2) hpag3 <- paginate_table(simple_tbl, lpp = 80, cpp = 24, verbose = TRUE) expect_equal( rep(2, 3), sapply(hpag3, ncol) ) ## preceding siblings <2 expect_error(paginate_table(simple_tbl, lpp = 3, cpp = 120)) vpag1 <- paginate_table(simple_tbl, lpp = 3, cpp = 120, min_siblings = 0) expect_equal( rep(1, 30), sapply(vpag1, nrow) ) ## no lines for rows after header column expect_error(paginate_table(simple_tbl, lpp = 2, cpp = 120, min_siblings = 0)) ## first lpp that allows the default min_siblings = 2 to succeed, ## 3 rows per page, 10 pages vpag2 <- paginate_table(simple_tbl, lpp = 5, cpp = 120) expect_equal( rep(3, 10), sapply(vpag2, nrow) ) ## uneven pages, 8, 8, 8, 6 rows vpag3 <- paginate_table(simple_tbl, lpp = 10, cpp = 120) expect_equal( c(8, 8, 8, 6), sapply(vpag3, nrow) ) ## combined pagination cpag1 <- paginate_table(simple_tbl, lpp = 5, cpp = 17) expect_equal( replicate(60, list(c(3, 1))), lapply(cpag1, dim) ) ## ordering: horizontal pagination first then vertical in resulting list expect_equal( sapply(cpag1, names), rep(c("a", "b", "c", "d", "e", "f"), 10) ) tt <- tt_to_export() main_title(tt) <- "main title" main_footer(tt) <- "main footer" subtitles(tt) <- c("sub", "titles") prov_footer(tt) <- "prov footer" res <- paginate_table(tt, lpp = NULL, cpp = 40) expect_identical(length(res), 3L) expect_identical( tt[, 1:2, keep_titles = TRUE, reindex_refs = FALSE], res[[1]] ) ## this was lpp = 75, but manual line counting suggests that ## was a bad test, enforcing an off-by-one error. res2 <- paginate_table(tt, lpp = 76, cpp = 45, verbose = TRUE) expect_identical(length(res2), 6L) expect_identical( res2[[1]], tt[1:63, 1:2, keep_titles = TRUE, reindex_refs = FALSE] ) expect_identical( res2[[2]], tt[1:63, 3:4, keep_titles = TRUE, reindex_refs = FALSE] ) expect_identical( res2[[4]], tt[c(38, 57, 64:NROW(tt)), 1:2, keep_titles = TRUE, reindex_refs = FALSE] ) expect_identical( main_title(tt), main_title(res[[1]]) ) expect_identical(subtitles(tt), subtitles(res[[2]])) expect_identical(main_footer(tt), main_footer(res[[3]])) expect_identical(prov_footer(tt), prov_footer(res[[1]])) res2b <- paginate_table(tt, lpp = 75, cpp = 45, colwidths = c(12, rep(7, times = 6))) ## XXX TODO do careful analuysis to ensure this is actually right. expect_identical(nrow(res2b[[1]]), 59L) expect_identical(vapply(res2b, ncol, 1L), rep(3L, 4)) ## topleft perservation top_left(tt) <- "hahaha I'm a topleft" res3 <- paginate_table(tt, lpp = 75, cpp = 45) expect_identical(top_left(tt), top_left(res3[[1]])) lyt2 <- basic_table() %>% analyze( "mpg", function(x) in_rows(.list = setNames(as.list(x), as.character(seq_along(x)))) ) tt2 <- build_table(lyt2, cbind(mtcars, mtcars)) main_title(tt2) <- paste( strrep("a", 25), strrep("A", 25) ) subtitles(tt2) <- c( paste( strrep("a", 25), strrep("A", 25) ), "sub titles" ) main_footer(tt2) <- paste( strrep("a", 25), strrep("A", 25) ) prov_footer(tt2) <- paste( strrep("a", 25), strrep("A", 25) ) res3a <- paginate_table(tt2, lpp = 29, cpp = 40, tf_wrap = TRUE ) res3b <- paginate_table(tt2, lpp = 25, cpp = 40, tf_wrap = FALSE ) expect_true(all( mapply(function(a, b) identical(dim(a), dim(b)), a = res3a, b = res3b) )) res3c <- paginate_table(tt2, lpp = 29, cpp = 40, tf_wrap = FALSE) expect_equal( nrow(res3a[[1]]), nrow(res3c[[1]]) - 4 ) }) test_that("inset and pagination work together", { tt <- tt_to_export() main_title(tt) <- "main title" subtitles(tt) <- c("sub", "-------", "titles") main_footer(tt) <- "main footer" prov_footer(tt) <- "prov footer" table_inset(tt) <- 5 res <- paginate_table(tt, lpp = NULL, cpp = 45, verbose = TRUE) expect_identical(length(res), 3L) expect_identical( tt[, 1:2, keep_titles = TRUE, reindex_refs = FALSE], res[[1]] ) expect_identical( main_title(tt), main_title(res[[1]]) ) expect_identical(subtitles(tt), subtitles(res[[2]])) expect_identical(main_footer(tt), main_footer(res[[3]])) expect_identical(prov_footer(tt), prov_footer(res[[1]])) }) test_that("cell and column wrapping works in pagination", { # Set colwidths vector clw <- c(5, 7, 6, 6) + 12 lpp_tmp <- 18 ## header mats: 5 [ 1 (title) + 2 (newline + div) + 1 (col labels) + 1 (div)] ## footer mats: 3 [ 1 (footer) + 2 (newline + div) ## adj rlpp: 10 ## all rows fit on one page res1 <- pag_tt_indices(tt_for_wrap, lpp = lpp_tmp) expect_equal(length(res1), 1L) ## header mats: 7 [ 1 (title) + 2 (newline + div) + 3 (col labels) + 1 (div)] ## footer mats: 3 [ 1 (footer) + 2 (newline + div) ## adj rlpp: 8 ## can't break after on BLACK OR AFRICAN AMERICAN row (label) ## 2 pages, one after full asian block, one after full BOAA block res2 <- pag_tt_indices(tt_for_wrap, lpp = lpp_tmp, colwidths = clw) expect_identical( res2, list(1:5, 6:10) ) expect_identical( nlines(col_info(tt_for_wrap), colwidths = clw), nlines(col_info(tt_for_wrap)) + 2L ) ## 2 new lines from wrapping pdf <- make_row_df(tt_for_wrap, colwidths = clw) expect_identical( pdf$self_extent, c( 1L, 1L, 1L, 1L, 3L, 2L, 1L, 1L, 1L, 3L ) ) ## the 2 is row label wrap, 3s are cell wraps # propose_column_widths(matrix_form(tt_for_wrap, TRUE)) pg_tbl_w_clw <- paginate_table(tt_for_wrap, lpp = lpp_tmp, cpp = NULL, colwidths = clw) pg_tbl_no_clw <- paginate_table(tt_for_wrap, lpp = lpp_tmp, cpp = NULL, verbose = TRUE) res1 <- toString(matrix_form(pg_tbl_no_clw[[1]], TRUE)) res2 <- toString(matrix_form(tt_for_wrap, TRUE)) # The table should have 18 lines and 10 rows when it is not cell wrapped expect_identical(nrow(pg_tbl_no_clw[[1]]) + nrow(tt_for_wrap), 20L) expect_identical(.count_chr_from_str(res1, "\n") + .count_chr_from_str(res2, "\n"), 36L) ## entire table takes exactly 25 lines when content is wrapped result <- paginate_table(tt_for_wrap, colwidths = clw, lpp = 25L, cpp = NULL) expect_identical(result[[1]], tt_for_wrap) ## paginating at 24 walks up ## 10 row too long with wrap -> 9 label row -> 8 ok ## paginates after row 8 (BOAA -> AGE-> Mean) result2 <- paginate_table(tt_for_wrap, colwidths = clw, lpp = 24L, cpp = NULL) expect_identical( sapply(result2, nrow), c(8L, 3L) ) result_str <- toString(result[[1]], widths = clw) expect_identical(.count_chr_from_str(result_str, "\n"), 25L) ## Testing if the split happens with the right number of lines w/ pagination # Taking header and footer size (should be 10L with wrapping) tot_lines_w <- .count_chr_from_str(result_str, "\n") # 25L nr_res_w <- nrow(tt_for_wrap) + 5L # Cell (+ 2*2 l) and row values wrapping (+1 l) non_content_lines <- tot_lines_w - nr_res_w expect_identical(non_content_lines, 10L) # headers and footers with wrapping (+2 l) # Checking if the pages have the right amount of pages resw1 <- toString(result2[[1]], widths = clw) resw2 <- toString(result2[[2]], widths = clw) # context repetition is +2 lines exp_n_lines1 <- nrow(result2[[1]]) + non_content_lines + 2L + # Wrap of cell value 1L # Wrap of rowname exp_n_lines2 <- nrow(result2[[2]]) + non_content_lines + 2L + # Wrap of cell value 1L # Wrapping of rowname expect_identical(exp_n_lines1, .count_chr_from_str(resw1, "\n")) expect_identical(exp_n_lines2, .count_chr_from_str(resw2, "\n")) # Checking if the global number of lines is correctly split into pages paginated_lines <- exp_n_lines1 + exp_n_lines2 - non_content_lines - # Repeated header and footer 2L # Repeated rowname for context and its wrapping expect_identical(paginated_lines, tot_lines_w) }) test_that("Pagination works with section dividers", { lyt <- basic_table(title = "big title") %>% split_rows_by("SEX", section_div = "~") %>% split_rows_by("ARM") %>% analyze("AGE") tt <- build_table(lyt, DM) ttlst <- paginate_table(tt, lpp = 20) expect_identical(length(ttlst), 2L) expect_identical( tt[1:14, keep_titles = TRUE, reindex_refs = FALSE], ttlst[[1]] ) expect_identical( export_as_txt(ttlst[[1]][7:8, keep_titles = TRUE], hsep = "-", paginate = FALSE), "big title\n\n--------------\n all obs\n--------------\nMean 34.89 \n~~~~~~~~~~~~~~\nM \n" ) expect_identical( paste0( export_as_txt(tail(ttlst[[1]], 1), hsep = "-", paginate = FALSE), export_as_txt(head(ttlst[[2]], 1), hsep = "-", paginate = FALSE) ), paste0( "big title\n\n--------------\n all obs\n--------------\nMean 34.28 \n", "big title\n\n-----------\n all obs\n-----------\nU \n" ) ) }) test_that("Pagination works with non-default min_siblings", { lyt <- basic_table() %>% analyze("RACE") tt <- build_table(lyt, DM) ttlst <- expect_silent(paginate_table(tt, lpp = 3, min_siblings = 0)) expect_identical(length(ttlst), nlevels(DM$RACE)) expect_identical(tt[1], ttlst[[1]]) expect_error( paginate_table(tt, lpp = 3, min_siblings = 1), "Unable to find any valid pagination split between rows 1 and 1" ) }) test_that("Pagination works with wrapped titles/footers", { lyt <- basic_table() %>% split_cols_by("SEX") %>% analyze("RACE") tt <- build_table(lyt, DM) main_title(tt) <- "title with a\nnewline" main_footer(tt) <- "wrapped footer with\nnewline" res <- expect_silent(paginate_table(tt, cpp = 60, tf_wrap = TRUE)) expect_identical(main_title(res[[1]]), main_title(res[[2]])) expect_identical(main_title(res[[1]]), main_title(tt)) expect_identical(main_footer(res[[1]]), main_footer(res[[2]])) expect_identical(main_footer(res[[1]]), main_footer(tt)) main_title(tt) <- "this is a long long table title that should be wrapped to a new line" main_footer(tt) <- "this is an extra long table main footer and should also be wrapped" res2 <- expect_silent(paginate_table(tt, cpp = 60, tf_wrap = TRUE)) expect_equal(length(res2), 2) mf_res2 <- matrix_form(res2[[1]]) nrow_res2 <- nrow(mf_strings(mf_res2)) + 5 + 4 # 5 lines tbl seps/ws + 4 lines title/footer res2_str1 <- toString(res2[[1]], tf_wrap = TRUE, max_width = 60) res2_str1_spl <- strsplit(res2_str1, split = "\n")[[1]] expect_equal(nrow_res2, length(res2_str1_spl)) expect_true(all(nchar(res2_str1_spl)) <= 60) expect_equal(nchar(res2_str1_spl[1]), 59) expect_equal(nchar(res2_str1_spl[2]), 8) expect_equal(nchar(res2_str1_spl[nrow_res2 - 1]), 58) expect_equal(nchar(res2_str1_spl[nrow_res2]), 7) res2_str2 <- toString(res2[[2]], tf_wrap = TRUE, max_width = 60) res2_str2_spl <- strsplit(res2_str2, split = "\n")[[1]] expect_equal(nrow_res2, length(res2_str2_spl)) expect_true(all(nchar(res2_str2_spl)) <= 60) expect_equal(nchar(res2_str2_spl[1]), 59) expect_equal(nchar(res2_str2_spl[2]), 8) expect_equal(nchar(res2_str2_spl[nrow_res2 - 1]), 58) expect_equal(nchar(res2_str2_spl[nrow_res2]), 7) }) test_that("Pagination works with referential footnotes", { lyt <- basic_table( title = "main title", subtitles = "subtitle", main_footer = "main footer", prov_footer = "provenance footer" ) %>% split_cols_by("ARM") %>% split_cols_by("SEX", split_fun = keep_split_levels(c("F", "M"))) %>% split_rows_by("STRATA1", split_fun = keep_split_levels(c("A", "B")), page_by = TRUE, page_prefix = "Stratum") %>% split_rows_by("RACE", split_fun = keep_split_levels(c("ASIAN", "WHITE"))) %>% summarize_row_groups() %>% analyze("AGE", afun = function(x, ...) { in_rows( "mean (sd)" = rcell( c(mean(x), sd(x)), format = "xx.x (xx.x)" ), "range" = rcell(range(x), format = "xx.x - xx.x") ) }) tt <- build_table(lyt, ex_adsl) fnotes_at_path(tt, rowpath = c("STRATA1", "B", "RACE", "WHITE")) <- "3 Row footnote" fnotes_at_path( tt, rowpath = c("STRATA1", "A", "RACE", "WHITE", "AGE", "range"), colpath = c("ARM", "C: Combination", "SEX", "M") ) <- "2 Cell footnote" fnotes_at_path(tt, rowpath = c("STRATA1", "A", "RACE", "ASIAN")) <- "1 Row footnote" fnotes_at_path( tt, rowpath = c("STRATA1", "B", "RACE", "WHITE", "AGE", "mean (sd)"), colpath = c("ARM", "B: Placebo", "SEX", "F") ) <- "2 Cell footnote" main_title(tt) <- "title with a\nnewline" main_footer(tt) <- "wrapped footer with\nnewline" res <- expect_silent(paginate_table(tt, cpp = 60, tf_wrap = TRUE)) expect_identical(main_title(res[[1]]), main_title(res[[2]])) expect_identical(main_title(res[[1]]), main_title(tt)) expect_identical(main_footer(res[[1]]), main_footer(res[[2]])) expect_identical(main_footer(res[[1]]), main_footer(tt)) main_title(tt) <- "this is a long long table title that should be wrapped to a new line" main_footer(tt) <- "this is an extra long table main footer and should also be wrapped" res <- expect_silent(paginate_table(tt, cpp = 60, tf_wrap = TRUE)) expect_equal(length(res), 4) ref_fn_res1 <- matrix_form(res[[1]])$ref_fnote_df expect_equal(ref_fn_res1$msg, "1 Row footnote") expect_equal(ref_fn_res1$ref_index, 1) expect_equal(ref_fn_res1$symbol, "1") ref_fn_res2 <- matrix_form(res[[2]])$ref_fnote_df expect_equal(ref_fn_res2$msg, c("1 Row footnote", "2 Cell footnote")) expect_equal(ref_fn_res2$ref_index, 1:2) expect_equal(ref_fn_res2$symbol, c("1", "2")) ref_fn_res3 <- matrix_form(res[[3]])$ref_fnote_df expect_equal(ref_fn_res3$msg, c("3 Row footnote", "2 Cell footnote")) expect_equal(ref_fn_res3$ref_index, 1:2) expect_equal(ref_fn_res3$symbol, c("3", "2")) ref_fn_res4 <- matrix_form(res[[4]])$ref_fnote_df expect_equal(ref_fn_res4$msg, "3 Row footnote") expect_equal(ref_fn_res4$ref_index, 1) expect_equal(ref_fn_res4$symbol, "3") })