PluginProbe
WProofreader spell & grammar check plugin for WordPress / trunk
WProofreader spell & grammar check plugin for WordPress vtrunk
3.2.1 3.2.2 3.2.0 trunk 1.0 1.1 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.6.1 2.6.10 2.6.2 2.6.3 2.6.4 2.6.5 2.6.6 2.6.7 2.6.8 2.6.9 2.7.0 2.7.1 All 28 releases
webspellchecker / tests / test-content-cleaner.php

test-content-cleaner.php in WProofreader spell & grammar check plugin for WordPress trunk, at tests/test-content-cleaner.php

145 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Standalone test runner for WProofreader_Content_Cleaner.
4 *
5 * Usage (inside a WP container): php tests/test-content-cleaner.php /path/to/wp-load.php
6 * Exits non-zero on failure.
7 */
8
9 $wp_load = $argv[1] ?? '/var/www/web/wp/wp-load.php';
10 require $wp_load;
11
12 if ( ! class_exists( 'WProofreader_Content_Cleaner' ) ) {
13 require dirname( __DIR__ ) . '/includes/class-wproofreader-content-cleaner.php';
14 }
15
16 $failures = 0;
17
18 function check( string $name, bool $condition, string $detail = '' ) {
19 global $failures;
20 if ( $condition ) {
21 echo "PASS {$name}\n";
22 } else {
23 $failures++;
24 echo "FAIL {$name}" . ( $detail ? "{$detail}" : '' ) . "\n";
25 }
26 }
27
28 function clean( string $html ): string {
29 return WProofreader_Content_Cleaner::clean_content( $html );
30 }
31
32 function hook_path( string $html ): string {
33 $data = WProofreader_Content_Cleaner::clean_post_data(
34 array( 'post_content' => wp_slash( $html ), 'post_status' => 'private' ),
35 array()
36 );
37 return wp_unslash( $data['post_content'] );
38 }
39
40 // 1. Clean content without artifact markers is untouched (wsc- URLs included).
41 $plain = '<p>Hello <a href="https://webspellchecker.com/wsc-proofreader/">link</a></p>';
42 check( 'clean content untouched', clean( $plain ) === $plain );
43
44 // 2. Simple artifact span is unwrapped.
45 check(
46 'simple span unwrapped',
47 clean( '<p><span class="wsc-spelling-problem">word</span> rest</p>' ) === '<p>word rest</p>'
48 );
49
50 // 3. Multi-class artifact span with extra attributes.
51 check(
52 'multi-class span with attributes',
53 clean( '<p><span data-wsc-id="9" class="wsc-spelling-problem wsc-problem-42" style="background:red">word</span></p>' ) === '<p>word</p>'
54 );
55
56 // 4. Nested artifact spans.
57 check(
58 'nested artifact spans',
59 clean( '<p><span class="wsc-grammar-problem">a <span class="wsc-spelling-problem">b</span> c</span></p>' ) === '<p>a b c</p>'
60 );
61
62 // 5. Artifact span nested inside a regular span: regular span preserved byte-identical.
63 check(
64 'regular span preserved',
65 clean( "<p><span class='keep' data-x='1'>a <span class=\"wsc-spelling-problem\">b</span></span></p>" ) === "<p><span class='keep' data-x='1'>a b</span></p>"
66 );
67
68 // 6. rangySelectionBoundary removal.
69 check(
70 'rangy boundary removed',
71 clean( "<p>a<span class=\"rangySelectionBoundary\">\xE2\x80\x8B</span>b</p>" ) === "<p>a\xE2\x80\x8Bb</p>"
72 );
73
74 // 7. Unclosed artifact opener: opener dropped, rest intact.
75 check(
76 'unclosed artifact opener',
77 clean( '<p><span class="wsc-spelling-problem">word</p>' ) === '<p>word</p>'
78 );
79
80 // 8. Quoting / entities / boolean attributes outside artifacts stay byte-identical.
81 $tricky = '<figure class=\'"wp-block-table\' custom><a href=\'"https://x"\' rel=\'"nofollow"\'>x</a>'
82 . '<span class="wsc-spelling-problem">w</span>&nbsp;&amp; <br><svg viewBox="0 0 1 1"><rect/></svg></figure>';
83 $expected = '<figure class=\'"wp-block-table\' custom><a href=\'"https://x"\' rel=\'"nofollow"\'>x</a>'
84 . 'w&nbsp;&amp; <br><svg viewBox="0 0 1 1"><rect/></svg></figure>';
85 check( 'byte-identical outside splices', clean( $tricky ) === $expected );
86
87 // 9. Gutenberg block comments survive.
88 $blocks = "<!-- wp:paragraph -->\n<p><span class=\"wsc-grammar-problem\">Текст українською</span> 🚀</p>\n<!-- /wp:paragraph -->";
89 check(
90 'block comments and unicode survive',
91 clean( $blocks ) === "<!-- wp:paragraph -->\n<p>Текст українською 🚀</p>\n<!-- /wp:paragraph -->"
92 );
93
94 // 10. Slashed hook path: regression for the 3.1.1 corruption bug.
95 // Realistic Gutenberg fixture: quoted block attributes, existing &quot; entities,
96 // and a wsc- URL that used to false-positive the old substring guard.
97 $baseline = '<!-- wp:heading {"level":2,"anchor":"h-custom"} -->' . "\n"
98 . '<h2 class="wp-block-heading" id="h-custom">Say &quot;hi&quot;</h2>' . "\n"
99 . '<!-- /wp:heading -->' . "\n"
100 . '<!-- wp:image {"id":42,"sizeSlug":"large","linkDestination":"custom"} -->' . "\n"
101 . '<figure class="wp-block-image size-large"><a href="https://webspellchecker.com/wsc-proofreader/">'
102 . '<img src="https://x.test/a.png" alt="alt with &quot;quotes&quot;"/></a></figure>' . "\n"
103 . '<!-- /wp:image -->' . "\n"
104 . '<!-- wp:paragraph -->' . "\n"
105 . '<p>Plain <a href="https://webspellchecker.com/wsc-proofreader/" rel="noopener">link</a> text.</p>' . "\n"
106 . '<!-- /wp:paragraph -->';
107 if ( file_exists( '/tmp/before_editor_save.html' ) ) {
108 $real = file_get_contents( '/tmp/before_editor_save.html' );
109 check( 'slashed hook path lossless on real post baseline', hook_path( $real ) === $real );
110 }
111 check( 'slashed hook path lossless on clean content', hook_path( $baseline ) === $baseline );
112
113 // 11. Slashed hook path with artifacts: cleaned, no &quot; inflation.
114 $dirty = str_replace( '<p>', '<p><span class="wsc-spelling-problem">x</span> ', $baseline );
115 $cleaned = hook_path( $dirty );
116 check( 'slashed hook path removes artifacts', strpos( $cleaned, 'wsc-spelling-problem' ) === false );
117 check(
118 'slashed hook path adds no quot entities',
119 substr_count( $cleaned, '&quot;' ) === substr_count( $baseline, '&quot;' )
120 );
121
122 // 11b. Double invocation (Gutenberg REST save + meta-box-loader re-save) is idempotent.
123 $pass1 = hook_path( $dirty );
124 $pass2 = hook_path( $pass1 );
125 check( 'double hook run idempotent', $pass2 === $pass1 );
126 check( 'double hook run adds no quot entities', substr_count( $pass2, '&quot;' ) === substr_count( $baseline, '&quot;' ) );
127
128 // 12. Autosave/revision statuses are no longer skipped.
129 $autosave = WProofreader_Content_Cleaner::clean_post_data(
130 array( 'post_content' => wp_slash( '<p><span class="wsc-spelling-problem">w</span></p>' ), 'post_status' => 'inherit' ),
131 array()
132 );
133 check( 'inherit status cleaned', wp_unslash( $autosave['post_content'] ) === '<p>w</p>' );
134
135 // 13. Empty content untouched.
136 $empty = WProofreader_Content_Cleaner::clean_post_data( array( 'post_content' => '' ), array() );
137 check( 'empty content untouched', '' === $empty['post_content'] );
138
139 // 14. Span with artifact class substring in another class name must NOT match.
140 $lookalike = '<p><span class="wsc-spelling-problem-note">keep wrapper</span></p>';
141 check( 'class lookalike preserved', clean( $lookalike ) === $lookalike );
142
143 echo $failures ? "\n{$failures} FAILURE(S)\n" : "\nALL PASS\n";
144 exit( $failures ? 1 : 0 );
145