Passed
Push — lineLength120 ( 5ecdb4...e7bd7c )
by no
16:04
created

QuantityParserTest::validInputProvider()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 76
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
c 8
b 0
f 0
dl 0
loc 76
rs 8.9667
cc 2
eloc 56
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+
20
 * @author Daniel Kinzler
21
 */
22
class QuantityParserTest extends StringValueParserTest {
23
24
	/**
25
	 * @see ValueParserTestBase::getInstance
26
	 *
27
	 * @return QuantityParser
28
	 */
29
	protected function getInstance() {
30
		return $this->getQuantityParser();
31
	}
32
33
	/**
34
	 * @param ParserOptions|null $options
35
	 *
36
	 * @return QuantityParser
37
	 */
38
	private function getQuantityParser( ParserOptions $options = null ) {
39
		$unlocalizer = $this->getMock( NumberUnlocalizer::class );
40
41
		$unlocalizer->expects( $this->any() )
42
			->method( 'unlocalizeNumber' )
43
			->will( $this->returnArgument( 0 ) );
44
45
		// The most minimal regex that accepts all the test cases below.
46
		$unlocalizer->expects( $this->any() )
47
			->method( 'getNumberRegex' )
48
			->will( $this->returnValue( '[-+]? *(?:\d+\.\d*|\.?\d+)(?:e-?\d+)?' ) );
49
50
		// This minimal regex supports % and letters, optionally followed by a digit.
51
		$unlocalizer->expects( $this->any() )
52
			->method( 'getUnitRegex' )
53
			->will( $this->returnValue( '[\p{L}%]+[\d³]?' ) );
54
55
		return new QuantityParser( $options, $unlocalizer );
56
	}
57
58
	/**
59
	 * @see ValueParserTestBase::validInputProvider
60
	 */
61
	public function validInputProvider() {
62
		$amounts = [
63
			// amounts in various styles and forms
64
			'0' => UnboundedQuantityValue::newFromNumber( 0 ),
65
			'-0' => UnboundedQuantityValue::newFromNumber( 0 ),
66
			'-00.00' => UnboundedQuantityValue::newFromNumber( '+0.00' ),
67
			'+00.00' => UnboundedQuantityValue::newFromNumber( '+0.00' ),
68
			'0001' => UnboundedQuantityValue::newFromNumber( 1 ),
69
			'+01' => UnboundedQuantityValue::newFromNumber( 1 ),
70
			'-1' => UnboundedQuantityValue::newFromNumber( -1 ),
71
			'+42' => UnboundedQuantityValue::newFromNumber( 42 ),
72
			' -  42' => UnboundedQuantityValue::newFromNumber( -42 ),
73
			'9001' => UnboundedQuantityValue::newFromNumber( 9001 ),
74
			'.5' => UnboundedQuantityValue::newFromNumber( '+0.5' ),
75
			'-.125' => UnboundedQuantityValue::newFromNumber( '-0.125' ),
76
			'3.' => UnboundedQuantityValue::newFromNumber( 3 ),
77
			' 3 ' => UnboundedQuantityValue::newFromNumber( 3 ),
78
			'2.125' => UnboundedQuantityValue::newFromNumber( '+2.125' ),
79
			'2.1250' => UnboundedQuantityValue::newFromNumber( '+2.1250' ),
80
81
			'1.4e-2' => UnboundedQuantityValue::newFromNumber( '+0.014' ),
82
			'-1.4e-2' => UnboundedQuantityValue::newFromNumber( '-0.014' ),
83
			'1.4e3' => UnboundedQuantityValue::newFromNumber( '+1400' ),
84
			'1.4e3!m' => QuantityValue::newFromNumber( '+1400', 'm', '+1400', '+1400' ),
85
			'1.4e3m2' => UnboundedQuantityValue::newFromNumber( '+1400', 'm2' ),
86
			'1.4ev' => UnboundedQuantityValue::newFromNumber( '+1.4', 'ev' ),
87
			'1.4e' => UnboundedQuantityValue::newFromNumber( '+1.4', 'e' ),
88
			'12e3e4' => UnboundedQuantityValue::newFromNumber( '+12000', 'e4' ),
89
			// FIXME: Add support for 12x10^3, see DecimalParser.
90
			'0.004e3' => UnboundedQuantityValue::newFromNumber( '+4' ),
91
			'0.004e-3' => UnboundedQuantityValue::newFromNumber( '+0.000004' ),
92
			'4000e3' => UnboundedQuantityValue::newFromNumber( '+4000000' ),
93
			'4000e-3' => UnboundedQuantityValue::newFromNumber( '+4.000' ),
94
95
			// precision
96
			'0!' => QuantityValue::newFromNumber( 0, '1', 0, 0 ),
97
			'10.003!' => QuantityValue::newFromNumber( '+10.003', '1', '+10.003', '+10.003' ),
98
			'-200!' => QuantityValue::newFromNumber( -200, '1', -200, -200 ),
99
			'0~' => QuantityValue::newFromNumber( 0, '1', 0.5, -0.5 ),
100
			'10.003~' => QuantityValue::newFromNumber( '+10.003', '1', '+10.0035', '+10.0025' ),
101
			'-200~' => QuantityValue::newFromNumber( -200, '1', -199.5, -200.5 ),
102
103
			// uncertainty
104
			'5.3 +/- 0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
105
			'5.3+-0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
106
			'5.3 ±0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
107
108
			'5.3 +/- +0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
109
			'5.3+-+0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
110
111
			'5.3e3 +/- 0.2e2' => QuantityValue::newFromNumber( '+5300', '1', '+5320', '+5280' ),
112
			'2e-2+/-1.1e-1' => QuantityValue::newFromNumber( '+0.02', '1', '+0.13', '-0.09' ),
113
114
			// negative
115
			'5.3 +/- -0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
116
			'5.3+--0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
117
			'5.3 ±-0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ),
118
119
			// units
120
			'5.3+-0.2cm' => QuantityValue::newFromNumber( '+5.3', 'cm', '+5.5', '+5.1' ),
121
			'10.003! km' => QuantityValue::newFromNumber( '+10.003', 'km', '+10.003', '+10.003' ),
122
			'-200~ %  ' => QuantityValue::newFromNumber( -200, '%', -199.5, -200.5 ),
123
			'100003 m³' => UnboundedQuantityValue::newFromNumber( 100003, 'm³' ),
124
			'3.±-0.2µ' => QuantityValue::newFromNumber( '+3', 'µ', '+3.2', '+2.8' ),
125
			'+00.20 Å' => UnboundedQuantityValue::newFromNumber( '+0.20', 'Å' ),
126
		];
127
128
		$argLists = [];
129
130
		foreach ( $amounts as $amount => $expected ) {
131
			//NOTE: PHP may "helpfully" have converted $amount to an integer. Yay.
132
			$argLists[$amount] = [ strval( $amount ), $expected ];
133
		}
134
135
		return $argLists;
136
	}
137
138
	/**
139
	 * @see StringValueParserTest::invalidInputProvider
140
	 */
141
	public function invalidInputProvider() {
142
		$argLists = parent::invalidInputProvider();
143
144
		$invalid = [
145
			'foo',
146
			'',
147
			'.',
148
			'+.',
149
			'-.',
150
			'--1',
151
			'++1',
152
			'1-',
153
			'one',
154
			//'0x20', // this is actually valid, "x20" is read as the unit.
155
			'1+1',
156
			'1-1',
157
			'1.2.3',
158
159
			',3,',
160
			'10,000',
161
			'10\'000',
162
163
			'2!!',
164
			'!2',
165
			'2!2',
166
167
			'2!~',
168
			'2~!',
169
			'2~~',
170
			'~2',
171
			'2~2',
172
173
			'2 -- 2',
174
			'2++2',
175
			'2+±2',
176
			'2-±2',
177
178
			'2()',
179
			'2*',
180
			'2x y',
181
			'x 2 y',
182
183
			'100 003',
184
			'1 . 0',
185
		];
186
187
		foreach ( $invalid as $value ) {
188
			$argLists[] = [ $value ];
189
		}
190
191
		return $argLists;
192
	}
193
194
	public function testParseLocalizedQuantity() {
195
		$options = new ParserOptions();
196
		$options->setOption( ValueParser::OPT_LANG, 'test' );
197
198
		$unlocalizer = $this->getMock( NumberUnlocalizer::class );
199
200
		$charmap = [
201
			' ' => '',
202
			',' => '.',
203
		];
204
205
		$unlocalizer->expects( $this->any() )
206
			->method( 'unlocalizeNumber' )
207
			->will( $this->returnCallback(
208
				function( $number ) use ( $charmap ) {
209
					return str_replace( array_keys( $charmap ), array_values( $charmap ), $number );
210
				}
211
			) );
212
213
		$unlocalizer->expects( $this->any() )
214
			->method( 'getNumberRegex' )
215
			->will( $this->returnValue( '[\d ]+(?:,\d+)?' ) );
216
217
		$unlocalizer->expects( $this->any() )
218
			->method( 'getUnitRegex' )
219
			->will( $this->returnValue( '[a-z~]+' ) );
220
221
		$parser = new QuantityParser( $options, $unlocalizer );
222
223
		/** @var QuantityValue $quantity */
224
		$quantity = $parser->parse( '1 22 333,77+-3a~b' );
225
226
		$this->assertSame( '+122333.77', $quantity->getAmount()->getValue() );
227
		$this->assertSame( 'a~b', $quantity->getUnit() );
228
	}
229
230
	/**
231
	 * @dataProvider unitOptionProvider
232
	 */
233
	public function testUnitOption( $value, $unit, $expected ) {
234
		$options = new ParserOptions();
235
		$options->setOption( QuantityParser::OPT_UNIT, $unit );
236
237
		$parser = $this->getQuantityParser( $options );
238
239
		$quantity = $parser->parse( $value );
240
		$this->assertSame( $expected, $quantity->getUnit() );
241
	}
242
243
	public function unitOptionProvider() {
244
		return [
245
			[ '17 kittens', null, 'kittens' ],
246
			[ '17', 'kittens', 'kittens' ],
247
			[ '17 kittens', 'kittens', 'kittens' ],
248
			[ '17m', 'm', 'm' ],
249
			[ ' 17 ', ' http://concept.uri ', 'http://concept.uri' ],
250
		];
251
	}
252
253
	/**
254
	 * @dataProvider conflictingUnitOptionProvider
255
	 */
256
	public function testConflictingUnitOption( $value, $unit ) {
257
		$options = new ParserOptions();
258
		$options->setOption( QuantityParser::OPT_UNIT, $unit );
259
260
		$parser = $this->getQuantityParser( $options );
261
262
		$this->setExpectedException( ParseException::class );
263
		$parser->parse( $value );
264
	}
265
266
	public function conflictingUnitOptionProvider() {
267
		return [
268
			[ '17 kittens', 'm' ],
269
			[ '17m', 'kittens' ],
270
			[ '17m', '' ],
271
		];
272
	}
273
274
}
275