Completed
Push — master ( 43d0b8...1d1227 )
by Daniel
16:45
created

CheckboxSetFieldTest::testSafelyCast()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 4 Features 0
Metric Value
cc 1
eloc 18
nc 1
nop 0
dl 0
loc 20
rs 9.4285
c 4
b 4
f 0
1
<?php
2
3
use SilverStripe\ORM\ArrayList;
4
use SilverStripe\ORM\DataObject;
5
use SilverStripe\ORM\DB;
6
use SilverStripe\Security\Member;
7
use SilverStripe\ORM\FieldType\DBField;
8
9
/**
10
 * @package framework
11
 * @subpackage tests
12
 */
13
class CheckboxSetFieldTest extends SapphireTest {
14
15
	protected static $fixture_file = 'CheckboxSetFieldTest.yml';
16
17
	protected $extraDataObjects = array(
18
		'CheckboxSetFieldTest_Article',
19
		'CheckboxSetFieldTest_Tag',
20
	);
21
22
	public function testSetDefaultItems() {
23
		$f = new CheckboxSetField(
24
			'Test',
25
			false,
26
			array(0 => 'Zero', 1 => 'One', 2 => 'Two', 3 => 'Three')
27
		);
28
29
		$f->setValue(array(0,1));
30
		$f->setDefaultItems(array(2));
31
		$p = new CSSContentParser($f->Field());
32
		$item0 = $p->getBySelector('#Test_0');
33
		$item1 = $p->getBySelector('#Test_1');
34
		$item2 = $p->getBySelector('#Test_2');
35
		$item3 = $p->getBySelector('#Test_3');
36
		$this->assertEquals(
37
			(string)$item0[0]['checked'],
38
			'checked',
39
			'Selected through value'
40
		);
41
		$this->assertEquals(
42
			(string)$item1[0]['checked'],
43
			'checked',
44
			'Selected through value'
45
		);
46
		$this->assertEquals(
47
			(string)$item2[0]['checked'],
48
			'checked',
49
			'Selected through default items'
50
		);
51
		$this->assertEquals(
52
			(string)$item3[0]['checked'],
53
			'',
54
			'Not selected by either value or default items'
55
		);
56
	}
57
58
59
60
	/**
61
	 * Test different data sources
62
	 */
63
	public function testSources() {
64
		// Array
65
		$items = array('a' => 'Apple', 'b' => 'Banana', 'c' => 'Cranberry');
66
		$field = new CheckboxSetField('Field', null, $items);
67
		$this->assertEquals($items, $field->getSource());
68
69
		// SS_List
70
		$list = new ArrayList(array(
71
			new ArrayData(array(
72
				'ID' => 'a',
73
				'Title' => 'Apple'
74
			)),
75
			new ArrayData(array(
76
				'ID' => 'b',
77
				'Title' => 'Banana'
78
			)),
79
			new ArrayData(array(
80
				'ID' => 'c',
81
				'Title' => 'Cranberry'
82
			))
83
		));
84
		$field2 = new CheckboxSetField('Field', null, $list);
85
		$this->assertEquals($items, $field2->getSource());
86
87
		$field3 = new CheckboxSetField('Field', null, $list->map());
88
		$this->assertEquals($items, $field3->getSource());
89
	}
90
91
	public function testSaveWithNothingSelected() {
92
		$article = $this->objFromFixture('CheckboxSetFieldTest_Article', 'articlewithouttags');
93
94
		/* Create a CheckboxSetField with nothing selected */
95
		$field = new CheckboxSetField("Tags", "Test field", DataObject::get("CheckboxSetFieldTest_Tag")->map());
96
97
		/* Saving should work */
98
		$field->saveInto($article);
0 ignored issues
show
Bug introduced by
It seems like $article defined by $this->objFromFixture('C..., 'articlewithouttags') on line 92 can be null; however, MultiSelectField::saveInto() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
99
100
		$this->assertNull(
101
			DB::prepared_query("SELECT *
102
				FROM \"CheckboxSetFieldTest_Article_Tags\"
103
				WHERE \"CheckboxSetFieldTest_Article_Tags\".\"CheckboxSetFieldTest_ArticleID\" = ?", array($article->ID)
104
			)->value(),
105
			'Nothing should go into manymany join table for a saved field without any ticked boxes'
106
		);
107
	}
108
109
	public function testSaveWithArrayValueSet() {
110
		$article = $this->objFromFixture('CheckboxSetFieldTest_Article', 'articlewithouttags');
111
		$articleWithTags = $this->objFromFixture('CheckboxSetFieldTest_Article', 'articlewithtags');
112
		$tag1 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag1');
113
		$tag2 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag2');
114
115
		/* Create a CheckboxSetField with 2 items selected.  Note that the array is a list of values */
116
		$field = new CheckboxSetField("Tags", "Test field", DataObject::get("CheckboxSetFieldTest_Tag")->map());
117
		$field->setValue(array(
118
			$tag1->ID,
119
			$tag2->ID
120
		));
121
122
		/* Saving should work */
123
		$field->saveInto($article);
0 ignored issues
show
Bug introduced by
It seems like $article defined by $this->objFromFixture('C..., 'articlewithouttags') on line 110 can be null; however, MultiSelectField::saveInto() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
124
125
		$this->assertEquals(
126
			array($tag1->ID,$tag2->ID),
127
			DB::prepared_query("SELECT \"CheckboxSetFieldTest_TagID\"
128
				FROM \"CheckboxSetFieldTest_Article_Tags\"
129
				WHERE \"CheckboxSetFieldTest_Article_Tags\".\"CheckboxSetFieldTest_ArticleID\" = ?", array($article->ID)
130
			)->column(),
131
			'Data shold be saved into CheckboxSetField manymany relation table on the "right end"'
132
		);
133
		$this->assertEquals(
134
			array($articleWithTags->ID,$article->ID),
135
			DB::query("SELECT \"CheckboxSetFieldTest_ArticleID\"
136
				FROM \"CheckboxSetFieldTest_Article_Tags\"
137
				WHERE \"CheckboxSetFieldTest_Article_Tags\".\"CheckboxSetFieldTest_TagID\" = $tag1->ID
138
			")->column(),
139
			'Data shold be saved into CheckboxSetField manymany relation table on the "left end"'
140
		);
141
	}
142
143
	public function testLoadDataFromObject() {
144
		$article = $this->objFromFixture('CheckboxSetFieldTest_Article', 'articlewithouttags');
145
		$articleWithTags = $this->objFromFixture('CheckboxSetFieldTest_Article', 'articlewithtags');
146
		$tag1 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag1');
147
		$tag2 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag2');
148
149
		$field = new CheckboxSetField("Tags", "Test field", DataObject::get("CheckboxSetFieldTest_Tag")->map());
150
		$form = new Form(
151
			new Controller(),
152
			'Form',
153
			new FieldList($field),
154
			new FieldList()
155
		);
156
		$form->loadDataFrom($articleWithTags);
0 ignored issues
show
Bug introduced by
It seems like $articleWithTags defined by $this->objFromFixture('C...le', 'articlewithtags') on line 145 can be null; however, Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
157
		$value = $field->Value();
158
		sort($value);
159
		$this->assertEquals(
160
			array(
161
				$tag1->ID,
162
				$tag2->ID
163
			),
164
			$value,
165
			'CheckboxSetField loads data from a manymany relationship in an object through Form->loadDataFrom()'
166
		);
167
	}
168
169
	public function testSavingIntoTextField() {
170
		$field = new CheckboxSetField('Content', 'Content', array(
171
			'Test' => 'Test',
172
			'Another' => 'Another',
173
			'Something' => 'Something'
174
		));
175
		$article = new CheckboxSetFieldTest_Article();
176
		$field->setValue(array('Test' => 'Test', 'Another' => 'Another'));
177
		$field->saveInto($article);
178
		$article->write();
179
180
		$dbValue = DB::query(sprintf(
181
			'SELECT "Content" FROM "CheckboxSetFieldTest_Article" WHERE "ID" = %s',
182
			$article->ID
183
		))->value();
184
185
		// JSON encoded values
186
		$this->assertEquals('["Test","Another"]', $dbValue);
187
	}
188
189
	public function testValidationWithArray() {
190
		// Test with array input
191
		$field = CheckboxSetField::create('Test', 'Testing', array(
192
			"One" => "One",
193
			"Two" => "Two",
194
			"Three" => "Three"
195
		));
196
		$validator = new RequiredFields();
197
		$field->setValue(array("One", "Two"));
198
		$this->assertTrue(
199
			$field->validate($validator),
200
			'Field validates values within source array'
201
		);
202
203
		// Non valid value should fail
204
		$field->setValue(array("Four" => "Four"));
205
		$this->assertFalse(
206
			$field->validate($validator),
207
			'Field does not validate values outside of source array'
208
		);
209
210
		// Non valid value, even if included with valid options, should fail
211
		$field->setValue(array("One", "Two", "Four"));
212
		$this->assertFalse(
213
			$field->validate($validator),
214
			'Field does not validate when presented with mixed valid and invalid values'
215
		);
216
	}
217
218
	public function testValidationWithDataList() {
219
		//test with datalist input
220
		$checkboxTestArticle = $this->objFromFixture('CheckboxSetFieldTest_Article', 'articlewithtags');
221
		$tag1 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag1');
222
		$tag2 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag2');
223
		$tag3 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag3');
224
		$field = CheckboxSetField::create('Test', 'Testing', $checkboxTestArticle->Tags()	->map());
225
		$validator = new RequiredFields();
226
		$field->setValue(array(
227
			$tag1->ID => $tag1->ID,
228
			$tag2->ID => $tag2->ID
229
		));
230
		$this->assertTrue(
231
			$field->validate($validator),
232
			'Validates values in source map'
233
		);
234
		//invalid value should fail
235
		$fakeID = CheckboxSetFieldTest_Tag::get()->max('ID') + 1;
236
		$field->setValue(array($fakeID => $fakeID));
237
		$this->assertFalse(
238
			$field->validate($validator),
239
			'Field does not valid values outside of source map'
240
		);
241
		//non valid value included with valid options should succeed
242
		$field->setValue(array(
243
			$tag1->ID => $tag1->ID,
244
			$tag2->ID => $tag2->ID,
245
			$tag3->ID => $tag3->ID
246
		));
247
		$this->assertFalse(
248
			$field->validate($validator),
249
			'Field does not validate when presented with mixed valid and invalid values'
250
		);
251
	}
252
253
	public function testSafelyCast() {
254
		$member = new Member();
255
		$member->FirstName = '<firstname>';
256
		$member->Surname = '<surname>';
257
		$member->write();
258
		$field1 = new CheckboxSetField('Options', 'Options', array(
259
			'one' => 'One',
260
			'two' => 'Two & Three',
261
			'three' => DBField::create_field('HTMLText', 'Four &amp; Five &amp; Six'),
262
			'four' => $member->FirstName,
263
		));
264
		$fieldHTML = (string)$field1->Field();
265
		$this->assertContains('One', $fieldHTML);
266
		$this->assertContains('Two &amp; Three', $fieldHTML);
267
		$this->assertNotContains('Two & Three', $fieldHTML);
268
		$this->assertContains('Four &amp; Five &amp; Six', $fieldHTML);
269
		$this->assertNotContains('Four & Five & Six', $fieldHTML);
270
		$this->assertContains('&lt;firstname&gt;', $fieldHTML);
271
		$this->assertNotContains('<firstname>', $fieldHTML);
272
	}
273
274
}
275
276
/**
277
 * @package framework
278
 * @subpackage tests
279
 */
280
281
class CheckboxSetFieldTest_Article extends DataObject implements TestOnly {
282
283
	private static $db = array(
284
		"Content" => "Text",
285
	);
286
287
	private static $many_many = array(
288
		"Tags" => "CheckboxSetFieldTest_Tag",
289
	);
290
291
}
292
293
/**
294
 * @package framework
295
 * @subpackage tests
296
 */
297
class CheckboxSetFieldTest_Tag extends DataObject implements TestOnly {
298
299
	private static $belongs_many_many = array(
300
		'Articles' => 'CheckboxSetFieldTest_Article'
301
	);
302
}
303