blob: 5b4db982e3ddd5ffaddc6c272955ac7818a1a2bb [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 Kupietzdbd431a2021-08-29 12:17:45 +0200118 expect_gt(df$O, df$E)
Marc Kupietzf9129592025-01-26 19:17:54 +0100119 expect_gt(df$logDice, -1)
Marc Kupietzdbd431a2021-08-29 12:17:45 +0200120})
121
122test_that("collocationAnalysis on unaccounted strings does not error out", {
Marc Kupietz83d0af32022-02-24 12:49:28 +0100123 skip_if_offline()
Marc Kupietz617266d2025-02-27 10:43:07 +0100124 kco <- KorAPConnection(accessToken = NULL, verbose = TRUE)
Marc Kupietz581a29b2021-09-04 20:51:04 +0200125 expect_warning(
Marc Kupietz7de5f322025-06-04 17:17:22 +0200126 df <- collocationAnalysis(kco, "XXXXXXXXAmeisenplage", vc = c("corpusSigle=/WDD17/", "corpusSigle=/WUD17/"), maxRecurse = 2),
Marc Kupietz581a29b2021-09-04 20:51:04 +0200127 "access token"
128 )
Marc Kupietzdbd431a2021-08-29 12:17:45 +0200129 testthat::expect_equal(nrow(df), 0)
130})
Marc Kupietzd6314b62021-12-22 12:49:09 +0100131
Marc Kupietz7de5f322025-06-04 17:17:22 +0200132# test_that("removeWithinSpanWorks", {
Marc Kupietz76dee312025-04-06 16:24:47 +0200133# expect_equal(
134# removeWithinSpan("contains(<base/s=s>, (machen []{0,1} aufmerksam | aufmerksam []{0,1} machen))", "base/s=s"),
135# "(machen []{0,1} aufmerksam | aufmerksam []{0,1} machen)")
Marc Kupietz7de5f322025-06-04 17:17:22 +0200136# })
Marc Kupietzdbdbb1f2025-02-19 10:33:06 +0100137
138
139test_that("mergeDuplicateCollocatesWorksAsExpected", {
Marc Kupietz5057f502025-04-06 16:55:57 +0200140 ldf <- tibble::tibble(
Marc Kupietzdbdbb1f2025-02-19 10:33:06 +0100141 node = c("focus(in [tt/p=NN] {[tt/l=nehmen]})"),
142 collocate = c("Anspruch"),
143 label = c(""),
144 vc = c(""),
145 query = c("Anspruch focus(in [tt/p=NN] {[tt/l=nehmen]})"),
146 webUIRequestUrl = c(
147 "https://korap.ids-mannheim.de/?q=Anspruch%20focus%28in%20%5btt%2fp%3dNN%5d%20%7b%5btt%2fl%3dnehmen%5d%7d%29&ql=poliqarp"
148 ),
149 w = c(1),
150 leftContextSize = c(1),
151 rightContextSize = c(0),
152 N = c(23578528381.5),
153 O = c(0.5),
154 O1 = c(1168410.5),
155 O2 = c(1296870.5),
156 E = c(64.2651265093014),
157 pmi = c(11.9173498777957),
158 mi2 = c(29.8406639214616),
159 mi3 = c(47.7639779651274),
160 logDice = c(11.6899933757298),
161 ll = c(3717716.74208791)
162 )
Marc Kupietz5057f502025-04-06 16:55:57 +0200163 rdf <- tibble::tibble(
Marc Kupietzdbdbb1f2025-02-19 10:33:06 +0100164 node = c("focus({[tt/l=nehmen] in} [tt/p=NN])"),
165 collocate = c("Anspruch"),
166 label = c(""),
167 vc = c(""),
168 query = c("focus({[tt/l=nehmen] in} [tt/p=NN]) Anspruch"),
169 webUIRequestUrl = c(
170 "https://korap.ids-mannheim.de/?q=focus%28%7b%5btt%2fl%3dnehmen%5d%20in%7d%20%5btt%2fp%3dNN%5d%29%20Anspruch&ql=poliqarp"
171 ),
172 w = c(1),
173 leftContextSize = c(0),
174 rightContextSize = c(1),
175 N = c(23578528381.5),
176 O = c(0.5),
177 O1 = c(17077.5),
178 O2 = c(1296870.5),
179 E = c(0.939299756346416),
180 pmi = c(7.99469408391783),
181 mi2 = c(15.8990457079122),
182 mi3 = c(23.8033973319065),
183 logDice = c(2.57887487309409),
184 ll = c(2181.35986032019)
185 )
186 merged <- mergeDuplicateCollocates(ldf, rdf, smoothingConstant = 0.5)
187 expect_equal(merged$O, 0.5)
188 expect_equal(merged$O1, 1185487.5)
189 expect_equal(merged$O2, 1296870.5)
190 expect_equal(merged$query, "Anspruch focus(in [tt/p=NN] {[tt/l=nehmen]}) | focus({[tt/l=nehmen] in} [tt/p=NN]) Anspruch")
191})
Marc Kupietz7de5f322025-06-04 17:17:22 +0200192
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200193test_that("add_multi_vc_comparisons adds favorite columns", {
194 sample_result <- tibble::tibble(
195 node = c("n", "n"),
196 collocate = c("c", "c"),
197 vc = c("vc1", "vc2"),
198 label = c("A", "B"),
199 N = c(100, 100),
200 O = c(10, 20),
201 O1 = c(50, 50),
202 O2 = c(30, 30),
203 E = c(5, 5),
204 w = c(2, 2),
205 leftContextSize = c(1, 1),
206 rightContextSize = c(1, 1),
207 frequency = c(10, 20),
Marc Kupietz09b1c082026-05-01 14:45:47 +0200208 webUIRequestUrl = c("https://korap.example/A", "https://korap.example/B"),
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200209 logDice = c(5, 7),
210 pmi = c(2, 3)
211 )
212
Marc Kupietz77852b22025-10-19 11:35:34 +0200213 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200214
215 expect_true(all(c(
216 "winner_logDice",
217 "winner_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200218 "winner_logDice_webUIRequestUrl",
219 "winner_webUIRequestUrl",
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200220 "runner_up_logDice",
Marc Kupietz28a29842025-10-18 12:25:09 +0200221 "runner_up_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200222 "loser_logDice_webUIRequestUrl",
223 "loser_webUIRequestUrl",
Marc Kupietz28a29842025-10-18 12:25:09 +0200224 "max_delta_logDice",
225 "winner_rank_logDice",
226 "winner_rank_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200227 "winner_rank_logDice_webUIRequestUrl",
Marc Kupietz28a29842025-10-18 12:25:09 +0200228 "runner_up_rank_logDice",
229 "runner_up_rank_logDice_value",
230 "loser_rank_logDice",
231 "loser_rank_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200232 "loser_rank_logDice_webUIRequestUrl",
Marc Kupietz28a29842025-10-18 12:25:09 +0200233 "max_delta_rank_logDice",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200234 "winner_percentile_rank_logDice",
235 "winner_percentile_rank_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200236 "winner_percentile_rank_logDice_webUIRequestUrl",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200237 "runner_up_percentile_rank_logDice",
238 "runner_up_percentile_rank_logDice_value",
239 "loser_percentile_rank_logDice",
240 "loser_percentile_rank_logDice_value",
Marc Kupietz09b1c082026-05-01 14:45:47 +0200241 "loser_percentile_rank_logDice_webUIRequestUrl",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200242 "max_delta_percentile_rank_logDice",
Marc Kupietz28a29842025-10-18 12:25:09 +0200243 "winner_rank_pmi",
244 "winner_rank_pmi_value",
245 "runner_up_rank_pmi",
246 "runner_up_rank_pmi_value",
247 "loser_rank_pmi",
248 "loser_rank_pmi_value",
249 "max_delta_rank_pmi",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200250 "winner_percentile_rank_pmi",
251 "winner_percentile_rank_pmi_value",
252 "runner_up_percentile_rank_pmi",
253 "runner_up_percentile_rank_pmi_value",
254 "loser_percentile_rank_pmi",
255 "loser_percentile_rank_pmi_value",
256 "max_delta_percentile_rank_pmi",
Marc Kupietz28a29842025-10-18 12:25:09 +0200257 "rank_A_logDice",
258 "rank_B_logDice",
259 "rank_A_pmi",
260 "rank_B_pmi",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200261 "percentile_rank_A_logDice",
262 "percentile_rank_B_logDice",
263 "percentile_rank_A_pmi",
264 "percentile_rank_B_pmi",
Marc Kupietz28a29842025-10-18 12:25:09 +0200265 "delta_rank_logDice",
Marc Kupietz130a2a22025-10-18 16:09:23 +0200266 "delta_rank_pmi",
267 "delta_percentile_rank_logDice",
268 "delta_percentile_rank_pmi"
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200269 ) %in% colnames(enriched)))
270
271 expect_true(all(enriched$winner_logDice == "B"))
272 expect_true(all(enriched$runner_up_logDice == "A"))
273 expect_true(all(enriched$winner_logDice_value >= enriched$runner_up_logDice_value))
Marc Kupietz09b1c082026-05-01 14:45:47 +0200274 expect_true(all(enriched$winner_logDice_webUIRequestUrl == "https://korap.example/B"))
275 expect_true(all(enriched$loser_logDice_webUIRequestUrl == "https://korap.example/A"))
276 expect_true(all(enriched$winner_webUIRequestUrl == "https://korap.example/B"))
277 expect_true(all(enriched$loser_webUIRequestUrl == "https://korap.example/A"))
Marc Kupietz130a2a22025-10-18 16:09:23 +0200278 expect_true(all(enriched$percentile_rank_A_logDice == 1))
279 expect_true(all(enriched$percentile_rank_B_logDice == 1))
280 expect_true(all(enriched$delta_percentile_rank_logDice == 0))
Marc Kupietz5e35d7a2025-10-17 21:21:22 +0200281})
282
Marc Kupietz09b1c082026-05-01 14:45:47 +0200283test_that("add_multi_vc_comparisons fills missing label URLs from vc", {
284 sample_result <- tibble::tibble(
285 node = c("n", "n", "n"),
286 collocate = c("c1", "c2", "c2"),
287 vc = c("corpusSigle=/A/", "corpusSigle=/A/", "corpusSigle=/B/"),
288 label = c("A", "A", "B"),
289 N = c(100, 100, 100),
290 O = c(10, 10, 20),
291 O1 = c(50, 50, 50),
292 O2 = c(30, 30, 30),
293 E = c(5, 5, 5),
294 w = c(2, 2, 2),
295 leftContextSize = c(1, 1, 1),
296 rightContextSize = c(1, 1, 1),
297 frequency = c(10, 10, 20),
298 webUIRequestUrl = c(
299 "https://korap.example/?q=q1&cq=corpusSigle%3D%2FA%2F&ql=poliqarp",
300 "https://korap.example/?q=q2&cq=corpusSigle%3D%2FA%2F&ql=poliqarp",
301 "https://korap.example/?q=q2&cq=corpusSigle%3D%2FB%2F&ql=poliqarp"
302 ),
303 logDice = c(6, 5, 7),
304 pmi = c(3, 2, 4)
305 )
306
307 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
308 c1 <- enriched[enriched$collocate == "c1", ]
309 expected_b_url <- paste0(
310 "https://korap.example/?q=q1&cq=",
311 urltools::url_encode("corpusSigle=/B/"),
312 "&ql=poliqarp"
313 )
314
315 expect_equal(
316 unique(c1$loser_logDice_webUIRequestUrl),
317 expected_b_url
318 )
319 expect_equal(
320 unique(c1$loser_webUIRequestUrl),
321 expected_b_url
322 )
323})
324
Marc Kupietzd7bb5cb2026-08-31 10:18:02 +0200325test_that("add_multi_vc_comparisons flags imputed cells", {
326 sample_result <- tibble::tibble(
327 node = c("n", "n", "n"),
328 collocate = c("c1", "c2", "c2"),
329 vc = c("corpusSigle=/A/", "corpusSigle=/A/", "corpusSigle=/B/"),
330 label = c("A", "A", "B"),
331 N = c(100, 100, 100),
332 O = c(10, 10, 20),
333 O1 = c(50, 50, 50),
334 O2 = c(30, 30, 30),
335 E = c(5, 5, 5),
336 w = c(2, 2, 2),
337 leftContextSize = c(1, 1, 1),
338 rightContextSize = c(1, 1, 1),
339 frequency = c(10, 10, 20),
340 webUIRequestUrl = c(
341 "https://korap.example/A1",
342 "https://korap.example/A2",
343 "https://korap.example/B2"
344 ),
345 logDice = c(6, 5, 7),
346 pmi = c(3, 2, 4)
347 )
348
349 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
350
351 expect_true(all(c("imputed_A", "imputed_B", "n_imputed", "imputed") %in% colnames(enriched)))
352
353 c1 <- enriched[enriched$collocate == "c1", ]
354 c2 <- enriched[enriched$collocate == "c2", ]
355
356 # c1 only occurs in A, so B's scores had to be imputed
357 expect_true(all(c1$imputed_B))
358 expect_true(all(!c1$imputed_A))
359 expect_true(all(c1$imputed))
360 expect_true(all(c1$n_imputed == 1L))
361
362 # c2 occurs in both, so nothing is imputed
363 expect_true(all(!c2$imputed_A))
364 expect_true(all(!c2$imputed_B))
365 expect_true(all(!c2$imputed))
366 expect_true(all(c2$n_imputed == 0L))
367
368 # c1's delta is measured against the imputed floor rather than against observed data:
369 # the absent label loses, at or below the weakest score actually attested anywhere.
370 expect_true(all(is.finite(c1$max_delta_logDice)))
371 expect_equal(unique(c1$loser_logDice), "B")
372 expect_lte(unique(c1$loser_logDice_value), min(sample_result$logDice))
373})
374
Marc Kupietz7b7a73b2026-08-31 10:31:27 +0200375test_that("add_multi_vc_comparisons warns about dropped duplicate rows", {
376 sample_result <- tibble::tibble(
377 node = rep("n", 4),
378 collocate = c("c", "c", "c", "c"),
379 vc = c("vc1", "vc1", "vc2", "vc2"),
380 # the same collocate twice per label, as after collecting several context positions
381 label = c("A", "A", "B", "B"),
382 N = rep(100, 4),
383 O = c(10, 11, 20, 21),
384 O1 = rep(50, 4),
385 O2 = rep(30, 4),
386 E = rep(5, 4),
387 w = rep(2, 4),
388 leftContextSize = rep(1, 4),
389 rightContextSize = rep(1, 4),
390 frequency = c(10, 11, 20, 21),
391 logDice = c(5, 6, 7, 8),
392 pmi = c(2, 2, 3, 3)
393 )
394
395 expect_warning(
396 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result),
397 "occur more than once"
398 )
399 expect_warning(
400 RKorAPClient:::add_multi_vc_comparisons(sample_result),
401 "mergeDuplicateCollocates"
402 )
403 # the first row of each combination is what ends up in the comparison
404 expect_true(all(enriched$logDice_A == 5))
405 expect_true(all(enriched$logDice_B == 7))
406})
407
408test_that("add_multi_vc_comparisons is silent for unique node/collocate/label rows", {
409 sample_result <- tibble::tibble(
410 node = rep("n", 4),
411 collocate = c("c1", "c1", "c2", "c2"),
412 vc = rep(c("vc1", "vc2"), 2),
413 label = rep(c("A", "B"), 2),
414 N = rep(100, 4),
415 O = c(10, 20, 30, 40),
416 O1 = rep(50, 4),
417 O2 = rep(30, 4),
418 E = rep(5, 4),
419 w = rep(2, 4),
420 leftContextSize = rep(1, 4),
421 rightContextSize = rep(1, 4),
422 frequency = c(10, 20, 30, 40),
423 logDice = c(5, 7, 6, 4),
424 pmi = c(2, 3, 4, 1)
425 )
426
427 expect_no_warning(RKorAPClient:::add_multi_vc_comparisons(sample_result))
428})
429
Marc Kupietz424cb782026-08-31 10:19:29 +0200430test_that("add_multi_vc_comparisons reports imputation when verbose", {
431 sample_result <- tibble::tibble(
432 node = c("n", "n", "n"),
433 collocate = c("c1", "c2", "c2"),
434 vc = c("vc1", "vc1", "vc2"),
435 label = c("A", "A", "B"),
436 N = rep(100, 3),
437 O = c(10, 10, 20),
438 O1 = rep(50, 3),
439 O2 = rep(30, 3),
440 E = rep(5, 3),
441 w = rep(2, 3),
442 leftContextSize = rep(1, 3),
443 rightContextSize = rep(1, 3),
444 frequency = c(10, 10, 20),
445 logDice = c(6, 5, 7),
446 pmi = c(3, 2, 4)
447 )
448
449 output <- capture.output(
450 RKorAPClient:::add_multi_vc_comparisons(sample_result, verbose = TRUE)
451 )
452 expect_match(paste(output, collapse = " "), "Imputed scores for 1 of 2")
453 expect_match(paste(output, collapse = " "), "queryMissingScores")
454
455 # nothing is printed unless verbose
456 expect_silent(RKorAPClient:::add_multi_vc_comparisons(sample_result, verbose = FALSE))
457})
458
Marc Kupietzd7bb5cb2026-08-31 10:18:02 +0200459test_that("add_multi_vc_comparisons reports no imputation when all labels are complete", {
460 sample_result <- tibble::tibble(
461 node = rep("n", 4),
462 collocate = c("c1", "c1", "c2", "c2"),
463 vc = rep(c("vc1", "vc2"), 2),
464 label = rep(c("A", "B"), 2),
465 N = rep(100, 4),
466 O = c(10, 20, 30, 40),
467 O1 = rep(50, 4),
468 O2 = rep(30, 4),
469 E = rep(5, 4),
470 w = rep(2, 4),
471 leftContextSize = rep(1, 4),
472 rightContextSize = rep(1, 4),
473 frequency = c(10, 20, 30, 40),
474 logDice = c(5, 7, 6, 4),
475 pmi = c(2, 3, 4, 1)
476 )
477
478 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
479
480 expect_true(all(!enriched$imputed))
481 expect_true(all(enriched$n_imputed == 0L))
482})
483
Marc Kupietzb2862d42025-10-18 10:17:49 +0200484test_that("add_multi_vc_comparisons handles more than two labels", {
485 sample_result <- tibble::tibble(
486 node = rep("n", 3),
487 collocate = rep("c", 3),
488 vc = c("vc1", "vc2", "vc3"),
489 label = c("A", "B", "C"),
490 N = rep(100, 3),
491 O = c(10, 30, 5),
492 O1 = rep(50, 3),
493 O2 = rep(30, 3),
494 E = rep(5, 3),
495 w = rep(2, 3),
496 leftContextSize = rep(1, 3),
497 rightContextSize = rep(1, 3),
498 frequency = c(10, 30, 5),
Marc Kupietz09b1c082026-05-01 14:45:47 +0200499 webUIRequestUrl = c("https://korap.example/A", "https://korap.example/B", "https://korap.example/C"),
Marc Kupietzb2862d42025-10-18 10:17:49 +0200500 logDice = c(5, 8, 4),
501 pmi = c(2, 3, 1)
502 )
503
Marc Kupietz77852b22025-10-19 11:35:34 +0200504 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
Marc Kupietzb2862d42025-10-18 10:17:49 +0200505 expect_equal(enriched$winner_logDice[1], "B")
506 expect_equal(enriched$winner_logDice_value[1], 8)
Marc Kupietz09b1c082026-05-01 14:45:47 +0200507 expect_equal(enriched$winner_logDice_webUIRequestUrl[1], "https://korap.example/B")
Marc Kupietzb2862d42025-10-18 10:17:49 +0200508 expect_equal(enriched$runner_up_logDice[1], "A")
509 expect_equal(enriched$runner_up_logDice_value[1], 5)
510 expect_equal(enriched$loser_logDice[1], "C")
511 expect_equal(enriched$loser_logDice_value[1], 4)
Marc Kupietz09b1c082026-05-01 14:45:47 +0200512 expect_equal(enriched$loser_logDice_webUIRequestUrl[1], "https://korap.example/C")
Marc Kupietzb2862d42025-10-18 10:17:49 +0200513 expect_equal(enriched$max_delta_logDice[1], 4)
514})
515
Marc Kupietz09b1c082026-05-01 14:45:47 +0200516test_that("add_multi_vc_comparisons leaves unsuffixed URLs ambiguous when scores disagree", {
517 sample_result <- tibble::tibble(
518 node = c("n", "n"),
519 collocate = c("c", "c"),
520 vc = c("vc1", "vc2"),
521 label = c("A", "B"),
522 N = c(100, 100),
523 O = c(10, 20),
524 O1 = c(50, 50),
525 O2 = c(30, 30),
526 E = c(5, 5),
527 w = c(2, 2),
528 leftContextSize = c(1, 1),
529 rightContextSize = c(1, 1),
530 frequency = c(10, 20),
531 webUIRequestUrl = c("https://korap.example/A", "https://korap.example/B"),
532 logDice = c(5, 7),
533 pmi = c(4, 3)
534 )
535
536 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
537
538 expect_equal(enriched$winner_logDice_webUIRequestUrl[1], "https://korap.example/B")
539 expect_equal(enriched$winner_pmi_webUIRequestUrl[1], "https://korap.example/A")
540 expect_true(is.na(enriched$winner_webUIRequestUrl[1]))
541 expect_true(is.na(enriched$loser_webUIRequestUrl[1]))
542})
543
Marc Kupietz28a29842025-10-18 12:25:09 +0200544test_that("add_multi_vc_comparisons computes rank deltas", {
545 base_tbl <- tidyr::expand_grid(
546 label = c("A", "B", "C"),
547 collocate = c("c1", "c2", "c3")
548 ) |>
549 dplyr::mutate(
550 node = "n",
551 vc = paste0("vc", label),
552 N = 100,
553 O = 10,
554 O1 = 50,
555 O2 = 40,
556 E = 5,
557 w = 2,
558 leftContextSize = 1,
559 rightContextSize = 1,
560 frequency = 10,
561 logDice = dplyr::case_when(
562 label == "A" & collocate == "c1" ~ 9,
563 label == "A" & collocate == "c2" ~ 6,
564 label == "A" & collocate == "c3" ~ 3,
565 label == "B" & collocate == "c1" ~ 7,
566 label == "B" & collocate == "c2" ~ 9,
567 label == "B" & collocate == "c3" ~ 5,
568 label == "C" & collocate == "c1" ~ 4,
569 label == "C" & collocate == "c2" ~ 6,
570 label == "C" & collocate == "c3" ~ 8,
571 TRUE ~ 0
572 )
573 )
574
Marc Kupietz77852b22025-10-19 11:35:34 +0200575 enriched <- RKorAPClient:::add_multi_vc_comparisons(base_tbl)
Marc Kupietz28a29842025-10-18 12:25:09 +0200576 target_row <- enriched |>
577 dplyr::filter(collocate == "c1") |>
578 dplyr::slice_head(n = 1)
579
580 expect_equal(target_row$rank_A_logDice, 1)
581 expect_equal(target_row$rank_B_logDice, 2)
582 expect_equal(target_row$rank_C_logDice, 3)
583 expect_equal(target_row$winner_rank_logDice, "A")
584 expect_equal(target_row$winner_rank_logDice_value, 1)
585 expect_equal(target_row$runner_up_rank_logDice, "B")
586 expect_equal(target_row$runner_up_rank_logDice_value, 2)
587 expect_equal(target_row$loser_rank_logDice, "C")
588 expect_equal(target_row$loser_rank_logDice_value, 3)
589 expect_equal(target_row$max_delta_rank_logDice, 2)
590})
591
592test_that("add_multi_vc_comparisons imputes missing ranks for max delta", {
593 sample_result <- tibble::tibble(
594 node = c("n", "n"),
595 collocate = c("c", "c"),
596 vc = c("vc1", "vc2"),
597 label = c("A", "B"),
598 N = c(100, 100),
599 O = c(10, 10),
600 O1 = c(50, 50),
601 O2 = c(30, 30),
602 E = c(5, 5),
603 w = c(2, 2),
604 leftContextSize = c(1, 1),
605 rightContextSize = c(1, 1),
606 frequency = c(10, 10),
607 logDice = c(5, NA)
608 )
609
Marc Kupietz77852b22025-10-19 11:35:34 +0200610 enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result)
Marc Kupietz28a29842025-10-18 12:25:09 +0200611
612 expect_equal(enriched$rank_A_logDice[1], 1)
613 expect_true(is.na(enriched$rank_B_logDice[1]))
614 expect_equal(enriched$winner_rank_logDice[1], "A")
615 expect_equal(enriched$loser_rank_logDice[1], "B")
616 expect_equal(enriched$loser_rank_logDice_value[1], 2)
617 expect_equal(enriched$max_delta_rank_logDice[1], 1)
618})
619
Marc Kupietz9894a372025-10-18 14:51:29 +0200620test_that("adaptive missing score imputation respects measure-specific scales", {
621 sample_result <- tibble::tibble(
622 node = c("n", "n", "n"),
623 collocate = c("c", "c", "c"),
624 vc = c("vc1", "vc2", "vc3"),
625 label = c("A", "B", "C"),
626 N = c(100, 100, 100),
627 O = c(12, 9, 7),
628 O1 = c(60, 40, 30),
629 O2 = c(33, 22, 18),
630 E = c(6, 6, 6),
631 w = c(2, 2, 2),
632 leftContextSize = c(1, 1, 1),
633 rightContextSize = c(1, 1, 1),
634 frequency = c(15, 11, 9),
635 logDice = c(-0.31, NA, -0.12),
636 pmi = c(-1.65, NA, -0.48),
637 ll = c(12.4, NA, 7.9)
638 )
639
640 enriched <- RKorAPClient:::add_multi_vc_comparisons(
641 sample_result,
Marc Kupietz9894a372025-10-18 14:51:29 +0200642 missingScoreQuantile = 0.05
643 )
644
645 row_a <- dplyr::filter(enriched, label == "A") |> dplyr::slice_head(n = 1)
646
647 expect_false(is.na(row_a$logDice_B))
648 expect_false(is.na(row_a$pmi_B))
649 expect_false(is.na(row_a$ll_B))
650
651 expect_lt(row_a$logDice_B, min(sample_result$logDice, na.rm = TRUE))
652 expect_lt(row_a$pmi_B, min(sample_result$pmi, na.rm = TRUE))
653 expect_lte(row_a$ll_B, min(sample_result$ll, na.rm = TRUE))
654
655 expect_gt(row_a$max_delta_logDice, 0)
656 expect_gt(row_a$winner_logDice_value - row_a$loser_logDice_value, 0)
657})
658
Marc Kupietz7de5f322025-06-04 17:17:22 +0200659# New tests for improved coverage of collocationAnalysis.R helper functions
660
661test_that("synsemanticStopwords returns German stopwords", {
662 stopwords <- synsemanticStopwords()
663 expect_true(is.character(stopwords))
664 expect_true(length(stopwords) > 50)
665 expect_true("der" %in% stopwords)
666 expect_true("die" %in% stopwords)
667 expect_true("und" %in% stopwords)
668 expect_true("mit" %in% stopwords)
669})
670
671test_that("removeWithinSpan removes span constraints correctly", {
672 # Test basic span removal
673 query1 <- "contains(<base/s=s>, (machen []{0,1} aufmerksam | aufmerksam []{0,1} machen))"
674 result1 <- RKorAPClient:::removeWithinSpan(query1, "base/s=s")
675 expect_equal(result1, "(machen []{0,1} aufmerksam | aufmerksam []{0,1} machen)")
676
677 # Test with different span
678 query2 <- "contains(<p/s=s>, (test query))"
679 result2 <- RKorAPClient:::removeWithinSpan(query2, "p/s=s")
680 expect_equal(result2, "(test query)")
681
682 # Test with empty span - should return original query
683 query3 <- "simple query"
684 result3 <- RKorAPClient:::removeWithinSpan(query3, "")
685 expect_equal(result3, query3)
686
687 # Test with non-matching span
688 query4 <- "contains(<base/s=s>, test)"
689 result4 <- RKorAPClient:::removeWithinSpan(query4, "other/span")
690 expect_equal(result4, query4)
691})
692
693test_that("matches2FreqTable handles empty matches", {
694 empty_matches <- data.frame()
695 result <- RKorAPClient:::matches2FreqTable(empty_matches, index = 0)
696
697 expect_true(is.data.frame(result))
698 expect_equal(nrow(result), 0)
699})
700
701test_that("matches2FreqTable processes single match correctly", {
702 # Create mock matches data
703 mock_matches <- data.frame(
704 tokens = I(list(list(
705 left = c("der", "große"),
706 match = "Test",
707 right = c("ist", "wichtig")
708 ))),
709 stringsAsFactors = FALSE
710 )
711
712 result <- RKorAPClient:::matches2FreqTable(
713 mock_matches,
714 index = 1,
715 leftContextSize = 2,
716 rightContextSize = 2,
717 stopwords = c("der", "ist") # Provide stopwords to avoid empty join
718 )
719
720 expect_true(is.data.frame(result))
721})
722
723test_that("snippet2FreqTable handles empty snippet", {
724 result <- RKorAPClient:::snippet2FreqTable(character(0))
725
726 expect_true(is.data.frame(result))
727 expect_equal(nrow(result), 0)
728})
729
730test_that("snippet2FreqTable processes single snippet correctly", {
731 snippet <- '<span class="context-left">der große </span><span class="match"><mark>Test</mark></span><span class="context-right"> ist wichtig</span>'
732
733 result <- RKorAPClient:::snippet2FreqTable(
734 snippet,
735 leftContextSize = 2,
736 rightContextSize = 2,
737 stopwords = c("der"), # Provide stopwords to avoid empty join
738 verbose = FALSE
739 )
740
741 expect_true(is.data.frame(result))
742})
743
Marc Kupietz0a292632025-10-19 14:04:36 +0200744test_that("inject_focus_into_query adds focus wrappers when span queries lack them", {
745 unfocused <- "contains(<base/s=s>, (Anspruch [tt/l=nehmen] | [tt/l=nehmen] Anspruch))"
746 focused <- RKorAPClient:::inject_focus_into_query(unfocused)
747
748 expect_equal(
749 focused,
750 "contains(<base/s=s>, (focus({Anspruch [tt/l=nehmen]}) | focus({[tt/l=nehmen] Anspruch})))"
751 )
752})
753
754test_that("inject_focus_into_query leaves existing focus segments unchanged", {
755 already_focused <- "contains(<base/s=s>, (focus({Anspruch [tt/l=nehmen]})))"
756 expect_identical(
757 RKorAPClient:::inject_focus_into_query(already_focused),
758 already_focused
759 )
760})
761
Marc Kupietz7de5f322025-06-04 17:17:22 +0200762# Removed hanging findExample tests as they cause infinite wait
763# These tests make API calls that don't complete properly
764
765# Removed hanging collocatesQuery tests as they cause infinite wait
766# These tests were causing the test suite to hang and not terminate
767
768test_that("collocationAnalysis handles exactFrequencies parameter", {
769 skip_if_offline()
770 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
771
772 expect_warning(
773 result <- collocationAnalysis(
774 kco,
775 "Test",
776 exactFrequencies = TRUE,
777 searchHitsSampleLimit = 5,
778 topCollocatesLimit = 5
779 ),
780 "access token"
781 )
782 expect_true(is.data.frame(result))
783})
784
785test_that("collocationAnalysis handles withinSpan parameter", {
786 skip_if_offline()
787 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
788
789 expect_warning(
790 result <- collocationAnalysis(
791 kco,
792 "Test",
793 withinSpan = "base/s=s",
794 exactFrequencies = TRUE,
795 searchHitsSampleLimit = 5,
796 topCollocatesLimit = 5
797 ),
798 "access token"
799 )
800 expect_true(is.data.frame(result))
801})
802
803test_that("collocationAnalysis handles expand parameter", {
804 skip_if_offline()
805 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
806
807 expect_warning(
808 result <- collocationAnalysis(
809 kco,
810 c("Test", "der"),
811 expand = TRUE,
812 searchHitsSampleLimit = 2,
813 topCollocatesLimit = 2
814 ),
815 "access token"
816 )
817 expect_true(is.data.frame(result))
818})
819
Marc Kupietze34a8be2025-10-17 20:13:42 +0200820test_that("collocationAnalysis honors named vc labels", {
821 skip_if_offline()
822 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
823
824 named_vc <- c(
825 Western = "textType=/.*Western.*/ & pubDate in 2012",
826 Erotic = "textType=/.*(Erotik|Gay).*/ & pubDate in 2012",
827 Historic = "textType=/.*Historisch.*/ & pubDate in 2012"
828 )
829
830 expect_warning(
831 result <- collocationAnalysis(
832 kco,
833 "[tt/l=treffen]",
834 vc = named_vc,
835 searchHitsSampleLimit = 2,
836 topCollocatesLimit = 2
837 ),
838 "access token"
839 )
840
841 if (nrow(result) > 0) {
842 expect_true("label" %in% colnames(result))
843 expect_setequal(unique(result$label), names(named_vc))
844 }
845})
846
Marc Kupietz7de5f322025-06-04 17:17:22 +0200847test_that("collocationAnalysis handles stopwords parameter", {
848 skip_if_offline()
849 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
850
851 expect_warning(
852 result <- collocationAnalysis(
853 kco,
854 "Test",
855 stopwords = c("der", "die", "und"),
856 searchHitsSampleLimit = 5,
857 topCollocatesLimit = 5
858 ),
859 "access token"
860 )
861 expect_true(is.data.frame(result))
862})
863
864test_that("collocationAnalysis handles lemmatizeNodeQuery parameter", {
865 skip_if_offline()
866 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
867
868 expect_warning(
869 result <- collocationAnalysis(
870 kco,
871 "laufen",
872 lemmatizeNodeQuery = TRUE,
873 searchHitsSampleLimit = 5,
874 topCollocatesLimit = 5
875 ),
876 "access token"
877 )
878 expect_true(is.data.frame(result))
879})
880
881test_that("collocationAnalysis handles addExamples parameter", {
882 skip_if_offline()
883 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
884
885 expect_warning(
886 result <- collocationAnalysis(
887 kco,
888 "Test",
889 addExamples = TRUE,
890 searchHitsSampleLimit = 3,
891 topCollocatesLimit = 3
892 ),
893 "access token"
894 )
895 expect_true(is.data.frame(result))
896 if (nrow(result) > 0) {
897 expect_true("example" %in% colnames(result))
898 }
899})
900
901test_that("collocationAnalysis handles maxRecurse parameter", {
902 skip_if_offline()
903 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
904
905 expect_warning(
906 result <- collocationAnalysis(
907 kco,
908 "Test",
909 maxRecurse = 1,
910 searchHitsSampleLimit = 2,
911 topCollocatesLimit = 2
912 ),
913 "access token"
914 )
915 expect_true(is.data.frame(result))
916})
Marc Kupietzbceeb052026-08-31 10:30:07 +0200917
918test_that("collocationAnalysis returns cached result without contacting the server", {
919 kco <- methods::new(
920 "KorAPConnection",
921 apiUrl = "https://example.invalid/",
922 KorAPUrl = "https://example.invalid/",
Marc Kupietzc902c232026-09-08 07:58:01 +0200923 authorizationSupported = FALSE,
Marc Kupietzbceeb052026-08-31 10:30:07 +0200924 verbose = FALSE
925 )
926
Marc Kupietzbceeb052026-08-31 10:30:07 +0200927 cache_file <- tempfile(fileext = ".rds")
928 on.exit(unlink(cache_file), add = TRUE)
Marc Kupietzc902c232026-09-08 07:58:01 +0200929
930 # written by the package itself, so that it carries the record saying what
931 # produced it - a file without one is refused, as one from an older version
932 cached <- local({
933 testthat::local_mocked_bindings(
934 collocatesQuery = function(...) tibble::tibble(),
935 .package = "RKorAPClient"
936 )
937 collocationAnalysis(kco, "n", cacheAs = cache_file)
938 })
Marc Kupietzbceeb052026-08-31 10:30:07 +0200939
940 testthat::local_mocked_bindings(
941 corpusQuery = function(...) stop("server must not be contacted"),
Marc Kupietzc902c232026-09-08 07:58:01 +0200942 collocatesQuery = function(...) stop("server must not be contacted"),
Marc Kupietzbceeb052026-08-31 10:30:07 +0200943 .package = "RKorAPClient"
944 )
945
946 expect_equal(
947 collocationAnalysis(kco, "n", cacheAs = cache_file),
948 cached
949 )
950})
951
952test_that("collocationAnalysis appends the .rds extension to cacheAs", {
953 kco <- methods::new(
954 "KorAPConnection",
955 apiUrl = "https://example.invalid/",
956 KorAPUrl = "https://example.invalid/",
957 verbose = FALSE
958 )
959
Marc Kupietzbceeb052026-08-31 10:30:07 +0200960 base_path <- tempfile()
961 on.exit(unlink(paste0(base_path, ".rds")), add = TRUE)
Marc Kupietzc902c232026-09-08 07:58:01 +0200962
963 # given without extension, .rds is appended when writing
964 cached <- local({
965 testthat::local_mocked_bindings(
966 collocatesQuery = function(...) tibble::tibble(),
967 .package = "RKorAPClient"
968 )
969 collocationAnalysis(kco, "n", cacheAs = base_path)
970 })
971 expect_true(file.exists(paste0(base_path, ".rds")))
Marc Kupietzbceeb052026-08-31 10:30:07 +0200972
973 testthat::local_mocked_bindings(
974 corpusQuery = function(...) stop("server must not be contacted"),
Marc Kupietzc902c232026-09-08 07:58:01 +0200975 collocatesQuery = function(...) stop("server must not be contacted"),
Marc Kupietzbceeb052026-08-31 10:30:07 +0200976 .package = "RKorAPClient"
977 )
978
Marc Kupietzc902c232026-09-08 07:58:01 +0200979 # and the same file is found again, given with or without the extension
Marc Kupietzbceeb052026-08-31 10:30:07 +0200980 expect_equal(collocationAnalysis(kco, "n", cacheAs = base_path), cached)
Marc Kupietzbceeb052026-08-31 10:30:07 +0200981 expect_equal(collocationAnalysis(kco, "n", cacheAs = paste0(base_path, ".rds")), cached)
982})
983
984test_that("collocationAnalysis writes and reuses its cache file", {
985 skip_if_offline()
986 kco <- KorAPConnection(accessToken = NULL, cache = TRUE, verbose = FALSE)
987
988 cache_file <- tempfile(fileext = ".rds")
989 on.exit(unlink(cache_file), add = TRUE)
990 expect_false(file.exists(cache_file))
991
992 expect_warning(
993 first <- collocationAnalysis(
994 kco,
995 "Ameisenplage",
996 searchHitsSampleLimit = 2,
997 topCollocatesLimit = 2,
998 cacheAs = cache_file
999 ),
1000 "access token"
1001 )
1002
1003 expect_true(file.exists(cache_file))
Marc Kupietzd4a7b302026-09-04 10:40:48 +02001004 # the file records the analysis parameters, the returned value does not
1005 cached <- readRDS(cache_file)
Marc Kupietzc902c232026-09-08 07:58:01 +02001006 expect_false(is.null(attr(cached, RKorAPClient:::cacheAsAttribute)))
1007 attr(cached, RKorAPClient:::cacheAsAttribute) <- NULL
Marc Kupietzd4a7b302026-09-04 10:40:48 +02001008 expect_equal(cached, first)
Marc Kupietzbceeb052026-08-31 10:30:07 +02001009
1010 # the second call is served from the cache and therefore issues no token warning
1011 second <- collocationAnalysis(
1012 kco,
1013 "Ameisenplage",
1014 searchHitsSampleLimit = 2,
1015 topCollocatesLimit = 2,
1016 cacheAs = cache_file
1017 )
1018 expect_equal(second, first)
1019})