blob: 81591493a135216cc878a000e01a0eb6216515fd [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
Marc Kupietz6b65c032026-08-25 09:43:28 +0300147test_that("title slide notes and timing are merged into the title section", {
148 lines <- c(
149 '<section class="title-frame" id="title-slide">',
150 "<h1 class=\"title\">Title</h1>",
151 "</section>",
152 "<section class=\"slide level2\">",
153 "<!-- JD 00:30 -->",
154 "<aside class=\"notes\">",
155 "<p>Welcome</p>",
156 "</aside>",
157 "</section>",
158 "<section id=\"slide-a\" class=\"slide level2\">",
159 "<!-- MK 01:00 -->",
160 "</section>"
161 )
162 merged <- merge_title_slide_notes(lines)
163 # phantom slide (6 lines) removed, its 4 content lines moved into the title
164 expect_length(merged, length(lines) - 2)
165 expect_identical(merged[1], lines[1])
166 expect_match(paste(merged[1:5], collapse = "\n"), "Welcome")
167 expect_match(paste(merged[1:5], collapse = "\n"), "JD 00:30")
168 # only the real Slide A section remains
169 expect_equal(sum(grepl('class="slide level2"', merged)), 1)
170
171 # a phantom slide with real content is left alone
172 with_content <- lines
173 with_content[6] <- "<p>Visible intro content</p>"
174 expect_identical(merge_title_slide_notes(with_content), with_content)
175
176 # no title slide: nothing to merge
177 no_title <- lines[-1]
178 expect_identical(merge_title_slide_notes(no_title), no_title)
179})
180
181test_that("rendered title slides carry notes and data-timing", {
182 skip_if_not_pandoc()
183 skip_if_not_installed("xml2")
184 rmd <- local_temp_rmd_file(
185 "---",
186 "title: Timing test",
187 "output: revealjs.ids::revealjs_presentation",
188 "---",
189 "",
190 "::: notes",
191 "Welcome note",
192 ":::",
193 "",
194 "<!-- 00:30 -->",
195 "",
196 "## Slide A",
197 "",
198 "Content A"
199 )
200 html <- .render_and_read(rmd)
201 title <- xml2::xml_find_first(html, "//section[@id='title-slide']")
202 expect_equal(xml2::xml_attr(title, "data-timing"), "30")
203 expect_equal(
204 xml2::xml_text(xml2::xml_find_first(title, "./aside[@class='notes']"), trim = TRUE),
205 "Welcome note"
206 )
207 # no phantom slide between title and first content slide
208 sections <- xml2::xml_find_all(html, "//section[@class]")
209 expect_equal(
210 xml2::xml_attr(sections[1:2], "id"),
211 c("title-slide", "slide-a")
212 )
213})
214
Marc Kupietz4fc867c2026-08-25 10:30:18 +0300215test_that("overview slides get the TOC script", {
216 skip_if_not_pandoc()
217 rmd <- local_temp_rmd_file(
218 "---",
219 "title: Overview test",
220 "output: revealjs.ids::revealjs_presentation",
221 "---",
222 "",
223 "## Overview",
224 "",
225 "Placeholder",
226 "",
227 "# Chapter One",
228 "",
229 "## Slide",
230 "",
231 "Content"
232 )
233 html <- .render_and_read(rmd, xml = FALSE)
234 expect_true(any(grepl("ids-overview-toc", html)))
235 expect_true(any(grepl("Überblick", html, fixed = TRUE)))
236})
237
Marc Kupietzeaaa2b42026-08-26 09:41:27 +0300238test_that("transcript view script is included", {
239 skip_if_not_pandoc()
240 rmd <- local_temp_rmd_file(
241 "---",
242 "title: Transcript test",
243 "output: revealjs.ids::revealjs_presentation",
244 "---",
245 "",
246 "## Slide",
247 "",
248 "::: notes",
249 "Note text",
250 ":::"
251 )
252 html <- .render_and_read(rmd, xml = FALSE)
253 expect_true(any(grepl("ids-transcript", html)))
254})
255
Marc Kupietzb066e742026-08-23 09:57:06 +0300256test_that("a user-supplied totalTime is respected", {
257 skip_if_not_pandoc()
258 rmd <- local_temp_rmd_file(
259 "---",
260 "title: Timing test",
261 "output: revealjs.ids::revealjs_presentation",
262 "---",
263 "",
264 "## Slide A",
265 "",
266 "<!-- 00:30 -->"
267 )
268 html <- .render_and_read(rmd, output_options = list(
269 reveal_options = list(totalTime = 1800)
270 ))
271 # passed through to the config...
272 expect_true(any(grepl("totalTime:\\s*1800", html)))
273 # ... and not overwritten by the calculated total
274 expect_false(any(grepl("totalTime:\\s*30", html)))
275})
276
277test_that("slide_timing reports totals", {
278 rmd_file <- tempfile(fileext = ".Rmd")
279 writeLines(c(
280 "---",
281 "title: x",
282 "output: revealjs.ids::revealjs_presentation",
283 "---",
284 "",
285 "## Slide one",
286 "",
287 "<!-- MK 00:30 -->",
288 "",
289 "## Slide two",
290 "",
291 "<!-- AB 1:00 -->",
292 "<!-- MK 90 -->"
293 ), rmd_file)
294 expect_output(result <- slide_timing(rmd_file), "total")
295 expect_equal(result$total, 180)
296 expect_equal(result$speakers$seconds[result$speakers$speaker == "MK"], 120)
297 expect_equal(result$speakers$seconds[result$speakers$speaker == "AB"], 60)
298 unlink(rmd_file)
299})