diff --git a/tests/Handler/TextDocumentSyncHandlerTest.php b/tests/Handler/TextDocumentSyncHandlerTest.php new file mode 100644 index 00000000..30b4c247 --- /dev/null +++ b/tests/Handler/TextDocumentSyncHandlerTest.php @@ -0,0 +1,169 @@ +supports('textDocument/didOpen'), 'didOpen is handled'); + self::assertTrue($handler->supports('textDocument/didChange'), 'didChange is handled'); + self::assertTrue($handler->supports('textDocument/didClose'), 'didClose is handled'); + self::assertFalse( + $handler->supports('textDocument/hover'), + 'the handler does not claim any other method', + ); + } + + public function testHandleDidOpenRegistersWithTheManagerThenForwardsTheDocumentToTheSink(): void + { + $document = new TextDocument(self::URI, 'php', 1, 'createMock(DocumentManagerInterface::class); + $documents + ->expects($this->once()) + ->method('open') + ->with(self::URI, 'php', 1, 'expects($this->once()) + ->method('get') + ->with(self::URI) + ->willReturn($document); + $documents->expects($this->never())->method('update'); + $documents->expects($this->never())->method('close'); + + $symbols = $this->createMock(SymbolSinkInterface::class); + $symbols->expects($this->once())->method('openDocument')->with($document); + $symbols->expects($this->never())->method('updateDocument'); + $symbols->expects($this->never())->method('closeDocument'); + + $invalidator = $this->createMock(InvalidatableInterface::class); + $invalidator->expects($this->never())->method('invalidate'); + + $handler = new TextDocumentSyncHandler($documents, $symbols, $invalidator); + $handler->handle(NotificationMessage::fromArray([ + 'jsonrpc' => '2.0', + 'method' => 'textDocument/didOpen', + 'params' => [ + 'textDocument' => [ + 'uri' => self::URI, + 'languageId' => 'php', + 'version' => 1, + 'text' => 'createMock(DocumentManagerInterface::class); + $documents + ->expects($this->once()) + ->method('update') + ->with(self::URI, 'expects($this->once()) + ->method('get') + ->with(self::URI) + ->willReturn($document); + $documents->expects($this->never())->method('open'); + $documents->expects($this->never())->method('close'); + + $symbols = $this->createMock(SymbolSinkInterface::class); + $symbols->expects($this->once())->method('updateDocument')->with($document); + $symbols->expects($this->never())->method('openDocument'); + $symbols->expects($this->never())->method('closeDocument'); + + $invalidator = $this->createMock(InvalidatableInterface::class); + $invalidator->expects($this->never())->method('invalidate'); + + $handler = new TextDocumentSyncHandler($documents, $symbols, $invalidator); + $handler->handle(NotificationMessage::fromArray([ + 'jsonrpc' => '2.0', + 'method' => 'textDocument/didChange', + 'params' => [ + 'textDocument' => ['uri' => self::URI, 'version' => 2], + // Only the last change's text is applied (full-sync; LSP §Document Synchronization). + 'contentChanges' => [ + ['text' => ' 'createMock(DocumentManagerInterface::class); + $documents->expects($this->never())->method('update'); + $documents->expects($this->never())->method('get'); + + $symbols = $this->createMock(SymbolSinkInterface::class); + $symbols->expects($this->never())->method('updateDocument'); + + $invalidator = $this->createMock(InvalidatableInterface::class); + $invalidator->expects($this->never())->method('invalidate'); + + $handler = new TextDocumentSyncHandler($documents, $symbols, $invalidator); + $handler->handle(NotificationMessage::fromArray([ + 'jsonrpc' => '2.0', + 'method' => 'textDocument/didChange', + 'params' => [ + 'textDocument' => ['uri' => self::URI, 'version' => 2], + 'contentChanges' => [], + ], + ])); + } + + public function testHandleDidCloseInvalidatesAndClosesAcrossAllThreeCollaborators(): void + { + $documents = $this->createMock(DocumentManagerInterface::class); + $documents->expects($this->once())->method('close')->with(self::URI); + $documents->expects($this->never())->method('open'); + $documents->expects($this->never())->method('update'); + + $symbols = $this->createMock(SymbolSinkInterface::class); + $symbols->expects($this->once())->method('closeDocument')->with(self::URI); + $symbols->expects($this->never())->method('openDocument'); + $symbols->expects($this->never())->method('updateDocument'); + + // The invalidator drop is what makes the next on-disk query re-read disk + // rather than the pre-edit cached value (RFC 1 §5.3). Covered here at the + // handler boundary; the fan-out through composite invalidators lives in + // the integration test. + $invalidator = $this->createMock(InvalidatableInterface::class); + $invalidator->expects($this->once())->method('invalidate')->with(self::URI); + + $handler = new TextDocumentSyncHandler($documents, $symbols, $invalidator); + $handler->handle(NotificationMessage::fromArray([ + 'jsonrpc' => '2.0', + 'method' => 'textDocument/didClose', + 'params' => [ + 'textDocument' => ['uri' => self::URI], + ], + ])); + } +} diff --git a/tests/Integration/TextDocumentSyncHandlerIntegrationTest.php b/tests/Integration/TextDocumentSyncHandlerIntegrationTest.php index 82812158..69498457 100644 --- a/tests/Integration/TextDocumentSyncHandlerIntegrationTest.php +++ b/tests/Integration/TextDocumentSyncHandlerIntegrationTest.php @@ -4,7 +4,6 @@ namespace Firehed\PhpLsp\Tests\Integration; -use Firehed\PhpLsp\Cache\InvalidatableInterface; use Firehed\PhpLsp\Document\DocumentManager; use Firehed\PhpLsp\Domain\ClasslikeName; use Firehed\PhpLsp\Domain\ComposerAutoloadMap; @@ -39,14 +38,6 @@ protected function setUp(): void $this->handler = new TextDocumentSyncHandler($this->manager, $knowledge->sink, $knowledge->invalidator); } - public function testSupports(): void - { - self::assertTrue($this->handler->supports('textDocument/didOpen')); - self::assertTrue($this->handler->supports('textDocument/didChange')); - self::assertTrue($this->handler->supports('textDocument/didClose')); - self::assertFalse($this->handler->supports('textDocument/hover')); - } - public function testDidOpen(): void { $notification = NotificationMessage::fromArray([ @@ -209,31 +200,6 @@ public function testDidChangeUpdatesClasses(): void self::assertSame('NewClass', $newClass->name->qualifiedName->shortName); } - public function testDidCloseInvalidatesTheOnDiskCacheSoTheNextQueryReReadsDisk(): void - { - // Closing an edited file must drop the on-disk cache so the next query - // reflects disk rather than the pre-edit cached value (RFC 1 §5.3). - $uri = 'file:///workspace/src/Widget.php'; - $this->manager->open($uri, 'php', 1, 'createMock(InvalidatableInterface::class); - $invalidator->expects($this->once()) - ->method('invalidate') - ->with($uri); - - $production = ProductionSyntaxSource::create(); - $knowledge = $this->knowledgeStackForMap(new ComposerAutoloadMap(), $production); - $handler = new TextDocumentSyncHandler($this->manager, $knowledge->sink, $invalidator); - - $handler->handle(NotificationMessage::fromArray([ - 'jsonrpc' => '2.0', - 'method' => 'textDocument/didClose', - 'params' => [ - 'textDocument' => ['uri' => $uri], - ], - ])); - } - public function testDidCloseRemovesClasses(): void { /** @var class-string $className */