diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php
index d14662518..db26fdfed 100644
--- a/src/Composer/Installer.php
+++ b/src/Composer/Installer.php
@@ -188,7 +188,12 @@ class Installer
// output suggestions
foreach ($this->suggestedPackages as $suggestion) {
- if (!$installedRepo->findPackages($suggestion['target'])) {
+ $target = $suggestion['target'];
+ if ($installedRepo->filterPackages(function (PackageInterface $package) use ($target) {
+ if (in_array($target, $package->getNames())) {
+ return false;
+ }
+ })) {
$this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
}
}
diff --git a/tests/Composer/Test/Fixtures/installer/suggest-installed.test b/tests/Composer/Test/Fixtures/installer/suggest-installed.test
new file mode 100644
index 000000000..f46102d0a
--- /dev/null
+++ b/tests/Composer/Test/Fixtures/installer/suggest-installed.test
@@ -0,0 +1,29 @@
+--TEST--
+Suggestions are not displayed for installed packages
+--COMPOSER--
+{
+ "repositories": [
+ {
+ "type": "package",
+ "package": [
+ { "name": "a/a", "version": "1.0.0", "suggest": { "b/b": "an obscure reason" } },
+ { "name": "b/b", "version": "1.0.0" }
+ ]
+ }
+ ],
+ "require": {
+ "a/a": "1.0.0",
+ "b/b": "1.0.0"
+ }
+}
+--RUN--
+install
+--EXPECT-OUTPUT--
+Loading composer repositories with package information
+Installing dependencies
+Writing lock file
+Generating autoload files
+
+--EXPECT--
+Installing a/a (1.0.0)
+Installing b/b (1.0.0)
\ No newline at end of file
diff --git a/tests/Composer/Test/Fixtures/installer/suggest-replaced.test b/tests/Composer/Test/Fixtures/installer/suggest-replaced.test
new file mode 100644
index 000000000..d1e8f6102
--- /dev/null
+++ b/tests/Composer/Test/Fixtures/installer/suggest-replaced.test
@@ -0,0 +1,29 @@
+--TEST--
+Suggestions are not displayed for packages if they are replaced
+--COMPOSER--
+{
+ "repositories": [
+ {
+ "type": "package",
+ "package": [
+ { "name": "a/a", "version": "1.0.0", "suggest": { "b/b": "an obscure reason" } },
+ { "name": "c/c", "version": "1.0.0", "replace": { "b/b": "1.0.0" } }
+ ]
+ }
+ ],
+ "require": {
+ "a/a": "1.0.0",
+ "b/b": "1.0.0"
+ }
+}
+--RUN--
+install
+--EXPECT-OUTPUT--
+Loading composer repositories with package information
+Installing dependencies
+Writing lock file
+Generating autoload files
+
+--EXPECT--
+Installing a/a (1.0.0)
+Installing c/c (1.0.0)
\ No newline at end of file
diff --git a/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test b/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test
new file mode 100644
index 000000000..d2ea37766
--- /dev/null
+++ b/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test
@@ -0,0 +1,27 @@
+--TEST--
+Suggestions are displayed
+--COMPOSER--
+{
+ "repositories": [
+ {
+ "type": "package",
+ "package": [
+ { "name": "a/a", "version": "1.0.0", "suggest": { "b/b": "an obscure reason" } }
+ ]
+ }
+ ],
+ "require": {
+ "a/a": "1.0.0"
+ }
+}
+--RUN--
+install
+--EXPECT-OUTPUT--
+Loading composer repositories with package information
+Installing dependencies
+a/a suggests installing b/b (an obscure reason)
+Writing lock file
+Generating autoload files
+
+--EXPECT--
+Installing a/a (1.0.0)
\ No newline at end of file
diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php
index d4635f246..cca2fd95f 100644
--- a/tests/Composer/Test/InstallerTest.php
+++ b/tests/Composer/Test/InstallerTest.php
@@ -123,7 +123,7 @@ class InstallerTest extends TestCase
/**
* @dataProvider getIntegrationTests
*/
- public function testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $installedDev, $run, $expectLock, $expect)
+ public function testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $installedDev, $run, $expectLock, $expectOutput, $expect)
{
if ($condition) {
eval('$res = '.$condition.';');
@@ -226,6 +226,10 @@ class InstallerTest extends TestCase
$installationManager = $composer->getInstallationManager();
$this->assertSame($expect, implode("\n", $installationManager->getTrace()));
+
+ if ($expectOutput) {
+ $this->assertEquals($expectOutput, $output);
+ }
}
public function getIntegrationTests()
@@ -250,6 +254,7 @@ class InstallerTest extends TestCase
(?:--INSTALLED-DEV--\s*(?P'.$content.'))?\s*
--RUN--\s*(?P.*?)\s*
(?:--EXPECT-LOCK--\s*(?P'.$content.'))?\s*
+ (?:--EXPECT-OUTPUT--\s*(?P'.$content.'))?\s*
--EXPECT--\s*(?P.*?)\s*
$}xs';
@@ -279,6 +284,7 @@ class InstallerTest extends TestCase
if (!empty($match['expectLock'])) {
$expectLock = JsonFile::parseJson($match['expectLock']);
}
+ $expectOutput = $match['expectOutput'];
$expect = $match['expect'];
} catch (\Exception $e) {
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
@@ -287,7 +293,7 @@ class InstallerTest extends TestCase
die(sprintf('Test "%s" is not valid, did not match the expected format.', str_replace($fixturesDir.'/', '', $file)));
}
- $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $installedDev, $run, $expectLock, $expect);
+ $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $installedDev, $run, $expectLock, $expectOutput, $expect);
}
return $tests;