|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace ValueParsers\Test; |
|
4
|
|
|
|
|
5
|
|
|
use DataValues\QuantityValue; |
|
6
|
|
|
use ValueParsers\ParserOptions; |
|
7
|
|
|
use ValueParsers\QuantityParser; |
|
8
|
|
|
use ValueParsers\ValueParser; |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* @covers ValueParsers\QuantityParser |
|
12
|
|
|
* |
|
13
|
|
|
* @group DataValue |
|
14
|
|
|
* @group DataValueExtensions |
|
15
|
|
|
* |
|
16
|
|
|
* @license GPL-2.0+ |
|
17
|
|
|
* @author Daniel Kinzler |
|
18
|
|
|
*/ |
|
19
|
|
|
class QuantityParserTest extends StringValueParserTest { |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* @deprecated since DataValues Common 0.3, just use getInstance. |
|
23
|
|
|
*/ |
|
24
|
|
|
protected function getParserClass() { |
|
25
|
|
|
throw new \LogicException( 'Should not be called, use getInstance' ); |
|
26
|
|
|
} |
|
27
|
|
|
|
|
28
|
|
|
/** |
|
29
|
|
|
* @see ValueParserTestBase::getInstance |
|
30
|
|
|
* |
|
31
|
|
|
* @return QuantityParser |
|
32
|
|
|
*/ |
|
33
|
|
|
protected function getInstance() { |
|
34
|
|
|
return $this->getQuantityParser(); |
|
35
|
|
|
} |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* @param ParserOptions|null $options |
|
39
|
|
|
* |
|
40
|
|
|
* @return QuantityParser |
|
41
|
|
|
*/ |
|
42
|
|
|
private function getQuantityParser( ParserOptions $options = null ) { |
|
43
|
|
|
$unlocalizer = $this->getMock( 'ValueParsers\NumberUnlocalizer' ); |
|
44
|
|
|
|
|
45
|
|
|
$unlocalizer->expects( $this->any() ) |
|
46
|
|
|
->method( 'unlocalizeNumber' ) |
|
47
|
|
|
->will( $this->returnArgument( 0 ) ); |
|
48
|
|
|
|
|
49
|
|
|
// The most minimal regex that accepts all the test cases below. |
|
50
|
|
|
$unlocalizer->expects( $this->any() ) |
|
51
|
|
|
->method( 'getNumberRegex' ) |
|
52
|
|
|
->will( $this->returnValue( '[-+]? *(?:\d+\.\d*|\.?\d+)(?:e-?\d+)?' ) ); |
|
53
|
|
|
|
|
54
|
|
|
// This minimal regex supports % and letters, optionally followed by a digit. |
|
55
|
|
|
$unlocalizer->expects( $this->any() ) |
|
56
|
|
|
->method( 'getUnitRegex' ) |
|
57
|
|
|
->will( $this->returnValue( '[\p{L}%]+[\d³]?' ) ); |
|
58
|
|
|
|
|
59
|
|
|
return new QuantityParser( $options, $unlocalizer ); |
|
60
|
|
|
} |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* @see ValueParserTestBase::validInputProvider |
|
64
|
|
|
*/ |
|
65
|
|
|
public function validInputProvider() { |
|
66
|
|
|
$amounts = array( |
|
67
|
|
|
// amounts in various styles and forms |
|
68
|
|
|
'0' => QuantityValue::newFromNumber( 0, '1', 1, -1 ), |
|
|
|
|
|
|
69
|
|
|
'-0' => QuantityValue::newFromNumber( 0, '1', 1, -1 ), |
|
|
|
|
|
|
70
|
|
|
'-00.00' => QuantityValue::newFromNumber( '+0.00', '1', '+0.01', '-0.01' ), |
|
|
|
|
|
|
71
|
|
|
'+00.00' => QuantityValue::newFromNumber( '+0.00', '1', '+0.01', '-0.01' ), |
|
|
|
|
|
|
72
|
|
|
'0001' => QuantityValue::newFromNumber( 1, '1', 2, 0 ), |
|
|
|
|
|
|
73
|
|
|
'+01' => QuantityValue::newFromNumber( 1, '1', 2, 0 ), |
|
|
|
|
|
|
74
|
|
|
'-1' => QuantityValue::newFromNumber( -1, '1', 0, -2 ), |
|
|
|
|
|
|
75
|
|
|
'+42' => QuantityValue::newFromNumber( 42, '1', 43, 41 ), |
|
|
|
|
|
|
76
|
|
|
' - 42' => QuantityValue::newFromNumber( -42, '1', -41, -43 ), |
|
|
|
|
|
|
77
|
|
|
'9001' => QuantityValue::newFromNumber( 9001, '1', 9002, 9000 ), |
|
|
|
|
|
|
78
|
|
|
'.5' => QuantityValue::newFromNumber( '+0.5', '1', '+0.6', '+0.4' ), |
|
|
|
|
|
|
79
|
|
|
'-.125' => QuantityValue::newFromNumber( '-0.125', '1', '-0.124', '-0.126' ), |
|
|
|
|
|
|
80
|
|
|
'3.' => QuantityValue::newFromNumber( 3, '1', 4, 2 ), |
|
|
|
|
|
|
81
|
|
|
' 3 ' => QuantityValue::newFromNumber( 3, '1', 4, 2 ), |
|
|
|
|
|
|
82
|
|
|
'2.125' => QuantityValue::newFromNumber( '+2.125', '1', '+2.126', '+2.124' ), |
|
|
|
|
|
|
83
|
|
|
'2.1250' => QuantityValue::newFromNumber( '+2.1250', '1', '+2.1251', '+2.1249' ), |
|
|
|
|
|
|
84
|
|
|
|
|
85
|
|
|
'1.4e-2' => QuantityValue::newFromNumber( '+0.014', '1', '+0.015', '+0.013' ), |
|
|
|
|
|
|
86
|
|
|
'1.4e3' => QuantityValue::newFromNumber( '+1400', '1', '+1500', '+1300' ), |
|
|
|
|
|
|
87
|
|
|
'1.4e3!m' => QuantityValue::newFromNumber( '+1400', 'm', '+1400', '+1400' ), |
|
|
|
|
|
|
88
|
|
|
'1.4e3m2' => QuantityValue::newFromNumber( '+1400', 'm2', '+1500', '+1300' ), |
|
|
|
|
|
|
89
|
|
|
'1.4ev' => QuantityValue::newFromNumber( '+1.4', 'ev', '+1.5', '+1.3' ), |
|
|
|
|
|
|
90
|
|
|
'1.4e' => QuantityValue::newFromNumber( '+1.4', 'e', '+1.5', '+1.3' ), |
|
|
|
|
|
|
91
|
|
|
'12e3e4' => QuantityValue::newFromNumber( '+12000', 'e4', '+13000', '+11000' ), |
|
|
|
|
|
|
92
|
|
|
// FIXME: Add support for 12x10^3, see DecimalParser. |
|
93
|
|
|
'0.004e3' => QuantityValue::newFromNumber( '+4', '1', '+5', '+3' ), |
|
|
|
|
|
|
94
|
|
|
'0.004e-3' => QuantityValue::newFromNumber( '+0.000004', '1', '+0.000005', '+0.000003' ), |
|
|
|
|
|
|
95
|
|
|
'4000e3' => QuantityValue::newFromNumber( '+4000000', '1', '+4001000', '+3999000' ), |
|
|
|
|
|
|
96
|
|
|
'4000e-3' => QuantityValue::newFromNumber( '+4.000', '1', '+4.001', '+3.999' ), |
|
|
|
|
|
|
97
|
|
|
|
|
98
|
|
|
// precision |
|
99
|
|
|
'0!' => QuantityValue::newFromNumber( 0, '1', 0, 0 ), |
|
|
|
|
|
|
100
|
|
|
'10.003!' => QuantityValue::newFromNumber( '+10.003', '1', '+10.003', '+10.003' ), |
|
|
|
|
|
|
101
|
|
|
'-200!' => QuantityValue::newFromNumber( -200, '1', -200, -200 ), |
|
|
|
|
|
|
102
|
|
|
'0~' => QuantityValue::newFromNumber( 0, '1', 1, -1 ), |
|
|
|
|
|
|
103
|
|
|
'10.003~' => QuantityValue::newFromNumber( '+10.003', '1', '+10.004', '+10.002' ), |
|
|
|
|
|
|
104
|
|
|
'-200~' => QuantityValue::newFromNumber( -200, '1', -199, -201 ), |
|
|
|
|
|
|
105
|
|
|
|
|
106
|
|
|
// uncertainty |
|
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
|
|
|
'5.3 ±0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ), |
|
|
|
|
|
|
110
|
|
|
|
|
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.3e3 +/- 0.2e2' => QuantityValue::newFromNumber( '+5300', '1', '+5320', '+5280' ), |
|
|
|
|
|
|
115
|
|
|
'2e-2+/-1.1e-1' => QuantityValue::newFromNumber( '+0.02', '1', '+0.13', '-0.09' ), |
|
|
|
|
|
|
116
|
|
|
|
|
117
|
|
|
// negative |
|
118
|
|
|
'5.3 +/- -0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ), |
|
|
|
|
|
|
119
|
|
|
'5.3+--0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ), |
|
|
|
|
|
|
120
|
|
|
'5.3 ±-0.2' => QuantityValue::newFromNumber( '+5.3', '1', '+5.5', '+5.1' ), |
|
|
|
|
|
|
121
|
|
|
|
|
122
|
|
|
// units |
|
123
|
|
|
'5.3+-0.2cm' => QuantityValue::newFromNumber( '+5.3', 'cm', '+5.5', '+5.1' ), |
|
|
|
|
|
|
124
|
|
|
'10.003! km' => QuantityValue::newFromNumber( '+10.003', 'km', '+10.003', '+10.003' ), |
|
|
|
|
|
|
125
|
|
|
'-200~ % ' => QuantityValue::newFromNumber( -200, '%', -199, -201 ), |
|
|
|
|
|
|
126
|
|
|
'100003 m³' => QuantityValue::newFromNumber( 100003, 'm³', 100004, 100002 ), |
|
|
|
|
|
|
127
|
|
|
'3.±-0.2µ' => QuantityValue::newFromNumber( '+3', 'µ', '+3.2', '+2.8' ), |
|
|
|
|
|
|
128
|
|
|
'+00.20 Å' => QuantityValue::newFromNumber( '+0.20', 'Å', '+0.21', '+0.19' ), |
|
|
|
|
|
|
129
|
|
|
); |
|
130
|
|
|
|
|
131
|
|
|
$argLists = array(); |
|
132
|
|
|
|
|
133
|
|
|
foreach ( $amounts as $amount => $expected ) { |
|
134
|
|
|
//NOTE: PHP may "helpfully" have converted $amount to an integer. Yay. |
|
135
|
|
|
$argLists[] = array( strval( $amount ), $expected ); |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
return $argLists; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* @see StringValueParserTest::invalidInputProvider |
|
143
|
|
|
*/ |
|
144
|
|
|
public function invalidInputProvider() { |
|
145
|
|
|
$argLists = parent::invalidInputProvider(); |
|
146
|
|
|
|
|
147
|
|
|
$invalid = array( |
|
148
|
|
|
'foo', |
|
149
|
|
|
'', |
|
150
|
|
|
'.', |
|
151
|
|
|
'+.', |
|
152
|
|
|
'-.', |
|
153
|
|
|
'--1', |
|
154
|
|
|
'++1', |
|
155
|
|
|
'1-', |
|
156
|
|
|
'one', |
|
157
|
|
|
//'0x20', // this is actually valid, "x20" is read as the unit. |
|
158
|
|
|
'1+1', |
|
159
|
|
|
'1-1', |
|
160
|
|
|
'1.2.3', |
|
161
|
|
|
|
|
162
|
|
|
',3,', |
|
163
|
|
|
'10,000', |
|
164
|
|
|
'10\'000', |
|
165
|
|
|
|
|
166
|
|
|
'2!!', |
|
167
|
|
|
'!2', |
|
168
|
|
|
'2!2', |
|
169
|
|
|
|
|
170
|
|
|
'2!~', |
|
171
|
|
|
'2~!', |
|
172
|
|
|
'2~~', |
|
173
|
|
|
'~2', |
|
174
|
|
|
'2~2', |
|
175
|
|
|
|
|
176
|
|
|
'2 -- 2', |
|
177
|
|
|
'2++2', |
|
178
|
|
|
'2+±2', |
|
179
|
|
|
'2-±2', |
|
180
|
|
|
|
|
181
|
|
|
'2()', |
|
182
|
|
|
'2*', |
|
183
|
|
|
'2x y', |
|
184
|
|
|
'x 2 y', |
|
185
|
|
|
|
|
186
|
|
|
'100 003', |
|
187
|
|
|
'1 . 0', |
|
188
|
|
|
); |
|
189
|
|
|
|
|
190
|
|
|
foreach ( $invalid as $value ) { |
|
191
|
|
|
$argLists[] = array( $value ); |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
return $argLists; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
public function testParseLocalizedQuantity() { |
|
198
|
|
|
$options = new ParserOptions(); |
|
199
|
|
|
$options->setOption( ValueParser::OPT_LANG, 'test' ); |
|
200
|
|
|
|
|
201
|
|
|
$unlocalizer = $this->getMock( 'ValueParsers\NumberUnlocalizer' ); |
|
202
|
|
|
|
|
203
|
|
|
$charmap = array( |
|
204
|
|
|
' ' => '', |
|
205
|
|
|
',' => '.', |
|
206
|
|
|
); |
|
207
|
|
|
|
|
208
|
|
|
$unlocalizer->expects( $this->any() ) |
|
209
|
|
|
->method( 'unlocalizeNumber' ) |
|
210
|
|
|
->will( $this->returnCallback( |
|
211
|
|
|
function( $number ) use ( $charmap ) { |
|
212
|
|
|
return str_replace( array_keys( $charmap ), array_values( $charmap ), $number ); |
|
213
|
|
|
} |
|
214
|
|
|
) ); |
|
215
|
|
|
|
|
216
|
|
|
$unlocalizer->expects( $this->any() ) |
|
217
|
|
|
->method( 'getNumberRegex' ) |
|
218
|
|
|
->will( $this->returnValue( '[\d ]+(?:,\d+)?' ) ); |
|
219
|
|
|
|
|
220
|
|
|
$unlocalizer->expects( $this->any() ) |
|
221
|
|
|
->method( 'getUnitRegex' ) |
|
222
|
|
|
->will( $this->returnValue( '[a-z~]+' ) ); |
|
223
|
|
|
|
|
224
|
|
|
$parser = new QuantityParser( $options, $unlocalizer ); |
|
225
|
|
|
|
|
226
|
|
|
$quantity = $parser->parse( '1 22 333,77+-3a~b' ); |
|
227
|
|
|
|
|
228
|
|
|
$this->assertEquals( '122333.77', $quantity->getAmount() ); |
|
229
|
|
|
$this->assertEquals( 'a~b', $quantity->getUnit() ); |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
/** |
|
233
|
|
|
* @dataProvider unitOptionProvider |
|
234
|
|
|
*/ |
|
235
|
|
|
public function testUnitOption( $value, $unit, $expected ) { |
|
236
|
|
|
$options = new ParserOptions(); |
|
237
|
|
|
$options->setOption( QuantityParser::OPT_UNIT, $unit ); |
|
238
|
|
|
|
|
239
|
|
|
$parser = $this->getQuantityParser( $options ); |
|
240
|
|
|
|
|
241
|
|
|
$quantity = $parser->parse( $value ); |
|
242
|
|
|
$this->assertEquals( $expected, $quantity->getUnit() ); |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
public function unitOptionProvider() { |
|
246
|
|
|
return array( |
|
247
|
|
|
array( '17 kittens', null, 'kittens' ), |
|
248
|
|
|
array( '17', 'kittens', 'kittens' ), |
|
249
|
|
|
array( '17 kittens', 'kittens', 'kittens' ), |
|
250
|
|
|
array( '17m', 'm', 'm' ), |
|
251
|
|
|
array( ' 17 ', ' http://concept.uri ', 'http://concept.uri' ), |
|
252
|
|
|
); |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
/** |
|
256
|
|
|
* @dataProvider conflictingUnitOptionProvider |
|
257
|
|
|
*/ |
|
258
|
|
|
public function testConflictingUnitOption( $value, $unit ) { |
|
259
|
|
|
$options = new ParserOptions(); |
|
260
|
|
|
$options->setOption( QuantityParser::OPT_UNIT, $unit ); |
|
261
|
|
|
|
|
262
|
|
|
$parser = $this->getQuantityParser( $options ); |
|
263
|
|
|
|
|
264
|
|
|
$this->setExpectedException( 'ValueParsers\ParseException' ); |
|
265
|
|
|
$parser->parse( $value ); |
|
266
|
|
|
} |
|
267
|
|
|
|
|
268
|
|
|
public function conflictingUnitOptionProvider() { |
|
269
|
|
|
return array( |
|
270
|
|
|
array( '17 kittens', 'm' ), |
|
271
|
|
|
array( '17m', 'kittens' ), |
|
272
|
|
|
array( '17m', '' ), |
|
273
|
|
|
); |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
} |
|
277
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignorePhpDoc annotation to the duplicate definition and it will be ignored.