TestCommand   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 136
Duplicated Lines 0 %

Importance

Changes 13
Bugs 4 Features 2
Metric Value
eloc 65
c 13
b 4
f 2
dl 0
loc 136
rs 10
wmc 16

7 Methods

Rating   Name   Duplication   Size   Complexity  
A generate() 0 5 1
A prepare() 0 26 6
A convertToTest() 0 11 1
A onConstruct() 0 13 1
A getTestMetadata() 0 13 3
A execute() 0 22 3
A interact() 0 15 1
1
<?php
2
3
/*
4
 * This file is part of the PHINT package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace Ahc\Phint\Console;
13
14
use Ahc\Cli\IO\Interactor;
15
use Ahc\Phint\Generator\TwigGenerator;
16
17
class TestCommand extends BaseCommand
18
{
19
    /** @var string Command name */
20
    protected $_name = 'test';
21
22
    /** @var string Command description */
23
    protected $_desc = 'Generate test stubs';
24
25
    /**
26
     * Configure the command options/arguments.
27
     *
28
     * @return void
29
     */
30
    protected function onConstruct()
31
    {
32
        $this
33
            ->option('-t --no-teardown', 'Dont add teardown method')
34
            ->option('-s --no-setup', 'Dont add setup method')
35
            ->option('-n --naming', "Test method naming format\n(t: testMethod | m: test_method | i: it_tests_)")
36
            ->option('-a --with-abstract', 'Create stub for abstract/interface class')
37
            ->option('-p --phpunit [classFqcn]', 'Base PHPUnit class to extend from')
38
            ->option('-x --template', "User supplied template path\nIt has higher precedence than inbuilt templates")
39
            ->usage(
40
                '<bold>  phint test</end> <comment>-n i</end>        With `it_` naming<eol/>' .
41
                '<bold>  phint t</end> <comment>--no-teardown</end>  Without `tearDown()`<eol/>' .
42
                '<bold>  phint test</end> <comment>-a</end>          With stubs for abstract/interface<eol/>'
43
            );
44
    }
45
46
    public function interact(Interactor $io)
47
    {
48
        $promptConfig = [
49
            'naming' => [
50
                'choices' => ['t' => 'testMethod', 'i' => 'it_tests_', 'm' => 'test_method'],
51
                'default' => 't',
52
            ],
53
            'phpunit' => [
54
                'default' => 'PHPUnit\\Framework\\TestCase',
55
            ],
56
            'template' => false,
57
        ];
58
59
        $this->logging('start');
60
        $this->promptAll($io, $promptConfig);
61
    }
62
63
    /**
64
     * Generate test stubs.
65
     *
66
     * @return void
67
     */
68
    public function execute()
69
    {
70
        $io = $this->app()->io();
71
72
        $io->comment('Preparing metadata ...', true);
73
        $metadata = $this->prepare();
74
75
        if (empty($metadata)) {
76
            $io->bgGreen('Looks like nothing to do here', true);
77
78
            return;
79
        }
80
81
        $io->comment('Generating tests ...', true);
82
        $generated = $this->generate($metadata, $this->values());
83
84
        if ($generated) {
85
            $io->cyan("$generated test(s) generated", true);
86
        }
87
88
        $io->ok('Done', true);
89
        $this->logging('end');
90
    }
91
92
    protected function prepare(): array
93
    {
94
        // Sorry psr-0!
95
        $namespaces  = $this->_composer->config('autoload.psr-4');
96
        $namespaces += $this->_composer->config('autoload-dev.psr-4');
97
98
        $srcNs = $testNs = [];
99
100
        foreach ($namespaces as $ns => $path) {
101
            $ns = \rtrim($ns, '\\') . '\\';
102
            if (\preg_match('!^(source|src|lib|class)/?!', $path)) {
103
                $path    = \rtrim($path, '/\\') . '/';
104
                $srcNs[] = ['ns' => $ns, 'nsPath' => "{$this->_workDir}/$path"];
105
            } elseif (\strpos($path, 'test') === 0) {
106
                $path   = \rtrim($path, '/\\') . '/';
107
                $testNs = ['ns' => $ns, 'nsPath' => "{$this->_workDir}/$path"];
108
            }
109
        }
110
111
        if (empty($srcNs) || empty($testNs)) {
112
            throw new \RuntimeException(
113
                'The composer.json#(autoload.psr-4, autoload-dev.psr-4) contains no `src` or `test` paths'
114
            );
115
        }
116
117
        return $this->getTestMetadata($this->getSourceClasses(), $srcNs, $testNs);
118
    }
119
120
    protected function getTestMetadata(array $classes, array $srcNs, array $testNs): array
121
    {
122
        $metadata = [];
123
124
        foreach ($classes as $classFqcn) {
125
            if ([] === $meta = $this->getClassMetaData($classFqcn)) {
126
                continue;
127
            }
128
129
            $metadata[] = $meta + $this->convertToTest($meta, $srcNs, $testNs);
130
        }
131
132
        return $metadata;
133
    }
134
135
    private function convertToTest(array $metadata, array $srcNs, array $testNs): array
136
    {
137
        $srcNspcs  = \array_column($srcNs, 'ns');
138
        $testClass = $metadata['className'] . 'Test';
139
        $testPath  = \str_replace(\array_column($srcNs, 'nsPath'), $testNs['nsPath'], $metadata['classPath']);
140
        $testPath  = \preg_replace('!\.php$!i', 'Test.php', $testPath);
141
        $testFqcn  = \str_replace($srcNspcs, $testNs['ns'], $metadata['classFqcn']) . 'Test';
142
143
        $testNamespace = \trim(\str_replace($srcNspcs, $testNs['ns'], $metadata['namespace'] . '\\'), '\\');
144
145
        return compact('testClass', 'testNamespace', 'testFqcn', 'testPath');
146
    }
147
148
    protected function generate(array $testMetadata, array $parameters): int
149
    {
150
        $generator = new TwigGenerator($this->getTemplatePaths($parameters), $this->getCachePath());
151
152
        return $generator->generateTests($testMetadata, $parameters);
153
    }
154
}
155