diff --git a/README.md b/README.md
index 07671aa..6dcd251 100644
--- a/README.md
+++ b/README.md
@@ -29,11 +29,18 @@ you'd like to install with your library:
"dependency": [
"package/to-require",
...
- ]
+ ],
+ "dependency-or": {
+ "Question": [
+ "package/to-choose",
+ "package/or-this",
+ ...
+ ]
+ }
},
"require": {
"php": "^5.6 || ^7.0",
- "webimpress/composer-extra-dependency": "^0.1 || ^1.0",
+ "webimpress/composer-extra-dependency": "^0.3 || ^1.0",
...
}
...
diff --git a/src/Plugin.php b/src/Plugin.php
index e8c31b1..b020657 100644
--- a/src/Plugin.php
+++ b/src/Plugin.php
@@ -21,6 +21,9 @@
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
+use Composer\Script\Event;
+use InvalidArgumentException;
+use RuntimeException;
class Plugin implements PluginInterface, EventSubscriberInterface
{
@@ -48,11 +51,16 @@ class Plugin implements PluginInterface, EventSubscriberInterface
/** @var CompositeRepository */
private $repos;
+ /** @var string[] */
+ private $packagesToInstall = [];
+
public static function getSubscribedEvents()
{
return [
'post-package-install' => 'onPostPackage',
'post-package-update' => 'onPostPackage',
+ 'post-install-cmd' => 'onPostCommand',
+ 'post-update-cmd' => 'onPostCommand',
];
}
@@ -63,7 +71,27 @@ public function activate(Composer $composer, IOInterface $io)
$installedPackages = $this->composer->getRepositoryManager()->getLocalRepository()->getPackages();
foreach ($installedPackages as $package) {
- $this->installedPackages[$package->getName()] = $package->getPrettyVersion();
+ $this->installedPackages[strtolower($package->getName())] = $package->getPrettyVersion();
+ }
+ }
+
+ public function onPostCommand(Event $event)
+ {
+ if (! $event->isDevMode()) {
+ // Do nothing in production mode.
+ return;
+ }
+
+ if (! $this->io->isInteractive()) {
+ // Do nothing in no-interactive mode
+ return;
+ }
+
+ if ($this->packagesToInstall) {
+ $this->updateComposerJson($this->packagesToInstall);
+
+ $rootPackage = $this->updateRootPackage($this->composer->getPackage(), $this->packagesToInstall);
+ $this->runInstaller($rootPackage, array_keys($this->packagesToInstall));
}
}
@@ -85,36 +113,89 @@ public function onPostPackage(PackageEvent $event)
} else {
$package = $operation->getTargetPackage();
}
- $extra = $this->getExtraMetadata($package->getExtra());
- if (empty($extra)) {
- // Package does not define anything of interest; do nothing.
- return;
+
+ $extra = $package->getExtra();
+
+ $this->packagesToInstall += $this->andDependencies($extra);
+ $this->packagesToInstall += $this->orDependencies($extra);
+ }
+
+ private function andDependencies(array $extra)
+ {
+ $deps = isset($extra['dependency']) && is_array($extra['dependency'])
+ ? $extra['dependency']
+ : [];
+
+ if (! $deps) {
+ // No defined any packages to install
+ return [];
}
- $packages = array_flip($extra);
+ $packages = array_flip($deps);
foreach ($packages as $package => &$constraint) {
+ if ($this->isPackageReadyToInstall($package)) {
+ unset($packages[$package]);
+ continue;
+ }
+
if ($this->hasPackage($package)) {
unset($packages[$package]);
continue;
}
+ // Check if package is currently installed and use installed version.
+ if ($constraint = $this->getInstalledPackageConstraint($package)) {
+ continue;
+ }
+
+ // Package is not installed, then prompt user for the version.
$constraint = $this->promptForPackageVersion($package);
}
- if ($packages) {
- $this->updateComposerJson($packages);
-
- $rootPackage = $this->updateRootPackage($this->composer->getPackage(), $packages);
- $this->runInstaller($rootPackage, array_keys($packages));
- }
+ return $packages;
}
- private function getExtraMetadata(array $extra)
+ private function orDependencies(array $extra)
{
- return isset($extra['dependency']) && is_array($extra['dependency'])
- ? $extra['dependency']
+ $deps = isset($extra['dependency-or']) && is_array($extra['dependency-or'])
+ ? $extra['dependency-or']
: [];
+
+ if (! $deps) {
+ // No any dependencies to choose defined in the package.
+ return [];
+ }
+
+ $packages = [];
+ foreach ($deps as $question => $options) {
+ if (! is_array($options) || count($options) < 2) {
+ throw new RuntimeException('You must provide at least two optional dependencies.');
+ }
+
+ foreach ($options as $package) {
+ if ($this->isPackageReadyToInstall($package)) {
+ // Package has been already prepared to be installed, skipping.
+ continue 2;
+ }
+
+ if ($this->hasPackage($package)) {
+ // Package from this group has been found in root composer, skipping.
+ continue 2;
+ }
+
+ // Check if package is currently installed, if so, use installed constraint and skip question.
+ if ($constraint = $this->getInstalledPackageConstraint($package)) {
+ $packages[$package] = $constraint;
+ continue 2;
+ }
+ }
+
+ $package = $this->promptForPackageSelection($question, $options);
+ $packages[$package] = $this->promptForPackageVersion($package);
+ }
+
+ return $packages;
}
private function updateRootPackage(RootPackageInterface $rootPackage, array $packages)
@@ -157,21 +238,55 @@ private function runInstaller(RootPackageInterface $rootPackage, array $packages
return $installer->run();
}
- private function promptForPackageVersion($name)
+ private function getInstalledPackageConstraint($package)
{
+ $lower = strtolower($package);
+
// Package is currently installed. Add it to root composer.json
- if (isset($this->installedPackages[$name])) {
- $this->io->write(sprintf(
- 'Added package %s to composer.json with constraint %s;'
- . ' to upgrade, run composer require %s:VERSION',
- $name,
- '^' . $this->installedPackages[$name],
- $name
- ));
+ if (! isset($this->installedPackages[$lower])) {
+ return null;
+ }
+
+ $constraint = '^' . $this->installedPackages[$lower];
+ $this->io->write(sprintf(
+ 'Added package %s to composer.json with constraint %s;'
+ . ' to upgrade, run composer require %s:VERSION',
+ $package,
+ $constraint,
+ $package
+ ));
+
+ return $constraint;
+ }
- return '^' . $this->installedPackages[$name];
+ private function promptForPackageSelection($question, array $packages)
+ {
+ $ask = [sprintf('%s' . "\n", $question)];
+ foreach ($packages as $i => $name) {
+ $ask[] = sprintf(' [%d] %s' . "\n", $i + 1, $name);
}
+ $ask[] = ' Make your selection: ';
+
+ do {
+ $package = $this->io->askAndValidate(
+ $ask,
+ function ($input) use ($packages) {
+ $input = is_numeric($input) ? (int) trim($input) : 0;
+
+ if (isset($packages[$input - 1])) {
+ return $packages[$input - 1];
+ }
+
+ return null;
+ }
+ );
+ } while (! $package);
+ return $package;
+ }
+
+ private function promptForPackageVersion($name)
+ {
$constraint = $this->io->askAndValidate(
sprintf(
'Enter the version of %s to require (or leave blank to use the latest version): ',
@@ -214,10 +329,25 @@ private function createInstaller(Composer $composer, IOInterface $io, RootPackag
private function hasPackage($package)
{
+ $lower = strtolower($package);
+
$rootPackage = $this->composer->getPackage();
$requires = $rootPackage->getRequires() + $rootPackage->getDevRequires();
foreach ($requires as $name => $link) {
- if (strtolower($name) === strtolower($package)) {
+ if (strtolower($name) === $lower) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function isPackageReadyToInstall($package)
+ {
+ $lower = strtolower($package);
+
+ foreach ($this->packagesToInstall as $name => $version) {
+ if (strtolower($name) === $lower) {
return true;
}
}
@@ -282,7 +412,7 @@ private function findBestVersionForPackage($name)
$package = $versionSelector->findBestCandidate($name, null, null, 'stable');
if (! $package) {
- throw new \InvalidArgumentException(sprintf(
+ throw new InvalidArgumentException(sprintf(
'Could not find package %s at any version for your minimum-stability (%s).'
. ' Check the package spelling or your minimum-stability',
$name,
diff --git a/test/PluginTest.php b/test/PluginTest.php
index fff6b98..e10ee58 100644
--- a/test/PluginTest.php
+++ b/test/PluginTest.php
@@ -19,12 +19,15 @@
use Composer\Package\Version\VersionSelector;
use Composer\Repository\RepositoryManager;
use Composer\Repository\WritableRepositoryInterface;
+use Composer\Script\Event;
use org\bovigo\vfs\vfsStream;
+use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use ReflectionMethod;
use ReflectionProperty;
+use RuntimeException;
use Webimpress\ComposerExtraDependency\Plugin;
class PluginTest extends TestCase
@@ -62,7 +65,24 @@ protected function setUp()
$this->plugin->activate($this->composer->reveal(), $this->io->reveal());
}
- protected function setUpComposerInstaller(array $expectedPackages, $expectedReturn = 0)
+ private function setUpRootPackage(
+ array $dependencies = [],
+ array $devDependencies = [],
+ $minimumStability = null
+ ) {
+ $rootPackage = $this->prophesize(RootPackageInterface::class);
+ $rootPackage->getRequires()->willReturn($dependencies)->shouldBeCalled();
+ $rootPackage->getDevRequires()->willReturn($devDependencies)->shouldBeCalled();
+ if ($minimumStability) {
+ $rootPackage->getMinimumStability()->willReturn($minimumStability)->shouldBeCalled();
+ } else {
+ $rootPackage->getMinimumStability()->shouldNotBeCalled();
+ }
+
+ $this->composer->getPackage()->willReturn($rootPackage->reveal())->shouldBeCalled();
+ }
+
+ private function setUpComposerInstaller(array $expectedPackages, $expectedReturn = 0)
{
$installer = $this->prophesize(Installer::class);
$installer->setRunScripts(false)->shouldBeCalled();
@@ -78,7 +98,7 @@ protected function setUpComposerInstaller(array $expectedPackages, $expectedRetu
});
}
- protected function setUpVersionSelector(VersionSelector $versionSelector)
+ private function setUpVersionSelector(VersionSelector $versionSelector)
{
$r = new ReflectionProperty($this->plugin, 'versionSelectorFactory');
$r->setAccessible(true);
@@ -87,7 +107,7 @@ protected function setUpVersionSelector(VersionSelector $versionSelector)
});
}
- protected function setUpPool()
+ private function setUpPool()
{
$pool = $this->prophesize(Pool::class);
@@ -96,7 +116,7 @@ protected function setUpPool()
$r->setValue($this->plugin, $pool->reveal());
}
- protected function setUpComposerJson($data = null)
+ private function setUpComposerJson($data = null)
{
$project = vfsStream::setup('project');
vfsStream::newFile('composer.json')
@@ -110,13 +130,13 @@ protected function setUpComposerJson($data = null)
});
}
- protected function createComposerJson($data)
+ private function createComposerJson($data)
{
$data = $data ?: $this->getDefaultComposerData();
return json_encode($data);
}
- protected function getDefaultComposerData()
+ private function getDefaultComposerData()
{
return [
'name' => 'test/project',
@@ -128,6 +148,46 @@ protected function getDefaultComposerData()
];
}
+ private function getCommandEvent($isDevMode = true)
+ {
+ $event = $this->prophesize(Event::class);
+ $event->isDevMode()->willReturn($isDevMode);
+
+ return $event->reveal();
+ }
+
+ private function getPackageEvent(
+ $packageName,
+ array $extra,
+ $operationClass = InstallOperation::class
+ ) {
+ /** @var PackageInterface|ObjectProphecy $package */
+ $package = $this->prophesize(PackageInterface::class);
+ $package->getName()->willReturn($packageName);
+ $package->getExtra()->willReturn($extra);
+
+ $operation = $this->prophesize($operationClass);
+ if ($operationClass === InstallOperation::class) {
+ $operation->getPackage()->willReturn($package->reveal());
+ } else {
+ $operation->getTargetPackage()->willReturn($package->reveal());
+ }
+
+ $event = $this->prophesize(PackageEvent::class);
+ $event->isDevMode()->willReturn(true);
+ $event->getOperation()->willReturn($operation->reveal())->shouldBeCalled();
+
+ return $event->reveal();
+ }
+
+ private function injectPackages(array $packagesToInstall)
+ {
+ $p = new ReflectionProperty($this->plugin, 'packagesToInstall');
+ $p->setAccessible(true);
+
+ $p->setValue($this->plugin, $packagesToInstall);
+ }
+
public function testActivateSetsComposerAndIoProperties()
{
$plugin = new Plugin();
@@ -142,111 +202,162 @@ public function testSubscribesToExpectedEvents()
$subscribers = Plugin::getSubscribedEvents();
$this->assertArrayHasKey('post-package-install', $subscribers);
$this->assertArrayHasKey('post-package-update', $subscribers);
+ $this->assertArrayHasKey('post-install-cmd', $subscribers);
+ $this->assertArrayHasKey('post-update-cmd', $subscribers);
$this->assertEquals('onPostPackage', $subscribers['post-package-install']);
$this->assertEquals('onPostPackage', $subscribers['post-package-update']);
+ $this->assertEquals('onPostCommand', $subscribers['post-install-cmd']);
+ $this->assertEquals('onPostCommand', $subscribers['post-update-cmd']);
}
- public function testDoNothingIfItIsNotInDevMode()
+ public function testPostPackageDoNothingInNoDevMode()
{
$event = $this->prophesize(PackageEvent::class);
$event->isDevMode()->willReturn(false);
+ $event->getOperation()->shouldNotBeCalled();
$this->assertNull($this->plugin->onPostPackage($event->reveal()));
}
- public function testDoNothingInNoInteractionMode()
+ public function testPostCommandDoNothingInNoDevMode()
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
- 'dependency' => [
- 'extra-dependency-foo',
- ],
- ]);
-
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
+ $this->assertNull($this->plugin->onPostCommand($this->getCommandEvent(false)));
+ }
+ public function testPostPackageDoNothingInNoInteractionMode()
+ {
$event = $this->prophesize(PackageEvent::class);
$event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
-
- $rootPackage = $this->prophesize(RootPackageInterface::class);
- $rootPackage->getRequires()->willReturn([]);
- $rootPackage->getDevRequires()->willReturn([]);
-
- $this->composer->getPackage()->willReturn($rootPackage);
+ $event->getOperation()->shouldNotBeCalled();
$this->io->isInteractive()->willReturn(false);
$this->assertNull($this->plugin->onPostPackage($event->reveal()));
}
- public function testDoNothingWhenThereIsNoExtraDependencies()
+ public function testPostCommandDoNothingInNoInteractionMode()
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([]);
-
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
+ $this->injectPackages([
+ 'my-package-foo' => '2.37.1',
+ 'other-package' => 'dev-feature/branch',
+ ]);
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
+ $this->io->isInteractive()->willReturn(false);
- $this->assertNull($this->plugin->onPostPackage($event->reveal()));
+ $this->assertNull($this->plugin->onPostCommand($this->getCommandEvent()));
}
- public function testDependencyAlreadyIsInRequiredSection()
+ public function sortPackages()
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
- 'dependency' => [
- 'extra-dependency-foo',
+ return [
+ [
+ true,
+ [
+ 'zoo/bar' => '1.2.4',
+ 'foo/baz' => '2.7.3',
+ ],
+ '{"foo\/baz":"2.7.3","webimpress\/my-package":"^1.0.0-dev@dev","zoo\/bar":"1.2.4"}',
],
- ]);
+ [
+ false,
+ [
+ 'zoo/bar' => '1.2.4',
+ 'foo/baz' => '2.7.3',
+ ],
+ '{"webimpress\/my-package":"^1.0.0-dev@dev","zoo\/bar":"1.2.4","foo\/baz":"2.7.3"}',
+ ],
+ ];
+ }
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
+ /**
+ * @dataProvider sortPackages
+ *
+ * @param bool $sortPackages
+ * @param array $packages
+ * @param string $result
+ */
+ public function testPostCommandInstallPackagesAndUpdateComposer($sortPackages, array $packages, $result)
+ {
+ $this->injectPackages($packages);
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
+ $this->io->isInteractive()->willReturn(true);
+ $this->io->write(' Updating composer.json')->shouldBeCalled();
+ $this->io->write('Updating root package')->shouldBeCalled();
+ $this->io->write(' Running an update to install dependent packages')->shouldBeCalled();
- $link = $this->prophesize(Link::class);
- $link->getTarget()->willReturn('extra-dependency-foo');
+ $config = $this->prophesize(Config::class);
+ $config->get('sort-packages')->willReturn($sortPackages)->shouldBeCalled();
$rootPackage = $this->prophesize(RootPackageInterface::class);
- $rootPackage->getDevRequires()->willReturn(['extra-dependency-foo' => $link->reveal()]);
+ $rootPackage->getRequires()->willReturn([])->shouldBeCalled();
+ $rootPackage->getDevRequires()->willReturn([])->shouldNotBeCalled();
+ $rootPackage->setRequires(Argument::that(function (array $arguments) use ($packages) {
+ if (count($arguments) !== count($packages)) {
+ return false;
+ }
- $this->composer->getPackage()->willReturn($rootPackage);
+ foreach ($packages as $package => $version) {
+ if (! $this->assertSetRequiresArgument($package, $version, $arguments)) {
+ return false;
+ }
+ }
- $this->assertNull($this->plugin->onPostPackage($event->reveal()));
+ return true;
+ }))->shouldBeCalled();
+
+ $this->composer->getPackage()->willReturn($rootPackage)->shouldBeCalled();
+ $this->composer->getConfig()->willReturn($config->reveal())->shouldBeCalled();
+
+ $this->setUpComposerJson();
+ $this->setUpComposerInstaller(array_keys($packages));
+
+ $this->assertNull($this->plugin->onPostCommand($this->getCommandEvent()));
+
+ $json = file_get_contents(vfsStream::url('project/composer.json'));
+ $composer = json_decode($json, true);
+ foreach ($packages as $package => $version) {
+ $this->assertTrue(isset($composer['require'][$package]));
+ $this->assertSame($version, $composer['require'][$package]);
+ }
+ $this->assertSame($result, json_encode($composer['require']));
}
- public function testDependencyAlreadyIsInRequiredDevSection()
+ public function operation()
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
+ return [
+ 'install' => [InstallOperation::class],
+ 'update' => [UpdateOperation::class],
+ ];
+ }
+
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testDoNothingWhenThereIsNoExtraDependencies($operation)
+ {
+ $event = $this->getPackageEvent('some/component', [], $operation);
+
+ $this->io->isInteractive()->willReturn(true)->shouldBeCalled();
+
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall([]);
+ }
+
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testDependencyAlreadyIsInRequireSection($operation)
+ {
+ $event = $this->getPackageEvent('some/component', [
'dependency' => [
'extra-dependency-foo',
],
- ]);
-
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
-
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
+ ], $operation);
$this->io->isInteractive()->willReturn(true)->shouldBeCalled();
$this->io->askAndValidate(Argument::any())->shouldNotBeCalled();
@@ -255,109 +366,59 @@ public function testDependencyAlreadyIsInRequiredDevSection()
$link->getTarget()->willReturn('extra-dependency-foo');
$rootPackage = $this->prophesize(RootPackageInterface::class);
- $rootPackage->getRequires()->willReturn([]);
- $rootPackage->getDevRequires()->willReturn(['extra-dependency-foo' => $link->reveal()]);
+ $rootPackage->getRequires()->willReturn(['extra-dependency-foo' => $link->reveal()])->shouldBeCalled();
+ $rootPackage->getDevRequires()->willReturn([])->shouldBeCalled();
$this->composer->getPackage()->willReturn($rootPackage);
- $this->assertNull($this->plugin->onPostPackage($event->reveal()));
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall([]);
}
- public function testInstallSingleDependencyOnPackageUpdate()
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testDependencyAlreadyIsInRequireDevSection($operation)
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
+ $event = $this->getPackageEvent('some/component', [
'dependency' => [
'extra-dependency-foo',
],
- ]);
-
- $operation = $this->prophesize(UpdateOperation::class);
- $operation->getTargetPackage()->willReturn($package->reveal());
-
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
+ ], $operation);
$this->io->isInteractive()->willReturn(true)->shouldBeCalled();
$this->io->askAndValidate(Argument::any())->shouldNotBeCalled();
- $config = $this->prophesize(Config::class);
- $config->get('sort-packages')->willReturn(true);
- $config->get(Argument::any())->willReturn(null);
+ $link = $this->prophesize(Link::class);
+ $link->getTarget()->willReturn('extra-dependency-foo');
$rootPackage = $this->prophesize(RootPackageInterface::class);
$rootPackage->getRequires()->willReturn([]);
- $rootPackage->getDevRequires()->willReturn([]);
- $rootPackage->setRequires(Argument::that(function ($arguments) {
- if (! is_array($arguments)) {
- return false;
- }
-
- if (! isset($arguments['extra-dependency-foo'])) {
- return false;
- }
-
- $argument = $arguments['extra-dependency-foo'];
-
- if (! $argument instanceof Link) {
- return false;
- }
-
- if ($argument->getTarget() !== 'extra-dependency-foo') {
- return false;
- }
-
- if ($argument->getConstraint()->getPrettyString() !== '17.0.1-dev') {
- return false;
- }
-
- if ($argument->getDescription() !== 'requires') {
- return false;
- }
-
- return true;
- }))->shouldBeCalled();
+ $rootPackage->getDevRequires()->willReturn(['extra-dependency-foo' => $link->reveal()]);
$this->composer->getPackage()->willReturn($rootPackage);
- $this->composer->getConfig()->willReturn($config->reveal());
- $this->io->isInteractive()->willReturn(true);
- $this->io->askAndValidate(
- 'Enter the version of extra-dependency-foo to require'
- . ' (or leave blank to use the latest version): ',
- Argument::type('callable')
- )->willReturn('17.0.1-dev');
-
- $this->io->write(' Updating composer.json')->shouldBeCalled();
- $this->io->write('Updating root package')->shouldBeCalled();
- $this->io->write(' Running an update to install dependent packages')->shouldBeCalled();
-
- $this->setUpComposerInstaller(['extra-dependency-foo']);
- $this->setUpComposerJson();
-
- $this->assertNull($this->plugin->onPostPackage($event->reveal()));
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall([]);
}
- public function testInstallSingleDependencyOnPackageInstall()
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testInstallSingleDependency($operation)
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
+ $event = $this->getPackageEvent('some/component', [
'dependency' => [
'extra-dependency-foo',
],
- ]);
-
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
+ ], $operation);
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
+ $this->io->isInteractive()->willReturn(true)->shouldBeCalled();
+ $this->io->askAndValidate(Argument::any())->shouldNotBeCalled();
$config = $this->prophesize(Config::class);
$config->get('sort-packages')->willReturn(true);
@@ -366,35 +427,6 @@ public function testInstallSingleDependencyOnPackageInstall()
$rootPackage = $this->prophesize(RootPackageInterface::class);
$rootPackage->getRequires()->willReturn([]);
$rootPackage->getDevRequires()->willReturn([]);
- $rootPackage->setRequires(Argument::that(function ($arguments) {
- if (! is_array($arguments)) {
- return false;
- }
-
- if (! isset($arguments['extra-dependency-foo'])) {
- return false;
- }
-
- $argument = $arguments['extra-dependency-foo'];
-
- if (! $argument instanceof Link) {
- return false;
- }
-
- if ($argument->getTarget() !== 'extra-dependency-foo') {
- return false;
- }
-
- if ($argument->getConstraint()->getPrettyString() !== '17.0.1-dev') {
- return false;
- }
-
- if ($argument->getDescription() !== 'requires') {
- return false;
- }
-
- return true;
- }))->shouldBeCalled();
$this->composer->getPackage()->willReturn($rootPackage);
$this->composer->getConfig()->willReturn($config->reveal());
@@ -403,37 +435,40 @@ public function testInstallSingleDependencyOnPackageInstall()
$this->io->askAndValidate(
'Enter the version of extra-dependency-foo to require'
. ' (or leave blank to use the latest version): ',
- Argument::type('callable')
+ Argument::that(function ($arg) {
+ if (! is_callable($arg)) {
+ return false;
+ }
+
+ Assert::assertFalse($arg(0));
+ Assert::assertFalse($arg('0'));
+ Assert::assertFalse($arg(''));
+ Assert::assertFalse($arg(' '));
+ Assert::assertSame('1', $arg(' 1 '));
+ Assert::assertSame('1', $arg('1'));
+ Assert::assertSame('0.*', $arg('0.*'));
+
+ return true;
+ })
)->willReturn('17.0.1-dev');
- $this->io->write(' Updating composer.json')->shouldBeCalled();
- $this->io->write('Updating root package')->shouldBeCalled();
- $this->io->write(' Running an update to install dependent packages')->shouldBeCalled();
-
- $this->setUpComposerInstaller(['extra-dependency-foo']);
- $this->setUpComposerJson();
-
- $this->assertNull($this->plugin->onPostPackage($event->reveal()));
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall(['extra-dependency-foo' => '17.0.1-dev']);
}
- public function testInstallOneDependenciesWhenOneIsAlreadyInstalled()
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testInstallOneDependenciesWhenOneIsAlreadyInstalled($operation)
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
+ $event = $this->getPackageEvent('some/component', [
'dependency' => [
'extra-dependency-foo',
'extra-dependency-bar',
],
- ]);
-
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
-
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
+ ], $operation);
$config = $this->prophesize(Config::class);
$config->get('sort-packages')->willReturn(true);
@@ -445,39 +480,6 @@ public function testInstallOneDependenciesWhenOneIsAlreadyInstalled()
$rootPackage = $this->prophesize(RootPackageInterface::class);
$rootPackage->getRequires()->willReturn(['extra-dependency-bar' => $link->reveal()]);
$rootPackage->getDevRequires()->willReturn([]);
- $rootPackage->setRequires(Argument::that(function ($arguments) {
- if (! is_array($arguments)) {
- return false;
- }
-
- if (! isset($arguments['extra-dependency-foo'])) {
- return false;
- }
-
- if (! isset($arguments['extra-dependency-bar'])) {
- return false;
- }
-
- $argument = $arguments['extra-dependency-foo'];
-
- if (! $argument instanceof Link) {
- return false;
- }
-
- if ($argument->getTarget() !== 'extra-dependency-foo') {
- return false;
- }
-
- if ($argument->getConstraint()->getPrettyString() !== '17.0.1-dev') {
- return false;
- }
-
- if ($argument->getDescription() !== 'requires') {
- return false;
- }
-
- return true;
- }))->shouldBeCalled();
$this->composer->getPackage()->willReturn($rootPackage);
$this->composer->getConfig()->willReturn($config->reveal());
@@ -489,79 +491,24 @@ public function testInstallOneDependenciesWhenOneIsAlreadyInstalled()
Argument::type('callable')
)->willReturn('17.0.1-dev');
- $this->io->write(' Updating composer.json')->shouldBeCalled();
- $this->io->write('Updating root package')->shouldBeCalled();
- $this->io->write(' Running an update to install dependent packages')->shouldBeCalled();
-
- $this->setUpComposerInstaller(['extra-dependency-foo']);
- $this->setUpComposerJson();
-
- $this->assertNull($this->plugin->onPostPackage($event->reveal()));
-
- $json = file_get_contents(vfsStream::url('project/composer.json'));
- $composer = json_decode($json, true);
- $this->assertTrue(isset($composer['require']['extra-dependency-foo']));
- $this->assertSame('17.0.1-dev', $composer['require']['extra-dependency-foo']);
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall(['extra-dependency-foo' => '17.0.1-dev']);
}
- public function testInstallSingleDependencyAndAutomaticallyChooseLatestVersion()
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testInstallSingleDependencyAndAutomaticallyChooseLatestVersion($operation)
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
+ $event = $this->getPackageEvent('some/component', [
'dependency' => [
'extra-dependency-foo',
],
- ]);
-
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
-
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
-
- $config = $this->prophesize(Config::class);
- $config->get('sort-packages')->willReturn(true);
- $config->get(Argument::any())->willReturn(null);
-
- $rootPackage = $this->prophesize(RootPackageInterface::class);
- $rootPackage->getRequires()->willReturn([]);
- $rootPackage->getDevRequires()->willReturn([]);
- $rootPackage->setRequires(Argument::that(function ($arguments) {
- if (! is_array($arguments)) {
- return false;
- }
-
- if (! isset($arguments['extra-dependency-foo'])) {
- return false;
- }
+ ], $operation);
- $argument = $arguments['extra-dependency-foo'];
-
- if (! $argument instanceof Link) {
- return false;
- }
-
- if ($argument->getTarget() !== 'extra-dependency-foo') {
- return false;
- }
-
- if ($argument->getConstraint()->getPrettyString() !== '13.4.2') {
- return false;
- }
-
- if ($argument->getDescription() !== 'requires') {
- return false;
- }
-
- return true;
- }))->shouldBeCalled();
- $rootPackage->getMinimumStability()->willReturn('stable');
-
- $this->composer->getPackage()->willReturn($rootPackage);
- $this->composer->getConfig()->willReturn($config->reveal());
+ $this->setUpRootPackage();
$this->io->isInteractive()->willReturn(true);
$this->io->askAndValidate(
@@ -570,14 +517,8 @@ public function testInstallSingleDependencyAndAutomaticallyChooseLatestVersion()
Argument::type('callable')
)->willReturn(false);
- $this->io->write(' Updating composer.json')->shouldBeCalled();
- $this->io->write('Updating root package')->shouldBeCalled();
- $this->io->write(' Running an update to install dependent packages')->shouldBeCalled();
$this->io->write('Using version 13.4.2 for extra-dependency-foo')->shouldBeCalled();
- $this->setUpComposerInstaller(['extra-dependency-foo']);
- $this->setUpComposerJson();
-
$package = $this->prophesize(PackageInterface::class);
$versionSelector = $this->prophesize(VersionSelector::class);
@@ -591,43 +532,24 @@ public function testInstallSingleDependencyAndAutomaticallyChooseLatestVersion()
$this->setUpVersionSelector($versionSelector->reveal());
$this->setUpPool();
- $this->assertNull($this->plugin->onPostPackage($event->reveal()));
-
- $json = file_get_contents(vfsStream::url('project/composer.json'));
- $composer = json_decode($json, true);
- $this->assertTrue(isset($composer['require']['extra-dependency-foo']));
- $this->assertSame('13.4.2', $composer['require']['extra-dependency-foo']);
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall(['extra-dependency-foo' => '13.4.2']);
}
- public function testInstallSingleDependencyAndAutomaticallyChooseLatestVersionNotFoundMatchingPackage()
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testInstallSingleDependencyAndAutomaticallyChooseLatestVersionNotFoundMatchingPackage($operation)
{
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
+ $event = $this->getPackageEvent('some/component', [
'dependency' => [
'extra-dependency-foo',
],
- ]);
+ ], $operation);
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
-
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
-
- $config = $this->prophesize(Config::class);
- $config->get('sort-packages')->willReturn(true);
- $config->get(Argument::any())->willReturn(null);
-
- $rootPackage = $this->prophesize(RootPackageInterface::class);
- $rootPackage->getRequires()->willReturn([]);
- $rootPackage->getDevRequires()->willReturn([]);
- $rootPackage->getMinimumStability()->willReturn('stable');
-
- $this->composer->getPackage()->willReturn($rootPackage);
- $this->composer->getConfig()->willReturn($config->reveal());
+ $this->setUpRootPackage([], [], 'stable-foo');
$this->io->isInteractive()->willReturn(true);
$this->io->askAndValidate(
@@ -636,22 +558,27 @@ public function testInstallSingleDependencyAndAutomaticallyChooseLatestVersionNo
Argument::type('callable')
)->willReturn(false);
- $this->setUpComposerJson();
-
$versionSelector = $this->prophesize(VersionSelector::class);
- $versionSelector->findBestCandidate('extra-dependency-foo', null, null, 'stable')->willReturn(null);
+ $versionSelector->findBestCandidate('extra-dependency-foo', null, null, 'stable')
+ ->willReturn(null)
+ ->shouldBeCalledTimes(1);
$this->setUpVersionSelector($versionSelector->reveal());
$this->setUpPool();
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage(
- 'Could not find package extra-dependency-foo at any version for your minimum-stability'
+ 'Could not find package extra-dependency-foo at any version for your minimum-stability (stable-foo)'
);
- $this->plugin->onPostPackage($event->reveal());
+ $this->plugin->onPostPackage($event);
}
- public function testUpdateComposerWithCurrentlyInstalledVersion()
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testUpdateComposerWithCurrentlyInstalledVersion($operation)
{
$installedPackage = $this->prophesize(PackageInterface::class);
$installedPackage->getName()->willReturn('extra-dependency-foo');
@@ -660,83 +587,401 @@ public function testUpdateComposerWithCurrentlyInstalledVersion()
$this->localRepository->getPackages()->willReturn([$installedPackage->reveal()]);
$this->plugin->activate($this->composer->reveal(), $this->io->reveal());
- /** @var PackageInterface|ObjectProphecy $package */
- $package = $this->prophesize(PackageInterface::class);
- $package->getName()->willReturn('some/component');
- $package->getExtra()->willReturn([
+ $event = $this->getPackageEvent('some/component', [
'dependency' => [
'extra-dependency-foo',
],
+ ], $operation);
+
+ $this->setUpRootPackage();
+
+ $this->io->isInteractive()->willReturn(true);
+ $this->io
+ ->write(
+ 'Added package extra-dependency-foo to composer.json with constraint'
+ . ' ^0.5.1; to upgrade, run composer require extra-dependency-foo:VERSION'
+ )
+ ->shouldBeCalled();
+
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall(['extra-dependency-foo' => '^0.5.1']);
+ }
+
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testDoNotInstallAskTwiceForTheSamePackage($operation)
+ {
+ $this->injectPackages([
+ 'extra-package' => '^1.0.1',
]);
- $operation = $this->prophesize(InstallOperation::class);
- $operation->getPackage()->willReturn($package->reveal());
+ $event = $this->getPackageEvent('some/component', [
+ 'dependency' => [
+ 'extra-package',
+ ],
+ ], $operation);
- $event = $this->prophesize(PackageEvent::class);
- $event->isDevMode()->willReturn(true);
- $event->getOperation()->willReturn($operation->reveal());
+ $this->composer->getPackage()->shouldNotBeCalled();
- $config = $this->prophesize(Config::class);
- $config->get('sort-packages')->willReturn(true);
- $config->get(Argument::any())->willReturn(null);
+ $this->io->isInteractive()->willReturn(true);
+ $this->io
+ ->askAndValidate(Argument::any(), Argument::type('callable'))
+ ->shouldNotBeCalled();
- $rootPackage = $this->prophesize(RootPackageInterface::class);
- $rootPackage->getRequires()->willReturn([]);
- $rootPackage->getDevRequires()->willReturn([]);
- $rootPackage->setRequires(Argument::that(function ($arguments) {
- if (! is_array($arguments)) {
- return false;
- }
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall([
+ 'extra-package' => '^1.0.1',
+ ]);
+ }
- if (! isset($arguments['extra-dependency-foo'])) {
- return false;
- }
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testDependencyOrChoosePackageToInstall($operation)
+ {
+ $event = $this->getPackageEvent('some/component', [
+ 'dependency-or' => [
+ 'My question foo bar baz' => [
+ 'extra-dependency-foo',
+ 'extra-dependency-bar',
+ ],
+ ],
+ ], $operation);
- $argument = $arguments['extra-dependency-foo'];
+ $this->setUpRootPackage();
- if (! $argument instanceof Link) {
- return false;
- }
+ $this->io->isInteractive()->willReturn(true);
+ $this->io
+ ->askAndValidate(
+ Argument::that(function ($arg) {
+ if (! is_array($arg)) {
+ return false;
+ }
+
+ Assert::assertCount(4, $arg);
+ Assert::assertSame('My question foo bar baz' . "\n", $arg[0]);
+ Assert::assertSame(' [1] extra-dependency-foo' . "\n", $arg[1]);
+ Assert::assertSame(' [2] extra-dependency-bar' . "\n", $arg[2]);
+ Assert::assertSame(' Make your selection: ', $arg[3]);
+
+ return true;
+ }),
+ Argument::that(function ($arg) {
+ if (! is_callable($arg)) {
+ return false;
+ }
+
+ Assert::assertSame('extra-dependency-foo', $arg(1));
+ Assert::assertSame('extra-dependency-foo', $arg('1'));
+ Assert::assertSame('extra-dependency-foo', $arg(' 1'));
+ Assert::assertSame('extra-dependency-foo', $arg('1.0'));
+ Assert::assertSame('extra-dependency-bar', $arg(2));
+ Assert::assertSame('extra-dependency-bar', $arg('2'));
+ Assert::assertSame('extra-dependency-bar', $arg(' 2'));
+ Assert::assertSame('extra-dependency-bar', $arg('2.2'));
+ Assert::assertNull($arg(''));
+ Assert::assertNull($arg(' '));
+ Assert::assertNull($arg('a'));
+ Assert::assertNull($arg('1a'));
+ Assert::assertNull($arg(' a'));
+ Assert::assertNull($arg(0));
+ Assert::assertNull($arg(3));
+
+ return true;
+ })
+ )
+ ->willReturn('', 'extra-dependency-bar')
+ ->shouldBeCalledTimes(2);
+ $this->io
+ ->askAndValidate(
+ 'Enter the version of extra-dependency-bar to require'
+ . ' (or leave blank to use the latest version): ',
+ Argument::type('callable')
+ )
+ ->willReturn(false)
+ ->shouldBeCalledTimes(1);
- if ($argument->getTarget() !== 'extra-dependency-foo') {
- return false;
- }
+ $this->io->write('Using version 13.4.2 for extra-dependency-bar')->shouldBeCalled();
- if ($argument->getConstraint()->getPrettyString() !== '^0.5.1') {
- return false;
- }
+ $package = $this->prophesize(PackageInterface::class);
- if ($argument->getDescription() !== 'requires') {
- return false;
- }
+ $versionSelector = $this->prophesize(VersionSelector::class);
+ $versionSelector->findBestCandidate('extra-dependency-bar', null, null, 'stable')
+ ->willReturn($package->reveal())
+ ->shouldBeCalled();
+ $versionSelector->findRecommendedRequireVersion($package->reveal())
+ ->willReturn('13.4.2')
+ ->shouldBeCalled();
- return true;
- }))->shouldBeCalled();
- $rootPackage->getMinimumStability()->willReturn('stable');
+ $this->setUpVersionSelector($versionSelector->reveal());
+ $this->setUpPool();
- $this->composer->getPackage()->willReturn($rootPackage);
- $this->composer->getConfig()->willReturn($config->reveal());
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall(['extra-dependency-bar' => '13.4.2']);
+ }
+
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testDependencyOrOnePackageIsAlreadyInstalledAndShouldBeAddedIntoRootComposer($operation)
+ {
+ $installedPackage = $this->prophesize(PackageInterface::class);
+ $installedPackage->getName()->willReturn('extra-dependency-baz');
+ $installedPackage->getPrettyVersion()->willReturn('3.7.1');
+
+ $this->localRepository->getPackages()->willReturn([$installedPackage->reveal()]);
+ $this->plugin->activate($this->composer->reveal(), $this->io->reveal());
+
+ $event = $this->getPackageEvent('some/component', [
+ 'dependency-or' => [
+ 'Choose something' => [
+ 'extra-dependency-bar',
+ 'extra-dependency-baz',
+ ],
+ ],
+ ], $operation);
+
+ $this->setUpRootPackage();
$this->io->isInteractive()->willReturn(true);
$this->io
->write(
- 'Added package extra-dependency-foo to composer.json with constraint'
- . ' ^0.5.1; to upgrade, run composer require extra-dependency-foo:VERSION'
+ 'Added package extra-dependency-baz to composer.json with constraint'
+ . ' ^3.7.1; to upgrade, run composer require extra-dependency-baz:VERSION'
)
->shouldBeCalled();
- $this->io->write(' Updating composer.json')->shouldBeCalled();
- $this->io->write('Updating root package')->shouldBeCalled();
- $this->io->write(' Running an update to install dependent packages')->shouldBeCalled();
+ $this->io
+ ->askAndValidate(Argument::any(), Argument::any())
+ ->shouldNotBeCalled();
- $this->setUpComposerInstaller(['extra-dependency-foo']);
- $this->setUpComposerJson();
+ $this->setUpPool();
- $this->assertNull($this->plugin->onPostPackage($event->reveal()));
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall(['extra-dependency-baz' => '^3.7.1']);
+ }
- $json = file_get_contents(vfsStream::url('project/composer.json'));
- $composer = json_decode($json, true);
- $this->assertTrue(isset($composer['require']['extra-dependency-foo']));
- $this->assertSame('^0.5.1', $composer['require']['extra-dependency-foo']);
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testDependencyOrOnePackageIsAlreadyInRootComposer($operation)
+ {
+ $event = $this->getPackageEvent('some/component', [
+ 'dependency-or' => [
+ 'Choose something' => [
+ 'extra-dependency-foo',
+ 'extra-dependency-baz',
+ ],
+ ],
+ ], $operation);
+
+ $link = $this->prophesize(Link::class);
+ $link->getTarget()->willReturn('extra-dependency-bar');
+
+ $this->setUpRootPackage(['extra-dependency-foo' => $link]);
+
+ $this->io->isInteractive()->willReturn(true)->shouldBeCalledTimes(1);
+ $this->io
+ ->askAndValidate(Argument::any(), Argument::any())
+ ->shouldNotBeCalled();
+
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall([]);
+ }
+
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testDependencyOrWrongDefinitionThrowsException($operation)
+ {
+ $event = $this->getPackageEvent('some/component', [
+ 'dependency-or' => [
+ 'extra-dependency-foo',
+ 'extra-dependency-baz',
+ ],
+ ], $operation);
+
+ $this->io->isInteractive()->willReturn(true)->shouldBeCalledTimes(1);
+ $this->io
+ ->askAndValidate(Argument::any(), Argument::any())
+ ->shouldNotBeCalled();
+
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('You must provide at least two optional dependencies.');
+ $this->assertNull($this->plugin->onPostPackage($event));
+ }
+
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testIntegrationHandleDependencyAndDependencyOr($operation)
+ {
+ $event = $this->getPackageEvent('some/component', [
+ 'dependency' => [
+ 'extra-package-required',
+ ],
+ 'dependency-or' => [
+ 'Choose something' => [
+ 'extra-choose-one',
+ 'extra-choose-two',
+ 'extra-choose-three',
+ ],
+ ],
+ ], $operation);
+
+ $this->setUpRootPackage();
+
+ $this->io->isInteractive()->willReturn(true);
+ $this->io
+ ->askAndValidate(
+ Argument::that(function ($arg) {
+ if (! is_array($arg)) {
+ return false;
+ }
+
+ Assert::assertCount(5, $arg);
+ Assert::assertSame('Choose something' . "\n", $arg[0]);
+ Assert::assertSame(' [1] extra-choose-one' . "\n", $arg[1]);
+ Assert::assertSame(' [2] extra-choose-two' . "\n", $arg[2]);
+ Assert::assertSame(' [3] extra-choose-three' . "\n", $arg[3]);
+ Assert::assertSame(' Make your selection: ', $arg[4]);
+
+ return true;
+ }),
+ Argument::type('callable')
+ )
+ ->willReturn('extra-choose-two')
+ ->shouldBeCalledTimes(1);
+ $this->io
+ ->askAndValidate(
+ 'Enter the version of extra-package-required to require'
+ . ' (or leave blank to use the latest version): ',
+ Argument::type('callable')
+ )
+ ->willReturn(false)
+ ->shouldBeCalledTimes(1);
+ $this->io
+ ->askAndValidate(
+ 'Enter the version of extra-choose-two to require'
+ . ' (or leave blank to use the latest version): ',
+ Argument::type('callable')
+ )
+ ->willReturn(false)
+ ->shouldBeCalledTimes(1);
+
+ $this->io->write('Using version 3.9.1 for extra-package-required')->shouldBeCalled();
+ $this->io->write('Using version 2.1.5 for extra-choose-two')->shouldBeCalled();
+
+ $package1 = $this->prophesize(PackageInterface::class)->reveal();
+ $package2 = $this->prophesize(PackageInterface::class)->reveal();
+
+ $versionSelector = $this->prophesize(VersionSelector::class);
+ $versionSelector->findBestCandidate('extra-package-required', null, null, 'stable')
+ ->willReturn($package1)
+ ->shouldBeCalledTimes(1);
+ $versionSelector->findBestCandidate('extra-choose-two', null, null, 'stable')
+ ->willReturn($package2)
+ ->shouldBeCalledTimes(1);
+ $versionSelector->findRecommendedRequireVersion($package1)
+ ->willReturn('3.9.1')
+ ->shouldBeCalledTimes(1);
+ $versionSelector->findRecommendedRequireVersion($package2)
+ ->willReturn('2.1.5')
+ ->shouldBeCalledTimes(1);
+
+ $this->setUpVersionSelector($versionSelector->reveal());
+ $this->setUpPool();
+
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall([
+ 'extra-package-required' => '3.9.1',
+ 'extra-choose-two' => '2.1.5',
+ ]);
+ }
+
+ /**
+ * @dataProvider operation
+ *
+ * @param string $operation
+ */
+ public function testIntegrationDoNotAskWhenAlreadyChosen($operation)
+ {
+ $event = $this->getPackageEvent('some/component', [
+ 'dependency' => [
+ 'extra-package-required',
+ ],
+ 'dependency-or' => [
+ 'Choose something' => [
+ 'extra-choose-one',
+ 'extra-choose-two',
+ 'extra-choose-three',
+ 'extra-package-required',
+ ],
+ ],
+ ], $operation);
+
+ $this->setUpRootPackage();
+
+ $this->io->isInteractive()->willReturn(true);
+ $this->io
+ ->askAndValidate(
+ 'Enter the version of extra-package-required to require'
+ . ' (or leave blank to use the latest version): ',
+ Argument::type('callable')
+ )
+ ->willReturn('1.8.3')
+ ->shouldBeCalledTimes(1);
+
+ $this->assertNull($this->plugin->onPostPackage($event));
+ $this->assertPackagesToInstall([
+ 'extra-package-required' => '1.8.3',
+ ]);
+ }
+
+ private function assertSetRequiresArgument($name, $version, array $arguments)
+ {
+ if (! isset($arguments[$name])) {
+ return false;
+ }
+
+ $argument = $arguments[$name];
+
+ if (! $argument instanceof Link) {
+ return false;
+ }
+
+ if ($argument->getTarget() !== $name) {
+ return false;
+ }
+
+ if ($argument->getConstraint()->getPrettyString() !== $version) {
+ return false;
+ }
+
+ if ($argument->getDescription() !== 'requires') {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function assertPackagesToInstall(array $packagesToInstall)
+ {
+ $p = new ReflectionProperty($this->plugin, 'packagesToInstall');
+ $p->setAccessible(true);
+ self::assertSame($packagesToInstall, $p->getValue($this->plugin));
}
public function testComposerInstallerFactory()