1<?php
2
3namespace Drupal\Tests\Core\Field;
4
5use Drupal\Core\DependencyInjection\ContainerBuilder;
6use Drupal\Core\Field\FieldDefinitionInterface;
7use Drupal\Core\Field\FieldItemInterface;
8use Drupal\Core\Field\FieldItemList;
9use Drupal\Core\Field\FieldStorageDefinitionInterface;
10use Drupal\Core\Field\FieldTypePluginManagerInterface;
11use Drupal\Core\Form\FormState;
12use Drupal\Tests\UnitTestCase;
13
14/**
15 * @coversDefaultClass \Drupal\Core\Field\FieldItemList
16 * @group Field
17 */
18class FieldItemListTest extends UnitTestCase {
19
20  /**
21   * @covers ::equals
22   *
23   * @dataProvider providerTestEquals
24   */
25  public function testEquals($expected, FieldItemInterface $first_field_item = NULL, FieldItemInterface $second_field_item = NULL) {
26
27    // Mock the field type manager and place it in the container.
28    $field_type_manager = $this->createMock('Drupal\Core\Field\FieldTypePluginManagerInterface');
29    $container = new ContainerBuilder();
30    $container->set('plugin.manager.field.field_type', $field_type_manager);
31    \Drupal::setContainer($container);
32
33    // Set up three properties, one of them being computed.
34    $property_definitions['0'] = $this->createMock('Drupal\Core\TypedData\DataDefinitionInterface');
35    $property_definitions['0']->expects($this->any())
36      ->method('isComputed')
37      ->willReturn(FALSE);
38    $property_definitions['1'] = $this->createMock('Drupal\Core\TypedData\DataDefinitionInterface');
39    $property_definitions['1']->expects($this->any())
40      ->method('isComputed')
41      ->willReturn(FALSE);
42    $property_definitions['2'] = $this->createMock('Drupal\Core\TypedData\DataDefinitionInterface');
43    $property_definitions['2']->expects($this->any())
44      ->method('isComputed')
45      ->willReturn(TRUE);
46
47    $field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
48    $field_storage_definition->expects($this->any())
49      ->method('getPropertyDefinitions')
50      ->will($this->returnValue($property_definitions));
51    $field_definition = $this->createMock('Drupal\Core\Field\FieldDefinitionInterface');
52    $field_definition->expects($this->any())
53      ->method('getFieldStorageDefinition')
54      ->willReturn($field_storage_definition);
55
56    $field_list_a = new FieldItemList($field_definition);
57    $field_list_b = new FieldItemList($field_definition);
58
59    // Set up the mocking necessary for creating field items.
60    $field_type_manager->expects($this->any())
61      ->method('createFieldItem')
62      ->willReturnOnConsecutiveCalls($first_field_item, $second_field_item);
63
64    // Set the field item values.
65    if ($first_field_item instanceof FieldItemInterface) {
66      $field_list_a->setValue($first_field_item);
67    }
68    if ($second_field_item instanceof FieldItemInterface) {
69      $field_list_b->setValue($second_field_item);
70    }
71
72    $this->assertEquals($expected, $field_list_a->equals($field_list_b));
73  }
74
75  /**
76   * Data provider for testEquals.
77   */
78  public function providerTestEquals() {
79    // Tests field item lists with no values.
80    $datasets[] = [TRUE];
81
82    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_a */
83    $field_item_a = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
84    $field_item_a->setValue([1]);
85    // Tests field item lists where one has a value and one does not.
86    $datasets[] = [FALSE, $field_item_a];
87
88    // Tests field item lists where both have the same value.
89    $datasets[] = [TRUE, $field_item_a, $field_item_a];
90
91    /** @var \Drupal\Core\Field\FieldItemBase  $fv */
92    $field_item_b = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
93    $field_item_b->setValue([2]);
94    // Tests field item lists where both have the different values.
95    $datasets[] = [FALSE, $field_item_a, $field_item_b];
96
97    /** @var \Drupal\Core\Field\FieldItemBase  $fv */
98    $field_item_c = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
99    $field_item_c->setValue(['0' => 1, '1' => 2]);
100    $field_item_d = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
101    $field_item_d->setValue(['1' => 2, '0' => 1]);
102
103    // Tests field item lists where both have the differently ordered values.
104    $datasets[] = [TRUE, $field_item_c, $field_item_d];
105
106    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_e */
107    $field_item_e = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
108    $field_item_e->setValue(['2']);
109
110    // Tests field item lists where both have same values but different data
111    // types.
112    $datasets[] = [TRUE, $field_item_b, $field_item_e];
113
114    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_f */
115    $field_item_f = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
116    $field_item_f->setValue(['0' => 1, '1' => 2, '2' => 3]);
117    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_g */
118    $field_item_g = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
119    $field_item_g->setValue(['0' => 1, '1' => 2, '2' => 4]);
120
121    // Tests field item lists where both have same values for the non-computed
122    // properties ('0' and '1') and a different value for the computed one
123    // ('2').
124    $datasets[] = [TRUE, $field_item_f, $field_item_g];
125
126    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_h */
127    $field_item_h = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
128    $field_item_h->setValue(['0' => 1, '1' => 2, '3' => 3]);
129    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_i */
130    $field_item_i = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
131    $field_item_i->setValue(['0' => 1, '1' => 2, '3' => 4]);
132
133    // Tests field item lists where both have same values for the non-computed
134    // properties ('0' and '1') and a different value for a property that does
135    // not exist ('3').
136    $datasets[] = [TRUE, $field_item_h, $field_item_i];
137
138    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_j */
139    $field_item_j = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
140    $field_item_j->setValue(['0' => 1]);
141    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_k */
142    $field_item_k = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
143    $field_item_k->setValue(['0' => 1, '1' => NULL]);
144    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_l */
145    $field_item_l = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
146    $field_item_l->setValue(['0' => 1, '1' => FALSE]);
147    /** @var \Drupal\Core\Field\FieldItemBase  $field_item_m */
148    $field_item_m = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
149    $field_item_m->setValue(['0' => 1, '1' => '']);
150
151    // Tests filter properties with a NULL value. Empty strings or other false-y
152    // values are not filtered.
153    $datasets[] = [TRUE, $field_item_j, $field_item_k];
154    $datasets[] = [FALSE, $field_item_j, $field_item_l];
155    $datasets[] = [FALSE, $field_item_j, $field_item_m];
156
157    return $datasets;
158  }
159
160  /**
161   * Tests identical behavior of ::hasAffectingChanges with ::equals.
162   *
163   * @covers ::hasAffectingChanges
164   *
165   * @dataProvider providerTestEquals
166   */
167  public function testHasAffectingChanges($expected, FieldItemInterface $first_field_item = NULL, FieldItemInterface $second_field_item = NULL) {
168    // Mock the field type manager and place it in the container.
169    $field_type_manager = $this->createMock(FieldTypePluginManagerInterface::class);
170    $container = new ContainerBuilder();
171    $container->set('plugin.manager.field.field_type', $field_type_manager);
172    \Drupal::setContainer($container);
173
174    $field_storage_definition = $this->createMock(FieldStorageDefinitionInterface::class);
175    $field_storage_definition->expects($this->any())
176      ->method('getColumns')
177      ->willReturn([0 => '0', 1 => '1']);
178
179    // Set up three properties, one of them being computed.
180    $property_definitions['0'] = $this->createMock('Drupal\Core\TypedData\DataDefinitionInterface');
181    $property_definitions['0']->expects($this->any())
182      ->method('isComputed')
183      ->willReturn(FALSE);
184    $property_definitions['1'] = $this->createMock('Drupal\Core\TypedData\DataDefinitionInterface');
185    $property_definitions['1']->expects($this->any())
186      ->method('isComputed')
187      ->willReturn(FALSE);
188    $property_definitions['2'] = $this->createMock('Drupal\Core\TypedData\DataDefinitionInterface');
189    $property_definitions['2']->expects($this->any())
190      ->method('isComputed')
191      ->willReturn(TRUE);
192
193    $field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
194    $field_storage_definition->expects($this->any())
195      ->method('getPropertyDefinitions')
196      ->will($this->returnValue($property_definitions));
197
198    $field_definition = $this->createMock(FieldDefinitionInterface::class);
199    $field_definition->expects($this->any())
200      ->method('getFieldStorageDefinition')
201      ->willReturn($field_storage_definition);
202    $field_definition->expects($this->any())
203      ->method('isComputed')
204      ->willReturn(FALSE);
205
206    $field_list_a = new FieldItemList($field_definition);
207    $field_list_b = new FieldItemList($field_definition);
208
209    // Set up the mocking necessary for creating field items.
210    $field_type_manager->expects($this->any())
211      ->method('createFieldItem')
212      ->willReturnOnConsecutiveCalls($first_field_item, $second_field_item);
213
214    // Set the field item values.
215    if ($first_field_item instanceof FieldItemInterface) {
216      $field_list_a->setValue($first_field_item);
217    }
218    if ($second_field_item instanceof FieldItemInterface) {
219      $field_list_b->setValue($second_field_item);
220    }
221
222    $this->assertEquals($expected, !$field_list_a->hasAffectingChanges($field_list_b, ''));
223  }
224
225  /**
226   * @covers ::equals
227   */
228  public function testEqualsEmptyItems() {
229    /** @var \Drupal\Core\Field\FieldItemBase  $fv */
230    $first_field_item = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
231    $first_field_item->setValue(['0' => 1, '1' => 2]);
232    $second_field_item = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
233    $second_field_item->setValue(['1' => 2, '0' => 1]);
234    $empty_field_item = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
235    // Mock the field type manager and place it in the container.
236    $field_type_manager = $this->createMock('Drupal\Core\Field\FieldTypePluginManagerInterface');
237    $container = new ContainerBuilder();
238    $container->set('plugin.manager.field.field_type', $field_type_manager);
239    \Drupal::setContainer($container);
240
241    // Set up the properties of the field item.
242    $property_definitions['0'] = $this->createMock('Drupal\Core\TypedData\DataDefinitionInterface');
243    $property_definitions['0']->expects($this->any())
244      ->method('isComputed')
245      ->willReturn(FALSE);
246    $property_definitions['1'] = $this->createMock('Drupal\Core\TypedData\DataDefinitionInterface');
247    $property_definitions['1']->expects($this->any())
248      ->method('isComputed')
249      ->willReturn(FALSE);
250
251    $field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
252    $field_storage_definition->expects($this->any())
253      ->method('getPropertyDefinitions')
254      ->will($this->returnValue($property_definitions));
255    $field_definition = $this->createMock('Drupal\Core\Field\FieldDefinitionInterface');
256    $field_definition->expects($this->any())
257      ->method('getFieldStorageDefinition')
258      ->willReturn($field_storage_definition);
259
260    $field_list_a = new FieldItemList($field_definition);
261    $field_list_b = new FieldItemList($field_definition);
262
263    // Set up the mocking necessary for creating field items.
264    $field_type_manager->expects($this->any())
265      ->method('createFieldItem')
266      ->willReturnOnConsecutiveCalls($first_field_item, $second_field_item, $empty_field_item, $empty_field_item);
267
268    // Set the field item values.
269    $field_list_a->setValue($first_field_item);
270    $field_list_b->setValue($second_field_item);
271    $field_list_a->appendItem($empty_field_item);
272
273    // Field list A has an empty item.
274    $this->assertEquals(FALSE, $field_list_a->equals($field_list_b));
275
276    // Field lists A and B have empty items.
277    $field_list_b->appendItem($empty_field_item);
278    $this->assertEquals(TRUE, $field_list_a->equals($field_list_b));
279
280    // Field list B has an empty item.
281    $field_list_a->filterEmptyItems();
282    $this->assertEquals(FALSE, $field_list_a->equals($field_list_b));
283
284    // Neither field lists A and B have empty items.
285    $field_list_b->filterEmptyItems();
286    $this->assertEquals(TRUE, $field_list_a->equals($field_list_b));
287  }
288
289  /**
290   * @covers ::defaultValuesForm
291   */
292  public function testDefaultValuesForm() {
293    $field_definition = $this->createMock(FieldDefinitionInterface::class);
294    $field_definition->expects($this->any())
295      ->method('getType')
296      ->willReturn('field_type');
297    /** @var \Drupal\Core\Field\FieldItemList|\PHPUnit\Framework\MockObject\MockObject $field_list */
298    $field_list = $this->getMockBuilder(FieldItemList::class)
299      ->setMethods(['defaultValueWidget'])
300      ->setConstructorArgs([$field_definition])
301      ->getMock();
302    $field_list->expects($this->any())
303      ->method('defaultValueWidget')
304      ->willReturn(NULL);
305    $form = [];
306    $form_state = new FormState();
307    $string_translation = $this->getStringTranslationStub();
308    $field_list->setStringTranslation($string_translation);
309
310    $this->assertEquals('No widget available for: <em class="placeholder">field_type</em>.', $field_list->defaultValuesForm($form, $form_state)['#markup']);
311  }
312
313  /**
314   * @covers ::defaultValuesFormValidate
315   */
316  public function testDefaultValuesFormValidate() {
317    $field_definition = $this->createMock(FieldDefinitionInterface::class);
318    /** @var \Drupal\Core\Field\FieldItemList|\PHPUnit\Framework\MockObject\MockObject $field_list */
319    $field_list = $this->getMockBuilder(FieldItemList::class)
320      ->setMethods(['defaultValueWidget', 'validate'])
321      ->setConstructorArgs([$field_definition])
322      ->getMock();
323    $field_list->expects($this->any())
324      ->method('defaultValueWidget')
325      ->willReturn(NULL);
326    $field_list->expects($this->never())
327      ->method('validate');
328    $form = [];
329    $form_state = new FormState();
330
331    $field_list->defaultValuesFormValidate([], $form, $form_state);
332  }
333
334  /**
335   * @covers ::defaultValuesFormSubmit
336   */
337  public function testDefaultValuesFormSubmit() {
338    $field_definition = $this->createMock(FieldDefinitionInterface::class);
339    /** @var \Drupal\Core\Field\FieldItemList|\PHPUnit\Framework\MockObject\MockObject $field_list */
340    $field_list = $this->getMockBuilder(FieldItemList::class)
341      ->setMethods(['defaultValueWidget', 'getValue'])
342      ->setConstructorArgs([$field_definition])
343      ->getMock();
344    $field_list->expects($this->any())
345      ->method('defaultValueWidget')
346      ->willReturn(NULL);
347    $form = [];
348    $form_state = new FormState();
349    $field_list->expects($this->never())
350      ->method('getValue');
351
352    $this->assertArrayEquals([], $field_list->defaultValuesFormSubmit([], $form, $form_state));
353  }
354
355}
356