Test Failed
Push — httpUnits ( 76c237...022822 )
by no
02:53 queued 40s
created

QuantityHtmlFormatterTest::newQuantityValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 14
rs 9.4285
c 1
b 0
f 0
cc 1
eloc 11
nc 1
nop 2
1
<?php
2
3
namespace ValueFormatters\Test;
4
5
use DataValues\DecimalValue;
6
use DataValues\QuantityValue;
7
use DataValues\UnboundedQuantityValue;
8
use ValueFormatters\DecimalFormatter;
9
use ValueFormatters\FormatterOptions;
10
use ValueFormatters\QuantityHtmlFormatter;
11
use ValueFormatters\ValueFormatter;
12
13
/**
14
 * @covers ValueFormatters\QuantityHtmlFormatter
15
 *
16
 * @license GPL-2.0+
17
 * @author Thiemo Mättig
18
 */
19
class QuantityHtmlFormatterTest extends ValueFormatterTestBase {
20
21
	/**
22
	 * @deprecated since DataValues Interfaces 0.2, just use getInstance.
23
	 */
24
	protected function getFormatterClass() {
25
		throw new \LogicException( 'Should not be called, use getInstance' );
26
	}
27
28
	/**
29
	 * @see ValueFormatterTestBase::getInstance
30
	 *
31
	 * @param FormatterOptions|null $options
32
	 *
33
	 * @return QuantityHtmlFormatter
34
	 */
35
	protected function getInstance( FormatterOptions $options = null ) {
36
		return $this->getQuantityHtmlFormatter( $options );
37
	}
38
39
	/**
40
	 * @param FormatterOptions|null $options
41
	 * @param DecimalFormatter|null $decimalFormatter
42
	 * @param string|null $quantityWithUnitFormat
43
	 *
44
	 * @return QuantityHtmlFormatter
45
	 */
46
	private function getQuantityHtmlFormatter(
47
		FormatterOptions $options = null,
48
		DecimalFormatter $decimalFormatter = null,
49
		$quantityWithUnitFormat = null
50
	) {
51
		$vocabularyUriFormatter = $this->getMock( ValueFormatter::class );
52
		$vocabularyUriFormatter->expects( $this->any() )
53
			->method( 'format' )
54
			->will( $this->returnCallback( function( $unit ) {
55
				return $unit === '1' ? null : $unit;
56
			} ) );
57
58
		return new QuantityHtmlFormatter(
59
			$options,
60
			$decimalFormatter,
61
			$vocabularyUriFormatter,
62
			$quantityWithUnitFormat
63
		);
64
	}
65
66
	/**
67
	 * @param string $className
68
	 * @param int $uncertaintyMargin
69
	 *
70
	 * @return QuantityValue
71
	 */
72
	private function newQuantityValue( $className, $uncertaintyMargin = 0 ) {
73
		$quantity = $this->getMockBuilder( $className )
74
			->disableOriginalConstructor()
75
			->getMock();
76
77
		$quantity->expects( $this->any() )
78
			->method( 'getAmount' )
79
			->will( $this->returnValue( new DecimalValue( 2 ) ) );
80
		$quantity->expects( $this->any() )
81
			->method( 'getUncertaintyMargin' )
82
			->will( $this->returnValue( new DecimalValue( $uncertaintyMargin ) ) );
83
84
		return $quantity;
85
	}
86
87
	/**
88
	 * @see ValueFormatterTestBase::validProvider
89
	 */
90
	public function validProvider() {
91
		return array(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('Unbounded,...on&lt;/b&gt;</span>')); (array<string,array<DataV...dQuantityValue|string>>) is incompatible with the return type declared by the abstract method ValueFormatters\Test\Val...TestBase::validProvider of type array[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
92
			'Unbounded, Unit 1' => array(
93
				UnboundedQuantityValue::newFromNumber( '+2', '1' ),
94
				'2'
95
			),
96
			'Unbounded, String unit' => array(
97
				UnboundedQuantityValue::newFromNumber( '+2', 'Ultrameter' ),
98
				'2 <span class="wb-unit">Ultrameter</span>'
99
			),
100
			'Unit 1' => array(
101
				QuantityValue::newFromNumber( '+2', '1', '+3', '+1' ),
102
				'2±1'
103
			),
104
			'String unit' => array(
105
				QuantityValue::newFromNumber( '+2', 'Ultrameter', '+3', '+1' ),
106
				'2±1 <span class="wb-unit">Ultrameter</span>'
107
			),
108
			'HTML injection' => array(
109
				QuantityValue::newFromNumber( '+2', '<b>injection</b>', '+2', '+2' ),
110
				'2±0 <span class="wb-unit">&lt;b&gt;injection&lt;/b&gt;</span>'
111
			),
112
		);
113
	}
114
115
	public function testFormatWithFormatString() {
116
		$formatter = $this->getQuantityHtmlFormatter( null, null, '$2&thinsp;$1' );
117
		$value = UnboundedQuantityValue::newFromNumber( '+5', 'USD' );
118
		$formatted = $formatter->format( $value );
119
		$this->assertSame( '<span class="wb-unit">USD</span>&thinsp;5', $formatted );
120
	}
121
122
	/**
123
	 * @dataProvider applyUnitOptionProvider
124
	 */
125
	public function testGivenHtmlCharacters_formatEscapesHtmlCharacters(
126
		FormatterOptions $options = null,
127
		UnboundedQuantityValue $value,
128
		$expected
129
	) {
130
		$decimalFormatter = $this->getMock( DecimalFormatter::class );
131
		$decimalFormatter->expects( $this->any() )
132
			->method( 'format' )
133
			->will( $this->returnValue( '<b>+2</b>' ) );
134
135
		$formatter = $this->getQuantityHtmlFormatter( $options, $decimalFormatter );
136
		$formatted = $formatter->format( $value );
137
		$this->assertSame( $expected, $formatted );
138
	}
139
140
	public function applyUnitOptionProvider() {
141
		$noUnit = new FormatterOptions();
142
		$noUnit->setOption( QuantityHtmlFormatter::OPT_APPLY_UNIT, false );
143
144
		return array(
145
			'Disabled without unit' => array(
146
				$noUnit,
147
				UnboundedQuantityValue::newFromNumber( 2, '1' ),
148
				'&lt;b&gt;+2&lt;/b&gt;'
149
			),
150
			'Disabled with unit' => array(
151
				$noUnit,
152
				UnboundedQuantityValue::newFromNumber( 2, '<b>m</b>' ),
153
				'&lt;b&gt;+2&lt;/b&gt;'
154
			),
155
			'Default without unit' => array(
156
				null,
157
				UnboundedQuantityValue::newFromNumber( 2, '1' ),
158
				'&lt;b&gt;+2&lt;/b&gt;'
159
			),
160
			'Default with unit' => array(
161
				null,
162
				UnboundedQuantityValue::newFromNumber( 2, '<b>m</b>' ),
163
				'&lt;b&gt;+2&lt;/b&gt; <span class="wb-unit">&lt;b&gt;m&lt;/b&gt;</span>'
164
			),
165
			'HTML escaping bounded' => array(
166
				null,
167
				$this->newQuantityValue( 'DataValues\QuantityValue' ),
168
				'&lt;b&gt;+2&lt;/b&gt;±&lt;b&gt;+2&lt;/b&gt;'
169
			),
170
			'HTML escaping bounded with uncertainty' => array(
171
				null,
172
				$this->newQuantityValue( 'DataValues\QuantityValue', 1 ),
173
				'&lt;b&gt;+2&lt;/b&gt;±&lt;b&gt;+2&lt;/b&gt;'
174
			),
175
			'HTML escaping unbounded' => array(
176
				null,
177
				$this->newQuantityValue( 'DataValues\UnboundedQuantityValue' ),
178
				'&lt;b&gt;+2&lt;/b&gt;'
179
			),
180
		);
181
	}
182
183
}
184