/*******************************************************************************
* Copyright (c) 2012 VMWare, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMWare, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.eclipse.search.test;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.ui.search.QuerySpecification;
import org.eclipse.text.edits.MultiTextEdit;
import org.grails.ide.eclipse.core.model.GrailsVersion;
import org.grails.ide.eclipse.core.util.GrailsNameUtils;
import org.grails.ide.eclipse.editor.groovy.elements.GrailsProject;
import org.grails.ide.eclipse.editor.groovy.elements.GrailsWorkspaceCore;
import org.grails.ide.eclipse.search.AbstractQueryParticipant;
import org.grails.ide.eclipse.search.SearchUtil;
import org.grails.ide.eclipse.search.action.ControllerActionQueryParticipant;
import org.grails.ide.eclipse.search.action.ControllerActionSearch;
/**
* Test the visitor that is in charge of renaming view/action references inside of controller classes.
*
* @author Kris De Volder
* @since 2.8
*/
public class ControllerActionQueryParticipantTest extends AbstractGrailsSearchParticipantTest {
protected IProject project;
protected IJavaProject javaProject;
protected GrailsProject grailsProject;
@Override
protected void setUp() throws Exception {
super.setUp();
ensureDefaultGrailsVersion(GrailsVersion.MOST_RECENT);
project = ensureProject(TEST_PROJECT_NAME);
javaProject = JavaCore.create(project);
grailsProject = GrailsWorkspaceCore.get().create(project);
}
/**
* Basic test, search for something that isn't found (i.e. there are no references to it).
*/
public void testNoResultsSearch() throws Exception {
String controllerClassName = "SongController";
String contents = "package "+PACKAGE_NAME+"\n" +
"\n" +
"class "+controllerClassName+" {\n" +
" def index() { }\n" +
"}\n";
createTmpResource(project, "grails-app/controllers/"+PACKAGE_NAME+"/"+controllerClassName+".groovy",
contents);
IMethod targetAction = method(javaProject, PACKAGE_NAME, controllerClassName, "index");
QuerySpecification query = SearchUtil.createReferencesQuery(targetAction);
AbstractQueryParticipant searchParticipant = new ControllerActionQueryParticipant();
int ticks = searchParticipant.estimateTicks(query);
assertTrue("Ticks must be between 0 and 1000 but is "+ticks, 0 < ticks && ticks < 1000);
assertMatches(searchParticipant, query /*no matches*/);
}
/**
* Test a search where the participant is not expected to participate (i.e. the target type
* is not actually a controller.
* @throws Exception
*/
public void testNotParticipating() throws Exception {
createTmpResource(project, "src/groovy/foobar/Foo.groovy",
"package foobar;\n" +
"class Foo {\n" +
" def doit() {}\n"+
"}\n");
IMethod target = method(javaProject, "foobar", "Foo", "doit");
QuerySpecification query = SearchUtil.createReferencesQuery(target);
AbstractQueryParticipant searchParticipant = new ControllerActionQueryParticipant();
int ticks = searchParticipant.estimateTicks(query);
assertEquals("Ticks", 0, ticks);
assertMatches(searchParticipant, query /*no matches*/);
}
/**
* A simple test where the controllerClass itself contains an action refering an action is the same controller.
*/
public void testSimpleMethodSearch() throws Exception {
String controllerClassName = "SongController";
String controllerLogicalName = "song";
String contents = "package "+PACKAGE_NAME+"\n" +
"\n" +
"class "+controllerClassName+" {\n" +
" def index() {\n" +
" redirect(controller:\""+controllerLogicalName+ "\", action: \"foo\")\n" +
" }\n" +
" def foo() {\n" +
" }\n"+
"}\n";
createTmpResource(project, "grails-app/controllers/"+PACKAGE_NAME+"/"+controllerClassName+".groovy",
contents);
IMethod targetAction = method(javaProject, PACKAGE_NAME, controllerClassName, "foo");
QuerySpecification query = SearchUtil.createReferencesQuery(targetAction);
AbstractQueryParticipant searchParticipant = new ControllerActionQueryParticipant();
int ticks = searchParticipant.estimateTicks(query);
assertTrue("Ticks must be between 0 and 1000 but is "+ticks, 0 < ticks && ticks < 1000);
assertMatches(searchParticipant, query,
methodMatch(javaProject, PACKAGE_NAME, controllerClassName, "index", "foo"));
}
/**
* A simple test where the controllerClass itself contains an action refering an action is the same controller.
*/
public void testSimpleFieldSearch() throws Exception {
String controllerClassName = "SongController";
String controllerLogicalName = "song";
String contents = "package "+PACKAGE_NAME+"\n" +
"\n" +
"class "+controllerClassName+" {\n" +
" def index = {\n" +
" redirect(controller:\""+controllerLogicalName+ "\", action: \"foo\")\n" +
" }\n" +
" def foo = {\n" +
" }\n"+
"}\n";
createTmpResource(project, "grails-app/controllers/"+PACKAGE_NAME+"/"+controllerClassName+".groovy",
contents);
IField targetAction = field(javaProject, PACKAGE_NAME, controllerClassName, "foo");
QuerySpecification query = SearchUtil.createReferencesQuery(targetAction);
AbstractQueryParticipant searchParticipant = new ControllerActionQueryParticipant();
int ticks = searchParticipant.estimateTicks(query);
assertTrue("Ticks must be between 0 and 1000 but is "+ticks, 0 < ticks && ticks < 1000);
assertMatches(searchParticipant, query,
fieldMatch(javaProject, PACKAGE_NAME, controllerClassName, "index", "foo"));
}
/**
* This tests the ControllerActionSearch used by the participant separately. (As used
* by the rename refactoring participants.
*
* @throws Exception
*/
public void testControllerActionSearchSeparately() throws Exception {
String findActionName = "create";
String targetPath = "grails-app/controllers/"+PACKAGE_NAME+"/FooController.groovy";
final String contents =
"package "+PACKAGE_NAME+"\n" +
"\n" +
"class FooController {\n" +
"\n" +
" def save = {\n" +
" def bananaInstance = new Song(params)\n" +
" if (bananaInstance.save(flush: true)) {\n" +
" flash.message = \"${message(code: 'default.created.message', args: [message(code: 'banana.label', default: 'Banana'), bananaInstance.id])}\"\n" +
" redirect(action: \"show\", id: bananaInstance.id)\n" +
" }\n" +
" else {\n" +
" render(view: \"create\", model: [bananaInstance: bananaInstance])\n" +
" }\n" +
" }\n" +
"}\n";
createTmpResource(project, targetPath, contents);
ControllerActionSearch search = new ControllerActionSearch(grailsProject, "FooController", findActionName);
MatchInfo[] expectedMatches = new MatchInfo[] {
fieldMatch(javaProject, PACKAGE_NAME, "FooController", "save", "\""+findActionName+"\"")
};
assertMatches(search, expectedMatches);
}
/**
* Similar to above test but initializes the search from a QuerySpecification
*
* @throws Exception
*/
public void testControllerActionSearchFieldAction() throws Exception {
String findActionName = "create";
String targetPath = "grails-app/controllers/"+PACKAGE_NAME+"/FooController.groovy";
final String contents =
"package "+PACKAGE_NAME+"\n" +
"\n" +
"class FooController {\n" +
" def create = {\n" +
" render(\"dummy\"\n"+
" }\n"+
"\n" +
" def save = {\n" +
" def bananaInstance = new Song(params)\n" +
" if (bananaInstance.save(flush: true)) {\n" +
" flash.message = \"${message(code: 'default.created.message', args: [message(code: 'banana.label', default: 'Banana'), bananaInstance.id])}\"\n" +
" redirect(action: \"show\", id: bananaInstance.id)\n" +
" }\n" +
" else {\n" +
" render(view: \"create\", model: [bananaInstance: bananaInstance])\n" +
" }\n" +
" }\n" +
"}\n";
createTmpResource(project, targetPath, contents);
QuerySpecification query = SearchUtil.createReferencesQuery(field(javaProject, PACKAGE_NAME, "FooController", findActionName));
ControllerActionSearch search = new ControllerActionSearch(query);
MatchInfo[] expectedMatches = new MatchInfo[] {
fieldMatch(javaProject, PACKAGE_NAME, "FooController", "save", "\""+findActionName+"\"")
};
assertMatches(search, expectedMatches);
}
/**
* Similar to above test but this time the actions are methods instead of fields.
*
* @throws Exception
*/
public void testControllerActionSearchMethodAction() throws Exception {
String findActionName = "create";
String targetPath = "grails-app/controllers/"+PACKAGE_NAME+"/FooController.groovy";
final String contents =
"package "+PACKAGE_NAME+"\n" +
"\n" +
"class FooController {\n" +
" def create() {\n" +
" render(\"dummy\")\n"+
" }\n"+
"\n" +
" def save() {\n" +
" def bananaInstance = new Song(params)\n" +
" if (bananaInstance.save(flush: true)) {\n" +
" flash.message = \"${message(code: 'default.created.message', args: [message(code: 'banana.label', default: 'Banana'), bananaInstance.id])}\"\n" +
" redirect(action: \"show\", id: bananaInstance.id)\n" +
" }\n" +
" else {\n" +
" render(view: \"create\", model: [bananaInstance: bananaInstance])\n" +
" }\n" +
" }\n" +
"}\n";
createTmpResource(project, targetPath, contents);
QuerySpecification query = SearchUtil.createReferencesQuery(method(javaProject, PACKAGE_NAME, "FooController", findActionName));
ControllerActionSearch search = new ControllerActionSearch(query);
MatchInfo[] expectedMatches = new MatchInfo[] {
methodMatch(javaProject, PACKAGE_NAME, "FooController", "save", "\""+findActionName+"\"")
};
assertMatches(search, expectedMatches);
}
/**
* If both action and controller in the call, should not replace if controller does *not* match.
*/
public void testRedirectControllerNotMatching() throws Exception {
String controllerName = "foo";
String findName = "list";
String inSnippet = "redirect(controller:\"book\", action:\"list\")";
String expectSnippet = "redirect(controller:\"book\", action:\"list\")";
doSnippetTest(controllerName, controllerName, findName, inSnippet, expectSnippet);
}
/**
* If both action and controller in the call, should not replace if controller *does* match.
*/
public void testRedirectControllerMatching() throws Exception {
String controllerName = "foo";
String oldName = "list";
String inSnippet = "redirect(controller:\"foo\", action:\"list\")";
String expectSnippet = "redirect(controller:\"foo\", action:\"####\")";
doSnippetTest(controllerName, controllerName, oldName, inSnippet, expectSnippet);
}
/**
* If only action in the call, should match only if action matches AND the 'context controller' matches
*/
public void testRedirectNoController1() throws Exception {
doSnippetTest(
//String contextControllerName
"foo",
//String controllerName
"foo",
//String oldName,
"list",
//String inSnippet
"redirect(action:\"list\")",
//String expectSnippet
"redirect(action:\"####\")"
);
}
/**
* If only action in the call, should match only if action matches AND the 'context controller' matches
*/
public void testRedirectNoController2() throws Exception {
doSnippetTest(
//String contextControllerName
"foo",
//String controllerName
"foo",
//String oldName,
"golly",
//String inSnippet
"redirect(action:\"list\")",
//String expectSnippet
"redirect(action:\"list\")"
);
}
/**
* If only action in the call, should match only if action matches AND the 'context controller' matches
*/
public void testRedirectNoController3() throws Exception {
doSnippetTest(
//String contextControllerName
"someOtherplace",
//String controllerName
"foo",
//String oldName,
"golly",
//String inSnippet
"redirect(action:\"list\")",
//String expectSnippet
"redirect(action:\"list\")"
);
}
/**
* Test that we don't replace "view" parameters if we are not inside the correct controller.
*/
public void testRenderOutOfContext1() throws Exception {
doSnippetTest(
//String contextControllerName
"samePlace",
//String controllerName
"samePlace",
//String oldName,
"golly",
//String inSnippet
"render(view:\"golly\")",
//String expectSnippet
"render(view:\"#####\")"
);
}
/**
* Test that we don't replace "view" parameters if we are not inside the correct controller.
*/
public void testRenderOutOfContext2() throws Exception {
doSnippetTest(
//String contextControllerName
"otherPlace",
//String controllerName
"samePlace",
//String oldName,
"golly",
//String inSnippet
"render(view:\"golly\")",
//String expectSnippet
"render(view:\"golly\")"
);
}
/**
* Expect snippet should be a copy of 'inSnippet' but with the expected matches replaced by a sequence of #.
* The expectSnippet will be searched for hashes to determine the expected matches for the search.
*/
public void doSnippetTest(String contextControllerName, String targetControllerName, String oldActionName, String inSnippet, String expectSnippet)
throws CoreException {
//This a 'test template' to do tests to see if these kinds of snippets are handled corretctly
// redirect(url:"http://www.blogjava.net/BlueSUN") (NOT supported)
// redirect(action:"show")
// redirect(controller:"book",action:"list")
// redirect(action:"show",id:4, params:[author:"Stephen King"])
// redirect(controller: "book", action: "show", fragment: "profile")
String contextControllerClassName = GrailsNameUtils.getClassName(contextControllerName, "Controller");
String targetControllerClassName = GrailsNameUtils.getClassName(targetControllerName, "Controller");
String contextControllerPath = "grails-app/controllers/"+PACKAGE_NAME+"/"+contextControllerClassName+".groovy";
final String template =
"package gtunez\n" +
"\n" +
"class "+contextControllerClassName+" {\n" +
"\n" +
" def anAction = {\n" +
" ***\n" +
" }\n" +
"}\n";
createTmpResource(project, contextControllerPath,
template.replace("***", inSnippet));
ControllerActionSearch search = new ControllerActionSearch(grailsProject, targetControllerClassName, oldActionName);
assertMatches(search, determineExpectedMatches(field(javaProject, PACKAGE_NAME, contextControllerClassName, "anAction"), template, expectSnippet));
}
private static IMethod method(IJavaProject javaProject, String pkgName, String clsName, String methodName) throws JavaModelException {
IType type = javaProject.findType(pkgName+"."+clsName);
IMethod method = null;
for (IMethod m : type.getMethods()) {
if (m.getElementName().equals(methodName)) {
if (method!=null) {
fail("Multiple methods with name '"+methodName+"' in '"+clsName+"'");
} else {
method = m;
}
}
}
if (method == null) {
fail("No method with name '"+methodName+"' in '"+clsName+"'");
}
return method;
}
// public void testRenameCreate() throws Exception {
// String oldName = "create";
// doTestSearch(oldName);
// }
//
// public void testRenameList() throws Exception {
// String oldName = "list";
// doTestSearch(oldName);
// }
// private void doTestRename(String oldName, String newName)
// throws IOException, CoreException {
// String targetPath = "grails-app/controllers/gtunez/SongController.groovy";
// String contents = getContents(project.getFile(new Path(targetPath)));
// ICompilationUnit cu = getCompilationUnit(project, targetPath);
// ModuleNode ast = getAST(cu);
//
// CompilationUnitChange changes = new CompilationUnitChange("test change", cu);
// RefactoringStatus status = new RefactoringStatus();
// EditGeneratingCodeVisitor visitor = new GrailsViewRenamingVisitor(cu, "song", oldName, newName, changes, status);
// visitor.visit(ast);
//
// assertTrue(status.isOK());
// assertEquals(contents.replace("\""+ oldName +"\"", "\""+ newName+ "\""), changes.getPreviewContent(null));
// assertSomeEdits(changes);
// }
/**
* Assert that a CU change set contains at least some edits. This is used to make sure that the
* test isn't somehow defective itself (generates and expects no edits because of typo :-)
*/
private void assertSomeEdits(CompilationUnitChange changes) {
MultiTextEdit edit = (MultiTextEdit)changes.getEdit();
assertTrue("Expected some edits but found none", edit!=null && edit.getChildrenSize()>0);
}
}