blob: 36dde7b088e44230078ee6f7efd4e2688f8b6eb3 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.appindexing.api;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.xml.XmlFile;
import org.jetbrains.android.AndroidTestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ApiCreatorTest extends AndroidTestCase {
private static final String BASE_PATH = PathManager.getHomePath() + "/../studio/google/appindexing/testData/ApiCreator";
private static final String ANDROID_MANIFEST = "AndroidManifest.xml";
private static final String ANDROID_MANIFEST_AFTER = "AndroidManifest_after.xml";
private static final String ANDROID_MANIFEST_HAS_GMS_TAG = "has_gms_tag.xml";
private static final String BUILD_GRADLE = "build.gradle";
private static final String BUILD_GRADLE_AFTER = "build_after.gradle";
private static final String BUILD_GRADLE_HAS_APP_INDEXING_DEPENDENCY = "has_app_indexing_dependency.gradle";
private static final String BUILD_GRADLE_LOW_VERSION_APP_INDEXING_DEPENDENCY = "low_version_app_indexing_dependency.gradle";
private static final String BUILD_GRADLE_LOW_VERSION_APP_INDEXING_DEPENDENCY_AFTER = "low_version_app_indexing_dependency_after.gradle";
private static final String BUILD_GRADLE_LOW_VERSION_PLAY_SERVICES_DEPENDENCY = "low_version_play_services_dependency.gradle";
private static final String BUILD_GRADLE_LOW_VERSION_PLAY_SERVICES_DEPENDENCY_AFTER = "low_version_play_services_dependency_after.gradle";
private static final String CARET_NOT_IN_JAVA_FILE = "caret_not_in_java_file.xml";
private static final String JAVA_FILE_CARET_OUTSIDE_ACTIVITY = "CaretOutsideActivity.java";
private static final String JAVA_FILE_NOT_ACTIVITY = "NotActivity.java";
private static final String JAVA_FILE_HAS_APP_INDEXING_START_AND_END = "HasAppIndexingStartAndEnd.java";
private static final String JAVA_FILE_HAS_APP_INDEXING_VIEW_AND_VIEW_END = "HasAppIndexingViewAndViewEnd.java";
private static final String JAVA_FILE_SPECIAL_STATEMENT = "SpecialStatement.java";
private static final String JAVA_FILE_EMPTY_ACTIVITY = "EmptyActivity.java";
private static final String JAVA_FILE_EMPTY_ACTIVITY_AFTER = "EmptyActivity_after.java";
private static final String JAVA_FILE_CONFLICT_IMPORTS = "ConflictImports.java";
private static final String JAVA_FILE_CONFLICT_IMPORTS_AFTER = "ConflictImports_after.java";
private static final String JAVA_FILE_WILD_CARD_IMPORTS = "WildCardImports.java";
private static final String JAVA_FILE_WILD_CARD_IMPORTS_AFTER = "WildCardImports_after.java";
private static final String JAVA_FILE_ONLY_START_CALL_AND_ADJUST_CLIENT_FIELD_INITIALIZER =
"OnlyStartCallAndAdjustClientFieldInitializer.java";
private static final String JAVA_FILE_ONLY_START_CALL_AND_ADJUST_CLIENT_FIELD_INITIALIZER_AFTER =
"OnlyStartCallAndAdjustClientFieldInitializer_after.java";
private static final String JAVA_FILE_ONLY_END_CALL_AND_ADJUST_CLIENT_INITIALIZING_STATEMENT =
"OnlyEndCallAndAdjustClientInitializingStatement.java";
private static final String JAVA_FILE_ONLY_END_CALL_AND_ADJUST_CLIENT_INITIALIZING_STATEMENT_AFTER =
"OnlyEndCallAndAdjustClientInitializingStatement_after.java";
private static final String JAVA_FILE_ONLY_VIEW_CALL = "OnlyViewCall.java";
private static final String JAVA_FILE_ONLY_VIEW_CALL_AFTER = "OnlyViewCall_after.java";
private static final String JAVA_FILE_ONLY_VIEW_END_CALL = "OnlyViewEndCall.java";
private static final String JAVA_FILE_ONLY_VIEW_END_CALL_AFTER = "OnlyViewEndCall_after.java";
private static final String JAVA_FILE_ACTION_NOT_INITIALIZED = "ActionNotInitialized.java";
private static final String JAVA_FILE_ACTION_NOT_INITIALIZED_AFTER = "ActionNotInitialized_after.java";
private static final String JAVA_FILE_CLIENT_FIELD = "ClientField.java";
private static final String JAVA_FILE_CLIENT_FIELD_AFTER = "ClientField_after.java";
private static final String JAVA_FILE_CLIENT_LOCAL_VARIABLE = "ClientLocalVariable.java";
private static final String JAVA_FILE_CLIENT_LOCAL_VARIABLE_FIELD_AFTER = "ClientLocalVariable_after.java";
private static final String JAVA_FILE_CLIENT_NOT_INITIALIZED = "ClientNotInitialized.java";
private static final String JAVA_FILE_CLIENT_NOT_INITIALIZED_AFTER = "ClientNotInitialized_after.java";
private static final String JAVA_FILE_CLIENT_INITIALIZED_WITHOUT_BUILDER = "ClientInitializedWithoutBuilder.java";
private static final String JAVA_FILE_CLIENT_INITIALIZED_WITHOUT_BUILDER_AFTER = "ClientInitializedWithoutBuilder_after.java";
private static final String JAVA_FILE_CLIENT_IN_DECLARATION_STATEMENT = "ClientInDeclarationStatement.java";
private static final String JAVA_FILE_CLIENT_IN_DECLARATION_STATEMENT_AFTER = "ClientInDeclarationStatement_after.java";
private static final String JAVA_FILE_OVERRIDE_METHOD = "OverrideMethod.java";
private static final String JAVA_FILE_OVERRIDE_METHOD_AFTER = "OverrideMethod_after.java";
private static final String JAVA_FILE_NO_KEYWORD_SUPER = "NoKeywordSuper.java";
private static final String JAVA_FILE_NO_KEYWORD_SUPER_AFTER = "NoKeywordSuper_after.java";
private static final String ACTION_WITH_NULL_DEEP_LINK =
"com.google.android.gms.appindexing.Action viewAction = com.google.android.gms.appindexing.Action.newAction(\n" +
" com.google.android.gms.appindexing.Action.TYPE_VIEW, // TODO: choose an action type.\n" +
" \"Main Page\", // TODO: Define a title for the content shown.\n" +
" // TODO: If you have web page content that matches this app activity's content,\n" +
" // make sure this auto-generated web page URL is correct.\n" +
" // Otherwise, set the URL to null.\n" +
" android.net.Uri.parse(\"http://host/path\"), \n" +
" // TODO: Make sure this auto-generated app deep link URI is correct.\n" +
" android.net.Uri.parse(\"android-app://com.example.creation/http/host/path\")\n" +
");";
private static final String ACTION_WITH_SCHEME_ONLY = ACTION_WITH_NULL_DEEP_LINK;
private static final String ACTION_WITHOUT_PATH =
"com.google.android.gms.appindexing.Action viewAction = com.google.android.gms.appindexing.Action.newAction(\n" +
" com.google.android.gms.appindexing.Action.TYPE_VIEW, // TODO: choose an action type.\n" +
" \"Main Page\", // TODO: Define a title for the content shown.\n" +
" // TODO: If you have web page content that matches this app activity's content,\n" +
" // make sure this auto-generated web page URL is correct.\n" +
" // Otherwise, set the URL to null.\n" +
" android.net.Uri.parse(\"http://www.example.com/path\"), \n" +
" // TODO: Make sure this auto-generated app deep link URI is correct.\n" +
" android.net.Uri.parse(\"android-app://com.example.creation/http/www.example.com/path\")\n" +
");";
public ApiCreatorTest() {
super(false);
}
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.setTestDataPath(BASE_PATH);
TemplateManagerImpl.setTemplateTesting(getProject(), getTestRootDisposable());
// Set an empty temporary code style. Otherwise, the variable name will follow users' custom setting.
CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(myFixture.getProject());
codeStyleSettings.clearCodeStyleSettings();
CodeStyleSettingsManager.getInstance().setTemporarySettings(codeStyleSettings);
}
@Override
public void tearDown() throws Exception {
super.tearDown();
CodeStyleSettingsManager.getInstance().dropTemporarySettings();
}
public void testNoAppIndexingDependencyInGradle() throws Exception {
doGradleFileTest(BUILD_GRADLE, BUILD_GRADLE_AFTER, null, true);
}
public void testHasAppIndexingDependencyInGradle() throws Exception {
doGradleFileTest(BUILD_GRADLE_HAS_APP_INDEXING_DEPENDENCY, BUILD_GRADLE_AFTER, "8.1.0", false);
}
public void testLowVersionAppIndexingDependencyInGradle() throws Exception {
doGradleFileTest(BUILD_GRADLE_LOW_VERSION_APP_INDEXING_DEPENDENCY, BUILD_GRADLE_LOW_VERSION_APP_INDEXING_DEPENDENCY_AFTER, "6.5.0",
true);
}
public void testLowVersionPlayServicesDependencyInGradle() throws Exception {
doGradleFileTest(BUILD_GRADLE_LOW_VERSION_PLAY_SERVICES_DEPENDENCY, BUILD_GRADLE_LOW_VERSION_PLAY_SERVICES_DEPENDENCY_AFTER, "6.5.0",
true);
}
public void testNoGmsTagInAndroidManifest() throws Exception {
doManifestFileTest(ANDROID_MANIFEST, ANDROID_MANIFEST_AFTER);
}
public void testHasGmsTagInAndroidManifest() throws Exception {
doManifestFileTest(ANDROID_MANIFEST_HAS_GMS_TAG, ANDROID_MANIFEST_AFTER);
}
public void testCaretInAndroidManifest() throws Exception {
doEnableTest(CARET_NOT_IN_JAVA_FILE, false);
}
public void testCaretOutsideActivity() throws Exception {
doEnableTest(JAVA_FILE_CARET_OUTSIDE_ACTIVITY, false);
}
public void testClassNotInheritActivity() throws Exception {
doEnableTest(JAVA_FILE_NOT_ACTIVITY, false);
}
public void testHasAppIndexingApiStartAndEnd() throws Exception {
doEnableTest(JAVA_FILE_HAS_APP_INDEXING_START_AND_END, false);
}
public void testHasAppIndexingApiViewAndViewEnd() throws Exception {
doEnableTest(JAVA_FILE_HAS_APP_INDEXING_VIEW_AND_VIEW_END, false);
}
/**
* The app indexing APIs are used in special statements,
* such as block statements, declaration statements and loop statement.
*/
public void testSpecialStatement() throws Exception {
doEnableTest(JAVA_FILE_SPECIAL_STATEMENT, false);
}
public void testEmptyActivity() throws Exception {
doJavaFileTest(JAVA_FILE_EMPTY_ACTIVITY, JAVA_FILE_EMPTY_ACTIVITY_AFTER);
}
public void testConflictImports() throws Exception {
doJavaFileTest(JAVA_FILE_CONFLICT_IMPORTS, JAVA_FILE_CONFLICT_IMPORTS_AFTER);
}
public void testWildCardImports() throws Exception {
doJavaFileTest(JAVA_FILE_WILD_CARD_IMPORTS, JAVA_FILE_WILD_CARD_IMPORTS_AFTER);
}
public void testOnlyAppIndexingStartAndAdjustGoogleApiClientFieldInitializer() throws Exception {
doJavaFileTest(JAVA_FILE_ONLY_START_CALL_AND_ADJUST_CLIENT_FIELD_INITIALIZER,
JAVA_FILE_ONLY_START_CALL_AND_ADJUST_CLIENT_FIELD_INITIALIZER_AFTER);
}
public void testOnlyAppIndexingEndAndAdjustClientInitializingStatement() throws Exception {
doJavaFileTest(JAVA_FILE_ONLY_END_CALL_AND_ADJUST_CLIENT_INITIALIZING_STATEMENT,
JAVA_FILE_ONLY_END_CALL_AND_ADJUST_CLIENT_INITIALIZING_STATEMENT_AFTER);
}
public void testOnlyAppIndexingView() throws Exception {
doJavaFileTest(JAVA_FILE_ONLY_VIEW_CALL, JAVA_FILE_ONLY_VIEW_CALL_AFTER);
}
public void testOnlyAppIndexingViewEnd() throws Exception {
doJavaFileTest(JAVA_FILE_ONLY_VIEW_END_CALL, JAVA_FILE_ONLY_VIEW_END_CALL_AFTER);
}
public void testGoogleApiActionNotInitialized() throws Exception {
doJavaFileTest(JAVA_FILE_ACTION_NOT_INITIALIZED, JAVA_FILE_ACTION_NOT_INITIALIZED_AFTER);
}
public void testWithGoogleApiClientField() throws Exception {
doJavaFileTest(JAVA_FILE_CLIENT_FIELD, JAVA_FILE_CLIENT_FIELD_AFTER);
}
public void testWithGoogleApiClientLocalVariable() throws Exception {
doJavaFileTest(JAVA_FILE_CLIENT_LOCAL_VARIABLE, JAVA_FILE_CLIENT_LOCAL_VARIABLE_FIELD_AFTER);
}
public void testGoogleApiClientNotInitialized() throws Exception {
doJavaFileTest(JAVA_FILE_CLIENT_NOT_INITIALIZED, JAVA_FILE_CLIENT_NOT_INITIALIZED_AFTER);
}
public void testGoogleApiClientInitializedWithoutBuilder() throws Exception {
doJavaFileTest(JAVA_FILE_CLIENT_INITIALIZED_WITHOUT_BUILDER, JAVA_FILE_CLIENT_INITIALIZED_WITHOUT_BUILDER_AFTER);
}
public void testGetGoogleApiClientFromDeclarationStatement() throws Exception {
doJavaFileTest(JAVA_FILE_CLIENT_IN_DECLARATION_STATEMENT, JAVA_FILE_CLIENT_IN_DECLARATION_STATEMENT_AFTER);
}
public void testWithOverridingMethod() throws Exception {
doJavaFileTest(JAVA_FILE_OVERRIDE_METHOD, JAVA_FILE_OVERRIDE_METHOD_AFTER);
}
/**
* Tests class with overriding method but not invoking overridden method through keyword super
*/
public void testNoKeywordSuper() throws Exception {
doJavaFileTest(JAVA_FILE_NO_KEYWORD_SUPER, JAVA_FILE_NO_KEYWORD_SUPER_AFTER);
}
public void testGenerateActionInitializerWithIncompleteDeepLink() throws Exception {
myFixture.configureByFiles(JAVA_FILE_EMPTY_ACTIVITY);
ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile());
assertEquals(creator.getActionStatement(null, "viewAction"), ACTION_WITH_NULL_DEEP_LINK);
assertEquals(creator.getActionStatement("http://", "viewAction"), ACTION_WITH_SCHEME_ONLY);
assertEquals(creator.getActionStatement("http://www.example.com", "viewAction"), ACTION_WITHOUT_PATH);
}
/**
* Tests whether the project is eligible for App Indexing API code inserting.
*
* @param filePath The path of the Java source file.
* @param enable If the creator (generator / intention) should be enabled.
*/
private void doEnableTest(@NotNull String filePath, boolean enable) {
myFixture.configureByFiles(filePath);
assertEquals(ApiCreator.eligibleForInsertingAppIndexingApiCode(myFixture.getEditor(), myFixture.getFile()), enable);
}
/**
* Test for creation in Java source file.
*/
private void doJavaFileTest(@NotNull String testFilePath, @NotNull String expectedFilePath) {
doEnableTest(testFilePath, true);
myFixture.copyFileToProject(ANDROID_MANIFEST);
final ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile());
CommandProcessor.getInstance().executeCommand(null, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
creator.insertAppIndexingApiCodeInJavaFile(creator.getDeepLinkOfActivity(), false);
}
});
}
}, null, null);
VirtualFile expectedFile = myFixture.copyFileToProject(expectedFilePath);
PsiFile expectedPsiFile = PsiManager.getInstance(myFixture.getProject()).findFile(expectedFile);
assertNotNull(expectedPsiFile);
String normalizedTestFile = getNormalizedContent(myFixture.getFile()).replace("<caret>", "");
assertEquals(normalizedTestFile, getNormalizedContent(expectedPsiFile));
}
/**
* Test for creation in build.gradle file.
*/
private void doGradleFileTest(@NotNull String testFilePath,
@NotNull String expectedFilePath,
@Nullable final String version,
final boolean needSync) {
VirtualFile testVirtual = myFixture.copyFileToProject(testFilePath, getGradleRelativePath());
myFixture.configureByFile(JAVA_FILE_EMPTY_ACTIVITY); // This file is not used. It's for opening editor.
final ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile());
creator.setGmsLibVersion(version);
CommandProcessor.getInstance().executeCommand(null, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
assertEquals(creator.insertGmsCompileDependencyInGradleIfNeeded(), needSync);
}
catch (ApiCreator.ApiCreatorException e) {
e.printStackTrace();
fail();
}
}
});
}
}, null, null);
PsiFile testPsi = PsiManager.getInstance(myFixture.getProject()).findFile(testVirtual);
PsiFile expectedPsi = myFixture.configureByFile(expectedFilePath);
assertNotNull(testPsi);
assertEquals(getNormalizedContent(testPsi), getNormalizedContent(expectedPsi));
}
/**
* Test for creation in AndroidManifest.xml file.
*/
private void doManifestFileTest(@NotNull String testFilePath, @NotNull String expectedFilePath) {
PsiFile[] files = myFixture.configureByFiles(testFilePath, expectedFilePath);
final ApiCreator creator = new ApiCreator(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile());
CommandProcessor.getInstance().executeCommand(null, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
assertTrue(myFixture.getFile() instanceof XmlFile);
creator.insertGmsVersionTagInManifestIfNeeded((XmlFile)myFixture.getFile());
}
});
}
}, null, null);
assertEquals(getNormalizedContent(myFixture.getFile()), getNormalizedContent(files[1]));
}
/**
* Normalizes file content: remove java.lang imports, comments, white space and line terminators.
*
* @param file The file to normalize.
* @return The normalized content.
*/
protected static String getNormalizedContent(@NotNull final PsiFile file) {
StringBuilder stringBuilder = new StringBuilder();
// Removes line terminators.
Iterable<String> lines = Splitter.onPattern("\r?\n").omitEmptyStrings().split(file.getText());
for (String line : lines) {
line = CharMatcher.WHITESPACE.removeFrom(line);
// Removes import statements of classes from java.lang package if it is a Java file.
// Because Android Studio will automatically import them.
if (line.startsWith("importjava.lang.")) {
continue;
}
stringBuilder.append(line);
}
return stringBuilder.toString();
}
@NotNull
protected String getGradleRelativePath() {
StringBuilder builder = new StringBuilder();
String projectPath = myFixture.getProject().getBasePath();
while (projectPath != null && projectPath.contains("/")) {
projectPath = projectPath.substring(0, projectPath.lastIndexOf("/"));
builder.append("../");
}
builder.deleteCharAt(builder.length() - 1);
String moduleFilePath = myModule.getModuleFilePath();
builder.append(moduleFilePath.substring(0, moduleFilePath.lastIndexOf("/")));
builder.append("/build.gradle");
return builder.toString();
}
}