QuantityParserTest::invalidInputProvider()   B
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 59
rs 8.8945
c 0
b 0
f 0
cc 2
nc 2
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace ValueParsers\Test;
4
5
use DataValues\QuantityValue;
6
use DataValues\UnboundedQuantityValue;
7
use ValueParsers\NumberUnlocalizer;
8
use ValueParsers\ParseException;
9
use ValueParsers\ParserOptions;
10
use ValueParsers\QuantityParser;
11
use ValueParsers\ValueParser;
12
13
/**
14
 * @covers \ValueParsers\QuantityParser
15
 *
16
 * @group DataValue
17
 * @group DataValueExtensions
18
 *
19
 * @license GPL-2.0-or-later
20
 * @author Daniel Kinzler
21
 */
22
class QuantityParserTest extends ValueParserTestCase {
23
24
	public function setUp() : void {
25
		if ( !\extension_loaded( 'bcmath' ) ) {
26
			$this->markTestSkipped( 'bcmath extension not loaded' );
27
		}
28
	}
29
30
	/**
31
	 * @return QuantityParser
32
	 * @see ValueParserTestCase::getInstance
33
	 *
34
	 */
35
	protected function getInstance() {
36
		return $this->getQuantityParser();
37
	}
38
39
	/**
40
	 * @param ParserOptions|null $options
41
	 *
42
	 * @return QuantityParser
43
	 */
44
	private function getQuantityParser( ParserOptions $options = null ) {
45
		$unlocalizer = $this->createMock( NumberUnlocalizer::class );
46
47
		$unlocalizer->expects( $this->any() )
48
			->method( 'unlocalizeNumber' )
49
			->will( $this->returnArgument( 0 ) );
50
51
		// The most minimal regex that accepts all the test cases below.
52
		$unlocalizer->expects( $this->any() )
53
			->method( 'getNumberRegex' )
54
			->will( $this->returnValue( '[-+]? *(?:\d+\.\d*|\.?\d+)(?:e-?\d+)?' ) );
55
56
		// This minimal regex supports % and letters, optionally followed by a digit.
57
		$unlocalizer->expects( $this->any() )
58
			->method( 'getUnitRegex' )
59
			->will( $this->returnValue( '[\p{L}%]+[\d³]?' ) );
60
61
		return new QuantityParser( $options, $unlocalizer );
0 ignored issues
show
Documentation introduced by
$unlocalizer is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a null|object<ValueParsers\NumberUnlocalizer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
62
	}
63
64
	/**
65
	 * @see ValueParserTestCase::validInputProvider
66
	 */
67
	public function validInputProvider() {
68
		$amounts = [
69
			// amounts in various styles and forms
70
			'0' => UnboundedQuantityValue::newFromNumber( 0 ),
71
			'-0' => UnboundedQuantityValue::newFromNumber( 0 ),
72
			'-00.00' => UnboundedQuantityValue::newFromNumber( '+0.00' ),
73
			'+00.00' => UnboundedQuantityValue::newFromNumber( '+0.00' ),
74
			'0001' => UnboundedQuantityValue::newFromNumber( 1 ),
75
			'+01' => UnboundedQuantityValue::newFromNumber( 1 ),
76
			'-1' => UnboundedQuantityValue::newFromNumber( -1 ),
77
			'+42' => UnboundedQuantityValue::newFromNumber( 42 ),
78
			' -  42' => UnboundedQuantityValue::newFromNumber( -42 ),
79
			'9001' => UnboundedQuantityValue::newFromNumber( 9001 ),
80
			'.5' => UnboundedQuantityValue::newFromNumber( '+0.5' ),
81
			'-.125' => UnboundedQuantityValue::newFromNumber( '-0.125' ),
82
			'3.' => UnboundedQuantityValue::newFromNumber( 3 ),
83
			' 3 ' => UnboundedQuantityValue::newFromNumber( 3 ),
84
			'2.125' => UnboundedQuantityValue::newFromNumber( '+2.125' ),
85
			'2.1250' => UnboundedQuantityValue::newFromNumber( '+2.1250' ),
86
87
			'1.4e-2' => UnboundedQuantityValue::newFromNumber( '+0.014' ),
88
			'-1.4e-2' => UnboundedQuantityValue::newFromNumber( '-0.014' ),
89
			'1.4e3' => UnboundedQuantityValue::newFromNumber( '+1400' ),
90
			'1.4e3!m' => QuantityValue::newFromNumber( '+1400', 'm', '+1400', '+1400' ),
91
			'1.4e3m2' => UnboundedQuantityValue::newFromNumber( '+1400', 'm2' ),
92
			'1.4ev' => UnboundedQuantityValue::newFromNumber( '+1.4', 'ev' ),
93
			'1.4e' => UnboundedQuantityValue::newFromNumber( '+1.4', 'e' ),
94
			'12e3e4' => UnboundedQuantityValue::newFromNumber( '+12000', 'e4' ),
95
			// FIXME: Add support for 12x10^3, see DecimalParser.
96
			'0.004e3' => UnboundedQuantityValue::newFromNumber( '+4' ),
97
			'0.004e-3' => UnboundedQuantityValue::newFromNumber( '+0.000004' ),
98
			'4000e3' => UnboundedQuantityValue::newFromNumber( '+4000000' ),
99
			'4000e-3' => UnboundedQuantityValue::newFromNumber( '+4.000' ),
100
101
			// precision
102
			'0!' => QuantityValue::newFromNumber( 0, '1', 0, 0 ),
103
			'10.003!' => QuantityValue::newFromNumber( '+10.003', '1', '+10.003', '+10.003' ),
104
			'-200!' => QuantityValue::newFromNumber( -200, '1', -200, -200 ),
105
			'0~' => QuantityValue::newFromNumber( 0, '1', 0.5, -0.5 ),
106
			'10.003~' => QuantityValue::newFromNumber( '+10.003', '1', '+10.0035', '+10.0025' ),
107
			'-200~' => QuantityValue::newFromNumber( -200, '1', -199.5, -200.5 ),
108
109
			// uncertainty
110
			'5.3 +/- 0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
111
			'5.3+-0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
112
			'5.3 ±0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
113
114
			'5.3 +/- +0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
115
			'5.3+-+0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
116
117
			'5.3e3 +/- 0.2e2' => QuantityValue::newFromNumber( '+5300', '1', '+5320', '+5280' ),
118
			'2e-2+/-1.1e-1' => QuantityValue::newFromNumber( '+0.02', '1', '+0.13', '-0.09' ),
119
120
			// negative
121
			'5.3 +/- -0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
122
			'5.3+--0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
123
			'5.3 ±-0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
124
125
			// units
126
			'5.3+-0.2cm' => QuantityValue::newFromNumber( '+5.3', 'cm', '+5.5', '+5.1' ),
127
			'10.003! km' => QuantityValue::newFromNumber( '+10.003', 'km', '+10.003', '+10.003' ),
128
			'-200~ %  ' => QuantityValue::newFromNumber( -200, '%', -199.5, -200.5 ),
129
			'100003 m³' => UnboundedQuantityValue::newFromNumber( 100003, 'm³' ),
130
			'3.±-0.2µ' => QuantityValue::newFromNumber( '+3', 'µ', '+3.2', '+2.8' ),
131
			'+00.20 Å' => UnboundedQuantityValue::newFromNumber( '+0.20', 'Å' ),
132
		];
133
134
		$argLists = [];
135
136
		foreach ( $amounts as $amount => $expected ) {
137
			//NOTE: PHP may "helpfully" have converted $amount to an integer. Yay.
138
			$argLists[$amount] = [ strval( $amount ), $expected ];
139
		}
140
141
		return $argLists;
142
	}
143
144
	/**
145
	 * @see StringValueParserTest::invalidInputProvider
146
	 */
147
	public function invalidInputProvider() {
148
		$argLists = [
149
			[ true ],
150
			[ false ],
151
			[ null ],
152
			[ 4.2 ],
153
			[ [] ],
154
			[ 42 ]
155
		];
156
157
		$invalid = [
158
			'foo',
159
			'',
160
			'.',
161
			'+.',
162
			'-.',
163
			'--1',
164
			'++1',
165
			'1-',
166
			'one',
167
			//'0x20', // this is actually valid, "x20" is read as the unit.
168
			'1+1',
169
			'1-1',
170
			'1.2.3',
171
172
			',3,',
173
			'10,000',
174
			'10\'000',
175
176
			'2!!',
177
			'!2',
178
			'2!2',
179
180
			'2!~',
181
			'2~!',
182
			'2~~',
183
			'~2',
184
			'2~2',
185
186
			'2 -- 2',
187
			'2++2',
188
			'2+±2',
189
			'2-±2',
190
191
			'2()',
192
			'2*',
193
			'2x y',
194
			'x 2 y',
195
196
			'100 003',
197
			'1 . 0',
198
		];
199
200
		foreach ( $invalid as $value ) {
201
			$argLists[] = [ $value ];
202
		}
203
204
		return $argLists;
205
	}
206
207
	public function testParseLocalizedQuantity() {
208
		$options = new ParserOptions();
209
		$options->setOption( ValueParser::OPT_LANG, 'test' );
210
211
		$unlocalizer = $this->createMock( NumberUnlocalizer::class );
212
213
		$charmap = [
214
			' ' => '',
215
			',' => '.',
216
		];
217
218
		$unlocalizer->expects( $this->any() )
219
			->method( 'unlocalizeNumber' )
220
			->will( $this->returnCallback(
221
				function ( $number ) use ( $charmap ) {
222
					return str_replace( array_keys( $charmap ), array_values( $charmap ), $number );
223
				}
224
			) );
225
226
		$unlocalizer->expects( $this->any() )
227
			->method( 'getNumberRegex' )
228
			->will( $this->returnValue( '[\d ]+(?:,\d+)?' ) );
229
230
		$unlocalizer->expects( $this->any() )
231
			->method( 'getUnitRegex' )
232
			->will( $this->returnValue( '[a-z~]+' ) );
233
234
		$parser = new QuantityParser( $options, $unlocalizer );
0 ignored issues
show
Documentation introduced by
$unlocalizer is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a null|object<ValueParsers\NumberUnlocalizer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
235
236
		/** @var QuantityValue $quantity */
237
		$quantity = $parser->parse( '1 22 333,77+-3a~b' );
238
239
		$this->assertSame( '+122333.77', $quantity->getAmount()->getValue() );
240
		$this->assertSame( 'a~b', $quantity->getUnit() );
241
	}
242
243
	/**
244
	 * @dataProvider unitOptionProvider
245
	 */
246
	public function testUnitOption( $value, $unit, $expected ) {
247
		$options = new ParserOptions();
248
		$options->setOption( QuantityParser::OPT_UNIT, $unit );
249
250
		$parser = $this->getQuantityParser( $options );
251
252
		$quantity = $parser->parse( $value );
253
		$this->assertSame( $expected, $quantity->getUnit() );
254
	}
255
256
	public function unitOptionProvider() {
257
		return [
258
			[ '17 kittens', null, 'kittens' ],
259
			[ '17', 'kittens', 'kittens' ],
260
			[ '17 kittens', 'kittens', 'kittens' ],
261
			[ '17m', 'm', 'm' ],
262
			[ ' 17 ', ' http://concept.uri ', 'http://concept.uri' ],
263
		];
264
	}
265
266
	/**
267
	 * @dataProvider conflictingUnitOptionProvider
268
	 */
269
	public function testConflictingUnitOption( $value, $unit ) {
270
		$options = new ParserOptions();
271
		$options->setOption( QuantityParser::OPT_UNIT, $unit );
272
273
		$parser = $this->getQuantityParser( $options );
274
275
		$this->expectException( ParseException::class );
276
		$parser->parse( $value );
277
	}
278
279
	public function conflictingUnitOptionProvider() {
280
		return [
281
			[ '17 kittens', 'm' ],
282
			[ '17m', 'kittens' ],
283
			[ '17m', '' ],
284
		];
285
	}
286
287
}
288