WP_Test_Jetpack_Tweetstorm_Helper   F
last analyzed

Complexity

Total Complexity 87

Size/Duplication

Total Lines 2452
Duplicated Lines 24.59 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 603
loc 2452
rs 0.8
c 0
b 0
f 0
wmc 87
lcom 1
cbo 1

82 Methods

Rating   Name   Duplication   Size   Complexity  
A setUpBeforeClass() 0 6 1
A tearDownAfterClass() 0 6 1
A generateParagraphData() 0 13 1
A generateHeadingData() 12 12 1
A generateVerseData() 12 12 1
A generateQuoteData() 0 17 1
A generateListData() 12 12 1
A generateImageData() 13 13 1
A generateVideoData() 12 12 1
A generateVideoPressData() 0 17 1
A generateGalleryData() 0 18 1
A generateSpacerData() 10 10 1
A generateSeparatorData() 10 10 1
A generateCoreEmbedData() 0 17 1
A generateJetpackGifData() 14 14 1
A generateNormalBoundary() 0 8 1
A generateLineBreakBoundary() 0 8 1
A generateLineBoundary() 0 7 1
A assertTweetContains() 0 39 4
A assertTweetGenerated() 6 35 3
A test_no_blocks_no_tweets() 0 3 1
A test_no_content_no_tweets() 0 11 1
A test_unsupported_block_no_tweets() 0 16 1
A test_no_innerhtml_no_tweets() 0 15 1
A test_single_paragraph() 8 8 1
A test_multiple_paragraphs() 0 15 1
A test_single_long_paragraph() 0 13 1
A test_single_long_paragraph_with_line_breaks() 0 13 1
A test_line_break_is_preserved() 13 13 1
A test_single_long_multibyte_paragraph() 0 31 1
A test_long_paragraph_followed_by_short_paragraph() 0 29 1
A test_short_paragraph_followed_by_long_paragraph() 0 32 1
A test_long_sentence() 28 28 1
A test_long_sentence_with_nbsp() 0 29 1
A test_long_sentence_followed_by_short_sentences() 0 32 1
A test_short_sentence_followed_by_a_long_sentence() 0 32 1
A test_short_paragraph_followed_by_long_sentence() 34 34 1
A test_basic_verse() 13 13 1
A test_long_verse() 0 25 1
A test_long_list() 29 29 1
A test_short_paragraph_followed_by_long_list() 33 33 1
A test_emoji_count_as_two_characters() 29 29 1
A test_inline_images_dont_show_in_tweets() 0 22 1
A test_inline_images_are_counted_for_boundaries() 0 45 1
A test_short_list_items_followed_by_long_list_item() 0 37 1
A test_blank_list_items() 14 14 1
A test_simple_quote() 0 15 1
A test_long_quote() 26 26 1
A test_multi_paragraph_quote() 0 26 1
A test_quote_attribution_sentence_splits() 26 26 1
A test_heading() 0 31 1
A test_image_is_appended() 0 25 1
A test_long_alt_is_removed() 0 22 1
B test_gallery_and_second_image_are_not_appended() 0 72 1
A test_unsupported_types_are_removed() 0 53 1
A test_image_following_long_paragraph_is_not_appended() 0 32 1
A test_gallery_is_appended() 0 33 1
A test_long_gallery_is_trimmed() 45 45 1
A test_gallery_starting_with_gif_is_trimmed() 46 46 1
A test_gallery_with_gif_is_filtered() 0 45 1
A test_video_is_appended() 0 23 1
A test_videopress_video_is_appended() 0 24 1
A test_spacer_starts_new_tweet() 16 16 1
A test_separator_starts_new_tweet() 16 16 1
A test_embedded_tweet_is_appended() 0 31 1
A test_youtube_embed_is_appended() 0 31 1
A test_embed_after_long_text_starts_new_tweet() 0 39 1
A test_jetpack_gif_is_appended() 0 17 1
A test_embeds_start_new_tweet_after_links() 0 42 1
A test_links_handled() 23 23 1
A test_invalid_links_ignored() 0 31 1
A test_long_links_dont_break_a_paragraph_up() 19 19 1
A test_many_urls_in_a_long_paragraph() 0 35 1
B test_many_urls_in_different_list_items() 0 59 1
A test_nearly_full_tweet_followed_by_image() 0 34 1
A test_text_urls_are_counted_correctly() 0 33 1
A test_text_urls_inside_links_are_deduplicated() 32 32 1
A test_generating_twitter_card() 0 17 1
A test_generating_multiple_twitter_cards() 0 33 1
A test_site_with_no_twitter_card() 0 13 1
A test_twitter_card_with_redirect() 25 25 1
A test_twitter_cards_with_odd_URLs() 27 27 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WP_Test_Jetpack_Tweetstorm_Helper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WP_Test_Jetpack_Tweetstorm_Helper, and based on these observations, apply Extract Interface, too.

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