blob: edcce26b34fcbf54a89c0448443983061cbcec6f [file] [log] [blame]
Marc Kupietzb066e742026-08-23 09:57:06 +03001test_that("parse_duration_seconds handles all duration formats", {
2 expect_equal(parse_duration_seconds("00:30"), 30)
3 expect_equal(parse_duration_seconds("1:05"), 65)
4 expect_equal(parse_duration_seconds("45"), 45)
5 expect_equal(parse_duration_seconds("1:00:00"), 3600)
6 expect_equal(parse_duration_seconds("1:30:15"), 5415)
7 expect_true(is.na(parse_duration_seconds("abc")))
8})
9
10test_that("extract_timing_comments finds comments", {
11 lines <- c(
12 "---",
13 "title: x",
14 "---",
15 "",
16 "## Slide one",
17 "",
18 "<!-- MK 00:30 -->",
19 "",
20 "## Slide two",
21 "",
22 "<!-- AB 1:00 -->",
23 "<!-- MK 90 -->"
24 )
25 comments <- extract_timing_comments(lines)
26 expect_equal(comments$speaker, c("MK", "AB", "MK"))
27 expect_equal(comments$seconds, c(30, 60, 90))
28 expect_equal(assign_slides(lines, comments, slide_level = 2),
29 c("Slide one", "Slide two", "Slide two"))
30})
31
32test_that("comments without speaker codes are supported", {
33 lines <- c("## Slide one", "<!-- 00:30 -->", "## Slide two", "<!-- 1:05 -->")
34 comments <- extract_timing_comments(lines)
35 expect_true(all(is.na(comments$speaker)))
36 expect_equal(comments$seconds, c(30, 65))
37
38 # a plain number must not be split into a speaker code plus duration
39 comments2 <- extract_timing_comments("<!-- 45 -->")
40 expect_true(is.na(comments2$speaker))
41 expect_equal(comments2$seconds, 45)
42
43 result <- apply_timing_attributes(c(
44 "<section id='s1' class='level2'>",
45 "<!-- 00:30 -->",
46 "</section>"
47 ))
48 expect_match(result[1], "data-timing=\"30\"")
49
50 report <- timing_report_message(extract_timing_comments(lines))
51 expect_equal(report, "Slide timing -- total 1:35")
52})
53
54test_that("comments before first heading belong to title slide", {
55 lines <- c("<!-- MK 00:10 -->", "", "# Title", "", "## Slide")
56 comments <- extract_timing_comments(lines)
57 expect_equal(assign_slides(lines, comments, slide_level = 2), "(title)")
58})
59
60test_that("apply_timing_attributes adds data-timing to sections", {
61 lines <- c(
62 "<html>",
63 "<section id='title-slide'>",
64 "title",
65 "</section>",
66 "<section id='s1' class='level2'>",
67 "<!-- MK 00:30 -->",
68 "</section>",
69 "<section id='s2' class='level2'>",
70 "<!-- MK 0:20 -->",
71 "<!-- AB 00:30 -->",
72 "</section>"
73 )
74 result <- apply_timing_attributes(lines)
75 html <- paste(result, collapse = "\n")
76 expect_match(result[2], "<section id='title-slide'>")
77 expect_match(result[5], "data-timing=\"30\"")
78 # multiple speakers on one slide are summed
79 expect_match(result[8], "data-timing=\"50\"")
80 comments <- attr(result, "comments")
81 expect_equal(sum(comments$seconds), 80)
82})
83
84test_that("apply_timing_attributes is a no-op without comments", {
85 lines <- c("<section id='s1'>", "x", "</section>")
86 result <- apply_timing_attributes(lines)
87 expect_identical(as.vector(result), lines)
88 expect_equal(nrow(attr(result, "comments")), 0)
89})
90
91test_that("inject_total_time activates the pacing timer", {
92 lines <- c(
93 "var opts = {",
94 " controls: true,",
95 "};",
96 "Reveal.initialize({",
97 " controls: true,",
98 " slideNumber: true",
99 "});"
100 )
101 result <- inject_total_time(lines, 90)
102 expect_contains(result, "\t\t\ttotalTime: 90,")
103
104 # existing pacing config is respected
105 with_default <- c(lines, "", "defaultTiming: 60")
106 expect_identical(inject_total_time(with_default, 90), with_default)
107 with_total <- c("totalTime: 120", lines)
108 expect_identical(inject_total_time(with_total, 90), with_total)
109
110 # nothing to hook into -- leave the document alone
111 expect_identical(inject_total_time(c("<html>", "</html>"), 90),
112 c("<html>", "</html>"))
113})
114
115test_that("rendered presentations carry data-timing attributes", {
116 skip_if_not_pandoc()
117 skip_if_not_installed("xml2")
118 rmd <- local_temp_rmd_file(
119 "---",
120 "title: Timing test",
121 "output: revealjs.ids::revealjs_presentation",
122 "---",
123 "",
124 "## Slide A",
125 "",
126 "Content A",
127 "",
128 "<!-- MK 00:30 -->",
129 "",
130 "## Slide B",
131 "",
132 "Content B",
133 "",
134 "<!-- MK 01:00 -->",
135 "<!-- AB 0:30 -->"
136 )
137 html <- .render_and_read(rmd)
138 timings <- xml2::xml_attr(
139 xml2::xml_find_all(html, "//section[@data-timing]"),
140 "data-timing"
141 )
142 expect_equal(timings, c("30", "90"))
143 # the grand total activates the pacing timer of the notes plugin
144 expect_true(any(grepl("totalTime:\\s*120", html)))
145})
146
147test_that("a user-supplied totalTime is respected", {
148 skip_if_not_pandoc()
149 rmd <- local_temp_rmd_file(
150 "---",
151 "title: Timing test",
152 "output: revealjs.ids::revealjs_presentation",
153 "---",
154 "",
155 "## Slide A",
156 "",
157 "<!-- 00:30 -->"
158 )
159 html <- .render_and_read(rmd, output_options = list(
160 reveal_options = list(totalTime = 1800)
161 ))
162 # passed through to the config...
163 expect_true(any(grepl("totalTime:\\s*1800", html)))
164 # ... and not overwritten by the calculated total
165 expect_false(any(grepl("totalTime:\\s*30", html)))
166})
167
168test_that("slide_timing reports totals", {
169 rmd_file <- tempfile(fileext = ".Rmd")
170 writeLines(c(
171 "---",
172 "title: x",
173 "output: revealjs.ids::revealjs_presentation",
174 "---",
175 "",
176 "## Slide one",
177 "",
178 "<!-- MK 00:30 -->",
179 "",
180 "## Slide two",
181 "",
182 "<!-- AB 1:00 -->",
183 "<!-- MK 90 -->"
184 ), rmd_file)
185 expect_output(result <- slide_timing(rmd_file), "total")
186 expect_equal(result$total, 180)
187 expect_equal(result$speakers$seconds[result$speakers$speaker == "MK"], 120)
188 expect_equal(result$speakers$seconds[result$speakers$speaker == "AB"], 60)
189 unlink(rmd_file)
190})