diff --git a/.drone.yml b/.drone.yml
index 85b59905..821c8a86 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -11,6 +11,15 @@ services:
image: percona/percona-server:5.6
environment:
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
+ selenium:
+ image: selenium/standalone-chrome:3.141.5
+ shm_size: 2g
+ environment:
+ - DISPLAY=:99
+ - SCREEN_WIDTH=1280
+ - SCREEN_HEIGHT=800
+ - NODE_MAX_INSTANCES=5
+ - NODE_MAX_SESSION=5
pipeline:
composer-install:
diff --git a/composer.json b/composer.json
index 2eaf4bcf..9e0982bb 100644
--- a/composer.json
+++ b/composer.json
@@ -7,7 +7,7 @@
"prefer-stable": true,
"require": {
"drupal/core": "^8.8",
- "openeuropa/entity_meta_relation": "dev-OPENEUROPA-3271",
+ "openeuropa/entity_meta_relation": "dev-8.x-1.x",
"drupal/search_api": "~1.17",
"php": ">=7.2"
},
diff --git a/config/install/emr.entity_meta_type.oe_list_page.yml b/config/install/emr.entity_meta_type.oe_list_page.yml
new file mode 100755
index 00000000..6bb5d2cb
--- /dev/null
+++ b/config/install/emr.entity_meta_type.oe_list_page.yml
@@ -0,0 +1,5 @@
+langcode: en
+status: true
+dependencies: { }
+id: oe_list_page
+label: 'List Page'
diff --git a/config/install/field.field.entity_meta.oe_list_page.oe_list_page_config.yml b/config/install/field.field.entity_meta.oe_list_page.oe_list_page_config.yml
new file mode 100644
index 00000000..44975372
--- /dev/null
+++ b/config/install/field.field.entity_meta.oe_list_page.oe_list_page_config.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - emr.entity_meta_type.oe_list_page
+ - field.storage.entity_meta.oe_list_page_config
+id: entity_meta.oe_list_page.oe_list_page_config
+field_name: oe_list_page_config
+entity_type: entity_meta
+bundle: oe_list_page
+label: 'List Pages configuration'
+description: ''
+required: false
+translatable: false
+default_value: { }
+default_value_callback: ''
+settings: { }
+field_type: string_long
diff --git a/config/install/field.field.entity_meta.oe_list_page.oe_list_page_source.yml b/config/install/field.field.entity_meta.oe_list_page.oe_list_page_source.yml
new file mode 100644
index 00000000..14c9ef90
--- /dev/null
+++ b/config/install/field.field.entity_meta.oe_list_page.oe_list_page_source.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - emr.entity_meta_type.oe_list_page
+ - field.storage.entity_meta.oe_list_page_source
+id: entity_meta.oe_list_page.oe_list_page_source
+field_name: oe_list_page_source
+entity_type: entity_meta
+bundle: oe_list_page
+label: 'List Pages source'
+description: ''
+required: true
+translatable: false
+default_value: { }
+default_value_callback: ''
+settings: { }
+field_type: string
diff --git a/config/install/field.storage.entity_meta.oe_list_page_config.yml b/config/install/field.storage.entity_meta.oe_list_page_config.yml
new file mode 100644
index 00000000..0e9ba1de
--- /dev/null
+++ b/config/install/field.storage.entity_meta.oe_list_page_config.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - emr
+id: entity_meta.oe_list_page_config
+field_name: oe_list_page_config
+entity_type: entity_meta
+type: string_long
+settings:
+ case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: { }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/config/install/field.storage.entity_meta.oe_list_page_source.yml b/config/install/field.storage.entity_meta.oe_list_page_source.yml
new file mode 100644
index 00000000..d4e1aae6
--- /dev/null
+++ b/config/install/field.storage.entity_meta.oe_list_page_source.yml
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - emr
+id: entity_meta.oe_list_page_source
+field_name: oe_list_page_source
+entity_type: entity_meta
+type: string
+settings:
+ max_length: 255
+ is_ascii: false
+ case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: { }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/docker-compose.yml b/docker-compose.yml
index 7a02cb4f..1e164598 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -21,6 +21,27 @@ services:
# ports:
# - 3306:3306
+ # If you would like to see what is going on you can run the following on your host:
+ # docker run --rm -p 4444:4444 -p 5900:5900 --network="host" selenium/standalone-chrome-debug:latest
+ # Newer version of this image might run into this issue:
+ # @link https://github.com/elgalu/docker-selenium/issues/20
+ selenium:
+ image: selenium/standalone-chrome-debug:3.141.59-oxygen
+ expose:
+ - '4444'
+ environment:
+ - DISPLAY=:99
+ - SE_OPTS=-debug
+ - SCREEN_WIDTH=1280
+ - SCREEN_HEIGHT=800
+ - VNC_NO_PASSWORD=1
+ ports:
+ - '4444:4444'
+ - "5900:5900"
+ volumes:
+ - /dev/shm:/dev/shm
+ shm_size: 2g
+
#### Mac users: uncomment the "volumes" key to enable the NFS file sharing. You can find more information about Docker for Mac here: https://github.com/openeuropa/openeuropa/blob/master/docs/starting/tooling.md#using-docker-on-macos
#volumes:
diff --git a/oe_list_pages.info.yml b/oe_list_pages.info.yml
index 7a57d1dd..5dc62131 100644
--- a/oe_list_pages.info.yml
+++ b/oe_list_pages.info.yml
@@ -6,5 +6,13 @@ type: module
core: 8.x
dependencies:
- - emr_node:emr_node
+ - emr:emr_node
- search_api:search_api
+
+config_devel:
+ install:
+ - emr.entity_meta_type.oe_list_page
+ - field.field.entity_meta.oe_list_page.list_page_plugin
+ - field.field.entity_meta.oe_list_page.list_page_plugin_config
+ - field.storage.entity_meta.list_page_plugin
+ - field.storage.entity_meta.list_page_plugin_config
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 7002013e..db99eb82 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -6,6 +6,7 @@
+
diff --git a/runner.yml.dist b/runner.yml.dist
index 0f099b9b..544bf5a5 100644
--- a/runner.yml.dist
+++ b/runner.yml.dist
@@ -30,6 +30,12 @@ drupal:
- "vendor"
- "${drupal.root}"
+
+selenium:
+ host: "http://selenium"
+ port: "4444"
+ browser: "chrome"
+
commands:
drupal:site-setup:
- { task: "symlink", from: "../../..", to: "${drupal.root}/modules/custom/oe_list_pages" }
diff --git a/src/ListPageEvents.php b/src/ListPageEvents.php
new file mode 100644
index 00000000..a52599af
--- /dev/null
+++ b/src/ListPageEvents.php
@@ -0,0 +1,26 @@
+entityTypes = $entity_types;
+ $this->bundles = $bundles;
+ }
+
+ /**
+ * Returns the allowed entity types.
+ *
+ * @return string[]
+ * The list of entity types.
+ */
+ public function getEntityTypes(): array {
+ return $this->entityTypes;
+ }
+
+ /**
+ * Set the allowed entity types.
+ *
+ * @param array $entity_types
+ * The list of entity types.
+ */
+ public function setEntityTypes(array $entity_types): void {
+ $this->entityTypes = $entity_types;
+ }
+
+ /**
+ * Returns the allowed bundles.
+ *
+ * @return string[]
+ * The list of allowed bundles.
+ */
+ public function getBundles(): array {
+ return $this->bundles;
+ }
+
+ /**
+ * Set the allowed bundles.
+ *
+ * @param string $entity_type
+ * The entity type.
+ * @param array $bundles
+ * The bundles.
+ */
+ public function setBundles(string $entity_type, array $bundles): void {
+ $this->entityTypes = [$entity_type];
+ $this->bundles = $bundles;
+ }
+
+}
diff --git a/src/ListPageWrapper.php b/src/ListPageWrapper.php
new file mode 100644
index 00000000..9ed95fc8
--- /dev/null
+++ b/src/ListPageWrapper.php
@@ -0,0 +1,91 @@
+entityMeta->set('oe_list_page_source', $entity_type . ':' . $bundle);
+ }
+
+ /**
+ * Returns the entity meta list page source.
+ *
+ * This is a pair of entity_type:bundle that will be used for querying on this
+ * list page.
+ *
+ * @return string|null
+ * The entity_type:bundle pair source.
+ */
+ public function getSource(): ?string {
+ return $this->entityMeta->get('oe_list_page_source')->value;
+ }
+
+ /**
+ * Returns the entity type used as the source.
+ *
+ * @return string|null
+ * The entity type.
+ */
+ public function getSourceEntityType(): ?string {
+ $source = $this->getSource();
+ if (!$source) {
+ return NULL;
+ }
+
+ list($entity_type, $bundle) = explode(':', $source);
+ return $entity_type;
+ }
+
+ /**
+ * Returns the bundle used in the source.
+ *
+ * @return string|null
+ * The bundle.
+ */
+ public function getSourceEntityBundle(): ?string {
+ $source = $this->getSource();
+ if (!$source) {
+ return NULL;
+ }
+
+ list($entity_type, $bundle) = explode(':', $source);
+ return $bundle;
+ }
+
+ /**
+ * Returns the entity meta configuration.
+ *
+ * @return array
+ * The list page configuration.
+ */
+ public function getConfiguration(): array {
+ return $this->entityMeta->get('oe_list_page_config')->isEmpty() ? [] : unserialize($this->entityMeta->get('oe_list_page_config')->value);
+ }
+
+ /**
+ * Sets the entity meta configuration.
+ *
+ * @param array $configuration
+ * The list page configuration.
+ */
+ public function setConfiguration(array $configuration): void {
+ $this->entityMeta->set('oe_list_page_config', serialize($configuration));
+ }
+
+}
diff --git a/src/Plugin/EntityMetaRelation/ListPage.php b/src/Plugin/EntityMetaRelation/ListPage.php
new file mode 100755
index 00000000..b04c22c5
--- /dev/null
+++ b/src/Plugin/EntityMetaRelation/ListPage.php
@@ -0,0 +1,214 @@
+entityTypeBundleInfo = $entity_type_bundle_info;
+ $this->eventDispatcher = $dispatcher;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_field.manager'),
+ $container->get('entity_type.manager'),
+ $container->get('entity_type.bundle.info'),
+ $container->get('event_dispatcher')
+ );
+ }
+
+ /**
+ * Ajax request handler.
+ *
+ * @param array $form
+ * The form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @return array
+ * The form element.
+ */
+ public function updateEntityBundles(array &$form, FormStateInterface $form_state): array {
+ $key = $this->getFormKey();
+ return $form[$key]['bundle_wrapper'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function build(array $form, FormStateInterface $form_state, ContentEntityInterface $entity): array {
+ $key = $this->getFormKey();
+ $this->buildFormContainer($form, $form_state, $key);
+ $entity_meta_bundle = $this->getPluginDefinition()['entity_meta_bundle'];
+
+ // Get the related List Page entity meta.
+ /** @var \Drupal\emr\Field\EntityMetaItemListInterface $entity_meta_list */
+ $entity_meta_list = $entity->get('emr_entity_metas');
+ /** @var \Drupal\emr\Entity\EntityMetaInterface $navigation_block_entity_meta */
+ $entity_meta = $entity_meta_list->getEntityMeta($entity_meta_bundle);
+ /** @var \Drupal\oe_list_pages\ListPageWrapper $entity_meta_wrapper */
+ $entity_meta_wrapper = $entity_meta->getWrapper();
+
+ $entity_type_options = [];
+ $entity_types = $this->entityTypeManager->getDefinitions();
+ foreach ($entity_types as $entity_type_key => $entity_type) {
+ if (!$entity_type instanceof ContentEntityTypeInterface) {
+ continue;
+ }
+ $entity_type_options[$entity_type_key] = $entity_type->getLabel();
+ }
+
+ $event = new ListPageSourceAlterEvent(array_keys($entity_type_options));
+ $this->eventDispatcher->dispatch(ListPageEvents::ALTER_ENTITY_TYPES, $event);
+ $entity_type_options = array_intersect_key($entity_type_options, array_combine($event->getEntityTypes(), $event->getEntityTypes()));
+
+ $entity_type_id = $entity_meta_wrapper->getSourceEntityType();
+
+ $form[$key]['entity_type'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Source entity type'),
+ '#description' => $this->t('Select the entity type that will be used as the source for this list page.'),
+ '#options' => $entity_type_options,
+ // If there is no selection, the default entity type will be Node, due to
+ // self::fillDefaultEntityMetaValues().
+ '#default_value' => $form_state->getValue('entity_type') ?? $entity_type_id,
+ '#required' => TRUE,
+ '#ajax' => [
+ 'callback' => [$this, 'updateEntityBundles'],
+ 'disable-refocus' => FALSE,
+ 'event' => 'change',
+ 'wrapper' => 'list-page-entity-bundles',
+ ],
+ ];
+
+ $bundle_options = [];
+ if ($selected_entity_type = $form[$key]['entity_type']['#default_value']) {
+ $bundles = $this->entityTypeBundleInfo->getBundleInfo($selected_entity_type);
+ foreach ($bundles as $bundle_key => $bundle) {
+ $bundle_options[$bundle_key] = $bundle['label'];
+ }
+
+ $event->setBundles($selected_entity_type, array_keys($bundle_options));
+ $this->eventDispatcher->dispatch(ListPageEvents::ALTER_BUNDLES, $event);
+ $bundle_options = array_intersect_key($bundle_options, array_combine($event->getBundles(), $event->getBundles()));
+ }
+
+ $entity_bundle_id = $entity_meta_wrapper->getSourceEntityBundle();
+
+ $form[$key]['bundle_wrapper'] = [
+ '#type' => 'container',
+ '#attributes' => [
+ 'id' => 'list-page-entity-bundles',
+ ],
+ ];
+
+ if (!empty($form_state->getValue('entity_type')) || $entity_bundle_id) {
+ $form[$key]['bundle_wrapper']['bundle'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Source bundle'),
+ '#default_value' => $form_state->getValue('bundle') ?? $entity_bundle_id,
+ '#options' => $bundle_options,
+ '#required' => TRUE,
+ ];
+ }
+
+ // Set the entity meta so we use it in the submit handler.
+ $form_state->set($entity_meta_bundle . '_entity_meta', $entity_meta);
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submit(array $form, FormStateInterface $form_state): void {
+ // Do not save new entity meta if we don't have required values.
+ if (!$form_state->getValue('entity_type') || !$form_state->getValue('bundle')) {
+ return;
+ }
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $host_entity */
+ $host_entity = $form_state->getFormObject()->getEntity();
+
+ $entity_meta_bundle = $this->getPluginDefinition()['entity_meta_bundle'];
+
+ /** @var \Drupal\emr\Entity\EntityMetaInterface $entity_meta */
+ $entity_meta = $form_state->get($entity_meta_bundle . '_entity_meta');
+ /** @var \Drupal\oe_list_pages\ListPageWrapper $entity_meta_wrapper */
+ $entity_meta_wrapper = $entity_meta->getWrapper();
+
+ $entity_meta_wrapper->setSource($form_state->getValue('entity_type'), $form_state->getValue('bundle'));
+ $host_entity->get('emr_entity_metas')->attach($entity_meta);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fillDefaultEntityMetaValues(EntityMetaInterface $entity_meta): void {
+ // Set the default value to be the first node bundle.
+ // We want to do this because we don't want any entity meta being created
+ // without a value (via the API).
+ $bundles = $this->entityTypeBundleInfo->getBundleInfo('node');
+ /** @var \Drupal\oe_list_pages\ListPageWrapper $wrapper */
+ $wrapper = $entity_meta->getWrapper();
+ $wrapper->setSource('node', key($bundles));
+ }
+
+}
diff --git a/tests/FunctionalJavascript/ListPagesPluginTest.php b/tests/FunctionalJavascript/ListPagesPluginTest.php
new file mode 100755
index 00000000..c07ac39f
--- /dev/null
+++ b/tests/FunctionalJavascript/ListPagesPluginTest.php
@@ -0,0 +1,242 @@
+drupalCreateContentType([
+ 'type' => 'node_type_one',
+ 'name' => 'Node Type 1',
+ ]);
+
+ $this->drupalCreateContentType([
+ 'type' => 'node_type_two',
+ 'name' => 'Node Type 2',
+ ]);
+
+ $this->drupalCreateContentType([
+ 'type' => 'node_type_three',
+ 'name' => 'Node Type 3',
+ ]);
+
+ Vocabulary::create([
+ 'vid' => 'vocab_one',
+ 'name' => 'Vocabulary 1',
+ ])->save();
+
+ Vocabulary::create([
+ 'vid' => 'vocab_two',
+ 'name' => 'Vocabulary 2',
+ ])->save();
+
+ Vocabulary::create([
+ 'vid' => 'vocab_three',
+ 'name' => 'Vocabulary 3',
+ ])->save();
+
+ /** @var \Drupal\emr\EntityMetaRelationInstaller $installer */
+ $installer = \Drupal::service('emr.installer');
+ $installer->installEntityMetaTypeOnContentEntityType('oe_list_page', 'node', [
+ 'node_type_one',
+ ]);
+ }
+
+ /**
+ * Test List Page entity meta plugin and available entity types/bundles.
+ */
+ public function testListPagePluginForm(): void {
+ $this->drupalLogin($this->rootUser);
+ $this->drupalGet('node/add/node_type_one');
+
+ // Open the list page details element.
+ $this->clickLink('List Page');
+
+ $actual_entity_types = $this->getSelectOptions('Source entity type');
+
+ $expected_entity_types = [
+ 'entity_meta_relation' => 'Entity Meta Relation',
+ 'entity_meta' => 'Entity meta',
+ 'node' => 'Content',
+ 'path_alias' => 'URL alias',
+ 'search_api_task' => 'Search task',
+ 'user' => 'User',
+ 'taxonomy_term' => 'Taxonomy term',
+ ];
+ $this->assertEquals($expected_entity_types, $actual_entity_types);
+ // By default, Node is selected if there are no stored values.
+ $this->assertOptionSelected('Source entity type', 'Content');
+
+ $actual_bundles = $this->getSelectOptions('Source bundle');
+ $expected_bundles = [
+ 'node_type_one' => 'Node Type 1',
+ 'node_type_two' => 'Node Type 2',
+ 'node_type_three' => 'Node Type 3',
+ ];
+ $this->assertEquals($expected_bundles, $actual_bundles);
+
+ // Switch to the taxonomy term and assert that we have different bundles.
+ $this->getSession()->getPage()->selectFieldOption('Source entity type', 'taxonomy_term');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $actual_bundles = $this->getSelectOptions('Source bundle');
+ $expected_bundles = [
+ 'vocab_one' => 'Vocabulary 1',
+ 'vocab_two' => 'Vocabulary 2',
+ 'vocab_three' => 'Vocabulary 3',
+ ];
+ $this->assertEquals($expected_bundles, $actual_bundles);
+
+ // Switch to a bundle-less entity type and assert we have only one bundle
+ // selection available.
+ $this->getSession()->getPage()->selectFieldOption('Source entity type', 'user');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $actual_bundles = $this->getSelectOptions('Source bundle');
+ $expected_bundles = [
+ 'user' => 'User',
+ ];
+ $this->assertEquals($expected_bundles, $actual_bundles);
+
+ // Set state values to trigger the test event subscriber and make some
+ // limitations.
+ $allowed = [
+ 'node' => [
+ 'node_type_two',
+ 'node_type_three',
+ ],
+ 'taxonomy_term' => [
+ 'vocab_one',
+ 'vocab_three',
+ ],
+ ];
+ \Drupal::state()->set('oe_list_pages_test.allowed_entity_types_bundles', $allowed);
+
+ $this->drupalGet('node/add/node_type_one');
+ $this->clickLink('List Page');
+ $actual_entity_types = $this->getSelectOptions('Source entity type');
+ $this->assertEquals([
+ 'node' => 'Content',
+ 'taxonomy_term' => 'Taxonomy term',
+ ], $actual_entity_types);
+ $this->assertOptionSelected('Source entity type', 'Content');
+ $actual_bundles = $this->getSelectOptions('Source bundle');
+ $expected_bundles = [
+ 'node_type_two' => 'Node Type 2',
+ 'node_type_three' => 'Node Type 3',
+ ];
+ $this->assertEquals($expected_bundles, $actual_bundles);
+ $this->getSession()->getPage()->selectFieldOption('Source entity type', 'taxonomy_term');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $actual_bundles = $this->getSelectOptions('Source bundle');
+ $expected_bundles = [
+ 'vocab_one' => 'Vocabulary 1',
+ 'vocab_three' => 'Vocabulary 3',
+ ];
+ $this->assertEquals($expected_bundles, $actual_bundles);
+
+ // Select a bundle and save the node.
+ $this->getSession()->getPage()->fillField('Title', 'Node title');
+ $this->getSession()->getPage()->selectFieldOption('Source bundle', 'vocab_three');
+ $this->getSession()->getPage()->pressButton('Save');
+
+ // Assert the entity meta was correctly saved.
+ $node = Node::load(1);
+ $this->assertEquals(1, $node->getRevisionId());
+ $this->assertEquals('Node title', $node->label());
+ /** @var \Drupal\emr\Field\ComputedEntityMetasItemList $entity_meta_list */
+ $entity_meta_list = $node->get('emr_entity_metas');
+ $entity_meta = $entity_meta_list->getEntityMeta('oe_list_page');
+ $this->assertFalse($entity_meta->isNew());
+
+ /** @var \Drupal\oe_list_pages\ListPageWrapper $entity_meta_wrapper */
+ $entity_meta_wrapper = $entity_meta->getWrapper();
+ $this->assertEquals('taxonomy_term:vocab_three', $entity_meta_wrapper->getSource());
+ $this->assertEquals('taxonomy_term', $entity_meta_wrapper->getSourceEntityType());
+ $this->assertEquals('vocab_three', $entity_meta_wrapper->getSourceEntityBundle());
+
+ // Edit the node and assert that we show correct values in the form.
+ $this->drupalGet($node->toUrl('edit-form'));
+ $this->clickLink('List Page');
+ $this->assertOptionSelected('Source entity type', 'Taxonomy term');
+ $this->assertOptionSelected('Source bundle', 'Vocabulary 3');
+
+ // Change the source to a Node type.
+ $this->getSession()->getPage()->fillField('Title', 'Node title 2');
+ $this->getSession()->getPage()->selectFieldOption('Source entity type', 'node');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->getSession()->getPage()->selectFieldOption('Source bundle', 'node_type_two');
+ $this->getSession()->getPage()->pressButton('Save');
+
+ \Drupal::entityTypeManager()->getStorage('node')->resetCache();
+ $node = Node::load(1);
+ $this->assertEquals(2, $node->getRevisionId());
+ /** @var \Drupal\emr\Field\ComputedEntityMetasItemList $entity_meta_list */
+ $entity_meta_list = $node->get('emr_entity_metas');
+ $entity_meta = $entity_meta_list->getEntityMeta('oe_list_page');
+ $entity_meta_wrapper = $entity_meta->getWrapper();
+ $this->assertEquals('node:node_type_two', $entity_meta_wrapper->getSource());
+ $this->assertEquals('node', $entity_meta_wrapper->getSourceEntityType());
+ $this->assertEquals('node_type_two', $entity_meta_wrapper->getSourceEntityBundle());
+
+ // Assert the previous entity meta revision kept the old value.
+ $first_revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision(1);
+ $this->assertEquals(1, $first_revision->getRevisionId());
+ /** @var \Drupal\emr\Field\ComputedEntityMetasItemList $entity_meta_list */
+ $entity_meta_list = $first_revision->get('emr_entity_metas');
+ $entity_meta = $entity_meta_list->getEntityMeta('oe_list_page');
+ $this->assertFalse($entity_meta->isNew());
+
+ /** @var \Drupal\oe_list_pages\ListPageWrapper $entity_meta_wrapper */
+ $entity_meta_wrapper = $entity_meta->getWrapper();
+ $this->assertEquals('taxonomy_term:vocab_three', $entity_meta_wrapper->getSource());
+ $this->assertEquals('taxonomy_term', $entity_meta_wrapper->getSourceEntityType());
+ $this->assertEquals('vocab_three', $entity_meta_wrapper->getSourceEntityBundle());
+ }
+
+ /**
+ * Get available options of select box.
+ *
+ * @param string $field
+ * The label, id or name of select box.
+ *
+ * @return array
+ * Select box options.
+ */
+ protected function getSelectOptions(string $field): array {
+ $page = $this->getSession()->getPage();
+ $options = $page->findField($field)->findAll('css', 'option');
+ $actual_options = [];
+ foreach ($options as $option) {
+ $actual_options[$option->getValue()] = $option->getText();
+ }
+ return $actual_options;
+ }
+
+}
diff --git a/tests/Kernel/ListPagesTest.php b/tests/Kernel/ListPagesTest.php
new file mode 100644
index 00000000..9aa8b566
--- /dev/null
+++ b/tests/Kernel/ListPagesTest.php
@@ -0,0 +1,106 @@
+installConfig(['oe_list_pages', 'emr', 'emr_node']);
+ $this->installSchema('node', ['node_access']);
+ $this->installEntitySchema('node');
+ $this->installEntitySchema('entity_meta');
+ $this->installEntitySchema('entity_meta_relation');
+
+ $values = ['type' => 'list_page', 'name' => 'List page'];
+ $this->nodeType = NodeType::create($values);
+ $this->nodeType->save();
+
+ /** @var \Drupal\emr\EntityMetaRelationInstaller $installer */
+ $installer = \Drupal::service('emr.installer');
+ $installer->installEntityMetaTypeOnContentEntityType('oe_list_page', 'node', [
+ 'list_page',
+ ]);
+
+ $this->nodeStorage = $this->entityTypeManager->getStorage('node');
+ $this->entityMetaStorage = $this->entityTypeManager->getStorage('entity_meta');
+ }
+
+ /**
+ * Tests that node works correctly with entity meta.
+ */
+ public function testListPagesEntityMeta(): void {
+ $node_storage = $this->entityTypeManager->getStorage('node');
+ $node = $node_storage->create([
+ 'type' => $this->nodeType->id(),
+ 'title' => 'List Page',
+ ]);
+ $node->save();
+
+ /** @var \Drupal\emr\Entity\EntityMetaInterface $entity_meta */
+ $entity_meta = $node->get('emr_entity_metas')->getEntityMeta('oe_list_page');
+ $this->assertFalse($entity_meta->isNew());
+ /** @var \Drupal\oe_list_pages\ListPageWrapper $wrapper */
+ $wrapper = $entity_meta->getWrapper();
+ $this->assertEquals('node:list_page', $wrapper->getSource());
+ $this->assertEquals([], $wrapper->getConfiguration());
+ $wrapper->setSource('node', 'list_page');
+ $wrapper->setConfiguration(['exposed' => ['list']]);
+ $node->get('emr_entity_metas')->attach($entity_meta);
+ $node->save();
+
+ $updated_node = $node_storage->load($node->id());
+ /** @var \Drupal\emr\Entity\EntityMetaInterface $entity_meta */
+ $entity_meta = $updated_node->get('emr_entity_metas')
+ ->getEntityMeta('oe_list_page');
+
+ /** @var \Drupal\oe_list_pages\ListPageWrapper $wrapper */
+ $wrapper = $entity_meta->getWrapper();
+ $this->assertEquals($wrapper->getConfiguration(), ['exposed' => ['list']]);
+ }
+
+}
diff --git a/tests/modules/oe_list_pages_test/oe_list_pages_test.info.yml b/tests/modules/oe_list_pages_test/oe_list_pages_test.info.yml
new file mode 100755
index 00000000..d76a1428
--- /dev/null
+++ b/tests/modules/oe_list_pages_test/oe_list_pages_test.info.yml
@@ -0,0 +1,9 @@
+name: OpenEuropa List Pages
+type: module
+description: The test module for OE List Pages.
+
+package: Testing
+core: 8.x
+
+dependencies:
+ - oe_list_pages
diff --git a/tests/modules/oe_list_pages_test/oe_list_pages_test.services.yml b/tests/modules/oe_list_pages_test/oe_list_pages_test.services.yml
new file mode 100755
index 00000000..f6862f0f
--- /dev/null
+++ b/tests/modules/oe_list_pages_test/oe_list_pages_test.services.yml
@@ -0,0 +1,6 @@
+services:
+ oe_list_pages_test.event_subscriber:
+ class: Drupal\oe_list_pages_test\EventSubscriber\ListPagesTestSubscriber
+ arguments: ['@state']
+ tags:
+ - { name: event_subscriber }
diff --git a/tests/modules/oe_list_pages_test/src/EventSubscriber/ListPagesTestSubscriber.php b/tests/modules/oe_list_pages_test/src/EventSubscriber/ListPagesTestSubscriber.php
new file mode 100755
index 00000000..f6392fd0
--- /dev/null
+++ b/tests/modules/oe_list_pages_test/src/EventSubscriber/ListPagesTestSubscriber.php
@@ -0,0 +1,82 @@
+state = $state;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ return [
+ ListPageEvents::ALTER_ENTITY_TYPES => ['onEntityTypesAlter'],
+ ListPageEvents::ALTER_BUNDLES => ['onBundlesAlter'],
+ ];
+ }
+
+ /**
+ * Event handler for limiting the allowed entity types.
+ *
+ * @param \Drupal\oe_list_pages\ListPageSourceAlterEvent $event
+ * The event object.
+ */
+ public function onEntityTypesAlter(ListPageSourceAlterEvent $event): void {
+ $entity_types = $event->getEntityTypes();
+ $allowed = $this->state->get('oe_list_pages_test.allowed_entity_types_bundles');
+ if ($allowed === NULL) {
+ return;
+ }
+
+ $allowed_entity_types = array_keys($allowed);
+ $event->setEntityTypes(array_intersect($entity_types, $allowed_entity_types));
+ }
+
+ /**
+ * Event handler for limiting the allowed bundles.
+ *
+ * @param \Drupal\oe_list_pages\ListPageSourceAlterEvent $event
+ * The event object.
+ */
+ public function onBundlesAlter(ListPageSourceAlterEvent $event): void {
+ $entity_types = $event->getEntityTypes();
+ $entity_type = reset($entity_types);
+ $bundles = $event->getBundles();
+
+ $allowed = $this->state->get('oe_list_pages_test.allowed_entity_types_bundles');
+ if ($allowed === NULL) {
+ return;
+ }
+
+ $allowed_bundles = $allowed[$entity_type] ?? [];
+
+ $event->setBundles($entity_type, array_intersect($bundles, $allowed_bundles));
+ }
+
+}