1
|
|
|
<?php |
2
|
|
|
namespace Thunder\Shortcode\Tests; |
3
|
|
|
|
4
|
|
|
use Thunder\Shortcode\Handler\ContentHandler; |
5
|
|
|
use Thunder\Shortcode\Handler\DeclareHandler; |
6
|
|
|
use Thunder\Shortcode\Handler\EmailHandler; |
7
|
|
|
use Thunder\Shortcode\Handler\NameHandler; |
8
|
|
|
use Thunder\Shortcode\Handler\NullHandler; |
9
|
|
|
use Thunder\Shortcode\Handler\PlaceholderHandler; |
10
|
|
|
use Thunder\Shortcode\Handler\RawHandler; |
11
|
|
|
use Thunder\Shortcode\Handler\SerializerHandler; |
12
|
|
|
use Thunder\Shortcode\Handler\UrlHandler; |
13
|
|
|
use Thunder\Shortcode\Handler\WrapHandler; |
14
|
|
|
use Thunder\Shortcode\HandlerContainer\HandlerContainer; |
15
|
|
|
use Thunder\Shortcode\Parser\RegexParser; |
16
|
|
|
use Thunder\Shortcode\Parser\RegularParser; |
17
|
|
|
use Thunder\Shortcode\Processor\Processor; |
18
|
|
|
use Thunder\Shortcode\Shortcode\ParsedShortcodeInterface; |
19
|
|
|
use Thunder\Shortcode\Serializer\JsonSerializer; |
20
|
|
|
use Thunder\Shortcode\Serializer\TextSerializer; |
21
|
|
|
use Thunder\Shortcode\Shortcode\ProcessedShortcode; |
22
|
|
|
use Thunder\Shortcode\Shortcode\ShortcodeInterface; |
23
|
|
|
use Thunder\Shortcode\Syntax\CommonSyntax; |
24
|
|
|
use Thunder\Shortcode\Tests\Fake\ReverseShortcode; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @author Tomasz Kowalczyk <[email protected]> |
28
|
|
|
*/ |
29
|
|
|
final class ProcessorTest extends \PHPUnit_Framework_TestCase |
30
|
|
|
{ |
31
|
|
|
private function getHandlers() |
32
|
|
|
{ |
33
|
|
|
$handlers = new HandlerContainer(); |
34
|
|
|
$handlers |
35
|
|
|
->add('name', function (ShortcodeInterface $s) { return $s->getName(); }) |
36
|
|
|
->add('content', function (ShortcodeInterface $s) { return $s->getContent(); }) |
37
|
|
|
->add('reverse', new ReverseShortcode()) |
38
|
|
|
->add('url', function(ShortcodeInterface $s) { |
39
|
|
|
$url = $s->getParameter('url', $s->getBbCode()); |
40
|
|
|
|
41
|
|
|
return '<a href="'.$url.'">'.$url.'</a>'; |
42
|
|
|
}) |
43
|
|
|
->addAlias('c', 'content') |
44
|
|
|
->addAlias('n', 'name'); |
45
|
|
|
|
46
|
|
|
return $handlers; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
View Code Duplication |
public function testReplaceWithoutContentOffset() |
|
|
|
|
50
|
|
|
{ |
51
|
|
|
$text = ' [x value=" [name]yyy[/name] "] [name]yyy[/name] [/x] '; |
52
|
|
|
$result = ' [x value=" [name]yyy[/name] "] name [/x] '; |
53
|
|
|
|
54
|
|
|
$processor = new Processor(new RegexParser(), $this->getHandlers()); |
55
|
|
|
|
56
|
|
|
$this->assertSame($result, $processor->process($text)); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @param string $text |
61
|
|
|
* @param string $result |
62
|
|
|
* |
63
|
|
|
* @dataProvider provideTexts |
64
|
|
|
*/ |
65
|
|
|
public function testProcessorProcess($text, $result) |
66
|
|
|
{ |
67
|
|
|
$processor = new Processor(new RegexParser(), $this->getHandlers()); |
68
|
|
|
|
69
|
|
|
$this->assertSame($result, $processor->process($text)); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
public function provideTexts() |
73
|
|
|
{ |
74
|
|
|
return array( |
75
|
|
|
array('[name]', 'name'), |
76
|
|
|
array('[content]random[/content]', 'random'), |
77
|
|
|
array('[content]象形字[/content]', '象形字'), |
78
|
|
|
array('xxx [content]象形字[/content] yyy', 'xxx 象形字 yyy'), |
79
|
|
|
array('xxx [content]ąćęłńóśżź ąćęłńóśżź[/content] yyy', 'xxx ąćęłńóśżź ąćęłńóśżź yyy'), |
80
|
|
|
array('[name]random[/other]', 'namerandom[/other]'), |
81
|
|
|
array('[name][other]random[/other]', 'name[other]random[/other]'), |
82
|
|
|
array('[content]random-[name]-random[/content]', 'random-name-random'), |
83
|
|
|
array('random [content]other[/content] various', 'random other various'), |
84
|
|
|
array('x [content]a-[name]-b[/content] y', 'x a-name-b y'), |
85
|
|
|
array('x [c]a-[n][/n]-b[/c] y', 'x a-n-b y'), |
86
|
|
|
array('x [content]a-[c]v[/c]-b[/content] y', 'x a-v-b y'), |
87
|
|
|
array('x [html]bold[/html] z', 'x [html]bold[/html] z'), |
88
|
|
|
array('x [reverse]abc xyz[/reverse] z', 'x zyx cba z'), |
89
|
|
|
array('x [i /][i]i[/i][i /][i]i[/i][i /] z', 'x [i /][i]i[/i][i /][i]i[/i][i /] z'), |
90
|
|
|
array('x [url url="http://giggle.com/search" /] z', 'x <a href="http://giggle.com/search">http://giggle.com/search</a> z'), |
91
|
|
|
array('x [url="http://giggle.com/search"] z', 'x <a href="http://giggle.com/search">http://giggle.com/search</a> z'), |
92
|
|
|
); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
public function testProcessorParentContext() |
96
|
|
|
{ |
97
|
|
|
$handlers = new HandlerContainer(); |
98
|
|
|
$handlers->add('outer', function (ProcessedShortcode $s) { |
99
|
|
|
$name = $s->getParent() ? $s->getParent()->getName() : 'root'; |
100
|
|
|
|
101
|
|
|
return $name.'['.$s->getContent().']'; |
102
|
|
|
}); |
103
|
|
|
$handlers->addAlias('inner', 'outer'); |
104
|
|
|
$handlers->addAlias('level', 'outer'); |
105
|
|
|
|
106
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
107
|
|
|
|
108
|
|
|
$text = 'x [outer]a [inner]c [level]x[/level] d[/inner] b[/outer] y'; |
109
|
|
|
$result = 'x root[a outer[c inner[x] d] b] y'; |
110
|
|
|
$this->assertSame($result, $processor->process($text)); |
111
|
|
|
$this->assertSame($result.$result, $processor->process($text.$text)); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
View Code Duplication |
public function testReplacesLongerThanInputText() |
|
|
|
|
115
|
|
|
{ |
116
|
|
|
$handlers = new HandlerContainer(); |
117
|
|
|
$handlers->add('x', function() { return '<length>'; }); |
118
|
|
|
$processor = new Processor(new RegularParser(), $handlers); |
119
|
|
|
|
120
|
|
|
$this->assertSame('<length><length><length>', $processor->process('[x][x][x]')); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
View Code Duplication |
public function testProcessorWithoutRecursion() |
|
|
|
|
124
|
|
|
{ |
125
|
|
|
$processor = new Processor(new RegexParser(), $this->getHandlers()); |
126
|
|
|
$text = 'x [content]a-[name][/name]-b[/content] y'; |
127
|
|
|
|
128
|
|
|
$this->assertSame('x a-[name][/name]-b y', $processor->withRecursionDepth(0)->process($text)); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
public function testProcessContentIfHasChildHandlerButNotParent() |
132
|
|
|
{ |
133
|
|
|
$handlers = new HandlerContainer(); |
134
|
|
|
$handlers->add('valid', function (ShortcodeInterface $s) { return $s->getName(); }); |
135
|
|
|
|
136
|
|
|
$text = 'x [invalid ] [valid /] [/invalid] y'; |
137
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
138
|
|
|
|
139
|
|
|
$this->assertSame('x [invalid ] valid [/invalid] y', $processor->withAutoProcessContent(true)->process($text)); |
140
|
|
|
$this->assertSame('x [invalid ] [valid /] [/invalid] y', $processor->withAutoProcessContent(false)->process($text)); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
View Code Duplication |
public function testProcessorWithoutContentAutoProcessing() |
|
|
|
|
144
|
|
|
{ |
145
|
|
|
$processor = new Processor(new RegexParser(), $this->getHandlers()); |
146
|
|
|
$text = 'x [content]a-[name][/name]-b[/content] y'; |
147
|
|
|
|
148
|
|
|
$this->assertSame('x a-[name][/name]-b y', $processor->withAutoProcessContent(false)->process($text)); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
View Code Duplication |
public function testProcessorShortcodePositions() |
|
|
|
|
152
|
|
|
{ |
153
|
|
|
$handlers = new HandlerContainer(); |
154
|
|
|
$handlers->add('p', function (ProcessedShortcode $s) { return $s->getPosition(); }); |
155
|
|
|
$handlers->add('n', function (ProcessedShortcode $s) { return $s->getNamePosition(); }); |
156
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
157
|
|
|
|
158
|
|
|
$this->assertSame('123', $processor->process('[n][n][n]'), '3n'); |
159
|
|
|
$this->assertSame('123', $processor->process('[p][p][p]'), '3p'); |
160
|
|
|
$this->assertSame('113253', $processor->process('[p][n][p][n][p][n]'), 'pnpnpn'); |
161
|
|
|
$this->assertSame('1231567', $processor->process('[p][p][p][n][p][p][p]'), 'pppnppp'); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* @dataProvider provideBuiltInTests |
166
|
|
|
*/ |
167
|
|
|
public function testBuiltInHandlers($text, $result) |
168
|
|
|
{ |
169
|
|
|
$handlers = new HandlerContainer(); |
170
|
|
|
$handlers |
171
|
|
|
->add('content', new ContentHandler()) |
172
|
|
|
->add('name', new NameHandler()) |
173
|
|
|
->add('null', new NullHandler()) |
174
|
|
|
->add('json', new SerializerHandler(new JsonSerializer())) |
175
|
|
|
->add('text', new SerializerHandler(new TextSerializer())) |
176
|
|
|
->add('placeholder', new PlaceholderHandler()) |
177
|
|
|
->add('b', new WrapHandler('<b>', '</b>')) |
178
|
|
|
->add('bb', WrapHandler::createBold()) |
179
|
|
|
->add('declare', new DeclareHandler($handlers)) |
180
|
|
|
->add('url', new UrlHandler()) |
181
|
|
|
->add('email', new EmailHandler()) |
182
|
|
|
->add('raw', new RawHandler()); |
183
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
184
|
|
|
|
185
|
|
|
$this->assertSame($result, $processor->process($text)); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
public function provideBuiltInTests() |
189
|
|
|
{ |
190
|
|
|
return array( |
191
|
|
|
array('[declare date]%year%.%month%.%day%[/declare][date year=2015 month=08 day=26]', '2015.08.26'), |
192
|
|
|
array('[declare sample]%param%[/declare][invalid param=value]', '[invalid param=value]'), |
193
|
|
|
array('[declare]%param%[/declare][invalid param=value]', '[invalid param=value]'), |
194
|
|
|
array('[url]http://kowalczyk.cc[/url]', '<a href="http://kowalczyk.cc">http://kowalczyk.cc</a>'), |
195
|
|
|
array('[url="http://kowalczyk.cc"]Visit![/url]', '<a href="http://kowalczyk.cc">Visit!</a>'), |
196
|
|
|
array('[email][email protected][/email]', '<a href="mailto:[email protected]">[email protected]</a>'), |
197
|
|
|
array('[email="[email protected]"]Send![/email]', '<a href="mailto:[email protected]">Send!</a>'), |
198
|
|
|
array('[email="[email protected]" /]', '<a href="mailto:[email protected]">[email protected]</a>'), |
199
|
|
|
array('[b]text[/b]', '<b>text</b>'), |
200
|
|
|
array('[bb]text[/bb]', '<b>text</b>'), |
201
|
|
|
array('[json arg=val]value[/json]', '{"name":"json","parameters":{"arg":"val"},"content":"value","bbCode":null}'), |
202
|
|
|
array('[text arg=val]value[/text]', '[text arg=val]value[/text]'), |
203
|
|
|
array('[null arg=val]value[/null]', ''), |
204
|
|
|
array('[name /]', 'name'), |
205
|
|
|
array('[content]cnt[/content]', 'cnt'), |
206
|
|
|
array('[placeholder param=val]%param%[/placeholder]', 'val'), |
207
|
|
|
array('[placeholder param=val]%param%[/placeholder]', 'val'), |
208
|
|
|
array('[raw][null][content]cnt[/content][name /][/raw]', '[null][content]cnt[/content][name /]'), |
209
|
|
|
array('[listing-link id="12345"]Holá[/listing-link]', '[listing-link id="12345"]Holá[/listing-link]'), |
210
|
|
|
); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
public function testProcessorDeclare() |
214
|
|
|
{ |
215
|
|
|
$handlers = new HandlerContainer(); |
216
|
|
|
$handlers->add('declare', function (ProcessedShortcode $s) use ($handlers) { |
217
|
|
|
$handlers->add($s->getParameterAt(0), function (ShortcodeInterface $x) use ($s) { |
218
|
|
|
$keys = array_map(function ($item) { |
219
|
|
|
return '%'.$item.'%'; |
220
|
|
|
}, array_keys($x->getParameters())); |
221
|
|
|
$values = array_values($x->getParameters()); |
222
|
|
|
|
223
|
|
|
return str_replace($keys, $values, $s->getContent()); |
224
|
|
|
}); |
225
|
|
|
}); |
226
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
227
|
|
|
|
228
|
|
|
$this->assertSame('You are 18 years old.', trim($processor->process(' |
229
|
|
|
[declare age]You are %age% years old.[/declare] |
230
|
|
|
[age age=18] |
231
|
|
|
'))); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
public function testBaseOffset() |
235
|
|
|
{ |
236
|
|
|
$handlers = new HandlerContainer(); |
237
|
|
|
$handlers->setDefault(function(ProcessedShortcode $s) { |
238
|
|
|
return '['.$s->getBaseOffset().']'.$s->getContent(); |
239
|
|
|
}); |
240
|
|
|
$processor = new Processor(new RegularParser(new CommonSyntax()), $handlers); |
241
|
|
|
|
242
|
|
|
$this->assertSame('[0][3] ’[8][11]’ [20]', $processor->process('[a][b] ’[c][d]’ [/b][e]')); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
public function testProcessorIterative() |
246
|
|
|
{ |
247
|
|
|
$handlers = new HandlerContainer(); |
248
|
|
|
$handlers |
249
|
|
|
->add('name', function (ShortcodeInterface $s) { return $s->getName(); }) |
250
|
|
|
->add('content', function (ShortcodeInterface $s) { return $s->getContent(); }) |
251
|
|
|
->addAlias('c', 'content') |
252
|
|
|
->addAlias('n', 'name') |
253
|
|
|
->addAlias('d', 'c') |
254
|
|
|
->addAlias('e', 'c'); |
255
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
256
|
|
|
|
257
|
|
|
/** @var $processor Processor */ |
258
|
|
|
$processor = $processor->withRecursionDepth(0)->withMaxIterations(2); |
259
|
|
|
$this->assertSame('x a y', $processor->process('x [c]a[/c] y')); |
260
|
|
|
$this->assertSame('x abc y', $processor->process('x [c]a[d]b[/d]c[/c] y')); |
261
|
|
|
$this->assertSame('x ab[e]c[/e]de y', $processor->process('x [c]a[d]b[e]c[/e]d[/d]e[/c] y')); |
262
|
|
|
|
263
|
|
|
$processor = $processor->withMaxIterations(null); |
264
|
|
|
$this->assertSame('x abcde y', $processor->process('x [c]a[d]b[e]c[/e]d[/d]e[/c] y')); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
public function testExceptionOnInvalidRecursionDepth() |
268
|
|
|
{ |
269
|
|
|
$processor = new Processor(new RegularParser(), new HandlerContainer()); |
270
|
|
|
$this->setExpectedException('InvalidArgumentException'); |
|
|
|
|
271
|
|
|
$processor->withRecursionDepth(new \stdClass()); |
|
|
|
|
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
public function testExceptionOnInvalidMaxIterations() |
275
|
|
|
{ |
276
|
|
|
$processor = new Processor(new RegularParser(), new HandlerContainer()); |
277
|
|
|
$this->setExpectedException('InvalidArgumentException'); |
|
|
|
|
278
|
|
|
$processor->withMaxIterations(new \stdClass()); |
|
|
|
|
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
public function testExceptionOnInvalidAutoProcessFlag() |
282
|
|
|
{ |
283
|
|
|
$processor = new Processor(new RegularParser(), new HandlerContainer()); |
284
|
|
|
$this->setExpectedException('InvalidArgumentException'); |
|
|
|
|
285
|
|
|
$processor->withAutoProcessContent(new \stdClass()); |
|
|
|
|
286
|
|
|
} |
287
|
|
|
|
288
|
|
View Code Duplication |
public function testDefaultHandler() |
|
|
|
|
289
|
|
|
{ |
290
|
|
|
$handlers = new HandlerContainer(); |
291
|
|
|
$handlers->setDefault(function (ShortcodeInterface $s) { return $s->getName(); }); |
292
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
293
|
|
|
|
294
|
|
|
$this->assertSame('namerandom', $processor->process('[name][other][/name][random]')); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
public function testStripOuter() |
298
|
|
|
{ |
299
|
|
|
$handlers = new HandlerContainer(); |
300
|
|
|
$handlers->add('q', function(ShortcodeInterface $s) { |
301
|
|
|
return $s->getContent(); |
302
|
|
|
}); |
303
|
|
|
$handlers->add('p', function(ProcessedShortcode $s) use(&$handlers) { |
304
|
|
|
$parser = new RegexParser(); |
305
|
|
|
$processor = new Processor($parser, $handlers); |
306
|
|
|
$shortcodes = $parser->parse($s->getTextContent()); |
307
|
|
|
|
308
|
|
|
return array_reduce($shortcodes, function($result, ParsedShortcodeInterface $s) use($processor) { |
309
|
|
|
return $result.$processor->process($s->getText()); |
310
|
|
|
}, ''); |
311
|
|
|
}); |
312
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
313
|
|
|
|
314
|
|
|
$this->assertSame('x ab y', $processor->process('x [p] [q]a[/q] [q]b[/q] [/p] y')); |
315
|
|
|
$this->assertSame('x ab c y', $processor->process('x [p] [q]a[/q] [q]b [q]c[/q][/q] [/p] y')); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
public function testOriginalContent() |
319
|
|
|
{ |
320
|
|
|
$handlers = new HandlerContainer(); |
321
|
|
|
$handlers->add('p', function(ProcessedShortcode $s) { return $s->getTextContent(); }); |
322
|
|
|
$handlers->addAlias('q', 'p'); |
323
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
324
|
|
|
|
325
|
|
|
$this->assertSame('x [q]a[/q] [q]b[/q] y', $processor->process('x [p] [q]a[/q] [q]b[/q] [/p] y')); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
public function testMultipleParent() |
329
|
|
|
{ |
330
|
|
|
$parents = 0; |
331
|
|
|
$handlers = new HandlerContainer(); |
332
|
|
|
$handlers->add('p', function(ProcessedShortcode $s) use(&$parents) { $parents += $s->getParent() ? 1 : 0; }); |
333
|
|
|
$handlers->addAlias('q', 'p'); |
334
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
335
|
|
|
$processor->process('x [p] [q]a[/q] [q]b[/q] [q]c[/q] [/p] y'); |
336
|
|
|
|
337
|
|
|
$this->assertSame(3, $parents); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
public function testPreventInfiniteLoop() |
341
|
|
|
{ |
342
|
|
|
$handlers = new HandlerContainer(); |
343
|
|
|
$handlers |
344
|
|
|
->add('self', function () { return '[self]'; }) |
345
|
|
|
->add('other', function () { return '[self]'; }) |
346
|
|
|
->add('random', function () { return '[other]'; }); |
347
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
348
|
|
|
$processor->withMaxIterations(null); |
349
|
|
|
|
350
|
|
|
$processor->process('[self]'); |
351
|
|
|
$processor->process('[other]'); |
352
|
|
|
$processor->process('[random]'); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
View Code Duplication |
public function testValidProcessAfterHandlerRemoval() |
|
|
|
|
356
|
|
|
{ |
357
|
|
|
$handlers = new HandlerContainer(); |
358
|
|
|
$handlers->add('name', function(ShortcodeInterface $s) { return $s->getName(); }); |
359
|
|
|
$handlers->addAlias('n', 'name'); |
360
|
|
|
$processor = new Processor(new RegexParser(), $handlers); |
361
|
|
|
$this->assertSame('n', $processor->process('[n]')); |
362
|
|
|
$this->assertSame('name', $processor->process('[name]')); |
363
|
|
|
$handlers->remove('name'); |
364
|
|
|
$this->assertSame('n', $processor->process('[n]')); |
365
|
|
|
$this->assertSame('[name]', $processor->process('[name]')); |
366
|
|
|
} |
367
|
|
|
} |
368
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.