$options['prefix'] ?? 'enwiki', 'pageName' => $options['pageName'] ?? 'main', 'wrapSections' => false ]; $siteOptions = array_merge( [ 'linting' => true ], $options ); $siteConfig = new MockSiteConfig( $siteOptions ); $dataAccess = new MockDataAccess( [] ); $parsoid = new Parsoid( $siteConfig, $dataAccess ); $content = new MockPageContent( [ $opts['pageName'] => $wt ] ); $pageConfig = new MockPageConfig( [], $content ); return $parsoid->wikitext2lint( $pageConfig, [] ); } /** * @param $description * @param $wt * @param array $opts */ private function expectEmptyResults( $description, $wt, $opts = [] ): void { $result = $this->parseWT( $wt, $opts ); $this->assertTrue( empty( $result ), $description ); } /** * @param $description * @param $wt * @param $cat * @param array $opts */ private function expectLinterCategoryToBeAbsent( $description, $wt, $cat, $opts = [] ): void { $result = $this->parseWT( $wt, $opts ); foreach ( $result as $r ) { if ( isset( $r['type'] ) ) { $this->assertNotEquals( $cat, $r['type'], $description ); } } } /** * @param $description * @param $wt * @param $type */ private function noLintsOfThisType( $description, $wt, $type ): void { $this->expectLinterCategoryToBeAbsent( $description, $wt, $type ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testNoIssues(): void { $desc = 'should not have lint any issues'; $this->expectEmptyResults( $desc, 'foo' ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testMissingEndTags(): void { $desc = 'should lint missing end tags correctly'; $result = $this->parseWT( '
foo' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 8, 5, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'div', $result[0]['params']['name'], $desc ); $desc = 'should lint missing end tags for quotes correctly'; $result = $this->parseWT( "'''foo" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 6, 3, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'b', $result[0]['params']['name'], $desc ); $desc = 'should lint missing end tags found in transclusions correctly'; $result = $this->parseWT( '{{1x|
foo

bar

}}' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 27, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'p', $result[0]['params']['name'], $desc ); $desc = 'should not flag tags where end tags are optional in the spec'; $wt = '
heading 1
col 1col 2
'; $this->expectEmptyResults( $desc, $wt ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testStrippedTags(): void { $desc = 'should lint stripped tags correctly'; $result = $this->parseWT( 'foo
' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'stripped-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'div', $result[0]['params']['name'], $desc ); $this->assertEquals( [ 3, 9, null, null ], $result[0]['dsr'], $desc ); $desc = 'should lint stripped tags found in transclusions correctly'; $result = $this->parseWT( '{{1x|
foo
}}' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'stripped-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'div', $result[0]['params']['name'], $desc ); $this->assertEquals( [ 0, 27, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); $desc = 'should lint stripped tags correctly in misnested tag situations ( is stripped)'; $result = $this->parseWT( 'X' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misnested-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 3, 7, 3, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'i', $result[0]['params']['name'], $desc ); $desc = 'should lint stripped tags correctly in misnested tag situations ' . 'from template ( is stripped)'; $result = $this->parseWT( '{{1x|X}}' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misnested-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 22, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'i', $result[0]['params']['name'], $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); $desc = 'should lint stripped tags correctly in misnested tag situations ( is auto-inserted)'; $result = $this->parseWT( 'XY' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misnested-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 3, 7, 3, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'i', $result[0]['params']['name'], $desc ); $desc = 'should lint stripped tags correctly in misnested tag situations ' . '(skip over empty autoinserted )'; $result = $this->parseWT( "*ab\n*cd" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misnested-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 2, 10, 7, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'small', $result[0]['params']['name'], $desc ); $desc = 'should lint stripped tags correctly in misnested tag situations ' . '(formatting tags around lists, but ok for div)'; $result = $this->parseWT( "a\n*b\n*c\nd\n
a\n*b\n*c\nd
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misnested-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 8, 7, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'small', $result[0]['params']['name'], $desc ); $desc = 'should lint stripped tags correctly in misnested tag situations ' . '(T221989 regression test case)'; $result = $this->parseWT( "
\n* foo\n\n
\ny" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misnested-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 8, 17, 6, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'span', $result[0]['params']['name'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testObsoleteTags(): void { $desc = 'should lint obsolete tags correctly'; $result = $this->parseWT( 'foobar' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'obsolete-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 12, 4, 5 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'tt', $result[0]['params']['name'], $desc ); $desc = 'should not lint big as an obsolete tag'; $this->expectEmptyResults( $desc, 'foobar' ); $desc = 'should lint obsolete tags found in transclusions correctly'; $result = $this->parseWT( '{{1x|
foo
}}foo' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'obsolete-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 30, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'tt', $result[0]['params']['name'], $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); $desc = 'should not lint auto-inserted obsolete tags'; $result = $this->parseWT( "foo\n\n\nbar" ); // obsolete-tag and missing-end-tag $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( 'obsolete-tag', $result[1]['type'], $desc ); $this->assertEquals( [ 0, 7, 4, 0 ], $result[1]['dsr'], $desc ); $this->assertTrue( isset( $result[1]['params'] ), $desc ); $this->assertEquals( 'tt', $result[1]['params']['name'], $desc ); $desc = 'should not have template info for extension tags'; $result = $this->parseWT( "\nFile:Test.jpg|foo\n" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'obsolete-tag', $result[0]['type'], $desc ); $this->assertFalse( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( [ 24, 36, 4, 5 ], $result[0]['dsr'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testFosteredContent(): void { $desc = 'should lint fostered content correctly'; $result = $this->parseWT( "{|\nfoo\n|-\n| bar\n|}" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'fostered', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 18, 2, 2 ], $result[0]['dsr'], $desc ); $desc = 'should not lint fostered categories'; $this->expectEmptyResults( $desc, "{|\n[[Category:Fostered]]\n|-\n| bar\n|}" ); $desc = 'should not lint fostered behavior switches'; $this->expectEmptyResults( $desc, "{|\n__NOTOC__\n|-\n| bar\n|}" ); $desc = 'should not lint fostered include directives without fostered content'; $this->expectEmptyResults( $desc, "{|\nboo\n|-\n| bar\n|}" ); $desc = 'should lint fostered include directives that has fostered content'; $result = $this->parseWT( "{|\nboo\n|-\n| bar\n|}" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'fostered', $result[0]['type'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testBogusImageOptions(): void { $desc = 'should lint Bogus image options correctly'; $result = $this->parseWT( '[[file:a.jpg|foo|bar]]' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'bogus-image-options', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 22, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertTrue( isset( $result[0]['params']['items'] ), $desc ); $this->assertEquals( 'foo', $result[0]['params']['items'][0], $desc ); $desc = 'should lint Bogus image options found in transclusions correctly'; $result = $this->parseWT( '{{1x|[[file:a.jpg|foo|bar]]}}' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'bogus-image-options', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 29, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'foo', $result[0]['params']['items'][0], $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); $desc = 'should batch lint Bogus image options correctly'; $result = $this->parseWT( '[[file:a.jpg|foo|bar|baz]]' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'bogus-image-options', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 26, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'foo', $result[0]['params']['items'][0], $desc ); $this->assertEquals( 'bar', $result[0]['params']['items'][1], $desc ); $desc = 'should not send any Bogus image options if there are none'; $this->expectEmptyResults( $desc, '[[file:a.jpg|foo]]' ); $desc = 'should flag noplayer, noicon, and disablecontrols as bogus options'; $result = $this->parseWT( '[[File:Video.ogv|noplayer|noicon|disablecontrols=ok|These are bogus.]]' ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'bogus-image-options', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 70, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'noplayer', $result[0]['params']['items'][0], $desc ); $this->assertEquals( 'noicon', $result[0]['params']['items'][1], $desc ); $this->assertEquals( 'disablecontrols=ok', $result[0]['params']['items'][2], $desc ); $desc = 'should not crash on gallery images'; $this->expectEmptyResults( $desc, "\nfile:a.jpg\n" ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testSelfClosingTags(): void { $desc = 'should lint self-closing tags corrrectly'; $result = $this->parseWT( "foobarbaz
boo
" ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'self-closed-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 3, 8, 5, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'b', $result[0]['params']['name'], $desc ); $this->assertEquals( 'self-closed-tag', $result[1]['type'], $desc ); $this->assertEquals( [ 11, 19, 8, 0 ], $result[1]['dsr'], $desc ); $this->assertTrue( isset( $result[1]['params'] ), $desc ); $this->assertEquals( 'span', $result[1]['params']['name'], $desc ); $desc = 'should lint self-closing tags in a template correctly'; $result = $this->parseWT( "{{1x| }}" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'self-closed-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 31, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'b', $result[0]['params']['name'][0], $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testDeletableTableTag(): void { $desc = 'should identify deletable table tag for T161341 (1)'; $wt = implode( "\n", [ "{| style='border:1px solid red;'", "|a", "|-", "{| style='border:1px solid blue;'", "|b", "|c", "|}", "|}" ] ); $result = $this->parseWT( $wt ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'deletable-table-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'table', $result[0]['params']['name'], $desc ); $this->assertEquals( [ 39, 72, 0, 0 ], $result[0]['dsr'], $desc ); $desc = 'should identify deletable table tag for T161341 (2)'; $wt = implode( "\n", [ "{| style='border:1px solid red;'", "|a", "|- ", " ", "{| style='border:1px solid blue;'", "|b", "|c", "|}" ] ); $result = $this->parseWT( $wt ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'deletable-table-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'table', $result[0]['params']['name'], $desc ); $this->assertEquals( [ 58, 91, 0, 0 ], $result[0]['dsr'], $desc ); $desc = 'should identify deletable table tag for T161341 (3)'; $wt = implode( "\n", [ "{{1x|{{{!}}", "{{!}}a", "{{!}}-", "{{{!}}", "{{!}}b", "{{!}}c", "{{!}}}", "}}" ] ); $result = $this->parseWT( $wt ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'deletable-table-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); $this->assertEquals( [ 0, 56, null, null ], $result[0]['dsr'], $desc ); $desc = 'should identify deletable table tag for T161341 (4)'; $wt = implode( "\n", [ "{{1x|{{{!}}", "{{!}}a", "{{!}}-", "}}", "{|", "|b", "|c", "|}" ] ); $result = $this->parseWT( $wt ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'deletable-table-tag', $result[0]['type'], $desc ); $this->assertFalse( isset( $result[0]['templateInfo'] ), $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'table', $result[0]['params']['name'], $desc ); $this->assertEquals( [ 29, 31, 0, 0 ], $result[0]['dsr'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testPwrapBugWorkaround(): void { $desc = 'should identify rendering workarounds needed for doBlockLevels bug'; $wt = implode( "\n", [ "
", "a", "", "
" ] ); $result = $this->parseWT( $wt ); $this->assertEquals( 3, count( $result ), $desc ); $this->assertEquals( 'pwrap-bug-workaround', $result[1]['type'], $desc ); $this->assertFalse( isset( $result[1]['templateInfo'] ), $desc ); $this->assertTrue( isset( $result[1]['params'] ), $desc ); $this->assertEquals( 'div', $result[1]['params']['root'], $desc ); $this->assertEquals( 'span', $result[1]['params']['child'], $desc ); $this->assertEquals( [ 5, 48, 33, 0 ], $result[1]['dsr'], $desc ); $desc = 'should not lint doBlockLevels bug rendering workarounds if newline break is present'; $wt = implode( "\n", [ "
", "", "a", "", "
" ] ); $this->expectLinterCategoryToBeAbsent( $desc, $wt, 'pwrap-bug-workaround' ); $desc = 'should not lint doBlockLevels bug rendering workarounds if nowrap CSS is not present'; $wt = implode( "\n", [ "
", "a", "", "
" ] ); $this->expectLinterCategoryToBeAbsent( $desc, $wt, 'pwrap-bug-workaround' ); $desc = 'should not lint doBlockLevels bug rendering workarounds where not required'; $wt = implode( "\n", [ "
", "a", "", "
" ] ); $this->expectLinterCategoryToBeAbsent( $desc, $wt, 'pwrap-bug-workaround' ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testTidyWhitespaceBug(): void { $wt1 = implode( "", [ // Basic with inline CSS + text sibling "a ", "x", // Basic with inline CSS + span sibling "a ", "x", // Basic with class CSS + text sibling "a ", "x", // Basic with class CSS + span sibling "a ", "x", // Comments shouldn't trip it up "a ", "", "x" ] ); $desc = 'should detect problematic whitespace hoisting'; $result = $this->parseWT( $wt1, [ 'tidyWhitespaceBugMaxLength' => 0 ] ); $this->assertEquals( 5, count( $result ), $desc ); foreach ( $result as $r ) { $this->assertEquals( 'tidy-whitespace-bug', $r['type'], $desc ); $this->assertEquals( 'span', $r['params']['node'], $desc ); } $this->assertEquals( '#text', $result[0]['params']['sibling'], $desc ); $this->assertEquals( 'span', $result[1]['params']['sibling'], $desc ); $this->assertEquals( '#text', $result[2]['params']['sibling'], $desc ); $this->assertEquals( 'span', $result[3]['params']['sibling'], $desc ); $this->assertEquals( '#comment', $result[4]['params']['sibling'], $desc ); // skipping dsr tests $desc = 'should not detect problematic whitespace hoisting for short text runs'; // Nothing to trigger here $this->expectEmptyResults( $desc, $wt1, [ 'tidyWhitespaceBugMaxLength' => 100 ] ); $wt2 = implode( "", [ "some unaffected text here ", "a ", "bb", "cc", "d ", "e ", "x" ] ); $desc = 'should flag tidy whitespace bug on a run of affected content'; // The run length is 11 chars in the example above $result = $this->parseWT( $wt2, [ 'tidyWhitespaceBugMaxLength' => 5 ] ); $this->assertEquals( 3, count( $result ), $desc ); foreach ( $result as $r ) { $this->assertEquals( 'tidy-whitespace-bug', $r['type'], $desc ); $this->assertEquals( 'span', $r['params']['node'], $desc ); } $this->assertEquals( 'span', $result[0]['params']['sibling'], $desc ); $this->assertEquals( [ 26, 68, 33, 7 ], $result[0]['dsr'], $desc ); $this->assertEquals( 'span', $result[1]['params']['sibling'], $desc ); $this->assertEquals( [ 140, 170, 21, 7 ], $result[1]['dsr'], $desc ); $this->assertEquals( 'span', $result[2]['params']['sibling'], $desc ); $this->assertEquals( [ 170, 212, 33, 7 ], $result[2]['dsr'], $desc ); $desc = 'should not flag tidy whitespace bug on a run of short affected content'; $this->expectEmptyResults( $desc, $wt2, [ 'tidyWhitespaceBugMaxLength' => 12 ] ); $desc = 'should account for preceding text content'; // Run length changes to 16 chars because of preceding text $wt2 = str_replace( 'some unaffected text here ', 'some unaffected text HERE-', $wt2 ); $result = $this->parseWT( $wt2, [ 'tidyWhitespaceBugMaxLength' => 12 ] ); $this->assertEquals( 3, count( $result ), $desc ); foreach ( $result as $r ) { $this->assertEquals( 'tidy-whitespace-bug', $r['type'], $desc ); $this->assertEquals( 'span', $r['params']['node'], $desc ); } $this->assertEquals( 'span', $result[0]['params']['sibling'], $desc ); $this->assertEquals( [ 26, 68, 33, 7 ], $result[0]['dsr'], $desc ); $this->assertEquals( 'span', $result[1]['params']['sibling'], $desc ); $this->assertEquals( [ 140, 170, 21, 7 ], $result[1]['dsr'], $desc ); $this->assertEquals( 'span', $result[2]['params']['sibling'], $desc ); $this->assertEquals( [ 170, 212, 33, 7 ], $result[2]['dsr'], $desc ); $desc = 'should not flag tidy whitespace bug where it does not matter'; $wt = implode( "", [ // No CSS "a ", "x", // No trailing white-space "a", "x", // White-space follows "a ", " ", "x", // White-space follows "a ", " boo", "x", // Block tag "
a
", "x", // Block tag sibling "a ", "
x
", // br sibling "a ", "
", // No next sibling "a " ] ); $this->expectEmptyResults( $desc, $wt, [ 'tidyWhitespaceBugMaxLength' => 0 ] ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testMultipleColonEscape(): void { $desc = 'should lint links prefixed with multiple colons'; $result = $this->parseWT( "[[None]]\n[[:One]]\n[[::Two]]\n[[:::Three]]" ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( [ 18, 27, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( '::Two', $result[0]['params']['href'], $desc ); $this->assertEquals( [ 28, 40, null, null ], $result[1]['dsr'], $desc ); $this->assertTrue( isset( $result[1]['params'] ), $desc ); $this->assertEquals( ':::Three', $result[1]['params']['href'], $desc ); $desc = 'should lint links prefixed with multiple colons from templates'; $result = $this->parseWT( "{{1x|[[:One]]}}\n{{1x|[[::Two]]}}" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); // TODO(arlolra): Frame doesn't have tsr info yet $this->assertEquals( [ 0, 0, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( '::Two', $result[0]['params']['href'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testHtml5MisnestedTags(): void { $desc = "should not trigger html5 misnesting if there is no following content"; $result = $this->parseWT( "foo\nbar" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'del', $result[0]['params']['name'], $desc ); $desc = "should trigger html5 misnesting correctly"; $result = $this->parseWT( "foo\n\nbar" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'html5-misnesting', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 8, 5, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'del', $result[0]['params']['name'], $desc ); $desc = "should trigger html5 misnesting for span (1)"; $result = $this->parseWT( "foo\n\nbar" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'html5-misnesting', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 9, 6, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'span', $result[0]['params']['name'], $desc ); $desc = "should trigger html5 misnesting for span (2)"; $result = $this->parseWT( "foo\n\n
bar
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'html5-misnesting', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 9, 6, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'span', $result[0]['params']['name'], $desc ); $desc = "should trigger html5 misnesting for span (3)"; $result = $this->parseWT( "foo\n\n{|\n|x\n|}\nboo" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'html5-misnesting', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 9, 6, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'span', $result[0]['params']['name'], $desc ); $desc = "should not trigger html5 misnesting when there is no misnested content"; $result = $this->parseWT( "foo\n\ny" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misnested-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'span', $result[0]['params']['name'], $desc ); $desc = "should not trigger html5 misnesting when unclosed tag is inside a td/th/heading tags"; $result = $this->parseWT( "=x=\n{|\n!z\n|-\n|id=\"3\"\n|}" ); $this->assertEquals( 3, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( 'missing-end-tag', $result[1]['type'], $desc ); $this->assertEquals( 'missing-end-tag', $result[2]['type'], $desc ); $desc = "should not trigger html5 misnesting when misnested content is " . "outside an a-tag (without link-trails)"; $result = $this->parseWT( "[[Foo|foo]]Bar" ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'span', $result[0]['params']['name'], $desc ); $this->assertEquals( 'stripped-tag', $result[1]['type'], $desc ); // Note that this is a false positive because of T177086 and fixing that will fix this. // We expect this to be an edge case. $desc = "should trigger html5 misnesting when linktrails brings content inside an a-tag"; $result = $this->parseWT( "[[Foo|foo]]bar" ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'html5-misnesting', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'span', $result[0]['params']['name'], $desc ); $this->assertEquals( 'stripped-tag', $result[1]['type'], $desc ); $desc = "should not trigger html5 misnesting for formatting tags"; $result = $this->parseWT( "foo\n\nbar" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'small', $result[0]['params']['name'], $desc ); $desc = "should not trigger html5 misnesting for span if there is a nested span tag"; $result = $this->parseWT( "fooboo\n\nbar" ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( 'stripped-tag', $result[1]['type'], $desc ); $desc = "should trigger html5 misnesting for span if there is a nested non-span tag"; $result = $this->parseWT( "fooboo\n\nbar" ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'html5-misnesting', $result[0]['type'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'span', $result[0]['params']['name'], $desc ); $this->assertEquals( 'stripped-tag', $result[1]['type'], $desc ); $desc = "should trigger html5 misnesting for span if there is a nested unclosed span tag"; $result = $this->parseWT( "fooboo\n\nbar" ); $this->assertEquals( 3, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( 'html5-misnesting', $result[1]['type'], $desc ); $this->assertEquals( 'stripped-tag', $result[2]['type'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testTidyFontBug(): void { $desc = "should flag Tidy font fixups accurately when color attribute is present"; $wtLines = [ "[[Foo]]", "[[Category:Boo]][[Foo]]", "__NOTOC__[[Foo]]", "[[Foo]]", "[[Foo|bar]]", "[[Foo|''bar'']]", "[[Foo|''bar'' and boo]]", "[[Foo]]l", "{{1x|[[Foo]]}}" ]; $n = count( $wtLines ); $result = $this->parseWT( implode( "\n", $wtLines ) ); $this->assertEquals( 2 * $n, count( $result ), $desc ); for ( $i = 0; $i < 2 * $n; $i += 2 ) { $this->assertEquals( 'obsolete-tag', $result[$i]['type'], $desc ); $this->assertEquals( 'tidy-font-bug', $result[$i + 1]['type'], $desc ); } $desc = "should not flag Tidy font fixups when color attribute is absent"; $wtLinesReplaced = str_replace( " color='green'", '', $wtLines ); $n = count( $wtLinesReplaced ); $result = $this->parseWT( implode( "\n", $wtLinesReplaced ) ); $this->assertEquals( $n, count( $wtLinesReplaced ), $desc ); foreach ( $result as $r ) { $this->assertEquals( 'obsolete-tag', $r['type'], $desc ); } $desc = "should not flag Tidy font fixups when Tidy does not do the fixups"; $wtLines2 = [ "", // Regression test for T179757 "[[Foo]][[Bar]]", " [[Foo]]", "[[Foo]] ", "[[Foo]]D", "''[[Foo|bar]]''", "[[Foo|bar]]", "
[[Foo|bar]]
" ]; $n = count( $wtLines2 ); $result = $this->parseWT( implode( "\n", $wtLines2 ) ); $this->assertEquals( $n, count( $result ), $desc ); foreach ( $result as $r ) { $this->assertEquals( 'obsolete-tag', $r['type'], $desc ); } } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testMultipleUnclosedFormatTags(): void { $desc = 'should detect multiple unclosed small tags'; $result = $this->parseWT( '
x
y
' ); $this->assertEquals( 3, count( $result ), $desc ); $this->assertEquals( 'multiple-unclosed-formatting-tags', $result[2]['type'], $desc ); $this->assertEquals( 'small', $result[2]['params']['name'], $desc ); $desc = 'should detect multiple unclosed big tags'; $result = $this->parseWT( '
x
y
' ); $this->assertEquals( 3, count( $result ), $desc ); $this->assertEquals( 'multiple-unclosed-formatting-tags', $result[2]['type'], $desc ); $this->assertEquals( 'big', $result[2]['params']['name'], $desc ); $desc = 'should detect multiple unclosed big tags'; $result = $this->parseWT( '
y
' ); $this->assertEquals( 5, count( $result ), $desc ); $this->assertEquals( 'multiple-unclosed-formatting-tags', $result[4]['type'], $desc ); $this->assertEquals( 'small', $result[4]['params']['name'], $desc ); $desc = 'should ignore unclosed small tags in tables'; $this->noLintsOfThisType( $desc, "{|\n|a\n|b\n|}", 'multiple-unclosed-formatting-tags' ); $desc = 'should ignore unclosed small tags in tables but detect those outside it'; $result = $this->parseWT( "x\n{|\n|a\n|b\n|}\ny" ); $this->assertEquals( 5, count( $result ), $desc ); $this->assertEquals( 'multiple-unclosed-formatting-tags', $result[4]['type'], $desc ); $this->assertEquals( 'small', $result[4]['params']['name'], $desc ); $desc = 'should not flag undetected misnesting of formatting tags as " . "multiple unclosed formatting tags'; $this->noLintsOfThisType( $desc, "
{{1x|
\n*item 1\n
}}
", 'multiple-unclosed-formatting-tags' ); $desc = "should detect Tidy's smart auto-fixup of paired unclosed formatting tags"; $result = $this->parseWT( 'foo\nfoo x bar' ); $this->assertEquals( 6, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( 'multiple-unclosed-formatting-tags', $result[1]['type'], $desc ); $this->assertEquals( 'b', $result[1]['params']['name'], $desc ); $this->assertEquals( 'missing-end-tag', $result[3]['type'], $desc ); $this->assertEquals( 'multiple-unclosed-formatting-tags', $result[4]['type'], $desc ); $this->assertEquals( 'code', $result[4]['params']['name'], $desc ); $desc = "should not flag Tidy's smart auto-fixup of paired unclosed " . "formatting tags where Tidy won't do it"; $this->noLintsOfThisType( $desc, "foo \nfoo x ", 'multiple-unclosed-formatting-tags' ); $desc = "should not flag Tidy's smart auto-fixup of paired unclosed tags for non-formatting tags"; $this->noLintsOfThisType( $desc, "foo\n
foo x bar
", 'multiple-unclosed-formatting-tags' ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testUnclosedIBTagsInHeadings(): void { $desc = "should detect unclosed wikitext i tags in headings"; $result = $this->parseWT( "==foo''a==\nx" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'unclosed-quotes-in-heading', $result[0]['type'], $desc ); $this->assertEquals( 'i', $result[0]['params']['name'], $desc ); $this->assertEquals( 'h2', $result[0]['params']['ancestorName'], $desc ); $desc = "should detect unclosed wikitext b tags in headings"; $result = $this->parseWT( "==foo'''a==\nx" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'unclosed-quotes-in-heading', $result[0]['type'], $desc ); $this->assertEquals( 'b', $result[0]['params']['name'], $desc ); $this->assertEquals( 'h2', $result[0]['params']['ancestorName'], $desc ); $desc = "should not detect unclosed HTML i/b tags in headings"; $result = $this->parseWT( "==fooa==\nx\n==fooa==\ny" ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( 'missing-end-tag', $result[1]['type'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testMultilineHtmlTablesInLists(): void { $desc = "should detect multiline HTML tables in lists (li)"; $result = $this->parseWT( "* \n
x
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'multiline-html-table-in-list', $result[0]['type'], $desc ); $this->assertEquals( 'table', $result[0]['params']['name'], $desc ); $this->assertEquals( 'li', $result[0]['params']['ancestorName'], $desc ); $desc = "should detect multiline HTML tables in lists (table in div)"; $result = $this->parseWT( "*
\n
x
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'multiline-html-table-in-list', $result[0]['type'], $desc ); $this->assertEquals( 'table', $result[0]['params']['name'], $desc ); $this->assertEquals( 'li', $result[0]['params']['ancestorName'], $desc ); $desc = "should detect multiline HTML tables in lists (dt)"; $result = $this->parseWT( "; \n
x
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'multiline-html-table-in-list', $result[0]['type'], $desc ); $this->assertEquals( 'table', $result[0]['params']['name'], $desc ); $this->assertEquals( 'dt', $result[0]['params']['ancestorName'], $desc ); $desc = "should detect multiline HTML tables in lists (dd)"; $result = $this->parseWT( ": \n
x
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'multiline-html-table-in-list', $result[0]['type'], $desc ); $this->assertEquals( 'table', $result[0]['params']['name'], $desc ); $this->assertEquals( 'dd', $result[0]['params']['ancestorName'], $desc ); $desc = "should not detect multiline HTML tables in HTML lists"; $this->expectEmptyResults( $desc, "
  • \n\n
    x
    \n
" ); $desc = "should not detect single-line HTML tables in lists"; $this->expectEmptyResults( $desc, "*
x
" ); $desc = "should not detect multiline HTML tables in ref tags"; $this->expectEmptyResults( $desc, "a \n\n
b
" ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testLintIssueInRefTags(): void { $desc = "should attribute linter issues to the ref tag"; $result = $this->parseWT( "a x " ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 7, 11, 3, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'b', $result[0]['params']['name'], $desc ); $desc = "should attribute linter issues to the ref tag even if references is templated"; $result = $this->parseWT( "a x {{1x|}}" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 7, 11, 3, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'b', $result[0]['params']['name'], $desc ); $desc = "should attribute linter issues to the ref tag even when " . "ref and references are both templated"; $wt = "a x b {{1x|x}} " . "{{1x|c y}} {{1x|}}"; $result = $this->parseWT( $wt ); $this->assertEquals( 3, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 7, 11, 3, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 'b', $result[0]['params']['name'], $desc ); $this->assertEquals( 'missing-end-tag', $result[1]['type'], $desc ); $this->assertEquals( [ 25, 36, null, null ], $result[1]['dsr'], $desc ); $this->assertTrue( isset( $result[1]['params'] ), $desc ); $this->assertEquals( 'b', $result[1]['params']['name'], $desc ); $this->assertTrue( isset( $result[1]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[1]['templateInfo']['name'], $desc ); $this->assertEquals( 'missing-end-tag', $result[2]['type'], $desc ); $this->assertEquals( [ 43, 67, null, null ], $result[2]['dsr'], $desc ); $this->assertTrue( isset( $result[2]['params'] ), $desc ); $this->assertEquals( 'b', $result[2]['params']['name'], $desc ); $this->assertTrue( isset( $result[2]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[2]['templateInfo']['name'], $desc ); $desc = "should attribute linter issues properly when ref " . "tags are in non-templated references tag"; $wt = "a x b " . "{{1x|boo}} "; $result = $this->parseWT( $wt ); $this->assertEquals( 2, count( $result ), $desc ); $this->assertEquals( 'missing-end-tag', $result[0]['type'], $desc ); $this->assertEquals( [ 7, 11, 3, 0 ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['params'] ), $desc ); $this->assertEquals( 's', $result[0]['params']['name'], $desc ); $this->assertEquals( 'missing-end-tag', $result[1]['type'], $desc ); $this->assertEquals( [ 64, 77, null, null ], $result[1]['dsr'], $desc ); $this->assertTrue( isset( $result[1]['params'] ), $desc ); $this->assertEquals( 'b', $result[1]['params']['name'], $desc ); $this->assertTrue( isset( $result[1]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[1]['templateInfo']['name'], $desc ); // PORT-FIXME this code is not yet supported // $desc = "should not get into a cycle trying to lint ref in ref"; // return parseWT( // "{{#tag:ref||name='x'}}{{#tag:ref|" . // "|name='y'}}" ); // .then(function() { // return parseWT("{{#tag:ref||name=x}}"); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testDivSpanFlipTidyBug(): void { $desc = "should not trigger this lint when there are no style or class attributes"; $this->expectEmptyResults( $desc, "
x
" ); $desc = "should trigger this lint when there is a style or class attribute (1)"; $result = $this->parseWT( "
x
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misc-tidy-replacement-issues', $result[0]['type'], $desc ); $this->assertEquals( 'div-span-flip', $result[0]['params']['subtype'], $desc ); $desc = "should trigger this lint when there is a style or class attribute (2)"; $result = $this->parseWT( "
x
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misc-tidy-replacement-issues', $result[0]['type'], $desc ); $this->assertEquals( 'div-span-flip', $result[0]['params']['subtype'], $desc ); $desc = "should trigger this lint when there is a style or class attribute (3)"; $result = $this->parseWT( "
x
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misc-tidy-replacement-issues', $result[0]['type'], $desc ); $this->assertEquals( 'div-span-flip', $result[0]['params']['subtype'], $desc ); $desc = "should trigger this lint when there is a style or class attribute (4)"; $result = $this->parseWT( "
x
" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'misc-tidy-replacement-issues', $result[0]['type'], $desc ); $this->assertEquals( 'div-span-flip', $result[0]['params']['subtype'], $desc ); } /** * @covers \Wikimedia\Parsoid\Wt2Html\ParserPipeline */ public function testWikilinkInExternalLink(): void { $desc = "should lint wikilink in external link correctly"; $result = $this->parseWT( "[http://google.com This is [[Google]]'s search page]" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'wikilink-in-extlink', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 52, 19, 1 ], $result[0]['dsr'], $desc ); $desc = "should lint wikilink in external link correctly"; $result = $this->parseWT( "[http://stackexchange.com is the official website for [[Stack Exchange]]]" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'wikilink-in-extlink', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 73, 26, 1 ], $result[0]['dsr'], $desc ); $desc = "should lint wikilink in external link correctly"; $result = $this->parseWT( "{{1x|foo
and [http://google.com [[Google]] bar] baz
}}" ); $this->assertSame( 1, count( $result ), $desc ); $this->assertEquals( 'wikilink-in-extlink', $result[0]['type'], $desc ); $this->assertEquals( [ 0, 66, null, null ], $result[0]['dsr'], $desc ); $this->assertTrue( isset( $result[0]['templateInfo'] ), $desc ); $this->assertEquals( '1x', $result[0]['templateInfo']['name'], $desc ); } }