1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.editors.layout.refactoring; 18 19 import com.android.utils.Pair; 20 21 import org.eclipse.core.resources.IFile; 22 import org.eclipse.core.runtime.IPath; 23 import org.eclipse.core.runtime.NullProgressMonitor; 24 import org.eclipse.jface.text.ITextSelection; 25 import org.eclipse.jface.text.TextSelection; 26 import org.eclipse.ltk.core.refactoring.Change; 27 import org.eclipse.ltk.core.refactoring.TextFileChange; 28 import org.eclipse.ui.IWorkbench; 29 import org.eclipse.ui.IWorkbenchPage; 30 import org.eclipse.ui.IWorkbenchWindow; 31 import org.eclipse.ui.PlatformUI; 32 import org.eclipse.ui.ide.IDE; 33 import org.w3c.dom.Attr; 34 import org.w3c.dom.Element; 35 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.Comparator; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 44 public class ExtractStyleRefactoringTest extends RefactoringTest { 45 @Override 46 protected boolean testCaseNeedsUniqueProject() { 47 return true; 48 } 49 50 public void testExtract1() throws Exception { 51 // Test extracting into a new style file 52 checkRefactoring("extractstyle1.xml", "newstyles.xml", "newstyle", 53 false /* removeExtracted */, false /* applyStyle */, null, 1, "@+id/button2"); 54 } 55 56 public void testExtract1b() throws Exception { 57 // Extract and apply new style 58 checkRefactoring("extractstyle1.xml", "newstyles2.xml", "newstyle", 59 false /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); 60 } 61 62 public void testExtract1c() throws Exception { 63 // Extract and remove extracted 64 checkRefactoring("extractstyle1.xml", "newstyles3.xml", "newstyle", 65 true /* removeExtracted */, false /* applyStyle */, null, 2, "@+id/button2"); 66 } 67 68 public void testExtract1d() throws Exception { 69 // Extract and apply style and remove extracted 70 checkRefactoring("extractstyle1.xml", "newstyles4.xml", "newstyle", 71 true /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); 72 } 73 74 public void testExtract2() throws Exception { 75 getTestDataFile(getProject(), "navigationstyles.xml", "res/values/navigationstyles.xml"); 76 77 // -Modify- the existing styles.xml file 78 checkRefactoring("extractstyle1.xml", "navigationstyles.xml", "newstyle", 79 true /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); 80 } 81 82 public void testExtract3() throws Exception { 83 // Select multiple elements - overlap in values. 84 checkRefactoring("extractstyle1.xml", "newstyles4.xml", "newstyle", 85 true /* removeExtracted */, true /* applyStyle */, null, 2, 86 "@+id/button1", "@+id/button2"); 87 } 88 89 // This test fails for some reason - not in the refactoring (checked manually) 90 // but the DOM model returns null when run in a test context. 91 public void testExtract4() throws Exception { 92 // Test extracting on a single caret position over an attribute: Should extract 93 // just that one attribute 94 checkRefactoringByOffset("extractstyle1.xml", "newstyles5.xml", "newstyle", 95 true /* removeExtracted */, true /* applyStyle */, null, 2, 96 "android:text^Color=\"#FF00FF\"", "android:text^Color=\"#FF00FF\""); 97 } 98 99 public void testExtract5() throws Exception { 100 // Test extracting on a range selection inside an element: should extract just 101 // the attributes that overlap the selection 102 checkRefactoringByOffset("extractstyle1.xml", "newstyles6.xml", "newstyle", 103 true /* removeExtracted */, true /* applyStyle */, null, 2, 104 "android:^textSize=\"20pt", 105 "android:id=\"@+id/button1\" android:layout_a^lignParentBottom"); 106 } 107 108 public void testExtract6() throws Exception { 109 // Test extracting on a single caret position which is not over any attributes: 110 checkRefactoringByOffset("extractstyle1.xml", "newstyles7.xml", "newstyle", 111 true /* removeExtracted */, true /* applyStyle */, null, 0, 112 "<Bu^tton", "<Bu^tton"); 113 } 114 115 public void testExtract7() throws Exception { 116 // Verify that even with a different namespace prefix we end up with android: 117 // in the extracted style 118 checkRefactoring("extractstyle2.xml", "newstyles8.xml", "newstyle", 119 true /* removeExtracted */, true /* applyStyle */, null, 2, 120 "@+id/button1", "@+id/button2"); 121 } 122 123 public void testExtract8() throws Exception { 124 // Test setting parent style 125 checkRefactoring("extractstyle1.xml", "newstyles3.xml", "newstyle", 126 true /* removeExtracted */, false /* applyStyle */, "android:Widget.Button", 127 2, "@+id/button2"); 128 } 129 130 // Check extract style on a selection of elements 131 private void checkRefactoring(String basename, String styleFileName, String newStyleName, 132 boolean removeExtracted, boolean applyStyle, String parentStyle, 133 int expectedModifiedFileCount, String... ids) throws Exception { 134 assertTrue(ids.length > 0); 135 136 IFile file = getLayoutFile(getProject(), basename); 137 TestContext info = setupTestContext(file, basename); 138 TestLayoutEditorDelegate layoutEditor = info.mLayoutEditorDelegate; 139 List<Element> selectedElements = getElements(info.mElement, ids); 140 141 // Open the file such that ModelManager.getExistingModelForRead() in DomUtilities 142 // will succeed 143 IWorkbench workbench = PlatformUI.getWorkbench(); 144 IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow(); 145 IWorkbenchPage page = activeWorkbenchWindow.getActivePage(); 146 IDE.openEditor(page, file); 147 148 ExtractStyleRefactoring refactoring = new ExtractStyleRefactoring(selectedElements, 149 layoutEditor); 150 checkRefactoring(basename, styleFileName, newStyleName, removeExtracted, applyStyle, 151 parentStyle, expectedModifiedFileCount, file, refactoring); 152 } 153 154 // Check extract style against a set of editor text locations 155 private void checkRefactoringByOffset(String basename, String styleFileName, 156 String newStyleName, boolean removeExtracted, boolean applyStyle, 157 String parentStyle, 158 int expectedModifiedFileCount, String beginCaretLocation, String endCaretLocation) 159 throws Exception { 160 IFile file = getLayoutFile(getProject(), basename); 161 int beginOffset = getCaretOffset(file, beginCaretLocation); 162 int endOffset = getCaretOffset(file, endCaretLocation); 163 164 TestContext info = setupTestContext(file, basename); 165 TestLayoutEditorDelegate layoutEditor = info.mLayoutEditorDelegate; 166 167 // Open the file such that ModelManager.getExistingModelForRead() in DomUtilities 168 // will succeed 169 IWorkbench workbench = PlatformUI.getWorkbench(); 170 IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow(); 171 IWorkbenchPage page = activeWorkbenchWindow.getActivePage(); 172 IDE.openEditor(page, file); 173 174 ITextSelection selection = new TextSelection(beginOffset, endOffset - beginOffset); 175 ExtractStyleRefactoring refactoring = new ExtractStyleRefactoring(file, 176 layoutEditor, selection, null); 177 checkRefactoring(basename, styleFileName, newStyleName, removeExtracted, applyStyle, 178 parentStyle, expectedModifiedFileCount, file, refactoring); 179 } 180 181 // Common test code used by the other two check methods 182 private void checkRefactoring(String basename, String styleFileName, String newStyleName, 183 boolean removeExtracted, boolean applyStyle, String parentStyle, 184 int expectedModifiedFileCount, IFile file, 185 ExtractStyleRefactoring refactoring) throws Exception { 186 refactoring.setStyleName(newStyleName); 187 refactoring.setApplyStyle(applyStyle); 188 refactoring.setRemoveExtracted(removeExtracted); 189 refactoring.setStyleFileName(styleFileName); 190 refactoring.setParent(parentStyle); 191 192 // Pick the attributes to extract -- for now everything (and where there are 193 // conflicting values, pick the first one) 194 Pair<Map<String, List<Attr>>, Set<Attr>> result = refactoring.getAvailableAttributes(); 195 Map<String, List<Attr>> availableAttributes = result.getFirst(); 196 Set<Attr> selected = result.getSecond(); 197 List<Attr> chosenAttributes = new ArrayList<Attr>(); 198 for (List<Attr> list : availableAttributes.values()) { 199 Collections.sort(list, new Comparator<Attr>() { 200 @Override 201 public int compare(Attr a1, Attr a2) { 202 return a1.getValue().compareTo(a2.getValue()); 203 } 204 }); 205 Attr attr = list.get(0); 206 if (selected.contains(attr)) { 207 chosenAttributes.add(attr); 208 } 209 } 210 refactoring.setChosenAttributes(chosenAttributes); 211 212 List<Change> changes = refactoring.computeChanges(new NullProgressMonitor()); 213 assertEquals(expectedModifiedFileCount, changes.size()); 214 215 Map<IPath,String> fileToGolden = new HashMap<IPath,String>(); 216 IPath sourcePath = file.getProjectRelativePath(); 217 fileToGolden.put(sourcePath, basename); 218 IPath newPath = refactoring.getStyleFile(getProject()).getProjectRelativePath(); 219 fileToGolden.put(newPath, styleFileName); 220 221 checkEdits(changes, fileToGolden, true); 222 223 int modifiedFileCount = 0; 224 for (Change change : changes) { 225 if (change instanceof TextFileChange) { 226 modifiedFileCount++; 227 } 228 } 229 assertEquals(expectedModifiedFileCount, modifiedFileCount); 230 } 231 232 } 233