Skip to content

Commit d1e58b6

Browse files
committed
wip: Refactor GitIgnoreCommand
1 parent 845efc9 commit d1e58b6

2 files changed

Lines changed: 95 additions & 111 deletions

File tree

src/Console/Command/GitIgnoreCommand.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818

1919
namespace FastForward\DevTools\Console\Command;
2020

21+
use Composer\Command\BaseCommand;
2122
use FastForward\DevTools\GitIgnore\MergerInterface;
2223
use FastForward\DevTools\GitIgnore\ReaderInterface;
2324
use FastForward\DevTools\GitIgnore\WriterInterface;
25+
use Symfony\Component\Config\FileLocatorInterface;
2426
use Symfony\Component\Console\Attribute\AsCommand;
2527
use Symfony\Component\Console\Input\InputInterface;
2628
use Symfony\Component\Console\Input\InputOption;
2729
use Symfony\Component\Console\Output\OutputInterface;
28-
use Symfony\Component\Filesystem\Filesystem;
2930

3031
/**
3132
* Provides functionality to merge and synchronize .gitignore files.
@@ -41,23 +42,28 @@
4142
description: 'Merges and synchronizes .gitignore files.',
4243
help: "This command merges the canonical .gitignore from dev-tools with the project's existing .gitignore."
4344
)]
44-
final class GitIgnoreCommand extends AbstractCommand
45+
final class GitIgnoreCommand extends BaseCommand
4546
{
47+
/**
48+
* @var string the default filename for .gitignore files
49+
*/
50+
public const string FILENAME = '.gitignore';
51+
4652
/**
4753
* Creates a new GitIgnoreCommand instance.
4854
*
4955
* @param MergerInterface $merger the merger component
5056
* @param ReaderInterface $reader the reader component
5157
* @param WriterInterface|null $writer the writer component
52-
* @param Filesystem $filesystem the filesystem component
58+
* @param FilelocatorInterface $fileLocator the file locator
5359
*/
5460
public function __construct(
5561
private readonly MergerInterface $merger,
5662
private readonly ReaderInterface $reader,
5763
private readonly WriterInterface $writer,
58-
Filesystem $filesystem,
64+
private readonly FileLocatorInterface $fileLocator,
5965
) {
60-
parent::__construct($filesystem);
66+
parent::__construct();
6167
}
6268

6369
/**
@@ -74,14 +80,14 @@ protected function configure(): void
7480
shortcut: 's',
7581
mode: InputOption::VALUE_OPTIONAL,
7682
description: 'Path to the source .gitignore file (canonical)',
77-
default: parent::getDevToolsFile('.gitignore'),
83+
default: $this->fileLocator->locate(self::FILENAME, dirname(__DIR__, 3)),
7884
)
7985
->addOption(
8086
name: 'target',
8187
shortcut: 't',
8288
mode: InputOption::VALUE_OPTIONAL,
8389
description: 'Path to the target .gitignore file (project)',
84-
default: parent::getConfigFile('.gitignore', true)
90+
default: $this->fileLocator->locate(self::FILENAME)
8591
);
8692
}
8793

tests/Console/Command/GitIgnoreCommandTest.php

Lines changed: 82 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -19,163 +19,141 @@
1919
namespace FastForward\DevTools\Tests\Console\Command;
2020

2121
use FastForward\DevTools\Console\Command\GitIgnoreCommand;
22-
use FastForward\DevTools\GitIgnore\GitIgnore;
2322
use FastForward\DevTools\GitIgnore\GitIgnoreInterface;
24-
use FastForward\DevTools\GitIgnore\Merger;
2523
use FastForward\DevTools\GitIgnore\MergerInterface;
26-
use FastForward\DevTools\GitIgnore\Reader;
2724
use FastForward\DevTools\GitIgnore\ReaderInterface;
28-
use FastForward\DevTools\GitIgnore\Writer;
2925
use FastForward\DevTools\GitIgnore\WriterInterface;
3026
use PHPUnit\Framework\Attributes\CoversClass;
3127
use PHPUnit\Framework\Attributes\Test;
32-
use PHPUnit\Framework\Attributes\UsesClass;
28+
use PHPUnit\Framework\TestCase;
3329
use Prophecy\Argument;
3430
use Prophecy\PhpUnit\ProphecyTrait;
3531
use Prophecy\Prophecy\ObjectProphecy;
32+
use ReflectionMethod;
33+
use Symfony\Component\Config\FileLocatorInterface;
34+
use Symfony\Component\Console\Input\InputInterface;
35+
use Symfony\Component\Console\Output\OutputInterface;
3636

37-
/**
38-
* Test suite for the GitIgnoreCommand.
39-
*
40-
* This test class verifies that the GitIgnoreCommand correctly merges and
41-
* synchronizes .gitignore files using the Reader, Merger, and Writer components.
42-
*/
4337
#[CoversClass(GitIgnoreCommand::class)]
44-
#[UsesClass(Reader::class)]
45-
#[UsesClass(GitIgnore::class)]
46-
#[UsesClass(Merger::class)]
47-
#[UsesClass(Writer::class)]
48-
final class GitIgnoreCommandTest extends AbstractCommandTestCase
38+
final class GitIgnoreCommandTest extends TestCase
4939
{
5040
use ProphecyTrait;
5141

52-
/**
53-
* @var ObjectProphecy<ReaderInterface>
54-
*/
55-
private ObjectProphecy $reader;
56-
57-
/**
58-
* @var ObjectProphecy<MergerInterface>
59-
*/
42+
/** @var ObjectProphecy<MergerInterface> */
6043
private ObjectProphecy $merger;
6144

62-
/**
63-
* @var ObjectProphecy<WriterInterface>
64-
*/
45+
/** @var ObjectProphecy<ReaderInterface> */
46+
private ObjectProphecy $reader;
47+
48+
/** @var ObjectProphecy<WriterInterface> */
6549
private ObjectProphecy $writer;
6650

67-
/**
68-
* @var ObjectProphecy<GitIgnoreInterface>
69-
*/
51+
/** @var ObjectProphecy<FileLocatorInterface> */
52+
private ObjectProphecy $fileLocator;
53+
54+
/** @var ObjectProphecy<InputInterface> */
55+
private ObjectProphecy $input;
56+
57+
/** @var ObjectProphecy<OutputInterface> */
58+
private ObjectProphecy $output;
59+
60+
/** @var ObjectProphecy<GitIgnoreInterface> */
7061
private ObjectProphecy $gitIgnoreSource;
7162

72-
/**
73-
* @var ObjectProphecy<GitIgnoreInterface>
74-
*/
75-
private ObjectProphecy $gitIgnoreMerge;
63+
/** @var ObjectProphecy<GitIgnoreInterface> */
64+
private ObjectProphecy $gitIgnoreTarget;
65+
66+
/** @var ObjectProphecy<GitIgnoreInterface> */
67+
private ObjectProphecy $gitIgnoreMerged;
7668

77-
/**
78-
* @var string The source .gitignore path.
79-
*/
80-
private string $sourcePath;
69+
private GitIgnoreCommand $command;
8170

82-
/**
83-
* @var string The target .gitignore path.
84-
*/
85-
private string $targetPath;
71+
private const string SOURCE_PATH = '/path/to/source/.gitignore';
72+
private const string TARGET_PATH = '/path/to/target/.gitignore';
8673

87-
/**
88-
* Sets up the test fixtures.
89-
*/
9074
protected function setUp(): void
9175
{
92-
$this->reader = $this->prophesize(ReaderInterface::class);
76+
$this->fileLocator = $this->prophesize(FileLocatorInterface::class);
77+
$this->fileLocator->locate(Argument::cetera())
78+
->willReturn('/default/path/to/.gitignore');
79+
9380
$this->merger = $this->prophesize(MergerInterface::class);
81+
$this->reader = $this->prophesize(ReaderInterface::class);
9482
$this->writer = $this->prophesize(WriterInterface::class);
83+
$this->input = $this->prophesize(InputInterface::class);
84+
$this->output = $this->prophesize(OutputInterface::class);
85+
9586
$this->gitIgnoreSource = $this->prophesize(GitIgnoreInterface::class);
96-
$this->gitIgnoreMerge = $this->prophesize(GitIgnoreInterface::class);
87+
$this->gitIgnoreTarget = $this->prophesize(GitIgnoreInterface::class);
88+
$this->gitIgnoreMerged = $this->prophesize(GitIgnoreInterface::class);
9789

98-
$this->sourcePath = uniqid('source_', true) . '/.gitignore';
99-
$this->targetPath = uniqid('target_', true) . '/.gitignore';
90+
$this->input->getOption('source')
91+
->willReturn(self::SOURCE_PATH);
92+
$this->input->getOption('target')
93+
->willReturn(self::TARGET_PATH);
10094

101-
$this->reader->read($this->sourcePath)
95+
$this->reader->read(self::SOURCE_PATH)
10296
->willReturn($this->gitIgnoreSource->reveal());
103-
$this->reader->read($this->targetPath)
104-
->willReturn($this->gitIgnoreMerge->reveal());
97+
$this->reader->read(self::TARGET_PATH)
98+
->willReturn($this->gitIgnoreTarget->reveal());
10599

106-
parent::setUp();
107-
}
100+
$this->merger->merge($this->gitIgnoreSource->reveal(), $this->gitIgnoreTarget->reveal())
101+
->willReturn($this->gitIgnoreMerged->reveal());
108102

109-
/**
110-
* Returns the command class under test.
111-
*/
112-
protected function getCommandClass(): GitIgnoreCommand
113-
{
114-
return new GitIgnoreCommand(
103+
$this->writer->write(Argument::any());
104+
$this->output->writeln(Argument::any());
105+
106+
$this->command = new GitIgnoreCommand(
115107
$this->merger->reveal(),
116108
$this->reader->reveal(),
117109
$this->writer->reveal(),
118-
$this->filesystem->reveal()
110+
$this->fileLocator->reveal(),
119111
);
120112
}
121113

122-
/**
123-
* Returns the expected command name.
124-
*/
125-
protected function getCommandName(): string
114+
#[Test]
115+
public function commandWillSetExpectedNameDescriptionAndHelp(): void
126116
{
127-
return 'gitignore';
117+
self::assertSame('gitignore', $this->command->getName());
118+
self::assertSame(
119+
'Merges and synchronizes .gitignore files.',
120+
$this->command->getDescription()
121+
);
122+
self::assertSame(
123+
"This command merges the canonical .gitignore from dev-tools with the project's existing .gitignore.",
124+
$this->command->getHelp()
125+
);
128126
}
129127

130-
/**
131-
* Returns the expected command description.
132-
*/
133-
protected function getCommandDescription(): string
128+
#[Test]
129+
public function commandWillHaveExpectedOptions(): void
134130
{
135-
return 'Merges and synchronizes .gitignore files.';
136-
}
131+
$definition = $this->command->getDefinition();
137132

138-
/**
139-
* Returns the expected command help text.
140-
*/
141-
protected function getCommandHelp(): string
142-
{
143-
return "This command merges the canonical .gitignore from dev-tools with the project's existing .gitignore.";
133+
self::assertTrue($definition->hasOption('source'));
134+
self::assertTrue($definition->hasOption('target'));
144135
}
145136

146-
/**
147-
* Tests that execute() returns SUCCESS and correctly merges files.
148-
*/
149137
#[Test]
150-
public function executeWillReturnSuccessAndMergeFiles(): void
138+
public function executeWillReturnSuccessWhenMergeSucceeds(): void
151139
{
152-
$this->gitIgnoreSource->entries()
153-
->willReturn(['# Canonical', 'vendor/', 'node_modules/']);
154-
$this->gitIgnoreMerge->entries()
155-
->willReturn(['# Project', '*.log', 'tmp/']);
156-
157-
$this->merger->merge($this->gitIgnoreSource->reveal(), $this->gitIgnoreMerge->reveal())
158-
->willReturn(new GitIgnore($this->targetPath, ['vendor/', 'node_modules/', '*.log', 'tmp/']));
159-
160-
$this->writer->write(Argument::that(
161-
static fn($gitIgnore): bool => $gitIgnore instanceof GitIgnore && $gitIgnore->entries() === [
162-
'vendor/',
163-
'node_modules/',
164-
'*.log',
165-
'tmp/',
166-
]
167-
))->shouldBeCalled();
140+
$this->writer->write($this->gitIgnoreMerged->reveal())
141+
->shouldBeCalled();
168142

169143
$this->output->writeln('<info>Merging .gitignore files...</info>')
170144
->shouldBeCalled();
171145
$this->output->writeln('<info>Successfully merged .gitignore file.</info>')
172146
->shouldBeCalled();
173147

174-
$this->input->getOption('source')
175-
->willReturn($this->sourcePath);
176-
$this->input->getOption('target')
177-
->willReturn($this->targetPath);
148+
$result = $this->executeCommand();
149+
150+
self::assertSame(GitIgnoreCommand::SUCCESS, $result);
151+
}
152+
153+
private function executeCommand(): int
154+
{
155+
$reflectionMethod = new ReflectionMethod($this->command, 'execute');
178156

179-
self::assertSame(GitIgnoreCommand::SUCCESS, $this->invokeExecute());
157+
return $reflectionMethod->invoke($this->command, $this->input->reveal(), $this->output->reveal());
180158
}
181-
}
159+
}

0 commit comments

Comments
 (0)