From 153a81bccba3d51c0493bf74ef8e375004ba880f Mon Sep 17 00:00:00 2001 From: Matthias Klatte Date: Fri, 13 Sep 2024 18:28:28 +0200 Subject: [PATCH] test: implement test for moving prototype Add tests and cleanup interfaces and responsibilities --- .../refactoring/MovePrototypeDialog.java | 49 ++++++++++++-- .../refactoring/MovePrototypeProcessor.java | 43 ++++++++++--- .../refactoring/MovePrototypeToFile.java | 6 +- .../fusion/refactoring/MovePrototypeTest.java | 64 +++++++++++++++++++ .../refactoring/move_prototypes_after1.fusion | 14 ++++ .../refactoring/move_prototypes_before.fusion | 24 +++++++ .../move_prototypes_target1.fusion | 10 +++ .../move_prototypes_target2.fusion | 6 ++ 8 files changed, 198 insertions(+), 18 deletions(-) create mode 100644 src/test/java/de/vette/idea/neos/fusion/refactoring/MovePrototypeTest.java create mode 100644 testData/fusion/refactoring/move_prototypes_after1.fusion create mode 100644 testData/fusion/refactoring/move_prototypes_before.fusion create mode 100644 testData/fusion/refactoring/move_prototypes_target1.fusion create mode 100644 testData/fusion/refactoring/move_prototypes_target2.fusion diff --git a/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeDialog.java b/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeDialog.java index 9f76ba7a..8fd08147 100644 --- a/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeDialog.java +++ b/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeDialog.java @@ -15,6 +15,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.refactoring.classMembers.AbstractMemberInfoModel; @@ -34,6 +35,7 @@ import de.vette.idea.neos.lang.fusion.icons.FusionIcons; import de.vette.idea.neos.lang.fusion.psi.FusionFile; import de.vette.idea.neos.lang.fusion.psi.FusionPrototypeSignature; +import de.vette.idea.neos.lang.fusion.psi.FusionType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -49,6 +51,7 @@ public class MovePrototypeDialog extends RefactoringDialog { private final List myPrototypeInfos; private final VirtualFile myContextFile; + private final List mySelectedPrototypes; private final TextFieldWithHistoryWithBrowseButton myTargetFileField; private final Pattern PRIVATE_RESOURCE_PATH_PATTERN = Pattern.compile("(\\w+(\\.\\w+)+)/Resources/Private"); @@ -62,6 +65,7 @@ protected MovePrototypeDialog( super(project, true, true); this.myContextFile = allSignatures.get(0).getContainingFile().getVirtualFile(); this.setTitle(FusionBundle.message("refactoring.move.prototype.title")); + this.mySelectedPrototypes = preselectedSignatures; this.myTargetFileField = createTargetFileField(); List prototypeInfos = new ArrayList<>(); @@ -91,12 +95,11 @@ protected void doAction() { if (selectedPrototypes.isEmpty()) { return; } - var affectedElements = MovePrototypeProcessor.collectAffectedElements(getSelectedPrototypes(), myProject); invokeRefactoring(new MovePrototypeProcessor( myProject, getTitle(), getTargetFilePath(), - affectedElements, + getSelectedPrototypes(), isOpenInEditor() )); } @@ -227,6 +230,41 @@ protected void textChanged(@NotNull DocumentEvent e) { return panel; } + /** + * Returns a suggested file name for the move operation based on the selected prototypes. + * This will use the last name part of a prototype (e.g. Vendor.Package:Prototype.Name -> Name.fusion). + * The name will be derived from the first given prototype not matching the source file name. + * + * @param sourceFilePath Path to the current file to use as base path and fallback + * @param signatures List of prototypes to consider for suggestions + * @return A file path to a fusion file + */ + public static String getSuggestedTargetFileName(String sourceFilePath, List signatures) { + String sourceFileName = PathUtil.getFileName(sourceFilePath); + String sourceExtension = PathUtil.getFileExtension(sourceFilePath); + for (FusionPrototypeSignature signature : signatures) { + Optional prototypeName = Optional.of(signature) + .map(FusionPrototypeSignature::getType) + .map(FusionType::getUnqualifiedType) + .map(PsiElement::getText); + + if (prototypeName.isEmpty()) { + continue; + } + + String[] prototypeNameParts = prototypeName.get().split("\\."); + String lastPrototypeNamePart = prototypeNameParts[prototypeNameParts.length - 1]; + String fileName = PathUtil.makeFileName(lastPrototypeNamePart, sourceExtension); + + if (fileName.equals(sourceFileName)) { + continue; + } + + return PathUtil.getParentPath(sourceFilePath) + File.separator + fileName; + } + return sourceFilePath; + } + private TextFieldWithHistoryWithBrowseButton createTargetFileField() { TextFieldWithHistoryWithBrowseButton field = new TextFieldWithHistoryWithBrowseButton(); Set items = new LinkedHashSet<>(); @@ -240,9 +278,10 @@ private TextFieldWithHistoryWithBrowseButton createTargetFileField() { .withTitle(title); field.addBrowseFolderListener(title, null, myProject, descriptor, TextComponentAccessors.TEXT_FIELD_WITH_HISTORY_WHOLE_TEXT); String initialPath = myContextFile.getPresentableUrl(); - int lastSlash = initialPath.lastIndexOf(File.separatorChar); - field.setText(initialPath); - field.getChildComponent().getTextEditor().select(lastSlash + 1, initialPath.length()); + String suggestedTargetFileName = getSuggestedTargetFileName(initialPath, mySelectedPrototypes); + int lastSlash = suggestedTargetFileName.lastIndexOf(File.separatorChar); + field.setText(suggestedTargetFileName); + field.getChildComponent().getTextEditor().select(lastSlash + 1, suggestedTargetFileName.length()); return field; } diff --git a/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeProcessor.java b/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeProcessor.java index d0d86054..99d6d344 100644 --- a/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeProcessor.java +++ b/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeProcessor.java @@ -35,21 +35,37 @@ public class MovePrototypeProcessor extends BaseRefactoringProcessor { private final String myTitle; - private final String myTargetFilePath; + private final @Nullable PsiFile myTargetFile; private final PsiElement[] myAffectedElements; private final boolean myOpenInEditor; + private @Nullable String myTargetFilePath = null; - protected MovePrototypeProcessor( + public MovePrototypeProcessor( @NotNull Project project, String title, - String targetFilePath, - PsiElement[] affectedElements, + @NotNull PsiFile targetFile, + Iterable signaturesToMove, boolean openInEditor ) { super(project); this.myTitle = title; + this.myTargetFile = targetFile; + this.myAffectedElements = MovePrototypeProcessor.collectAffectedElements(signaturesToMove, project); + this.myOpenInEditor = openInEditor; + } + + public MovePrototypeProcessor( + @NotNull Project project, + String title, + @NotNull String targetFilePath, + Iterable signaturesToMove, + boolean openInEditor + ) { + super(project); + this.myTitle = title; + this.myTargetFile = getOrCreateFileFromPath(targetFilePath); this.myTargetFilePath = targetFilePath; - this.myAffectedElements = affectedElements; + this.myAffectedElements = MovePrototypeProcessor.collectAffectedElements(signaturesToMove, project); this.myOpenInEditor = openInEditor; } @@ -57,8 +73,8 @@ private static boolean isMultilineComment(@Nullable PsiElement element) { return element instanceof PsiComment && element.getText().startsWith("/*"); } - private @Nullable FusionFile getTargetFile() { - String path = FileUtil.toSystemIndependentName(myTargetFilePath); + private @Nullable FusionFile getOrCreateFileFromPath(String targetFilePath) { + String path = FileUtil.toSystemIndependentName(targetFilePath); VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path); if (file != null) { PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); @@ -143,12 +159,18 @@ private static List getPsiElementsForPrototypeSignature(FusionProtot @Override protected void performRefactoring(UsageInfo @NotNull [] usages) { - FusionFile targetFile = getTargetFile(); + PsiFile targetFile = myTargetFile != null + ? myTargetFile + : myTargetFilePath != null ? getOrCreateFileFromPath(myTargetFilePath) : null; if (targetFile == null) { // there could be other errors as well, but we assume, they have been validated before - CommonRefactoringUtil.showErrorMessage(myTitle, FusionBundle.message("refactoring.move.prototype.error.creating.file", myTargetFilePath), null, myProject); + if (myTargetFilePath != null) { + CommonRefactoringUtil.showErrorMessage(myTitle, FusionBundle.message("refactoring.move.prototype.error.creating.file", myTargetFilePath), null, myProject); + return; + } return; } + // we assume that everything is from the same file. we could alternatively go over everything by signature PsiFile originalFile = myAffectedElements[0].getContainingFile(); FileModificationService.getInstance().preparePsiElementsForWrite(originalFile, targetFile); @@ -198,6 +220,7 @@ protected void performRefactoring(UsageInfo @NotNull [] usages) { @Override protected @NotNull @NlsContexts.Command String getCommandName() { - return FusionBundle.message("refactoring.move.prototype.move.to", myTargetFilePath); + String path = myTargetFile != null ? myTargetFile.getVirtualFile().getPath() : myTargetFilePath; + return FusionBundle.message("refactoring.move.prototype.move.to", path); } } diff --git a/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeToFile.java b/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeToFile.java index 2407c6ee..b04aa577 100644 --- a/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeToFile.java +++ b/src/main/java/de/vette/idea/neos/lang/fusion/refactoring/MovePrototypeToFile.java @@ -109,15 +109,15 @@ private void startRefactoring(Project project , List s dialog.show(); } - private List findAllPrototypeSignatures(PsiFile psiFile) { + public static List findAllPrototypeSignatures(PsiFile psiFile) { List signatures = new ArrayList<>(PsiTreeUtil.findChildrenOfType(psiFile, FusionPrototypeSignature.class)); - return signatures.stream().filter(this::isTopLevelPrototype).collect(Collectors.toList()); + return signatures.stream().filter(MovePrototypeToFile::isTopLevelPrototype).collect(Collectors.toList()); } /** * Determines whether the prototype definition is an override on some path or not. */ - private boolean isTopLevelPrototype(@Nullable PsiElement prototypeSignature) { + private static boolean isTopLevelPrototype(@Nullable PsiElement prototypeSignature) { if (!(prototypeSignature instanceof FusionPrototypeSignature)) { return false; } diff --git a/src/test/java/de/vette/idea/neos/fusion/refactoring/MovePrototypeTest.java b/src/test/java/de/vette/idea/neos/fusion/refactoring/MovePrototypeTest.java new file mode 100644 index 00000000..d3e385fb --- /dev/null +++ b/src/test/java/de/vette/idea/neos/fusion/refactoring/MovePrototypeTest.java @@ -0,0 +1,64 @@ +package de.vette.idea.neos.fusion.refactoring; + +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import de.vette.idea.neos.lang.fusion.refactoring.MovePrototypeDialog; +import de.vette.idea.neos.lang.fusion.refactoring.MovePrototypeProcessor; +import de.vette.idea.neos.lang.fusion.refactoring.MovePrototypeToFile; +import util.FusionTestUtils; + +import java.io.File; + +public class MovePrototypeTest extends BasePlatformTestCase { + + @Override + protected String getTestDataPath() { + return FusionTestUtils.BASE_TEST_DATA_PATH; + } + + public void testFindAllPrototypesInFile() { + myFixture.configureByFile("fusion/refactoring/move_prototypes_before.fusion"); + var signatures = MovePrototypeToFile.findAllPrototypeSignatures(myFixture.getFile()); + var signatureNames = signatures.stream().map(signature -> signature.getType().getText()); + assertContainsElements( + signatureNames.toList(), + "Vendor.Package:Prototype.CopiedFrom", + "Vendor.Package:Prototype.WithImplementation", + "Vendor.Package:Prototype.Example2" + ); + } + + public void testSuggestedTargetFileName() { + myFixture.configureByFile("fusion/refactoring/move_prototypes_before.fusion"); + var signatures = MovePrototypeToFile.findAllPrototypeSignatures(myFixture.getFile()); + String basePath = "current" + File.separator + "path" + File.separator; + String sourceFilePath = basePath + "CopiedFrom.fusion"; + + var suggestedFilename = MovePrototypeDialog.getSuggestedTargetFileName(sourceFilePath, signatures); + assertEquals(basePath + "WithImplementation.fusion", suggestedFilename); + } + + public void testMoveSinglePrototypes() { + myFixture.configureByFile("fusion/refactoring/move_prototypes_before.fusion"); + var signatures = MovePrototypeToFile.findAllPrototypeSignatures(myFixture.getFile()); + + var after = myFixture.addFileToProject("after.fusion", ""); + myFixture.configureFromTempProjectFile("after.fusion"); + + var processor = new MovePrototypeProcessor(getProject(), "Move Prototypes", after, signatures.subList(1, 2), false); + processor.run(); + myFixture.checkResultByFile("after.fusion", "fusion/refactoring/move_prototypes_target2.fusion", false); + } + + public void testMoveMultiplePrototypes() { + myFixture.configureByFile("fusion/refactoring/move_prototypes_before.fusion"); + var signatures = MovePrototypeToFile.findAllPrototypeSignatures(myFixture.getFile()); + + var after = myFixture.addFileToProject("after.fusion", ""); + myFixture.configureFromTempProjectFile("after.fusion"); + + var processor = new MovePrototypeProcessor(getProject(), "Move Prototypes", after, signatures.subList(0, 2), false); + processor.run(); + myFixture.checkResultByFile("fusion/refactoring/move_prototypes_before.fusion", "fusion/refactoring/move_prototypes_after1.fusion", false); + myFixture.checkResultByFile("after.fusion", "fusion/refactoring/move_prototypes_target1.fusion", false); + } +} diff --git a/testData/fusion/refactoring/move_prototypes_after1.fusion b/testData/fusion/refactoring/move_prototypes_after1.fusion new file mode 100644 index 00000000..af982fe4 --- /dev/null +++ b/testData/fusion/refactoring/move_prototypes_after1.fusion @@ -0,0 +1,14 @@ +Namespace.Controller.Action = Vendor.Package:Prototype.At.Path { + prototype(Vendor.Package:Prototype.OverrideInPath) { + } +} + +# Some global comment +# with multiple lines + + + +prototype(Vendor.Package:Prototype.Example2) { + prototype(Vendor.Package:OverrideInPrototype) { + } +} \ No newline at end of file diff --git a/testData/fusion/refactoring/move_prototypes_before.fusion b/testData/fusion/refactoring/move_prototypes_before.fusion new file mode 100644 index 00000000..9687716d --- /dev/null +++ b/testData/fusion/refactoring/move_prototypes_before.fusion @@ -0,0 +1,24 @@ +Namespace.Controller.Action = Vendor.Package:Prototype.At.Path { + prototype(Vendor.Package:Prototype.OverrideInPath) { + } +} + +# Some global comment +# with multiple lines + +# A comment for Prototype.CopiedFrom +# that can span multiple lines +prototype(Vendor.Package:Prototype.CopiedFrom) < prototype(Vendor.Package:Example1) { +} + +/** + * Comment block for Prototype.WithImplementation + */ +prototype(Vendor.Package:Prototype.WithImplementation) { + @class = 'Vendor\\Package\\Fusion\\WithImplementationImplementation' +} + +prototype(Vendor.Package:Prototype.Example2) { + prototype(Vendor.Package:OverrideInPrototype) { + } +} \ No newline at end of file diff --git a/testData/fusion/refactoring/move_prototypes_target1.fusion b/testData/fusion/refactoring/move_prototypes_target1.fusion new file mode 100644 index 00000000..4c78d61c --- /dev/null +++ b/testData/fusion/refactoring/move_prototypes_target1.fusion @@ -0,0 +1,10 @@ +# A comment for Prototype.CopiedFrom +# that can span multiple lines +prototype(Vendor.Package:Prototype.CopiedFrom) < prototype(Vendor.Package:Example1) { +} +/** + * Comment block for Prototype.WithImplementation + */ +prototype(Vendor.Package:Prototype.WithImplementation) { + @class = 'Vendor\\Package\\Fusion\\WithImplementationImplementation' +} diff --git a/testData/fusion/refactoring/move_prototypes_target2.fusion b/testData/fusion/refactoring/move_prototypes_target2.fusion new file mode 100644 index 00000000..cbb1cb31 --- /dev/null +++ b/testData/fusion/refactoring/move_prototypes_target2.fusion @@ -0,0 +1,6 @@ +/** + * Comment block for Prototype.WithImplementation + */ +prototype(Vendor.Package:Prototype.WithImplementation) { + @class = 'Vendor\\Package\\Fusion\\WithImplementationImplementation' +}