blob: ee72c654c714539f1b68b3a9eb62fc49f530eafb [file] [log] [blame]
Marc Kupietzdbd431a2021-08-29 12:17:45 +02001test_that("collocationScoreQuery works", {
Marc Kupietz83d0af32022-02-24 12:49:28 +01002 skip_if_offline()
Marc Kupietz617266d2025-02-27 10:43:07 +01003 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = TRUE)
Marc Kupietz7de5f322025-06-04 17:17:22 +02004 df <- collocationScoreQuery(kco, "Ameisenplage", "heimgesucht", leftContextSize = 0, rightContextSize = 1)
Marc Kupietzdbd431a2021-08-29 12:17:45 +02005 expect_gt(df$logDice, 1)
6 expect_equal(df$ll, ll(df$O1, df$O2, df$O, df$N, df$E, df$w))
7 expect_equal(df$pmi, pmi(df$O1, df$O2, df$O, df$N, df$E, df$w))
8 expect_equal(df$mi2, mi2(df$O1, df$O2, df$O, df$N, df$E, df$w))
9 expect_equal(df$mi3, mi3(df$O1, df$O2, df$O, df$N, df$E, df$w))
10 expect_equal(df$logDice, logDice(df$O1, df$O2, df$O, df$N, df$E, df$w))
11})
12
Marc Kupietz9c53e412026-06-21 12:13:44 +020013test_that("collocationScoreQuery expands collocates and virtual corpora", {
14 kco <- methods::new(
15 "KorAPConnection",
16 apiUrl = "https://example.test/",
17 KorAPUrl = "https://example.test/"
18 )
19
20 fake_frequency_query <- function(kco, query, vc = "", ...) {
21 expand <- length(query) != length(vc)
22 combinations <- if (expand) {
23 tidyr::expand_grid(query = query, vc = vc)
24 } else {
25 tibble::tibble(query = query, vc = vc)
26 }
27
28 combinations |>
29 dplyr::mutate(
30 totalResults = 10,
31 webUIRequestUrl = paste0("https://example.test/", seq_len(dplyr::n())),
32 total = 1000
33 )
34 }
35
36 testthat::local_mocked_bindings(
37 frequencyQuery = fake_frequency_query,
38 .package = "RKorAPClient"
39 )
40
41 result <- collocationScoreQuery(
42 kco,
43 node = "node",
44 collocate = c("first", "second"),
45 vc = c("vc1", "vc2"),
46 leftContextSize = 0,
47 rightContextSize = 1
48 )
49
50 expect_equal(nrow(result), 4)
51 expect_equal(result$collocate, c("first", "first", "second", "second"))
52 expect_equal(result$vc, c("vc1", "vc2", "vc1", "vc2"))
53 expect_true(all(mapply(
54 grepl,
55 pattern = result$collocate,
56 x = result$query,
57 MoreArgs = list(fixed = TRUE)
58 )))
59})
60
61test_that("collocationScoreQuery aligns observed frequencies with collocates", {
62 kco <- methods::new(
63 "KorAPConnection",
64 apiUrl = "https://example.test/",
65 KorAPUrl = "https://example.test/"
66 )
67
68 fake_frequency_query <- function(kco, query, vc = "", ...) {
69 expand <- length(query) != length(vc)
70 combinations <- if (expand) {
71 tidyr::expand_grid(query = query, vc = vc)
72 } else {
73 tibble::tibble(query = query, vc = vc)
74 }
75
76 combinations |>
77 dplyr::mutate(
78 totalResults = 10,
79 webUIRequestUrl = "https://example.test/",
80 total = 1000
81 )
82 }
83
84 testthat::local_mocked_bindings(
85 frequencyQuery = fake_frequency_query,
86 .package = "RKorAPClient"
87 )
88
89 result <- collocationScoreQuery(
90 kco,
91 node = "node",
92 collocate = c("first", "second"),
93 vc = c("vc1", "vc2"),
94 observed = c(3, 7),
95 smoothingConstant = 0
96 )
97
98 expect_equal(result$O, c(3, 3, 7, 7))
99})
100
Marc Kupietz581a29b2021-09-04 20:51:04 +0200101
102test_that("collocationAnalysis works and warns about missing token", {
Marc Kupietz83d0af32022-02-24 12:49:28 +0100103 skip_if_offline()
Marc Kupietz617266d2025-02-27 10:43:07 +0100104 kco <- KorAPConnection(
Marc Kupietz7de5f322025-06-04 17:17:22 +0200105 accessToken = NULL,
106 verbose = TRUE
107 )
108 expect_warning(
109 df <-
110 collocationAnalysis(
111 kco,
112 "focus([tt/p=ADJA] {Newstickeritis})",
113 leftContextSize = 1,
114 rightContextSize = 0,
115 ),
116 "access token"
117 )
Marc Kupietz62b20fe2026-09-14 21:18:19 +0200118 expect_true(all(df$O > df$E))
119 reine <- df[df$collocate == "reine", ]
120 expect_equal(nrow(reine), 1)
121 expect_gt(reine$logDice, -1)
Marc Kupietzdbd431a2021-08-29 12:17:45 +0200122})
123
124test_that("collocationAnalysis on unaccounted strings does not error out", {
Marc Kupietz83d0af32022-02-24 12:49:28 +0100125 skip_if_offline()
Marc Kupietz617266d2025-02-27 10:43:07 +0100126 kco <- KorAPConnection(accessToken = NULL, verbose = TRUE)
Marc Kupietz581a29b2021-09-04 20:51:04 +0200127 expect_warning(
Marc Kupietz7de5f322025-06-04 17:17:22 +0200128 df <- collocationAnalysis(kco, "XXXXXXXXAmeisenplage", vc = c("corpusSigle=/WDD17/", "corpusSigle=/WUD17/"), maxRecurse = 2),
Marc Kupietz581a29b2021-09-04 20:51:04 +0200129 "access token"
130 )
Marc Kupietzdbd431a2021-08-29 12:17:45 +0200131 testthat::expect_equal(nrow(df), 0)
132})
Marc Kupietzd6314b62021-12-22 12:49:09 +0100133
Marc Kupietz7de5f322025-06-04 17:17:22 +0200134# test_that("removeWithinSpanWorks", {
Marc Kupietz76dee312025-04-06 16:24:47 +0200135# expect_equal(
136# removeWithinSpan("contains(<base/s=s>, (machen []{0,1} aufmerksam | aufmerksam []{0,1} machen))", "base/s=s"),
137# "(machen []{0,1} aufmerksam | aufmerksam []{0,1} machen)")
Marc Kupietz7de5f322025-06-04 17:17:22 +0200138# })
Marc Kupietzdbdbb1f2025-02-19 10:33:06 +0100139
140
141test_that("mergeDuplicateCollocatesWorksAsExpected", {
Marc Kupietz5057f502025-04-06 16:55:57 +0200142 ldf <- tibble::tibble(
Marc Kupietzdbdbb1f2025-02-19 10:33:06 +0100143 node = c("focus(in [tt/p=NN] {[tt/l=nehmen]})"),
144 collocate = c("Anspruch"),
145 label = c(""),
146 vc = c(""),
147 query = c("Anspruch focus(in [tt/p=NN] {[tt/l=nehmen]})"),
148 webUIRequestUrl = c(
149 "https://korap.ids-mannheim.de/?q=Anspruch%20focus%28in%20%5btt%2fp%3dNN%5d%20%7b%5btt%2fl%3dnehmen%5d%7d%29&ql=poliqarp"
150 ),
151 w = c(1),
152 leftContextSize = c(1),
153 rightContextSize = c(0),
154 N = c(23578528381.5),
155 O = c(0.5),
156 O1 = c(1168410.5),
157 O2 = c(1296870.5),
158 E = c(64.2651265093014),
159 pmi = c(11.9173498777957),
160 mi2 = c(29.8406639214616),
161 mi3 = c(47.7639779651274),
162 logDice = c(11.6899933757298),
163 ll = c(3717716.74208791)
164 )
Marc Kupietz5057f502025-04-06 16:55:57 +0200165 rdf <- tibble::tibble(
Marc Kupietzdbdbb1f2025-02-19 10:33:06 +0100166 node = c("focus({[tt/l=nehmen] in} [tt/p=NN])"),
167 collocate = c("Anspruch"),
168 label = c(""),
169 vc = c(""),
170 query = c("focus({[tt/l=nehmen] in} [tt/p=NN]) Anspruch"),
171 webUIRequestUrl = c(
172 "https://korap.ids-mannheim.de/?q=focus%28%7b%5btt%2fl%3dnehmen%5d%20in%7d%20%5btt%2fp%3dNN%5d%29%20Anspruch&ql=poliqarp"
173 ),
174 w = c(1),
175 leftContextSize = c(0),
176 rightContextSize = c(1),
177 N = c(23578528381.5),
178 O = c(0.5),
179 O1 = c(17077.5),
180 O2 = c(1296870.5),
181 E = c(0.939299756346416),
182 pmi = c(7.99469408391783),
183 mi2 = c(15.8990457079122),
184 mi3 = c(23.8033973319065),
185 logDice = c(2.57887487309409),
186 ll = c(2181.35986032019)
187 )
188 merged <- mergeDuplicateCollocates(ldf, rdf, smoothingConstant = 0.5)
189 expect_equal(merged$O, 0.5)
190 expect_equal(merged$O1, 1185487.5)
191 expect_equal(merged$O2, 1296870.5)
192 expect_equal(merged$query, "Anspruch focus(in [tt/p=NN] {[tt/l=nehmen]}) | focus({[tt/l=nehmen] in} [tt/p=NN]) Anspruch")
193})
Marc Kupietz7de5f322025-06-04 17:17:22 +0200194
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200195test_that("add_multi_vc_comparisons adds favorite columns", {
196 sample_result <- tibble::tibble(
197 node = c("n", "n"),
198 collocate = c("c", "c"),
199 vc = c("vc1", "vc2"),
200 label = c("A", "B"),
201 N = c(100, 100),
202 O = c(10, 20),
203 O1 = c(50, 50),
204 O2 = c(30, 30),
205 E = c(5, 5),
206 w = c(2, 2),
207 leftContextSize = c(1, 1),
208 rightContextSize = c(1, 1),
209 frequency = c(10, 20),
Marc Kupietz09b1c082026-05-01 14:45:47 +0200210 webUIRequestUrl = c("https://korap.example/A", "https://korap.example/B"),
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200211 logDice = c(5, 7),
212 pmi = c(2, 3)
213 )
214
Marc Kupietz77852b22025-10-19 11:35:34 +0200215 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200216
217 expect_true(all(c(
218 "winner_logDice",
219 "winner_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200220 "winner_logDice_webUIRequestUrl",
221 "winner_webUIRequestUrl",
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200222 "runner_up_logDice",
Marc Kupietz28a29842025-10-18 12:25:09 +0200223 "runner_up_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200224 "loser_logDice_webUIRequestUrl",
225 "loser_webUIRequestUrl",
Marc Kupietz28a29842025-10-18 12:25:09 +0200226 "max_delta_logDice",
227 "winner_rank_logDice",
228 "winner_rank_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200229 "winner_rank_logDice_webUIRequestUrl",
Marc Kupietz28a29842025-10-18 12:25:09 +0200230 "runner_up_rank_logDice",
231 "runner_up_rank_logDice_value",
232 "loser_rank_logDice",
233 "loser_rank_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200234 "loser_rank_logDice_webUIRequestUrl",
Marc Kupietz28a29842025-10-18 12:25:09 +0200235 "max_delta_rank_logDice",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200236 "winner_percentile_rank_logDice",
237 "winner_percentile_rank_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200238 "winner_percentile_rank_logDice_webUIRequestUrl",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200239 "runner_up_percentile_rank_logDice",
240 "runner_up_percentile_rank_logDice_value",
241 "loser_percentile_rank_logDice",
242 "loser_percentile_rank_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200243 "loser_percentile_rank_logDice_webUIRequestUrl",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200244 "max_delta_percentile_rank_logDice",
Marc Kupietz28a29842025-10-18 12:25:09 +0200245 "winner_rank_pmi",
246 "winner_rank_pmi_value",
247 "runner_up_rank_pmi",
248 "runner_up_rank_pmi_value",
249 "loser_rank_pmi",
250 "loser_rank_pmi_value",
251 "max_delta_rank_pmi",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200252 "winner_percentile_rank_pmi",
253 "winner_percentile_rank_pmi_value",
254 "runner_up_percentile_rank_pmi",
255 "runner_up_percentile_rank_pmi_value",
256 "loser_percentile_rank_pmi",
257 "loser_percentile_rank_pmi_value",
258 "max_delta_percentile_rank_pmi",
Marc Kupietz28a29842025-10-18 12:25:09 +0200259 "rank_A_logDice",
260 "rank_B_logDice",
261 "rank_A_pmi",
262 "rank_B_pmi",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200263 "percentile_rank_A_logDice",
264 "percentile_rank_B_logDice",
265 "percentile_rank_A_pmi",
266 "percentile_rank_B_pmi",
Marc Kupietz28a29842025-10-18 12:25:09 +0200267 "delta_rank_logDice",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200268 "delta_rank_pmi",
269 "delta_percentile_rank_logDice",
270 "delta_percentile_rank_pmi"
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200271 ) %in% colnames(enriched)))
272
273 expect_true(all(enriched$winner_logDice == "B"))
274 expect_true(all(enriched$runner_up_logDice == "A"))
275 expect_true(all(enriched$winner_logDice_value >= enriched$runner_up_logDice_value))
Marc Kupietz09b1c082026-05-01 14:45:47 +0200276 expect_true(all(enriched$winner_logDice_webUIRequestUrl == "https://korap.example/B"))
277 expect_true(all(enriched$loser_logDice_webUIRequestUrl == "https://korap.example/A"))
278 expect_true(all(enriched$winner_webUIRequestUrl == "https://korap.example/B"))
279 expect_true(all(enriched$loser_webUIRequestUrl == "https://korap.example/A"))
Marc Kupietz130a2a22025-10-18 16:09:23 +0200280 expect_true(all(enriched$percentile_rank_A_logDice == 1))
281 expect_true(all(enriched$percentile_rank_B_logDice == 1))
282 expect_true(all(enriched$delta_percentile_rank_logDice == 0))
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200283})
284
Marc Kupietz09b1c082026-05-01 14:45:47 +0200285test_that("add_multi_vc_comparisons fills missing label URLs from vc", {
286 sample_result <- tibble::tibble(
287 node = c("n", "n", "n"),
288 collocate = c("c1", "c2", "c2"),
289 vc = c("corpusSigle=/A/", "corpusSigle=/A/", "corpusSigle=/B/"),
290 label = c("A", "A", "B"),
291 N = c(100, 100, 100),
292 O = c(10, 10, 20),
293 O1 = c(50, 50, 50),
294 O2 = c(30, 30, 30),
295 E = c(5, 5, 5),
296 w = c(2, 2, 2),
297 leftContextSize = c(1, 1, 1),
298 rightContextSize = c(1, 1, 1),
299 frequency = c(10, 10, 20),
300 webUIRequestUrl = c(
301 "https://korap.example/?q=q1&cq=corpusSigle%3D%2FA%2F&ql=poliqarp",
302 "https://korap.example/?q=q2&cq=corpusSigle%3D%2FA%2F&ql=poliqarp",
303 "https://korap.example/?q=q2&cq=corpusSigle%3D%2FB%2F&ql=poliqarp"
304 ),
305 logDice = c(6, 5, 7),
306 pmi = c(3, 2, 4)
307 )
308
309 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
310 c1 <- enriched[enriched$collocate == "c1", ]
311 expected_b_url <- paste0(
312 "https://korap.example/?q=q1&cq=",
313 urltools::url_encode("corpusSigle=/B/"),
314 "&ql=poliqarp"
315 )
316
317 expect_equal(
318 unique(c1$loser_logDice_webUIRequestUrl),
319 expected_b_url
320 )
321 expect_equal(
322 unique(c1$loser_webUIRequestUrl),
323 expected_b_url
324 )
325})
326
Marc Kupietzd7bb5cb2026-08-31 10:18:02 +0200327test_that("add_multi_vc_comparisons flags imputed cells", {
328 sample_result <- tibble::tibble(
329 node = c("n", "n", "n"),
330 collocate = c("c1", "c2", "c2"),
331 vc = c("corpusSigle=/A/", "corpusSigle=/A/", "corpusSigle=/B/"),
332 label = c("A", "A", "B"),
333 N = c(100, 100, 100),
334 O = c(10, 10, 20),
335 O1 = c(50, 50, 50),
336 O2 = c(30, 30, 30),
337 E = c(5, 5, 5),
338 w = c(2, 2, 2),
339 leftContextSize = c(1, 1, 1),
340 rightContextSize = c(1, 1, 1),
341 frequency = c(10, 10, 20),
342 webUIRequestUrl = c(
343 "https://korap.example/A1",
344 "https://korap.example/A2",
345 "https://korap.example/B2"
346 ),
347 logDice = c(6, 5, 7),
348 pmi = c(3, 2, 4)
349 )
350
351 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
352
353 expect_true(all(c("imputed_A", "imputed_B", "n_imputed", "imputed") %in% colnames(enriched)))
354
355 c1 <- enriched[enriched$collocate == "c1", ]
356 c2 <- enriched[enriched$collocate == "c2", ]
357
358 # c1 only occurs in A, so B's scores had to be imputed
359 expect_true(all(c1$imputed_B))
360 expect_true(all(!c1$imputed_A))
361 expect_true(all(c1$imputed))
362 expect_true(all(c1$n_imputed == 1L))
363
364 # c2 occurs in both, so nothing is imputed
365 expect_true(all(!c2$imputed_A))
366 expect_true(all(!c2$imputed_B))
367 expect_true(all(!c2$imputed))
368 expect_true(all(c2$n_imputed == 0L))
369
370 # c1's delta is measured against the imputed floor rather than against observed data:
371 # the absent label loses, at or below the weakest score actually attested anywhere.
372 expect_true(all(is.finite(c1$max_delta_logDice)))
373 expect_equal(unique(c1$loser_logDice), "B")
374 expect_lte(unique(c1$loser_logDice_value), min(sample_result$logDice))
375})
376
Marc Kupietz7b7a73b2026-08-31 10:31:27 +0200377test_that("add_multi_vc_comparisons warns about dropped duplicate rows", {
378 sample_result <- tibble::tibble(
379 node = rep("n", 4),
380 collocate = c("c", "c", "c", "c"),
381 vc = c("vc1", "vc1", "vc2", "vc2"),
382 # the same collocate twice per label, as after collecting several context positions
383 label = c("A", "A", "B", "B"),
384 N = rep(100, 4),
385 O = c(10, 11, 20, 21),
386 O1 = rep(50, 4),
387 O2 = rep(30, 4),
388 E = rep(5, 4),
389 w = rep(2, 4),
390 leftContextSize = rep(1, 4),
391 rightContextSize = rep(1, 4),
392 frequency = c(10, 11, 20, 21),
393 logDice = c(5, 6, 7, 8),
394 pmi = c(2, 2, 3, 3)
395 )
396
397 expect_warning(
398 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result),
399 "occur more than once"
400 )
401 expect_warning(
402 RKorAPClient:::add_multi_vc_comparisons(sample_result),
403 "mergeDuplicateCollocates"
404 )
405 # the first row of each combination is what ends up in the comparison
406 expect_true(all(enriched$logDice_A == 5))
407 expect_true(all(enriched$logDice_B == 7))
408})
409
410test_that("add_multi_vc_comparisons is silent for unique node/collocate/label rows", {
411 sample_result <- tibble::tibble(
412 node = rep("n", 4),
413 collocate = c("c1", "c1", "c2", "c2"),
414 vc = rep(c("vc1", "vc2"), 2),
415 label = rep(c("A", "B"), 2),
416 N = rep(100, 4),
417 O = c(10, 20, 30, 40),
418 O1 = rep(50, 4),
419 O2 = rep(30, 4),
420 E = rep(5, 4),
421 w = rep(2, 4),
422 leftContextSize = rep(1, 4),
423 rightContextSize = rep(1, 4),
424 frequency = c(10, 20, 30, 40),
425 logDice = c(5, 7, 6, 4),
426 pmi = c(2, 3, 4, 1)
427 )
428
429 expect_no_warning(RKorAPClient:::add_multi_vc_comparisons(sample_result))
430})
431
Marc Kupietz424cb782026-08-31 10:19:29 +0200432test_that("add_multi_vc_comparisons reports imputation when verbose", {
433 sample_result <- tibble::tibble(
434 node = c("n", "n", "n"),
435 collocate = c("c1", "c2", "c2"),
436 vc = c("vc1", "vc1", "vc2"),
437 label = c("A", "A", "B"),
438 N = rep(100, 3),
439 O = c(10, 10, 20),
440 O1 = rep(50, 3),
441 O2 = rep(30, 3),
442 E = rep(5, 3),
443 w = rep(2, 3),
444 leftContextSize = rep(1, 3),
445 rightContextSize = rep(1, 3),
446 frequency = c(10, 10, 20),
447 logDice = c(6, 5, 7),
448 pmi = c(3, 2, 4)
449 )
450
451 output <- capture.output(
452 RKorAPClient:::add_multi_vc_comparisons(sample_result, verbose = TRUE)
453 )
454 expect_match(paste(output, collapse = " "), "Imputed scores for 1 of 2")
455 expect_match(paste(output, collapse = " "), "queryMissingScores")
456
457 # nothing is printed unless verbose
458 expect_silent(RKorAPClient:::add_multi_vc_comparisons(sample_result, verbose = FALSE))
459})
460
Marc Kupietzd7bb5cb2026-08-31 10:18:02 +0200461test_that("add_multi_vc_comparisons reports no imputation when all labels are complete", {
462 sample_result <- tibble::tibble(
463 node = rep("n", 4),
464 collocate = c("c1", "c1", "c2", "c2"),
465 vc = rep(c("vc1", "vc2"), 2),
466 label = rep(c("A", "B"), 2),
467 N = rep(100, 4),
468 O = c(10, 20, 30, 40),
469 O1 = rep(50, 4),
470 O2 = rep(30, 4),
471 E = rep(5, 4),
472 w = rep(2, 4),
473 leftContextSize = rep(1, 4),
474 rightContextSize = rep(1, 4),
475 frequency = c(10, 20, 30, 40),
476 logDice = c(5, 7, 6, 4),
477 pmi = c(2, 3, 4, 1)
478 )
479
480 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
481
482 expect_true(all(!enriched$imputed))
483 expect_true(all(enriched$n_imputed == 0L))
484})
485
Marc Kupietzb2862d42025-10-18 10:17:49 +0200486test_that("add_multi_vc_comparisons handles more than two labels", {
487 sample_result <- tibble::tibble(
488 node = rep("n", 3),
489 collocate = rep("c", 3),
490 vc = c("vc1", "vc2", "vc3"),
491 label = c("A", "B", "C"),
492 N = rep(100, 3),
493 O = c(10, 30, 5),
494 O1 = rep(50, 3),
495 O2 = rep(30, 3),
496 E = rep(5, 3),
497 w = rep(2, 3),
498 leftContextSize = rep(1, 3),
499 rightContextSize = rep(1, 3),
500 frequency = c(10, 30, 5),
Marc Kupietz09b1c082026-05-01 14:45:47 +0200501 webUIRequestUrl = c("https://korap.example/A", "https://korap.example/B", "https://korap.example/C"),
Marc Kupietzb2862d42025-10-18 10:17:49 +0200502 logDice = c(5, 8, 4),
503 pmi = c(2, 3, 1)
504 )
505
Marc Kupietz77852b22025-10-19 11:35:34 +0200506 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
Marc Kupietzb2862d42025-10-18 10:17:49 +0200507 expect_equal(enriched$winner_logDice[1], "B")
508 expect_equal(enriched$winner_logDice_value[1], 8)
Marc Kupietz09b1c082026-05-01 14:45:47 +0200509 expect_equal(enriched$winner_logDice_webUIRequestUrl[1], "https://korap.example/B")
Marc Kupietzb2862d42025-10-18 10:17:49 +0200510 expect_equal(enriched$runner_up_logDice[1], "A")
511 expect_equal(enriched$runner_up_logDice_value[1], 5)
512 expect_equal(enriched$loser_logDice[1], "C")
513 expect_equal(enriched$loser_logDice_value[1], 4)
Marc Kupietz09b1c082026-05-01 14:45:47 +0200514 expect_equal(enriched$loser_logDice_webUIRequestUrl[1], "https://korap.example/C")
Marc Kupietzb2862d42025-10-18 10:17:49 +0200515 expect_equal(enriched$max_delta_logDice[1], 4)
516})
517
Marc Kupietz09b1c082026-05-01 14:45:47 +0200518test_that("add_multi_vc_comparisons leaves unsuffixed URLs ambiguous when scores disagree", {
519 sample_result <- tibble::tibble(
520 node = c("n", "n"),
521 collocate = c("c", "c"),
522 vc = c("vc1", "vc2"),
523 label = c("A", "B"),
524 N = c(100, 100),
525 O = c(10, 20),
526 O1 = c(50, 50),
527 O2 = c(30, 30),
528 E = c(5, 5),
529 w = c(2, 2),
530 leftContextSize = c(1, 1),
531 rightContextSize = c(1, 1),
532 frequency = c(10, 20),
533 webUIRequestUrl = c("https://korap.example/A", "https://korap.example/B"),
534 logDice = c(5, 7),
535 pmi = c(4, 3)
536 )
537
538 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
539
540 expect_equal(enriched$winner_logDice_webUIRequestUrl[1], "https://korap.example/B")
541 expect_equal(enriched$winner_pmi_webUIRequestUrl[1], "https://korap.example/A")
542 expect_true(is.na(enriched$winner_webUIRequestUrl[1]))
543 expect_true(is.na(enriched$loser_webUIRequestUrl[1]))
544})
545
Marc Kupietz28a29842025-10-18 12:25:09 +0200546test_that("add_multi_vc_comparisons computes rank deltas", {
547 base_tbl <- tidyr::expand_grid(
548 label = c("A", "B", "C"),
549 collocate = c("c1", "c2", "c3")
550 ) |>
551 dplyr::mutate(
552 node = "n",
553 vc = paste0("vc", label),
554 N = 100,
555 O = 10,
556 O1 = 50,
557 O2 = 40,
558 E = 5,
559 w = 2,
560 leftContextSize = 1,
561 rightContextSize = 1,
562 frequency = 10,
563 logDice = dplyr::case_when(
564 label == "A" & collocate == "c1" ~ 9,
565 label == "A" & collocate == "c2" ~ 6,
566 label == "A" & collocate == "c3" ~ 3,
567 label == "B" & collocate == "c1" ~ 7,
568 label == "B" & collocate == "c2" ~ 9,
569 label == "B" & collocate == "c3" ~ 5,
570 label == "C" & collocate == "c1" ~ 4,
571 label == "C" & collocate == "c2" ~ 6,
572 label == "C" & collocate == "c3" ~ 8,
573 TRUE ~ 0
574 )
575 )
576
Marc Kupietz77852b22025-10-19 11:35:34 +0200577 enriched <- RKorAPClient:::add_multi_vc_comparisons(base_tbl)
Marc Kupietz28a29842025-10-18 12:25:09 +0200578 target_row <- enriched |>
579 dplyr::filter(collocate == "c1") |>
580 dplyr::slice_head(n = 1)
581
582 expect_equal(target_row$rank_A_logDice, 1)
583 expect_equal(target_row$rank_B_logDice, 2)
584 expect_equal(target_row$rank_C_logDice, 3)
585 expect_equal(target_row$winner_rank_logDice, "A")
586 expect_equal(target_row$winner_rank_logDice_value, 1)
587 expect_equal(target_row$runner_up_rank_logDice, "B")
588 expect_equal(target_row$runner_up_rank_logDice_value, 2)
589 expect_equal(target_row$loser_rank_logDice, "C")
590 expect_equal(target_row$loser_rank_logDice_value, 3)
591 expect_equal(target_row$max_delta_rank_logDice, 2)
592})
593
594test_that("add_multi_vc_comparisons imputes missing ranks for max delta", {
595 sample_result <- tibble::tibble(
596 node = c("n", "n"),
597 collocate = c("c", "c"),
598 vc = c("vc1", "vc2"),
599 label = c("A", "B"),
600 N = c(100, 100),
601 O = c(10, 10),
602 O1 = c(50, 50),
603 O2 = c(30, 30),
604 E = c(5, 5),
605 w = c(2, 2),
606 leftContextSize = c(1, 1),
607 rightContextSize = c(1, 1),
608 frequency = c(10, 10),
609 logDice = c(5, NA)
610 )
611
Marc Kupietz77852b22025-10-19 11:35:34 +0200612 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
Marc Kupietz28a29842025-10-18 12:25:09 +0200613
614 expect_equal(enriched$rank_A_logDice[1], 1)
615 expect_true(is.na(enriched$rank_B_logDice[1]))
616 expect_equal(enriched$winner_rank_logDice[1], "A")
617 expect_equal(enriched$loser_rank_logDice[1], "B")
618 expect_equal(enriched$loser_rank_logDice_value[1], 2)
619 expect_equal(enriched$max_delta_rank_logDice[1], 1)
620})
621
Marc Kupietz9894a372025-10-18 14:51:29 +0200622test_that("adaptive missing score imputation respects measure-specific scales", {
623 sample_result <- tibble::tibble(
624 node = c("n", "n", "n"),
625 collocate = c("c", "c", "c"),
626 vc = c("vc1", "vc2", "vc3"),
627 label = c("A", "B", "C"),
628 N = c(100, 100, 100),
629 O = c(12, 9, 7),
630 O1 = c(60, 40, 30),
631 O2 = c(33, 22, 18),
632 E = c(6, 6, 6),
633 w = c(2, 2, 2),
634 leftContextSize = c(1, 1, 1),
635 rightContextSize = c(1, 1, 1),
636 frequency = c(15, 11, 9),
637 logDice = c(-0.31, NA, -0.12),
638 pmi = c(-1.65, NA, -0.48),
639 ll = c(12.4, NA, 7.9)
640 )
641
642 enriched <- RKorAPClient:::add_multi_vc_comparisons(
643 sample_result,
Marc Kupietz9894a372025-10-18 14:51:29 +0200644 missingScoreQuantile = 0.05
645 )
646
647 row_a <- dplyr::filter(enriched, label == "A") |> dplyr::slice_head(n = 1)
648
649 expect_false(is.na(row_a$logDice_B))
650 expect_false(is.na(row_a$pmi_B))
651 expect_false(is.na(row_a$ll_B))
652
653 expect_lt(row_a$logDice_B, min(sample_result$logDice, na.rm = TRUE))
654 expect_lt(row_a$pmi_B, min(sample_result$pmi, na.rm = TRUE))
655 expect_lte(row_a$ll_B, min(sample_result$ll, na.rm = TRUE))
656
657 expect_gt(row_a$max_delta_logDice, 0)
658 expect_gt(row_a$winner_logDice_value - row_a$loser_logDice_value, 0)
659})
660
Marc Kupietz7de5f322025-06-04 17:17:22 +0200661# New tests for improved coverage of collocationAnalysis.R helper functions
662
663test_that("synsemanticStopwords returns German stopwords", {
664 stopwords <- synsemanticStopwords()
665 expect_true(is.character(stopwords))
666 expect_true(length(stopwords) > 50)
667 expect_true("der" %in% stopwords)
668 expect_true("die" %in% stopwords)
669 expect_true("und" %in% stopwords)
670 expect_true("mit" %in% stopwords)
671})
672
673test_that("removeWithinSpan removes span constraints correctly", {
674 # Test basic span removal
675 query1 <- "contains(<base/s=s>, (machen []{0,1} aufmerksam | aufmerksam []{0,1} machen))"
676 result1 <- RKorAPClient:::removeWithinSpan(query1, "base/s=s")
677 expect_equal(result1, "(machen []{0,1} aufmerksam | aufmerksam []{0,1} machen)")
678
679 # Test with different span
680 query2 <- "contains(<p/s=s>, (test query))"
681 result2 <- RKorAPClient:::removeWithinSpan(query2, "p/s=s")
682 expect_equal(result2, "(test query)")
683
684 # Test with empty span - should return original query
685 query3 <- "simple query"
686 result3 <- RKorAPClient:::removeWithinSpan(query3, "")
687 expect_equal(result3, query3)
688
689 # Test with non-matching span
690 query4 <- "contains(<base/s=s>, test)"
691 result4 <- RKorAPClient:::removeWithinSpan(query4, "other/span")
692 expect_equal(result4, query4)
693})
694
695test_that("matches2FreqTable handles empty matches", {
696 empty_matches <- data.frame()
697 result <- RKorAPClient:::matches2FreqTable(empty_matches, index = 0)
698
699 expect_true(is.data.frame(result))
700 expect_equal(nrow(result), 0)
701})
702
703test_that("matches2FreqTable processes single match correctly", {
704 # Create mock matches data
705 mock_matches <- data.frame(
706 tokens = I(list(list(
707 left = c("der", "große"),
708 match = "Test",
709 right = c("ist", "wichtig")
710 ))),
711 stringsAsFactors = FALSE
712 )
713
714 result <- RKorAPClient:::matches2FreqTable(
715 mock_matches,
716 index = 1,
717 leftContextSize = 2,
718 rightContextSize = 2,
719 stopwords = c("der", "ist") # Provide stopwords to avoid empty join
720 )
721
722 expect_true(is.data.frame(result))
723})
724
725test_that("snippet2FreqTable handles empty snippet", {
726 result <- RKorAPClient:::snippet2FreqTable(character(0))
727
728 expect_true(is.data.frame(result))
729 expect_equal(nrow(result), 0)
730})
731
732test_that("snippet2FreqTable processes single snippet correctly", {
733 snippet <- '<span class="context-left">der große </span><span class="match"><mark>Test</mark></span><span class="context-right"> ist wichtig</span>'
734
735 result <- RKorAPClient:::snippet2FreqTable(
736 snippet,
737 leftContextSize = 2,
738 rightContextSize = 2,
739 stopwords = c("der"), # Provide stopwords to avoid empty join
740 verbose = FALSE
741 )
742
743 expect_true(is.data.frame(result))
744})
745
Marc Kupietz0a292632025-10-19 14:04:36 +0200746test_that("inject_focus_into_query adds focus wrappers when span queries lack them", {
747 unfocused <- "contains(<base/s=s>, (Anspruch [tt/l=nehmen] | [tt/l=nehmen] Anspruch))"
748 focused <- RKorAPClient:::inject_focus_into_query(unfocused)
749
750 expect_equal(
751 focused,
752 "contains(<base/s=s>, (focus({Anspruch [tt/l=nehmen]}) | focus({[tt/l=nehmen] Anspruch})))"
753 )
754})
755
756test_that("inject_focus_into_query leaves existing focus segments unchanged", {
757 already_focused <- "contains(<base/s=s>, (focus({Anspruch [tt/l=nehmen]})))"
758 expect_identical(
759 RKorAPClient:::inject_focus_into_query(already_focused),
760 already_focused
761 )
762})
763
Marc Kupietz7de5f322025-06-04 17:17:22 +0200764# Removed hanging findExample tests as they cause infinite wait
765# These tests make API calls that don't complete properly
766
767# Removed hanging collocatesQuery tests as they cause infinite wait
768# These tests were causing the test suite to hang and not terminate
769
770test_that("collocationAnalysis handles exactFrequencies parameter", {
771 skip_if_offline()
772 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
773
774 expect_warning(
775 result <- collocationAnalysis(
776 kco,
777 "Test",
778 exactFrequencies = TRUE,
779 searchHitsSampleLimit = 5,
780 topCollocatesLimit = 5
781 ),
782 "access token"
783 )
784 expect_true(is.data.frame(result))
785})
786
787test_that("collocationAnalysis handles withinSpan parameter", {
788 skip_if_offline()
789 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
790
791 expect_warning(
792 result <- collocationAnalysis(
793 kco,
794 "Test",
795 withinSpan = "base/s=s",
796 exactFrequencies = TRUE,
797 searchHitsSampleLimit = 5,
798 topCollocatesLimit = 5
799 ),
800 "access token"
801 )
802 expect_true(is.data.frame(result))
803})
804
805test_that("collocationAnalysis handles expand parameter", {
806 skip_if_offline()
807 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
808
809 expect_warning(
810 result <- collocationAnalysis(
811 kco,
812 c("Test", "der"),
813 expand = TRUE,
814 searchHitsSampleLimit = 2,
815 topCollocatesLimit = 2
816 ),
817 "access token"
818 )
819 expect_true(is.data.frame(result))
820})
821
Marc Kupietze34a8be2025-10-17 20:13:42 +0200822test_that("collocationAnalysis honors named vc labels", {
823 skip_if_offline()
824 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
825
826 named_vc <- c(
827 Western = "textType=/.*Western.*/ & pubDate in 2012",
828 Erotic = "textType=/.*(Erotik|Gay).*/ & pubDate in 2012",
829 Historic = "textType=/.*Historisch.*/ & pubDate in 2012"
830 )
831
832 expect_warning(
833 result <- collocationAnalysis(
834 kco,
835 "[tt/l=treffen]",
836 vc = named_vc,
837 searchHitsSampleLimit = 2,
838 topCollocatesLimit = 2
839 ),
840 "access token"
841 )
842
843 if (nrow(result) > 0) {
844 expect_true("label" %in% colnames(result))
845 expect_setequal(unique(result$label), names(named_vc))
846 }
847})
848
Marc Kupietz7de5f322025-06-04 17:17:22 +0200849test_that("collocationAnalysis handles stopwords parameter", {
850 skip_if_offline()
851 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
852
853 expect_warning(
854 result <- collocationAnalysis(
855 kco,
856 "Test",
857 stopwords = c("der", "die", "und"),
858 searchHitsSampleLimit = 5,
859 topCollocatesLimit = 5
860 ),
861 "access token"
862 )
863 expect_true(is.data.frame(result))
864})
865
866test_that("collocationAnalysis handles lemmatizeNodeQuery parameter", {
867 skip_if_offline()
868 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
869
870 expect_warning(
871 result <- collocationAnalysis(
872 kco,
873 "laufen",
874 lemmatizeNodeQuery = TRUE,
875 searchHitsSampleLimit = 5,
876 topCollocatesLimit = 5
877 ),
878 "access token"
879 )
880 expect_true(is.data.frame(result))
881})
882
883test_that("collocationAnalysis handles addExamples parameter", {
884 skip_if_offline()
885 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
886
887 expect_warning(
888 result <- collocationAnalysis(
889 kco,
890 "Test",
891 addExamples = TRUE,
892 searchHitsSampleLimit = 3,
893 topCollocatesLimit = 3
894 ),
895 "access token"
896 )
897 expect_true(is.data.frame(result))
898 if (nrow(result) > 0) {
899 expect_true("example" %in% colnames(result))
900 }
901})
902
903test_that("collocationAnalysis handles maxRecurse parameter", {
904 skip_if_offline()
905 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
906
907 expect_warning(
908 result <- collocationAnalysis(
909 kco,
910 "Test",
911 maxRecurse = 1,
912 searchHitsSampleLimit = 2,
913 topCollocatesLimit = 2
914 ),
915 "access token"
916 )
917 expect_true(is.data.frame(result))
918})
Marc Kupietzbceeb052026-08-31 10:30:07 +0200919
920test_that("collocationAnalysis returns cached result without contacting the server", {
921 kco <- methods::new(
922 "KorAPConnection",
923 apiUrl = "https://example.invalid/",
924 KorAPUrl = "https://example.invalid/",
Marc Kupietzc902c232026-09-08 07:58:01 +0200925 authorizationSupported = FALSE,
Marc Kupietzbceeb052026-08-31 10:30:07 +0200926 verbose = FALSE
927 )
928
Marc Kupietzbceeb052026-08-31 10:30:07 +0200929 cache_file <- tempfile(fileext = ".rds")
930 on.exit(unlink(cache_file), add = TRUE)
Marc Kupietzc902c232026-09-08 07:58:01 +0200931
932 # written by the package itself, so that it carries the record saying what
933 # produced it - a file without one is refused, as one from an older version
934 cached <- local({
935 testthat::local_mocked_bindings(
936 collocatesQuery = function(...) tibble::tibble(),
937 .package = "RKorAPClient"
938 )
939 collocationAnalysis(kco, "n", cacheAs = cache_file)
940 })
Marc Kupietzbceeb052026-08-31 10:30:07 +0200941
942 testthat::local_mocked_bindings(
943 corpusQuery = function(...) stop("server must not be contacted"),
Marc Kupietzc902c232026-09-08 07:58:01 +0200944 collocatesQuery = function(...) stop("server must not be contacted"),
Marc Kupietzbceeb052026-08-31 10:30:07 +0200945 .package = "RKorAPClient"
946 )
947
948 expect_equal(
949 collocationAnalysis(kco, "n", cacheAs = cache_file),
950 cached
951 )
952})
953
954test_that("collocationAnalysis appends the .rds extension to cacheAs", {
955 kco <- methods::new(
956 "KorAPConnection",
957 apiUrl = "https://example.invalid/",
958 KorAPUrl = "https://example.invalid/",
959 verbose = FALSE
960 )
961
Marc Kupietzbceeb052026-08-31 10:30:07 +0200962 base_path <- tempfile()
963 on.exit(unlink(paste0(base_path, ".rds")), add = TRUE)
Marc Kupietzc902c232026-09-08 07:58:01 +0200964
965 # given without extension, .rds is appended when writing
966 cached <- local({
967 testthat::local_mocked_bindings(
968 collocatesQuery = function(...) tibble::tibble(),
969 .package = "RKorAPClient"
970 )
971 collocationAnalysis(kco, "n", cacheAs = base_path)
972 })
973 expect_true(file.exists(paste0(base_path, ".rds")))
Marc Kupietzbceeb052026-08-31 10:30:07 +0200974
975 testthat::local_mocked_bindings(
976 corpusQuery = function(...) stop("server must not be contacted"),
Marc Kupietzc902c232026-09-08 07:58:01 +0200977 collocatesQuery = function(...) stop("server must not be contacted"),
Marc Kupietzbceeb052026-08-31 10:30:07 +0200978 .package = "RKorAPClient"
979 )
980
Marc Kupietzc902c232026-09-08 07:58:01 +0200981 # and the same file is found again, given with or without the extension
Marc Kupietzbceeb052026-08-31 10:30:07 +0200982 expect_equal(collocationAnalysis(kco, "n", cacheAs = base_path), cached)
Marc Kupietzbceeb052026-08-31 10:30:07 +0200983 expect_equal(collocationAnalysis(kco, "n", cacheAs = paste0(base_path, ".rds")), cached)
984})
985
986test_that("collocationAnalysis writes and reuses its cache file", {
987 skip_if_offline()
988 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
989
990 cache_file <- tempfile(fileext = ".rds")
991 on.exit(unlink(cache_file), add = TRUE)
992 expect_false(file.exists(cache_file))
993
994 expect_warning(
995 first <- collocationAnalysis(
996 kco,
997 "Ameisenplage",
998 searchHitsSampleLimit = 2,
999 topCollocatesLimit = 2,
1000 cacheAs = cache_file
1001 ),
1002 "access token"
1003 )
1004
1005 expect_true(file.exists(cache_file))
Marc Kupietzd4a7b302026-09-04 10:40:48 +02001006 # the file records the analysis parameters, the returned value does not
1007 cached <- readRDS(cache_file)
Marc Kupietzc902c232026-09-08 07:58:01 +02001008 expect_false(is.null(attr(cached, RKorAPClient:::cacheAsAttribute)))
1009 attr(cached, RKorAPClient:::cacheAsAttribute) <- NULL
Marc Kupietzd4a7b302026-09-04 10:40:48 +02001010 expect_equal(cached, first)
Marc Kupietzbceeb052026-08-31 10:30:07 +02001011
1012 # the second call is served from the cache and therefore issues no token warning
1013 second <- collocationAnalysis(
1014 kco,
1015 "Ameisenplage",
1016 searchHitsSampleLimit = 2,
1017 topCollocatesLimit = 2,
1018 cacheAs = cache_file
1019 )
1020 expect_equal(second, first)
1021})