QuantityHtmlFormatterTest   A
last analyzed

Complexity

Total Complexity 8

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 8
lcom 1
cbo 7
dl 0
loc 158
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstance() 0 3 1
A getQuantityHtmlFormatter() 0 19 2
A newQuantityValue() 0 14 1
A validProvider() 0 24 1
A testFormatWithFormatString() 0 6 1
A testGivenHtmlCharacters_formatEscapesHtmlCharacters() 0 14 1
A applyUnitOptionProvider() 0 42 1
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 Kreuz
18
 */
19
class QuantityHtmlFormatterTest extends ValueFormatterTestBase {
20
21
	/**
22
	 * @see ValueFormatterTestBase::getInstance
23
	 *
24
	 * @param FormatterOptions|null $options
25
	 *
26
	 * @return QuantityHtmlFormatter
27
	 */
28
	protected function getInstance( FormatterOptions $options = null ) {
29
		return $this->getQuantityHtmlFormatter( $options );
30
	}
31
32
	/**
33
	 * @param FormatterOptions|null $options
34
	 * @param DecimalFormatter|null $decimalFormatter
35
	 * @param string|null $quantityWithUnitFormat
36
	 *
37
	 * @return QuantityHtmlFormatter
38
	 */
39
	private function getQuantityHtmlFormatter(
40
		FormatterOptions $options = null,
41
		DecimalFormatter $decimalFormatter = null,
42
		$quantityWithUnitFormat = null
43
	) {
44
		$vocabularyUriFormatter = $this->getMock( ValueFormatter::class );
45
		$vocabularyUriFormatter->expects( $this->any() )
46
			->method( 'format' )
47
			->will( $this->returnCallback( function( $unit ) {
48
				return $unit === '1' ? null : $unit;
49
			} ) );
50
51
		return new QuantityHtmlFormatter(
52
			$options,
53
			$decimalFormatter,
54
			$vocabularyUriFormatter,
55
			$quantityWithUnitFormat
56
		);
57
	}
58
59
	/**
60
	 * @param string $className
61
	 * @param int $uncertaintyMargin
62
	 *
63
	 * @return QuantityValue
64
	 */
65
	private function newQuantityValue( $className, $uncertaintyMargin = 0 ) {
66
		$quantity = $this->getMockBuilder( $className )
67
			->disableOriginalConstructor()
68
			->getMock();
69
70
		$quantity->expects( $this->any() )
71
			->method( 'getAmount' )
72
			->will( $this->returnValue( new DecimalValue( 2 ) ) );
73
		$quantity->expects( $this->any() )
74
			->method( 'getUncertaintyMargin' )
75
			->will( $this->returnValue( new DecimalValue( $uncertaintyMargin ) ) );
76
77
		return $quantity;
78
	}
79
80
	/**
81
	 * @see ValueFormatterTestBase::validProvider
82
	 */
83
	public function validProvider() {
84
		return [
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...
85
			'Unbounded, Unit 1' => [
86
				UnboundedQuantityValue::newFromNumber( '+2', '1' ),
87
				'2'
88
			],
89
			'Unbounded, String unit' => [
90
				UnboundedQuantityValue::newFromNumber( '+2', 'Ultrameter' ),
91
				'2 <span class="wb-unit">Ultrameter</span>'
92
			],
93
			'Unit 1' => [
94
				QuantityValue::newFromNumber( '+2', '1', '+3', '+1' ),
95
				'2±1'
96
			],
97
			'String unit' => [
98
				QuantityValue::newFromNumber( '+2', 'Ultrameter', '+3', '+1' ),
99
				'2±1 <span class="wb-unit">Ultrameter</span>'
100
			],
101
			'HTML injection' => [
102
				QuantityValue::newFromNumber( '+2', '<b>injection</b>', '+2', '+2' ),
103
				'2±0 <span class="wb-unit">&lt;b&gt;injection&lt;/b&gt;</span>'
104
			],
105
		];
106
	}
107
108
	public function testFormatWithFormatString() {
109
		$formatter = $this->getQuantityHtmlFormatter( null, null, '$2&thinsp;$1' );
110
		$value = UnboundedQuantityValue::newFromNumber( '+5', 'USD' );
111
		$formatted = $formatter->format( $value );
112
		$this->assertSame( '<span class="wb-unit">USD</span>&thinsp;5', $formatted );
113
	}
114
115
	/**
116
	 * @dataProvider applyUnitOptionProvider
117
	 */
118
	public function testGivenHtmlCharacters_formatEscapesHtmlCharacters(
119
		FormatterOptions $options = null,
120
		UnboundedQuantityValue $value,
121
		$expected
122
	) {
123
		$decimalFormatter = $this->getMock( DecimalFormatter::class );
124
		$decimalFormatter->expects( $this->any() )
125
			->method( 'format' )
126
			->will( $this->returnValue( '<b>+2</b>' ) );
127
128
		$formatter = $this->getQuantityHtmlFormatter( $options, $decimalFormatter );
129
		$formatted = $formatter->format( $value );
130
		$this->assertSame( $expected, $formatted );
131
	}
132
133
	public function applyUnitOptionProvider() {
134
		$noUnit = new FormatterOptions();
135
		$noUnit->setOption( QuantityHtmlFormatter::OPT_APPLY_UNIT, false );
136
137
		return [
138
			'Disabled without unit' => [
139
				$noUnit,
140
				UnboundedQuantityValue::newFromNumber( 2, '1' ),
141
				'&lt;b&gt;+2&lt;/b&gt;'
142
			],
143
			'Disabled with unit' => [
144
				$noUnit,
145
				UnboundedQuantityValue::newFromNumber( 2, '<b>m</b>' ),
146
				'&lt;b&gt;+2&lt;/b&gt;'
147
			],
148
			'Default without unit' => [
149
				null,
150
				UnboundedQuantityValue::newFromNumber( 2, '1' ),
151
				'&lt;b&gt;+2&lt;/b&gt;'
152
			],
153
			'Default with unit' => [
154
				null,
155
				UnboundedQuantityValue::newFromNumber( 2, '<b>m</b>' ),
156
				'&lt;b&gt;+2&lt;/b&gt; <span class="wb-unit">&lt;b&gt;m&lt;/b&gt;</span>'
157
			],
158
			'HTML escaping bounded' => [
159
				null,
160
				$this->newQuantityValue( QuantityValue::class ),
161
				'&lt;b&gt;+2&lt;/b&gt;±&lt;b&gt;+2&lt;/b&gt;'
162
			],
163
			'HTML escaping bounded with uncertainty' => [
164
				null,
165
				$this->newQuantityValue( QuantityValue::class, 1 ),
166
				'&lt;b&gt;+2&lt;/b&gt;±&lt;b&gt;+2&lt;/b&gt;'
167
			],
168
			'HTML escaping unbounded' => [
169
				null,
170
				$this->newQuantityValue( UnboundedQuantityValue::class ),
171
				'&lt;b&gt;+2&lt;/b&gt;'
172
			],
173
		];
174
	}
175
176
}
177