Test Failed
Push — stable ( 52f02e...8e9716 )
by Nuno
04:37
created

Section::footer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 0
cts 16
cp 0
rs 9.552
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 6
1
<?php
2
3
/**
4
 * This file is part of Collision.
5
 *
6
 * (c) Nuno Maduro <[email protected]>
7
 *
8
 *  For the full copyright and license information, please view the LICENSE
9
 *  file that was distributed with this source code.
10
 */
11
12
namespace NunoMaduro\Collision\Adapters\Phpunit;
13
14
use PHPUnit\Framework\TestCase;
15
use PHPUnit\Framework\Warning;
16
use Symfony\Component\Console\Output\ConsoleOutput;
17
use Symfony\Component\Console\Output\ConsoleSectionOutput;
18
use Throwable;
19
20
/**
21
 * @internal
22
 */
23
final class Section
24
{
25
    /**
26
     * Holds an instance of the console output.
27
     *
28
     * @var ConsoleOutput
29
     */
30
    private $output;
31
32
    /**
33
     * Holds an instance of the console section.
34
     *
35
     * @var ConsoleSectionOutput
36
     */
37
    private $section;
38
39
    /**
40
     * Holds an instance of the console section.
41
     *
42
     * @var ConsoleSectionOutput
43
     */
44
    private $footer;
45
46
    /**
47
     * If the current testSuite is dirty.
48
     *
49
     * @var bool
50
     */
51
    private $dirty = false;
52
53
    /**
54
     * Holds an instance of the test case.
55
     *
56
     * @var TestCase
57
     */
58
    private $testCase;
59
60
    /**
61
     * If the current testCase should pass.
62
     *
63
     * @var bool
64
     */
65
    public $shouldPass = true;
66
67
    /**
68
     * Holds the content of the section.
69
     *
70
     * @var array<int, string>
71
     */
72
    private $tests = [];
73
74
    /**
75
     * Section constructor.
76
     *
77
     * @param  ConsoleOutput  $output
78
     */
79 3
    public function __construct(ConsoleOutput $output)
80
    {
81 3
        $this->output = $output;
82 3
        $this->section = $output->section();
83 3
        $this->footer = $output->section();
84
        $this->testCase = new class extends TestCase {
85
        };
86 3
    }
87
88
    /**
89
     * Runs the given test.
90
     *
91
     * @param  TestCase  $test
92
     *
93
     * @return void
94
     */
95
    public function runs(TestCase $test): void
96
    {
97
        if (get_class($this->testCase) !== get_class($test)) {
98
            $this->end();
99
            $this->section = $this->output->section();
100
            $this->tests = [];
101
            $this->dirty = false;
102
        }
103
104
        $this->shouldPass = true;
105
        $this->testCase = $test;
106
        $this->updateTest('•', 'yellow', true);
107
    }
108
109
    /**
110
     * Passes the current test case.
111
     *
112
     * @return void
113
     */
114
    public function pass(): void
115
    {
116
        if ($this->shouldPass) {
117
            $this->updateTest('✓', 'green');
118
        }
119
    }
120
121
    /**
122
     * Marks the current test case as failed.
123
     *
124
     * @return void
125
     */
126
    public function fail(): void
127
    {
128
        $this->updateTest('✕', 'red');
129
        $this->title('FAIL', 'red');
130
131
        $this->footer->clear();
132
        $this->section->write($this->tests);
0 ignored issues
show
Documentation introduced by
$this->tests is of type array, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
133
134
        $this->dirty = true;
135
        $this->shouldPass = false;
136
    }
137
138
    /**
139
     * Marks the current test case as incomplete.
140
     *
141
     * @param  Throwable  $throwable
142
     *
143
     * @return void
144
     */
145 View Code Duplication
    public function incomplete(Throwable $throwable): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
    {
147
        $this->updateTest('i', 'yellow', false, $throwable->getMessage());
148
149
        $this->dirty = true;
150
        $this->shouldPass = false;
151
    }
152
153
    /**
154
     * Marks the current test case as risky.
155
     *
156
     * @return void
157
     */
158
    public function risky(): void
159
    {
160
        $this->updateTest('r', 'yellow');
161
162
        $this->dirty = true;
163
        $this->shouldPass = false;
164
    }
165
166
    /**
167
     * Marks the current test case as risky.
168
     *
169
     * @param  Throwable  $throwable
170
     *
171
     * @return void
172
     */
173 View Code Duplication
    public function skipped(Throwable $throwable): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174
    {
175
        $this->updateTest('s', 'yellow', false, $throwable->getMessage());
176
177
        $this->dirty = true;
178
        $this->shouldPass = false;
179
    }
180
181
    /**
182
     * Marks the current test case as risky.
183
     *
184
     * @return void
185
     */
186 View Code Duplication
    public function warn(Warning $warning): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
187
    {
188
        $this->updateTest('w', 'yellow', false, $warning->getMessage());
189
190
        $this->dirty = true;
191
        $this->shouldPass = false;
192
    }
193
194
    /**
195
     * Ends the current test suite.
196
     *
197
     * Here we do 3 things:
198
     *
199
     * 0. Remove the footer.
200
     * 1. Display the title.
201
     * 2. Display the tests results.
202
     */
203
    public function end(): void
204
    {
205
        if (count($this->tests)) {
206
            $this->footer->clear();
207
208
            if (! $this->dirty) {
209
                $this->title('PASS', 'green');
210
            } else {
211
                $this->title('WARN', 'yellow');
212
            }
213
214
            $this->section->write($this->tests);
0 ignored issues
show
Documentation introduced by
$this->tests is of type array, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
215
        }
216
217
    }
218
219
    /**
220
     * Updates the console with the current state.
221
     *
222
     * @param  string  $icon
223
     * @param  string  $color
224
     *
225
     * @param  bool  $create
226
     *
227
     * @return void
228
     */
229
    private function updateTest(string $icon, string $color, bool $create = false, string $note = null): void
230
    {
231
        $value = sprintf(
232
            '  <fg=%s;options=bold>%s</><fg=default> %s</>',
233
            $color,
234
            $icon,
235
            $name = $this->getTestCaseDescription()
236
        );
237
238
        if ($note) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $note of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
239
            $value .= sprintf(' → <fg=%s>%s</>', $color, trim((string) preg_replace("/\r|\n/", ' ', $note)));
240
        }
241
242
        if ($create) {
243
            $this->tests[] = $value;
244
        }
245
246
        $this->tests[count($this->tests) - 1] = $value;
247
248
        $this->footer('RUNS', 'yellow');
249
        $this->footer->write($value);
250
    }
251
252
    /**
253
     * Get the current test case description.
254
     *
255
     * @return string
256
     */
257
    private function getTestCaseDescription(): string
258
    {
259
        $name = $this->testCase->getName(true);
260
261
        // First, lets replace underscore by spaces.
262
        $name = str_replace('_', ' ', $name);
263
264
        // Then, replace upper cases by spaces.
265
        $name = (string) preg_replace('/([A-Z])/', ' $1', $name);
266
267
        // Finally, if it starts with `test`, we remove it.
268
        $name = (string) preg_replace('/^test/', '', $name);
269
270
        // Removes spaces
271
        $name = (string) trim($name);
272
273
        // Finally, lower case everything
274
        return (string) mb_strtolower($name);
275
    }
276
277
    private function footer(string $title, string $color): void
278
    {
279
        $fg = $title === 'FAIL' ? 'default' : 'black';
280
281
        $classParts = explode('\\', get_class($this->testCase));
282
283
        // Removes `Tests` part
284
        array_shift($classParts);
285
286
        $highlightedPart = array_pop($classParts);
287
        $nonHighlightedPart = implode('\\', $classParts);
288
        $class = sprintf("\e[2m%s\e[22m<fg=white;options=bold>%s</>", "$nonHighlightedPart\\", $highlightedPart);
289
290
        $this->footer->clear();
291
        $this->footer = $this->output->section();
292
        $this->footer->write(sprintf(
293
            "\n  <fg=%s;bg=%s;options=bold> %s </><fg=default> %s</>",
294
            $fg,
295
            $color,
296
            $title,
297
            $class
298
        ));
299
    }
300
301
    private function title(string $title, string $color): void
302
    {
303
        $fg = $title === 'FAIL' ? 'default' : 'black';
304
        $classParts = explode('\\', get_class($this->testCase));
305
306
        // Removes `Tests` part
307
        array_shift($classParts);
308
309
        $highlightedPart = array_pop($classParts);
310
        $nonHighlightedPart = implode('\\', $classParts);
311
        $class = sprintf("\e[2m%s\e[22m<fg=white;options=bold>%s</>", "$nonHighlightedPart\\", $highlightedPart);
312
313
        $this->section->write(sprintf(
314
            "\n  <fg=%s;bg=%s;options=bold> %s </><fg=default> %s</>",
315
            $fg,
316
            $color,
317
            $title,
318
            $class
319
        ));
320
    }
321
}
322