Skip to content

Commit

Permalink
test: implement test for moving prototype
Browse files Browse the repository at this point in the history
Add tests and cleanup interfaces and responsibilities
  • Loading branch information
PRGfx committed Sep 13, 2024
1 parent 6199d20 commit 153a81b
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -49,6 +51,7 @@
public class MovePrototypeDialog extends RefactoringDialog {
private final List<PrototypeInfo> myPrototypeInfos;
private final VirtualFile myContextFile;
private final List<FusionPrototypeSignature> mySelectedPrototypes;
private final TextFieldWithHistoryWithBrowseButton myTargetFileField;
private final Pattern PRIVATE_RESOURCE_PATH_PATTERN = Pattern.compile("(\\w+(\\.\\w+)+)/Resources/Private");

Expand All @@ -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<PrototypeInfo> prototypeInfos = new ArrayList<>();
Expand Down Expand Up @@ -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()
));
}
Expand Down Expand Up @@ -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<FusionPrototypeSignature> signatures) {
String sourceFileName = PathUtil.getFileName(sourceFilePath);
String sourceExtension = PathUtil.getFileExtension(sourceFilePath);
for (FusionPrototypeSignature signature : signatures) {
Optional<String> 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<String> items = new LinkedHashSet<>();
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,46 @@
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<FusionPrototypeSignature> 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<FusionPrototypeSignature> 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;
}

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);
Expand Down Expand Up @@ -143,12 +159,18 @@ private static List<PsiElement> 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);

Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,15 @@ private void startRefactoring(Project project , List<FusionPrototypeSignature> s
dialog.show();
}

private List<FusionPrototypeSignature> findAllPrototypeSignatures(PsiFile psiFile) {
public static List<FusionPrototypeSignature> findAllPrototypeSignatures(PsiFile psiFile) {
List<FusionPrototypeSignature> 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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
14 changes: 14 additions & 0 deletions testData/fusion/refactoring/move_prototypes_after1.fusion
Original file line number Diff line number Diff line change
@@ -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) {
}
}
24 changes: 24 additions & 0 deletions testData/fusion/refactoring/move_prototypes_before.fusion
Original file line number Diff line number Diff line change
@@ -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) {
}
}
10 changes: 10 additions & 0 deletions testData/fusion/refactoring/move_prototypes_target1.fusion
Original file line number Diff line number Diff line change
@@ -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'
}
6 changes: 6 additions & 0 deletions testData/fusion/refactoring/move_prototypes_target2.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Comment block for Prototype.WithImplementation
*/
prototype(Vendor.Package:Prototype.WithImplementation) {
@class = 'Vendor\\Package\\Fusion\\WithImplementationImplementation'
}

0 comments on commit 153a81b

Please sign in to comment.