Passed
Push — master ( 497031...53d9bd )
by no
04:40
created

QuantityParserTest::getParserClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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