3 namespace Drupal\Tests\content_moderation\Kernel;
5 use Drupal\KernelTests\KernelTestBase;
6 use Drupal\language\Entity\ConfigurableLanguage;
7 use Drupal\node\Entity\Node;
8 use Drupal\node\Entity\NodeType;
9 use Drupal\workflows\Entity\Workflow;
12 * @coversDefaultClass \Drupal\content_moderation\Plugin\Validation\Constraint\ModerationStateConstraintValidator
13 * @group content_moderation
15 class EntityStateChangeValidationTest extends KernelTestBase {
20 public static $modules = [
26 'content_translation',
33 protected function setUp() {
36 $this->installSchema('node', 'node_access');
37 $this->installEntitySchema('node');
38 $this->installEntitySchema('user');
39 $this->installEntitySchema('content_moderation_state');
40 $this->installConfig('content_moderation');
44 * Test valid transitions.
48 public function testValidTransition() {
49 $node_type = NodeType::create([
53 $workflow = Workflow::load('editorial');
54 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
57 $node = Node::create([
59 'title' => 'Test title',
61 $node->moderation_state->value = 'draft';
64 $node->moderation_state->value = 'published';
65 $this->assertCount(0, $node->validate());
68 $this->assertEquals('published', $node->moderation_state->value);
72 * Test invalid transitions.
76 public function testInvalidTransition() {
77 $node_type = NodeType::create([
81 $workflow = Workflow::load('editorial');
82 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
85 $node = Node::create([
87 'title' => 'Test title',
89 $node->moderation_state->value = 'draft';
92 $node->moderation_state->value = 'archived';
93 $violations = $node->validate();
94 $this->assertCount(1, $violations);
96 $this->assertEquals('Invalid state transition from <em class="placeholder">Draft</em> to <em class="placeholder">Archived</em>', $violations->get(0)->getMessage());
100 * Test validation with an invalid state.
102 public function testInvalidState() {
103 $node_type = NodeType::create([
107 $workflow = Workflow::load('editorial');
108 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
111 $node = Node::create([
113 'title' => 'Test title',
115 $node->moderation_state->value = 'invalid_state';
116 $violations = $node->validate();
118 $this->assertCount(1, $violations);
119 $this->assertEquals('State <em class="placeholder">invalid_state</em> does not exist on <em class="placeholder">Editorial</em> workflow', $violations->get(0)->getMessage());
123 * Test validation with content that has no initial state or an invalid state.
125 public function testInvalidStateWithoutExisting() {
126 // Create content without moderation enabled for the content type.
127 $node_type = NodeType::create([
131 $node = Node::create([
133 'title' => 'Test title',
137 // Enable moderation to test validation on existing content, with no
139 $workflow = Workflow::load('editorial');
140 $workflow->getTypePlugin()->addState('deleted_state', 'Deleted state');
141 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
144 // Validate the invalid state.
145 $node->moderation_state->value = 'invalid_state';
146 $violations = $node->validate();
147 $this->assertCount(1, $violations);
149 // Assign the node to a state we're going to delete.
150 $node->moderation_state->value = 'deleted_state';
153 // Delete the state so $node->original contains an invalid state when
155 $workflow->getTypePlugin()->deleteState('deleted_state');
157 $node->moderation_state->value = 'draft';
158 $violations = $node->validate();
159 $this->assertCount(0, $violations);
163 * Test state transition validation with multiple languages.
165 public function testInvalidStateMultilingual() {
166 ConfigurableLanguage::createFromLangcode('fr')->save();
167 $node_type = NodeType::create([
172 $workflow = Workflow::load('editorial');
173 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
176 $node = Node::create([
178 'title' => 'English Published Node',
180 'moderation_state' => 'published',
184 $node_fr = $node->addTranslation('fr', $node->toArray());
185 $node_fr->setTitle('French Published Node');
187 $this->assertEquals('published', $node_fr->moderation_state->value);
189 // Create a pending revision of the original node.
190 $node->moderation_state = 'draft';
191 $node->setNewRevision(TRUE);
192 $node->isDefaultRevision(FALSE);
195 // For the pending english revision, there should be a violation from draft
197 $node->moderation_state = 'archived';
198 $violations = $node->validate();
199 $this->assertCount(1, $violations);
200 $this->assertEquals('Invalid state transition from <em class="placeholder">Draft</em> to <em class="placeholder">Archived</em>', $violations->get(0)->getMessage());
202 // From the default french published revision, there should be none.
203 $node_fr = Node::load($node->id())->getTranslation('fr');
204 $this->assertEquals('published', $node_fr->moderation_state->value);
205 $node_fr->moderation_state = 'archived';
206 $violations = $node_fr->validate();
207 $this->assertCount(0, $violations);
209 // From the latest french revision, there should also be no violation.
210 $node_fr = Node::load($node->id())->getTranslation('fr');
211 $this->assertEquals('published', $node_fr->moderation_state->value);
212 $node_fr->moderation_state = 'archived';
213 $violations = $node_fr->validate();
214 $this->assertCount(0, $violations);
218 * Tests that content without prior moderation information can be moderated.
220 public function testExistingContentWithNoModeration() {
221 $node_type = NodeType::create([
225 /** @var \Drupal\node\NodeInterface $node */
226 $node = Node::create([
228 'title' => 'Test title',
234 // Enable moderation for our node type.
235 $workflow = Workflow::load('editorial');
236 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
239 $node = Node::load($nid);
241 // Having no previous state should not break validation.
242 $violations = $node->validate();
244 $this->assertCount(0, $violations);
246 // Having no previous state should not break saving the node.
247 $node->setTitle('New');
252 * Tests that content without prior moderation information can be translated.
254 public function testExistingMultilingualContentWithNoModeration() {
256 ConfigurableLanguage::createFromLangcode('fr')->save();
258 $node_type = NodeType::create([
262 /** @var \Drupal\node\NodeInterface $node */
263 $node = Node::create([
265 'title' => 'Test title',
272 $node = Node::load($nid);
274 // Creating a translation shouldn't break, even though there's no previous
275 // moderated revision for the new language.
276 $node_fr = $node->addTranslation('fr');
277 $node_fr->setTitle('Francais');
280 // Enable moderation for our node type.
281 $workflow = Workflow::load('editorial');
282 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
285 // Reload the French version of the node.
286 $node = Node::load($nid);
287 $node_fr = $node->getTranslation('fr');
289 /** @var \Drupal\node\NodeInterface $node_fr */
290 $node_fr->setTitle('Nouveau');