Version 1
[yaffs-website] / web / core / tests / Drupal / Tests / Core / EventSubscriber / ActiveLinkResponseFilterTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\EventSubscriber;
4
5 use Drupal\Component\Serialization\Json;
6 use Drupal\Core\EventSubscriber\ActiveLinkResponseFilter;
7 use Drupal\Core\Template\Attribute;
8 use Drupal\Tests\UnitTestCase;
9
10 /**
11  * @coversDefaultClass \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
12  * @group EventSubscriber
13  */
14 class ActiveLinkResponseFilterTest extends UnitTestCase {
15
16   /**
17    * Provides test data for testSetLinkActiveClass().
18    *
19    * @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter::setLinkActiveClass()
20    */
21   public function providerTestSetLinkActiveClass() {
22     // Define all the variations that *don't* affect whether or not an
23     // "is-active" class is set, but that should remain unchanged:
24     // - surrounding HTML
25     // - tags for which to test the setting of the "is-active" class
26     // - content of said tags
27     $edge_case_html5 = '<audio src="foo.ogg">
28   <track kind="captions" src="foo.en.vtt" srclang="en" label="English">
29   <track kind="captions" src="foo.sv.vtt" srclang="sv" label="Svenska">
30 </audio>';
31     $html = [
32       // Simple HTML.
33       0 => ['prefix' => '<div><p>', 'suffix' => '</p></div>'],
34       // Tricky HTML5 example that's unsupported by PHP <=5.4's DOMDocument:
35       // https://www.drupal.org/comment/7938201#comment-7938201.
36       1 => ['prefix' => '<div><p>', 'suffix' => '</p>' . $edge_case_html5 . '</div>'],
37       // Multi-byte content *before* the HTML that needs the "is-active" class.
38       2 => ['prefix' => '<div><p>αβγδεζηθικλμνξοσὠ</p><p>', 'suffix' => '</p></div>'],
39     ];
40     $tags = [
41       // Of course, it must work on anchors.
42       'a',
43       // Unfortunately, it must also work on list items.
44       'li',
45       // … and therefor, on *any* tag, really.
46       'foo',
47     ];
48     $contents = [
49       // Regular content.
50       'test',
51       // Mix of UTF-8 and HTML entities, both must be retained.
52       '☆ 3 × 4 = €12 and 4 &times; 3 = &euro;12 &#9734',
53       // Multi-byte content.
54       'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ',
55       // Text that closely approximates an important attribute, but should be
56       // ignored.
57       'data-drupal-link-system-path=&quot;&lt;front&gt;&quot;',
58     ];
59
60     // Define all variations that *do* affect whether or not an "is-active"
61     // class is set: all possible situations that can be encountered.
62     $situations = [];
63
64     // Situations with context: front page, English, no query.
65     $context = [
66       'path' => 'myfrontpage',
67       'front' => TRUE,
68       'language' => 'en',
69       'query' => [],
70     ];
71     // Nothing to do.
72     $markup = '<foo>bar</foo>';
73     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => []];
74     // Matching path, plus all matching variations.
75     $attributes = [
76       'data-drupal-link-system-path' => 'myfrontpage',
77     ];
78     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes];
79     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes + ['hreflang' => 'en']];
80     // Matching path, plus all non-matching variations.
81     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl']];
82     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => '{"foo":"bar"}']];
83     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => ""]];
84     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => TRUE]];
85     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => '{"foo":"bar"}']];
86     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => ""]];
87     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => TRUE]];
88     // Special matching path, plus all variations.
89     $attributes = [
90       'data-drupal-link-system-path' => '<front>',
91     ];
92     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes];
93     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes + ['hreflang' => 'en']];
94     // Special matching path, plus all non-matching variations.
95     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl']];
96     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => '{"foo":"bar"}']];
97     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => ""]];
98     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => TRUE]];
99     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => '{"foo":"bar"}']];
100     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => ""]];
101     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => TRUE]];
102
103     // Situations with context: non-front page, Dutch, no query.
104     $context = [
105       'path' => 'llama',
106       'front' => FALSE,
107       'language' => 'nl',
108       'query' => [],
109     ];
110     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => []];
111     // Matching path, plus all matching variations.
112     $attributes = [
113       'data-drupal-link-system-path' => 'llama',
114     ];
115     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes];
116     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes + ['hreflang' => 'nl']];
117     // Matching path, plus all non-matching variations.
118     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en']];
119     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => '{"foo":"bar"}']];
120     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => ""]];
121     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => TRUE]];
122     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => '{"foo":"bar"}']];
123     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => ""]];
124     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => TRUE]];
125     // Special non-matching path, plus all variations.
126     $attributes = [
127       'data-drupal-link-system-path' => '<front>',
128     ];
129     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes];
130     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en']];
131     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => '{"foo":"bar"}']];
132     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => ""]];
133     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => TRUE]];
134     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => '{"foo":"bar"}']];
135     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => ""]];
136     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => TRUE]];
137
138     // Situations with context: non-front page, Dutch, with query.
139     $context = [
140       'path' => 'llama',
141       'front' => FALSE,
142       'language' => 'nl',
143       'query' => ['foo' => 'bar'],
144     ];
145     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => []];
146     // Matching path, plus all matching variations.
147     $attributes = [
148       'data-drupal-link-system-path' => 'llama',
149       'data-drupal-link-query' => Json::encode(['foo' => 'bar']),
150     ];
151     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes];
152     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes + ['hreflang' => 'nl']];
153     // Matching path, plus all non-matching variations.
154     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en']];
155     unset($attributes['data-drupal-link-query']);
156     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => ""]];
157     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => TRUE]];
158     // Special non-matching path, plus all variations.
159     $attributes = [
160       'data-drupal-link-system-path' => '<front>',
161     ];
162     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes];
163     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl']];
164     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en']];
165     unset($attributes['data-drupal-link-query']);
166     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => ""]];
167     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => TRUE]];
168
169     // Situations with context: non-front page, Dutch, with query.
170     $context = [
171       'path' => 'llama',
172       'front' => FALSE,
173       'language' => 'nl',
174       'query' => ['foo' => 'bar'],
175     ];
176     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => []];
177     // Matching path, plus all matching variations.
178     $attributes = [
179       'data-drupal-link-system-path' => 'llama',
180       'data-drupal-link-query' => Json::encode(['foo' => 'bar']),
181     ];
182     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes];
183     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes + ['hreflang' => 'nl']];
184     // Matching path, plus all non-matching variations.
185     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en']];
186     unset($attributes['data-drupal-link-query']);
187     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => ""]];
188     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => TRUE]];
189     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => ""]];
190     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => TRUE]];
191     // Special non-matching path, plus all variations.
192     $attributes = [
193       'data-drupal-link-system-path' => '<front>',
194     ];
195     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes];
196     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl']];
197     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en']];
198     unset($attributes['data-drupal-link-query']);
199     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => ""]];
200     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => TRUE]];
201     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => ""]];
202     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl', 'data-drupal-link-query' => TRUE]];
203
204     // Situations with context: front page, English, query.
205     $context = [
206       'path' => 'myfrontpage',
207       'front' => TRUE,
208       'language' => 'en',
209       'query' => ['foo' => 'bar'],
210     ];
211     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => []];
212     // Matching path, plus all matching variations.
213     $attributes = [
214       'data-drupal-link-system-path' => 'myfrontpage',
215       'data-drupal-link-query' => Json::encode(['foo' => 'bar']),
216     ];
217     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes];
218     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes + ['hreflang' => 'en']];
219     // Matching path, plus all non-matching variations.
220     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl']];
221     unset($attributes['data-drupal-link-query']);
222     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => ""]];
223     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => TRUE]];
224     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => ""]];
225     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => TRUE]];
226     // Special matching path, plus all variations.
227     $attributes = [
228       'data-drupal-link-system-path' => '<front>',
229       'data-drupal-link-query' => Json::encode(['foo' => 'bar']),
230     ];
231     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes];
232     $situations[] = ['context' => $context, 'is active' => TRUE, 'attributes' => $attributes + ['hreflang' => 'en']];
233     // Special matching path, plus all non-matching variations.
234     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'nl']];
235     unset($attributes['data-drupal-link-query']);
236     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => ""]];
237     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['data-drupal-link-query' => TRUE]];
238     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => ""]];
239     $situations[] = ['context' => $context, 'is active' => FALSE, 'attributes' => $attributes + ['hreflang' => 'en', 'data-drupal-link-query' => TRUE]];
240
241     // Loop over the surrounding HTML variations.
242     $data = [];
243     for ($h = 0; $h < count($html); $h++) {
244       $html_prefix = $html[$h]['prefix'];
245       $html_suffix = $html[$h]['suffix'];
246       // Loop over the tag variations.
247       for ($t = 0; $t < count($tags); $t++) {
248         $tag = $tags[$t];
249         // Loop over the tag contents variations.
250         for ($c = 0; $c < count($contents); $c++) {
251           $tag_content = $contents[$c];
252
253           $create_markup = function (Attribute $attributes) use ($html_prefix, $html_suffix, $tag, $tag_content) {
254             return $html_prefix . '<' . $tag . $attributes . '>' . $tag_content . '</' . $tag . '>' . $html_suffix;
255           };
256
257           // Loop over the situations.
258           for ($s = 0; $s < count($situations); $s++) {
259             $situation = $situations[$s];
260
261             // Build the source markup.
262             $source_markup = $create_markup(new Attribute($situation['attributes']));
263
264             // Build the target markup. If no "is-active" class should be set,
265             // the resulting HTML should be identical. Otherwise, it should get
266             // an "is-active" class, either by extending an existing "class"
267             // attribute or by adding a "class" attribute.
268             $target_markup = NULL;
269             if (!$situation['is active']) {
270               $target_markup = $source_markup;
271             }
272             else {
273               $active_attributes = $situation['attributes'];
274               if (!isset($active_attributes['class'])) {
275                 $active_attributes['class'] = [];
276               }
277               $active_attributes['class'][] = 'is-active';
278               $target_markup = $create_markup(new Attribute($active_attributes));
279             }
280
281             $data[] = [$source_markup, $situation['context']['path'], $situation['context']['front'], $situation['context']['language'], $situation['context']['query'], $target_markup];
282           }
283         }
284       }
285     }
286
287     // Test case to verify that the 'is-active' class is not added multiple
288     // times.
289     $data[] = [
290       0 => '<a data-drupal-link-system-path="&lt;front&gt;">Once</a> <a data-drupal-link-system-path="&lt;front&gt;">Twice</a>',
291       1 => '',
292       2 => TRUE,
293       3 => 'en',
294       4 => [],
295       5 => '<a data-drupal-link-system-path="&lt;front&gt;" class="is-active">Once</a> <a data-drupal-link-system-path="&lt;front&gt;" class="is-active">Twice</a>',
296     ];
297
298     // Test cases to verify that the 'is-active' class is added when on the
299     // front page, and there are two different kinds of matching links on the
300     // page:
301     // - the matching path (the resolved front page path)
302     // - the special matching path ('<front>')
303     $front_special_link = '<a data-drupal-link-system-path="&lt;front&gt;">Front</a>';
304     $front_special_link_active = '<a data-drupal-link-system-path="&lt;front&gt;" class="is-active">Front</a>';
305     $front_path_link = '<a data-drupal-link-system-path="myfrontpage">Front Path</a>';
306     $front_path_link_active = '<a data-drupal-link-system-path="myfrontpage" class="is-active">Front Path</a>';
307     $data[] = [
308       0 => $front_path_link . ' ' . $front_special_link,
309       1 => 'myfrontpage',
310       2 => TRUE,
311       3 => 'en',
312       4 => [],
313       5 => $front_path_link_active . ' ' . $front_special_link_active,
314     ];
315     $data[] = [
316       0 => $front_special_link . ' ' . $front_path_link,
317       1 => 'myfrontpage',
318       2 => TRUE,
319       3 => 'en',
320       4 => [],
321       5 => $front_special_link_active . ' ' . $front_path_link_active,
322     ];
323
324     // Test cases to verify that links to the front page do not get the
325     // 'is-active' class when not on the front page.
326     $other_link = '<a data-drupal-link-system-path="otherpage">Other page</a>';
327     $other_link_active = '<a data-drupal-link-system-path="otherpage" class="is-active">Other page</a>';
328     $data['<front>-and-other-link-on-other-path'] = [
329       0 => $front_special_link . ' ' . $other_link,
330       1 => 'otherpage',
331       2 => FALSE,
332       3 => 'en',
333       4 => [],
334       5 => $front_special_link . ' ' . $other_link_active,
335     ];
336     $data['front-and-other-link-on-other-path'] = [
337       0 => $front_path_link . ' ' . $other_link,
338       1 => 'otherpage',
339       2 => FALSE,
340       3 => 'en',
341       4 => [],
342       5 => $front_path_link . ' ' . $other_link_active,
343     ];
344     $data['other-and-<front>-link-on-other-path'] = [
345       0 => $other_link . ' ' . $front_special_link,
346       1 => 'otherpage',
347       2 => FALSE,
348       3 => 'en',
349       4 => [],
350       5 => $other_link_active . ' ' . $front_special_link,
351     ];
352     $data['other-and-front-link-on-other-path'] = [
353       0 => $other_link . ' ' . $front_path_link,
354       1 => 'otherpage',
355       2 => FALSE,
356       3 => 'en',
357       4 => [],
358       5 => $other_link_active . ' ' . $front_path_link,
359     ];
360     return $data;
361   }
362
363   /**
364    * Tests setLinkActiveClass().
365    *
366    * @param string $html_markup
367    *   The original HTML markup.
368    * @param string $current_path
369    *   The system path of the currently active page.
370    * @param bool $is_front
371    *   Whether the current page is the front page (which implies the current
372    *   path might also be <front>).
373    * @param string $url_language
374    *   The language code of the current URL.
375    * @param array $query
376    *   The query string for the current URL.
377    * @param string $expected_html_markup
378    *   The expected updated HTML markup.
379    *
380    * @dataProvider providerTestSetLinkActiveClass
381    * @covers ::setLinkActiveClass
382    */
383   public function testSetLinkActiveClass($html_markup, $current_path, $is_front, $url_language, array $query, $expected_html_markup) {
384     $this->assertSame($expected_html_markup, ActiveLinkResponseFilter::setLinkActiveClass($html_markup, $current_path, $is_front, $url_language, $query));
385   }
386
387 }