Completed
Push — master ( b00e9b...6e0e0e )
by Vladimir
03:28
created

PHPUnit_Stakx_TestCase::getMockMenuManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 0
1
<?php
2
3
/**
4
 * @copyright 2017 Vladimir Jimenez
5
 * @license   https://github.com/allejo/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx\Test;
9
10
use allejo\stakx\Command\BuildableCommand;
11
use allejo\stakx\Configuration;
12
use allejo\stakx\Core\StakxLogger;
13
use allejo\stakx\Document\FrontMatterDocument;
14
use allejo\stakx\Filesystem\File;
15
use allejo\stakx\Filesystem\FilesystemLoader as fs;
16
use allejo\stakx\Manager\CollectionManager;
17
use allejo\stakx\Manager\DataManager;
18
use allejo\stakx\Manager\MenuManager;
19
use allejo\stakx\Manager\PageManager;
20
use allejo\stakx\Service;
21
use allejo\stakx\System\Filesystem;
22
use allejo\stakx\Filesystem\Folder;
23
use org\bovigo\vfs\vfsStream;
24
use org\bovigo\vfs\vfsStreamDirectory;
25
use org\bovigo\vfs\vfsStreamFile;
26
use Psr\Log\LoggerInterface;
27
use Symfony\Component\Console\Output\ConsoleOutput;
28
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
29
use Symfony\Component\Yaml\Yaml;
30
31
abstract class PHPUnit_Stakx_TestCase extends \PHPUnit_Framework_TestCase
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
32
{
33
    const FM_OBJ_TEMPLATE = "---\n%s\n---\n\n%s";
34
35
    /** @var string */
36
    protected $assetFolder;
37
    /** @var vfsStreamFile */
38
    protected $dummyFile;
39
    /** @var vfsStreamDirectory */
40
    protected $rootDir;
41
    /** @var Filesystem */
42
    protected $fs;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
43
44
    public function setUp()
45
    {
46
        $this->dummyFile = vfsStream::newFile('stakx.html.twig');
47
        $this->rootDir = vfsStream::setup();
48
        $this->fs = new Filesystem();
49
50
        Service::setWorkingDirectory(null);
51
        Service::setParameter(BuildableCommand::USE_DRAFTS, false);
52
        Service::setParameter(BuildableCommand::WATCHING, false);
53
        Service::setParameter(BuildableCommand::SAFE_MODE, false);
54
        Service::setParameter(BuildableCommand::BUILD_PROFILE, false);
55
        Service::setParameter(Configuration::HIGHLIGHTER_ENABLED, true);
56
        Service::setParameter('build.preserveCase', false);
57
58
        // Inspect the VFS as an array
59
        // vfsStream::inspect(new vfsStreamStructureVisitor())->getStructure();
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
60
    }
61
62
    public function tearDown()
63
    {
64
        if ($this->assetFolder !== null)
65
        {
66
            $this->fs->remove($this->assetFolder);
67
        }
68
    }
69
70
    ///
71
    // Assertion Functions
72
    ///
73
74
    /**
75
     * @param string $needle
76
     * @param string $haystack
77
     * @param string $message
78
     */
79
    protected function assertStringContains($needle, $haystack, $message = '')
80
    {
81
        $this->assertNotFalse(strpos($haystack, $needle), $message);
82
    }
83
84
    /**
85
     * @param string $fileContent
86
     * @param string $filePath
87
     * @param string $message
88
     */
89
    protected function assertFileContains($fileContent, $filePath, $message = '')
90
    {
91
        (substr($filePath, -1, 1) == '/') && $filePath .= 'index.html';
92
93
        $contents = file_get_contents($filePath);
94
95
        $this->assertStringContains($fileContent, $contents, $message);
96
    }
97
98
    ///
99
    // Filesystem Functions
100
    ///
101
102
    /**
103
     * Create a temporary folder where temporary file writes will be made to.
104
     *
105
     * @param string $folderName
106
     */
107
    protected function createAssetFolder($folderName)
108
    {
109
        $this->assetFolder = fs::getRelativePath(fs::appendPath(__DIR__, $folderName));
110
111
        fs::mkdir($this->assetFolder);
112
    }
113
114
    /**
115
     * Write a file to the asset folder.
116
     *
117
     * This file will be written to the actual filesystem and not the virtual filesystem. This file will be deleted at
118
     * each tearDown().
119
     *
120
     * @param string $fileName
121
     * @param string $content
122
     *
123
     * @return string Path to the temporary file; relative to the project's root
124
     */
125
    protected function createPhysicalFile($fileName, $content)
126
    {
127
        $folder = new Folder($this->assetFolder);
128
        $folder->writeFile($fileName, $content);
129
130
        return fs::appendPath($this->assetFolder, $fileName);
131
    }
132
133
    /**
134
     * Write a file to the virtual filesystem.
135
     *
136
     * This file will be deleted at each tearDown().
137
     *
138
     * @param string $filename
139
     * @param string $content
140
     *
141
     * @return string The URL of the file on the virtual filesystem.
142
     */
143
    protected function createVirtualFile($filename, $content)
144
    {
145
        $file = vfsStream::newFile($filename);
146
        $file
147
            ->setContent($content)
148
            ->at($this->rootDir)
149
        ;
150
151
        return $file->url();
152
    }
153
154
    /**
155
     * Create an object of a given type.
156
     *
157
     * This will create a virtual file and then create an object of the specified type for the created file.
158
     *
159
     * @param string $classType
160
     * @param string $filename
161
     * @param string $content
162
     *
163
     * @return object An instance of $classType
164
     */
165
    protected function createDocumentOfType($classType, $filename, $content)
166
    {
167
        $file = $this->createVirtualFile($filename, $content);
168
169
        return new $classType(new File($file));
170
    }
171
172
    /**
173
     * Create an object of a given type following the Front Matter format.
174
     *
175
     * @param string $classType
176
     * @param string $filename
0 ignored issues
show
Documentation introduced by
Should the type for parameter $filename not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
177
     * @param array  $frontMatter
178
     * @param string $content
179
     *
180
     * @return object An instance of $classType
181
     */
182
    protected function createFrontMatterDocumentOfType($classType, $filename = null, $frontMatter = [], $content = 'Body Text')
183
    {
184
        $body = $this->buildFrontMatterTemplate($frontMatter, $content);
185
186
        if (!$filename)
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type string|null is loosely compared to false; 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...
187
        {
188
            $filename = hash('sha256', uniqid(mt_rand(), true), false);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filename. This often makes code more readable.
Loading history...
189
        }
190
191
        return $this->createDocumentOfType($classType, $filename, $body);
192
    }
193
194
    /**
195
     * Create multiple virtual files from a given array of information.
196
     *
197
     * ```php
198
     * $elements = [
199
     *     [
200
     *         'filename' => '<string>',
201
     *         'frontmatter' => [],
202
     *         'body' => '<string>',
203
     *     ],
204
     * ];
205
     * ```
206
     *
207
     * @param string $classType
208
     * @param array  $elements
209
     *
210
     * @return array
211
     */
212
    protected function createMultipleFrontMatterDocumentsOfType($classType, $elements)
213
    {
214
        $results = [];
215
216
        foreach ($elements as $element)
217
        {
218
            $filename = (isset($element['filename'])) ? $element['filename'] : null;
219
            $frontMatter = (!isset($element['frontmatter']) || empty($element['frontmatter'])) ? [] : $element['frontmatter'];
220
            $body = (isset($element['body'])) ? $element['body'] : 'Body Text';
221
222
            /** @var FrontMatterDocument $item */
223
            $item = $this->createFrontMatterDocumentOfType($classType, $filename, $frontMatter, $body);
224
            $item->evaluateFrontMatter();
225
226
            $results[] = $item;
227
        }
228
229
        return $results;
230
    }
231
232
    /**
233
     * Create a File object from a given path.
234
     *
235
     * @deprecated
236
     *
237
     * @param  string $filePath
238
     *
239
     * @return File
240
     */
241
    protected function createFileObjectFromPath($filePath)
242
    {
243
        return (new File($filePath));
244
    }
245
246
    ///
247
    // Mock Objects
248
    ///
249
250
    /**
251
     * @return Configuration|\PHPUnit_Framework_MockObject_MockObject
252
     */
253
    protected function getMockConfiguration()
254
    {
255
        $stub = $this->getMockBuilder(Configuration::class)
256
            ->disableOriginalConstructor()
257
            ->getMock()
258
        ;
259
260
        $stub->method('getConfiguration')->willReturn([]);
261
        $stub->method('getTwigAutoescape')->willReturn(false);
262
263
        return $stub;
264
    }
265
266
    /**
267
     * @return PageManager|\PHPUnit_Framework_MockObject_MockObject
268
     */
269
    protected function getMockPageManager()
270
    {
271
        $stub = $this->getMockBuilder(PageManager::class)
272
            ->disableOriginalConstructor()
273
            ->getMock()
274
        ;
275
276
        $stub->method('getJailedStaticPageViews')->willReturn([]);
277
278
        return $stub;
279
    }
280
281
    /**
282
     * @return MenuManager|\PHPUnit_Framework_MockObject_MockObject
283
     */
284
    protected function getMockMenuManager()
285
    {
286
        $stub = $this->getMockBuilder(MenuManager::class)
287
            ->disableOriginalConstructor()
288
            ->getMock()
289
        ;
290
291
        $stub->method('getSiteMenu')->willReturn([]);
292
293
        return $stub;
294
    }
295
296
    /**
297
     * @return CollectionManager|\PHPUnit_Framework_MockObject_MockObject
298
     */
299
    protected function getMockCollectionManager()
300
    {
301
        $stub = $this->getMockBuilder(CollectionManager::class)
302
            ->disableOriginalConstructor()
303
            ->getMock()
304
        ;
305
306
        $stub->method('getJailedCollections')->willReturn([]);
307
308
        return $stub;
309
    }
310
311
    /**
312
     * @return DataManager|\PHPUnit_Framework_MockObject_MockObject
313
     */
314
    protected function getMockDataManager()
315
    {
316
        $stub = $this->getMockBuilder(DataManager::class)
317
            ->disableOriginalConstructor()
318
            ->getMock()
319
        ;
320
321
        $stub->method('getJailedDataItems')->willReturn([]);
322
323
        return $stub;
324
    }
325
326
    /**
327
     * Get a mock EventDispatcher.
328
     *
329
     * @return EventDispatcherInterface
330
     */
331
    protected function getMockEventDistpatcher()
332
    {
333
        return $this->getMock(EventDispatcherInterface::class);
334
    }
335
336
    /**
337
     * Get a mock logger.
338
     *
339
     * @return LoggerInterface
340
     */
341
    protected function getMockLogger()
342
    {
343
        return $this->getMock(LoggerInterface::class);
344
    }
345
346
    /**
347
     * Get a real logger instance that will save output to the console.
348
     *
349
     * @return StakxLogger
350
     */
351
    protected function getReadableLogger()
352
    {
353
        stream_filter_register('intercept', StreamInterceptor::class);
354
        $stakxLogger = new StakxLogger(new ConsoleOutput());
355
        stream_filter_append($stakxLogger->getOutputInterface()->getStream(), 'intercept');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method getStream() does only exist in the following implementations of said interface: Symfony\Component\Console\Output\ConsoleOutput, Symfony\Component\Console\Output\StreamOutput.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
356
357
        return $stakxLogger;
358
    }
359
360
    ///
361
    // Utility Functions
362
    ///
363
364
    /**
365
     * Get the directory of the unit tests.
366
     *
367
     * @return string
368
     */
369
    protected function getTestRoot()
370
    {
371
        return __DIR__;
372
    }
373
374
    /**
375
     * Generate a FrontMatter-ready syntax to be used as a file's content.
376
     *
377
     * @param array  $frontMatter
378
     * @param string $body
379
     *
380
     * @return string
381
     */
382
    protected function buildFrontMatterTemplate(array $frontMatter = array(), $body = 'Body text')
383
    {
384
        $fm = (empty($frontMatter)) ? '' : Yaml::dump($frontMatter, 2);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
385
386
        return sprintf(self::FM_OBJ_TEMPLATE, $fm, $body);
387
    }
388
389
    ///
390
    // Misc Functions
391
    ///
392
393
    protected function bookCollectionProvider($jailed = false)
394
    {
395
        $cm = new CollectionManager(
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $cm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
396
            $this->getMock(Configuration::class),
397
            $this->getMockEventDistpatcher(),
398
            $this->getMockLogger()
399
        );
400
        $cm->parseCollections(array(
401
            array(
402
                'name' => 'books',
403
                'folder' => 'tests/allejo/stakx/Test/assets/MyBookCollection/',
404
            ),
405
        ));
406
407
        return (!$jailed) ? $cm->getCollections() : $cm->getJailedCollections();
408
    }
409
}
410