Completed
Push — ignore/lazy-images-linting-pac... ( 3c044f...b5c515 )
by Jeremy
367:06 queued 352:42
created

generateLineBreakBoundary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Tweetstorm testing.
4
 *
5
 * @package Jetpack
6
 */
7
8
/**
9
 * Class for Tweetstorm testing.
10
 */
11
class WP_Test_Jetpack_Tweetstorm_Helper extends WP_UnitTestCase {
12
13
	/**
14
	 * Setting up.
15
	 */
16
	public static function setUpBeforeClass() {
17
		parent::setUpBeforeClass();
18
19
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- This is cloning the internal behaviour of Requests.
20
		Requests::$transport[ serialize( array() ) ] = 'Tweetstorm_Requests_Transport_Override';
21
	}
22
23
	/**
24
	 * Tearing down.
25
	 */
26
	public static function tearDownAfterClass() {
27
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- This is cloning the internal behaviour of Requests.
28
		unset( Requests::$transport[ serialize( array() ) ] );
29
30
		parent::tearDownAfterClass();
31
	}
32
33
	/**
34
	 * Helper function. Given a string of text, it will generate the blob of data
35
	 * that the parser expects to receive for a paragraph block.
36
	 *
37
	 * @param string $text The paragraph text.
38
	 * @return array The paragraph blob of data.
39
	 */
40
	public function generateParagraphData( $text ) {
41
		$text = str_replace( "\n", '<br>', $text );
42
		return array(
43
			'attributes' => array(
44
				'content' => $text,
45
			),
46
			'block'      => array(
47
				'blockName' => 'core/paragraph',
48
				'innerHTML' => "\n<p>$text</p>\n",
49
			),
50
			'clientId'   => wp_generate_uuid4(),
51
		);
52
	}
53
54
	/**
55
	 * Helper function. Given a string of text, it will generate the blob of data
56
	 * that the parser expects to receive for a heading block.
57
	 *
58
	 * @param string $text The heading text.
59
	 * @return array The heading blob of data.
60
	 */
61 View Code Duplication
	public function generateHeadingData( $text ) {
62
		return array(
63
			'attributes' => array(
64
				'content' => $text,
65
			),
66
			'block'      => array(
67
				'blockName' => 'core/heading',
68
				'innerHTML' => "\n<h2>$text</h2>\n",
69
			),
70
			'clientId'   => wp_generate_uuid4(),
71
		);
72
	}
73
74
	/**
75
	 * Helper function. Given a string of text, it will generate the blob of data
76
	 * that the parser expects to receive for a verse block.
77
	 *
78
	 * @param string $text The verse text.
79
	 * @return array The verse blob of data.
80
	 */
81 View Code Duplication
	public function generateVerseData( $text ) {
82
		return array(
83
			'attributes' => array(
84
				'content' => $text,
85
			),
86
			'block'      => array(
87
				'blockName' => 'core/verse',
88
				'innerHTML' => "<pre>$text</pre>",
89
			),
90
			'clientId'   => wp_generate_uuid4(),
91
		);
92
	}
93
94
	/**
95
	 * Helper function. Given a quote and attribution, it will generate the blob of data
96
	 * that the parser expects to receive for a quote block.
97
	 *
98
	 * @param string $quote       The quote text.
99
	 * @param string $attribution The attribution text.
100
	 * @return array The quote blob of data.
101
	 */
102
	public function generateQuoteData( $quote, $attribution ) {
103
		// Generate an array of lines for the quote filtering out empty lines.
104
		$quote_lines = array_filter( array_map( 'trim', explode( "\n", $quote ) ), 'strlen' );
105
		$quote_value = '<p>' . implode( '</p><p>', $quote_lines ) . '</p>';
106
107
		return array(
108
			'attributes' => array(
109
				'value'    => $quote_value,
110
				'citation' => $attribution,
111
			),
112
			'block'      => array(
113
				'blockName' => 'core/quote',
114
				'innerHTML' => "<blockquote>$quote_value<cite>$attribution</cite></blockquote>",
115
			),
116
			'clientId'   => wp_generate_uuid4(),
117
		);
118
	}
119
120
	/**
121
	 * Helper function. Given a string of list data, it will generate the blob of data
122
	 * that the parser expects to receive for a list block.
123
	 *
124
	 * @param string $html The list data.
125
	 * @return array The list blob of data.
126
	 */
127 View Code Duplication
	public function generateListData( $html ) {
128
		return array(
129
			'attributes' => array(
130
				'values' => $html,
131
			),
132
			'block'      => array(
133
				'blockName' => 'core/list',
134
				'innerHTML' => "<ul>$html</ul>",
135
			),
136
			'clientId'   => wp_generate_uuid4(),
137
		);
138
	}
139
140
	/**
141
	 * Helper function. Given a URL, it will generate the blob of data
142
	 * that the parser expects to receive for an image block.
143
	 *
144
	 * @param string $url The image URL.
145
	 * @param string $alt The image alt text.
146
	 * @return array The image blob of data.
147
	 */
148 View Code Duplication
	public function generateImageData( $url, $alt ) {
149
		return array(
150
			'attributes' => array(
151
				'url' => $url,
152
				'alt' => $alt,
153
			),
154
			'block'      => array(
155
				'blockName' => 'core/image',
156
				'innerHTML' => "<figure><img src='$url' alt='$alt'/></figure>",
157
			),
158
			'clientId'   => wp_generate_uuid4(),
159
		);
160
	}
161
162
	/**
163
	 * Helper function. Given a URL, it will generate the blob of data
164
	 * that the parser expects to receive for a video block.
165
	 *
166
	 * @param string $url The video URL.
167
	 * @return array The video blob of data.
168
	 */
169 View Code Duplication
	public function generateVideoData( $url ) {
170
		return array(
171
			'attributes' => array(
172
				'url' => $url,
173
			),
174
			'block'      => array(
175
				'blockName' => 'core/video',
176
				'innerHTML' => "<figure><video src='$url'/></figure>",
177
			),
178
			'clientId'   => wp_generate_uuid4(),
179
		);
180
	}
181
182
	/**
183
	 * Helper function. Given a URL, it will generate the blob of data
184
	 * that the parser expects to receive for a VidePress video block.
185
	 *
186
	 * @param string $guid     The VideoPress video ID.
187
	 * @param string $filename The filename of the video.
188
	 * @return array The VideoPress blob of data.
189
	 */
190
	public function generateVideoPressData( $guid, $filename ) {
191
		return array(
192
			'attributes' => array(
193
				'guid' => $guid,
194
				'src'  => "https://videos.files.wordpress.com/$guid/$filename",
195
			),
196
			'block'      => array(
197
				'attrs'     => array(
198
					'guid' => $guid,
199
					'src'  => "https://videos.files.wordpress.com/$guid/$filename",
200
				),
201
				'blockName' => 'core/video',
202
				'innerHTML' => "<figure><div>\nhttps://videopress.com/v/$guid?preloadContent=metadata\n</div></figure>",
203
			),
204
			'clientId'   => wp_generate_uuid4(),
205
		);
206
	}
207
208
	/**
209
	 * Helper function. Given an array of image URLs and alt text, it will generate the
210
	 * blob of data that the parser expects to receive for a gallery block.
211
	 *
212
	 * @param array $images {
213
	 *     An array of images to include in the gallery.
214
	 *
215
	 *     @type string $url The image URL.
216
	 *     @type string $alt The image alt text.
217
	 * }
218
	 * @return array The gallery blob of data.
219
	 */
220
	public function generateGalleryData( $images ) {
221
		return array(
222
			'attributes' => array(
223
				'images' => $images,
224
			),
225
			'block'      => array(
226
				'blockName' => 'core/image',
227
				'innerHTML' => '<figure><ul>' . array_reduce(
228
					$images,
229
					function ( $image_string, $image ) {
230
						return "$image_string<li><figure><img src='{$image['url']}' alt='{$image['alt']}'/></li></figure>";
231
					},
232
					''
233
				) . '</ul></figure>',
234
			),
235
			'clientId'   => wp_generate_uuid4(),
236
		);
237
	}
238
239
	/**
240
	 * Helper function. Generate the blob of data that the parser
241
	 * expects to receive for a spacer block.
242
	 *
243
	 * @return array The spacer blob of data.
244
	 */
245 View Code Duplication
	public function generateSpacerData() {
246
		return array(
247
			'attributes' => array(),
248
			'block'      => array(
249
				'blockName' => 'core/spacer',
250
				'innerHTML' => '<div />',
251
			),
252
			'clientId'   => wp_generate_uuid4(),
253
		);
254
	}
255
256
	/**
257
	 * Helper function. Generate the blob of data that the parser
258
	 * expects to receive for a separator block.
259
	 *
260
	 * @return array The separator blob of data.
261
	 */
262 View Code Duplication
	public function generateSeparatorData() {
263
		return array(
264
			'attributes' => array(),
265
			'block'      => array(
266
				'blockName' => 'core/separator',
267
				'innerHTML' => '<hr />',
268
			),
269
			'clientId'   => wp_generate_uuid4(),
270
		);
271
	}
272
273
	/**
274
	 * Helper function. Generate the blob of data that the parser
275
	 * expects to receive for an embed block.
276
	 *
277
	 * @param string $provider The embed provider name.
278
	 * @param string $url      The url of the embed.
279
	 * @param bool   $classic  Whether to use the pre-WordPress 5.6 embed block format.
280
	 *
281
	 * @return array The embed blob of data.
282
	 */
283
	public function generateCoreEmbedData( $provider, $url, $classic ) {
284
		// @todo This is a fallback definition, it can be removed when WordPress 5.6 is the minimum supported version.
285
		if ( $classic ) {
286
			return array(
287
				'attributes' => array(
288
					'url' => $url,
289
				),
290
				'block'      => array(
291
					'attrs'     => array(
292
						'url' => $url,
293
					),
294
					'blockName' => "core-embed/$provider",
295
					'innerHTML' => '',
296
				),
297
				'clientId'   => wp_generate_uuid4(),
298
			);
299
		} else {
300
			return array(
301
				'attributes' => array(
302
					'providerNameSlug' => $provider,
303
					'url'              => $url,
304
				),
305
				'block'      => array(
306
					'attrs'     => array(
307
						'providerNameSlug' => $provider,
308
						'url'              => $url,
309
					),
310
					'blockName' => 'core/embed',
311
					'innerHTML' => '',
312
				),
313
				'clientId'   => wp_generate_uuid4(),
314
			);
315
		}
316
	}
317
318
	/**
319
	 * Helper function. Generate the blob of data that the parser
320
	 * expects to receive for Jetpack GIF block.
321
	 *
322
	 * @param string $url The embed URL of the GIF.
323
	 *
324
	 * @return array The embedded tweet blob of data.
325
	 */
326 View Code Duplication
	public function generateJetpackGifData( $url ) {
327
		return array(
328
			'attributes' => array(
329
				'giphyUrl' => $url,
330
			),
331
			'block'      => array(
332
				'attrs'     => array(
333
					'giphyUrl' => $url,
334
				),
335
				'blockName' => 'jetpack/gif',
336
			),
337
			'clientId'   => wp_generate_uuid4(),
338
		);
339
	}
340
341
	/**
342
	 * Helper function. Generates a normal boundary marker.
343
	 *
344
	 * @param int    $start     The start position of the marker.
345
	 * @param int    $end       The end position of the marker.
346
	 * @param string $container The name of the RichText container this boundary is for.
347
	 * @return array The boundary marker definition.
348
	 */
349
	public function generateNormalBoundary( $start, $end, $container ) {
350
		return array(
351
			'start'     => $start,
352
			'end'       => $end,
353
			'container' => $container,
354
			'type'      => 'normal',
355
		);
356
	}
357
358
	/**
359
	 * Helper function. Generates a line break boundary marker.
360
	 *
361
	 * @param int    $start     The start position of the marker.
362
	 * @param int    $end       The end position of the marker.
363
	 * @param string $container The name of the RichText container this boundary is for.
364
	 * @return array The boundary marker definition.
365
	 */
366
	public function generateLineBreakBoundary( $start, $end, $container ) {
367
		return array(
368
			'start'     => $start,
369
			'end'       => $end,
370
			'container' => $container,
371
			'type'      => 'line-break',
372
		);
373
	}
374
375
	/**
376
	 * Helper function. Generates a normal boundary marker.
377
	 *
378
	 * @param int    $line      The line number of the marker.
379
	 * @param string $container The name of the RichText container this boundary is for.
380
	 * @return array The boundary marker definition.
381
	 */
382
	public function generateLineBoundary( $line, $container ) {
383
		return array(
384
			'line'      => $line,
385
			'container' => $container,
386
			'type'      => 'end-of-line',
387
		);
388
	}
389
390
	/**
391
	 * Helper function. Tests that a generate tweet contains the expected data.
392
	 *
393
	 * @param string|array $content {
394
	 *     The content of the tweet. Passing a string is the equivalent of passing
395
	 *     an array with the `text` parameter set.
396
	 *
397
	 *     @type string $text  Optional. The text of the tweet.
398
	 *     @type array  $media Optional. Array of media that will be used for media attachments.
399
	 *     @type string $tweet Optional. URL of a tweet to be quoted.
400
	 *     @type array  $urls  Optional. A list of URLs that appear in the tweet text.
401
	 * }
402
	 * @param array        $blocks      An array of blocks that should be defined in the tweet.
403
	 * @param array        $boundary    The boundary data that the tweet should contain.
404
	 * @param array        $tweet       A single tweet returned from the parser.
405
	 * @param bool         $editor_info Flag whether or not editor-related info should be in the tweet.
406
	 */
407
	public function assertTweetContains( $content, $blocks, $boundary, $tweet, $editor_info ) {
408
		if ( is_string( $content ) ) {
409
			$content = array(
410
				'text' => $content,
411
			);
412
		}
413
414
		$content = wp_parse_args(
415
			$content,
416
			array(
0 ignored issues
show
Documentation introduced by
array('text' => '', 'med... '', 'urls' => array()) is of type array<string,string|arra...tring","urls":"array"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
417
				'text'  => '',
418
				'media' => array(),
419
				'tweet' => '',
420
				'urls'  => array(),
421
			)
422
		);
423
424
		$this->assertEquals( $content['text'], $tweet['text'] );
425
		$this->assertEquals( $content['media'], $tweet['media'] );
426
		$this->assertEquals( $content['tweet'], $tweet['tweet'] );
427
		$this->assertEquals( $content['urls'], $tweet['urls'] );
428
429
		if ( $editor_info ) {
430
			$block_count = count( $blocks );
431
432
			$this->assertEquals( $block_count, count( $tweet['blocks'] ) );
433
434
			for ( $ii = 0; $ii < $block_count; $ii++ ) {
435
				$this->assertEquals( 2, count( $tweet['blocks'][ $ii ] ) );
436
				$this->assertEquals( $blocks[ $ii ]['clientId'], $tweet['blocks'][ $ii ]['clientId'] );
437
				$this->assertEquals( $blocks[ $ii ]['attributes'], $tweet['blocks'][ $ii ]['attributes'] );
438
			}
439
440
			$this->assertEquals( $boundary, $tweet['boundary'] );
441
		} else {
442
			$this->assertEquals( array(), $tweet['blocks'] );
443
			$this->assertFalse( $tweet['boundary'] );
444
		}
445
	}
446
447
	/**
448
	 * Helper function. Generates tweets in the form of both editor and Publicize requests, then
449
	 * confirms that they've been generated the same way.
450
	 *
451
	 * @param array $blocks       The array of blocks used to generate the tweets.
452
	 * @param array $content {
453
	 *     An array of content matching the generated tweets. Each element can be a string, which is
454
	 *     the equivalent of passing an array with the `text` parameter set.
455
	 *
456
	 *     @type string $text  Optional. The text of the tweet.
457
	 *     @type array  $media Optional. Array of media that will be used for media attachments.
458
	 *     @type string $tweet Optional. URL of a tweet to be quoted.
459
	 *     @type array  $urls  Optional. A list of URLs that appear in the tweet text.
460
	 * }
461
	 * @param array $boundaries   The boundary data that each tweet should contain.
462
	 * @param array $tweet_blocks An array of arrays: each child array should be the blocks used
463
	 *                            to generate each tweet.
464
	 */
465
	public function assertTweetGenerated( $blocks, $content, $boundaries, $tweet_blocks ) {
466
		$tweets      = Jetpack_Tweetstorm_Helper::parse( $blocks );
467
		$tweet_count = count( $tweets );
468
469
		$this->assertEquals( $tweet_count, count( $content ) );
470
		$this->assertEquals( $tweet_count, count( $boundaries ) );
471
		$this->assertEquals( $tweet_count, count( $tweet_blocks ) );
472
473 View Code Duplication
		for ( $ii = 0; $ii < $tweet_count; $ii++ ) {
474
			$this->assertTweetContains( $content[ $ii ], $tweet_blocks[ $ii ], $boundaries[ $ii ], $tweets[ $ii ], true );
475
		}
476
477
		// Remove the data that the editor sends, to match Publicize's behaviour.
478
		$publicize_blocks = array_map(
479
			function ( $block ) {
480
				unset( $block['attributes'] );
481
				unset( $block['clientId'] );
482
483
				return $block;
484
			},
485
			$blocks
486
		);
487
488
		$tweets      = Jetpack_Tweetstorm_Helper::parse( $publicize_blocks );
489
		$tweet_count = count( $tweets );
490
491
		$this->assertEquals( $tweet_count, count( $content ) );
492
		$this->assertEquals( $tweet_count, count( $boundaries ) );
493
		$this->assertEquals( $tweet_count, count( $tweet_blocks ) );
494
495 View Code Duplication
		for ( $ii = 0; $ii < $tweet_count; $ii++ ) {
496
			$this->assertTweetContains( $content[ $ii ], $tweet_blocks[ $ii ], $boundaries[ $ii ], $tweets[ $ii ], false );
497
		}
498
499
	}
500
501
	/**
502
	 * Test that sending no blocks gives no tweets.
503
	 */
504
	public function test_no_blocks_no_tweets() {
505
		$this->assertTweetGenerated( array(), array(), array(), array() );
506
	}
507
508
	/**
509
	 * Test that sending empty blocks gives no tweets.
510
	 */
511
	public function test_no_content_no_tweets() {
512
		$blocks = array(
513
			$this->generateParagraphData( '' ),
514
			$this->generateHeadingData( '&nbsp;' ),
515
			$this->generateHeadingData( '' ),
516
			$this->generateListData( '<li></li>' ),
517
			$this->generateQuoteData( '', '' ),
518
		);
519
520
		$this->assertTweetGenerated( $blocks, array(), array(), array() );
521
	}
522
523
	/**
524
	 * Test that an unsupported block gives no tweets.
525
	 */
526
	public function test_unsupported_block_no_tweets() {
527
		$blocks = array(
528
			array(
529
				'attributes' => array(
530
					'feedURL' => 'https://wordpress.org/news/feed/',
531
				),
532
				'block'      => array(
533
					'blockName' => 'core/rss',
534
					'innerHTML' => '',
535
				),
536
				'clientId'   => wp_generate_uuid4(),
537
			),
538
		);
539
540
		$this->assertTweetGenerated( $blocks, array(), array(), array() );
541
	}
542
543
	/**
544
	 * Test that a block which relies on innerHTML for the content won't
545
	 * generate a tweet if they don't have innerHTML set.
546
	 */
547
	public function test_no_innerhtml_no_tweets() {
548
		$blocks = array(
549
			array(
550
				'attributes' => array(
551
					'content' => null,
552
				),
553
				'block'      => array(
554
					'blockName' => 'core/paragraph',
555
				),
556
				'clientId'   => wp_generate_uuid4(),
557
			),
558
		);
559
560
		$this->assertTweetGenerated( $blocks, array(), array(), array() );
561
	}
562
563
	/**
564
	 * Test that a single short paragraph turns into one tweet.
565
	 */
566 View Code Duplication
	public function test_single_paragraph() {
567
		$test_content = 'This is some content.';
568
		$blocks       = array(
569
			$this->generateParagraphData( $test_content ),
570
		);
571
572
		$this->assertTweetGenerated( $blocks, array( $test_content ), array( false ), array( $blocks ) );
573
	}
574
575
	/**
576
	 * Test that multiple short paragraphs are joined together into one tweet.
577
	 */
578
	public function test_multiple_paragraphs() {
579
		$test_content = 'This is some content.';
580
		$blocks       = array(
581
			$this->generateParagraphData( $test_content ),
582
			$this->generateParagraphData( $test_content ),
583
			$this->generateParagraphData( $test_content ),
584
		);
585
586
		$this->assertTweetGenerated(
587
			$blocks,
588
			array( "$test_content\n\n$test_content\n\n$test_content" ),
589
			array( false ),
590
			array( $blocks )
591
		);
592
	}
593
594
	/**
595
	 * Test that a single long paragraph is split into two tweets, breaking at the end of a sentence.
596
	 */
597
	public function test_single_long_paragraph() {
598
		$test_content = 'This is 23 characters. ';
599
		$blocks       = array(
600
			$this->generateParagraphData( str_repeat( $test_content, 13 ) ),
601
		);
602
603
		$this->assertTweetGenerated(
604
			$blocks,
605
			array( trim( str_repeat( $test_content, 12 ) ), trim( $test_content ) ),
606
			array( $this->generateNormalBoundary( 275, 276, 'content' ), false ),
607
			array( $blocks, $blocks )
608
		);
609
	}
610
611
	/**
612
	 * Test that a single long paragraph is split into two tweets, breaking at the end of a line.
613
	 */
614
	public function test_single_long_paragraph_with_line_breaks() {
615
		$test_content = 'This is 21 characters';
616
		$blocks       = array(
617
			$this->generateParagraphData( str_repeat( "$test_content\n", 7 ) . trim( str_repeat( "$test_content ", 7 ) ) . '.' ),
618
		);
619
620
		$this->assertTweetGenerated(
621
			$blocks,
622
			array( trim( str_repeat( "$test_content\n", 7 ) ), trim( str_repeat( "$test_content ", 7 ) ) . '.' ),
623
			array( $this->generateLineBreakBoundary( 153, 154, 'content' ), false ),
624
			array( $blocks, $blocks )
625
		);
626
	}
627
628
	/**
629
	 * Test that a single long paragraph is split into two tweets, breaking at the end of a sentence.
630
	 */
631 View Code Duplication
	public function test_line_break_is_preserved() {
632
		$test_content = "First line.\nsecond line.";
633
		$blocks       = array(
634
			$this->generateParagraphData( $test_content ),
635
		);
636
637
		$this->assertTweetGenerated(
638
			$blocks,
639
			array( $test_content ),
640
			array( false ),
641
			array( $blocks )
642
		);
643
	}
644
645
	/**
646
	 * Test that a single long paragraph containing multibyte characters is split into two tweets,
647
	 * breaking at the end of a sentence.
648
	 */
649
	public function test_single_long_multibyte_paragraph() {
650
		$test_content = 'ℌ𝔢𝔯𝔢 𝔦𝔰 𝔰𝔬𝔪𝔢 𝔱𝔢𝔵𝔱. ';
651
		$blocks       = array(
652
			$this->generateParagraphData( str_repeat( $test_content, 18 ) ),
653
		);
654
655
		$expected_text = array(
656
			trim( str_repeat( $test_content, 8 ) ),
657
			trim( str_repeat( $test_content, 8 ) ),
658
			trim( str_repeat( $test_content, 2 ) ),
659
		);
660
661
		$expected_boundaries = array(
662
			$this->generateNormalBoundary( 255, 256, 'content' ),
663
			$this->generateNormalBoundary( 511, 512, 'content' ),
664
			false,
665
		);
666
667
		$expected_blocks = array(
668
			$blocks,
669
			$blocks,
670
			$blocks,
671
		);
672
673
		$this->assertTweetGenerated(
674
			$blocks,
675
			$expected_text,
676
			$expected_boundaries,
677
			$expected_blocks
678
		);
679
	}
680
681
	/**
682
	 * Test that, after a long paragraph is split into two tweets, a subsequent short
683
	 * paragraph is appended to the second tweet.
684
	 */
685
	public function test_long_paragraph_followed_by_short_paragraph() {
686
		$test_content = 'This is 23 characters. ';
687
		$blocks       = array(
688
			$this->generateParagraphData( str_repeat( $test_content, 13 ) ),
689
			$this->generateParagraphData( $test_content ),
690
		);
691
692
		$expected_text = array(
693
			trim( str_repeat( $test_content, 12 ) ),
694
			trim( $test_content ) . "\n\n" . trim( $test_content ),
695
		);
696
697
		$expected_boundaries = array(
698
			$this->generateNormalBoundary( 275, 276, 'content' ),
699
			false,
700
		);
701
702
		$expected_blocks = array(
703
			array( $blocks[0] ),
704
			$blocks,
705
		);
706
707
		$this->assertTweetGenerated(
708
			$blocks,
709
			$expected_text,
710
			$expected_boundaries,
711
			$expected_blocks
712
		);
713
	}
714
715
	/**
716
	 * Test that a long paragraph will start a new tweet if it's too long to append to the previous tweet.
717
	 */
718
	public function test_short_paragraph_followed_by_long_paragraph() {
719
		$test_content = 'This is 23 characters. ';
720
		$blocks       = array(
721
			$this->generateParagraphData( $test_content ),
722
			$this->generateParagraphData( str_repeat( $test_content, 13 ) ),
723
		);
724
725
		$expected_text = array(
726
			trim( $test_content ),
727
			trim( str_repeat( $test_content, 12 ) ),
728
			trim( $test_content ),
729
		);
730
731
		$expected_boundaries = array(
732
			false,
733
			$this->generateNormalBoundary( 275, 276, 'content' ),
734
			false,
735
		);
736
737
		$expected_blocks = array(
738
			array( $blocks[0] ),
739
			array( $blocks[1] ),
740
			array( $blocks[1] ),
741
		);
742
743
		$this->assertTweetGenerated(
744
			$blocks,
745
			$expected_text,
746
			$expected_boundaries,
747
			$expected_blocks
748
		);
749
	}
750
751
	/**
752
	 * Test that a sentence which is too long for a single tweet is split into two tweets, at a word break.
753
	 */
754 View Code Duplication
	public function test_long_sentence() {
755
		$test_content = 'This is 22 characters ';
756
		$blocks       = array(
757
			$this->generateParagraphData( str_repeat( $test_content, 13 ) ),
758
		);
759
760
		$expected_text = array(
761
			str_repeat( $test_content, 12 ) . 'This is 22…',
762
			'…characters',
763
		);
764
765
		$expected_boundaries = array(
766
			$this->generateNormalBoundary( 274, 275, 'content' ),
767
			false,
768
		);
769
770
		$expected_blocks = array(
771
			$blocks,
772
			$blocks,
773
		);
774
775
		$this->assertTweetGenerated(
776
			$blocks,
777
			$expected_text,
778
			$expected_boundaries,
779
			$expected_blocks
780
		);
781
	}
782
783
	/**
784
	 * Test that other space characters will be used when splitting sentences up into words.
785
	 */
786
	public function test_long_sentence_with_nbsp() {
787
		$test_content = 'This&nbsp;is&nbsp;22&nbsp;characters&nbsp;';
788
		$blocks       = array(
789
			$this->generateParagraphData( str_repeat( $test_content, 13 ) ),
790
		);
791
792
		$expected_text = array(
793
			// The parser will decode the HTML entities.
794
			html_entity_decode( str_repeat( $test_content, 12 ) . 'This&nbsp;is&nbsp;22…', ENT_QUOTES ),
795
			html_entity_decode( '…characters', ENT_QUOTES ),
796
		);
797
798
		$expected_boundaries = array(
799
			$this->generateNormalBoundary( 274, 275, 'content' ),
800
			false,
801
		);
802
803
		$expected_blocks = array(
804
			$blocks,
805
			$blocks,
806
		);
807
808
		$this->assertTweetGenerated(
809
			$blocks,
810
			$expected_text,
811
			$expected_boundaries,
812
			$expected_blocks
813
		);
814
	}
815
816
	/**
817
	 * Test that short sentences are split up correctly when they're following a long sentence
818
	 * which has been split into two tweets.
819
	 */
820
	public function test_long_sentence_followed_by_short_sentences() {
821
		$test_content   = 'This is 22 characters ';
822
		$short_sentence = 'This is 23 characters. ';
823
		$blocks         = array(
824
			$this->generateParagraphData( trim( str_repeat( $test_content, 13 ) ) . '. ' . str_repeat( $short_sentence, 13 ) ),
825
		);
826
827
		$expected_text = array(
828
			str_repeat( $test_content, 12 ) . 'This is 22…',
829
			trim( '…characters. ' . str_repeat( $short_sentence, 11 ) ),
830
			trim( str_repeat( $short_sentence, 2 ) ),
831
		);
832
833
		$expected_boundaries = array(
834
			$this->generateNormalBoundary( 274, 275, 'content' ),
835
			$this->generateNormalBoundary( 539, 540, 'content' ),
836
			false,
837
		);
838
839
		$expected_blocks = array(
840
			$blocks,
841
			$blocks,
842
			$blocks,
843
		);
844
845
		$this->assertTweetGenerated(
846
			$blocks,
847
			$expected_text,
848
			$expected_boundaries,
849
			$expected_blocks
850
		);
851
	}
852
853
	/**
854
	 * Test that a long sentence will start in the next tweet when it's too long.
855
	 */
856
	public function test_short_sentence_followed_by_a_long_sentence() {
857
		$test_content   = 'This is 22 characters ';
858
		$short_sentence = 'This is 23 characters. ';
859
		$blocks         = array(
860
			$this->generateParagraphData( $short_sentence . trim( str_repeat( $test_content, 13 ) ) . '.' ),
861
		);
862
863
		$expected_text = array(
864
			trim( $short_sentence ),
865
			trim( str_repeat( $test_content, 12 ) . 'This is 22…' ),
866
			'…characters.',
867
		);
868
869
		$expected_boundaries = array(
870
			$this->generateNormalBoundary( 22, 23, 'content' ),
871
			$this->generateNormalBoundary( 297, 298, 'content' ),
872
			false,
873
		);
874
875
		$expected_blocks = array(
876
			$blocks,
877
			$blocks,
878
			$blocks,
879
		);
880
881
		$this->assertTweetGenerated(
882
			$blocks,
883
			$expected_text,
884
			$expected_boundaries,
885
			$expected_blocks
886
		);
887
	}
888
889
	/**
890
	 * Test that a long sentence will start a new tweet when it's too long to append to the previous tweet.
891
	 */
892 View Code Duplication
	public function test_short_paragraph_followed_by_long_sentence() {
893
		$test_paragraph      = 'This is 23 characters. ';
894
		$test_sentence_chunk = 'This is 22 characters ';
895
896
		$blocks = array(
897
			$this->generateParagraphData( $test_paragraph ),
898
			$this->generateParagraphData( str_repeat( $test_sentence_chunk, 13 ) ),
899
		);
900
901
		$expected_text = array(
902
			trim( $test_paragraph ),
903
			str_repeat( $test_sentence_chunk, 12 ) . 'This is 22…',
904
			'…characters',
905
		);
906
907
		$expected_boundaries = array(
908
			false,
909
			$this->generateNormalBoundary( 274, 275, 'content' ),
910
			false,
911
		);
912
913
		$expected_blocks = array(
914
			array( $blocks[0] ),
915
			array( $blocks[1] ),
916
			array( $blocks[1] ),
917
		);
918
919
		$this->assertTweetGenerated(
920
			$blocks,
921
			$expected_text,
922
			$expected_boundaries,
923
			$expected_blocks
924
		);
925
	}
926
927
	/**
928
	 * Test that a basic verse maintains spacing.
929
	 */
930 View Code Duplication
	public function test_basic_verse() {
931
		$test_content = " They say that code \n        is poetry.\n\n    Is indentation poetry,\n  too?";
932
933
		$blocks = array(
934
			$this->generateVerseData( $test_content ),
935
		);
936
937
		$expected_text = array(
938
			" They say that code\n        is poetry.\n\n    Is indentation poetry,\n  too?",
939
		);
940
941
		$this->assertTweetGenerated( $blocks, $expected_text, array( false ), array( $blocks ) );
942
	}
943
944
	/**
945
	 * Test that a long verse splits correctly.
946
	 */
947
	public function test_long_verse() {
948
		$test_content = "They say that code\n        is poetry.\n\n    Is indentation poetry,\n  too?\n\n";
949
950
		$blocks = array(
951
			$this->generateVerseData( trim( str_repeat( $test_content, 4 ) ) ),
952
		);
953
954
		$expected_content = array(
955
			array(
956
				'text' => str_repeat( $test_content, 3 ) . "They say that code\n        is poetry.",
957
			),
958
			array(
959
				'text' => "Is indentation poetry,\n  too?",
960
			),
961
		);
962
963
		$expected_boundaries = array(
964
			$this->generateNormalBoundary( 264, 265, 'content' ),
965
			false,
966
		);
967
968
		$expected_blocks = array( $blocks, $blocks );
969
970
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
971
	}
972
973
	/**
974
	 * Test that a list which is too long for a single tweet is split at the end of a list line.
975
	 */
976 View Code Duplication
	public function test_long_list() {
977
		$test_content = 'This is 22 characters.';
978
979
		$blocks = array(
980
			$this->generateListData( str_repeat( "<li>$test_content</li>", 12 ) ),
981
		);
982
983
		$expected_text = array(
984
			trim( str_repeat( "- $test_content\n", 11 ) ),
985
			"- $test_content",
986
		);
987
988
		$expected_boundaries = array(
989
			$this->generateLineBoundary( 10, 'values' ),
990
			false,
991
		);
992
993
		$expected_blocks = array(
994
			$blocks,
995
			$blocks,
996
		);
997
998
		$this->assertTweetGenerated(
999
			$blocks,
1000
			$expected_text,
1001
			$expected_boundaries,
1002
			$expected_blocks
1003
		);
1004
	}
1005
1006
	/**
1007
	 * Test that a long list will start a new tweet when it's too long to be appended to the previous
1008
	 * tweet, even if some of the lines in the list would fit.
1009
	 */
1010 View Code Duplication
	public function test_short_paragraph_followed_by_long_list() {
1011
		$test_content = 'This is 22 characters.';
1012
1013
		$blocks = array(
1014
			$this->generateParagraphData( $test_content ),
1015
			$this->generateListData( str_repeat( "<li>$test_content</li>", 12 ) ),
1016
		);
1017
1018
		$expected_text = array(
1019
			$test_content,
1020
			trim( str_repeat( "- $test_content\n", 11 ) ),
1021
			"- $test_content",
1022
		);
1023
1024
		$expected_boundaries = array(
1025
			false,
1026
			$this->generateLineBoundary( 10, 'values' ),
1027
			false,
1028
		);
1029
1030
		$expected_blocks = array(
1031
			array( $blocks[0] ),
1032
			array( $blocks[1] ),
1033
			array( $blocks[1] ),
1034
		);
1035
1036
		$this->assertTweetGenerated(
1037
			$blocks,
1038
			$expected_text,
1039
			$expected_boundaries,
1040
			$expected_blocks
1041
		);
1042
	}
1043
1044
	/**
1045
	 * Test that a range of emoji (including a variety of compound emoji) count as two characters.
1046
	 */
1047 View Code Duplication
	public function test_emoji_count_as_two_characters() {
1048
		$test_content = '🙂 🏳️‍🌈 👩‍👩‍👧‍👧 👨🏾‍🦰 👩🏻‍💻 ';
1049
1050
		$blocks = array(
1051
			$this->generateParagraphData( str_repeat( $test_content, 19 ) ),
1052
		);
1053
1054
		$expected_text = array(
1055
			trim( str_repeat( $test_content, 18 ) ) . ' 🙂 🏳️‍🌈 👩‍👩‍👧‍👧…',
1056
			'…👨🏾‍🦰 👩🏻‍💻',
1057
		);
1058
1059
		$expected_boundaries = array(
1060
			$this->generateNormalBoundary( 705, 706, 'content' ),
1061
			false,
1062
		);
1063
1064
		$expected_blocks = array(
1065
			$blocks,
1066
			$blocks,
1067
		);
1068
1069
		$this->assertTweetGenerated(
1070
			$blocks,
1071
			$expected_text,
1072
			$expected_boundaries,
1073
			$expected_blocks
1074
		);
1075
	}
1076
1077
	/**
1078
	 * Test that inline images don't show up in the tweet.
1079
	 */
1080
	public function test_inline_images_dont_show_in_tweets() {
1081
		$test_content = 'This is some text and an image, <img src="foo.jpg" />friend.';
1082
1083
		$blocks = array(
1084
			$this->generateParagraphData( $test_content ),
1085
		);
1086
1087
		$expected_text = array(
1088
			wp_strip_all_tags( $test_content ),
1089
		);
1090
1091
		$expected_boundaries = array( false );
1092
1093
		$expected_blocks = array( $blocks );
1094
1095
		$this->assertTweetGenerated(
1096
			$blocks,
1097
			$expected_text,
1098
			$expected_boundaries,
1099
			$expected_blocks
1100
		);
1101
	}
1102
1103
	/**
1104
	 * Test that inline images are accounted for when generating boundaries.
1105
	 */
1106
	public function test_inline_images_are_counted_for_boundaries() {
1107
		$test_content = 'This is a sentence that takes up some space. ';
1108
1109
		$blocks = array(
1110
			$this->generateParagraphData(
1111
				trim( str_repeat( $test_content, 4 ) ) .
1112
				// Test that the boundary doesn't appear on an image immediately after a period.
1113
				'<img src="1.jpg" />' .
1114
				// Test that a mid-sentence image is counted properly.
1115
				' This is a <img src="2.jpg" />sentence that takes up some space. ' .
1116
				// Test that the boundary doesn't appear on an image immediately before the next sentence.
1117
				'<img src="3.jpg" />' .
1118
				trim( $test_content ) .
1119
				// Test that an image that replaces the space between sentences is handled.
1120
				'<img src="4.jpg" />' .
1121
				trim( str_repeat( $test_content, 4 ) )
1122
			),
1123
		);
1124
1125
		$expected_text = array(
1126
			trim( str_repeat( $test_content, 3 ) ),
1127
			// There's no space inserted when 4.jpg is removed.
1128
			trim( str_repeat( $test_content, 3 ) ) . trim( str_repeat( $test_content, 3 ) ),
1129
			trim( $test_content ),
1130
		);
1131
1132
		$expected_boundaries = array(
1133
			$this->generateNormalBoundary( 134, 135, 'content' ),
1134
			$this->generateNormalBoundary( 407, 408, 'content' ),
1135
			false,
1136
		);
1137
1138
		$expected_blocks = array(
1139
			$blocks,
1140
			$blocks,
1141
			$blocks,
1142
		);
1143
1144
		$this->assertTweetGenerated(
1145
			$blocks,
1146
			$expected_text,
1147
			$expected_boundaries,
1148
			$expected_blocks
1149
		);
1150
	}
1151
1152
	/**
1153
	 * Test that a boundary will be set at the end of the short items, and correct
1154
	 * boundaries will be set inside the long item.
1155
	 */
1156
	public function test_short_list_items_followed_by_long_list_item() {
1157
		$test_content = '🙂 🏳️‍🌈 👩‍👩‍👧‍👧 👨🏾‍🦰 👩🏻‍💻 ';
1158
1159
		$blocks = array(
1160
			$this->generateListData(
1161
				str_repeat( '<li>' . trim( $test_content ) . '</li>', 2 ) . '<li>' . trim( str_repeat( $test_content, 50 ) ) . '</li>'
1162
			),
1163
		);
1164
1165
		$expected_text = array(
1166
			trim( str_repeat( '- ' . trim( $test_content ) . "\n", 2 ) ),
1167
			'- ' . trim( str_repeat( $test_content, 18 ) ) . ' 🙂 🏳️‍🌈…',
1168
			'…👩‍👩‍👧‍👧 👨🏾‍🦰 👩🏻‍💻 ' . trim( str_repeat( $test_content, 17 ) ) . ' 🙂 🏳️‍🌈 👩‍👩‍👧‍👧 👨🏾‍🦰…',
1169
			'…👩🏻‍💻 ' . trim( str_repeat( $test_content, 13 ) ),
1170
		);
1171
1172
		$expected_boundaries = array(
1173
			$this->generateLineBoundary( 1, 'values' ),
1174
			$this->generateNormalBoundary( 769, 770, 'values' ),
1175
			$this->generateNormalBoundary( 1473, 1474, 'values' ),
1176
			false,
1177
		);
1178
1179
		$expected_blocks = array(
1180
			$blocks,
1181
			$blocks,
1182
			$blocks,
1183
			$blocks,
1184
		);
1185
1186
		$this->assertTweetGenerated(
1187
			$blocks,
1188
			$expected_text,
1189
			$expected_boundaries,
1190
			$expected_blocks
1191
		);
1192
	}
1193
1194
	/**
1195
	 * Test that an assortment of blank lines in a list are ignored.
1196
	 */
1197 View Code Duplication
	public function test_blank_list_items() {
1198
		$test_content = 'This is 22 characters.';
1199
1200
		$blocks = array(
1201
			$this->generateListData( "<li></li><li></li><li><li></li><li>$test_content</li></li><li></li><li>$test_content</li>" ),
1202
		);
1203
1204
		$this->assertTweetGenerated(
1205
			$blocks,
1206
			array( "- $test_content\n- $test_content" ),
1207
			array( false ),
1208
			array( $blocks )
1209
		);
1210
	}
1211
1212
	/**
1213
	 * Test that a simple quote block renders correctly.
1214
	 */
1215
	public function test_simple_quote() {
1216
		$test_quote       = '“You miss 100% of the shots you don’t take” – Wayne Gretzky – Michael Scott';
1217
		$test_attribution = 'Gary Pendergast';
1218
1219
		$blocks = array(
1220
			$this->generateQuoteData( $test_quote, $test_attribution ),
1221
		);
1222
1223
		$this->assertTweetGenerated(
1224
			$blocks,
1225
			array( "“{$test_quote}” – $test_attribution" ),
1226
			array( false ),
1227
			array( $blocks )
1228
		);
1229
	}
1230
1231
	/**
1232
	 * Test that a long quote block splits.
1233
	 */
1234 View Code Duplication
	public function test_long_quote() {
1235
		$test_quote       = 'Here is a bunch of text for you. ';
1236
		$test_attribution = 'Gary Pendergast';
1237
1238
		$blocks = array(
1239
			$this->generateQuoteData( trim( str_repeat( $test_quote, 9 ) ), $test_attribution ),
1240
		);
1241
1242
		$expected_content = array(
1243
			array(
1244
				'text' => '“' . trim( str_repeat( $test_quote, 8 ) ),
1245
			),
1246
			array(
1247
				'text' => trim( $test_quote ) . "” – $test_attribution",
1248
			),
1249
		);
1250
1251
		$expected_boundaries = array(
1252
			$this->generateNormalBoundary( 263, 264, 'value' ),
1253
			false,
1254
		);
1255
1256
		$expected_blocks = array( $blocks, $blocks );
1257
1258
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1259
	}
1260
1261
	/**
1262
	 * Test that a quote block with multiple paragraphs splits correctly.
1263
	 */
1264
	public function test_multi_paragraph_quote() {
1265
		$test_quote       = 'Here is a bunch of text for you. ';
1266
		$test_attribution = 'Gary Pendergast';
1267
1268
		$blocks = array(
1269
			$this->generateQuoteData( trim( str_repeat( $test_quote, 4 ) ) . "\n" . trim( str_repeat( $test_quote, 4 ) ), $test_attribution ),
1270
		);
1271
1272
		$expected_content = array(
1273
			array(
1274
				'text' => '“' . trim( str_repeat( $test_quote, 4 ) ) . "\n" . trim( str_repeat( $test_quote, 3 ) ),
1275
			),
1276
			array(
1277
				'text' => trim( $test_quote ) . "” – $test_attribution",
1278
			),
1279
		);
1280
1281
		$expected_boundaries = array(
1282
			$this->generateNormalBoundary( 230, 231, 'value' ),
1283
			false,
1284
		);
1285
1286
		$expected_blocks = array( $blocks, $blocks );
1287
1288
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1289
	}
1290
1291
	/**
1292
	 * Test that a quote attribution with sentences in it splits.
1293
	 */
1294 View Code Duplication
	public function test_quote_attribution_sentence_splits() {
1295
		$test_quote       = 'Here is a bunch of text for you. ';
1296
		$test_attribution = 'Ugh. That guy. You know.';
1297
1298
		$blocks = array(
1299
			$this->generateQuoteData( trim( str_repeat( $test_quote, 8 ) ), $test_attribution ),
1300
		);
1301
1302
		$expected_content = array(
1303
			array(
1304
				'text' => '“' . trim( str_repeat( $test_quote, 8 ) ) . '” – Ugh.',
1305
			),
1306
			array(
1307
				'text' => 'That guy. You know.',
1308
			),
1309
		);
1310
1311
		$expected_boundaries = array(
1312
			$this->generateNormalBoundary( 4, 5, 'citation' ),
1313
			false,
1314
		);
1315
1316
		$expected_blocks = array( $blocks, $blocks );
1317
1318
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1319
	}
1320
1321
	/**
1322
	 * Test that a heading will start a new block.
1323
	 */
1324
	public function test_heading() {
1325
		$test_content = 'Here is some text.';
1326
		$test_heading = 'This is more text!';
1327
1328
		$blocks = array(
1329
			$this->generateParagraphData( $test_content ),
1330
			$this->generateHeadingData( $test_heading ),
1331
		);
1332
1333
		$expected_text = array(
1334
			$test_content,
1335
			$test_heading,
1336
		);
1337
1338
		$expected_boundaries = array(
1339
			false,
1340
			false,
1341
		);
1342
1343
		$expected_blocks = array(
1344
			array( $blocks[0] ),
1345
			array( $blocks[1] ),
1346
		);
1347
1348
		$this->assertTweetGenerated(
1349
			$blocks,
1350
			$expected_text,
1351
			$expected_boundaries,
1352
			$expected_blocks
1353
		);
1354
	}
1355
1356
	/**
1357
	 * Test that an image block will be appended to the previous tweet.
1358
	 */
1359
	public function test_image_is_appended() {
1360
		$test_content = 'That selfie lyfe';
1361
		$test_url     = 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg';
1362
		$test_alt     = 'This is how we roll.';
1363
1364
		$blocks = array(
1365
			$this->generateParagraphData( $test_content ),
1366
			$this->generateImageData( $test_url, $test_alt ),
1367
		);
1368
1369
		$expected_content = array(
1370
			array(
1371
				'text'  => $test_content,
1372
				'media' => array(
1373
					array(
1374
						'url'  => $test_url,
1375
						'alt'  => $test_alt,
1376
						'type' => 'image/jpeg',
1377
					),
1378
				),
1379
			),
1380
		);
1381
1382
		$this->assertTweetGenerated( $blocks, $expected_content, array( false ), array( $blocks ) );
1383
	}
1384
1385
	/**
1386
	 * Test that an image block with really long alt text will have the alt text removed.
1387
	 */
1388
	public function test_long_alt_is_removed() {
1389
		$test_url = 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg';
1390
		$test_alt = str_repeat( 'a', 1001 );
1391
1392
		$blocks = array(
1393
			$this->generateImageData( $test_url, $test_alt ),
1394
		);
1395
1396
		$expected_content = array(
1397
			array(
1398
				'media' => array(
1399
					array(
1400
						'url'  => $test_url,
1401
						'alt'  => '',
1402
						'type' => 'image/jpeg',
1403
					),
1404
				),
1405
			),
1406
		);
1407
1408
		$this->assertTweetGenerated( $blocks, $expected_content, array( false ), array( $blocks ) );
1409
	}
1410
1411
	/**
1412
	 * Test that an image block will be appended to the previous tweet, but then a gallery and
1413
	 * second image won't be appended.
1414
	 */
1415
	public function test_gallery_and_second_image_are_not_appended() {
1416
		$test_content = 'That selfie lyfe';
1417
		$test_images  = array(
1418
			array(
1419
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1420
				'alt'  => 'This is how we roll.',
1421
				'type' => 'image/jpeg',
1422
			),
1423
			array(
1424
				'url'  => 'https://pentophoto.files.wordpress.com/2019/01/IMG_20190101_175338.jpg',
1425
				'alt'  => 'Like a boss.',
1426
				'type' => 'image/jpeg',
1427
			),
1428
			array(
1429
				'url'  => 'https://pentophoto.files.wordpress.com/2020/02/wp-1582952469369.jpg',
1430
				'alt'  => 'Is this really a selfie?',
1431
				'type' => 'image/jpeg',
1432
			),
1433
			array(
1434
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190318_152120.jpg',
1435
				'alt'  => 'Keeping up with pop culture.',
1436
				'type' => 'image/jpeg',
1437
			),
1438
			array(
1439
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1440
				'alt'  => 'Why does the raccoon miss out?! 😢',
1441
				'type' => 'image/jpeg',
1442
			),
1443
		);
1444
1445
		$blocks = array(
1446
			$this->generateListData( "<li>$test_content</li>" ),
1447
			$this->generateImageData( $test_images[0]['url'], $test_images[0]['alt'] ),
1448
			$this->generateGalleryData( array_slice( $test_images, 2, 3 ) ),
1449
			$this->generateImageData( $test_images[1]['url'], $test_images[1]['alt'] ),
1450
		);
1451
1452
		$expected_content = array(
1453
			array(
1454
				'text'  => "- $test_content",
1455
				'media' => array(
1456
					array(
1457
						'url'  => $test_images[0]['url'],
1458
						'alt'  => $test_images[0]['alt'],
1459
						'type' => $test_images[0]['type'],
1460
					),
1461
				),
1462
			),
1463
			array(
1464
				'media' => array_slice( $test_images, 2, 3 ),
1465
			),
1466
			array(
1467
				'media' => array(
1468
					array(
1469
						'url'  => $test_images[1]['url'],
1470
						'alt'  => $test_images[1]['alt'],
1471
						'type' => $test_images[0]['type'],
1472
					),
1473
				),
1474
			),
1475
		);
1476
1477
		$expected_boundaries = array( false, false, false );
1478
1479
		$expected_blocks = array(
1480
			array_slice( $blocks, 0, 2 ),
1481
			array_slice( $blocks, 2, 1 ),
1482
			array_slice( $blocks, 3, 1 ),
1483
		);
1484
1485
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1486
	}
1487
1488
	/**
1489
	 * Test that unsupported content types will not be added.
1490
	 */
1491
	public function test_unsupported_types_are_removed() {
1492
		$test_content = "There's something fake in here....";
1493
		$test_images  = array(
1494
			array(
1495
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1496
				'alt'  => 'This is how we roll.',
1497
				'type' => 'image/jpeg',
1498
			),
1499
			array(
1500
				'url'  => 'https://pentophoto.files.wordpress.com/2019/01/IMG_20190101_175338.jpg',
1501
				'alt'  => 'Like a boss.',
1502
				'type' => 'image/jpeg',
1503
			),
1504
			array(
1505
				'url'  => 'https://pentophoto.files.wordpress.com/2020/02/ms-selfie.docx',
1506
				'alt'  => 'Is this really a selfie?',
1507
				'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1508
			),
1509
			array(
1510
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190318_152120.jpg',
1511
				'alt'  => 'Keeping up with pop culture.',
1512
				'type' => 'image/jpeg',
1513
			),
1514
			array(
1515
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1516
				'alt'  => 'Why does the raccoon miss out?! 😢',
1517
				'type' => 'image/jpeg',
1518
			),
1519
		);
1520
1521
		$blocks = array(
1522
			$this->generateParagraphData( $test_content ),
1523
			$this->generateGalleryData( $test_images ),
1524
		);
1525
1526
		$expected_content = array(
1527
			array(
1528
				'text'  => "$test_content",
1529
				'media' => array(
1530
					$test_images[0],
1531
					$test_images[1],
1532
					$test_images[3],
1533
					$test_images[4],
1534
				),
1535
			),
1536
		);
1537
1538
		$expected_boundaries = array( false );
1539
1540
		$expected_blocks = array( $blocks );
1541
1542
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1543
	}
1544
1545
	/**
1546
	 * Test that an image block will not be appended to the previous tweet when the previous tweet text
1547
	 * takes up too many characters to allow the image to fit inside Twitter's limits.
1548
	 */
1549
	public function test_image_following_long_paragraph_is_not_appended() {
1550
		$test_content = trim( str_repeat( 'That selfie lyfe. ', 15 ) );
1551
		$test_url     = 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg';
1552
		$test_alt     = 'This is how we roll.';
1553
1554
		$blocks = array(
1555
			$this->generateParagraphData( $test_content ),
1556
			$this->generateImageData( $test_url, $test_alt ),
1557
		);
1558
1559
		$expected_content = array(
1560
			$test_content,
1561
			array(
1562
				'media' => array(
1563
					array(
1564
						'url'  => $test_url,
1565
						'alt'  => $test_alt,
1566
						'type' => 'image/jpeg',
1567
					),
1568
				),
1569
			),
1570
		);
1571
1572
		$expected_boundaries = array( false, false );
1573
1574
		$expected_blocks = array(
1575
			array_slice( $blocks, 0, 1 ),
1576
			array_slice( $blocks, 1, 1 ),
1577
		);
1578
1579
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1580
	}
1581
1582
	/**
1583
	 * Test that a gallery block will be appended to the previous tweet.
1584
	 */
1585
	public function test_gallery_is_appended() {
1586
		$test_content = 'That selfie lyfe';
1587
		$test_images  = array(
1588
			array(
1589
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1590
				'alt'  => 'This is how we roll.',
1591
				'type' => 'image/jpeg',
1592
			),
1593
			array(
1594
				'url'  => 'https://pentophoto.files.wordpress.com/2019/01/IMG_20190101_175338.jpg',
1595
				'alt'  => 'Like a boss.',
1596
				'type' => 'image/jpeg',
1597
			),
1598
		);
1599
1600
		$blocks = array(
1601
			$this->generateListData( "<li>$test_content</li>" ),
1602
			$this->generateGalleryData( $test_images ),
1603
		);
1604
1605
		$expected_content = array(
1606
			array(
1607
				'text'  => "- $test_content",
1608
				'media' => $test_images,
1609
			),
1610
		);
1611
1612
		$expected_boundaries = array( false );
1613
1614
		$expected_blocks = array( $blocks );
1615
1616
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1617
	}
1618
1619
	/**
1620
	 * Test that a gallery block with too many images will be trimmed down to 4.
1621
	 */
1622 View Code Duplication
	public function test_long_gallery_is_trimmed() {
1623
		$test_images = array(
1624
			array(
1625
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1626
				'alt'  => 'This is how we roll.',
1627
				'type' => 'image/jpeg',
1628
			),
1629
			array(
1630
				'url'  => 'https://pentophoto.files.wordpress.com/2019/01/IMG_20190101_175338.jpg',
1631
				'alt'  => 'Like a boss.',
1632
				'type' => 'image/jpeg',
1633
			),
1634
			array(
1635
				'url'  => 'https://pentophoto.files.wordpress.com/2020/02/wp-1582952469369.jpg',
1636
				'alt'  => 'Is this really a selfie?',
1637
				'type' => 'image/jpeg',
1638
			),
1639
			array(
1640
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190318_152120.jpg',
1641
				'alt'  => 'Keeping up with pop culture.',
1642
				'type' => 'image/jpeg',
1643
			),
1644
			array(
1645
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1646
				'alt'  => 'Why does the raccoon miss out?! 😢',
1647
				'type' => 'image/jpeg',
1648
			),
1649
		);
1650
1651
		$blocks = array(
1652
			$this->generateGalleryData( $test_images ),
1653
		);
1654
1655
		$expected_content = array(
1656
			array(
1657
				'media' => array_slice( $test_images, 0, 4 ),
1658
			),
1659
		);
1660
1661
		$expected_boundaries = array( false );
1662
1663
		$expected_blocks = array( $blocks );
1664
1665
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1666
	}
1667
1668
	/**
1669
	 * Test that a gallery block with a GIF as the first image is trimmed down to just that GIF.
1670
	 */
1671 View Code Duplication
	public function test_gallery_starting_with_gif_is_trimmed() {
1672
		$test_images = array(
1673
			array(
1674
				'url'  => 'https://jetpackme.files.wordpress.com/2018/10/jetpack-site-accelerator-toggle-gif.gif',
1675
				'alt'  => 'This is probably a GIF.',
1676
				'type' => 'image/gif',
1677
			),
1678
			array(
1679
				'url'  => 'https://pentophoto.files.wordpress.com/2019/01/IMG_20190101_175338.jpg',
1680
				'alt'  => 'Like a boss.',
1681
				'type' => 'image/jpeg',
1682
			),
1683
			array(
1684
				'url'  => 'https://pentophoto.files.wordpress.com/2020/02/wp-1582952469369.jpg',
1685
				'alt'  => 'Is this really a selfie?',
1686
				'type' => 'image/jpeg',
1687
			),
1688
			array(
1689
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190318_152120.jpg',
1690
				'alt'  => 'Keeping up with pop culture.',
1691
				'type' => 'image/jpeg',
1692
			),
1693
			array(
1694
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1695
				'alt'  => 'Why does the raccoon miss out?! 😢',
1696
				'type' => 'image/jpeg',
1697
			),
1698
		);
1699
1700
		$blocks = array(
1701
			$this->generateGalleryData( $test_images ),
1702
		);
1703
1704
		$expected_content = array(
1705
			array(
1706
				'media' => array_slice( $test_images, 0, 1 ),
1707
			),
1708
		);
1709
1710
		$expected_boundaries = array( false );
1711
1712
		$expected_blocks = array( $blocks );
1713
1714
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1715
1716
	}
1717
	/**
1718
	 * Test that a gallery block with a GIF not as the first image has that GIF filtered out.
1719
	 */
1720
	public function test_gallery_with_gif_is_filtered() {
1721
		$test_images = array(
1722
			array(
1723
				'url'  => 'https://pentophoto.files.wordpress.com/2019/01/IMG_20190101_175338.jpg',
1724
				'alt'  => 'Like a boss.',
1725
				'type' => 'image/jpeg',
1726
			),
1727
			array(
1728
				'url'  => 'https://pentophoto.files.wordpress.com/2020/02/wp-1582952469369.jpg',
1729
				'alt'  => 'Is this really a selfie?',
1730
				'type' => 'image/jpeg',
1731
			),
1732
			array(
1733
				'url'  => 'https://jetpackme.files.wordpress.com/2018/10/jetpack-site-accelerator-toggle-gif.gif',
1734
				'alt'  => 'This is probably a GIF.',
1735
				'type' => 'image/gif',
1736
			),
1737
			array(
1738
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190318_152120.jpg',
1739
				'alt'  => 'Keeping up with pop culture.',
1740
				'type' => 'image/jpeg',
1741
			),
1742
			array(
1743
				'url'  => 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg',
1744
				'alt'  => 'Why does the raccoon miss out?! 😢',
1745
				'type' => 'image/jpeg',
1746
			),
1747
		);
1748
1749
		$blocks = array(
1750
			$this->generateGalleryData( $test_images ),
1751
		);
1752
1753
		$expected_content = array(
1754
			array(
1755
				'media' => array_merge( array_slice( $test_images, 0, 2 ), array_slice( $test_images, 3, 2 ) ),
1756
			),
1757
		);
1758
1759
		$expected_boundaries = array( false );
1760
1761
		$expected_blocks = array( $blocks );
1762
1763
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1764
	}
1765
1766
	/**
1767
	 * Test that a video block will be appended to the previous tweet.
1768
	 */
1769
	public function test_video_is_appended() {
1770
		$test_content = 'KITTENS';
1771
		$test_url     = 'https://pentophoto.files.wordpress.com/2012/10/chatty-kitten.mov';
1772
1773
		$blocks = array(
1774
			$this->generateParagraphData( $test_content ),
1775
			$this->generateVideoData( $test_url ),
1776
		);
1777
1778
		$expected_content = array(
1779
			array(
1780
				'text'  => $test_content,
1781
				'media' => array(
1782
					array(
1783
						'url'  => $test_url,
1784
						'type' => 'video/quicktime',
1785
					),
1786
				),
1787
			),
1788
		);
1789
1790
		$this->assertTweetGenerated( $blocks, $expected_content, array( false ), array( $blocks ) );
1791
	}
1792
1793
	/**
1794
	 * Test that a VideoPress video block will be appended to the previous tweet.
1795
	 */
1796
	public function test_videopress_video_is_appended() {
1797
		$test_content  = 'KITTENS';
1798
		$test_guid     = 'mmQ4ecI6';
1799
		$test_filename = 'chatty-kitten_dvd.mp4';
1800
1801
		$blocks = array(
1802
			$this->generateParagraphData( $test_content ),
1803
			$this->generateVideoPressData( $test_guid, $test_filename ),
1804
		);
1805
1806
		$expected_content = array(
1807
			array(
1808
				'text'  => $test_content,
1809
				'media' => array(
1810
					array(
1811
						'url'  => "https://videos.files.wordpress.com/$test_guid/$test_filename",
1812
						'type' => 'video/mp4',
1813
					),
1814
				),
1815
			),
1816
		);
1817
1818
		$this->assertTweetGenerated( $blocks, $expected_content, array( false ), array( $blocks ) );
1819
	}
1820
1821
	/**
1822
	 * Test that a spacer block starts a new tweet.
1823
	 */
1824 View Code Duplication
	public function test_spacer_starts_new_tweet() {
1825
		$test_content = 'This is some content.';
1826
		$blocks       = array(
1827
			$this->generateParagraphData( $test_content ),
1828
			$this->generateSpacerData(),
1829
			$this->generateParagraphData( $test_content ),
1830
			$this->generateParagraphData( $test_content ),
1831
		);
1832
1833
		$this->assertTweetGenerated(
1834
			$blocks,
1835
			array( "$test_content", "$test_content\n\n$test_content" ),
1836
			array( false, false ),
1837
			array( array_slice( $blocks, 0, 2 ), array_slice( $blocks, 2, 2 ) )
1838
		);
1839
	}
1840
1841
	/**
1842
	 * Test that a separator block starts a new tweet.
1843
	 */
1844 View Code Duplication
	public function test_separator_starts_new_tweet() {
1845
		$test_content = 'This is some content.';
1846
		$blocks       = array(
1847
			$this->generateParagraphData( $test_content ),
1848
			$this->generateParagraphData( $test_content ),
1849
			$this->generateSeparatorData(),
1850
			$this->generateParagraphData( $test_content ),
1851
		);
1852
1853
		$this->assertTweetGenerated(
1854
			$blocks,
1855
			array( "$test_content\n\n$test_content", "$test_content" ),
1856
			array( false, false ),
1857
			array( array_slice( $blocks, 0, 3 ), array_slice( $blocks, 3, 1 ) )
1858
		);
1859
	}
1860
1861
	/**
1862
	 * Test that an embedded tweet block will be appended to the previous tweet.
1863
	 */
1864
	public function test_embedded_tweet_is_appended() {
1865
		$test_content = 'As true today as it was then.';
1866
		$test_url     = 'https://twitter.com/GaryPendergast/status/934003415507546112';
1867
1868
		$blocks = array(
1869
			$this->generateParagraphData( $test_content ),
1870
			$this->generateCoreEmbedData( 'twitter', $test_url, false ),
1871
			$this->generateParagraphData( $test_content ),
1872
			$this->generateCoreEmbedData( 'twitter', $test_url, true ),
1873
		);
1874
1875
		$expected_content = array(
1876
			array(
1877
				'text'  => $test_content,
1878
				'tweet' => $test_url,
1879
			),
1880
			array(
1881
				'text'  => $test_content,
1882
				'tweet' => $test_url,
1883
			),
1884
		);
1885
1886
		$expected_boundaries = array( false, false );
1887
1888
		$expected_blocks = array(
1889
			array_slice( $blocks, 0, 2 ),
1890
			array_slice( $blocks, 2, 2 ),
1891
		);
1892
1893
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1894
	}
1895
1896
	/**
1897
	 * Test that other embeds will be appended as URLs.
1898
	 */
1899
	public function test_youtube_embed_is_appended() {
1900
		$test_content = 'The master.';
1901
		$test_url     = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
1902
1903
		$blocks = array(
1904
			$this->generateParagraphData( $test_content ),
1905
			$this->generateCoreEmbedData( 'youtube', $test_url, false ),
1906
			$this->generateParagraphData( $test_content ),
1907
			$this->generateCoreEmbedData( 'youtube', $test_url, true ),
1908
		);
1909
1910
		$expected_content = array(
1911
			array(
1912
				'text' => "$test_content $test_url",
1913
				'urls' => array( $test_url ),
1914
			),
1915
			array(
1916
				'text' => "$test_content $test_url",
1917
				'urls' => array( $test_url ),
1918
			),
1919
		);
1920
1921
		$expected_boundaries = array( false, false );
1922
1923
		$expected_blocks = array(
1924
			array_slice( $blocks, 0, 2 ),
1925
			array_slice( $blocks, 2, 2 ),
1926
		);
1927
1928
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1929
	}
1930
1931
	/**
1932
	 * Test that embeds which would make the tweet to long start a new tweet.
1933
	 */
1934
	public function test_embed_after_long_text_starts_new_tweet() {
1935
		$test_content = trim( str_repeat( 'a. ', 90 ) );
1936
		$test_url     = 'https://jetpack.com';
1937
1938
		$blocks = array(
1939
			$this->generateParagraphData( $test_content ),
1940
			$this->generateCoreEmbedData( 'wordpress', $test_url, false ),
1941
			$this->generateParagraphData( $test_content ),
1942
			$this->generateCoreEmbedData( 'wordpress', $test_url, true ),
1943
		);
1944
1945
		$expected_content = array(
1946
			array(
1947
				'text' => $test_content,
1948
			),
1949
			array(
1950
				'text' => $test_url,
1951
				'urls' => array( $test_url ),
1952
			),
1953
			array(
1954
				'text' => $test_content,
1955
			),
1956
			array(
1957
				'text' => $test_url,
1958
				'urls' => array( $test_url ),
1959
			),
1960
		);
1961
1962
		$expected_boundaries = array( false, false, false, false );
1963
1964
		$expected_blocks = array(
1965
			array( $blocks[0] ),
1966
			array( $blocks[1] ),
1967
			array( $blocks[2] ),
1968
			array( $blocks[3] ),
1969
		);
1970
1971
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
1972
	}
1973
1974
	/**
1975
	 * Test that Jetpack GIF embeds will be appended as URLs, with the URL re-written correctly.
1976
	 */
1977
	public function test_jetpack_gif_is_appended() {
1978
		$test_url     = 'https://giphy.com/embed/jTqfCm1C0BV5fFAYvT';
1979
		$expected_url = 'https://giphy.com/gifs/jTqfCm1C0BV5fFAYvT';
1980
1981
		$blocks = array(
1982
			$this->generateJetpackGifData( $test_url ),
1983
		);
1984
1985
		$expected_content = array(
1986
			array(
1987
				'text' => $expected_url,
1988
				'urls' => array( $expected_url ),
1989
			),
1990
		);
1991
1992
		$this->assertTweetGenerated( $blocks, $expected_content, array( false ), array( $blocks ) );
1993
	}
1994
1995
	/**
1996
	 * Test that embeds will start a new tweet when the previous tweet already has URLs in it.
1997
	 */
1998
	public function test_embeds_start_new_tweet_after_links() {
1999
		$test_url     = 'https://pento.net';
2000
		$test_content = "The <a href='$test_url'>joker</a>.";
2001
		$test_embed   = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
2002
2003
		$blocks = array(
2004
			$this->generateParagraphData( $test_content ),
2005
			$this->generateCoreEmbedData( 'youtube', $test_embed, false ),
2006
			$this->generateParagraphData( $test_content ),
2007
			$this->generateCoreEmbedData( 'youtube', $test_embed, true ),
2008
		);
2009
2010
		$expected_content = array(
2011
			array(
2012
				'text' => "The joker ($test_url).",
2013
				'urls' => array( $test_url ),
2014
			),
2015
			array(
2016
				'text' => $test_embed,
2017
				'urls' => array( $test_embed ),
2018
			),
2019
			array(
2020
				'text' => "The joker ($test_url).",
2021
				'urls' => array( $test_url ),
2022
			),
2023
			array(
2024
				'text' => $test_embed,
2025
				'urls' => array( $test_embed ),
2026
			),
2027
		);
2028
2029
		$expected_boundaries = array( false, false, false, false );
2030
2031
		$expected_blocks = array(
2032
			array( $blocks[0] ),
2033
			array( $blocks[1] ),
2034
			array( $blocks[2] ),
2035
			array( $blocks[3] ),
2036
		);
2037
2038
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
2039
	}
2040
2041
	/**
2042
	 * Test that link URLs are added to the tweet text.
2043
	 */
2044 View Code Duplication
	public function test_links_handled() {
2045
		$test_urls = array(
2046
			'https://jetpack.com',
2047
			'https://WordPress.org/',
2048
			'https://jetpack.com',
2049
		);
2050
2051
		$test_content  = "This <a href='$test_urls[0]'>is</a> <a href='$test_urls[1]'>a</a> <a href='$test_urls[2]'>test</a>.";
2052
		$expected_text = "This is ($test_urls[0]) a ($test_urls[1]) test ($test_urls[2]).";
2053
2054
		$blocks = array(
2055
			$this->generateParagraphData( $test_content ),
2056
		);
2057
2058
		$expected_content = array(
2059
			array(
2060
				'text' => $expected_text,
2061
				'urls' => $test_urls,
2062
			),
2063
		);
2064
2065
		$this->assertTweetGenerated( $blocks, $expected_content, array( false ), array( $blocks ) );
2066
	}
2067
2068
	/**
2069
	 * Test that unsupported URL formats in links are ignored.
2070
	 */
2071
	public function test_invalid_links_ignored() {
2072
		$test_urls = array(
2073
			'aaa' => 'mailto:[email protected]',
2074
			'bbb' => 'ftp://foo.com',
2075
		);
2076
2077
		$test_content = implode(
2078
			' ',
2079
			array_map(
2080
				function ( $text, $url ) {
2081
					return "<a href='$url'>$text</a>";
2082
				},
2083
				array_keys( $test_urls ),
2084
				array_values( $test_urls )
2085
			)
2086
		);
2087
2088
		$expected_text = trim( implode( ' ', array_keys( $test_urls ) ) );
2089
2090
		$blocks = array(
2091
			$this->generateParagraphData( $test_content ),
2092
		);
2093
2094
		$expected_content = array(
2095
			array(
2096
				'text' => $expected_text,
2097
			),
2098
		);
2099
2100
		$this->assertTweetGenerated( $blocks, $expected_content, array( false ), array( $blocks ) );
2101
	}
2102
2103
	/**
2104
	 * Test that long URLs don't cause text to break into multiple tweets.
2105
	 */
2106 View Code Duplication
	public function test_long_links_dont_break_a_paragraph_up() {
2107
		$test_url = 'https://jetpack.com/' . str_repeat( 'a', 280 );
2108
2109
		$test_content  = "It's <a href='$test_url'>a celebration</a>!";
2110
		$expected_text = "It's a celebration ($test_url)!";
2111
2112
		$blocks = array(
2113
			$this->generateParagraphData( $test_content ),
2114
		);
2115
2116
		$expected_content = array(
2117
			array(
2118
				'text' => $expected_text,
2119
				'urls' => array( $test_url ),
2120
			),
2121
		);
2122
2123
		$this->assertTweetGenerated( $blocks, $expected_content, array( false ), array( $blocks ) );
2124
	}
2125
2126
	/**
2127
	 * Test that URLs appearing before and after paragraph breaks are counted correctly.
2128
	 */
2129
	public function test_many_urls_in_a_long_paragraph() {
2130
		$test_url = 'https://jetpack.com/';
2131
2132
		$test_content  = "This is <a href='$test_url'>some text</a> for testing. ";
2133
		$expected_text = "This is some text ($test_url) for testing. ";
2134
2135
		$blocks = array(
2136
			$this->generateParagraphData( trim( str_repeat( $test_content, 9 ) ) ),
2137
		);
2138
2139
		$expected_content = array(
2140
			array(
2141
				'text' => trim( str_repeat( $expected_text, 4 ) ),
2142
				'urls' => array_fill( 0, 4, $test_url ),
2143
			),
2144
			array(
2145
				'text' => trim( str_repeat( $expected_text, 4 ) ),
2146
				'urls' => array_fill( 0, 4, $test_url ),
2147
			),
2148
			array(
2149
				'text' => trim( $expected_text ),
2150
				'urls' => array( $test_url ),
2151
			),
2152
		);
2153
2154
		$expected_boundaries = array(
2155
			$this->generateNormalBoundary( 123, 124, 'content' ),
2156
			$this->generateNormalBoundary( 247, 248, 'content' ),
2157
			false,
2158
		);
2159
2160
		$expected_blocks = array_fill( 0, 3, $blocks );
2161
2162
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
2163
	}
2164
2165
	/**
2166
	 * Test that URLs appearing in long and varied lists are counted correctly.
2167
	 */
2168
	public function test_many_urls_in_different_list_items() {
2169
		$test_url = 'https://jetpack.com/';
2170
2171
		$test_content  = "This is <a href='$test_url'>some text</a> for testing. ";
2172
		$expected_text = "This is some text ($test_url) for testing. ";
2173
2174
		$blocks = array(
2175
			$this->generateListData(
2176
				'<li>' . trim( str_repeat( $test_content, 7 ) ) . '</li>' .
2177
				'<li></li>' .
2178
				'<li>' . trim( $test_content ) . '</li>' .
2179
				'<li><ul><li></li>' .
2180
				'<li>' . trim( $test_content ) . '</li>' .
2181
				'<li></li></ul>' .
2182
				'<li></li>' .
2183
				'<li>' . trim( str_repeat( $test_content, 9 ) ) . '</li>'
2184
			),
2185
		);
2186
2187
		$expected_content = array(
2188
			array(
2189
				'text' => '- ' . trim( str_repeat( $expected_text, 4 ) ),
2190
				'urls' => array_fill( 0, 4, $test_url ),
2191
			),
2192
			array(
2193
				'text' => trim( str_repeat( $expected_text, 3 ) ) . "\n- " . trim( $expected_text ),
2194
				'urls' => array_fill( 0, 4, $test_url ),
2195
			),
2196
			array(
2197
				'text' => trim( "- $expected_text" ),
2198
				'urls' => array( $test_url ),
2199
			),
2200
			array(
2201
				'text' => '- ' . trim( str_repeat( $expected_text, 4 ) ),
2202
				'urls' => array_fill( 0, 4, $test_url ),
2203
			),
2204
			array(
2205
				'text' => trim( str_repeat( $expected_text, 4 ) ),
2206
				'urls' => array_fill( 0, 4, $test_url ),
2207
			),
2208
			array(
2209
				'text' => trim( $expected_text ),
2210
				'urls' => array( $test_url ),
2211
			),
2212
		);
2213
2214
		$expected_boundaries = array(
2215
			$this->generateNormalBoundary( 123, 124, 'values' ),
2216
			$this->generateLineBoundary( 2, 'values' ),
2217
			$this->generateLineBoundary( 5, 'values' ),
2218
			$this->generateNormalBoundary( 407, 408, 'values' ),
2219
			$this->generateNormalBoundary( 531, 532, 'values' ),
2220
			false,
2221
		);
2222
2223
		$expected_blocks = array_fill( 0, 6, $blocks );
2224
2225
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
2226
	}
2227
2228
	/**
2229
	 * Test that a tweet that's nearly filled with miscellaneous characters that count for 2 characters
2230
	 * will cause an image block to start a new tweet.
2231
	 */
2232
	public function test_nearly_full_tweet_followed_by_image() {
2233
		$test_content = str_repeat( '†', 135 );
2234
		$test_url     = 'https://pentophoto.files.wordpress.com/2019/03/mvimg_20190317_1915122.jpg';
2235
		$test_alt     = 'This is how we roll.';
2236
2237
		$blocks = array(
2238
			$this->generateParagraphData( $test_content ),
2239
			$this->generateImageData( $test_url, $test_alt ),
2240
		);
2241
2242
		$expected_content = array(
2243
			array(
2244
				'text' => $test_content,
2245
			),
2246
			array(
2247
				'media' => array(
2248
					array(
2249
						'url'  => $test_url,
2250
						'alt'  => $test_alt,
2251
						'type' => 'image/jpeg',
2252
					),
2253
				),
2254
			),
2255
		);
2256
2257
		$expected_boundaries = array( false, false );
2258
2259
		$expected_blocks = array(
2260
			array( $blocks[0] ),
2261
			array( $blocks[1] ),
2262
		);
2263
2264
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
2265
	}
2266
2267
	/**
2268
	 * All URLs count for 24 characters, regardless of their actual length. We need to ensure URLs
2269
	 * that have been typed into text (but aren't necessarily linked) are counted correctly.
2270
	 */
2271
	public function test_text_urls_are_counted_correctly() {
2272
		$test_content = 'https://jetpack.com https://Jetpack.com jetpack.com ';
2273
2274
		$blocks = array(
2275
			$this->generateParagraphData( trim( str_repeat( $test_content, 4 ) ) ),
2276
		);
2277
2278
		$expected_content = array(
2279
			array(
2280
				'text' => trim( str_repeat( $test_content, 3 ) ) . ' https://jetpack.com https://Jetpack.com…',
2281
				'urls' => array_merge(
2282
					array_merge( ...array_fill( 0, 3, array( 'https://jetpack.com', 'https://Jetpack.com', 'jetpack.com' ) ) ),
2283
					array( 'https://jetpack.com', 'https://Jetpack.com' )
2284
				),
2285
			),
2286
			array(
2287
				'text' => '…jetpack.com',
2288
				'urls' => array( 'jetpack.com' ),
2289
			),
2290
		);
2291
2292
		$expected_boundaries = array(
2293
			$this->generateNormalBoundary( 195, 196, 'content' ),
2294
			false,
2295
		);
2296
2297
		$expected_blocks = array(
2298
			$blocks,
2299
			$blocks,
2300
		);
2301
2302
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
2303
	}
2304
2305
	/**
2306
	 * If the text of the link is the same as the href, we should only include one in the tweet.
2307
	 */
2308 View Code Duplication
	public function test_text_urls_inside_links_are_deduplicated() {
2309
		$test_urls = array(
2310
			'https://jetpack.com',
2311
			'https://wordpress.org/',
2312
		);
2313
2314
		$test_content = "Visiting <a href='$test_urls[0]'>$test_urls[0]</a> is good, so is visiting <a href='$test_urls[1]'>WordPress.org</a>.";
2315
2316
		$blocks = array(
2317
			$this->generateParagraphData( $test_content ),
2318
		);
2319
2320
		$expected_content = array(
2321
			array(
2322
				'text' => "Visiting $test_urls[0] is good, so is visiting WordPress.org.",
2323
				'urls' => array(
2324
					$test_urls[0],
2325
					'WordPress.org',
2326
				),
2327
			),
2328
		);
2329
2330
		$expected_boundaries = array(
2331
			false,
2332
		);
2333
2334
		$expected_blocks = array(
2335
			$blocks,
2336
		);
2337
2338
		$this->assertTweetGenerated( $blocks, $expected_content, $expected_boundaries, $expected_blocks );
2339
	}
2340
2341
	/**
2342
	 * Test that a single Twitter card generates correctly.
2343
	 */
2344
	public function test_generating_twitter_card() {
2345
		$urls = array( 'https://publicizetests.wpsandbox.me/2015/03/26/hello-world/' );
2346
2347
		$expected = array(
2348
			'https://publicizetests.wpsandbox.me/2015/03/26/hello-world/' => array(
2349
				'creator'     => '@wpcomrestapi',
2350
				'description' => 'Kindly do not delete this post or modify it',
2351
				'image'       => 'https://i2.wp.com/publicizetests.wpsandbox.me/wp-content/uploads/2015/05/keep-calm-its-almost-party-time.png?fit=600%2C700&ssl=1&w=640',
2352
				'title'       => 'Hello world!',
2353
				'type'        => 'summary_large_image',
2354
			),
2355
		);
2356
2357
		$cards = Jetpack_Tweetstorm_Helper::generate_cards( $urls );
2358
2359
		$this->assertEqualSetsWithIndex( $expected, $cards );
2360
	}
2361
2362
	/**
2363
	 * Test that multiple cards generate correctly, and are returned attached to the correct URL.
2364
	 */
2365
	public function test_generating_multiple_twitter_cards() {
2366
		$urls = array(
2367
			'https://publicizetests.wpsandbox.me/2015/05/16/contributor-test/',
2368
			'https://publicizetests.wpsandbox.me/2015/06/29/unsupported-shortcodes-test/',
2369
			'https://publicizetests.wpsandbox.me/',
2370
		);
2371
2372
		$expected = array(
2373
			'https://publicizetests.wpsandbox.me/2015/05/16/contributor-test/' => array(
2374
				'description' => 'Post Written By Contributor.',
2375
				'image'       => 'https://s0.wp.com/i/blank.jpg',
2376
				'title'       => 'Contributor test',
2377
				'type'        => 'summary',
2378
			),
2379
			'https://publicizetests.wpsandbox.me/2015/06/29/unsupported-shortcodes-test/' => array(
2380
				'creator'     => '@wpcomrestapi',
2381
				'description' => '[:en]English: It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-le…',
2382
				'image'       => 'https://s0.wp.com/i/blank.jpg',
2383
				'title'       => 'Unsupported Shortcodes test',
2384
				'type'        => 'summary',
2385
			),
2386
			'https://publicizetests.wpsandbox.me/' => array(
2387
				'creator'     => '@wpcomrestapi',
2388
				'description' => 'Just another WordPress site',
2389
				'image'       => 'https://s0.wp.com/i/blank.jpg',
2390
				'title'       => 'Publicize Jetpack',
2391
			),
2392
		);
2393
2394
		$cards = Jetpack_Tweetstorm_Helper::generate_cards( $urls );
2395
2396
		$this->assertEqualSetsWithIndex( $expected, $cards );
2397
	}
2398
2399
	/**
2400
	 * Test that a site that doesn't contain Twitter card data won't produce a card.
2401
	 */
2402
	public function test_site_with_no_twitter_card() {
2403
		$urls = array( 'https://www.google.com/' );
2404
2405
		$expected = array(
2406
			'https://www.google.com/' => array(
2407
				'error' => 'no_og_data',
2408
			),
2409
		);
2410
2411
		$cards = Jetpack_Tweetstorm_Helper::generate_cards( $urls );
2412
2413
		$this->assertEqualSetsWithIndex( $expected, $cards );
2414
	}
2415
2416
	/**
2417
	 * Test that a URL which redirects will still get the Twitter card.
2418
	 *
2419
	 * @group external-http
2420
	 */
2421 View Code Duplication
	public function test_twitter_card_with_redirect() {
2422
		$urls = array(
2423
			'https://jetpack.me/',
2424
			'https://jetpack.com/',
2425
		);
2426
2427
		$expected = array(
2428
			'https://jetpack.me/'  => array(
2429
				'description' => 'The ultimate WordPress plugin for security, backups, malware scan, anti-spam, CDN, site search, CRM, Stripe, Facebook, & Instagram',
2430
				'image'       => 'https://jetpackme.files.wordpress.com/2018/04/cropped-jetpack-favicon-2018.png?w=240',
2431
				'title'       => 'Jetpack',
2432
				'type'        => 'summary',
2433
			),
2434
			'https://jetpack.com/' => array(
2435
				'description' => 'The ultimate WordPress plugin for security, backups, malware scan, anti-spam, CDN, site search, CRM, Stripe, Facebook, & Instagram',
2436
				'image'       => 'https://jetpackme.files.wordpress.com/2018/04/cropped-jetpack-favicon-2018.png?w=240',
2437
				'title'       => 'Jetpack',
2438
				'type'        => 'summary',
2439
			),
2440
		);
2441
2442
		$cards = Jetpack_Tweetstorm_Helper::generate_cards( $urls );
2443
2444
		$this->assertEqualSetsWithIndex( $expected, $cards );
2445
	}
2446
2447
	/**
2448
	 * Test that the return data is keyed by the URLs passed.
2449
	 *
2450
	 * @group external-http
2451
	 */
2452 View Code Duplication
	public function test_twitter_cards_with_odd_URLs() {
2453
		$urls = array(
2454
			'https://Jetpack.com/',
2455
			'https://jetpack.com',
2456
			'jetpack.com/',
2457
			'JETPACK.com',
2458
		);
2459
2460
		$expected = array(
2461
			'https://Jetpack.com/' => array(
2462
				'description' => 'The ultimate WordPress plugin for security, backups, malware scan, anti-spam, CDN, site search, CRM, Stripe, Facebook, & Instagram',
2463
				'image'       => 'https://jetpackme.files.wordpress.com/2018/04/cropped-jetpack-favicon-2018.png?w=240',
2464
				'title'       => 'Jetpack',
2465
				'type'        => 'summary',
2466
			),
2467
			'https://jetpack.com'  => array(
2468
				'description' => 'The ultimate WordPress plugin for security, backups, malware scan, anti-spam, CDN, site search, CRM, Stripe, Facebook, & Instagram',
2469
				'image'       => 'https://jetpackme.files.wordpress.com/2018/04/cropped-jetpack-favicon-2018.png?w=240',
2470
				'title'       => 'Jetpack',
2471
				'type'        => 'summary',
2472
			),
2473
		);
2474
2475
		$cards = Jetpack_Tweetstorm_Helper::generate_cards( $urls );
2476
2477
		$this->assertEqualSetsWithIndex( $expected, $cards );
2478
	}
2479
}
2480