Repository: phpactor/phpactor (php)

Issue:
Navigator was try generate already exists source\unit_test classes
**New unit test with navigation into source**
![phpactor navigation issue](https://github.com/user-attachments/assets/b3afbde0-a574-4be9-af7a-83761f4c1628)

**Navigation into exist unit test**
![phpactor navigation issue2](https://github.com/user-attachments/assets/3ce774e1-dd8b-4efb-b6dd-454fd6f7a1a5)

**lsp-config**

```lua
phpactor = {
  root_dir = util.root_pattern('composer.json', '.git'),
  init_options = {
    ['language_server_php_cs_fixer.bin'] = '~/.local/bin/php-cs-fixer.phar',
    ['language_server_php_cs_fixer.enabled'] = true,
    ['language_server_phpstan.bin'] = '~/.local/bin/phpstan',
    ['language_server_phpstan.enabled'] = false,
    ['language_server_psalm.bin'] = '~/.local/bin/psalm',
    ['language_server_psalm.enabled'] = false,
    ['phpunit.enabled'] = true,
  }
}
```

**.phpactor**
```json
{
    "navigator.destinations": {
        "source": "src/<kernel>.php",
        "unit_test": "tests/Unit/<kernel>Test.php"
    },
    "navigator.autocreate": {
        "source": "default",
        "unit_test": "unit_test"
    },
    "code_transform.class_new.variants": {
        "source": "default",
        "unit_test": "phpunit_test"
    }
}
```


**phpactor config:dump**
```
$ phpactor conf:dump
Config files:

  [✖] /home/klobastov/.config/phpactor/phpactor.json
  [✖] /home/klobastov/.config/phpactor/phpactor.yml
  [✔] /home/klobastov/projects/alg/.phpactor.json
  [✖] /home/klobastov/projects/alg/.phpactor.yml

File path tokens:

  %project_id%: alg-c909b3
  %project_root%: /home/klobastov/projects/alg
  %cache%: /home/klobastov/.cache/phpactor
  %config%: /home/klobastov/.config/phpactor
  %data%: /home/klobastov/.local/share/phpactor
  %application_root%: phar:///home/klobastov/.local/share/nvim/mason/packages/phpactor/phpactor.phar
  %project_config%: /home/klobastov/projects/alg/.phpactor

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

{
    "console_dumper_default": "indented",
    "xdebug_disable": true,
    "command": "conf:dump",
    "core.min_memory_limit": 1610612736,
    "$schema": "",
    "class_to_file.project_root": "%project_root%",
    "class_to_file.brute_force_conversion": true,
    "code_transform.class_new.variants": {
        "source": "default",
        "unit_test": "phpunit_test"
    },
    "code_transform.template_paths": [
        "%project_config%\/templates",
        "%config%\/templates"
    ],
    "code_transform.indentation": "    ",
    "code_transform.refactor.generate_accessor.prefix": "",
    "code_transform.refactor.generate_accessor.upper_case_first": false,
    "code_transform.refactor.generate_mutator.prefix": "set",
    "code_transform.refactor.generate_mutator.upper_case_first": true,
    "code_transform.refactor.generate_mutator.fluent": false,
    "code_transform.import_globals": false,
    "code_transform.refactor.object_fill.hint": true,
    "code_transform.refactor.object_fill.named_parameters": true,
    "completion_worse.completor.doctrine_annotation.enabled": true,
    "completion_worse.completor.imported_names.enabled": true,
    "completion_worse.completor.worse_parameter.enabled": true,
    "completion_worse.completor.named_parameter.enabled": true,
    "completion_worse.completor.constructor.enabled": true,
    "completion_worse.completor.class_member.enabled": true,
    "completion_worse.completor.scf_class.enabled": true,
    "completion_worse.completor.local_variable.enabled": true,
    "completion_worse.completor.subscript.enabled": true,
    "completion_worse.completor.declared_function.enabled": true,
    "completion_worse.completor.declared_constant.enabled": true,
    "completion_worse.completor.declared_class.enabled": true,
    "completion_worse.completor.expression_name_search.enabled": true,
    "completion_worse.completor.use.enabled": true,
    "completion_worse.completor.attribute.enabled": true,
    "completion_worse.completor.class_like.enabled": true,
    "completion_worse.completor.type.enabled": true,
    "completion_worse.completor.keyword.enabled": true,
    "completion_worse.completor.docblock.enabled": true,
    "completion_worse.completor.constant.enabled": false,
    "completion_worse.completor.class.limit": 100,
    "completion_worse.name_completion_priority": "proximity",
    "completion_worse.snippets": true,
    "completion_worse.experimantal": false,
    "completion_worse.debug": false,
    "completion.dedupe": true,
    "completion.dedupe_match_fqn": true,
    "completion.limit": null,
    "completion.label_formatter": "helpful",
    "navigator.destinations": {
        "source": "src\/<kernel>.php",
        "unit_test": "tests\/Unit\/<kernel>Test.php"
    },
    "navigator.autocreate": {
        "source": "default",
        "unit_test": "unit_test"
    },
    "rpc.store_replay": false,
    "rpc.replay_path": "%cache%\/replay.json",
    "source_code_filesystem.project_root": "%project_root%",
    "language_server_code_transform.import_globals": false,
    "worse_reflection.enable_cache": true,
    "worse_reflection.cache_lifetime": 1,
    "worse_reflection.enable_context_location": true,
    "worse_reflection.cache_dir": "%cache%\/worse-reflection",
    "worse_reflection.stub_dir": "%application_root%\/vendor\/jetbrains\/phpstorm-stubs",
    "worse_reflection.diagnostics.undefined_variable.suggestion_levenshtein_disatance": 4,
    "file_path_resolver.project_root": "\/home\/klobastov\/projects\/alg",
    "file_path_resolver.app_name": "phpactor",
    "file_path_resolver.application_root": "phar:\/\/\/home\/klobastov\/.local\/share\/nvim\/mason\/packages\/phpactor\/phpactor.phar",
    "file_path_resolver.enable_cache": true,
    "file_path_resolver.enable_logging": true,
    "logging.enabled": false,
    "logging.fingers_crossed": false,
    "logging.path": "application.log",
    "logging.level": "warning",
    "logger.name": "logger",
    "logging.formatter": null,
    "composer.enable": true,
    "composer.autoloader_path": "%project_root%\/vendor\/autoload.php",
    "composer.autoload_deregister": true,
    "composer.class_maps_only": true,
    "console.verbosity": 32,
    "console.decorated": null,
    "php.version": null,
    "language_server.catch_errors": true,
    "language_server.enable_workspace": true,
    "language_server.session_parameters": {
        "language_server.method_alias_map": {
            "indexer\/reindex": "phpactor\/indexer\/reindex",
            "session\/dumpConfig": "phpactor\/debug\/config",
            "service\/running": "phpactor\/service\/running",
            "system\/status": "phpactor\/stats"
        },
        "worse_reflection.enable_context_location": false,
        "class_to_file.brute_force_conversion": false,
        "completion_worse.completor.scf_class.enabled": false,
        "completion_worse.completor.declared_class.enabled": false,
        "completion_worse.completor.declared_constant.enabled": false,
        "completion_worse.completor.declared_function.enabled": false
    },
    "language_server.method_alias_map": [],
    "language_server.diagnostic_sleep_time": 1000,
    "language_server.diagnostics_on_update": true,
    "language_server.diagnostics_on_save": true,
    "language_server.diagnostics_on_open": true,
    "language_server.diagnostic_providers": null,
    "language_server.diagnostic_outsource": true,
    "language_server.file_events": true,
    "language_server.file_event_globs": [
        "**\/*.php"
    ],
    "language_server.profile": false,
    "language_server.trace": false,
    "language_server.shutdown_grace_period": 200,
    "language_server.phpactor_bin": "\/home\/klobastov\/.local\/share\/nvim\/mason\/packages\/phpactor\/phpactor.phar",
    "language_server.self_destruct_timeout": 2500,
    "language_server.diagnostic_outsource_timeout": 5,
    "language_server_completion.trim_leading_dollar": false,
    "language_server_reference_reference_finder.reference_timeout": 60,
    "language_server_worse_reflection.workspace_index.update_interval": 100,
    "language_server_worse_reflection.inlay_hints.enable": false,
    "language_server_worse_reflection.inlay_hints.types": false,
    "language_server_worse_reflection.inlay_hints.params": true,
    "language_server_worse_reflection.diagnostics.enable": true,
    "language_server_indexer.workspace_symbol_search_limit": 250,
    "language_server_indexer.reindex_timeout": 300,
    "language_server_code_transform.import_name.report_non_existing_names": true,
    "language_server_configuration.auto_config": true,
    "indexer.enabled_watchers": [
        "lsp",
        "inotify",
        "find",
        "php"
    ],
    "indexer.index_path": "%cache%\/index\/%project_id%",
    "indexer.include_patterns": [
        "\/**\/*.php",
        "\/**\/*.phar"
    ],
    "indexer.exclude_patterns": [
        "\/vendor\/**\/Tests\/**\/*",
        "\/vendor\/**\/tests\/**\/*",
        "\/vendor\/composer\/**\/*"
    ],
    "indexer.stub_paths": [
        "phar:\/\/\/home\/klobastov\/.local\/share\/nvim\/mason\/packages\/phpactor\/phpactor.phar\/vendor\/jetbrains\/phpstorm-stubs"
    ],
    "indexer.poll_time": 5000,
    "indexer.buffer_time": 500,
    "indexer.follow_symlinks": false,
    "indexer.project_root": "%project_root%",
    "indexer.reference_finder.deep": true,
    "indexer.implementation_finder.deep": true,
    "indexer.supported_extensions": [
        "php",
        "phar"
    ],
    "object_renderer.template_paths.markdown": [
        "%project_config%\/templates\/markdown",
        "%config%\/templates\/markdown"
    ],
    "language_server_phpstan.enabled": false,
    "language_server_phpstan.bin": "%project_root%\/vendor\/bin\/phpstan",
    "language_server_phpstan.level": null,
    "language_server_phpstan.config": null,
    "language_server_phpstan.mem_limit": null,
    "language_server_psalm.enabled": false,
    "language_server_psalm.bin": "%project_root%\/vendor\/bin\/psalm",
    "language_server_psalm.show_info": true,
    "language_server_psalm.use_cache": true,
    "language_server_psalm.error_level": null,
    "language_server_psalm.threads": 1,
    "language_server_psalm.timeout": 15,
    "language_server_php_cs_fixer.enabled": false,
    "language_server_php_cs_fixer.bin": "%project_root%\/vendor\/bin\/php-cs-fixer",
    "language_server_php_cs_fixer.env": {
        "XDEBUG_MODE": "off",
        "PHP_CS_FIXER_IGNORE_ENV": true
    },
    "language_server_php_cs_fixer.show_diagnostics": true,
    "language_server_php_cs_fixer.config": null,
    "php_code_sniffer.enabled": false,
    "php_code_sniffer.bin": "%project_root%\/vendor\/bin\/phpcs",
    "php_code_sniffer.env": {
        "XDEBUG_MODE": "off"
    },
    "php_code_sniffer.show_diagnostics": true,
    "php_code_sniffer.args": [],
    "php_code_sniffer.cwd": null,
    "blackfire.enabled": false,
    "prophecy.enabled": false,
    "behat.enabled": false,
    "behat.config_path": "%project_root%\/behat.yml",
    "behat.symfony.di_xml_path": null,
    "symfony.enabled": false,
    "symfony.xml_path": "%project_root%\/var\/cache\/dev\/App_KernelDevDebugContainer.xml",
    "completion_worse.completor.symfony.enabled": true,
    "public_services_only": false,
    "phpunit.enabled": false,
    "container.extension_classes": [
        "Phpactor\\Extension\\Core\\CoreExtension",
        "Phpactor\\Extension\\ClassToFileExtra\\ClassToFileExtraExtension",
        "Phpactor\\Extension\\ClassToFile\\ClassToFileExtension",
        "Phpactor\\Extension\\ClassMover\\ClassMoverExtension",
        "Phpactor\\ClassMover\\Extension\\ClassMoverExtension",
        "Phpactor\\Extension\\CodeTransform\\CodeTransformExtension",
        "Phpactor\\Extension\\CodeTransformExtra\\CodeTransformExtraExtension",
        "Phpactor\\Extension\\CompletionExtra\\CompletionExtraExtension",
        "Phpactor\\Extension\\CompletionWorse\\CompletionWorseExtension",
        "Phpactor\\Extension\\Completion\\CompletionExtension",
        "Phpactor\\Extension\\CompletionRpc\\CompletionRpcExtension",
        "Phpactor\\Extension\\Navigation\\NavigationExtension",
        "Phpactor\\Extension\\ContextMenu\\ContextMenuExtension",
        "Phpactor\\Extension\\Rpc\\RpcExtension",
        "Phpactor\\Extension\\SourceCodeFilesystemExtra\\SourceCodeFilesystemExtraExtension",
        "Phpactor\\Extension\\SourceCodeFilesystem\\SourceCodeFilesystemExtension",
        "Phpactor\\Extension\\WorseReflection\\WorseReflectionExtension",
        "Phpactor\\Extension\\WorseReflectionExtra\\WorseReflectionExtraExtension",
        "Phpactor\\Extension\\WorseReflectionAnalyse\\WorseReflectionAnalyseExtension",
        "Phpactor\\Extension\\FilePathResolver\\FilePathResolverExtension",
        "Phpactor\\Extension\\Logger\\LoggingExtension",
        "Phpactor\\Extension\\ComposerAutoloader\\ComposerAutoloaderExtension",
        "Phpactor\\Extension\\Console\\ConsoleExtension",
        "Phpactor\\Extension\\WorseReferenceFinder\\WorseReferenceFinderExtension",
        "Phpactor\\Extension\\ReferenceFinderRpc\\ReferenceFinderRpcExtension",
        "Phpactor\\Extension\\ReferenceFinder\\ReferenceFinderExtension",
        "Phpactor\\Extension\\Php\\PhpExtension",
        "Phpactor\\Extension\\Configuration\\ConfigurationExtension",
        "Phpactor\\Extension\\ComposerInspector\\ComposerInspectorExtension",
        "Phpactor\\Extension\\LanguageServer\\LanguageServerExtension",
        "Phpactor\\Extension\\LanguageServerCompletion\\LanguageServerCompletionExtension",
        "Phpactor\\Extension\\LanguageServerReferenceFinder\\LanguageServerReferenceFinderExtension",
        "Phpactor\\Extension\\LanguageServerWorseReflection\\LanguageServerWorseReflectionExtension",
        "Phpactor\\Extension\\LanguageServerIndexer\\LanguageServerIndexerExtension",
        "Phpactor\\Extension\\LanguageServerHover\\LanguageServerHoverExtension",
        "Phpactor\\Extension\\LanguageServerBridge\\LanguageServerBridgeExtension",
        "Phpactor\\Extension\\LanguageServerCodeTransform\\LanguageServerCodeTransformExtension",
        "Phpactor\\Extension\\LanguageServerSymbolProvider\\LanguageServerSymbolProviderExtension",
        "Phpactor\\Extension\\LanguageServerSelectionRange\\LanguageServerSelectionRangeExtension",
        "Phpactor\\Extension\\LanguageServerDiagnostics\\LanguageServerDiagnosticsExtension",
        "Phpactor\\Extension\\LanguageServerRename\\LanguageServerRenameExtension",
        "Phpactor\\Extension\\LanguageServerRename\\LanguageServerRenameWorseExtension",
        "Phpactor\\Extension\\LanguageServerConfiguration\\LanguageServerConfigurationExtension",
        "Phpactor\\Indexer\\Extension\\IndexerExtension",
        "Phpactor\\Extension\\ObjectRenderer\\ObjectRendererExtension",
        "Phpactor\\Extension\\LanguageServerPhpstan\\LanguageServerPhpstanExtension",
        "Phpactor\\Extension\\LanguageServerPhpstan\\LanguageServerPhpstanSuggestExtension",
        "Phpactor\\Extension\\LanguageServerPsalm\\LanguageServerPsalmExtension",
        "Phpactor\\Extension\\LanguageServerPsalm\\LanguageServerPsalmSuggestExtension",
        "Phpactor\\Extension\\LanguageServerPhpCsFixer\\LanguageServerPhpCsFixerExtension",
        "Phpactor\\Extension\\LanguageServerPhpCsFixer\\LanguageServerPhpCsFixerSuggestExtension",
        "Phpactor\\Extension\\PhpCodeSniffer\\PhpCodeSnifferExtension",
        "Phpactor\\Extension\\PhpCodeSniffer\\PhpCodeSnifferSuggestExtension",
        "Phpactor\\Extension\\LanguageServerBlackfire\\LanguageServerBlackfireExtension",
        "Phpactor\\Extension\\Prophecy\\ProphecyExtension",
        "Phpactor\\Extension\\Prophecy\\ProphecySuggestExtension",
        "Phpactor\\Extension\\Behat\\BehatExtension",
        "Phpactor\\Extension\\Behat\\BehatSuggestExtension",
        "Phpactor\\Extension\\Symfony\\SymfonyExtension",
        "Phpactor\\Extension\\Symfony\\SymfonySuggestExtension",
        "Phpactor\\Extension\\PHPUnit\\PHPUnitExtension",
        "Phpactor\\Extension\\Debug\\DebugExtension"
    ]
}


```

Pull request:
Navigator: Fix attempt to create existing destination
Remove false-positive file creation attempt by resolving absolute path of destination. Introduce a rough set of tests for that, for further refinement.

Fix #2758

Patch:
diff --git a/lib/Extension/Navigation/Application/Navigator.php b/lib/Extension/Navigation/Application/Navigator.php
index 2d5853bd..9dd73c0a 100644
--- a/lib/Extension/Navigation/Application/Navigator.php
+++ b/lib/Extension/Navigation/Application/Navigator.php
@@ -5,22 +5,26 @@ namespace Phpactor\Extension\Navigation\Application;
 use Phpactor\Extension\Navigation\Navigator\Navigator as NavigatorInterface;
 use RuntimeException;
 use Phpactor\Extension\CodeTransformExtra\Application\ClassNew;
+use Symfony\Component\Filesystem\Path;
 
 class Navigator
 {
+    /** @param array<string, string> $autoCreateConfig */
     public function __construct(
         private NavigatorInterface $navigator,
         private ClassNew $classNew,
-        private array $autoCreateConfig
+        private array $autoCreateConfig,
+        private string $absolutePath
     ) {
     }
 
-    public function destinationsFor(string $path)
+    /** @return array<string, string> */
+    public function destinationsFor(string $path): array
     {
         return $this->navigator->destinationsFor($path);
     }
 
-    public function canCreateNew(string $path, string $destinationName)
+    public function canCreateNew(string $path, string $destinationName): bool
     {
         $destination = $this->destination($path, $destinationName);
 
@@ -38,7 +42,7 @@ class Navigator
         $this->classNew->generate($destination, $variant);
     }
 
-    private function destination(string $path, string $destinationName)
+    private function destination(string $path, string $destinationName): string
     {
         $destinations = $this->destinationsFor($path);
 
@@ -50,10 +54,10 @@ class Navigator
             ));
         }
 
-        return $destinations[$destinationName];
+        return Path::makeAbsolute($destinations[$destinationName], $this->absolutePath);
     }
 
-    private function variant(string $destinationName)
+    private function variant(string $destinationName): string
     {
         if (!isset($this->autoCreateConfig[$destinationName])) {
             throw new RuntimeException(sprintf(
diff --git a/lib/Extension/Navigation/NavigationExtension.php b/lib/Extension/Navigation/NavigationExtension.php
index 187da88e..49270a1b 100644
--- a/lib/Extension/Navigation/NavigationExtension.php
+++ b/lib/Extension/Navigation/NavigationExtension.php
@@ -2,16 +2,17 @@
 
 namespace Phpactor\Extension\Navigation;
 
+use Phpactor\Container\Container;
 use Phpactor\Container\ContainerBuilder;
 use Phpactor\Container\Extension;
-use Phpactor\Extension\WorseReflection\WorseReflectionExtension;
-use Phpactor\MapResolver\Resolver;
-use Phpactor\Container\Container;
+use Phpactor\Extension\FilePathResolver\FilePathResolverExtension;
 use Phpactor\Extension\Navigation\Application\Navigator;
-use Phpactor\Extension\Navigation\Navigator\ChainNavigator;
 use Phpactor\Extension\Navigation\Handler\NavigateHandler;
+use Phpactor\Extension\Navigation\Navigator\ChainNavigator;
 use Phpactor\Extension\Navigation\Navigator\PathFinderNavigator;
 use Phpactor\Extension\Navigation\Navigator\WorseReflectionNavigator;
+use Phpactor\Extension\WorseReflection\WorseReflectionExtension;
+use Phpactor\MapResolver\Resolver;
 use Phpactor\PathFinder\PathFinder;
 
 class NavigationExtension implements Extension
@@ -39,15 +40,20 @@ class NavigationExtension implements Extension
 
     private function registerPathFinder(ContainerBuilder $container): void
     {
-        $container->register(self::SERVICE_PATH_FINDER, function (Container $container) {
-            return PathFinder::fromDestinations($container->getParameter(self::PATH_FINDER_DESTINATIONS));
-        });
+        $container->register(
+            self::SERVICE_PATH_FINDER,
+            fn (Container $container) => PathFinder::fromAbsoluteDestinations(
+                $container->parameter(FilePathResolverExtension::PARAM_PROJECT_ROOT)->string(),
+                $container->parameter(self::PATH_FINDER_DESTINATIONS)->value() // @phpstan-ignore argument.type
+            )
+        );
 
         $container->register('application.navigator', function (Container $container) {
             return new Navigator(
                 $container->get('navigation.navigator.chain'),
                 $container->get('application.class_new'),
-                $container->getParameter(self::NAVIGATOR_AUTOCREATE)
+                $container->parameter(self::NAVIGATOR_AUTOCREATE)->value(), // @phpstan-ignore argument.type
+                $container->parameter(FilePathResolverExtension::PARAM_PROJECT_ROOT)->string()
             );
         });
     }


Test patch:
diff --git a/lib/Extension/Navigation/Tests/Application/NavigatorTest.php b/lib/Extension/Navigation/Tests/Application/NavigatorTest.php
new file mode 100644
index 00000000..62fc92a9
--- /dev/null
+++ b/lib/Extension/Navigation/Tests/Application/NavigatorTest.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Phpactor\Extension\Navigation\Tests\Application;
+
+use Phpactor\Extension\Navigation\Tests\IntegrationTestCase;
+use Phpactor\Extension\Navigation\Application\Navigator;
+use Phpactor\Extension\Navigation\NavigationExtension;
+
+class NavigatorTest extends IntegrationTestCase
+{
+    public function testProvidesCorrectDestination(): void
+    {
+        $navigator = $this->navigator();
+
+        $this->workspace->put('src/Kernel.php', '<?php');
+        $result = $navigator->destinationsFor($this->workspace->path('src/Kernel.php'));
+
+        self::assertSame(['unit_test' => 'tests/Unit/KernelTest.php'], $result);
+    }
+
+    public function testCanCreate(): void
+    {
+        $navigator = $this->navigator();
+
+        $this->workspace->put('src/Kernel.php', '<?php');
+        $result = $navigator->canCreateNew($this->workspace->path('src/Kernel.php'), 'unit_test');
+
+        self::assertTrue($result);
+    }
+
+    public function testNoNeedToCreate(): void
+    {
+        $navigator = $this->navigator();
+
+        $this->workspace->put('src/Kernel.php', '<?php');
+        $this->workspace->put('tests/Unit/KernelTest.php', '<?php');
+        $result = $navigator->canCreateNew($this->workspace->path('src/Kernel.php'), 'unit_test');
+
+        self::assertFalse($result);
+    }
+
+    /**
+     * @param array<string,string> $destinations
+     * @param array<string,string> $autocreate
+     */
+    private function navigator(
+        array $destinations = ['source' => 'src/<kernel>.php', 'unit_test' => 'tests/Unit/<kernel>Test.php'],
+        array $autocreate = ['source' => 'source', 'unit_test' => 'unit_test'],
+    ): Navigator {
+        $container = $this->container([
+          NavigationExtension::PATH_FINDER_DESTINATIONS => $destinations,
+          NavigationExtension::NAVIGATOR_AUTOCREATE  => $autocreate
+        ]);
+        /** @var Navigator */
+        return $container->get('application.navigator');
+    }
+}
diff --git a/lib/Extension/Navigation/Tests/IntegrationTestCase.php b/lib/Extension/Navigation/Tests/IntegrationTestCase.php
new file mode 100644
index 00000000..6342dc02
--- /dev/null
+++ b/lib/Extension/Navigation/Tests/IntegrationTestCase.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Phpactor\Extension\Navigation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Phpactor\Container\Container;
+use Phpactor\Container\PhpactorContainer;
+use Phpactor\Extension\ClassToFile\ClassToFileExtension;
+use Phpactor\Extension\CodeTransformExtra\CodeTransformExtraExtension;
+use Phpactor\Extension\CodeTransform\CodeTransformExtension;
+use Phpactor\Extension\ComposerAutoloader\ComposerAutoloaderExtension;
+use Phpactor\Extension\Core\CoreExtension;
+use Phpactor\Extension\FilePathResolver\FilePathResolverExtension;
+use Phpactor\Extension\Logger\LoggingExtension;
+use Phpactor\Extension\Navigation\NavigationExtension;
+use Phpactor\Extension\Php\PhpExtension;
+use Phpactor\Extension\SourceCodeFilesystem\SourceCodeFilesystemExtension;
+use Phpactor\Extension\WorseReflection\WorseReflectionExtension;
+use Phpactor\TestUtils\Workspace;
+
+abstract class IntegrationTestCase extends TestCase
+{
+    protected Workspace $workspace;
+
+    public function setUp(): void
+    {
+        $this->workspace = $this->workspace();
+        $this->workspace->reset();
+    }
+
+    /**
+     * @param array{
+     * 'navigator.destinations': array<string, string>,
+     * 'navigator.autocreate': array<string, string>,
+     * } $config
+     */
+    protected function container(array $config): Container
+    {
+        $key = serialize($config);
+        static $container = [];
+
+        if (isset($container[$key])) {
+            return $container[$key];
+        }
+
+        $container[$key] = PhpactorContainer::fromExtensions([
+            CodeTransformExtension::class,
+            CodeTransformExtraExtension::class,
+            PhpExtension::class,
+            CoreExtension::class,
+            NavigationExtension::class,
+            LoggingExtension::class,
+            SourceCodeFilesystemExtension::class,
+            ClassToFileExtension::class,
+            ComposerAutoloaderExtension::class,
+            FilePathResolverExtension::class,
+            WorseReflectionExtension::class,
+        ], array_merge([
+            LoggingExtension::PARAM_ENABLED=> true,
+            LoggingExtension::PARAM_PATH=> 'php://stderr',
+            WorseReflectionExtension::PARAM_ENABLE_CACHE=> false,
+            ComposerAutoloaderExtension::PARAM_COMPOSER_ENABLE => false,
+            WorseReflectionExtension::PARAM_STUB_DIR => $this->workspace()->path(),
+            FilePathResolverExtension::PARAM_APPLICATION_ROOT => __DIR__ . '/../',
+            FilePathResolverExtension::PARAM_PROJECT_ROOT => $this->workspace()->path(),
+        ], $config));
+
+        return $container[$key];
+    }
+
+    protected function workspace(): Workspace
+    {
+        return Workspace::create(__DIR__ . '/Workspace');
+    }
+}
diff --git a/lib/Extension/Navigation/Tests/Navigator/PathFinderNavigatorTest.php b/lib/Extension/Navigation/Tests/Navigator/PathFinderNavigatorTest.php
new file mode 100644
index 00000000..0227fdb4
--- /dev/null
+++ b/lib/Extension/Navigation/Tests/Navigator/PathFinderNavigatorTest.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Phpactor\Extension\Navigation\Tests\Navigator;
+
+use Phpactor\Extension\Navigation\Navigator\PathFinderNavigator;
+use Phpactor\PathFinder\PathFinder;
+use PHPUnit\Framework\TestCase;
+
+class PathFinderNavigatorTest extends TestCase
+{
+    private PathFinder $pathFinder;
+
+    public function setUp(): void
+    {
+        $this->pathFinder = PathFinder::fromDestinations([
+        'source' => 'src/<kernel>.php',
+        'unit_test' => 'tests/Unit/<kernel>Test.php'
+        ]);
+    }
+
+    public function testSomething(): void
+    {
+        $navigator = new PathFinderNavigator($this->pathFinder);
+        $result = $navigator->destinationsFor('src/Kernel.php');
+
+        self::assertSame(['unit_test' => 'tests/Unit/KernelTest.php'], $result);
+    }
+
+}