Skip to content

Commit

Permalink
New make Stimulus controller
Browse files Browse the repository at this point in the history
update test

Coding standards

Coding standards

Make constants private

Add targets and language

Respeect Coding Standards

Possibility to add values

remove extra line
  • Loading branch information
JabriAbdelilah committed Feb 27, 2022
1 parent b6b0afe commit 9086819
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 0 deletions.
240 changes: 240 additions & 0 deletions src/Maker/MakeStimulusController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
<?php

/*
* This file is part of the Symfony MakerBundle package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MakerBundle\Maker;

use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\WebpackEncoreBundle\WebpackEncoreBundle;

/**
* @author Abdelilah Jabri <[email protected]>
*
* @internal
*/
final class MakeStimulusController extends AbstractMaker
{
public static function getCommandName(): string
{
return 'make:stimulus-controller';
}

public static function getCommandDescription(): string
{
return 'Creates a new Stimulus controller';
}

public function configureCommand(Command $command, InputConfiguration $inputConfig)
{
$command
->addArgument('name', InputArgument::REQUIRED, 'The name of the Stimulus controller (e.g. <fg=yellow>hello</>)')
->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeStimulusController.txt'));
}

public function interact(InputInterface $input, ConsoleStyle $io, Command $command)
{
$command->addArgument('extension', InputArgument::OPTIONAL);
$command->addArgument('targets', InputArgument::OPTIONAL, '', []);
$command->addArgument('values', InputArgument::OPTIONAL, '', []);

$chosenExtension = $io->choice(
'Language (<fg=yellow>JavaScript</> or <fg=yellow>TypeScript</>)',
[
'js' => 'JavaScript',
'ts' => 'TypeScript',
]
);

$input->setArgument('extension', $chosenExtension);

$includeTargets = $io->confirm('Do you want to include targets?');
if ($includeTargets) {
$targets = [];
$isFirstTarget = true;
while (true) {
$newTarget = $this->askForNextTarget($io, $targets, $isFirstTarget);
$isFirstTarget = false;

if (null === $newTarget) {
break;
}

$targets[] = $newTarget;
}

$input->setArgument('targets', $targets);
}

$includeValues = $io->confirm('Do you want to include values?');
if ($includeValues) {
$values = [];
$isFirstValue = true;
while (true) {
$newValue = $this->askForNextValue($io, $values, $isFirstValue);
$isFirstValue = false;

if (null === $newValue) {
break;
}

$values[$newValue['name']] = $newValue;
}

$input->setArgument('values', $values);
}
}

public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
{
$controllerName = Str::asSnakeCase($input->getArgument('name'));
$chosenExtension = $input->getArgument('extension');
$targets = $input->getArgument('targets');
$values = $input->getArgument('values');

$targets = empty($targets) ? $targets : sprintf("['%s']", implode("', '", $targets));

$fileName = sprintf('%s_controller.%s', $controllerName, $chosenExtension);
$filePath = sprintf('assets/controllers/%s', $fileName);

$generator->generateFile(
$filePath,
'stimulus/Controller.tpl.php',
[
'targets' => $targets,
'values' => $values,
]
);

$generator->writeChanges();

$this->writeSuccessMessage($io);

$io->text([
'Next:',
sprintf('- Open <info>%s</info> and add the code you need', $filePath),
'Find the documentation at <fg=yellow>https://github.com/symfony/stimulus-bridge</>',
]);
}

private function askForNextTarget(ConsoleStyle $io, array $targets, bool $isFirstTarget)
{
if ($isFirstTarget) {
$questionText = 'New target name (press <return> to stop adding targets)';
} else {
$questionText = 'Add another target? Enter the target name (or press <return> to stop adding targets)';
}

$targetName = $io->ask($questionText, null, function ($name) use ($targets) {
if (\in_array($name, $targets)) {
throw new \InvalidArgumentException(sprintf('The "%s" target already exists.', $name));
}

return $name;
});

if (!$targetName) {
return null;
}

return $targetName;
}

private function askForNextValue(ConsoleStyle $io, array $values, bool $isFirstValue)
{
if ($isFirstValue) {
$questionText = 'New value name (press <return> to stop adding values)';
} else {
$questionText = 'Add another value? Enter the value name (or press <return> to stop adding values)';
}

$valueName = $io->ask($questionText, null, function ($name) use ($values) {
if (\array_key_exists($name, $values)) {
throw new \InvalidArgumentException(sprintf('The "%s" value already exists.', $name));
}

return $name;
});

if (!$valueName) {
return null;
}

$defaultType = 'String';
// try to guess the type by the value name prefix/suffix
// convert to snake case for simplicity
$snakeCasedField = Str::asSnakeCase($valueName);

if ('_id' === $suffix = substr($snakeCasedField, -3)) {
$defaultType = 'Number';
} elseif (0 === strpos($snakeCasedField, 'is_')) {
$defaultType = 'Boolean';
} elseif (0 === strpos($snakeCasedField, 'has_')) {
$defaultType = 'Boolean';
}

$type = null;
$types = $this->getValuesTypes();
while (null === $type) {
$question = new Question('Value type (enter <comment>?</comment> to see all types)', $defaultType);
$question->setAutocompleterValues($types);
$type = $io->askQuestion($question);

if ('?' === $type) {
$this->printAvailableTypes($io);
$io->writeln('');

$type = null;
} elseif (!\in_array($type, $types)) {
$this->printAvailableTypes($io);
$io->error(sprintf('Invalid type "%s".', $type));
$io->writeln('');

$type = null;
}
}

return ['name' => $valueName, 'type' => $type];
}

private function printAvailableTypes(ConsoleStyle $io)
{
$allTypes = $this->getValuesTypes();
foreach ($allTypes as $type) {
$io->writeln(sprintf('<info>%s</info>', $type));
}
}

private function getValuesTypes(): array
{
return [
'Array',
'Boolean',
'Number',
'Object',
'String',
];
}

public function configureDependencies(DependencyBuilder $dependencies)
{
$dependencies->addClassDependency(
WebpackEncoreBundle::class,
'webpack-encore-bundle'
);
}
}
4 changes: 4 additions & 0 deletions src/Resources/config/makers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,9 @@
<argument>%kernel.project_dir%</argument>
<tag name="maker.command" />
</service>

<service id="maker.maker.make_stimulus_controller" class="Symfony\Bundle\MakerBundle\Maker\MakeStimulusController">
<tag name="maker.command" />
</service>
</services>
</container>
5 changes: 5 additions & 0 deletions src/Resources/help/MakeStimulusController.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The <info>%command.name%</info> command generates new Stimulus Controller.

<info>php %command.full_name% hello</info>

If the argument is missing, the command will ask for the controller name interactively.
18 changes: 18 additions & 0 deletions src/Resources/skeleton/stimulus/Controller.tpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Controller } from '@hotwired/stimulus';

/*
* The following line makes this controller "lazy": it won't be downloaded until needed
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
*/
/* stimulusFetch: 'lazy' */
export default class extends Controller {
<?= $targets ? " static targets = $targets\n" : "" ?>
<?php if ($values) { ?>
static values = {
<?php foreach ($values as $value): ?>
<?= $value['name'] ?>: <?= $value['type'] ?>,
<?php endforeach; ?>
}
<?php } ?>
// ...
}
90 changes: 90 additions & 0 deletions tests/Maker/MakeStimulusControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

/*
* This file is part of the Symfony MakerBundle package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MakerBundle\Tests\Maker;

use Symfony\Bundle\MakerBundle\Maker\MakeStimulusController;
use Symfony\Bundle\MakerBundle\Test\MakerTestCase;
use Symfony\Bundle\MakerBundle\Test\MakerTestRunner;

class MakeStimulusControllerTest extends MakerTestCase
{
protected function getMakerClass(): string
{
return MakeStimulusController::class;
}

public function getTestDetails()
{
yield 'it_generates_stimulus_controller_with_targets' => [$this->createMakerTest()
->run(function (MakerTestRunner $runner) {
$runner->runMaker(
[
'with_targets', //controller name
'js', //controller language
'yes', //add targets
'results', //first target
'messages', //second target
'errors', //third target
'', //empty input to stop adding targets
]);

$generatedFilePath = $runner->getPath('assets/controllers/with_targets_controller.js');

$this->assertFileExists($generatedFilePath);

$generatedFileContents = file_get_contents($generatedFilePath);
$expectedContents = file_get_contents(__DIR__.'/../fixtures/make-stimulus-controller/with_targets.js');

$this->assertSame(
$expectedContents,
$generatedFileContents
);
}),
];

yield 'it_generates_stimulus_controller_without_targets' => [$this->createMakerTest()
->run(function (MakerTestRunner $runner) {
$runner->runMaker(
[
'without_targets', //controller name
'js', //controller language
'no', //do not add targets
]);

$generatedFilePath = $runner->getPath('assets/controllers/without_targets_controller.js');

$this->assertFileExists($generatedFilePath);

$generatedFileContents = file_get_contents($generatedFilePath);
$expectedContents = file_get_contents(__DIR__.'/../fixtures/make-stimulus-controller/without_targets.js');

$this->assertSame(
$expectedContents,
$generatedFileContents
);
}),
];

yield 'it_generates_typescript_stimulus_controller' => [$this->createMakerTest()
->run(function (MakerTestRunner $runner) {
$runner->runMaker(
[
'typescript', //controller name
'ts', //controller language
'no', //do not add targets
]);

$this->assertFileExists($runner->getPath('assets/controllers/typescript_controller.ts'));
}),
];
}
}
11 changes: 11 additions & 0 deletions tests/fixtures/make-stimulus-controller/with_targets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Controller } from '@hotwired/stimulus';

/*
* The following line makes this controller "lazy": it won't be downloaded until needed
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
*/
/* stimulusFetch: 'lazy' */
export default class extends Controller {
static targets = ['results', 'messages', 'errors']
// ...
}
10 changes: 10 additions & 0 deletions tests/fixtures/make-stimulus-controller/without_targets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Controller } from '@hotwired/stimulus';

/*
* The following line makes this controller "lazy": it won't be downloaded until needed
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
*/
/* stimulusFetch: 'lazy' */
export default class extends Controller {
// ...
}

0 comments on commit 9086819

Please sign in to comment.