1
/*
2
Copyright (C) 2020-2021 Kunal Mehta <legoktm@debian.org>
3

            
4
This program is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
8

            
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13

            
14
You should have received a copy of the GNU General Public License
15
along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
use crate::prelude::*;
18
use crate::tests::test_client;
19
use crate::{map::IndexMap, Result};
20

            
21
#[tokio::test]
22
3
async fn test_templates() -> Result<()> {
23
3
    let client = test_client::mw_client();
24
3
    let code = client.get("MediaWiki").await?.into_mutable();
25
3
    let mut found = false;
26
5
    for template in code.filter_templates()? {
27
5
        if template.name_in_wikitext() == "Main page" {
28
5
            found = true;
29
5
        }
30
2
    }
31
3
    assert!(found);
32
3
    Ok(())
33
2
}
34

            
35
#[tokio::test]
36
3
async fn test_more_cases() -> Result<()> {
37
3
    let client = test_client::mw_client();
38
3
    let code = client
39
3
            .transform_to_html(
40
3
                "{{1x|param<!--comment-->name=value|normal=value2}}{{#if:{{{1}}}|foo|bar}}",
41
3
            )
42
3
            .await?.into_mutable();
43
3
    let templates = code.filter_templates()?;
44
2
    // The second parser function wasn't included
45
3
    assert_eq!(templates.len(), 1);
46
3
    let temp = &templates[0];
47
3
    assert!(temp.is_template());
48
3
    assert!(!temp.is_parser_function());
49
3
    assert_eq!(temp.raw_name(), "./Template:1x");
50
3
    assert_eq!(temp.name(), "Template:1x");
51
3
    let mut params = IndexMap::new();
52
3
    params.insert("normal".to_string(), "value2".to_string());
53
3
    params.insert("paramname".to_string(), "value".to_string());
54
3
    assert_eq!(temp.params(), params);
55
3
    assert_eq!(temp.param("paramname"), Some("value".to_string()));
56
3
    assert_eq!(temp.param("notset"), None);
57
3
    assert_eq!(
58
3
        temp.param_in_wikitext("paramname"),
59
3
        Some("param<!--comment-->name".to_string())
60
3
    );
61
3
    assert_eq!(temp.param_in_wikitext("normal"), Some("normal".to_string()));
62
3
    assert_eq!(temp.param_in_wikitext("notset"), None);
63
3
    let pfs = code.filter_parser_functions()?;
64
2
    // The previous template wasn't included
65
3
    assert_eq!(pfs.len(), 1);
66
3
    let pf = &pfs[0];
67
3
    assert!(pf.is_parser_function());
68
3
    assert!(!pf.is_template());
69
3
    assert_eq!(pf.raw_name(), "if");
70
3
    Ok(())
71
2
}
72

            
73
#[tokio::test]
74
3
async fn test_template_mutation() -> Result<()> {
75
3
    let client = test_client::mw_client();
76
3
    let original = "{{1x|foo=bar}}";
77
3
    let code = client.transform_to_html(original).await?.into_mutable();
78
3
    let mut templates = code.filter_templates()?;
79
3
    let temp = &mut templates[0];
80
3
    temp.set_param("new", "placeholder")?;
81
3
    let previous = temp.set_param("new", "wikitext")?;
82
3
    assert_eq!(previous, Some("placeholder".to_string()));
83
3
    let html = client.transform_to_wikitext(&code).await?;
84
3
    assert_eq!(html, "{{1x|foo=bar|new=wikitext}}".to_string());
85
3
    let value = temp.remove_param("new")?;
86
3
    assert_eq!(value, Some("wikitext".to_string()));
87
3
    let new_html = client.transform_to_wikitext(&code).await?;
88
3
    assert_eq!(new_html, original.to_string());
89
3
    temp.set_params([
90
3
        ("baz".to_string(), "blah".to_string()),
91
3
        ("1".to_string(), "what".to_string()),
92
3
    ])?;
93
3
    let html = client.transform_to_wikitext(&code).await?;
94
3
    assert_eq!(&html, "{{1x|baz=blah|what}}");
95
3
    temp.set_name("2x".to_string())?;
96
3
    let html = client.transform_to_wikitext(&code).await?;
97
3
    println!("{}", temp);
98
3
    assert_eq!(&html, "{{2x|baz=blah|what}}");
99
3
    Ok(())
100
2
}
101

            
102
#[tokio::test]
103
3
async fn test_text_contents() -> Result<()> {
104
3
    let client = test_client::mw_client();
105
3
    let code = client
106
3
        .get("User:Legoktm/parsoid-rs/strip_code")
107
3
        .await?
108
3
        .into_mutable();
109
3
    assert_eq!(
110
3
        code.text_contents(),
111
3
        "This is some formatted code. Also a link.".to_string()
112
3
    );
113
3
    Ok(())
114
2
}
115

            
116
#[tokio::test]
117
3
async fn test_wikilinks() -> Result<()> {
118
3
    let client = test_client::mw_client();
119
3
    let code = client
120
3
        .transform_to_html("[[Main Page#some fragment|link text]]")
121
3
        .await?
122
3
        .into_mutable()
123
3
        .without_parsoid_data();
124
3
    let links = code.filter_links();
125
3
    let link = &links[0];
126
3
    assert_eq!(link.raw_target(), "./Main_Page#some_fragment".to_string());
127
3
    assert_eq!(link.target(), "Main Page#some fragment".to_string());
128
3
    assert_eq!(link.text_contents(), "link text".to_string());
129
3
    assert!(link.is_redirect());
130
3
    if link.to_string() != "<a rel=\"mw:WikiLink\" href=\"./Main_Page#some_fragment\" title=\"Main Page\" class=\"mw-redirect\" id=\"mwAw\">link text</a>"
131
2
    && link.to_string() != "<a rel=\"mw:WikiLink\" href=\"./Main_Page#some_fragment\" class=\"mw-selflink-fragment mw-redirect\" id=\"mwAw\">link text</a>" {
132
2
        // the response is currently different between core/extension API and RESTBase API on WMF projects
133
2
        assert_eq!(link.to_string(), "");
134
3
    }
135
2
    // Mutability
136
3
    link.set_target("MediaWiki");
137
3
    assert_eq!(link.raw_target(), "./MediaWiki".to_string());
138
3
    assert_eq!(link.target(), "MediaWiki".to_string());
139
3
    assert!(code.to_string().contains("href=\"./MediaWiki\""));
140
3
    let wikitext = client.transform_to_wikitext(&code).await?;
141
3
    assert_eq!(wikitext, "[[MediaWiki|link text]]".to_string());
142
2
    // Percent encoding
143
3
    let code = client
144
3
        .transform_to_html(
145
3
            "[[Manual:What is MediaWiki?#some fragment with ö and ?]]",
146
3
        )
147
3
        .await?
148
3
        .into_mutable();
149
3
    let links = code.filter_links();
150
3
    let link = &links[0];
151
3
    assert_eq!(
152
3
        link.raw_target(),
153
3
        "./Manual:What_is_MediaWiki%3F#some_fragment_with_ö_and_?".to_string()
154
3
    );
155
3
    assert_eq!(
156
3
        link.target(),
157
3
        "Manual:What is MediaWiki?#some fragment with ö and ?".to_string()
158
3
    );
159
3
    Ok(())
160
2
}
161

            
162
#[tokio::test]
163
3
async fn test_redlinks() -> Result<()> {
164
3
    let client = test_client::mw_client();
165
3
    let code = client
166
3
        .transform_to_html("[[Page does not exist#Section heading]]")
167
3
        .await?
168
3
        .into_mutable();
169
3
    let links = code.filter_links();
170
3
    let link = &links[0];
171
3
    assert_eq!(
172
3
        link.target(),
173
3
        "Page does not exist#Section heading".to_string()
174
3
    );
175
3
    Ok(())
176
2
}
177

            
178
#[tokio::test]
179
3
async fn test_new_link() -> Result<()> {
180
3
    let client = test_client::mw_client();
181
3
    let link = WikiLink::new("Foo", &Wikicode::new_text("bar"));
182
3
    assert_eq!(
183
3
        &link.to_string(),
184
3
        "<a href=\"./Foo\" rel=\"mw:WikiLink\">bar</a>"
185
3
    );
186
3
    let code = Wikicode::new("");
187
3
    code.append(&link);
188
3
    let new_wikitext = client.transform_to_wikitext(&code).await?;
189
3
    assert_eq!(new_wikitext, "[[Foo|bar]]".to_string());
190
3
    Ok(())
191
2
}
192

            
193
#[tokio::test]
194
3
async fn test_external_links() -> Result<()> {
195
3
    let client = test_client::mw_client();
196
3
    let code = client
197
3
        .transform_to_html("[https://example.com Link content] ")
198
3
        .await?
199
3
        .into_mutable()
200
3
        .without_parsoid_data();
201
3
    println!("{}", code.to_string());
202
3
    let links = code.filter_external_links();
203
3
    let link = &links[0];
204
3
    assert_eq!(link.target(), "https://example.com".to_string());
205
3
    assert_eq!(link.text_contents(), "Link content".to_string());
206
3
    assert_eq!(
207
3
        &link.to_string(),
208
3
        "<a rel=\"mw:ExtLink nofollow\" href=\"https://example.com\" class=\"external text\" id=\"mwAw\">Link content</a>"
209
3
    );
210
2
    // Or via new_from_node
211
45
    let found_links = code.inclusive_descendants().any(|node| {
212
45
        dbg!(&node);
213
45
        matches!(node, Wikinode::ExtLink(_))
214
45
    });
215
3
    assert!(found_links);
216
2
    // Mutability
217
3
    link.set_target("https://wiki.example.org/foo?query=1");
218
3
    assert_eq!(
219
3
        link.target(),
220
3
        "https://wiki.example.org/foo?query=1".to_string()
221
3
    );
222
3
    let wikitext = client.transform_to_wikitext(&code).await?;
223
3
    assert_eq!(
224
3
        wikitext,
225
3
        "[https://wiki.example.org/foo?query=1 Link content] ".to_string()
226
3
    );
227
3
    Ok(())
228
2
}
229

            
230
#[tokio::test]
231
3
async fn test_comments() -> Result<()> {
232
3
    let client = test_client::mw_client();
233
3
    let code = client
234
3
        .transform_to_html("<!--comment-->")
235
3
        .await?
236
3
        .into_mutable();
237
3
    let comments = code.filter_comments();
238
3
    let comment = &comments[0];
239
3
    assert_eq!(comment.text(), "comment".to_string());
240
2
    // Surround with spaces for extra whitespace
241
3
    comment.set_text(" new ");
242
3
    assert_eq!(comment.text(), " new ".to_string());
243
2
    // Change is reflected in Wikicode serialization
244
3
    assert!(code.to_string().contains("<!-- new -->"));
245
3
    Ok(())
246
2
}
247

            
248
#[tokio::test]
249
3
async fn test_properties() -> Result<()> {
250
3
    let client = test_client::mw_client();
251
2
    // FIXME: Use a real stable page
252
3
    let code = client.get("User:Legoktm/archive.txt").await?.into_mutable();
253
3
    assert_eq!(code.revision_id(), Some(2016428));
254
3
    assert_eq!(code.title(), Some("User:Legoktm/archive.txt".to_string()));
255
3
    Ok(())
256
2
}
257

            
258
#[tokio::test]
259
3
async fn test_iterators() -> Result<()> {
260
3
    let client = test_client::mw_client();
261
3
    let code = client
262
3
        .transform_to_html("This is a [[PageThatDoesntExist|sentence]].")
263
3
        .await?
264
3
        .into_mutable();
265
3
    let link = code
266
3
        .descendants()
267
45
        .filter_map(|node| {
268
45
            dbg!(&node);
269
45
            node.as_wikilink()
270
45
        })
271
3
        .next()
272
3
        .unwrap();
273
3
    assert_eq!(
274
3
        link.raw_target(),
275
3
        "./PageThatDoesntExist?action=edit&redlink=1".to_string()
276
3
    );
277
3
    assert_eq!(link.target(), "PageThatDoesntExist".to_string());
278
3
    assert_eq!(link.text_contents(), "sentence".to_string());
279
3
    Ok(())
280
2
}
281

            
282
#[tokio::test]
283
3
async fn test_title() -> Result<()> {
284
3
    let client = test_client::testwp_client();
285
3
    let code = client.get("Mwbot-rs/DISPLAYTITLE").await?.into_mutable();
286
3
    assert_eq!(code.title().unwrap(), "Mwbot-rs/DISPLAYTITLE".to_string());
287
2
    // T326490 regression test
288
3
    let wikitext = client.transform_to_wikitext(&code).await?;
289
3
    assert!(wikitext.starts_with("{{DISPLAYTITLE"));
290
3
    Ok(())
291
2
}
292

            
293
#[tokio::test]
294
3
async fn test_sections() -> Result<()> {
295
3
    let client = test_client::mw_client();
296
3
    let wikitext = r#"
297
3
...lead section contents...
298
3
== foo=bar ==
299
3
...section contents...
300
3
=== nested ===
301
3
...section contents...
302
3
"#;
303
3
    let code = client
304
3
        .transform_to_html(wikitext)
305
3
        .await?
306
3
        .into_mutable()
307
3
        .without_parsoid_data();
308
3
    let sections = code.iter_sections();
309
3
    {
310
3
        let section = &sections[0];
311
3
        assert!(section.is_pseudo_section());
312
3
        assert_eq!(section.section_id(), 0);
313
3
        assert!(section.heading().is_none());
314
3
        assert!(section.is_editable());
315
2
    }
316
2
    {
317
3
        let section = &sections[1];
318
3
        assert!(!section.is_pseudo_section());
319
3
        assert_eq!(section.section_id(), 1);
320
3
        let heading = section.heading().unwrap();
321
3
        assert_eq!(heading.text_contents(), "foo=bar");
322
3
        assert_eq!(heading.level(), 2);
323
3
        assert!(section.is_editable());
324
2
    }
325
2
    {
326
3
        let section = &sections[2];
327
3
        assert!(!section.is_pseudo_section());
328
3
        assert_eq!(section.section_id(), 2);
329
3
        let heading = section.heading().unwrap();
330
3
        assert_eq!(heading.text_contents(), "nested");
331
3
        assert_eq!(heading.level(), 3);
332
3
        assert!(section.is_editable());
333
2
    }
334
3
    let new_heading = Heading::new(3, &Wikicode::new_text("inserted"))?;
335
3
    sections[2].insert_before(&new_heading);
336
3
    assert_eq!(
337
3
        r#"
338
3
...lead section contents...
339
3

            
340
3
== foo=bar ==
341
3
...section contents...
342
3

            
343
3
=== inserted ===
344
3

            
345
3
=== nested ===
346
3
...section contents...
347
3
"#,
348
3
        client.transform_to_wikitext(&code).await?
349
2
    );
350
2

            
351
3
    Ok(())
352
2
}
353

            
354
#[tokio::test]
355
3
async fn test_heading() -> Result<()> {
356
3
    let client = test_client::mw_client();
357
3
    let heading = Heading::new(2, &Wikicode::new_text("Some text"))?;
358
3
    let code = Wikicode::new("");
359
3
    code.append(&heading);
360
3
    let wikitext = client.transform_to_wikitext(&code).await?;
361
3
    assert_eq!(&wikitext, "== Some text ==\n");
362
2

            
363
3
    Ok(())
364
2
}
365

            
366
#[test]
367
2
fn test_invalid_heading() {
368
2
    let err = Heading::new(7, &Wikicode::new_text("foo")).unwrap_err();
369
2
    assert_eq!(
370
2
        err.to_string(),
371
2
        "Heading levels must be between 1 and 6, '7' was provided"
372
2
    );
373
2
}
374

            
375
#[tokio::test]
376
3
async fn test_category() -> Result<()> {
377
3
    let client = test_client::mw_client();
378
3
    let category = Category::new("Category:Foo bar", Some("Bar baz#quux"));
379
3
    let code = Wikicode::new("");
380
3
    code.append(&category);
381
3
    let wikitext = client.transform_to_wikitext(&code).await?;
382
3
    assert_eq!(&wikitext, "[[Category:Foo bar|Bar baz#quux]]");
383
3
    let cats: Vec<_> = code
384
3
        .inclusive_descendants()
385
11
        .filter_map(|node| node.as_category())
386
3
        .collect();
387
3
    let cat = &cats[0];
388
3
    assert_eq!(&cat.category(), "Category:Foo bar");
389
3
    assert_eq!(cat.sort_key(), Some("Bar baz#quux".to_string()));
390
3
    cat.set_category("Category:Bar baz");
391
3
    cat.set_sort_key(None);
392
3
    assert_eq!(cat.sort_key(), None);
393
3
    assert_eq!(
394
3
        &cat.to_string(),
395
3
        "<link href=\"./Category:Bar_baz\" rel=\"mw:PageProp/Category\">"
396
3
    );
397
3
    let new_wikitext = client.transform_to_wikitext(&code).await?;
398
3
    assert_eq!(&new_wikitext, "[[Category:Bar baz]]");
399
2

            
400
3
    Ok(())
401
2
}
402

            
403
#[tokio::test]
404
3
async fn test_language_link() -> Result<()> {
405
3
    let client = test_client::mw_client();
406
3
    let link = LanguageLink::new("https://en.wikipedia.org/wiki/Foo");
407
3
    let code = Wikicode::new("");
408
3
    code.append(&link);
409
3
    let wikitext = client.transform_to_wikitext(&code).await?;
410
3
    assert_eq!(&wikitext, "[[en:Foo]]");
411
3
    link.set_target("https://de.wikipedia.org/wiki/Foo");
412
3
    assert_eq!(&link.target(), "https://de.wikipedia.org/wiki/Foo");
413
3
    let wikitext2 = client.transform_to_wikitext(&code).await?;
414
3
    assert_eq!(&wikitext2, "[[de:Foo]]");
415
3
    Ok(())
416
2
}
417

            
418
#[tokio::test]
419
3
async fn test_behavior_switch() -> Result<()> {
420
3
    let client = test_client::mw_client();
421
3
    let code = Wikicode::new("");
422
3
    code.append(&BehaviorSwitch::new("toc"));
423
3
    let wikitext = client.transform_to_wikitext(&code).await?;
424
3
    assert_eq!(&wikitext, "__TOC__\n");
425
3
    let switches: Vec<_> = code
426
3
        .inclusive_descendants()
427
11
        .filter_map(|node| node.as_behavior_switch())
428
3
        .collect();
429
3
    let switch = &switches[0];
430
3
    assert_eq!(&switch.property(), "toc");
431
3
    Ok(())
432
2
}
433

            
434
#[tokio::test]
435
3
async fn test_redirect() -> Result<()> {
436
3
    let client = test_client::mw_client();
437
3
    let code = Wikicode::new("");
438
3
    code.append(&Redirect::new("Foo"));
439
3
    let redirect = code.redirect().unwrap();
440
3
    assert_eq!(redirect.target(), "Foo".to_string());
441
3
    assert_eq!(redirect.raw_target(), "./Foo".to_string());
442
3
    assert!(!redirect.is_external());
443
3
    let wikitext = client.transform_to_wikitext(&code).await?;
444
3
    assert_eq!(&wikitext, "#REDIRECT [[Foo]]");
445
2

            
446
3
    assert_eq!(
447
3
        test_client::testwp_client()
448
3
            .get("Mwbot-rs/RedirectSym")
449
3
            .await?
450
3
            .into_mutable()
451
3
            .redirect()
452
3
            .unwrap()
453
3
            .target(),
454
2
        "Mwbot-rs/RedirectSym?"
455
2
    );
456
2

            
457
3
    Ok(())
458
2
}
459

            
460
#[tokio::test]
461
3
async fn test_external_redirect() -> Result<()> {
462
3
    let client = test_client::mw_client();
463
3
    let code = client
464
3
        .transform_to_html("#REDIRECT [[w:Main Page]]")
465
3
        .await?
466
3
        .into_mutable();
467
3
    dbg!(&code.to_string());
468
3
    dbg!(&code.redirect());
469
3
    let redirect = code.redirect().expect("not a redirect");
470
3
    assert!(redirect.is_external());
471
3
    assert_eq!(
472
3
        redirect.target(),
473
3
        "https://en.wikipedia.org/wiki/Main%20Page".to_string()
474
3
    );
475
3
    assert_eq!(
476
3
        redirect.raw_target(),
477
3
        "https://en.wikipedia.org/wiki/Main%20Page".to_string()
478
3
    );
479
3
    Ok(())
480
2
}
481

            
482
#[tokio::test]
483
3
async fn test_detach() -> Result<()> {
484
3
    let client = test_client::mw_client();
485
3
    let code = client
486
3
        .transform_to_html("This is a [[link]].")
487
3
        .await?
488
3
        .into_mutable();
489
3
    for link in code.filter_links() {
490
3
        link.detach();
491
3
    }
492
2

            
493
3
    let wikitext = client.transform_to_wikitext(&code).await?;
494
3
    assert_eq!(&wikitext, "This is a .");
495
2

            
496
3
    Ok(())
497
2
}
498

            
499
#[tokio::test]
500
3
async fn test_section_prepend() -> Result<()> {
501
3
    let client = test_client::mw_client();
502
3
    let code = client
503
3
        .transform_to_html("== Section ==\n[[foo]]")
504
3
        .await?
505
3
        .into_mutable()
506
3
        .without_parsoid_data();
507
3
    let section = code.iter_sections()[1].clone();
508
3
    // Need to insert_after the heading otherwise parsoid will move the content before
509
3
    // the <section> tag
510
3
    section
511
3
        .heading()
512
3
        .unwrap()
513
3
        .insert_after(&WikiLink::new("./Bar", &Wikicode::new_text("Bar")));
514
3
    let wikitext = client.transform_to_wikitext(&code).await?;
515
3
    assert_eq!(&wikitext, "== Section ==\n[[Bar]]\n[[foo]]");
516
3
    Ok(())
517
2
}
518

            
519
#[tokio::test]
520
3
async fn test_parser_functions() -> Result<()> {
521
3
    let client = test_client::mw_client();
522
3
    let code = client
523
3
        .get("User:Legoktm/parsoid-rs/parser function")
524
3
        .await?
525
3
        .into_mutable();
526
3
    let templates = code.filter_parser_functions()?;
527
3
    let temp = &templates[0];
528
3
    assert!(temp.is_parser_function());
529
2
    // name is literally the entire function
530
3
    assert_eq!(
531
3
        temp.name_in_wikitext(),
532
3
        "#expr:{{formatnum:{{NUMBEROFUSERS}}|R}}+100"
533
3
    );
534
3
    assert_eq!(temp.raw_name(), "expr");
535
2
    // identical to normalized_name
536
3
    assert_eq!(temp.name(), "expr");
537
3
    Ok(())
538
2
}
539

            
540
#[tokio::test]
541
3
async fn test_question() -> Result<()> {
542
3
    let client = test_client::mw_client();
543
3
    let code = client
544
3
        .get("User:Legoktm/parsoid-rs/question")
545
3
        .await?
546
3
        .into_mutable();
547
3
    let templates = code.filter_templates()?;
548
3
    let temp = &templates[0];
549
3
    assert_eq!(&temp.name(), "Template:But why?");
550
3
    Ok(())
551
2
}
552

            
553
#[tokio::test]
554
3
async fn test_includeonly() -> Result<()> {
555
3
    let client = test_client::mw_client();
556
3
    let wikitext = "foo<includeonly>bar</includeonly>baz";
557
3
    let code = client.transform_to_html(wikitext).await?.into_mutable();
558
3
    let includeonlys: Vec<_> = code
559
3
        .descendants()
560
49
        .filter_map(|node| node.as_includeonly())
561
3
        .collect();
562
3
    assert_eq!(includeonlys.len(), 1);
563
3
    let includeonly = includeonlys[0].clone();
564
3
    assert_eq!(includeonly.wikitext()?, "bar");
565
3
    includeonly.set_wikitext("yay!")?;
566
3
    let new = client.transform_to_wikitext(&code).await?;
567
3
    assert_eq!(&new, "foo<includeonly>yay!</includeonly>baz");
568
3
    includeonly.detach();
569
3
    let detached = client.transform_to_wikitext(&code).await?;
570
3
    assert_eq!(&detached, "foobaz");
571
3
    let node = IncludeOnly::new("included")?;
572
3
    code.append(&node);
573
3
    let appended = client.transform_to_wikitext(&code).await?;
574
3
    assert_eq!(&appended, "foobaz<includeonly>included</includeonly>");
575
3
    Ok(())
576
2
}
577

            
578
#[tokio::test]
579
3
async fn test_noinclude() -> Result<()> {
580
3
    let client = test_client::mw_client();
581
3
    let code = client
582
3
        .transform_to_html("foo<noinclude>bar</noinclude>baz")
583
3
        .await?
584
3
        .into_mutable();
585
3
    let noincludes = code.filter_noinclude();
586
3
    assert_eq!(noincludes.len(), 1);
587
3
    let noinclude = noincludes[0].clone();
588
3
    let text_contents = {
589
3
        let text: Vec<_> = noinclude
590
3
            .inclusive_descendants()
591
3
            .iter()
592
3
            .map(|node| node.text_contents())
593
3
            .collect();
594
3
        text.join("")
595
3
    };
596
3
    assert_eq!(&text_contents, "bar");
597
3
    noinclude.detach();
598
3
    assert_eq!(&client.transform_to_wikitext(&code).await?, "foobaz");
599
3
    let new = NoInclude::new(&WikiLink::new(
600
3
        "Main Page",
601
3
        &Wikicode::new_text("text"),
602
3
    ));
603
3
    code.append(&new);
604
3
    assert_eq!(
605
3
        &client.transform_to_wikitext(&code).await?,
606
2
        "foobaz<noinclude>[[Main Page|text]]</noinclude>"
607
2
    );
608
3
    Ok(())
609
2
}
610

            
611
#[tokio::test]
612
3
async fn test_noinclude_children() -> Result<()> {
613
3
    let client = test_client::mw_client();
614
3
    let code = client
615
3
        .transform_to_html("foo<noinclude>[[bar]] baz</noinclude>!")
616
3
        .await?
617
3
        .into_mutable();
618
3
    let noinclude = code.filter_noinclude()[0].clone();
619
3
    let links: Vec<WikiLink> = noinclude
620
3
        .inclusive_descendants()
621
3
        .iter()
622
7
        .filter_map(|node| node.as_wikilink())
623
3
        .collect();
624
3
    assert_eq!(&links[0].target(), "Bar");
625
3
    Ok(())
626
2
}
627

            
628
#[tokio::test]
629
3
async fn test_onlyinclude() -> Result<()> {
630
3
    let client = test_client::mw_client();
631
3
    let code = client
632
3
        .transform_to_html("foo<onlyinclude>bar</onlyinclude>baz")
633
3
        .await?
634
3
        .into_mutable();
635
3
    let onlyincludes = code.filter_onlyinclude();
636
3
    assert_eq!(onlyincludes.len(), 1);
637
3
    let onlyinclude = onlyincludes[0].clone();
638
3
    let text_contents = {
639
3
        let text: Vec<_> = onlyinclude
640
3
            .inclusive_descendants()
641
3
            .iter()
642
3
            .map(|node| node.text_contents())
643
3
            .collect();
644
3
        text.join("")
645
3
    };
646
3
    assert_eq!(&text_contents, "bar");
647
3
    onlyinclude.detach();
648
3
    assert_eq!(&client.transform_to_wikitext(&code).await?, "foobaz");
649
3
    let new = OnlyInclude::new(&WikiLink::new(
650
3
        "Main Page",
651
3
        &Wikicode::new_text("text"),
652
3
    ));
653
3
    code.append(&new);
654
3
    assert_eq!(
655
3
        &client.transform_to_wikitext(&code).await?,
656
2
        "foobaz<onlyinclude>[[Main Page|text]]</onlyinclude>"
657
2
    );
658
3
    Ok(())
659
2
}
660

            
661
#[tokio::test]
662
3
async fn test_placeholder() -> Result<()> {
663
3
    let client = test_client::mw_client();
664
2
    // This was copied out of Parsoid's parser tests
665
3
    let code = client
666
3
        .transform_to_html(";one</b>two :bad tag fun")
667
3
        .await?
668
3
        .into_mutable();
669
3
    let placeholders: Vec<_> = code
670
3
        .descendants()
671
53
        .filter_map(|node| node.as_placeholder())
672
3
        .collect();
673
3
    assert_eq!(placeholders.len(), 1);
674
3
    Ok(())
675
2
}
676

            
677
#[tokio::test]
678
3
async fn test_displayspace() -> Result<()> {
679
3
    let client = test_client::mw_client();
680
2
    // This was copied out of Parsoid's parser tests
681
3
    let code = client
682
3
        .transform_to_html("<nowiki>test : 123</nowiki>")
683
3
        .await?
684
3
        .into_mutable();
685
3
    let displayspaces: Vec<_> = code
686
3
        .inclusive_descendants()
687
53
        .filter_map(|node| node.as_displayspace())
688
3
        .collect();
689
3
    assert_eq!(displayspaces.len(), 1);
690
3
    Ok(())
691
2
}
692

            
693
#[tokio::test]
694
3
async fn test_spec_version() -> Result<()> {
695
3
    let client = test_client::mw_client();
696
3
    let code = client.get("Main Page").await?.into_mutable();
697
3
    let version = code.spec_version().unwrap();
698
3
    // The version is 2.x
699
3
    assert!(version.starts_with("2."));
700
3
    let sp: Vec<_> = version.split('.').collect();
701
3
    // and has three parts.
702
3
    assert_eq!(sp.len(), 3);
703
3
    Ok(())
704
2
}
705

            
706
/// Regression test for extension-generated <section> tags
707
/// https://gitlab.com/mwbot-rs/parsoid/-/issues/14
708
#[tokio::test]
709
3
async fn test_extension_sections() -> Result<()> {
710
3
    let client = test_client::mw_client();
711
3
    let code = client.get("Help:TemplateData").await?.into_mutable();
712
55
    for section in code.iter_sections() {
713
55
        // Verify none of these panic
714
55
        section.section_id();
715
55
        section.is_pseudo_section();
716
55
        section.heading();
717
55
    }
718
55
    for section in code
719
3
        .inclusive_descendants()
720
16409
        .filter_map(|node| node.as_section())
721
55
    {
722
55
        // Verify none of these panic
723
55
        section.section_id();
724
55
        section.is_pseudo_section();
725
55
        section.heading();
726
55
    }
727
3
    Ok(())
728
2
}
729

            
730
#[tokio::test]
731
3
async fn test_magic_links() -> Result<()> {
732
3
    let client = test_client::testwp_client();
733
2
    // Assert magic links are correctly detected
734
3
    let code = client
735
3
        .transform_to_html(
736
3
            r#"
737
3
PMID 1234
738
3
ISBN 978-0-307-26395-7
739
3
RFC 4321
740
3
"#,
741
3
        )
742
3
        .await?
743
3
        .into_mutable();
744
3
    let links = code.filter_links();
745
3
    assert!(links[0].is_isbn_magic_link());
746
3
    let extlinks = code.filter_external_links();
747
3
    assert!(extlinks[0].is_magic_link());
748
3
    assert!(extlinks[1].is_magic_link());
749
2
    // Assert wikitext equivalents aren't
750
3
    let code = client
751
3
        .transform_to_html(
752
3
            r#"
753
3
[[pmid:1234|PMID 1234]]
754
3
[[Special:BookSources/9780307263957|ISBN 978-0-307-26395-7]]
755
3
[[rfc:4321|RFC 4321]]
756
3
"#,
757
3
        )
758
3
        .await?
759
3
        .into_mutable();
760
3
    let links = code.filter_links();
761
3
    assert!(!links[0].is_isbn_magic_link());
762
3
    assert_eq!(code.filter_external_links().len(), 0);
763
3
    assert_eq!(
764
3
        code.descendants()
765
61
            .filter_map(|node| node.as_interwiki_link())
766
3
            .count(),
767
3
        2
768
3
    );
769
2

            
770
3
    Ok(())
771
2
}
772

            
773
#[tokio::test]
774
3
async fn test_templatearg() -> Result<()> {
775
3
    let client = test_client::enwp_client();
776
3
    // Reduced test case of a multi-part template with a "templatearg"
777
3
    let wikitext = "{{discussion-top}}{{{1|}}}{{discussion-bottom}}";
778
3
    let code = client.transform_to_html(wikitext).await?.into_mutable();
779
3
    // No error
780
3
    code.filter_templates()?;
781
3
    let converted = client.transform_to_wikitext(&code).await?;
782
3
    assert_eq!(converted, wikitext);
783
2
    // T330371 regression test
784
3
    let code = client
785
3
        .get_revision("Talk:Signaling of the New York City Subway", 1095492368)
786
3
        .await?
787
3
        .into_mutable();
788
3
    // No error
789
3
    code.filter_templates()?;
790
2

            
791
3
    Ok(())
792
2
}