Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,327 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: contextual_recent
|
||||
label: 'Recent content'
|
||||
module: node
|
||||
description: 'Recent content.'
|
||||
tag: default
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
id: default
|
||||
display_title: Default
|
||||
display_plugin: default
|
||||
position: 0
|
||||
display_options:
|
||||
title: 'Recent content'
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: field
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
trim: false
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
changed:
|
||||
id: changed
|
||||
table: node_field_data
|
||||
field: changed
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: changed
|
||||
plugin_id: field
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: timestamp_ago
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
offset: 0
|
||||
items_per_page: 10
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: text_custom
|
||||
empty: true
|
||||
content: 'No content available.'
|
||||
tokenize: false
|
||||
sorts:
|
||||
changed:
|
||||
id: changed
|
||||
table: node_field_data
|
||||
field: changed
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: changed
|
||||
plugin_id: date
|
||||
order: DESC
|
||||
expose:
|
||||
label: ''
|
||||
exposed: false
|
||||
granularity: second
|
||||
arguments: { }
|
||||
filters:
|
||||
status_extra:
|
||||
id: status_extra
|
||||
table: node_field_data
|
||||
field: status_extra
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
plugin_id: node_status
|
||||
operator: '='
|
||||
value: false
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
langcode:
|
||||
id: langcode
|
||||
table: node_field_data
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: langcode
|
||||
plugin_id: language
|
||||
operator: in
|
||||
value:
|
||||
'***LANGUAGE_language_content***': '***LANGUAGE_language_content***'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
style:
|
||||
type: html_list
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
type: ul
|
||||
wrapper_class: item-list
|
||||
class: ''
|
||||
row:
|
||||
type: fields
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
query_comment: ''
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_tags: { }
|
||||
relationships:
|
||||
uid:
|
||||
id: uid
|
||||
table: node_field_data
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: author
|
||||
entity_type: node
|
||||
entity_field: uid
|
||||
plugin_id: standard
|
||||
required: true
|
||||
use_more: false
|
||||
use_more_always: false
|
||||
use_more_text: More
|
||||
link_display: '0'
|
||||
link_url: ''
|
||||
header: { }
|
||||
footer: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
block_1:
|
||||
id: block_1
|
||||
display_title: Block
|
||||
display_plugin: block
|
||||
position: 2
|
||||
display_options:
|
||||
row:
|
||||
type: 'entity:node'
|
||||
options:
|
||||
relationship: none
|
||||
view_mode: teaser
|
||||
defaults:
|
||||
style: false
|
||||
row: false
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'Contextual Test'
|
||||
type: module
|
||||
description: 'Provides test contextual links.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:contextual
|
||||
@ -0,0 +1,12 @@
|
||||
contextual_test:
|
||||
title: 'Test Link'
|
||||
route_name: 'contextual_test'
|
||||
group: 'contextual_test'
|
||||
contextual_test_ajax:
|
||||
title: 'Test Link with Ajax'
|
||||
route_name: 'contextual_test'
|
||||
group: 'contextual_test'
|
||||
options:
|
||||
attributes:
|
||||
class: ['use-ajax']
|
||||
data-dialog-type: 'modal'
|
||||
@ -0,0 +1,6 @@
|
||||
contextual_test:
|
||||
path: '/contextual-tests'
|
||||
defaults:
|
||||
_controller: '\Drupal\contextual_test\Controller\TestController::render'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\contextual_test\Controller;
|
||||
|
||||
/**
|
||||
* Test controller to provide a callback for the contextual link.
|
||||
*/
|
||||
class TestController {
|
||||
|
||||
/**
|
||||
* Callback for the contextual link.
|
||||
*
|
||||
* @return array
|
||||
* Render array.
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'#type' => 'markup',
|
||||
'#markup' => 'Everything is contextual!',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\contextual_test\Hook;
|
||||
|
||||
use Drupal\Core\Block\BlockPluginInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for contextual_test.
|
||||
*/
|
||||
class ContextualTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_block_view_alter().
|
||||
*/
|
||||
#[Hook('block_view_alter')]
|
||||
public function blockViewAlter(array &$build, BlockPluginInterface $block): void {
|
||||
$build['#contextual_links']['contextual_test'] = ['route_parameters' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_contextual_links_view_alter().
|
||||
*
|
||||
* @todo Apparently this too late to attach the library?
|
||||
* It won't work without contextual_test_page_attachments_alter()
|
||||
* Is that a problem? Should the contextual module itself do the attaching?
|
||||
*/
|
||||
#[Hook('contextual_links_view_alter')]
|
||||
public function contextualLinksViewAlter(&$element, $items): void {
|
||||
if (isset($element['#links']['contextual-test-ajax'])) {
|
||||
$element['#attached']['library'][] = 'core/drupal.dialog.ajax';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_page_attachments_alter().
|
||||
*/
|
||||
#[Hook('page_attachments_alter')]
|
||||
public function pageAttachmentsAlter(array &$attachments): void {
|
||||
$attachments['#attached']['library'][] = 'core/drupal.dialog.ajax';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\contextual\Functional;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Tests contextual link display on the front page based on permissions.
|
||||
*
|
||||
* @group contextual
|
||||
*/
|
||||
class ContextualDynamicContextTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user with permission to access contextual links and edit content.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $editorUser;
|
||||
|
||||
/**
|
||||
* An authenticated user with permission to access contextual links.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $authenticatedUser;
|
||||
|
||||
/**
|
||||
* A simulated anonymous user with access only to node content.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $anonymousUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'contextual',
|
||||
'node',
|
||||
'views',
|
||||
'views_ui',
|
||||
'language',
|
||||
'menu_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('it')->save();
|
||||
$this->rebuildContainer();
|
||||
|
||||
$this->editorUser = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'access contextual links',
|
||||
'edit any article content',
|
||||
]);
|
||||
$this->authenticatedUser = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'access contextual links',
|
||||
]);
|
||||
$this->anonymousUser = $this->drupalCreateUser(['access content']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests contextual links with different permissions.
|
||||
*
|
||||
* Ensures that contextual link placeholders always exist, even if the user is
|
||||
* not allowed to use contextual links.
|
||||
*/
|
||||
public function testDifferentPermissions(): void {
|
||||
$this->drupalLogin($this->editorUser);
|
||||
|
||||
// Create three nodes in the following order:
|
||||
// - An article, which should be user-editable.
|
||||
// - A page, which should not be user-editable.
|
||||
// - A second article, which should also be user-editable.
|
||||
$node1 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
|
||||
$node2 = $this->drupalCreateNode(['type' => 'page', 'promote' => 1]);
|
||||
$node3 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
|
||||
|
||||
// Now, on the front page, all article nodes should have contextual links
|
||||
// placeholders, as should the view that contains them.
|
||||
$ids = [
|
||||
'node:node=' . $node1->id() . ':changed=' . $node1->getChangedTime() . '&langcode=en',
|
||||
'node:node=' . $node2->id() . ':changed=' . $node2->getChangedTime() . '&langcode=en',
|
||||
'node:node=' . $node3->id() . ':changed=' . $node3->getChangedTime() . '&langcode=en',
|
||||
'entity.view.edit_form:view=frontpage:location=page&name=frontpage&display_id=page_1&langcode=en',
|
||||
];
|
||||
|
||||
// Editor user: can access contextual links and can edit articles.
|
||||
$this->drupalGet('node');
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$this->assertContextualLinkPlaceHolder($ids[$i]);
|
||||
}
|
||||
$response = $this->renderContextualLinks([], 'node');
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
$this->assertStringContainsString('No contextual ids specified.', (string) $response->getBody());
|
||||
$response = $this->renderContextualLinks($ids, 'node');
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$json = Json::decode((string) $response->getBody());
|
||||
$this->assertSame('<ul class="contextual-links"><li><a href="' . base_path() . 'node/1/edit">Edit</a></li></ul>', $json[$ids[0]]);
|
||||
$this->assertSame('', $json[$ids[1]]);
|
||||
$this->assertSame('<ul class="contextual-links"><li><a href="' . base_path() . 'node/3/edit">Edit</a></li></ul>', $json[$ids[2]]);
|
||||
$this->assertSame('', $json[$ids[3]]);
|
||||
|
||||
// Verify that link language is properly handled.
|
||||
$node3->addTranslation('it')->set('title', $this->randomString())->save();
|
||||
$id = 'node:node=' . $node3->id() . ':changed=' . $node3->getChangedTime() . '&langcode=it';
|
||||
$this->drupalGet('node', ['language' => ConfigurableLanguage::createFromLangcode('it')]);
|
||||
$this->assertContextualLinkPlaceHolder($id);
|
||||
|
||||
// Authenticated user: can access contextual links, cannot edit articles.
|
||||
$this->drupalLogin($this->authenticatedUser);
|
||||
$this->drupalGet('node');
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$this->assertContextualLinkPlaceHolder($ids[$i]);
|
||||
}
|
||||
$response = $this->renderContextualLinks([], 'node');
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
$this->assertStringContainsString('No contextual ids specified.', (string) $response->getBody());
|
||||
$response = $this->renderContextualLinks($ids, 'node');
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$json = Json::decode((string) $response->getBody());
|
||||
$this->assertSame('', $json[$ids[0]]);
|
||||
$this->assertSame('', $json[$ids[1]]);
|
||||
$this->assertSame('', $json[$ids[2]]);
|
||||
$this->assertSame('', $json[$ids[3]]);
|
||||
|
||||
// Anonymous user: cannot access contextual links.
|
||||
$this->drupalLogin($this->anonymousUser);
|
||||
$this->drupalGet('node');
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$this->assertNoContextualLinkPlaceHolder($ids[$i]);
|
||||
}
|
||||
$response = $this->renderContextualLinks([], 'node');
|
||||
$this->assertSame(403, $response->getStatusCode());
|
||||
$this->renderContextualLinks($ids, 'node');
|
||||
$this->assertSame(403, $response->getStatusCode());
|
||||
|
||||
// Get a page where contextual links are directly rendered.
|
||||
$this->drupalGet(Url::fromRoute('menu_test.contextual_test'));
|
||||
$this->assertSession()->assertEscaped("<script>alert('Welcome to the jungle!')</script>");
|
||||
$this->assertSession()->responseContains('<li><a href="' . base_path() . 'menu-test-contextual/1/edit" class="use-ajax" data-dialog-type="modal" data-is-something>Edit menu - contextual</a></li>');
|
||||
// Test contextual links respects the weight set in *.links.contextual.yml.
|
||||
$firstLink = $this->assertSession()->elementExists('css', 'ul.contextual-links li:nth-of-type(1) a');
|
||||
$secondLink = $this->assertSession()->elementExists('css', 'ul.contextual-links li:nth-of-type(2) a');
|
||||
$this->assertEquals(base_path() . 'menu-test-contextual/1/edit', $firstLink->getAttribute('href'));
|
||||
$this->assertEquals(base_path() . 'menu-test-contextual/1', $secondLink->getAttribute('href'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the contextual placeholder content is protected by a token.
|
||||
*/
|
||||
public function testTokenProtection(): void {
|
||||
$this->drupalLogin($this->editorUser);
|
||||
|
||||
// Create a node that will have a contextual link.
|
||||
$node1 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
|
||||
|
||||
// Now, on the front page, all article nodes should have contextual links
|
||||
// placeholders, as should the view that contains them.
|
||||
$id = 'node:node=' . $node1->id() . ':changed=' . $node1->getChangedTime() . '&langcode=en';
|
||||
|
||||
// Editor user: can access contextual links and can edit articles.
|
||||
$this->drupalGet('node');
|
||||
$this->assertContextualLinkPlaceHolder($id);
|
||||
|
||||
$http_client = $this->getHttpClient();
|
||||
$url = Url::fromRoute('contextual.render', [], [
|
||||
'query' => [
|
||||
'_format' => 'json',
|
||||
'destination' => 'node',
|
||||
],
|
||||
])->setAbsolute()->toString();
|
||||
|
||||
$response = $http_client->request('POST', $url, [
|
||||
'cookies' => $this->getSessionCookies(),
|
||||
'form_params' => ['ids' => [$id], 'tokens' => []],
|
||||
'http_errors' => FALSE,
|
||||
]);
|
||||
$this->assertEquals('400', $response->getStatusCode());
|
||||
$this->assertStringContainsString('No contextual ID tokens specified.', (string) $response->getBody());
|
||||
|
||||
$response = $http_client->request('POST', $url, [
|
||||
'cookies' => $this->getSessionCookies(),
|
||||
'form_params' => ['ids' => [$id], 'tokens' => ['wrong_token']],
|
||||
'http_errors' => FALSE,
|
||||
]);
|
||||
$this->assertEquals('400', $response->getStatusCode());
|
||||
$this->assertStringContainsString('Invalid contextual ID specified.', (string) $response->getBody());
|
||||
|
||||
$response = $http_client->request('POST', $url, [
|
||||
'cookies' => $this->getSessionCookies(),
|
||||
'form_params' => ['ids' => [$id], 'tokens' => ['wrong_key' => $this->createContextualIdToken($id)]],
|
||||
'http_errors' => FALSE,
|
||||
]);
|
||||
$this->assertEquals('400', $response->getStatusCode());
|
||||
$this->assertStringContainsString('Invalid contextual ID specified.', (string) $response->getBody());
|
||||
|
||||
$response = $http_client->request('POST', $url, [
|
||||
'cookies' => $this->getSessionCookies(),
|
||||
'form_params' => ['ids' => [$id], 'tokens' => [$this->createContextualIdToken($id)]],
|
||||
'http_errors' => FALSE,
|
||||
]);
|
||||
$this->assertEquals('200', $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a contextual link placeholder with the given id exists.
|
||||
*
|
||||
* @param string $id
|
||||
* A contextual link id.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertContextualLinkPlaceHolder(string $id): void {
|
||||
$this->assertSession()->elementAttributeContains(
|
||||
'css',
|
||||
'div[data-contextual-id="' . $id . '"]',
|
||||
'data-contextual-token',
|
||||
$this->createContextualIdToken($id)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a contextual link placeholder with a given id does not exist.
|
||||
*
|
||||
* @param string $id
|
||||
* A contextual link id.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertNoContextualLinkPlaceHolder(string $id): void {
|
||||
$this->assertSession()->elementNotExists('css', 'div[data-contextual-id="' . $id . '"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server-rendered contextual links for the given contextual link ids.
|
||||
*
|
||||
* @param array $ids
|
||||
* An array of contextual link ids.
|
||||
* @param string $current_path
|
||||
* The Drupal path for the page for which the contextual links are rendered.
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* The response object.
|
||||
*/
|
||||
protected function renderContextualLinks($ids, $current_path): ResponseInterface {
|
||||
$tokens = array_map([$this, 'createContextualIdToken'], $ids);
|
||||
$http_client = $this->getHttpClient();
|
||||
$url = Url::fromRoute('contextual.render', [], [
|
||||
'query' => [
|
||||
'_format' => 'json',
|
||||
'destination' => $current_path,
|
||||
],
|
||||
]);
|
||||
|
||||
return $http_client->request('POST', $this->buildUrl($url), [
|
||||
'cookies' => $this->getSessionCookies(),
|
||||
'form_params' => ['ids' => $ids, 'tokens' => $tokens],
|
||||
'http_errors' => FALSE,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a contextual ID token.
|
||||
*
|
||||
* @param string $id
|
||||
* The contextual ID to create a token for.
|
||||
*
|
||||
* @return string
|
||||
* The contextual ID token.
|
||||
*/
|
||||
protected function createContextualIdToken($id) {
|
||||
return Crypt::hmacBase64($id, Settings::getHashSalt() . $this->container->get('private_key')->get());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\contextual\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for contextual.
|
||||
*
|
||||
* @group contextual
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\contextual\FunctionalJavascript;
|
||||
|
||||
/**
|
||||
* Functions for testing contextual links.
|
||||
*/
|
||||
trait ContextualLinkClickTrait {
|
||||
|
||||
/**
|
||||
* Clicks a contextual link.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector for the element that contains the contextual link.
|
||||
* @param string $link_locator
|
||||
* The link id, title, or text.
|
||||
* @param bool $force_visible
|
||||
* If true then the button will be forced to visible so it can be clicked.
|
||||
*/
|
||||
protected function clickContextualLink($selector, $link_locator, $force_visible = TRUE) {
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->waitFor(10, function () use ($page, $selector) {
|
||||
return $page->find('css', "$selector .contextual-links");
|
||||
});
|
||||
|
||||
if ($force_visible) {
|
||||
$this->toggleContextualTriggerVisibility($selector);
|
||||
}
|
||||
|
||||
$element = $this->getSession()->getPage()->find('css', $selector);
|
||||
$element->find('css', '.contextual button')->press();
|
||||
$element->findLink($link_locator)->click();
|
||||
|
||||
if ($force_visible) {
|
||||
$this->toggleContextualTriggerVisibility($selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the visibility of a contextual trigger.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector for the element that contains the contextual link.
|
||||
*/
|
||||
protected function toggleContextualTriggerVisibility($selector) {
|
||||
// Hovering over the element itself with should be enough, but does not
|
||||
// work. Manually remove the visually-hidden class.
|
||||
$this->getSession()->executeScript("jQuery('{$selector} .contextual .trigger').toggleClass('visually-hidden');");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\contextual\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests the UI for correct contextual links.
|
||||
*
|
||||
* @group contextual
|
||||
*/
|
||||
class ContextualLinksTest extends WebDriverTestBase {
|
||||
|
||||
use ContextualLinkClickTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'contextual'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->createUser(['access contextual links']));
|
||||
$this->placeBlock('system_branding_block', [
|
||||
'id' => 'branding',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the visibility of contextual links.
|
||||
*/
|
||||
public function testContextualLinksVisibility(): void {
|
||||
$this->drupalGet('user');
|
||||
$contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
|
||||
$this->assertEmpty($contextualLinks);
|
||||
|
||||
// Ensure visibility remains correct after cached paged load.
|
||||
$this->drupalGet('user');
|
||||
$contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
|
||||
$this->assertEmpty($contextualLinks);
|
||||
|
||||
// Grant permissions to use contextual links on blocks.
|
||||
$this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), [
|
||||
'access contextual links',
|
||||
'administer blocks',
|
||||
]);
|
||||
|
||||
$this->drupalGet('user');
|
||||
$contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
|
||||
$this->assertNotEmpty($contextualLinks);
|
||||
|
||||
// Confirm touchevents detection is loaded with Contextual Links
|
||||
$this->assertSession()->elementExists('css', 'html.no-touchevents');
|
||||
|
||||
// Ensure visibility remains correct after cached paged load.
|
||||
$this->drupalGet('user');
|
||||
$contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
|
||||
$this->assertNotEmpty($contextualLinks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests clicking contextual links.
|
||||
*/
|
||||
public function testContextualLinksClick(): void {
|
||||
$this->container->get('module_installer')->install(['contextual_test']);
|
||||
// Test clicking contextual link without toolbar.
|
||||
$this->drupalGet('user');
|
||||
$this->clickContextualLink('#block-branding', 'Test Link');
|
||||
$this->assertSession()->pageTextContains('Everything is contextual!');
|
||||
|
||||
// Test click a contextual link that uses ajax.
|
||||
$this->drupalGet('user');
|
||||
$current_page_string = 'NOT_RELOADED_IF_ON_PAGE';
|
||||
$this->getSession()->executeScript('document.body.appendChild(document.createTextNode("' . $current_page_string . '"));');
|
||||
|
||||
// Move the pointer over the branding block so the contextual link appears
|
||||
// as it would with a real user interaction. Otherwise clickContextualLink()
|
||||
// does not open the dialog in a manner that is opener-aware, and it isn't
|
||||
// possible to reliably test focus management.
|
||||
$this->getSession()->getDriver()->mouseOver('.//*[@id="block-branding"]');
|
||||
$this->clickContextualLink('#block-branding', 'Test Link with Ajax', FALSE);
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '#drupal-modal'));
|
||||
$this->assertSession()->elementContains('css', '#drupal-modal', 'Everything is contextual!');
|
||||
$this->getSession()->executeScript('document.querySelector("#block-branding .trigger").addEventListener("focus", (e) => e.target.classList.add("i-am-focused"))');
|
||||
$this->getSession()->getPage()->pressButton('Close');
|
||||
$this->assertSession()->assertNoElementAfterWait('css', 'ui.dialog');
|
||||
|
||||
// When the dialog is closed, the opening contextual link is now inside a
|
||||
// collapsed container, so focus should be routed to the contextual link
|
||||
// toggle button.
|
||||
$this->assertNotNull($this->assertSession()->waitForElement('css', '.trigger.i-am-focused'), $this->getSession()->getPage()->find('css', '#block-branding')->getOuterHtml());
|
||||
$this->assertJsCondition('document.activeElement === document.querySelector("#block-branding button.trigger")', 10000, 'Focus should be on the contextual trigger, but instead is at ' . $this->getSession()->evaluateScript('document.activeElement.outerHTML'));
|
||||
|
||||
// Check to make sure that page was not reloaded.
|
||||
$this->assertSession()->pageTextContains($current_page_string);
|
||||
|
||||
// Test clicking contextual link with toolbar.
|
||||
$this->container->get('module_installer')->install(['toolbar']);
|
||||
$this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['access toolbar']);
|
||||
$this->drupalGet('user');
|
||||
$this->assertSession()->assertExpectedAjaxRequest(1);
|
||||
|
||||
// Click "Edit" in toolbar to show contextual links.
|
||||
$this->getSession()->getPage()->find('css', '.contextual-toolbar-tab button')->press();
|
||||
$this->clickContextualLink('#block-branding', 'Test Link', FALSE);
|
||||
$this->assertSession()->pageTextContains('Everything is contextual!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the contextual links destination.
|
||||
*/
|
||||
public function testContextualLinksDestination(): void {
|
||||
$this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), [
|
||||
'access contextual links',
|
||||
'administer blocks',
|
||||
]);
|
||||
$this->drupalGet('user');
|
||||
$this->assertSession()->waitForElement('css', '.contextual button');
|
||||
$expected_destination_value = (string) $this->loggedInUser->toUrl()->toString();
|
||||
$contextual_link_url_parsed = parse_url($this->getSession()->getPage()->findLink('Configure block')->getAttribute('href'));
|
||||
$this->assertEquals("destination=$expected_destination_value", $contextual_link_url_parsed['query']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the contextual links destination with query.
|
||||
*/
|
||||
public function testContextualLinksDestinationWithQuery(): void {
|
||||
$this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), [
|
||||
'access contextual links',
|
||||
'administer blocks',
|
||||
]);
|
||||
|
||||
$this->drupalGet('admin/structure/block', ['query' => ['foo' => 'bar']]);
|
||||
$this->assertSession()->waitForElement('css', '.contextual button');
|
||||
$expected_destination_value = Url::fromRoute('block.admin_display')->toString();
|
||||
$contextual_link_url_parsed = parse_url($this->getSession()->getPage()->findLink('Configure block')->getAttribute('href'));
|
||||
$this->assertEquals("destination=$expected_destination_value%3Ffoo%3Dbar", $contextual_link_url_parsed['query']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\contextual\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the UI for correct contextual links.
|
||||
*
|
||||
* @group contextual
|
||||
*/
|
||||
class DuplicateContextualLinksTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'contextual',
|
||||
'node',
|
||||
'views',
|
||||
'views_ui',
|
||||
'contextual_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the contextual links with same id.
|
||||
*/
|
||||
public function testSameContextualLinks(): void {
|
||||
$this->drupalPlaceBlock('views_block:contextual_recent-block_1', ['id' => 'first']);
|
||||
$this->drupalPlaceBlock('views_block:contextual_recent-block_1', ['id' => 'second']);
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
$this->drupalCreateNode();
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access content',
|
||||
'access contextual links',
|
||||
'administer nodes',
|
||||
'administer blocks',
|
||||
'administer views',
|
||||
'edit any page content',
|
||||
]));
|
||||
// Ensure same contextual links work correct with fresh and cached page.
|
||||
foreach (['fresh', 'cached'] as $state) {
|
||||
$this->drupalGet('user');
|
||||
$contextual_id = '[data-contextual-id^="node:node=1"]';
|
||||
$this->assertJsCondition("(typeof jQuery !== 'undefined' && jQuery('[data-contextual-id]:empty').length === 0)");
|
||||
$this->getSession()->executeScript("jQuery('#block-first $contextual_id .trigger').trigger('click');");
|
||||
$contextual_links = $this->assertSession()->waitForElementVisible('css', "#block-first $contextual_id .contextual-links");
|
||||
$this->assertTrue($contextual_links->isVisible(), "Contextual links are visible with $state page.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\contextual\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests edit mode.
|
||||
*
|
||||
* @group contextual
|
||||
* @group #slow
|
||||
*/
|
||||
class EditModeTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* CSS selector for Drupal's announce element.
|
||||
*/
|
||||
const ANNOUNCE_SELECTOR = '#drupal-live-announce';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'block',
|
||||
'user',
|
||||
'system',
|
||||
'breakpoint',
|
||||
'toolbar',
|
||||
'contextual',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The administration theme name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $adminTheme = 'claro';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
\Drupal::service('theme_installer')->install([$this->adminTheme]);
|
||||
\Drupal::configFactory()
|
||||
->getEditable('system.theme')
|
||||
->set('admin', $this->adminTheme)
|
||||
->save();
|
||||
|
||||
$this->drupalLogin($this->createUser([
|
||||
'administer blocks',
|
||||
'access contextual links',
|
||||
'access toolbar',
|
||||
'view the administration theme',
|
||||
]));
|
||||
$this->placeBlock('system_powered_by_block', ['id' => 'powered']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests enabling and disabling edit mode.
|
||||
*/
|
||||
public function testEditModeEnableDisable(): void {
|
||||
$web_assert = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
// Get the page twice to ensure edit mode remains enabled after a new page
|
||||
// request.
|
||||
$this->drupalGet('user');
|
||||
$expected_restricted_tab_count = 1 + count($page->findAll('css', '[data-contextual-id]'));
|
||||
|
||||
// After the page loaded we need to additionally wait until the settings
|
||||
// tray Ajax activity is done.
|
||||
$web_assert->assertWaitOnAjaxRequest();
|
||||
|
||||
$unrestricted_tab_count = $this->getTabbableElementsCount();
|
||||
$this->assertGreaterThan($expected_restricted_tab_count, $unrestricted_tab_count);
|
||||
|
||||
// Enable edit mode.
|
||||
// After the first page load the page will be in edit mode when loaded.
|
||||
$this->pressToolbarEditButton();
|
||||
|
||||
$this->assertAnnounceEditMode();
|
||||
$this->assertSame($expected_restricted_tab_count, $this->getTabbableElementsCount());
|
||||
|
||||
// Disable edit mode.
|
||||
$this->pressToolbarEditButton();
|
||||
$this->assertAnnounceLeaveEditMode();
|
||||
$this->assertSame($unrestricted_tab_count, $this->getTabbableElementsCount());
|
||||
// Enable edit mode again.
|
||||
$this->pressToolbarEditButton();
|
||||
// Finally assert that the 'edit mode enabled' announcement is still
|
||||
// correct after toggling the edit mode at least once.
|
||||
$this->assertAnnounceEditMode();
|
||||
$this->assertSame($expected_restricted_tab_count, $this->getTabbableElementsCount());
|
||||
|
||||
// Test while Edit Mode is enabled it doesn't interfere with pages with
|
||||
// no contextual links.
|
||||
$this->drupalGet('admin/structure/block');
|
||||
$web_assert->elementContains('css', 'h1.page-title', 'Block layout');
|
||||
$this->assertEquals(0, count($page->findAll('css', '[data-contextual-id]')));
|
||||
$this->assertGreaterThan(0, $this->getTabbableElementsCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Presses the toolbar edit mode.
|
||||
*/
|
||||
protected function pressToolbarEditButton(): void {
|
||||
$edit_button = $this->getSession()->getPage()->find('css', '#toolbar-bar div.contextual-toolbar-tab button');
|
||||
$edit_button->press();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the correct message was announced when entering edit mode.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertAnnounceEditMode(): void {
|
||||
$web_assert = $this->assertSession();
|
||||
// Wait for contextual trigger button.
|
||||
$web_assert->waitForElementVisible('css', '.contextual trigger');
|
||||
$web_assert->elementContains('css', static::ANNOUNCE_SELECTOR, 'Tabbing is constrained to a set of');
|
||||
$web_assert->elementNotContains('css', static::ANNOUNCE_SELECTOR, 'Tabbing is no longer constrained by the Contextual module.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the correct message was announced when leaving edit mode.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertAnnounceLeaveEditMode(): void {
|
||||
$web_assert = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
// Wait till all the contextual links are hidden.
|
||||
$page->waitFor(1, function () use ($page) {
|
||||
return empty($page->find('css', '.contextual .trigger.visually-hidden'));
|
||||
});
|
||||
$web_assert->elementContains('css', static::ANNOUNCE_SELECTOR, 'Tabbing is no longer constrained by the Contextual module.');
|
||||
$web_assert->elementNotContains('css', static::ANNOUNCE_SELECTOR, 'Tabbing is constrained to a set of');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of elements that are tabbable.
|
||||
*
|
||||
* @return int
|
||||
* The number of tabbable elements.
|
||||
*/
|
||||
protected function getTabbableElementsCount(): int {
|
||||
// Mark all tabbable elements.
|
||||
$this->getSession()->executeScript("jQuery(window.tabbable.tabbable(document.body)).attr('data-marked', '');");
|
||||
// Count all marked elements.
|
||||
$count = count($this->getSession()->getPage()->findAll('css', "[data-marked]"));
|
||||
// Remove set attributes.
|
||||
$this->getSession()->executeScript("jQuery('[data-marked]').removeAttr('data-marked');");
|
||||
return $count;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\contextual\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests edge cases for converting between contextual links and IDs.
|
||||
*
|
||||
* @group contextual
|
||||
*/
|
||||
class ContextualUnitTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['contextual'];
|
||||
|
||||
/**
|
||||
* Provides test cases for both test functions.
|
||||
*
|
||||
* Used in testContextualLinksToId() and testContextualIdToLinks().
|
||||
*
|
||||
* @return array[]
|
||||
* Test cases.
|
||||
*/
|
||||
public static function contextualLinksDataProvider(): array {
|
||||
$tests['one group, one dynamic path argument, no metadata'] = [
|
||||
[
|
||||
'node' => [
|
||||
'route_parameters' => [
|
||||
'node' => '14031991',
|
||||
],
|
||||
'metadata' => ['langcode' => 'en'],
|
||||
],
|
||||
],
|
||||
'node:node=14031991:langcode=en',
|
||||
];
|
||||
|
||||
$tests['one group, multiple dynamic path arguments, no metadata'] = [
|
||||
[
|
||||
'foo' => [
|
||||
'route_parameters' => [
|
||||
0 => 'bar',
|
||||
'key' => 'baz',
|
||||
1 => 'qux',
|
||||
],
|
||||
'metadata' => ['langcode' => 'en'],
|
||||
],
|
||||
],
|
||||
'foo:0=bar&key=baz&1=qux:langcode=en',
|
||||
];
|
||||
|
||||
$tests['one group, one dynamic path argument, metadata'] = [
|
||||
[
|
||||
'views_ui_edit' => [
|
||||
'route_parameters' => [
|
||||
'view' => 'frontpage',
|
||||
],
|
||||
'metadata' => [
|
||||
'location' => 'page',
|
||||
'display' => 'page_1',
|
||||
'langcode' => 'en',
|
||||
],
|
||||
],
|
||||
],
|
||||
'views_ui_edit:view=frontpage:location=page&display=page_1&langcode=en',
|
||||
];
|
||||
|
||||
$tests['multiple groups, multiple dynamic path arguments'] = [
|
||||
[
|
||||
'node' => [
|
||||
'route_parameters' => [
|
||||
'node' => '14031991',
|
||||
],
|
||||
'metadata' => ['langcode' => 'en'],
|
||||
],
|
||||
'foo' => [
|
||||
'route_parameters' => [
|
||||
0 => 'bar',
|
||||
'key' => 'baz',
|
||||
1 => 'qux',
|
||||
],
|
||||
'metadata' => ['langcode' => 'en'],
|
||||
],
|
||||
'edge' => [
|
||||
'route_parameters' => ['20011988'],
|
||||
'metadata' => ['langcode' => 'en'],
|
||||
],
|
||||
],
|
||||
'node:node=14031991:langcode=en|foo:0=bar&key=baz&1=qux:langcode=en|edge:0=20011988:langcode=en',
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the conversion from contextual links to IDs.
|
||||
*
|
||||
* @param array $links
|
||||
* The #contextual_links property value array.
|
||||
* @param string $id
|
||||
* The serialized representation of the passed links.
|
||||
*
|
||||
* @covers ::_contextual_links_to_id
|
||||
*
|
||||
* @dataProvider contextualLinksDataProvider
|
||||
*/
|
||||
public function testContextualLinksToId(array $links, string $id): void {
|
||||
$this->assertSame($id, _contextual_links_to_id($links));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the conversion from contextual ID to links.
|
||||
*
|
||||
* @param array $links
|
||||
* The #contextual_links property value array.
|
||||
* @param string $id
|
||||
* The serialized representation of the passed links.
|
||||
*
|
||||
* @covers ::_contextual_id_to_links
|
||||
*
|
||||
* @dataProvider contextualLinksDataProvider
|
||||
*/
|
||||
public function testContextualIdToLinks(array $links, string $id): void {
|
||||
$this->assertSame($links, _contextual_id_to_links($id));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user