Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

View File

@ -0,0 +1,7 @@
name: "Content Block module tests"
type: module
description: "Support module for content block related testing."
package: Testing
version: VERSION
dependencies:
- drupal:block_content

View File

@ -0,0 +1,6 @@
block_content_test.block_content_view:
path: '/block-content/{block_content}'
defaults:
_entity_view: 'block_content'
requirements:
_entity_access: 'block_content.view'

View File

@ -0,0 +1,26 @@
langcode: en
status: true
dependencies:
module:
- block_content
theme:
- stark
id: foobar_gorilla
theme: stark
region: content
weight: 0
provider: null
plugin: 'block_content:fb5e8434-3617-4a1d-a252-8273e95ec30e'
settings:
id: 'block_content:fb5e8434-3617-4a1d-a252-8273e95ec30e'
label: 'Foobar Gorilla'
label_display: visible
provider: block_content
status: true
info: ''
view_mode: full
visibility:
request_path:
id: request_path
negate: false
pages: ''

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Drupal\block_content_test\Hook;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Core\Hook\Attribute\Hook;
/**
* Hook implementations for block_content_test.
*/
class BlockContentTestHooks {
/**
* Implements hook_ENTITY_TYPE_view().
*/
#[Hook('block_content_view')]
public function blockContentView(array &$build, BlockContent $block_content, $view_mode): void {
// Add extra content.
$build['extra_content'] = ['#markup' => '<blink>Wow</blink>'];
}
/**
* Implements hook_ENTITY_TYPE_presave().
*/
#[Hook('block_content_presave')]
public function blockContentPresave(BlockContent $block_content): void {
if ($block_content->label() == 'testing_block_content_presave') {
$block_content->setInfo($block_content->label() . '_presave');
}
// Determine changes.
if ($block_content->getOriginal() && $block_content->getOriginal()->label() == 'test_changes') {
if ($block_content->getOriginal()->label() != $block_content->label()) {
$block_content->setInfo($block_content->label() . '_presave');
// Drupal 1.0 release.
$block_content->changed = 979534800;
}
}
}
/**
* Implements hook_ENTITY_TYPE_update().
*/
#[Hook('block_content_update')]
public function blockContentUpdate(BlockContent $block_content): void {
// Determine changes on update.
if ($block_content->getOriginal() && $block_content->getOriginal()->label() == 'test_changes') {
if ($block_content->getOriginal()->label() != $block_content->label()) {
$block_content->setInfo($block_content->label() . '_update');
}
}
}
/**
* Implements hook_ENTITY_TYPE_insert().
*
* This tests saving a block_content on block_content insert.
*
* @see \Drupal\block_content\Tests\BlockContentSaveTest::testBlockContentSaveOnInsert()
*/
#[Hook('block_content_insert')]
public function blockContentInsert(BlockContent $block_content): void {
// Set the block_content title to the block_content ID and save.
if ($block_content->label() == 'new') {
$block_content->setInfo('BlockContent ' . $block_content->id());
$block_content->setNewRevision(FALSE);
$block_content->save();
}
if ($block_content->label() == 'fail_creation') {
throw new \Exception('Test exception for rollback.');
}
}
}

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\block_content_test\Plugin\EntityReferenceSelection;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
/**
* Test EntityReferenceSelection with conditions on the 'reusable' field.
*/
class TestSelection extends DefaultSelection {
/**
* The condition type.
*
* @var string
*/
protected $conditionType;
/**
* Whether to set the condition for reusable or non-reusable blocks.
*
* @var bool
*/
protected $isReusable;
/**
* Sets the test mode.
*
* @param string $condition_type
* The condition type.
* @param bool $is_reusable
* Whether to set the condition for reusable or non-reusable blocks.
*/
public function setTestMode($condition_type = NULL, $is_reusable = NULL) {
$this->conditionType = $condition_type;
$this->isReusable = $is_reusable;
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityQuery($match, $match_operator);
if ($this->conditionType) {
/** @var \Drupal\Core\Database\Query\ConditionInterface $add_condition */
$add_condition = NULL;
switch ($this->conditionType) {
case 'base':
$add_condition = $query;
break;
case 'group':
$group = $query->andConditionGroup()
->exists('type');
$add_condition = $group;
$query->condition($group);
break;
case "nested_group":
$query->exists('type');
$sub_group = $query->andConditionGroup()
->exists('type');
$add_condition = $sub_group;
$group = $query->andConditionGroup()
->exists('type')
->condition($sub_group);
$query->condition($group);
break;
}
if ($this->isReusable) {
$add_condition->condition('reusable', 1);
}
else {
$add_condition->condition('reusable', 0);
}
}
return $query;
}
}

View File

@ -0,0 +1,8 @@
name: 'Block Content test views'
type: module
description: 'Provides default views for views block_content tests.'
package: Testing
version: VERSION
dependencies:
- drupal:block_content
- drupal:views

View File

@ -0,0 +1,234 @@
langcode: en
status: true
dependencies:
module:
- block_content
id: test_block_content_redirect_destination
label: 'Redirect destination'
module: views
description: ''
tag: ''
base_table: block_content_field_data
base_field: id
display:
default:
display_plugin: default
id: default
display_title: Default
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
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
pager:
type: mini
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous:
next:
style:
type: table
options:
grouping: { }
class: ''
row_class: ''
default_row_class: true
override: true
sticky: false
caption: ''
summary: ''
description: ''
columns:
info: info
info:
info:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
default: '-1'
empty_table: false
row:
type: 'entity:block_content'
fields:
info:
table: block_content_field_data
field: info
id: info
entity_type: null
entity_field: info
plugin_id: field
relationship: none
group_type: group
admin_label: ''
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: true
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: string
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
operations:
id: operations
table: block_content
field: operations
relationship: none
group_type: group
admin_label: ''
label: 'Operations links'
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: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
destination: true
entity_type: block_content
plugin_id: entity_operations
filters: { }
sorts: { }
title: 'Redirect destination'
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: /admin/content/redirect_destination
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
tags: { }

View File

@ -0,0 +1,66 @@
langcode: en
status: true
dependencies:
module:
- block_content
id: test_block_content_revision_id
label: test_block_content_revision_id
module: views
description: ''
tag: ''
base_table: block_content_field_revision
base_field: revision_id
display:
default:
display_options:
relationships:
id:
id: id
table: block_content_field_revision
field: id
required: true
plugin_id: standard
fields:
revision_id:
id: revision_id
table: block_content_field_revision
field: revision_id
plugin_id: field
entity_type: block_content
entity_field: revision_id
id_1:
id: id_1
table: block_content_field_revision
field: id
plugin_id: field
entity_type: block_content
entity_field: id
id:
id: id
table: block_content_field_data
field: id
relationship: id
plugin_id: field
entity_type: block_content
entity_field: id
arguments:
id:
id: id
table: block_content_field_revision
field: id
plugin_id: numeric
entity_type: block_content
entity_field: id
sorts:
revision_id:
id: revision_id
table: block_content_field_revision
field: revision_id
order: ASC
plugin_id: field
entity_type: block_content
entity_field: revision_id
display_plugin: default
display_title: Default
id: default
position: 0

View File

@ -0,0 +1,69 @@
langcode: en
status: true
dependencies:
module:
- block_content
id: test_block_content_revision_revision_id
label: test_block_content_revision_revision_id
module: views
description: ''
tag: ''
base_table: block_content_field_revision
base_field: revision_id
display:
default:
display_options:
relationships:
revision_id:
id: revision_id
table: block_content_field_revision
field: revision_id
required: true
entity_type: block_content
entity_field: revision_id
plugin_id: standard
fields:
revision_id:
id: revision_id
table: block_content_field_revision
field: revision_id
plugin_id: field
entity_type: block_content
entity_field: revision_id
id_1:
id: id_1
table: block_content_field_revision
field: id
plugin_id: field
entity_type: block_content
entity_field: id
id:
id: id
table: block_content_field_data
field: id
relationship: revision_id
plugin_id: field
entity_type: block_content
entity_field: id
arguments:
id:
id: id
table: block_content_field_revision
field: id
plugin_id: block_content_id
entity_type: block_content
entity_field: id
sorts:
revision_id:
id: revision_id
table: block_content_field_revision
field: revision_id
order: ASC
plugin_id: field
entity_type: block_content
entity_field: revision_id
display_extenders: { }
display_plugin: default
display_title: Default
id: default
position: 0

View File

@ -0,0 +1,322 @@
langcode: en
status: true
dependencies:
module:
- block_content
- user
id: test_block_content_revision_user
label: 'Test block content revision user'
module: views
description: ''
tag: ''
base_table: block_content_field_data
base_field: id
display:
default:
display_plugin: default
id: default
display_title: Default
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
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
pager:
type: none
options:
offset: 0
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
id:
id: id
table: block_content_field_revision
field: id
relationship: none
group_type: group
admin_label: ''
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: number_integer
settings:
thousand_separator: ''
prefix_suffix: false
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
entity_type: block_content
entity_field: id
plugin_id: field
revision_id:
id: revision_id
table: block_content_field_revision
field: revision_id
relationship: none
group_type: group
admin_label: ''
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: number_integer
settings:
thousand_separator: ''
prefix_suffix: false
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
entity_type: block_content
entity_field: revision_id
plugin_id: field
revision_user:
id: revision_user
table: block_content_revision
field: revision_user
relationship: none
group_type: group
admin_label: ''
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: target_id
type: entity_reference_label
settings:
link: false
group_column: target_id
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
entity_type: block_content
entity_field: revision_user
plugin_id: field
filters:
revision_user:
id: revision_user
table: block_content_revision
field: revision_user
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: revision_user_op
label: 'Revision user'
description: ''
use_operator: false
operator: revision_user_op
operator_limit_selection: false
operator_list: { }
identifier: revision_user
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: { }
entity_type: block_content
entity_field: revision_user
plugin_id: user_name
sorts: { }
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
filter_groups:
operator: AND
groups: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- 'user.block_content_grants:view'
- user.permissions
tags: { }

View File

@ -0,0 +1,190 @@
langcode: en
status: true
dependencies:
module:
- block_content
id: test_block_content_view
label: test_block_content_view
module: views
description: ''
tag: ''
base_table: block_content_field_data
base_field: id
display:
default:
display_plugin: default
id: default
display_title: Default
position: null
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
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
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: default
row:
type: fields
fields:
id:
id: id
table: block_content_field_data
field: id
relationship: none
group_type: group
admin_label: ''
label: Id
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: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
plugin_id: field
entity_type: block_content
entity_field: id
sorts:
id:
id: id
table: block_content_field_data
field: id
relationship: none
group_type: group
admin_label: ''
order: ASC
exposed: false
expose:
label: ''
entity_type: block_content
entity_field: id
plugin_id: standard
title: test_block_content_view
header: { }
footer: { }
empty: { }
relationships: { }
display_extenders: { }
arguments:
type:
id: type
table: block_content_field_data
field: type
relationship: none
group_type: group
admin_label: ''
default_action: 'not found'
exception:
value: all
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: fixed
default_argument_options:
argument: ''
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: false
validate:
type: none
fail: 'not found'
validate_options: { }
glossary: false
limit: 0
case: none
path_case: none
transform_dash: false
break_phrase: false
entity_type: block_content
entity_field: type
plugin_id: string
page_1:
display_plugin: page
id: page_1
display_title: Page
position: null
display_options:
path: test-block_content-view
display_extenders: { }

View File

@ -0,0 +1,338 @@
langcode: en
status: true
dependencies:
module:
- block_content
id: test_field_filters
label: 'Test field filters'
module: views
description: ''
tag: ''
base_table: block_content_field_data
base_field: id
display:
default:
display_plugin: default
id: default
display_title: Default
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
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
pager:
type: none
options:
items_per_page: 0
offset: 0
style:
type: default
row:
type: 'entity:block_content'
options:
relationship: none
view_mode: default
fields:
info:
id: info
table: block_content_field_data
field: info
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
entity_type: block_content
type: string
settings:
link_to_entity: true
entity_field: title
plugin_id: field
filters:
info:
id: info
table: block_content_field_data
field: info
relationship: none
group_type: group
admin_label: ''
operator: contains
value: Paris
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
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: { }
plugin_id: string
entity_type: block_content
entity_field: info
sorts:
changed:
id: changed
table: block_content_field_data
field: changed
order: DESC
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
plugin_id: date
entity_type: block_content
entity_field: changed
title: 'Test field filters'
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
rendering_language: '***LANGUAGE_entity_translation***'
display_extenders: { }
page_bf:
display_plugin: page
id: page_bf
display_title: 'Body filter page'
position: 1
display_options:
path: test-body-filter
display_description: ''
title: 'Test body filters'
defaults:
title: false
filters: false
filter_groups: false
filters:
body_value:
id: body_value
table: block_content__body
field: body_value
relationship: none
group_type: group
admin_label: ''
operator: contains
value: Comida
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
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: { }
plugin_id: string
entity_type: block_content
entity_field: body
filter_groups:
operator: AND
groups:
1: AND
display_extenders: { }
page_bfp:
display_plugin: page
id: page_bfp
display_title: 'Body filter page Paris'
position: 1
display_options:
path: test-body-paris
display_description: ''
title: 'Test body filters'
defaults:
title: false
filters: false
filter_groups: false
filters:
body_value:
id: body_value
table: block_content__body
field: body_value
relationship: none
group_type: group
admin_label: ''
operator: contains
value: Paris
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
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: { }
plugin_id: string
entity_type: block_content
entity_field: body
filter_groups:
operator: AND
groups:
1: AND
display_extenders: { }
page_if:
display_plugin: page
id: page_if
display_title: 'Info filter page'
position: 1
display_options:
path: test-info-filter
display_description: ''
title: 'Test info filter'
defaults:
title: false
filters: false
filter_groups: false
filters:
info:
id: info
table: block_content_field_data
field: info
relationship: none
group_type: group
admin_label: ''
operator: contains
value: Comida
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
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: { }
plugin_id: string
entity_type: block_content
entity_field: info
filter_groups:
operator: AND
groups:
1: AND
display_extenders: { }
page_ifp:
display_plugin: page
id: page_ifp
display_title: 'Info filter page Paris'
position: 1
display_options:
path: test-info-paris
display_description: ''
title: 'Test info filter'
defaults:
title: false
display_extenders: { }

View File

@ -0,0 +1,27 @@
langcode: en
status: true
dependencies:
module:
- block_content
id: test_field_type
label: test_field_type
module: views
description: ''
tag: ''
base_table: block_content_field_data
base_field: id
display:
default:
display_options:
fields:
type:
field: type
id: type
table: block_content_field_data
plugin_id: field
entity_type: block_content
entity_field: type
display_plugin: default
display_title: Default
id: default
position: 0

View File

@ -0,0 +1,5 @@
name: 'Block Content Theme Suggestions Test'
type: module
description: 'Support module for testing.'
package: Testing
version: VERSION

View File

@ -0,0 +1,20 @@
<?php
/**
* @file
* Support module for testing.
*/
declare(strict_types=1);
use Drupal\block_content\BlockContentInterface;
/**
* Implements hook_preprocess_block().
*/
function block_content_theme_suggestions_test_preprocess_block(&$variables): void {
$block_content = $variables['elements']['content']['#block_content'] ?? NULL;
if ($block_content instanceof BlockContentInterface) {
$variables['label'] = $block_content->label();
}
}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Drupal\block_content_theme_suggestions_test\Hook;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Hook implementations for block_content_theme_suggestions_test.
*/
class BlockContentThemeSuggestionsTestHooks {
use StringTranslationTrait;
/**
* Implements hook_entity_extra_field_info().
*/
#[Hook('entity_extra_field_info')]
public function entityExtraFieldInfo(): array {
// Add an extra field to the test bundle.
$extra['node']['bundle_with_extra_field']['display']['block_content_extra_field_test'] = [
'label' => $this->t('Extra field'),
'description' => $this->t('Extra field description'),
'weight' => 0,
];
return $extra;
}
/**
* Implements hook_node_view().
*/
#[Hook('node_view')]
public function nodeView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, string $view_mode): void {
// Provide content for the extra field in the form of a content block.
if ($display->getComponent('block_content_extra_field_test')) {
$entity_type_manager = \Drupal::entityTypeManager();
// Load a block content entity with a known UUID created by test setup.
// @see \Drupal\Tests\block_content\Functional\BlockContentThemeSuggestionsTest::setUp()
$block_content = $entity_type_manager->getStorage('block_content')->loadByProperties([
'uuid' => 'b22c881a-bcfd-4d0c-a41d-3573327705df',
]);
$block_content = reset($block_content);
$build['block_content_extra_field_test'] = $entity_type_manager->getViewBuilder('block_content')->view($block_content);
}
}
/**
* Implements hook_theme().
*/
#[Hook('theme')]
public function theme(): array {
// It is necessary to explicitly register the template via hook_theme()
// because it is added via a module, not a theme.
return [
'block__block_content__view_type__basic__full' => [
'base hook' => 'block',
],
];
}
}

View File

@ -0,0 +1,3 @@
{{ label }}
<h1>I am a block content template for a specific bundle and view mode!</h1>

View File

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase;
/**
* Tests the Content Block entity's cache tags.
*
* @group block_content
*/
class BlockContentCacheTagsTest extends EntityCacheTagsTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function createEntity() {
$block_content_type = BlockContentType::create([
'id' => 'basic',
'label' => 'basic',
'revision' => FALSE,
]);
$block_content_type->save();
block_content_add_body_field($block_content_type->id());
// Create a "Llama" content block.
$block_content = BlockContent::create([
'info' => 'Llama',
'type' => 'basic',
'body' => [
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'format' => 'plain_text',
],
]);
$block_content->save();
return $block_content;
}
/**
* {@inheritdoc}
*
* @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
*/
protected function getAccessCacheContextsForEntity(EntityInterface $entity): array {
return [];
}
/**
* {@inheritdoc}
*
* Each comment must have a comment body, which always has a text format.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity): array {
return ['config:filter.format.plain_text'];
}
/**
* Tests that the block is cached with the correct contexts and tags.
*/
public function testBlock(): void {
$block = $this->drupalPlaceBlock('block_content:' . $this->entity->uuid());
$build = $this->container->get('entity_type.manager')->getViewBuilder('block')->view($block, 'block');
// Render the block.
$this->container->get('renderer')->renderRoot($build);
// Expected keys, contexts, and tags for the block.
// @see \Drupal\block\BlockViewBuilder::viewMultiple()
$expected_block_cache_keys = ['entity_view', 'block', $block->id()];
$expected_block_cache_tags = Cache::mergeTags(['block_view', 'rendered'], $block->getCacheTags());
$expected_block_cache_tags = Cache::mergeTags($expected_block_cache_tags, $block->getPlugin()->getCacheTags());
// Expected contexts and tags for the BlockContent entity.
// @see \Drupal\Core\Entity\EntityViewBuilder::getBuildDefaults().
$expected_entity_cache_tags = Cache::mergeTags(['block_content_view'], $this->entity->getCacheTags());
$expected_entity_cache_tags = Cache::mergeTags($expected_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
// Verify that what was render cached matches the above expectations.
$this->verifyRenderCache($expected_block_cache_keys, Cache::mergeTags($expected_block_cache_tags, $expected_entity_cache_tags), CacheableMetadata::createFromRenderArray($build));
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
/**
* Tests views contextual links on block content.
*
* @group block_content
*/
class BlockContentContextualLinksTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'contextual',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests contextual links.
*/
public function testBlockContentContextualLinks(): void {
$block_content = $this->createBlockContent();
$block = $this->placeBlock('block_content:' . $block_content->uuid());
$user = $this->drupalCreateUser([
'administer blocks',
'access contextual links',
]);
$this->drupalLogin($user);
$this->drupalGet('<front>');
$this->assertSession()->elementAttributeContains('css', 'div[data-contextual-id]', 'data-contextual-id', 'block:block=' . $block->id() . ':langcode=en|block_content:block_content=' . $block_content->id() . ':');
}
}

View File

@ -0,0 +1,339 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\BlockContentInterface;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Core\Database\Database;
// cspell:ignore testblock
/**
* Create a block and test saving it.
*
* @group block_content
*/
class BlockContentCreationTest extends BlockContentTestBase {
/**
* Modules to install.
*
* Enable dummy module that implements hook_block_insert() for exceptions and
* field_ui to edit display settings.
*
* @var array
*/
protected static $modules = ['block_content_test', 'dblog', 'field_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer blocks',
'administer block_content display',
'access block library',
'administer block content',
];
/**
* Sets the test up.
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->adminUser);
}
/**
* Creates a "Basic block" block and verifies its consistency in the database.
*/
public function testBlockContentCreation(): void {
$this->drupalLogin($this->adminUser);
// Create a block.
$edit = [];
$edit['info[0][value]'] = 'Test Block';
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalGet('block/add/basic');
$this->submitForm($edit, 'Save');
// Check that the Basic block has been created.
$this->assertSession()->pageTextContains('basic ' . $edit['info[0][value]'] . ' has been created.');
// Check that the view mode setting is hidden because only one exists.
$this->assertSession()->fieldNotExists('settings[view_mode]');
// Check that the block exists in the database.
$block = $this->getBlockByLabel($edit['info[0][value]']);
$this->assertNotEmpty($block, 'Content Block found in database.');
// Ensure a user with just the create permission can access the page.
$this->drupalLogin($this->drupalCreateUser([
'create basic block content',
]));
$this->drupalGet('block/add/basic');
$this->assertSession()->statusCodeEquals(200);
}
/**
* Creates a "Basic page" block with multiple view modes.
*/
public function testBlockContentCreationMultipleViewModes(): void {
// Add a new view mode and verify if it is selected as expected.
$this->drupalLogin($this->drupalCreateUser(['administer display modes']));
$this->drupalGet('admin/structure/display-modes/view/add/block_content');
$edit = [
'id' => 'test_view_mode',
'label' => 'Test View Mode',
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Saved the ' . $edit['label'] . ' view mode.');
$this->drupalLogin($this->adminUser);
// Create a block.
$edit = [];
$edit['info[0][value]'] = 'Test Block';
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalGet('block/add/basic');
$this->submitForm($edit, 'Save and configure');
// Save our block permanently
$this->submitForm(['region' => 'content'], 'Save block');
// Set test_view_mode as a custom display to be available on the list.
$this->drupalGet('admin/structure/block-content/manage/basic/display');
$custom_view_mode = [
'display_modes_custom[test_view_mode]' => 1,
];
$this->submitForm($custom_view_mode, 'Save');
// Go to the configure page and change the view mode.
$this->drupalGet('admin/structure/block/manage/stark_testblock');
// Test the available view mode options.
// Verify that the default view mode is available.
$this->assertSession()->optionExists('edit-settings-view-mode', 'default');
// Verify that the test view mode is available.
$this->assertSession()->optionExists('edit-settings-view-mode', 'test_view_mode');
$view_mode['settings[view_mode]'] = 'test_view_mode';
$this->submitForm($view_mode, 'Save block');
// Check that the view mode setting is shown because more than one exists.
$this->drupalGet('admin/structure/block/manage/stark_testblock');
$this->assertSession()->fieldExists('settings[view_mode]');
// Change the view mode.
$view_mode['region'] = 'content';
$view_mode['settings[view_mode]'] = 'test_view_mode';
$this->submitForm($view_mode, 'Save block');
// Go to the configure page and verify the view mode has changed.
$this->drupalGet('admin/structure/block/manage/stark_testblock');
$this->assertSession()->fieldValueEquals('settings[view_mode]', 'test_view_mode');
// Check that the block exists in the database.
$block = $this->getBlockByLabel($edit['info[0][value]']);
$this->assertNotEmpty($block, 'Content Block found in database.');
}
/**
* Tests the redirect workflow of creating a block_content and block.
*/
public function testBlockContentFormSubmitHandlers(): void {
$this->drupalLogin($this->adminUser);
// Create a block and place in block layout.
$this->drupalGet('/admin/content/block');
$this->clickLink('Add content block');
$this->assertSession()->addressEquals('/block/add/basic');
$edit = [];
$edit['info[0][value]'] = 'Test Block';
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->submitForm($edit, 'Save and configure');
$this->assertSession()->pageTextContains('basic ' . $edit['info[0][value]'] . ' has been created.');
$this->assertSession()->pageTextContains('Configure block');
// Verify when editing a block "Save and configure" does not appear.
$this->drupalGet('/admin/content/block/1');
$this->assertSession()->buttonNotExists('Save and configure');
// Create a block but go back to block library.
$edit = [];
$edit['info[0][value]'] = 'Test Block';
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalGet('block/add/basic');
$this->submitForm($edit, 'Save');
// Check that the Basic block has been created.
$this->assertSession()->pageTextContains('basic ' . $edit['info[0][value]'] . ' has been created.');
$this->assertSession()->addressEquals('/admin/content/block');
// Check that the user is redirected to the block library on edit.
$block = $this->getBlockByLabel($edit['info[0][value]']);
$this->drupalGet($block->toUrl('edit-form'));
$this->submitForm([
'info[0][value]' => 'Test Block Updated',
], 'Save');
$this->assertSession()->addressEquals('admin/content/block');
// Test with user who doesn't have permission to place a block.
$this->drupalLogin($this->drupalCreateUser(['administer block content']));
$this->drupalGet('block/add/basic');
$this->assertSession()->buttonNotExists('Save and configure');
}
/**
* Create a default content block.
*
* Creates a content block from defaults and ensures that the 'basic block'
* type is being used.
*/
public function testDefaultBlockContentCreation(): void {
$edit = [];
$edit['info[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
// Don't pass the content block type in the URL so the default is forced.
$this->drupalGet('block/add');
$this->submitForm($edit, 'Save');
// Check that the block has been created and that it is a basic block.
$this->assertSession()->pageTextContains('basic ' . $edit['info[0][value]'] . ' has been created.');
// Check that the block exists in the database.
$block = $this->getBlockByLabel($edit['info[0][value]']);
$this->assertNotEmpty($block, 'Default Content Block found in database.');
}
/**
* Verifies that a transaction rolls back the failed creation.
*/
public function testFailedBlockCreation(): void {
// Create a block.
try {
$this->createBlockContent('fail_creation');
$this->fail('Expected exception has not been thrown.');
}
catch (\Exception) {
// Expected exception; just continue testing.
}
$connection = Database::getConnection();
// Check that the block does not exist in the database.
$id = $connection->select('block_content_field_data', 'b')
->fields('b', ['id'])
->condition('info', 'fail_creation')
->execute()
->fetchField();
$this->assertFalse($id);
}
/**
* Tests deleting a block.
*/
public function testBlockDelete(): void {
// Create a block.
$edit = [];
$edit['info[0][value]'] = $this->randomMachineName(8);
$body = $this->randomMachineName(16);
$edit['body[0][value]'] = $body;
$this->drupalGet('block/add/basic');
$this->submitForm($edit, 'Save');
// Place the block.
$instance = [
'id' => mb_strtolower($edit['info[0][value]']),
'settings[label]' => $edit['info[0][value]'],
'region' => 'sidebar_first',
];
$block = BlockContent::load(1);
$url = 'admin/structure/block/add/block_content:' . $block->uuid() . '/' . $this->config('system.theme')->get('default');
$this->drupalGet($url);
$this->submitForm($instance, 'Save block');
$block = BlockContent::load(1);
// Test getInstances method.
$this->assertCount(1, $block->getInstances());
// Navigate to home page.
$this->drupalGet('');
$this->assertSession()->pageTextContains($body);
// Delete the block.
$this->drupalGet('admin/content/block/1/delete');
$this->assertSession()->pageTextContains('This will also remove 1 placed block instance.');
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextContains('The content block ' . $edit['info[0][value]'] . ' has been deleted.');
// Create another block and force the plugin cache to flush.
$edit2 = [];
$edit2['info[0][value]'] = $this->randomMachineName(8);
$body2 = $this->randomMachineName(16);
$edit2['body[0][value]'] = $body2;
$this->drupalGet('block/add/basic');
$this->submitForm($edit2, 'Save');
$this->assertSession()->responseNotContains('Error message');
// Create another block with no instances, and test we don't get a
// confirmation message about deleting instances.
$edit3 = [];
$edit3['info[0][value]'] = $this->randomMachineName(8);
$body = $this->randomMachineName(16);
$edit3['body[0][value]'] = $body;
$this->drupalGet('block/add/basic');
$this->submitForm($edit3, 'Save');
// Show the delete confirm form.
$this->drupalGet('admin/content/block/3/delete');
$this->assertSession()->pageTextNotContains('This will also remove');
}
/**
* Tests placed content blocks create a dependency in the block placement.
*/
public function testConfigDependencies(): void {
$block = $this->createBlockContent();
// Place the block.
$block_placement_id = mb_strtolower($block->label());
$instance = [
'id' => $block_placement_id,
'settings[label]' => $block->label(),
'region' => 'sidebar_first',
];
$block = BlockContent::load(1);
$url = 'admin/structure/block/add/block_content:' . $block->uuid() . '/' . $this->config('system.theme')->get('default');
$this->drupalGet($url);
$this->submitForm($instance, 'Save block');
$dependencies = \Drupal::service('config.manager')->findConfigEntityDependenciesAsEntities('content', [$block->getConfigDependencyName()]);
$block_placement = reset($dependencies);
$this->assertEquals($block_placement_id, $block_placement->id(), "The block placement config entity has a dependency on the block content entity.");
}
/**
* Load a block based on the label.
*/
private function getBlockByLabel(string $label): ?BlockContentInterface {
$blocks = \Drupal::entityTypeManager()
->getStorage('block_content')
->loadByProperties(['info' => $label]);
if (empty($blocks)) {
return NULL;
}
return reset($blocks);
}
}

View File

@ -0,0 +1,202 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContent;
/**
* Tests the listing of content blocks.
*
* Tests the fallback block content list when Views is disabled.
*
* @group block_content
* @see \Drupal\block\BlockContentListBuilder
* @see \Drupal\block_content\Tests\BlockContentListViewsTest
*/
class BlockContentListTest extends BlockContentTestBase {
/**
* A user with 'access block library' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser1;
/**
* A user with access to create and edit custom basic blocks.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser2;
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer blocks',
'access block library',
'create basic block content',
'edit any basic block content',
'delete any basic block content',
'translate configuration',
];
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'config_translation'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->baseUser1 = $this->drupalCreateUser(['access block library']);
$this->baseUser2 = $this->drupalCreateUser([
'access block library',
'create basic block content',
'edit any basic block content',
'delete any basic block content',
]);
}
/**
* Tests the region value when a new block is saved.
*/
public function testBlockRegionPlacement(): void {
$this->drupalLogin($this->drupalCreateUser($this->permissions));
$this->drupalGet("admin/structure/block/library/stark", ['query' => ['region' => 'content']]);
$this->clickLink('Add content block');
$this->assertSession()->statusCodeEquals(200);
$edit = [
'info[0][value]' => 'foo',
];
$this->submitForm($edit, 'Save');
$this->assertSession()->fieldValueEquals('region', 'content');
}
/**
* Tests the content block listing page with different permissions.
*/
public function testListing(): void {
// Test with the admin user.
$this->drupalLogin($this->drupalCreateUser(['access block library', 'administer block content']));
$this->drupalGet('admin/content/block');
// Test for the page title.
$this->assertSession()->titleEquals('Content blocks | Drupal');
// Test for the table.
$this->assertSession()->elementExists('xpath', '//div[@class="layout-content"]//table');
// Test the table header, two cells should be present.
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/thead/tr/th', 2);
// Test the contents of each th cell.
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[1]', 'Block description');
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[2]', 'Operations');
$label = 'Antelope';
$new_label = 'Albatross';
// Add a new entity using the operations link.
$this->clickLink('Add content block');
$this->assertSession()->statusCodeEquals(200);
$edit = [];
$edit['info[0][value]'] = $label;
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->submitForm($edit, 'Save');
// Confirm that once the user returns to the listing, the text of the label
// (versus elsewhere on the page).
$this->assertSession()->elementTextContains('xpath', '//td', $label);
// Check the number of table row cells.
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/tbody/tr[1]/td', 2);
// Check the contents of the row. The first cell contains the label,
// and the second contains the operations list.
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/tbody/tr[1]/td[1]', $label);
// Edit the entity using the operations link.
$blocks = $this->container
->get('entity_type.manager')
->getStorage('block_content')
->loadByProperties(['info' => $label]);
$block = reset($blocks);
if (!empty($block)) {
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id());
$this->clickLink('Edit');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->titleEquals("Edit content block $label | Drupal");
$edit = ['info[0][value]' => $new_label];
$this->submitForm($edit, 'Save');
}
else {
$this->fail('Did not find Albatross block in the database.');
}
// Confirm that once the user returns to the listing, the text of the label
// (versus elsewhere on the page).
$this->assertSession()->elementTextContains('xpath', '//td', $new_label);
// Delete the added entity using the operations link.
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id() . '/delete');
$this->clickLink('Delete');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->titleEquals("Are you sure you want to delete the content block $new_label? | Drupal");
$this->submitForm([], 'Delete');
// Verify that the text of the label and machine name does not appear in
// the list (though it may appear elsewhere on the page).
$this->assertSession()->elementTextNotContains('xpath', '//td', $new_label);
// Confirm that the empty text is displayed.
$this->assertSession()->pageTextContains('There are no content blocks yet.');
$block_content = BlockContent::create([
'info' => 'Non-reusable block',
'type' => 'basic',
'reusable' => FALSE,
]);
$block_content->save();
$this->drupalGet('admin/content/block');
// Confirm that the empty text is displayed.
$this->assertSession()->pageTextContains('There are no content blocks yet.');
// Confirm the non-reusable block is not on the page.
$this->assertSession()->pageTextNotContains('Non-reusable block');
$this->drupalLogout();
// Create test block for other user tests.
$test_block = $this->createBlockContent($label);
// Test as a user with view only permissions.
$this->drupalLogin($this->baseUser1);
$this->drupalGet('admin/content/block');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkNotExists('Add content block');
$this->assertSession()->linkByHrefNotExists('admin/content/block/' . $test_block->id());
$this->assertSession()->linkByHrefNotExists('admin/content/block/' . $test_block->id() . '/delete');
$this->drupalLogout();
// Test as a user with permission to create/edit/delete basic blocks.
$this->drupalLogin($this->baseUser2);
$this->drupalGet('admin/content/block');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkExists('Add content block');
$this->assertSession()->linkByHrefExists('admin/content/block/' . $test_block->id());
$this->assertSession()->linkByHrefExists('admin/content/block/' . $test_block->id() . '/delete');
}
}

View File

@ -0,0 +1,204 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContent;
/**
* Tests the Views-powered listing of content blocks.
*
* @group block_content
* @see \Drupal\block\BlockContentListBuilder
* @see \Drupal\block_content\Tests\BlockContentListTest
*/
class BlockContentListViewsTest extends BlockContentTestBase {
/**
* A user with 'access block library' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser1;
/**
* A user with access to create and edit custom basic blocks.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser2;
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer blocks',
'access block library',
'create basic block content',
'edit any basic block content',
'delete any basic block content',
'translate configuration',
];
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'block_content',
'config_translation',
'views',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->baseUser1 = $this->drupalCreateUser(['access block library']);
$this->baseUser2 = $this->drupalCreateUser([
'access block library',
'create basic block content',
'edit any basic block content',
'delete any basic block content',
]);
}
/**
* Tests the content block listing page.
*/
public function testListing(): void {
// Test with an admin user.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content/block');
// Test for the page title.
$this->assertSession()->titleEquals('Content blocks | Drupal');
// Test for the exposed filters.
$this->assertSession()->fieldExists('info');
$this->assertSession()->fieldExists('type');
// Test for the table.
$this->assertSession()->elementExists('xpath', '//div[@class="layout-content"]//table');
// Test the table header, four cells should be present.
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/thead/tr/th', 4);
// Test the contents of each th cell.
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[1]', 'Block description');
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[2]', 'Block type');
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[3]', 'Updated Sort ascending');
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[4]', 'Operations');
$label = 'Antelope';
$new_label = 'Albatross';
// Add a new entity using the operations link.
$this->clickLink('Add content block');
$this->assertSession()->statusCodeEquals(200);
$edit = [];
$edit['info[0][value]'] = $label;
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->submitForm($edit, 'Save');
// Confirm that once the user returns to the listing, the text of the label
// (versus elsewhere on the page).
$this->assertSession()->elementTextContains('xpath', '//td/a', $label);
// Check the number of table row cells.
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/tbody/tr/td', 4);
// Check the contents of each row cell. The first cell contains the label,
// the second contains the machine name, and the third contains the
// operations list.
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/tbody/tr/td/a', $label);
// Edit the entity using the operations link.
$blocks = $this->container
->get('entity_type.manager')
->getStorage('block_content')
->loadByProperties(['info' => $label]);
$block = reset($blocks);
if (!empty($block)) {
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id());
$this->clickLink('Edit');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->titleEquals("Edit content block $label | Drupal");
$edit = ['info[0][value]' => $new_label];
$this->submitForm($edit, 'Save');
}
else {
$this->fail('Did not find Albatross block in the database.');
}
// Confirm that once the user returns to the listing, the text of the label
// (versus elsewhere on the page).
$this->assertSession()->elementTextContains('xpath', '//td/a', $new_label);
// Delete the added entity using the operations link.
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id() . '/delete');
$this->clickLink('Delete');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->titleEquals("Are you sure you want to delete the content block $new_label? | Drupal");
$this->submitForm([], 'Delete');
// Verify that the text of the label and machine name does not appear in
// the list (though it may appear elsewhere on the page).
$this->assertSession()->elementTextNotContains('xpath', '//td', $new_label);
// Confirm that the empty text is displayed.
$this->assertSession()->pageTextContains('There are no content blocks available.');
$this->assertSession()->linkExists('content block');
$block_content = BlockContent::create([
'info' => 'Non-reusable block',
'type' => 'basic',
'reusable' => FALSE,
]);
$block_content->save();
$this->drupalGet('admin/content/block');
// Confirm that the empty text is displayed.
$this->assertSession()->pageTextContains('There are no content blocks available.');
// Confirm the non-reusable block is not on the page.
$this->assertSession()->pageTextNotContains('Non-reusable block');
$this->drupalLogout();
// Create test block for other user tests.
$test_block = $this->createBlockContent($label);
// Test as a user with view only permissions.
$this->drupalLogin($this->baseUser1);
$this->drupalGet('admin/content/block');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkNotExists('Add content block');
$matches = $this->xpath('//td[1]');
$actual = $matches[0]->getText();
$this->assertEquals($label, $actual, 'Label found for test block.');
$this->assertSession()->linkNotExists('Edit');
$this->assertSession()->linkNotExists('Delete');
$this->assertSession()->linkByHrefNotExists('admin/content/block/' . $test_block->id() . '/delete');
$this->drupalLogout();
// Test as a user with permission to create/edit/delete basic blocks.
$this->drupalLogin($this->baseUser2);
$this->drupalGet('admin/content/block');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkExists('Add content block');
$matches = $this->xpath('//td/a');
$actual = $matches[0]->getText();
$this->assertEquals($label, $actual, 'Label found for test block.');
$this->assertSession()->linkByHrefExists('admin/content/block/' . $test_block->id());
$this->assertSession()->linkByHrefExists('admin/content/block/' . $test_block->id() . '/delete');
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
/**
* Create a block and test block access by attempting to view the block.
*
* @group block_content
*/
class BlockContentPageViewTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Checks block edit and fallback functionality.
*/
public function testPageEdit(): void {
$this->drupalLogin($this->adminUser);
$block = $this->createBlockContent();
// Attempt to view the block.
$this->drupalGet('block-content/' . $block->id());
// Ensure user was able to view the block.
$this->assertSession()->statusCodeEquals(200);
$this->drupalGet('<front>');
$this->assertSession()->pageTextContains('This block is broken or missing. You may be missing content or you might need to install the original module.');
}
}

View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
/**
* Block content revision delete form test.
*
* @group block_content
* @coversDefaultClass \Drupal\Core\Entity\Form\RevisionDeleteForm
*/
class BlockContentRevisionDeleteTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $permissions = [
'view any basic block content history',
'delete any basic block content revisions',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->adminUser);
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests revision delete.
*/
public function testDeleteForm(): void {
$entity = $this->createBlockContent(save: FALSE)
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp())
->setRevisionTranslationAffected(TRUE);
$entity->setNewRevision();
$entity->save();
$revisionId = $entity->getRevisionId();
// Cannot delete latest revision.
$this->drupalGet($entity->toUrl('revision-delete-form'));
$this->assertSession()->statusCodeEquals(403);
// Create a new non default revision.
$entity
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 5pm'))->getTimestamp())
->setRevisionTranslationAffected(TRUE)
->setNewRevision();
$entity->isDefaultRevision(FALSE);
$entity->save();
$nonDefaultRevisionId = $entity->getRevisionId();
// Reload the default entity.
$revision = \Drupal::entityTypeManager()->getStorage('block_content')
->loadRevision($revisionId);
// Cannot delete default revision.
$this->drupalGet($revision->toUrl('revision-delete-form'));
$this->assertSession()->statusCodeEquals(403);
$this->assertFalse($revision->access('delete revision', $this->adminUser, FALSE));
// Reload the non default entity.
$revision2 = \Drupal::entityTypeManager()->getStorage('block_content')
->loadRevision($nonDefaultRevisionId);
$this->drupalGet($revision2->toUrl('revision-delete-form'));
$this->assertSession()->pageTextContains('Are you sure you want to delete the revision from Sun, 11 Jan 2009 - 17:00?');
$this->assertSession()->buttonExists('Delete');
$this->assertSession()->linkExists('Cancel');
$this->assertTrue($revision2->access('delete revision', $this->adminUser, FALSE));
$countRevisions = static function (): int {
return (int) \Drupal::entityTypeManager()->getStorage('block_content')
->getQuery()
->accessCheck(FALSE)
->allRevisions()
->count()
->execute();
};
$count = $countRevisions();
$this->submitForm([], 'Delete');
$this->assertEquals($count - 1, $countRevisions());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals(sprintf('admin/content/block/%s/revisions', $entity->id()));
$this->assertSession()->pageTextContains(sprintf('Revision from Sun, 11 Jan 2009 - 17:00 of basic %s has been deleted.', $entity->label()));
$this->assertSession()->elementsCount('css', 'table tbody tr', 1);
}
}

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
/**
* Block content revision form test.
*
* @group block_content
* @coversDefaultClass \Drupal\Core\Entity\Form\RevisionRevertForm
*/
class BlockContentRevisionRevertTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $permissions = [
'view any basic block content history',
'revert any basic block content revisions',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->adminUser);
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests revision revert.
*/
public function testRevertForm(): void {
$entity = $this->createBlockContent(save: FALSE)
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp())
->setRevisionTranslationAffected(TRUE);
$entity->setNewRevision();
$entity->save();
$revisionId = $entity->getRevisionId();
// Cannot revert latest revision.
$this->drupalGet($entity->toUrl('revision-revert-form'));
$this->assertSession()->statusCodeEquals(403);
// Create a new non default revision.
$entity
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 5pm'))->getTimestamp())
->setRevisionTranslationAffected(TRUE)
->setNewRevision();
$entity->isDefaultRevision(FALSE);
$entity->save();
$nonDefaultRevisionId = $entity->getRevisionId();
// Reload the default entity.
$revision = \Drupal::entityTypeManager()->getStorage('block_content')
->loadRevision($revisionId);
// Cannot revert default revision.
$this->drupalGet($revision->toUrl('revision-revert-form'));
$this->assertSession()->statusCodeEquals(403);
$this->assertFalse($revision->access('revert', $this->adminUser, FALSE));
// Reload the non default entity.
$revision2 = \Drupal::entityTypeManager()->getStorage('block_content')
->loadRevision($nonDefaultRevisionId);
$this->drupalGet($revision2->toUrl('revision-revert-form'));
$this->assertSession()->pageTextContains('Are you sure you want to revert to the revision from Sun, 11 Jan 2009 - 17:00?');
$this->assertSession()->buttonExists('Revert');
$this->assertSession()->linkExists('Cancel');
$this->assertTrue($revision2->access('revert', $this->adminUser, FALSE));
$countRevisions = static function (): int {
return (int) \Drupal::entityTypeManager()->getStorage('block_content')
->getQuery()
->accessCheck(FALSE)
->allRevisions()
->count()
->execute();
};
$count = $countRevisions();
$this->submitForm([], 'Revert');
$this->assertEquals($count + 1, $countRevisions());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals(sprintf('admin/content/block/%s/revisions', $entity->id()));
$this->assertSession()->pageTextContains(sprintf('basic %s has been reverted to the revision from Sun, 11 Jan 2009 - 17:00.', $entity->label()));
// Three rows, from the top: the newly reverted revision, the revision from
// 5pm, and the revision from 4pm.
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
}
}

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
/**
* Block content version history test.
*
* @group block_content
* @coversDefaultClass \Drupal\Core\Entity\Controller\VersionHistoryController
*/
class BlockContentRevisionVersionHistoryTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $permissions = [
'view any basic block content history',
'revert any basic block content revisions',
'delete any basic block content revisions',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->adminUser);
}
/**
* Tests version history page.
*/
public function testVersionHistory(): void {
$entity = $this->createBlockContent(save: FALSE);
$entity
->setInfo('first revision')
->setRevisionCreationTime((new \DateTimeImmutable('1st June 2020 7am'))->getTimestamp())
->setRevisionLogMessage('first revision log')
->setRevisionUser($this->drupalCreateUser(name: 'first author'))
->setNewRevision();
$entity->save();
$entity
->setInfo('second revision')
->setRevisionCreationTime((new \DateTimeImmutable('2nd June 2020 8am'))->getTimestamp())
->setRevisionLogMessage('second revision log')
->setRevisionUser($this->drupalCreateUser(name: 'second author'))
->setNewRevision();
$entity->save();
$entity
->setInfo('third revision')
->setRevisionCreationTime((new \DateTimeImmutable('3rd June 2020 9am'))->getTimestamp())
->setRevisionLogMessage('third revision log')
->setRevisionUser($this->drupalCreateUser(name: 'third author'))
->setNewRevision();
$entity->save();
$this->drupalGet($entity->toUrl('version-history'));
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
// Order is newest to oldest revision by creation order.
$row1 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(1)');
// Latest revision does not have revert or delete revision operation.
$this->assertSession()->elementNotExists('named', ['link', 'Revert'], $row1);
$this->assertSession()->elementNotExists('named', ['link', 'Delete'], $row1);
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'Current revision');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'third revision log');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', '3 Jun 2020 - 09:00 by third author');
$row2 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(2)');
$this->assertSession()->elementExists('named', ['link', 'Revert'], $row2);
$this->assertSession()->elementExists('named', ['link', 'Delete'], $row2);
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', 'second revision log');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', '2 Jun 2020 - 08:00 by second author');
$row3 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(3)');
$this->assertSession()->elementExists('named', ['link', 'Revert'], $row3);
$this->assertSession()->elementExists('named', ['link', 'Delete'], $row3);
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', 'first revision log');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', '1 Jun 2020 - 07:00 by first author');
}
}

View File

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContent;
/**
* Tests $block_content->save() for saving content.
*
* @group block_content
*/
class BlockContentSaveTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Sets the test up.
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->adminUser);
}
/**
* Checks whether content block IDs are saved properly during an import.
*/
public function testImport(): void {
// Content block ID must be a number that is not in the database.
$max_id = (int) \Drupal::entityQueryAggregate('block_content')
->accessCheck(FALSE)
->aggregate('id', 'max')
->execute()[0]['id_max'];
$test_id = $max_id + mt_rand(1000, 1000000);
$info = $this->randomMachineName(8);
$block_array = [
'info' => $info,
'body' => ['value' => $this->randomMachineName(32)],
'type' => 'basic',
'id' => $test_id,
];
$block = BlockContent::create($block_array);
$block->enforceIsNew(TRUE);
$block->save();
// Verify that block_submit did not wipe the provided id.
$this->assertEquals($test_id, $block->id(), 'Block imported using provide id');
// Test the import saved.
$block_by_id = BlockContent::load($test_id);
$this->assertNotEmpty($block_by_id, 'Content block load by block ID.');
$this->assertSame($block_array['body']['value'], $block_by_id->body->value);
}
/**
* Tests determining changes in hook_block_presave().
*
* Verifies the static block load cache is cleared upon save.
*/
public function testDeterminingChanges(): void {
// Initial creation.
$block = $this->createBlockContent('test_changes');
// Creating a block should set the changed date to the current time
// which is always greater than the time set by hooks we're testing.
$this->assertGreaterThan(979534800, $block->getChangedTime(), 'Creating a block sets default "changed" timestamp.');
// Update the block without applying changes.
$block->save();
$this->assertEquals('test_changes', $block->label(), 'No changes have been determined.');
// Apply changes.
$block->setInfo('updated');
$block->save();
// The hook implementations block_content_test_block_content_presave() and
// block_content_test_block_content_update() determine changes and change
// the title as well as programmatically set the 'changed' timestamp.
$this->assertEquals('updated_presave_update', $block->label(), 'Changes have been determined.');
$this->assertEquals(979534800, $block->getChangedTime(), 'Saving a content block uses "changed" timestamp set in presave hook.');
// Test the static block load cache to be cleared.
$block = BlockContent::load($block->id());
$this->assertEquals('updated_presave', $block->label(), 'Static cache has been cleared.');
}
/**
* Tests saving a block on block insert.
*
* This test ensures that a block has been fully saved when
* hook_block_content_insert() is invoked, so that the block can be saved
* again in a hook implementation without errors.
*
* @see block_test_block_insert()
*/
public function testBlockContentSaveOnInsert(): void {
// block_content_test_block_content_insert() triggers a save on insert if
// the title equals 'new'.
$block = $this->createBlockContent('new');
$this->assertEquals('BlockContent ' . $block->id(), $block->label(), 'Content block saved on block insert.');
}
}

View File

@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Tests\BrowserTestBase;
/**
* Sets up block content types.
*/
abstract class BlockContentTestBase extends BrowserTestBase {
/**
* Profile to use.
*
* @var string
*/
protected $profile = 'testing';
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer blocks',
'access block library',
'administer block types',
'administer block content',
];
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content'];
/**
* Whether or not to auto-create the basic block type during setup.
*
* @var bool
*/
protected $autoCreateBasicBlockType = TRUE;
/**
* Sets the test up.
*/
protected function setUp(): void {
parent::setUp();
if ($this->autoCreateBasicBlockType) {
$this->createBlockContentType(['id' => 'basic'], TRUE);
}
$this->adminUser = $this->drupalCreateUser($this->permissions);
$this->drupalPlaceBlock('local_actions_block');
}
/**
* Creates a content block.
*
* @param bool|string $title
* (optional) Title of block. When no value is given uses a random name.
* Defaults to FALSE.
* @param string $bundle
* (optional) Bundle name. Defaults to 'basic'.
* @param bool $save
* (optional) Whether to save the block. Defaults to TRUE.
*
* @return \Drupal\block_content\Entity\BlockContent
* Created content block.
*/
protected function createBlockContent($title = FALSE, $bundle = 'basic', $save = TRUE) {
$title = $title ?: $this->randomMachineName();
$block_content = BlockContent::create([
'info' => $title,
'type' => $bundle,
'langcode' => 'en',
]);
if ($block_content && $save === TRUE) {
$block_content->save();
}
return $block_content;
}
/**
* Creates a block type (bundle).
*
* @param array|string $values
* (deprecated) The variable $values as string is deprecated. Provide as an
* array as parameter. The value to create the block content type. If
* $values is an array it should be like: ['id' => 'foo', 'label' => 'Foo'].
* If $values is a string, it will be considered that it represents the
* label.
* @param bool $create_body
* Whether or not to create the body field.
*
* @return \Drupal\block_content\Entity\BlockContentType
* Created block type.
*/
protected function createBlockContentType($values, $create_body = FALSE) {
if (is_string($values)) {
@trigger_error('Using the variable $values as string is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Provide an array as parameter. See https://www.drupal.org/node/3473739', E_USER_DEPRECATED);
}
if (is_array($values)) {
if (!isset($values['id'])) {
do {
$id = $this->randomMachineName(8);
} while (BlockContentType::load($id));
}
else {
$id = $values['id'];
}
$values += [
'id' => $id,
'label' => $id,
'revision' => FALSE,
];
$bundle = BlockContentType::create($values);
}
else {
$bundle = BlockContentType::create([
'id' => $values,
'label' => $values,
'revision' => FALSE,
]);
}
$bundle->save();
if ($create_body) {
block_content_add_body_field($bundle->id());
}
return $bundle;
}
}

View File

@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
/**
* Tests block content theme suggestions.
*
* @group block_content
*/
class BlockContentThemeSuggestionsTest extends BlockContentTestBase {
/**
* Path prefix for the field UI for the test bundle.
*
* @var string
*/
const FIELD_UI_PREFIX = 'admin/structure/types/manage/bundle_with_extra_field';
/**
* The UUID for a block content entity.
*/
protected string $uuid = 'b22c881a-bcfd-4d0c-a41d-3573327705df';
/**
* {@inheritdoc}
*/
protected static $modules = [
'field_ui',
'layout_builder',
'node',
'block_content_theme_suggestions_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a block with a known UUID.
$block = $this->createBlockContent('Example block!', 'basic', FALSE);
$block->set('uuid', $this->uuid);
$block->save();
}
/**
* Test suggestions for content blocks.
*/
public function testBlockContentThemeSuggestionsContent(): void {
$this->drupalLogin($this->adminUser);
$this->drupalPlaceBlock('block_content:' . $this->uuid);
$this->drupalGet('');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('Example block!');
$this->assertSession()->pageTextContainsOnce('I am a block content template for a specific bundle and view mode!');
}
/**
* Test suggestions for content blocks within extra fields blocks.
*/
public function testBlockContentThemeSuggestionsExtraField(): void {
// Extra field blocks are a block plugin provided by layout builder, so
// enable layouts for the test bundle and view a node of that bundle.
// A test module injects an extra field referencing a block content entity.
// @see block_content_theme_suggestions_test.module
// @see \Drupal\block_content_theme_suggestions_test\Hook\BlockContentThemeSuggestionsTestHooks
$this->drupalLogin($this->drupalCreateUser([
'configure any layout',
'administer node display',
]));
$this->createContentType(['type' => 'bundle_with_extra_field']);
$this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
$this->submitForm(['layout[enabled]' => TRUE], 'Save');
$node = $this->createNode([
'type' => 'bundle_with_extra_field',
'title' => 'The first node title',
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('Example block!');
$this->assertSession()->pageTextContains('I am a block content template for a specific bundle and view mode!');
}
}

View File

@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
/**
* Tests the block content translation UI.
*
* @group block_content
*/
class BlockContentTranslationUITest extends ContentTranslationUITestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'content_translation',
'block',
'field_ui',
'block_content',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $defaultCacheContexts = [
'languages:language_interface',
'session',
'theme',
'url.path',
'url.query_args',
'user.permissions',
'user.roles:authenticated',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->entityTypeId = 'block_content';
$this->bundle = 'basic';
$this->testLanguageSelector = FALSE;
parent::setUp();
$this->doSetup();
$this->drupalPlaceBlock('page_title_block');
}
/**
* {@inheritdoc}
*/
protected function setupBundle(): void {
// Create the basic bundle since it is provided by standard.
$bundle = BlockContentType::create([
'id' => $this->bundle,
'label' => $this->bundle,
'revision' => FALSE,
]);
$bundle->save();
}
/**
* {@inheritdoc}
*/
public function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), [
'translate any entity',
'access administration pages',
'administer blocks',
'administer block_content fields',
'access block library',
'create basic block content',
'edit any basic block content',
'delete any basic block content',
]);
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return ['info' => $this->randomMachineName()] + parent::getNewEntityValues($langcode);
}
/**
* Returns an edit array containing the values to be posted.
*/
protected function getEditValues($values, $langcode, $new = FALSE) {
$edit = parent::getEditValues($values, $langcode, $new);
foreach ($edit as $property => $value) {
if ($property == 'info') {
$edit['info[0][value]'] = $value;
unset($edit[$property]);
}
}
return $edit;
}
/**
* {@inheritdoc}
*/
protected function doTestBasicTranslation(): void {
parent::doTestBasicTranslation();
// Ensure that a block translation can be created using the same description
// as in the original language.
$default_langcode = $this->langcodes[0];
$values = $this->getNewEntityValues($default_langcode);
$storage = \Drupal::entityTypeManager()->getStorage($this->entityTypeId);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $storage->create(['type' => 'basic'] + $values);
$entity->save();
$entity->addTranslation('it', $values);
try {
$entity->save();
}
catch (\Exception) {
$this->fail('Blocks can have translations with the same "info" value.');
}
// Check that the translate operation link is shown.
$this->drupalGet('admin/content/block');
$this->assertSession()->linkByHrefExists('admin/content/block/' . $entity->id() . '/translations');
}
/**
* {@inheritdoc}
*/
protected function doTestTranslationEdit(): void {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {
// We only want to test the title for non-english translations.
if ($langcode != 'en') {
$options = ['language' => $languages[$langcode]];
$url = $entity->toUrl('edit-form', $options);
$this->drupalGet($url);
$this->assertSession()->pageTextContains("Edit {$entity->bundle()} {$entity->getTranslation($langcode)->label()} [{$languages[$langcode]->getName()} translation]");
}
}
}
}

View File

@ -0,0 +1,266 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait;
/**
* Ensures that block type functions work correctly.
*
* @group block_content
*/
class BlockContentTypeTest extends BlockContentTestBase {
use AssertBreadcrumbTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['field_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer block content',
'administer blocks',
'administer block_content fields',
'administer block types',
'administer block content',
'access block library',
];
/**
* Whether or not to create an initial block type.
*
* @var bool
*/
protected $autoCreateBasicBlockType = FALSE;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests the order of the block content types on the add page.
*/
public function testBlockContentAddPageOrder(): void {
$this->createBlockContentType(['id' => 'bundle_1', 'label' => 'Bundle 1']);
$this->createBlockContentType(['id' => 'bundle_2', 'label' => 'Aaa Bundle 2']);
$this->drupalLogin($this->adminUser);
$this->drupalGet('block/add');
$this->assertSession()->pageTextMatches('/Aaa Bundle 2(.*)Bundle 1/');
}
/**
* Tests creating a block type programmatically and via a form.
*/
public function testBlockContentTypeCreation(): void {
// Log in a test user.
$this->drupalLogin($this->adminUser);
// Test the page with no block-types.
$this->drupalGet('block/add');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('You have not created any block types yet');
$this->clickLink('block type creation page');
// Create a block type via the user interface.
$edit = [
'id' => 'foo',
'label' => 'title for foo',
];
$this->submitForm($edit, 'Save and manage fields');
// Asserts that form submit redirects to the expected manage fields page.
$this->assertSession()->addressEquals('admin/structure/block-content/manage/' . $edit['id'] . '/fields');
$block_type = BlockContentType::load('foo');
$this->assertInstanceOf(BlockContentType::class, $block_type);
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('block_content', 'foo');
$this->assertTrue(isset($field_definitions['body']), 'Body field created when using the UI to create block content types.');
// Check that the block type was created in site default language.
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
$this->assertEquals($block_type->language()->getId(), $default_langcode);
// Create block types programmatically.
$this->createBlockContentType(['id' => 'basic'], TRUE);
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('block_content', 'basic');
$this->assertTrue(isset($field_definitions['body']), "Body field for 'basic' block type created when using the testing API to create block content types.");
$this->createBlockContentType(['id' => 'other']);
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('block_content', 'other');
$this->assertFalse(isset($field_definitions['body']), "Body field for 'other' block type not created when using the testing API to create block content types.");
$block_type = BlockContentType::load('other');
$this->assertInstanceOf(BlockContentType::class, $block_type);
$this->drupalGet('block/add/' . $block_type->id());
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests editing a block type using the UI.
*/
public function testBlockContentTypeEditing(): void {
$this->drupalPlaceBlock('system_breadcrumb_block');
// Now create an initial block-type.
$this->createBlockContentType(['id' => 'basic'], TRUE);
$this->drupalLogin($this->adminUser);
// We need two block types to prevent /block/add redirecting.
$this->createBlockContentType(['id' => 'other']);
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('block_content', 'other');
$this->assertFalse(isset($field_definitions['body']), 'Body field was not created when using the API to create block content types.');
// Verify that title and body fields are displayed.
$this->drupalGet('block/add/basic');
$this->assertSession()->pageTextContains('Block description');
$this->assertNotEmpty($this->cssSelect('#edit-body-0-value'), 'Body field was found.');
// Change the block type name.
$edit = [
'label' => 'Bar',
];
$this->drupalGet('admin/structure/block-content/manage/basic');
$this->assertSession()->titleEquals('Edit basic block type | Drupal');
$this->submitForm($edit, 'Save');
$front_page_path = Url::fromRoute('<front>')->toString();
$this->assertBreadcrumb('admin/structure/block-content/manage/basic/fields', [
$front_page_path => 'Home',
'admin/structure/block-content' => 'Block types',
'admin/structure/block-content/manage/basic' => 'Edit Bar',
]);
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
$this->drupalGet('block/add');
$this->assertSession()->pageTextContains('Bar');
$this->clickLink('Bar');
// Verify that the original machine name was used in the URL.
$this->assertSession()->addressEquals(Url::fromRoute('block_content.add_form', ['block_content_type' => 'basic']));
// Remove the body field.
$this->drupalGet('admin/structure/block-content/manage/basic/fields/block_content.basic.body/delete');
$this->submitForm([], 'Delete');
// Resave the settings for this type.
$this->drupalGet('admin/structure/block-content/manage/basic');
$this->submitForm([], 'Save');
// Check that the body field doesn't exist.
$this->drupalGet('block/add/basic');
$this->assertEmpty($this->cssSelect('#edit-body-0-value'), 'Body field was not found.');
}
/**
* Tests deleting a block type that still has content.
*/
public function testBlockContentTypeDeletion(): void {
// Now create an initial block-type.
$this->createBlockContentType(['id' => 'basic'], TRUE);
// Create a block type programmatically.
$type = $this->createBlockContentType(['id' => 'foo']);
$this->drupalLogin($this->adminUser);
// Add a new block of this type.
$block = $this->createBlockContent(FALSE, 'foo');
// Attempt to delete the block type, which should not be allowed.
$this->drupalGet('admin/structure/block-content/manage/' . $type->id() . '/delete');
$this->assertSession()->pageTextContains($type->label() . ' is used by 1 content block on your site. You can not remove this block type until you have removed all of the ' . $type->label() . ' blocks.');
$this->assertSession()->pageTextNotContains('This action cannot be undone.');
// Delete the block.
$block->delete();
// Attempt to delete the block type, which should now be allowed.
$this->drupalGet('admin/structure/block-content/manage/' . $type->id() . '/delete');
$this->assertSession()->pageTextContains('Are you sure you want to delete the block type ' . $type->id() . '?');
$this->assertSession()->pageTextContains('This action cannot be undone.');
}
/**
* Tests that redirects work as expected when multiple block types exist.
*/
public function testsBlockContentAddTypes(): void {
// Now create an initial block-type.
$this->createBlockContentType(['id' => 'basic'], TRUE);
$this->drupalLogin($this->adminUser);
// Create two block types programmatically.
$this->createBlockContentType(['id' => 'foo']);
$this->createBlockContentType(['id' => 'bar']);
// Get the content block storage.
$storage = $this->container
->get('entity_type.manager')
->getStorage('block_content');
// Install all themes.
$themes = ['olivero', 'stark', 'claro'];
\Drupal::service('theme_installer')->install($themes);
$theme_settings = $this->config('system.theme');
foreach ($themes as $default_theme) {
// Change the default theme.
$theme_settings->set('default', $default_theme)->save();
$this->drupalPlaceBlock('local_actions_block');
// For each installed theme, go to its block page and test the redirects.
foreach ($themes as $theme) {
// Test that adding a block from the 'place blocks' form sends you to
// the block configure form.
$path = $theme == $default_theme ? 'admin/structure/block' : "admin/structure/block/list/$theme";
$this->drupalGet($path);
$this->clickLink('Place block');
$this->clickLink('Add content block');
$this->clickLink('foo');
// Create a new block.
$edit = ['info[0][value]' => $this->randomMachineName(8)];
$this->submitForm($edit, 'Save and configure');
$blocks = $storage->loadByProperties(['info' => $edit['info[0][value]']]);
if (!empty($blocks)) {
$block = reset($blocks);
$this->assertSession()->addressEquals(Url::fromRoute('block.admin_add', ['plugin_id' => 'block_content:' . $block->uuid(), 'theme' => $theme]));
$this->submitForm(['region' => 'content'], 'Save block');
$this->assertSession()->addressEquals(Url::fromRoute('block.admin_display_theme', ['theme' => $theme], ['query' => ['block-placement' => $theme . '-' . Html::getClass($edit['info[0][value]'])]]));
}
else {
$this->fail('Could not load created block.');
}
}
}
// Test that adding a block from the 'content blocks list' doesn't send you
// to the block configure form.
$this->drupalGet('admin/content/block');
$this->clickLink('Add content block');
$this->clickLink('foo');
$edit = ['info[0][value]' => $this->randomMachineName(8)];
$this->submitForm($edit, 'Save');
$blocks = $storage->loadByProperties(['info' => $edit['info[0][value]']]);
if (!empty($blocks)) {
$this->assertSession()->addressEquals(Url::fromRoute('entity.block_content.collection'));
}
else {
$this->fail('Could not load created block.');
}
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
/**
* Generic module test for block_content.
*
* @group block_content
*/
class GenericTest extends GenericModuleTestBase {}

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
/**
* Tests block_content local action links.
*
* @group block_content
*/
class LocalActionTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->adminUser);
}
/**
* Tests the block_content_add_action link.
*/
public function testAddContentBlockLink(): void {
// Verify that the link takes you straight to the block form if there's only
// one type.
$this->drupalGet('/admin/content/block');
$this->clickLink('Add content block');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals('/block/add/basic');
$type = $this->randomMachineName();
$this->createBlockContentType([
'id' => $type,
'label' => $type,
]);
// Verify that the link takes you to the block add page if there's more than
// one type.
$this->drupalGet('/admin/content/block');
$this->clickLink('Add content block');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals('/block/add');
}
}

View File

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait;
/**
* Create a block and test block edit functionality.
*
* @group block_content
*/
class PageEditTest extends BlockContentTestBase {
use AssertBreadcrumbTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
$this->drupalPlaceBlock('system_breadcrumb_block');
}
/**
* Checks block edit functionality.
*/
public function testPageEdit(): void {
$this->drupalLogin($this->adminUser);
$title_key = 'info[0][value]';
$body_key = 'body[0][value]';
// Create block to edit.
$edit = [];
$edit['info[0][value]'] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
$this->drupalGet('block/add/basic');
$this->submitForm($edit, 'Save');
// Check that the block exists in the database.
$blocks = \Drupal::entityQuery('block_content')
->accessCheck(FALSE)
->condition('info', $edit['info[0][value]'])
->execute();
$block = BlockContent::load(reset($blocks));
$this->assertNotEmpty($block, 'Content block found in database.');
// Load the edit page.
$this->drupalGet('admin/content/block/' . $block->id());
$this->assertSession()->fieldValueEquals($title_key, $edit[$title_key]);
$this->assertSession()->fieldValueEquals($body_key, $edit[$body_key]);
// Edit the content of the block.
$edit = [];
$edit[$title_key] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
// Stay on the current page, without reloading.
$this->submitForm($edit, 'Save');
// Edit the same block, creating a new revision.
$this->drupalGet("admin/content/block/" . $block->id());
$edit = [];
$edit['info[0][value]'] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
$edit['revision'] = TRUE;
$this->submitForm($edit, 'Save');
// Ensure that the block revision has been created.
$revised_block = BlockContent::load($block->id());
$this->assertNotSame($block->getRevisionId(), $revised_block->getRevisionId(), 'A new revision has been created.');
// Test deleting the block.
$this->drupalGet("admin/content/block/" . $revised_block->id());
$this->clickLink('Delete');
$this->assertSession()->pageTextContains('Are you sure you want to delete the content block ' . $revised_block->label() . '?');
// Test breadcrumb.
$trail = [
'' => 'Home',
'admin/content/block' => 'Content blocks',
'admin/content/block/' . $revised_block->id() => $revised_block->label(),
];
$this->assertBreadcrumb(
'admin/content/block/' . $revised_block->id() . '/delete', $trail
);
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class BlockContentJsonAnonTest extends BlockContentResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class BlockContentJsonBasicAuthTest extends BlockContentResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class BlockContentJsonCookieTest extends BlockContentResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,239 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Core\Cache\Cache;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* Resource test base for BlockContent entity.
*/
abstract class BlockContentResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content', 'content_translation'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'block_content';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [
'changed' => NULL,
];
/**
* @var \Drupal\block_content\BlockContentInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
case 'PATCH':
$this->grantPermissionsToTestedRole(['access block library', 'edit any basic block content']);
break;
case 'POST':
$this->grantPermissionsToTestedRole(['create basic block content']);
break;
case 'DELETE':
$this->grantPermissionsToTestedRole(['delete any basic block content']);
break;
default:
$this->grantPermissionsToTestedRole(['administer block content']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
if (!BlockContentType::load('basic')) {
$block_content_type = BlockContentType::create([
'id' => 'basic',
'label' => 'basic',
'revision' => TRUE,
]);
$block_content_type->save();
block_content_add_body_field($block_content_type->id());
}
// Create a "Llama" content block.
$block_content = BlockContent::create([
'info' => 'Llama',
'type' => 'basic',
'body' => [
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'format' => 'plain_text',
],
])
->setUnpublished();
$block_content->save();
return $block_content;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'id' => [
[
'value' => 1,
],
],
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
'langcode' => [
[
'value' => 'en',
],
],
'reusable' => [
[
'value' => TRUE,
],
],
'type' => [
[
'target_id' => 'basic',
'target_type' => 'block_content_type',
'target_uuid' => BlockContentType::load('basic')->uuid(),
],
],
'info' => [
[
'value' => 'Llama',
],
],
'revision_log' => [],
'changed' => [
[
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getChangedTime())
->setTimezone(new \DateTimeZone('UTC'))
->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'revision_id' => [
[
'value' => 1,
],
],
'revision_created' => [
[
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getRevisionCreationTime())
->setTimezone(new \DateTimeZone('UTC'))
->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'revision_user' => [],
'revision_translation_affected' => [
[
'value' => TRUE,
],
],
'default_langcode' => [
[
'value' => TRUE,
],
],
'body' => [
[
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'format' => 'plain_text',
'summary' => NULL,
'processed' => "<p>The name &quot;llama&quot; was adopted by European settlers from native Peruvians.</p>\n",
],
],
'status' => [
[
'value' => FALSE,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'type' => [
[
'target_id' => 'basic',
],
],
'info' => [
[
'value' => 'Drama llama',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if (!$this->resourceConfigStorage->load(static::$resourceConfigId)) {
return match ($method) {
'GET', 'PATCH' => "The 'edit any basic block content' permission is required.",
'POST' => "The following permissions are required: 'create basic block content' OR 'administer block content'.",
'DELETE' => "The 'delete any basic block content' permission is required.",
default => parent::getExpectedUnauthorizedAccessMessage($method),
};
}
return match ($method) {
'GET' => "The 'access block library' permission is required.",
'PATCH' => "The 'edit any basic block content' permission is required.",
'POST' => "The following permissions are required: 'create basic block content' OR 'administer block content'.",
'DELETE' => "The 'delete any basic block content' permission is required.",
default => parent::getExpectedUnauthorizedAccessMessage($method),
};
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
// @see \Drupal\block_content\BlockContentAccessControlHandler()
return parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated)
->addCacheTags(['block_content:1']);
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheTags() {
return Cache::mergeTags(parent::getExpectedCacheTags(), ['config:filter.format.plain_text']);
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return Cache::mergeContexts(['url.site'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class BlockContentTypeJsonAnonTest extends BlockContentTypeResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class BlockContentTypeJsonBasicAuthTest extends BlockContentTypeResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class BlockContentTypeJsonCookieTest extends BlockContentTypeResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
use Drupal\block_content\Entity\BlockContentType;
/**
* Resource test base for the BlockContentType entity.
*/
abstract class BlockContentTypeResourceTestBase extends ConfigEntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'block_content_type';
/**
* @var \Drupal\block_content\BlockContentTypeInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer block types']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$block_content_type = BlockContentType::create([
'id' => 'pascal',
'label' => 'Pascal',
'revision' => FALSE,
'description' => 'Provides a competitive alternative to the "basic" type',
]);
$block_content_type->save();
return $block_content_type;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'description' => 'Provides a competitive alternative to the "basic" type',
'id' => 'pascal',
'label' => 'Pascal',
'langcode' => 'en',
'revision' => FALSE,
'status' => TRUE,
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
return [];
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class BlockContentTypeXmlAnonTest extends BlockContentTypeResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class BlockContentTypeXmlBasicAuthTest extends BlockContentTypeResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class BlockContentTypeXmlCookieTest extends BlockContentTypeResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class BlockContentXmlAnonTest extends BlockContentResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class BlockContentXmlBasicAuthTest extends BlockContentResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class BlockContentXmlCookieTest extends BlockContentResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\Tests\BrowserTestBase;
/**
* Tests unpublishing of block_content entities.
*
* @group block_content
*/
class UnpublishedBlockTest extends BrowserTestBase {
use BlockCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests unpublishing of block_content entities.
*/
public function testViewShowsCorrectStates(): void {
$block_content = BlockContent::create([
'info' => 'Test block',
'type' => 'basic',
]);
$block_content->save();
$block = $this->placeBlock('block_content:' . $block_content->uuid());
$this->drupalGet('<front>');
$page = $this->getSession()->getPage();
$this->assertTrue($page->has('css', '#block-' . $block->id()));
$block_content->setUnpublished();
$block_content->save();
$this->drupalGet('<front>');
$page = $this->getSession()->getPage();
$this->assertFalse($page->has('css', '#block-' . $block->id()));
}
}

View File

@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Views;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests block_content field filters with translations.
*
* @group block_content
*/
class BlockContentFieldFilterTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_field_filters'];
/**
* List of block_content infos by language.
*
* @var array
*/
public $blockContentInfos = [];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['block_content_test_views']): void {
parent::setUp($import_test_views, $modules);
// Add two new languages.
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('es')->save();
// Make the body field translatable. The info is already translatable by
// definition.
$field_storage = FieldStorageConfig::loadByName('block_content', 'body');
$field_storage->setTranslatable(TRUE);
$field_storage->save();
// Set up block_content infos.
$this->blockContentInfos = [
'en' => 'Food in Paris',
'es' => 'Comida en Paris',
'fr' => 'Nourriture en Paris',
];
// Create block_content with translations.
$block_content = $this->createBlockContent(['info' => $this->blockContentInfos['en'], 'langcode' => 'en', 'type' => 'basic', 'body' => [['value' => $this->blockContentInfos['en']]]]);
foreach (['es', 'fr'] as $langcode) {
$translation = $block_content->addTranslation($langcode, ['info' => $this->blockContentInfos[$langcode]]);
$translation->body->value = $this->blockContentInfos[$langcode];
}
$block_content->save();
}
/**
* Tests body and info filters.
*/
public function testFilters(): void {
// Test the info filter page, which filters for info contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-info-filter', ['es' => 1, 'fr' => 0, 'en' => 0], 'Comida info filter');
// Test the body filter page, which filters for body contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-body-filter', ['es' => 1, 'fr' => 0, 'en' => 0], 'Comida body filter');
// Test the info Paris filter page, which filters for info contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-info-paris', ['es' => 1, 'fr' => 1, 'en' => 1], 'Paris info filter');
// Test the body Paris filter page, which filters for body contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-body-paris', ['es' => 1, 'fr' => 1, 'en' => 1], 'Paris body filter');
}
/**
* Asserts that the given block_content translation counts are correct.
*
* @param string $path
* Path of the page to test.
* @param array $counts
* Array whose keys are languages, and values are the number of times
* that translation should be shown on the given page.
* @param string $message
* Message suffix to display.
*
* @internal
*/
protected function assertPageCounts(string $path, array $counts, string $message): void {
// Get the text of the page.
$this->drupalGet($path);
$text = $this->getTextContent();
foreach ($counts as $langcode => $count) {
$this->assertEquals($count, substr_count($text, $this->blockContentInfos[$langcode]), 'Translation ' . $langcode . ' has count ' . $count . ' with ' . $message);
}
}
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Views;
/**
* Tests the block_content integration into views.
*
* @group block_content
*/
class BlockContentIntegrationTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_block_content_view'];
/**
* Tests basic block_content view with a block_content_type argument.
*/
public function testBlockContentViewTypeArgument(): void {
// Create two content types with three block_contents each.
$types = [];
$all_ids = [];
$block_contents = [];
for ($i = 0; $i < 2; $i++) {
$type = $this->createBlockContentType();
$types[] = $type;
for ($j = 0; $j < 5; $j++) {
// Ensure the right order of the block_contents.
$block_content = $this->createBlockContent(['type' => $type->id()]);
$block_contents[$type->id()][$block_content->id()] = $block_content;
$all_ids[] = $block_content->id();
}
}
$this->drupalGet('test-block_content-view');
$this->assertSession()->statusCodeEquals(404);
$this->drupalGet('test-block_content-view/all');
$this->assertSession()->statusCodeEquals(200);
$this->assertIds($all_ids);
/** @var \Drupal\block_content\Entity\BlockContentType[] $types*/
foreach ($types as $type) {
$this->drupalGet("test-block_content-view/{$type->id()}");
$this->assertIds(array_keys($block_contents[$type->id()]));
}
}
/**
* Ensures that a list of block_contents appear on the page.
*
* @param array $expected_ids
* An array of block_content IDs.
*
* @internal
*/
protected function assertIds(array $expected_ids = []): void {
$result = $this->xpath('//span[@class="field-content"]');
$ids = [];
foreach ($result as $element) {
$ids[] = $element->getText();
}
$this->assertEquals($expected_ids, $ids);
}
}

View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Views;
/**
* Tests the redirect destination on block content on entity operations.
*
* @group block_content
*/
class BlockContentRedirectTest extends BlockContentTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_block_content_redirect_destination'];
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'views'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the redirect destination when editing block content.
*/
public function testRedirectDestination(): void {
$this->drupalLogin($this->drupalCreateUser(['access block library', 'administer block content']));
$this->drupalGet('admin/content/block');
// Create a content block.
$this->clickLink('content block');
$edit = [];
$edit['info[0][value]'] = 'Test redirect destination';
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->submitForm($edit, 'Save');
// Check the block content is present in the view redirect destination.
$this->drupalGet('admin/content/redirect_destination');
$this->assertSession()->pageTextContains('Test redirect destination');
// Edit the created block and save.
$this->clickLink('Edit');
$this->submitForm([], 'Save');
$this->assertSession()->addressEquals('admin/content/redirect_destination');
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Views;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Tests\views\Functional\ViewTestBase;
/**
* Base class for all block_content tests.
*/
abstract class BlockContentTestBase extends ViewTestBase {
/**
* Admin user.
*
* @var object
*/
protected $adminUser;
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer blocks',
'administer block content',
'access block library',
];
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'block_content',
'block_content_test_views',
];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['block_content_test_views']): void {
parent::setUp($import_test_views, $modules);
// Ensure the basic bundle exists. This is provided by the standard profile.
$this->createBlockContentType(['id' => 'basic']);
$this->adminUser = $this->drupalCreateUser($this->permissions);
}
/**
* Creates a content block.
*
* @param array $values
* (optional) The values for the block_content entity.
*
* @return \Drupal\block_content\Entity\BlockContent
* Created content block.
*/
protected function createBlockContent(array $values = []) {
$status = 0;
$values += [
'info' => $this->randomMachineName(),
'type' => 'basic',
'langcode' => 'en',
];
if ($block_content = BlockContent::create($values)) {
$status = $block_content->save();
}
$this->assertEquals(SAVED_NEW, $status, "Created block content {$block_content->label()}.");
return $block_content;
}
/**
* Creates a block type (bundle).
*
* @param array $values
* An array of settings to change from the defaults.
*
* @return \Drupal\block_content\Entity\BlockContentType
* Created block type.
*/
protected function createBlockContentType(array $values = []) {
// Find a non-existent random type name.
if (!isset($values['id'])) {
do {
$id = $this->randomMachineName(8);
} while (BlockContentType::load($id));
}
else {
$id = $values['id'];
}
$values += [
'id' => $id,
'label' => $id,
'revision' => FALSE,
];
$bundle = BlockContentType::create($values);
$status = $bundle->save();
block_content_add_body_field($bundle->id());
$this->assertEquals(SAVED_NEW, $status, sprintf('Created block content type %s.', $bundle->id()));
return $bundle;
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Functional\Views;
use Drupal\Tests\block_content\Functional\BlockContentTestBase;
/**
* Tests block_content wizard and generic entity integration.
*
* @group block_content
*/
class BlockContentWizardTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content', 'views_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer views']));
}
/**
* Tests creating a 'block_content' entity view.
*/
public function testViewAddBlockContent(): void {
$view = [];
$view['label'] = $this->randomMachineName(16);
$view['id'] = $this->randomMachineName(16);
$view['description'] = $this->randomMachineName(16);
$view['page[create]'] = FALSE;
$view['show[wizard_key]'] = 'block_content';
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$view_storage_controller = $this->container->get('entity_type.manager')->getStorage('view');
/** @var \Drupal\views\Entity\View $view */
$view = $view_storage_controller->load($view['id']);
$display_options = $view->getDisplay('default')['display_options'];
$this->assertEquals('block_content', $display_options['filters']['reusable']['entity_type']);
$this->assertEquals('reusable', $display_options['filters']['reusable']['entity_field']);
$this->assertEquals('boolean', $display_options['filters']['reusable']['plugin_id']);
$this->assertEquals('1', $display_options['filters']['reusable']['value']);
}
}

View File

@ -0,0 +1,621 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block_content\BlockContentAccessControlHandler;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Access\AccessResultAllowed;
use Drupal\Core\Access\AccessResultForbidden;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Access\AccessResultReasonInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
/**
* Tests the block content entity access handler.
*
* @coversDefaultClass \Drupal\block_content\BlockContentAccessControlHandler
*
* @group block_content
*/
class BlockContentAccessHandlerTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'block_content',
'system',
'user',
];
/**
* The BlockContent access controller to test.
*
* @var \Drupal\block_content\BlockContentAccessControlHandler
*/
protected $accessControlHandler;
/**
* The BlockContent entity used for testing.
*
* @var \Drupal\block_content\Entity\BlockContent
*/
protected $blockEntity;
/**
* The test role.
*
* @var \Drupal\user\RoleInterface
*/
protected $role;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installSchema('user', ['users_data']);
$this->installEntitySchema('user');
$this->installEntitySchema('block_content');
// Create a basic block content type.
$block_content_type = BlockContentType::create([
'id' => 'basic',
'label' => 'A basic block type',
'description' => "Provides a block type that is basic.",
]);
$block_content_type->save();
// Create a square block content type.
$block_content_type = BlockContentType::create([
'id' => 'square',
'label' => 'A square block type',
'description' => "Provides a block type that is square.",
]);
$block_content_type->save();
$this->blockEntity = BlockContent::create([
'info' => 'The Block',
'type' => 'square',
]);
$this->blockEntity->save();
// Create user 1 test does not have all permissions.
User::create([
'name' => 'admin',
])->save();
$this->role = Role::create([
'id' => 'test',
'label' => 'test role',
]);
$this->role->save();
$this->accessControlHandler = new BlockContentAccessControlHandler(\Drupal::entityTypeManager()->getDefinition('block_content'), \Drupal::service('event_dispatcher'));
}
/**
* Test block content entity access.
*
* @param string $operation
* The entity operation to test.
* @param bool $published
* Whether the latest revision should be published.
* @param bool $reusable
* Whether the block content should be reusable. Non-reusable blocks are
* typically used in Layout Builder.
* @param array $permissions
* Permissions to grant to the test user.
* @param bool $isLatest
* Whether the block content should be the latest revision when checking
* access. If FALSE, multiple revisions will be created, and an older
* revision will be loaded before checking access.
* @param string|null $parent_access
* Whether the test user has access to the parent entity, valid values are
* class names of classes implementing AccessResultInterface. Set to NULL to
* assert parent will not be called.
* @param string $expected_access
* The expected access for the user and block content. Valid values are
* class names of classes implementing AccessResultInterface.
* @param string|null $expected_access_message
* The expected access message.
*
* @covers ::checkAccess
*
* @dataProvider providerTestAccess
*
* @phpstan-param class-string<\Drupal\Core\Access\AccessResultInterface>|null $parent_access
* @phpstan-param class-string<\Drupal\Core\Access\AccessResultInterface> $expected_access
*/
public function testAccess(string $operation, bool $published, bool $reusable, array $permissions, bool $isLatest, ?string $parent_access, string $expected_access, ?string $expected_access_message = NULL): void {
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $entityStorage */
$entityStorage = \Drupal::entityTypeManager()->getStorage('block_content');
$loadRevisionId = NULL;
if (!$isLatest) {
// Save a historical revision, then setup for a new revision to be saved.
$this->blockEntity->save();
$loadRevisionId = $this->blockEntity->getRevisionId();
$this->blockEntity = $entityStorage->createRevision($this->blockEntity);
}
$published ? $this->blockEntity->setPublished() : $this->blockEntity->setUnpublished();
$reusable ? $this->blockEntity->setReusable() : $this->blockEntity->setNonReusable();
$user = User::create([
'name' => 'Someone',
'mail' => 'hi@example.com',
]);
if ($permissions) {
foreach ($permissions as $permission) {
$this->role->grantPermission($permission);
}
$this->role->save();
}
$user->addRole($this->role->id())->save();
if ($parent_access !== NULL) {
$parent_entity = $this->prophesize(AccessibleInterface::class);
$expected_parent_result = new ($parent_access)();
$parent_entity->access($operation, $user, TRUE)
->willReturn($expected_parent_result)
->shouldBeCalled();
$this->blockEntity->setAccessDependency($parent_entity->reveal());
}
$this->blockEntity->save();
// Reload a previous revision.
if ($loadRevisionId !== NULL) {
$this->blockEntity = $entityStorage->loadRevision($loadRevisionId);
}
$result = $this->accessControlHandler->access($this->blockEntity, $operation, $user, TRUE);
$this->assertInstanceOf($expected_access, $result);
if ($expected_access_message !== NULL) {
$this->assertInstanceOf(AccessResultReasonInterface::class, $result);
$this->assertEquals($expected_access_message, $result->getReason());
}
}
/**
* Data provider for testAccess().
*/
public static function providerTestAccess(): array {
$cases = [
'view:published:reusable' => [
'view',
TRUE,
TRUE,
[],
TRUE,
NULL,
AccessResultAllowed::class,
],
'view:unpublished:reusable' => [
'view',
FALSE,
TRUE,
[],
TRUE,
NULL,
AccessResultNeutral::class,
],
'view:unpublished:reusable:admin' => [
'view',
FALSE,
TRUE,
['access block library'],
TRUE,
NULL,
AccessResultAllowed::class,
],
'view:unpublished:reusable:per-block-editor:basic' => [
'view',
FALSE,
TRUE,
['edit any basic block content'],
TRUE,
NULL,
AccessResultNeutral::class,
],
'view:unpublished:reusable:per-block-editor:square' => [
'view',
FALSE,
TRUE,
['access block library', 'edit any basic block content'],
TRUE,
NULL,
AccessResultAllowed::class,
],
'view:published:reusable:admin' => [
'view',
TRUE,
TRUE,
['access block library'],
TRUE,
NULL,
AccessResultAllowed::class,
],
'view:published:reusable:per-block-editor:basic' => [
'view',
TRUE,
TRUE,
['access block library', 'edit any basic block content'],
TRUE,
NULL,
AccessResultAllowed::class,
],
'view:published:reusable:per-block-editor:square' => [
'view',
TRUE,
TRUE,
['access block library', 'edit any square block content'],
TRUE,
NULL,
AccessResultAllowed::class,
],
'view:published:non_reusable' => [
'view',
TRUE,
FALSE,
[],
TRUE,
NULL,
AccessResultForbidden::class,
],
'view:published:non_reusable:parent_allowed' => [
'view',
TRUE,
FALSE,
[],
TRUE,
AccessResultAllowed::class,
AccessResultAllowed::class,
],
'view:published:non_reusable:parent_neutral' => [
'view',
TRUE,
FALSE,
[],
TRUE,
AccessResultNeutral::class,
AccessResultNeutral::class,
],
'view:published:non_reusable:parent_forbidden' => [
'view',
TRUE,
FALSE,
[],
TRUE,
AccessResultForbidden::class,
AccessResultForbidden::class,
],
];
foreach (['update', 'delete'] as $operation) {
$label = $operation === 'update' ? 'edit' : 'delete';
$cases += [
$operation . ':published:reusable' => [
$operation,
TRUE,
TRUE,
[],
TRUE,
NULL,
AccessResultNeutral::class,
],
$operation . ':unpublished:reusable' => [
$operation,
FALSE,
TRUE,
[],
TRUE,
NULL,
AccessResultNeutral::class,
],
$operation . ':unpublished:reusable:admin' => [
$operation,
FALSE,
TRUE,
[$label . ' any square block content'],
TRUE,
NULL,
AccessResultAllowed::class,
],
$operation . ':published:reusable:admin' => [
$operation,
TRUE,
TRUE,
[$label . ' any square block content'],
TRUE,
NULL,
AccessResultAllowed::class,
],
$operation . ':published:non_reusable' => [
$operation,
TRUE,
FALSE,
[],
TRUE,
NULL,
AccessResultForbidden::class,
],
$operation . ':published:non_reusable:parent_allowed' => [
$operation,
TRUE,
FALSE,
[],
TRUE,
AccessResultAllowed::class,
AccessResultNeutral::class,
],
$operation . ':published:non_reusable:parent_neutral' => [
$operation,
TRUE,
FALSE,
[],
TRUE,
AccessResultNeutral::class,
AccessResultNeutral::class,
],
$operation . ':published:non_reusable:parent_forbidden' => [
$operation,
TRUE,
FALSE,
[],
TRUE,
AccessResultForbidden::class,
AccessResultForbidden::class,
],
$operation . ':unpublished:reusable:per-block-editor:basic' => [
$operation,
FALSE,
TRUE,
['edit any basic block content'],
TRUE,
NULL,
AccessResultNeutral::class,
],
$operation . ':published:reusable:per-block-editor:basic' => [
$operation,
TRUE,
TRUE,
['edit any basic block content'],
TRUE,
NULL,
AccessResultNeutral::class,
],
];
}
$cases += [
'update:unpublished:reusable:per-block-editor:square' => [
'update',
FALSE,
TRUE,
['edit any square block content'],
TRUE,
NULL,
AccessResultAllowed::class,
],
'update:published:reusable:per-block-editor:square' => [
'update',
TRUE,
TRUE,
['edit any square block content'],
TRUE,
NULL,
AccessResultAllowed::class,
],
];
$cases += [
'delete:unpublished:reusable:per-block-editor:square' => [
'delete',
FALSE,
TRUE,
['edit any square block content'],
TRUE,
NULL,
AccessResultNeutral::class,
],
'delete:published:reusable:per-block-editor:square' => [
'delete',
TRUE,
TRUE,
['edit any square block content'],
TRUE,
NULL,
AccessResultNeutral::class,
],
];
// View all revisions:
$cases['view all revisions:none'] = [
'view all revisions',
TRUE,
TRUE,
[],
TRUE,
NULL,
AccessResultNeutral::class,
];
$cases['view all revisions:view any bundle history'] = [
'view all revisions',
TRUE,
TRUE,
['view any square block content history'],
TRUE,
NULL,
AccessResultAllowed::class,
];
$cases['view all revisions:administer block content'] = [
'view all revisions',
TRUE,
TRUE,
['administer block content'],
TRUE,
NULL,
AccessResultAllowed::class,
];
// Revert revisions:
$cases['revert:none:latest'] = [
'revert',
TRUE,
TRUE,
[],
TRUE,
NULL,
AccessResultForbidden::class,
];
$cases['revert:none:historical'] = [
'revert',
TRUE,
TRUE,
[],
FALSE,
NULL,
AccessResultNeutral::class,
];
$cases['revert:revert bundle:historical'] = [
'revert',
TRUE,
TRUE,
['revert any square block content revisions'],
FALSE,
NULL,
AccessResultAllowed::class,
];
$cases['revert:administer block content:latest'] = [
'revert',
TRUE,
TRUE,
['administer block content'],
TRUE,
NULL,
AccessResultForbidden::class,
];
$cases['revert:administer block content:historical'] = [
'revert',
TRUE,
TRUE,
['administer block content'],
FALSE,
NULL,
AccessResultAllowed::class,
];
$cases['revert:revert bundle:historical:non reusable'] = [
'revert',
TRUE,
FALSE,
['revert any square block content revisions'],
FALSE,
NULL,
AccessResultForbidden::class,
'Block content must be reusable to use `revert` operation',
];
// Delete revisions:
$cases['delete revision:none:latest'] = [
'delete revision',
TRUE,
TRUE,
[],
TRUE,
NULL,
AccessResultForbidden::class,
];
$cases['delete revision:none:historical'] = [
'delete revision',
TRUE,
TRUE,
[],
FALSE,
NULL,
AccessResultNeutral::class,
];
$cases['delete revision:administer block content:latest'] = [
'delete revision',
TRUE,
TRUE,
['administer block content'],
TRUE,
NULL,
AccessResultForbidden::class,
];
$cases['delete revision:administer block content:historical'] = [
'delete revision',
TRUE,
TRUE,
['administer block content'],
FALSE,
NULL,
AccessResultAllowed::class,
];
$cases['delete revision:delete bundle:latest'] = [
'delete revision',
TRUE,
TRUE,
['administer block content'],
TRUE,
NULL,
AccessResultForbidden::class,
];
$cases['delete revision:delete bundle:historical'] = [
'delete revision',
TRUE,
TRUE,
['delete any square block content revisions'],
FALSE,
NULL,
AccessResultAllowed::class,
];
$cases['delete revision:delete bundle:historical:non reusable'] = [
'delete revision',
TRUE,
FALSE,
['delete any square block content revisions'],
FALSE,
NULL,
AccessResultForbidden::class,
'Block content must be reusable to use `delete revision` operation',
];
return $cases;
}
/**
* Tests revision log access.
*/
public function testRevisionLogAccess(): void {
$admin = $this->createUser([
'administer block content',
'access content',
]);
$editor = $this->createUser([
'access content',
'access block library',
'view any square block content history',
]);
$viewer = $this->createUser([
'access content',
]);
$this->assertTrue($this->blockEntity->get('revision_log')->access('view', $admin));
$this->assertTrue($this->blockEntity->get('revision_log')->access('view', $editor));
$this->assertFalse($this->blockEntity->get('revision_log')->access('view', $viewer));
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Plugin\PluginBase;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\block\Traits\BlockCreationTrait;
/**
* Tests that deleting a block clears the cached definitions.
*
* @group block_content
*/
class BlockContentDeletionTest extends KernelTestBase {
use BlockCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'field', 'system', 'text', 'user'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('block_content');
$this->installConfig(['block_content']);
$this->container->get('theme_installer')->install(['stark']);
}
/**
* Tests deleting a block_content updates the discovered block plugin.
*/
public function testDeletingBlockContentShouldClearPluginCache(): void {
// Create a block content type.
$block_content_type = BlockContentType::create([
'id' => 'spiffy',
'label' => 'Very spiffy',
'description' => "Provides a block type that increases your site's spiffy rating by upto 11%",
]);
$block_content_type->save();
// And a block content entity.
$block_content = BlockContent::create([
'info' => 'Spiffy prototype',
'type' => 'spiffy',
]);
$block_content->save();
// Make sure the block content provides a derivative block plugin in the
// block repository.
/** @var \Drupal\Core\Block\BlockManagerInterface $block_manager */
$block_manager = $this->container->get('plugin.manager.block');
$plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid();
$this->assertTrue($block_manager->hasDefinition($plugin_id));
// Now delete the block content entity.
$block_content->delete();
// The plugin should no longer exist.
$this->assertFalse($block_manager->hasDefinition($plugin_id));
// Create another block content entity.
$block_content = BlockContent::create([
'info' => 'Spiffy prototype',
'type' => 'spiffy',
]);
$block_content->save();
$plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid();
$block = $this->placeBlock($plugin_id, ['region' => 'content', 'theme' => 'stark']);
// Delete it via storage.
$storage = $this->container->get('entity_type.manager')->getStorage('block_content');
$storage->delete([$block_content]);
// The plugin should no longer exist.
$this->assertFalse($block_manager->hasDefinition($plugin_id));
$this->assertNull($this->container->get('entity_type.manager')->getStorage('block')->loadUnchanged($block->id()));
}
}

View File

@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\block_content\Plugin\Derivative\BlockContent as DerivativeBlockContent;
use Drupal\Component\Plugin\PluginBase;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests block content plugin deriver.
*
* @group block_content
*/
class BlockContentDeriverTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'system', 'user'];
/**
* The definition array of the base plugin.
*
* @var array
*/
protected $baseDefinition = [
'id' => 'block_content',
'provider' => 'block_content',
'class' => '\Drupal\block_content\Plugin\Block\BlockContentBlock',
'deriver' => '\Drupal\block_content\Plugin\Derivative\BlockContent',
];
/**
* The block content storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $blockContentStorage;
/**
* The tested block content derivative class.
*
* @var \Drupal\block_content\Plugin\Derivative\BlockContent
*/
protected $blockContentDerivative;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('block_content');
$this->blockContentStorage = \Drupal::entityTypeManager()->getStorage('block_content');
$this->blockContentDerivative = new DerivativeBlockContent($this->blockContentStorage);
}
/**
* Tests that only reusable blocks are derived.
*/
public function testReusableBlocksOnlyAreDerived(): void {
// Create a block content type.
$block_content_type = BlockContentType::create([
'id' => 'spiffy',
'label' => 'Very spiffy',
'description' => "Provides a block type that increases your site's spiffy rating by up to 11%",
]);
$block_content_type->save();
// And a block content entity.
$block_content = BlockContent::create([
'info' => 'Spiffy prototype',
'type' => 'spiffy',
]);
$block_content->save();
// Ensure the reusable block content is provided as a derivative block
// plugin.
/** @var \Drupal\Core\Block\BlockManagerInterface $block_manager */
$block_manager = $this->container->get('plugin.manager.block');
$plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid();
$this->assertTrue($block_manager->hasDefinition($plugin_id));
// Set the block not to be reusable.
$block_content->setNonReusable();
$block_content->save();
// Ensure the non-reusable block content is not provided a derivative block
// plugin.
$this->assertFalse($block_manager->hasDefinition($plugin_id));
}
/**
* Tests the admin labels of derivative definitions.
*/
public function testGetDerivativeDefinitionsAdminLabels(): void {
$blockContentType = BlockContentType::create([
'id' => 'basic',
'label' => 'Basic Block',
]);
$blockContentType->save();
$blockContentWithLabel = BlockContent::create([
'info' => 'Basic prototype',
'type' => 'basic',
]);
$blockContentWithLabel->save();
$blockContentNoLabel = BlockContent::create([
'type' => 'basic',
]);
$blockContentNoLabel->save();
$blockPluginManager = \Drupal::service('plugin.manager.block');
$plugin = $blockPluginManager->createInstance('block_content:' . $blockContentWithLabel->uuid());
$this->assertEquals('Basic prototype', $plugin->getPluginDefinition()['admin_label']);
$plugin = $blockPluginManager->createInstance('block_content:' . $blockContentNoLabel->uuid());
$this->assertEquals('Basic Block: ' . $blockContentNoLabel->id(), $plugin->getPluginDefinition()['admin_label']);
}
}

View File

@ -0,0 +1,195 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\block_content_test\Plugin\EntityReferenceSelection\TestSelection;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests EntityReference selection handlers don't return non-reusable blocks.
*
* @see block_content_query_entity_reference_alter()
*
* @group block_content
*/
class BlockContentEntityReferenceSelectionTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'block_content',
'block_content_test',
'system',
'user',
];
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Test reusable block.
*
* @var \Drupal\block_content\BlockContentInterface
*/
protected $blockReusable;
/**
* Test non-reusable block.
*
* @var \Drupal\block_content\BlockContentInterface
*/
protected $blockNonReusable;
/**
* Test selection handler.
*
* @var \Drupal\block_content_test\Plugin\EntityReferenceSelection\TestSelection
*/
protected $selectionHandler;
/**
* Test block expectations.
*
* @var array
*/
protected $expectations;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('block_content');
// Create a block content type.
$block_content_type = BlockContentType::create([
'id' => 'spiffy',
'label' => 'Very spiffy',
'description' => "Provides a block type that increases your site's spiffy rating by up to 11%",
]);
$block_content_type->save();
$this->entityTypeManager = $this->container->get('entity_type.manager');
// And reusable block content entities.
$this->blockReusable = BlockContent::create([
'info' => 'Reusable Block',
'type' => 'spiffy',
]);
$this->blockReusable->save();
$this->blockNonReusable = BlockContent::create([
'info' => 'Non-reusable Block',
'type' => 'spiffy',
'reusable' => FALSE,
]);
$this->blockNonReusable->save();
$configuration = [
'target_type' => 'block_content',
'target_bundles' => ['spiffy' => 'spiffy'],
'sort' => ['field' => '_none'],
];
$this->selectionHandler = new TestSelection($configuration, '', '', $this->container->get('entity_type.manager'), $this->container->get('module_handler'), \Drupal::currentUser(), \Drupal::service('entity_field.manager'), \Drupal::service('entity_type.bundle.info'), \Drupal::service('entity.repository'));
// Setup the 3 expectation cases.
$this->expectations = [
'both_blocks' => [
'spiffy' => [
$this->blockReusable->id() => $this->blockReusable->label(),
$this->blockNonReusable->id() => $this->blockNonReusable->label(),
],
],
'block_reusable' => ['spiffy' => [$this->blockReusable->id() => $this->blockReusable->label()]],
'block_non_reusable' => ['spiffy' => [$this->blockNonReusable->id() => $this->blockNonReusable->label()]],
];
}
/**
* Tests to make sure queries without the expected tags are not altered.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function testQueriesNotAltered(): void {
// Ensure that queries without all the tags are not altered.
$query = $this->entityTypeManager->getStorage('block_content')
->getQuery()
->accessCheck(FALSE);
$this->assertCount(2, $query->execute());
$query = $this->entityTypeManager->getStorage('block_content')
->getQuery()
->accessCheck(FALSE);
$query->addTag('block_content_access');
$this->assertCount(2, $query->execute());
$query = $this->entityTypeManager->getStorage('block_content')
->getQuery()
->accessCheck(FALSE);
$query->addTag('entity_query_block_content');
$this->assertCount(2, $query->execute());
}
/**
* Tests with no conditions set.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function testNoConditions(): void {
$this->assertEquals(
$this->expectations['block_reusable'],
$this->selectionHandler->getReferenceableEntities()
);
$this->blockNonReusable->setReusable();
$this->blockNonReusable->save();
// Ensure that the block is now returned as a referenceable entity.
$this->assertEquals(
$this->expectations['both_blocks'],
$this->selectionHandler->getReferenceableEntities()
);
}
/**
* Tests setting 'reusable' condition on different levels.
*
* @dataProvider fieldConditionProvider
*
* @throws \Exception
*/
public function testFieldConditions($condition_type, $is_reusable): void {
$this->selectionHandler->setTestMode($condition_type, $is_reusable);
$this->assertEquals(
$is_reusable ? $this->expectations['block_reusable'] : $this->expectations['block_non_reusable'],
$this->selectionHandler->getReferenceableEntities()
);
}
/**
* Provides possible fields and condition types.
*/
public static function fieldConditionProvider() {
$cases = [];
foreach (['base', 'group', 'nested_group'] as $condition_type) {
foreach ([TRUE, FALSE] as $reusable) {
$cases["$condition_type:" . ($reusable ? 'reusable' : 'non-reusable')] = [
$condition_type,
$reusable,
];
}
}
return $cases;
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\block_content\Entity\BlockContentType;
/**
* Tests the permissions of content blocks.
*
* @coversDefaultClass \Drupal\block_content\BlockContentPermissions
*
* @group block_content
*/
class BlockContentPermissionsTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'block_content',
'block_content_test',
'system',
'user',
];
/**
* The permission handler.
*
* @var \Drupal\user\PermissionHandlerInterface
*/
protected $permissionHandler;
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('block_content');
$this->permissionHandler = $this->container->get('user.permissions');
}
/**
* @covers ::blockTypePermissions
*/
public function testDynamicPermissions(): void {
$permissions = $this->permissionHandler->getPermissions();
$this->assertArrayNotHasKey('edit any basic block content', $permissions, 'The per-block-type permission does not exist.');
$this->assertArrayNotHasKey('edit any square block content', $permissions, 'The per-block-type permission does not exist.');
// Create a basic block content type.
BlockContentType::create([
'id' => 'basic',
'label' => 'A basic block type',
'description' => 'Provides a basic block type',
])->save();
// Create a square block content type.
BlockContentType::create([
'id' => 'square',
'label' => 'A square block type',
'description' => 'Provides a block type that is square',
])->save();
$permissions = $this->permissionHandler->getPermissions();
// Assert the basic permission has been created.
$this->assertArrayHasKey('edit any basic block content', $permissions, 'The per-block-type permission exists.');
$this->assertEquals(
'<em class="placeholder">A basic block type</em>: Edit content block',
$permissions['edit any basic block content']['title']->render()
);
// Assert the square permission has been created.
$this->assertArrayHasKey('edit any square block content', $permissions, 'The per-block-type permission exists.');
$this->assertEquals(
'<em class="placeholder">A square block type</em>: Edit content block',
$permissions['edit any square block content']['title']->render()
);
}
}

View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block_content\Entity\BlockContent;
use Drupal\KernelTests\KernelTestBase;
use Drupal\block_content\Entity\BlockContentType;
/**
* Tests revision based functions for Block Content.
*
* @group block_content
*/
class BlockContentRevisionsTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'block_content',
'system',
'user',
];
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('block_content');
}
/**
* Tests block content revision user id doesn't throw error with null field.
*/
public function testNullRevisionUser(): void {
BlockContentType::create([
'id' => 'basic',
'label' => 'A basic block type',
])->save();
$block = BlockContent::create([
'info' => 'Test',
'type' => 'basic',
'revision_user' => NULL,
]);
$block->save();
$this->assertNull($block->getRevisionUserId());
}
}

View File

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block\Entity\Block;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\block_content\Hook\BlockContentHooks;
/**
* Tests the block content.
*
* @group block_content
*/
class BlockContentTest extends KernelTestBase {
use UserCreationTrait;
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'system', 'user'];
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('block_content');
}
/**
* Tests the editing links for BlockContentBlock.
*/
public function testOperationLinks(): void {
// Create a block content type.
BlockContentType::create([
'id' => 'spiffy',
'label' => 'Very spiffy',
'description' => "Provides a block type that increases your site's spiffy rating by up to 11%",
])->save();
// And a block content entity.
$block_content = BlockContent::create([
'info' => 'Spiffy prototype',
'type' => 'spiffy',
]);
$block_content->save();
$block = Block::create([
'plugin' => 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid(),
'region' => 'content',
'id' => 'machine_name',
'theme' => 'stark',
]);
// The anonymous user doesn't have the "administer block" permission.
$blockContentEntityOperation = new BlockContentHooks();
$this->assertEmpty($blockContentEntityOperation->entityOperation($block));
$this->setUpCurrentUser(['uid' => 1], ['edit any spiffy block content', 'administer blocks']);
// The admin user does have the "administer block" permission.
$this->assertEquals([
'block-edit' => [
'title' => 'Edit block',
'url' => $block_content->toUrl('edit-form')->setOptions([]),
'weight' => 50,
],
], $blockContentEntityOperation->entityOperation($block));
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
/**
* Tests validation of block_content_type entities.
*
* @group block_content
* @group #slow
*/
class BlockContentTypeValidationTest extends ConfigEntityValidationTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content'];
/**
* {@inheritdoc}
*/
protected static array $propertiesWithOptionalValues = ['description'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->entity = BlockContentType::create([
'id' => 'test',
'label' => 'Test',
]);
$this->entity->save();
}
}

View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block\Entity\Block;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\KernelTests\KernelTestBase;
use Drupal\block_content\Hook\BlockContentHooks;
/**
* Tests the block_content_theme_suggestions_block() function.
*
* @group block_content
*/
class BlockTemplateSuggestionsTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'user',
'block',
'block_content',
'system',
];
/**
* The BlockContent entity used for testing.
*
* @var \Drupal\block_content\Entity\BlockContent
*/
protected $blockContent;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
// Create a block content type.
$block_content_type = BlockContentType::create([
'id' => 'test_block',
'label' => 'A test block type',
'description' => "Provides a test block type.",
]);
$block_content_type->save();
$this->blockContent = BlockContent::create([
'info' => 'The Test Block',
'type' => 'test_block',
]);
$this->blockContent->save();
}
/**
* Tests template suggestions from block_content_theme_suggestions_block().
*/
public function testBlockThemeHookSuggestions(): void {
// Create a block using a block_content plugin.
$block = Block::create([
'plugin' => 'block_content:' . $this->blockContent->uuid(),
'region' => 'footer',
'id' => 'machine_name',
]);
$variables['elements']['#id'] = $block->id();
$variables['elements']['content']['#block_content'] = $this->blockContent;
$variables['elements']['content']['#view_mode'] = 'full';
$suggestions = [];
$suggestions[] = 'block__block_content__' . $block->uuid();
$blockTemplateSuggestionsAlter = new BlockContentHooks();
$blockTemplateSuggestionsAlter->themeSuggestionsBlockAlter($suggestions, $variables);
$this->assertSame([
'block__block_content__' . $block->uuid(),
'block__block_content__view__full',
'block__block_content__type__test_block',
'block__block_content__view_type__test_block__full',
'block__block_content__id__machine_name',
'block__block_content__id_view__machine_name__full',
], $suggestions);
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\FieldConfigInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Attaches a body field to the block type.
*
* @group block_content
*/
class MigrateBlockContentBodyFieldTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'filter', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(['block_content']);
$this->executeMigrations([
'block_content_type',
'block_content_body_field',
]);
}
/**
* Tests the block content body field migration.
*/
public function testBlockContentBodyFieldMigration(): void {
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
$storage = FieldStorageConfig::load('block_content.body');
$this->assertInstanceOf(FieldStorageConfigInterface::class, $storage);
$this->assertSame('block_content', $storage->getTargetEntityTypeId());
$this->assertSame(['basic'], array_values($storage->getBundles()));
$this->assertSame('body', $storage->getName());
/** @var \Drupal\field\FieldConfigInterface $field */
$field = FieldConfig::load('block_content.basic.body');
$this->assertInstanceOf(FieldConfigInterface::class, $field);
$this->assertSame('block_content', $field->getTargetEntityTypeId());
$this->assertSame('basic', $field->getTargetBundle());
$this->assertSame('body', $field->getName());
$this->assertSame('Body', $field->getLabel());
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of block content body field display configuration.
*
* @group block_content
*/
class MigrateBlockContentEntityDisplayTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'filter', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(static::$modules);
$this->executeMigrations([
'block_content_type',
'block_content_body_field',
'block_content_entity_display',
]);
}
/**
* Asserts a display entity.
*
* @param string $id
* The entity ID.
* @param string $component_id
* The ID of the display component.
*
* @internal
*/
protected function assertDisplay(string $id, string $component_id): void {
$component = EntityViewDisplay::load($id)->getComponent($component_id);
$this->assertIsArray($component);
$this->assertSame('hidden', $component['label']);
}
/**
* Tests the migrated display configuration.
*/
public function testMigration(): void {
$this->assertDisplay('block_content.basic.default', 'body');
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of block content body field form display configuration.
*
* @group block_content
*/
class MigrateBlockContentEntityFormDisplayTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'filter', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(static::$modules);
$this->executeMigrations([
'block_content_type',
'block_content_body_field',
'block_content_entity_form_display',
]);
}
/**
* Asserts a display entity.
*
* @param string $id
* The entity ID.
* @param string $component_id
* The ID of the form component.
*
* @internal
*/
protected function assertDisplay(string $id, string $component_id): void {
$component = EntityFormDisplay::load($id)->getComponent($component_id);
$this->assertIsArray($component);
$this->assertSame('text_textarea_with_summary', $component['type']);
}
/**
* Tests the migrated display configuration.
*/
public function testMigration(): void {
$this->assertDisplay('block_content.basic.default', 'body');
}
}

View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\migrate\MigrateException;
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
use Drupal\migrate_drupal\Tests\StubTestTrait;
/**
* Test stub creation for block_content entities.
*
* @group block_content
*/
class MigrateBlockContentStubTest extends MigrateDrupalTestBase {
use StubTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
}
/**
* Tests creation of block content stubs with no block_content_type available.
*/
public function testStubFailure(): void {
// Expected MigrateException thrown when no bundles exist.
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('Stubbing failed, no bundles available for entity type: block_content');
$this->createEntityStub('block_content');
}
/**
* Tests creation of block content stubs when there is a block_content_type.
*/
public function testStubSuccess(): void {
BlockContentType::create([
'id' => 'test_block_content_type',
'label' => 'Test block content type',
])->save();
$this->performStubTest('block_content');
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate;
use Drupal\block_content\BlockContentTypeInterface;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of the basic block content type.
*
* @group block_content
*/
class MigrateBlockContentTypeTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content', 'filter', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(['block_content']);
$this->executeMigration('block_content_type');
}
/**
* Tests the block content type migration.
*/
public function testBlockContentTypeMigration(): void {
/** @var \Drupal\block_content\BlockContentTypeInterface $entity */
$entity = BlockContentType::load('basic');
$this->assertInstanceOf(BlockContentTypeInterface::class, $entity);
$this->assertSame('Basic', $entity->label());
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate\d6;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade content blocks.
*
* @group migrate_drupal_6
*/
class MigrateBlockContentTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'block_content'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(['block_content']);
$this->executeMigrations([
'd6_filter_format',
'block_content_type',
'block_content_body_field',
'd6_custom_block',
]);
}
/**
* Tests the Drupal 6 content block to Drupal 8 migration.
*/
public function testBlockMigration(): void {
/** @var \Drupal\block_content\Entity\BlockContent $block */
$block = BlockContent::load(1);
$this->assertSame('My block 1', $block->label());
$requestTime = \Drupal::time()->getRequestTime();
$this->assertGreaterThanOrEqual($requestTime, (int) $block->getChangedTime());
$this->assertLessThanOrEqual(time(), $block->getChangedTime());
$this->assertSame('en', $block->language()->getId());
$this->assertSame('<h3>My first content block body</h3>', $block->body->value);
$this->assertSame('full_html', $block->body->format);
$block = BlockContent::load(2);
$this->assertSame('My block 2', $block->label());
$this->assertGreaterThanOrEqual($requestTime, (int) $block->getChangedTime());
$this->assertGreaterThanOrEqual($requestTime, (int) $block->getChangedTime());
$this->assertLessThanOrEqual(time(), $block->getChangedTime());
$this->assertSame('en', $block->language()->getId());
$this->assertSame('<h3>My second content block body</h3>', $block->body->value);
$this->assertSame('full_html', $block->body->format);
}
}

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate\d6;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Tests migration of i18n content block strings.
*
* @group migrate_drupal_6
*/
class MigrateCustomBlockContentTranslationTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block_content',
'content_translation',
'language',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(['block_content']);
$this->executeMigrations([
'language',
'd6_filter_format',
'block_content_type',
'block_content_body_field',
'd6_custom_block',
'd6_custom_block_translation',
]);
}
/**
* Tests the Drupal 6 i18n content block strings to Drupal 8 migration.
*/
public function testCustomBlockContentTranslation(): void {
/** @var \Drupal\block_content\Entity\BlockContent $block */
$block = BlockContent::load(1)->getTranslation('fr');
$this->assertSame('fr - Static Block', $block->label());
$this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $block->getChangedTime());
$this->assertLessThanOrEqual(time(), $block->getChangedTime());
$this->assertSame('fr', $block->language()->getId());
$this->assertSame('<h3>fr - My first content block body</h3>', $block->body->value);
$this->assertSame('full_html', $block->body->format);
$block = $block->getTranslation('zu');
$this->assertSame('My block 1', $block->label());
$this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $block->getChangedTime());
$this->assertLessThanOrEqual(time(), $block->getChangedTime());
$this->assertSame('zu', $block->language()->getId());
$this->assertSame('<h3>zu - My first content block body</h3>', $block->body->value);
$this->assertSame('full_html', $block->body->format);
$block = BlockContent::load(2)->getTranslation('fr');
$this->assertSame('Encore un bloc statique', $block->label());
$this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $block->getChangedTime());
$this->assertLessThanOrEqual(time(), $block->getChangedTime());
$this->assertSame('fr', $block->language()->getId());
$this->assertSame('Nom de vocabulaire beaucoup plus long que trente-deux caractères', $block->body->value);
$this->assertSame('full_html', $block->body->format);
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate\d7;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of i18n content block strings.
*
* @group migrate_drupal_7
*/
class MigrateCustomBlockContentTranslationTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block_content',
'content_translation',
'filter',
'language',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(['block_content']);
$this->executeMigrations([
'language',
'd7_filter_format',
'block_content_type',
'block_content_body_field',
'd7_custom_block',
'd7_custom_block_translation',
]);
}
/**
* Tests the Drupal 7 i18n content block strings to Drupal 8 migration.
*/
public function testCustomBlockContentTranslation(): void {
/** @var \Drupal\block_content\Entity\BlockContent $block */
$block = BlockContent::load(1)->getTranslation('fr');
$this->assertSame('fr - Mildly amusing limerick of the day', $block->label());
$this->assertGreaterThanOrEqual($block->getChangedTime(), \Drupal::time()->getRequestTime());
$this->assertLessThanOrEqual(time(), $block->getChangedTime());
$this->assertSame('fr', $block->language()->getId());
$translation = "fr - A fellow jumped off a high wall\r\nAnd had a most terrible fall\r\nHe went back to bed\r\nWith a bump on his head\r\nThat's why you don't jump off a wall";
$this->assertSame($translation, $block->body->value);
$this->assertSame('filtered_html', $block->body->format);
$block = $block->getTranslation('is');
$this->assertSame('is - Mildly amusing limerick of the day', $block->label());
$this->assertGreaterThanOrEqual($block->getChangedTime(), \Drupal::time()->getRequestTime());
$this->assertLessThanOrEqual(time(), $block->getChangedTime());
$this->assertSame('is', $block->language()->getId());
$text = "A fellow jumped off a high wall\r\nAnd had a most terrible fall\r\nHe went back to bed\r\nWith a bump on his head\r\nThat's why you don't jump off a wall";
$this->assertSame($text, $block->body->value);
$this->assertSame('filtered_html', $block->body->format);
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Migrate\d7;
use Drupal\block_content\BlockContentInterface;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of content blocks.
*
* @group block_content
*/
class MigrateCustomBlockTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block_content',
'filter',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(static::$modules);
$this->executeMigrations([
'd7_filter_format',
'block_content_type',
'block_content_body_field',
'd7_custom_block',
]);
}
/**
* Tests migration of content blocks from Drupal 7 to Drupal 8.
*/
public function testCustomBlockMigration(): void {
$block = BlockContent::load(1);
$this->assertInstanceOf(BlockContentInterface::class, $block);
/** @var \Drupal\block_content\BlockContentInterface $block */
$this->assertSame('Limerick', $block->label());
$expected_body = "A fellow jumped off a high wall\r\nAnd had a most terrible fall\r\nHe went back to bed\r\nWith a bump on his head\r\nThat's why you don't jump off a wall";
$this->assertSame($expected_body, $block->body->value);
$this->assertSame('filtered_html', $block->body->format);
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 block boxes source plugin.
*
* @covers \Drupal\block_content\Plugin\migrate\source\d6\Box
* @group block_content
*/
class BoxTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
$tests[0]['source_data']['boxes'] = [
[
'bid' => 1,
'body' => '<p>I made some custom content.</p>',
'info' => 'Static Block',
'format' => 1,
],
[
'bid' => 2,
'body' => '<p>I made some more custom content.</p>',
'info' => 'Test Content',
'format' => 1,
],
];
// The expected results are identical to the source data.
$tests[0]['expected_data'] = $tests[0]['source_data']['boxes'];
return $tests;
}
}

View File

@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
// cspell:ignore objectid objectindex plid
/**
* Tests i18n content block translations source plugin.
*
* @covers \Drupal\block_content\Plugin\migrate\source\d6\BoxTranslation
*
* @group content_translation
*/
class BoxTranslationTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['boxes'] = [
[
'bid' => 1,
'body' => 'box 1 body',
'info' => 'box 1 title',
'format' => '2',
],
[
'bid' => 2,
'body' => 'box 2 body',
'info' => 'box 2 title',
'format' => '2',
],
];
$tests[0]['source_data']['i18n_strings'] = [
[
'lid' => 1,
'objectid' => 1,
'type' => 'block',
'property' => 'title',
'objectindex' => 1,
'format' => 0,
],
[
'lid' => 2,
'objectid' => 1,
'type' => 'block',
'property' => 'body',
'objectindex' => 1,
'format' => 0,
],
[
'lid' => 3,
'objectid' => 2,
'type' => 'block',
'property' => 'body',
'objectindex' => 2,
'format' => 2,
],
];
$tests[0]['source_data']['locales_target'] = [
[
'lid' => 1,
'language' => 'fr',
'translation' => 'fr - title translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 2,
'language' => 'fr',
'translation' => 'fr - body translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 3,
'language' => 'zu',
'translation' => 'zu - body translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
];
$tests[0]['expected_data'] = [
[
'lid' => '1',
'property' => 'title',
'language' => 'fr',
'translation' => 'fr - title translation',
'bid' => '1',
'format' => '2',
'title_translated' => 'fr - title translation',
'body_translated' => 'fr - body translation',
'title' => 'box 1 title',
'body' => 'box 1 body',
],
[
'lid' => '2',
'property' => 'body',
'language' => 'fr',
'translation' => 'fr - body translation',
'bid' => '1',
'format' => '2',
'title_translated' => 'fr - title translation',
'body_translated' => 'fr - body translation',
'title' => 'box 1 title',
'body' => 'box 1 body',
],
[
'lid' => '3',
'property' => 'body',
'language' => 'zu',
'translation' => 'zu - body translation',
'bid' => '2',
'format' => '2',
'title_translated' => NULL,
'body_translated' => 'zu - body translation',
'title' => 'box 2 title',
'body' => 'box 2 body',
],
];
return $tests;
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests d7_block_custom source plugin.
*
* @covers \Drupal\block_content\Plugin\migrate\source\d7\BlockCustom
* @group block_content
*/
class BlockCustomTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
$tests[0]['source_data']['block_custom'] = [
[
'bid' => '1',
'body' => "I don't feel creative enough to write anything clever here.",
'info' => 'Meh',
'format' => 'filtered_html',
],
];
// The expected results are identical to the source data.
$tests[0]['expected_data'] = $tests[0]['source_data']['block_custom'];
return $tests;
}
}

View File

@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
// cspell:ignore objectid objectindex plid
/**
* Tests i18n content block translations source plugin.
*
* @covers \Drupal\block_content\Plugin\migrate\source\d7\BlockCustomTranslation
*
* @group content_translation
*/
class BlockCustomTranslationTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_content', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['block_custom'] = [
[
'bid' => 1,
'body' => 'box 1 body',
'info' => 'box 1 title',
'format' => '2',
],
[
'bid' => 2,
'body' => 'box 2 body',
'info' => 'box 2 title',
'format' => '2',
],
[
'bid' => 4,
'body' => 'box 2 body',
'info' => 'box 2 title',
'format' => '2',
],
];
$tests[0]['source_data']['i18n_string'] = [
[
'lid' => 1,
'objectid' => 1,
'type' => 'block',
'property' => 'title',
'objectindex' => 1,
'format' => 0,
],
[
'lid' => 2,
'objectid' => 1,
'type' => 'block',
'property' => 'body',
'objectindex' => 1,
'format' => 0,
],
[
'lid' => 3,
'objectid' => 2,
'type' => 'block',
'property' => 'body',
'objectindex' => 2,
'format' => 2,
],
[
'lid' => 4,
'objectid' => 4,
'type' => 'block',
'property' => 'body',
'objectindex' => 4,
'format' => 2,
],
];
$tests[0]['source_data']['locales_target'] = [
[
'lid' => 1,
'language' => 'fr',
'translation' => 'fr - title translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 2,
'language' => 'fr',
'translation' => 'fr - body translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 3,
'language' => 'zu',
'translation' => 'zu - body translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
];
$tests[0]['source_data']['system'] = [
[
'type' => 'module',
'name' => 'system',
'schema_version' => '7001',
'status' => '1',
],
];
$tests[0]['expected_data'] = [
[
'lid' => '1',
'property' => 'title',
'language' => 'fr',
'translation' => 'fr - title translation',
'bid' => '1',
'format' => '2',
'title_translated' => 'fr - title translation',
'body_translated' => 'fr - body translation',
'title' => 'box 1 title',
'body' => 'box 1 body',
],
[
'lid' => '2',
'property' => 'body',
'language' => 'fr',
'translation' => 'fr - body translation',
'bid' => '1',
'format' => '2',
'title_translated' => 'fr - title translation',
'body_translated' => 'fr - body translation',
'title' => 'box 1 title',
'body' => 'box 1 body',
],
[
'lid' => '3',
'property' => 'body',
'language' => 'zu',
'translation' => 'zu - body translation',
'bid' => '2',
'format' => '2',
'title_translated' => NULL,
'body_translated' => 'zu - body translation',
'title' => 'box 2 title',
'body' => 'box 2 body',
],
];
return $tests;
}
}

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Views;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
/**
* Tests the Drupal\block_content\Plugin\views\field\Type handler.
*
* @group block_content
*/
class FieldTypeTest extends ViewsKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block_content',
'block_content_test_views',
];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_field_type'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE): void {
parent::setUp($import_test_views);
if ($import_test_views) {
ViewTestData::createTestViews(get_class($this), ['block_content_test_views']);
}
}
/**
* Tests the field type.
*/
public function testFieldType(): void {
$this->installEntitySchema('block_content');
BlockContentType::create([
'id' => 'basic',
'label' => 'basic',
'revision' => FALSE,
]);
$block_content = BlockContent::create([
'info' => $this->randomMachineName(),
'type' => 'basic',
'langcode' => 'en',
]);
$block_content->save();
$expected_result[] = [
'id' => $block_content->id(),
'type' => $block_content->bundle(),
];
$column_map = [
'id' => 'id',
'type:target_id' => 'type',
];
$view = Views::getView('test_field_type');
$this->executeView($view);
$this->assertIdenticalResultset($view, $expected_result, $column_map, 'The correct block_content type was displayed.');
}
}

View File

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Views;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\KernelTests\KernelTestBase;
use Drupal\views\Tests\ViewResultAssertionTrait;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
/**
* Tests the integration of block_content_revision table.
*
* @group block_content
*/
class RevisionRelationshipsTest extends KernelTestBase {
use ViewResultAssertionTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block_content',
'block_content_test_views',
'system',
'user',
'views',
];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = [
'test_block_content_revision_id',
'test_block_content_revision_revision_id',
];
/**
* Create a block_content with revision and rest result count for both views.
*/
public function testBlockContentRevisionRelationship(): void {
$this->installEntitySchema('block_content');
ViewTestData::createTestViews(static::class, ['block_content_test_views']);
BlockContentType::create([
'id' => 'basic',
'label' => 'basic',
'revision' => TRUE,
]);
$block_content = BlockContent::create([
'info' => $this->randomMachineName(),
'type' => 'basic',
'langcode' => 'en',
]);
$block_content->save();
// Create revision of the block_content.
$block_content_revision = clone $block_content;
$block_content_revision->setNewRevision();
$block_content_revision->save();
$column_map = [
'revision_id' => 'revision_id',
'id_1' => 'id_1',
'block_content_field_data_block_content_field_revision_id' => 'block_content_field_data_block_content_field_revision_id',
];
// Here should be two rows.
$view = Views::getView('test_block_content_revision_id');
$view->preview(NULL, [$block_content->id()]);
$resultset_id = [
[
'revision_id' => '1',
'id_1' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
],
[
'revision_id' => '2',
'id_1' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
],
];
$this->assertIdenticalResultset($view, $resultset_id, $column_map);
// There should be only one row with active revision 2.
$view_revision = Views::getView('test_block_content_revision_revision_id');
$view_revision->preview(NULL, [$block_content->id()]);
$resultset_revision_id = [
[
'revision_id' => '2',
'id_1' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
],
];
$this->assertIdenticalResultset($view_revision, $resultset_revision_id, $column_map);
}
}

View File

@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Kernel\Views;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Tests\ViewResultAssertionTrait;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
/**
* Tests the block_content_revision_user field.
*
* @group block_content
*/
class RevisionUserTest extends ViewsKernelTestBase {
use UserCreationTrait;
use ViewResultAssertionTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block_content',
'block_content_test_views',
'system',
'user',
'views',
];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_block_content_revision_user'];
/**
* Map column names.
*
* @var array
*/
public static $columnMap = [
'id' => 'id',
'revision_id' => 'revision_id',
'revision_user' => 'revision_user',
];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE): void {
parent::setUp($import_test_views);
$this->installEntitySchema('block_content');
$this->installEntitySchema('user');
if ($import_test_views) {
ViewTestData::createTestViews(get_class($this), ['block_content_test_views']);
}
}
/**
* Tests the block_content_revision_user relationship.
*/
public function testRevisionUser(): void {
$primary_author = $this->createUser();
$secondary_author = $this->createUser();
$block_content_type = BlockContentType::create([
'id' => 'basic',
'label' => 'basic block',
]);
$block_content_type->save();
$block_content = BlockContent::create([
'info' => 'Test block content',
'type' => 'basic',
]);
$block_content->setRevisionUserId($primary_author->id());
$block_content->save();
$view = Views::getView('test_block_content_revision_user');
$this->executeView($view);
$this->assertIdenticalResultset($view, [
[
'id' => 1,
'revision_id' => 1,
'revision_user' => $primary_author->id(),
],
], static::$columnMap);
// Test results shows the revision author.
$block_content->setRevisionUser($secondary_author);
$block_content->setNewRevision();
$block_content->save();
$view = Views::getView('test_block_content_revision_user');
$this->executeView($view);
$this->assertIdenticalResultset($view, [
[
'id' => 1,
'revision_id' => 2,
'revision_user' => $secondary_author->id(),
],
], static::$columnMap);
// Build a larger dataset to allow filtering.
$block_content2_title = $this->randomString();
$block_content2 = BlockContent::create([
'info' => $block_content2_title,
'type' => 'basic',
]);
$block_content2->save();
$block_content2->setRevisionUser($primary_author);
$block_content2->setNewRevision();
$block_content2->save();
$view = Views::getView('test_block_content_revision_user');
$this->executeView($view);
$this->assertIdenticalResultset($view, [
[
'id' => 1,
'revision_id' => 2,
'revision_user' => $secondary_author->id(),
],
[
'id' => 2,
'revision_id' => 4,
'revision_user' => $primary_author->id(),
],
], static::$columnMap);
// Test filter by revision_author.
$view = Views::getView('test_block_content_revision_user');
$view->initHandlers();
$view->filter['revision_user']->value = [$secondary_author->id()];
$this->executeView($view);
$this->assertIdenticalResultset($view, [
[
'id' => 1,
'revision_id' => 2,
'revision_user' => $secondary_author->id(),
],
], static::$columnMap);
}
}

View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\block_content\Unit\Menu;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Tests\Core\Menu\LocalTaskIntegrationTestBase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Tests existence of block_content local tasks.
*
* @group block_content
*/
class BlockContentLocalTasksTest extends LocalTaskIntegrationTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->directoryList = [
'system' => 'core/modules/system',
'block_content' => 'core/modules/block_content',
];
parent::setUp();
$config_factory = $this->getConfigFactoryStub([
'system.theme' => ['default' => 'test_c'],
]);
$themes = [];
$themes['test_a'] = (object) [
'status' => 0,
];
$themes['test_b'] = (object) [
'status' => 1,
'info' => [
'name' => 'test_b',
],
];
$themes['test_c'] = (object) [
'status' => 1,
'info' => [
'name' => 'test_c',
],
];
$theme_handler = $this->createMock('Drupal\Core\Extension\ThemeHandlerInterface');
$theme_handler->expects($this->any())
->method('listInfo')
->willReturn($themes);
// Add services required for block local tasks.
$entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
$entity_type_manager->expects($this->any())
->method('getDefinitions')
->willReturn([]);
$container = new ContainerBuilder();
$container->set('config.factory', $config_factory);
$container->set('theme_handler', $theme_handler);
$container->set('entity_type.manager', $entity_type_manager);
\Drupal::setContainer($container);
}
/**
* Checks block_content listing local tasks.
*
* @dataProvider getBlockContentListingRoutes
*/
public function testBlockContentListLocalTasks($route): void {
$this->assertLocalTasks($route, [
0 => [
'system.admin_content',
'entity.block_content.collection',
],
]);
}
/**
* Provides a list of routes to test.
*/
public static function getBlockContentListingRoutes() {
return [
['entity.block_content.collection', 'system.admin_content'],
];
}
}