package p.studio.vfs; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import p.studio.vfs.messages.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; final class FilesystemVfsProjectDocumentTest { @TempDir Path tempDir; @Test void snapshotIncludesHiddenFilesOrdersFoldersFirstAndTagsSourceRoots() throws Exception { Files.createDirectories(tempDir.resolve("src")); Files.createDirectories(tempDir.resolve("assets")); Files.writeString(tempDir.resolve(".env"), "TOKEN=1\n"); Files.writeString(tempDir.resolve("README.md"), "# project\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final VfsProjectSnapshot snapshot = vfs.snapshot(); assertEquals("Example", snapshot.rootNode().displayName()); assertEquals("assets", snapshot.rootNode().children().get(0).displayName()); assertEquals("src", snapshot.rootNode().children().get(1).displayName()); assertEquals(".env", snapshot.rootNode().children().get(2).displayName()); assertTrue(snapshot.rootNode().children().get(1).taggedSourceRoot()); } @Test void openDocumentReturnsTextDocumentForUtf8TextFile() throws Exception { final Path file = tempDir.resolve("main.pbs"); Files.writeString(file, "fn main(): void\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final VfsDocumentOpenResult result = vfs.openDocument(file); final var document = assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, result); assertEquals("main.pbs", document.documentName()); assertEquals("pbs", document.typeId()); assertEquals("LF", document.lineSeparator()); assertEquals(VfsDocumentAccessMode.EDITABLE, document.accessContext().accessMode()); assertTrue(document.accessContext().frontendDocument()); assertTrue(document.content().contains("fn main()")); } @Test void openDocumentClassifiesJsonFilesWithJsonTypeIdentifier() throws Exception { final Path file = tempDir.resolve("prometeu.json"); Files.writeString(file, "{\n \"name\": \"Example\"\n}\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final VfsDocumentOpenResult result = vfs.openDocument(file); final var document = assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, result); assertEquals(VfsDocumentTypeIds.JSON, document.typeId()); assertEquals(VfsDocumentAccessMode.EDITABLE, document.accessContext().accessMode()); } @Test void openDocumentClassifiesNdjsonFilesWithJsonTypeIdentifier() throws Exception { final Path file = tempDir.resolve("events.ndjson"); Files.writeString(file, "{\"event\":\"start\"}\n{\"event\":\"done\"}\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final VfsDocumentOpenResult result = vfs.openDocument(file); final var document = assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, result); assertEquals(VfsDocumentTypeIds.NDJSON, document.typeId()); } @Test void openDocumentClassifiesBashScriptsWithBashTypeIdentifier() throws Exception { final Path file = tempDir.resolve("script.sh"); Files.writeString(file, "#!/usr/bin/env bash\nprintf \"hi\"\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final VfsDocumentOpenResult result = vfs.openDocument(file); final var document = assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, result); assertEquals(VfsDocumentTypeIds.BASH, document.typeId()); assertEquals(VfsDocumentAccessMode.EDITABLE, document.accessContext().accessMode()); } @Test void frontendDocumentsUseEditableInMemorySnapshotsUntilSavePersistsThem() throws Exception { final Path file = tempDir.resolve("main.pbs"); Files.writeString(file, "fn main(): void\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final var updated = vfs.updateDocument(file, "fn main(): int\n"); assertTrue(updated.dirty()); assertEquals("fn main(): int\n", updated.content()); assertEquals("fn main(): void\n", Files.readString(file)); assertEquals("fn main(): int\n", assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).content()); final VfsDocumentSaveResult saveResult = vfs.saveDocument(file); assertEquals(VfsDocumentSaveStatus.SAVED, saveResult.status()); assertEquals("fn main(): int\n", Files.readString(file)); assertFalse(assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).dirty()); } @Test void editableDocumentsUseInMemorySnapshotsUntilSavePersistsThem() throws Exception { final Path file = tempDir.resolve("notes.txt"); Files.writeString(file, "alpha\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final var updated = vfs.updateDocument(file, "beta\n"); assertTrue(updated.dirty()); assertEquals("beta\n", updated.content()); assertEquals("alpha\n", Files.readString(file)); assertEquals("beta\n", assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).content()); final VfsDocumentSaveResult saveResult = vfs.saveDocument(file); assertEquals(VfsDocumentSaveStatus.SAVED, saveResult.status()); assertEquals("beta\n", Files.readString(file)); assertFalse(assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).dirty()); } @Test void discardDocumentDropsDirtySnapshotAndRestoresFilesystemState() throws Exception { final Path file = tempDir.resolve("notes.txt"); Files.writeString(file, "alpha\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); vfs.updateDocument(file, "beta\n"); final var discarded = vfs.discardDocument(file); assertFalse(discarded.dirty()); assertEquals("alpha\n", discarded.content()); assertEquals("alpha\n", Files.readString(file)); assertEquals("alpha\n", assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).content()); } @Test void saveAllDocumentsPersistsEditableDirtySnapshotsIncludingFrontendDocuments() throws Exception { final Path editable = tempDir.resolve("notes.txt"); final Path frontend = tempDir.resolve("main.pbs"); Files.writeString(editable, "alpha\n"); Files.writeString(frontend, "fn main(): void\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); vfs.updateDocument(editable, "beta\n"); vfs.updateDocument(frontend, "fn main(): int\n"); final var results = vfs.saveAllDocuments(); assertEquals(2, results.size()); assertEquals(editable.toAbsolutePath().normalize(), results.get(0).path()); assertEquals(VfsDocumentSaveStatus.SAVED, results.get(0).status()); assertEquals(frontend.toAbsolutePath().normalize(), results.get(1).path()); assertEquals(VfsDocumentSaveStatus.SAVED, results.get(1).status()); assertEquals("beta\n", Files.readString(editable)); assertEquals("fn main(): int\n", Files.readString(frontend)); } @Test void accessContextCanBeReadAndMutatedWithoutChangingCanonicalPolicy() throws Exception { final Path file = tempDir.resolve("prometeu.json"); Files.writeString(file, "{\n \"name\": \"Example\"\n}\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final VfsDocumentAccessContext initialContext = vfs.accessContext(file); final VfsDocumentAccessContext updatedContext = vfs.updateAccessContext(file, Map.of("statusBar", "future-toggle")); assertEquals(VfsDocumentAccessMode.EDITABLE, initialContext.accessMode()); assertEquals(VfsDocumentTypeIds.JSON, initialContext.typeId()); assertEquals(Map.of("statusBar", "future-toggle"), updatedContext.attributes()); assertEquals(initialContext.accessMode(), updatedContext.accessMode()); assertEquals(initialContext.typeId(), updatedContext.typeId()); } @Test void openDocumentRejectsBinaryLikeFiles() throws Exception { final Path file = tempDir.resolve("sprite.bin"); Files.write(file, new byte[]{0x01, 0x00, 0x02}); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final VfsDocumentOpenResult result = vfs.openDocument(file); final var unsupported = assertInstanceOf(VfsDocumentOpenResult.VfsUnsupportedDocument.class, result); assertEquals(VfsUnsupportedReason.BINARY_CONTENT, unsupported.reason()); } @Test void openDocumentRejectsPathsOutsideProjectScope() throws Exception { final Path outsideFile = tempDir.getParent().resolve("outside.pbs"); Files.writeString(outsideFile, "fn stray(): void\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); final VfsDocumentOpenResult result = vfs.openDocument(outsideFile); final var unsupported = assertInstanceOf(VfsDocumentOpenResult.VfsUnsupportedDocument.class, result); assertEquals(VfsUnsupportedReason.OUTSIDE_PROJECT, unsupported.reason()); } @Test void targetedRefreshUpdatesOnlyTheRequestedSubtreeInTheSnapshot() throws Exception { final Path src = Files.createDirectories(tempDir.resolve("src")); Files.createDirectories(tempDir.resolve("assets")); Files.writeString(src.resolve("main.pbs"), "fn main(): void\n"); final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); Files.writeString(src.resolve("later.pbs"), "fn later(): void\n"); final VfsProjectSnapshot refreshedSnapshot = vfs.refresh(new VfsRefreshRequest(src)); final VfsProjectNode srcNode = refreshedSnapshot.rootNode().children().get(1); assertEquals("src", srcNode.displayName()); assertEquals(2, srcNode.children().size()); assertEquals("later.pbs", srcNode.children().get(0).displayName()); assertEquals("main.pbs", srcNode.children().get(1).displayName()); } private VfsProjectContext projectContext() { return new VfsProjectContext("Example", "pbs", tempDir); } }