Passed
Push — master ( 596b16...c68256 )
by Marius
56s queued 10s
created

QuantityParserTest::setUp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 2
nc 2
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\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
	public function setUp() {
25
		if ( !\extension_loaded( 'bcmath' ) ) {
26
			$this->markTestSkipped( 'bcmath extension not loaded' );
27
		}
28
	}
29
30
	/**
31
	 * @see ValueParserTestBase::getInstance
32
	 *
33
	 * @return QuantityParser
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->getMock( 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 );
62
	}
63
64
	/**
65
	 * @see ValueParserTestBase::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 = parent::invalidInputProvider();
149
150
		$invalid = [
151
			'foo',
152
			'',
153
			'.',
154
			'+.',
155
			'-.',
156
			'--1',
157
			'++1',
158
			'1-',
159
			'one',
160
			//'0x20', // this is actually valid, "x20" is read as the unit.
161
			'1+1',
162
			'1-1',
163
			'1.2.3',
164
165
			',3,',
166
			'10,000',
167
			'10\'000',
168
169
			'2!!',
170
			'!2',
171
			'2!2',
172
173
			'2!~',
174
			'2~!',
175
			'2~~',
176
			'~2',
177
			'2~2',
178
179
			'2 -- 2',
180
			'2++2',
181
			'2+±2',
182
			'2-±2',
183
184
			'2()',
185
			'2*',
186
			'2x y',
187
			'x 2 y',
188
189
			'100 003',
190
			'1 . 0',
191
		];
192
193
		foreach ( $invalid as $value ) {
194
			$argLists[] = [ $value ];
195
		}
196
197
		return $argLists;
198
	}
199
200
	public function testParseLocalizedQuantity() {
201
		$options = new ParserOptions();
202
		$options->setOption( ValueParser::OPT_LANG, 'test' );
203
204
		$unlocalizer = $this->getMock( NumberUnlocalizer::class );
205
206
		$charmap = [
207
			' ' => '',
208
			',' => '.',
209
		];
210
211
		$unlocalizer->expects( $this->any() )
212
			->method( 'unlocalizeNumber' )
213
			->will( $this->returnCallback(
214
				function( $number ) use ( $charmap ) {
215
					return str_replace( array_keys( $charmap ), array_values( $charmap ), $number );
216
				}
217
			) );
218
219
		$unlocalizer->expects( $this->any() )
220
			->method( 'getNumberRegex' )
221
			->will( $this->returnValue( '[\d ]+(?:,\d+)?' ) );
222
223
		$unlocalizer->expects( $this->any() )
224
			->method( 'getUnitRegex' )
225
			->will( $this->returnValue( '[a-z~]+' ) );
226
227
		$parser = new QuantityParser( $options, $unlocalizer );
228
229
		/** @var QuantityValue $quantity */
230
		$quantity = $parser->parse( '1 22 333,77+-3a~b' );
231
232
		$this->assertSame( '+122333.77', $quantity->getAmount()->getValue() );
233
		$this->assertSame( 'a~b', $quantity->getUnit() );
234
	}
235
236
	/**
237
	 * @dataProvider unitOptionProvider
238
	 */
239
	public function testUnitOption( $value, $unit, $expected ) {
240
		$options = new ParserOptions();
241
		$options->setOption( QuantityParser::OPT_UNIT, $unit );
242
243
		$parser = $this->getQuantityParser( $options );
244
245
		$quantity = $parser->parse( $value );
246
		$this->assertSame( $expected, $quantity->getUnit() );
247
	}
248
249
	public function unitOptionProvider() {
250
		return [
251
			[ '17 kittens', null, 'kittens' ],
252
			[ '17', 'kittens', 'kittens' ],
253
			[ '17 kittens', 'kittens', 'kittens' ],
254
			[ '17m', 'm', 'm' ],
255
			[ ' 17 ', ' http://concept.uri ', 'http://concept.uri' ],
256
		];
257
	}
258
259
	/**
260
	 * @dataProvider conflictingUnitOptionProvider
261
	 */
262
	public function testConflictingUnitOption( $value, $unit ) {
263
		$options = new ParserOptions();
264
		$options->setOption( QuantityParser::OPT_UNIT, $unit );
265
266
		$parser = $this->getQuantityParser( $options );
267
268
		$this->setExpectedException( ParseException::class );
269
		$parser->parse( $value );
270
	}
271
272
	public function conflictingUnitOptionProvider() {
273
		return [
274
			[ '17 kittens', 'm' ],
275
			[ '17m', 'kittens' ],
276
			[ '17m', '' ],
277
		];
278
	}
279
280
}
281