| /******************************************************************************* |
| * Copyright (c) 2000, 2005 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.tests.multieditor; |
| |
| import java.io.IOException; |
| import java.net.URL; |
| |
| import junit.framework.TestSuite; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.ILog; |
| import org.eclipse.core.runtime.ILogListener; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.jface.action.IContributionItem; |
| import org.eclipse.jface.action.ToolBarContributionItem; |
| import org.eclipse.jface.action.ToolBarManager; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.ToolItem; |
| import org.eclipse.ui.IActionBars2; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IEditorRegistry; |
| import org.eclipse.ui.IViewPart; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.internal.WorkbenchPage; |
| import org.eclipse.ui.internal.WorkbenchPlugin; |
| import org.eclipse.ui.part.FileEditorInput; |
| import org.eclipse.ui.part.IContributedContentsView; |
| import org.eclipse.ui.part.MultiEditor; |
| import org.eclipse.ui.part.MultiEditorInput; |
| import org.eclipse.ui.tests.TestPlugin; |
| import org.eclipse.ui.tests.harness.util.CallHistory; |
| import org.eclipse.ui.tests.harness.util.UITestCase; |
| |
| /** |
| * Test MultiEditor behaviour to highlight some of the broken functionality. |
| * |
| * @since 3.1 |
| */ |
| public class MultiEditorTest extends UITestCase { |
| private static final String ACTION_TOOLTIP = "MultiEditorActionThing"; |
| |
| private static final String PROJECT_NAME = "TiledEditorProject"; |
| |
| private static final String CONTENT_OUTLINE = "org.eclipse.ui.views.ContentOutline"; |
| |
| private static final String TESTEDITOR_COOLBAR = "org.eclipse.ui.tests.multieditor.actionSet"; |
| |
| private static final String TILED_EDITOR_ID = "org.eclipse.ui.tests.multieditor.TiledEditor"; |
| |
| // tiled editor test files |
| private static final String DATA_FILES_DIR = "/data/org.eclipse.newMultiEditor/"; |
| |
| private static final String TEST01_TXT = "test01.txt"; |
| |
| private static final String TEST02_TXT = "test02.txt"; |
| |
| private static final String TEST03_ETEST = "test03.etest"; |
| |
| private static final String TEST04_PROPERTIES = "test04.properties"; |
| |
| private static final String BUILD_XML = "build.xml"; |
| |
| // |
| // call trace for the editor open - setFocus - close test |
| // |
| private static String[] gEditorOpenTrace = { "setInitializationData", |
| "init", "createPartControl", "createInnerPartControl", |
| "createInnerPartControl", "setFocus", "updateGradient", |
| "updateGradient", }; |
| |
| private static String[] gEditorFocusTrace = { "setInitializationData", |
| "init", "createPartControl", "createInnerPartControl", |
| "createInnerPartControl", "setFocus", "updateGradient", |
| "updateGradient", "updateGradient", "updateGradient", }; |
| |
| private static String[] gEditorCloseTrace = { "setInitializationData", |
| "init", "createPartControl", "createInnerPartControl", |
| "createInnerPartControl", "setFocus", "updateGradient", |
| "updateGradient", "updateGradient", "updateGradient", |
| "widgetsDisposed", "dispose" }; |
| |
| public static TestSuite suite() { |
| return new TestSuite(MultiEditorTest.class); |
| } |
| |
| /** |
| * Can catch a MultiEditor unexpect Exception on init. |
| */ |
| private EditorErrorListener fErrorListener; |
| |
| public MultiEditorTest(String tc) { |
| super(tc); |
| } |
| |
| /** |
| * Test that the test tiled editor can be opened with a basic |
| * MultiEditorInput with the same type of files. |
| * |
| * Test: Select a couple of files from navigator and use the TiledEditor |
| * menu to open the editor. It should open with the first selected file on |
| * top. |
| * |
| * @throws Throwable |
| * on an error |
| */ |
| public void testOpenBasicEditor() throws Throwable { |
| final String[] simpleFiles = { TEST01_TXT, TEST02_TXT }; |
| |
| IWorkbenchWindow window = fWorkbench.getActiveWorkbenchWindow(); |
| IWorkbenchPage page = window.getActivePage(); |
| |
| IProject testProject = findOrCreateProject(PROJECT_NAME); |
| |
| MultiEditorInput input = generateEditorInput(simpleFiles, testProject); |
| |
| // validate there are no NullPointerExceptions during editor |
| // initialization |
| openAndValidateEditor(page, input); |
| } |
| |
| /** |
| * Test that the public methods in TiledEditor (and MultiEditor) are called |
| * in the correct order from 3.0 to 3.1. |
| * |
| * Test: this test involves opening the tiled editor on 2 files, changing |
| * the focus from the first file to the second file, and closing the tiled |
| * editor. |
| * |
| * @throws Throwable |
| */ |
| public void testOpenTestFile() throws Throwable { |
| final String[] simpleFiles = { TEST01_TXT, TEST03_ETEST }; |
| |
| IWorkbenchWindow window = fWorkbench.getActiveWorkbenchWindow(); |
| WorkbenchPage page = (WorkbenchPage) window.getActivePage(); |
| |
| IProject testProject = findOrCreateProject(PROJECT_NAME); |
| |
| MultiEditorInput input = generateEditorInput(simpleFiles, testProject); |
| |
| // catches the framework NPE |
| IEditorPart editor = openAndValidateEditor(page, input); |
| |
| // did we get a multieditor back? |
| assertTrue(editor instanceof MultiEditor); |
| MultiEditor multiEditor = (MultiEditor) editor; |
| |
| chewUpEvents(); |
| |
| // listHistory(((TiledEditor) multiEditor).callHistory); |
| |
| // check the public API called for opening the TiledEditor |
| // ((TiledEditor) multiEditor).callHistory.printToConsole(); |
| assertTrue("The editor open trace was incorrect", |
| ((TiledEditor) multiEditor).callHistory |
| .verifyOrder(gEditorOpenTrace)); |
| |
| // swap focus to the last editor, which is the test editor |
| // with the test coolbar contribution |
| IEditorPart[] innerEditors = multiEditor.getInnerEditors(); |
| innerEditors[innerEditors.length - 1].setFocus(); |
| |
| chewUpEvents(); |
| |
| // ((TiledEditor) multiEditor).callHistory.printToConsole(); |
| assertTrue("Editor setFocus trace was incorrect", |
| ((TiledEditor) multiEditor).callHistory |
| .verifyOrder(gEditorFocusTrace)); |
| |
| page.closeEditor(multiEditor, false); |
| |
| chewUpEvents(); |
| |
| // ((TiledEditor) multiEditor).callHistory.printToConsole(); |
| assertTrue("Editor close trace was incorrect", |
| ((TiledEditor) multiEditor).callHistory |
| .verifyOrder(gEditorCloseTrace)); |
| } |
| |
| /** |
| * Test that coolbar items in the workbench are updated when focus moves |
| * through the different inner editors ... this test as written is not 100% |
| * accurate, as the items are enabled. |
| * |
| * Test: Open two files where the first is a text file and the second is of |
| * type etest. Change focus to the etest file, and the coolbar should update |
| * with a new action icon and it should be enabled. |
| * |
| * @throws Throwable |
| * on an error |
| */ |
| public void testTrackCoolBar() throws Throwable { |
| final String[] simpleFiles = { TEST01_TXT, TEST02_TXT, |
| TEST04_PROPERTIES, BUILD_XML, TEST03_ETEST }; |
| |
| IWorkbenchWindow window = fWorkbench.getActiveWorkbenchWindow(); |
| WorkbenchPage page = (WorkbenchPage) window.getActivePage(); |
| |
| IProject testProject = findOrCreateProject(PROJECT_NAME); |
| |
| MultiEditorInput input = generateEditorInput(simpleFiles, testProject); |
| |
| // catches the framework NPE |
| IEditorPart editor = openAndValidateEditor(page, input); |
| |
| // did we get a multieditor back? |
| assertTrue(editor instanceof MultiEditor); |
| MultiEditor multiEditor = (MultiEditor) editor; |
| |
| chewUpEvents(); |
| |
| // get access to the appropriate coolbar |
| IContributionItem contribution = findMyCoolBar(page); |
| |
| // our test editor contribution should not be visible |
| // but it should be enabled |
| validateIconState(contribution, ACTION_TOOLTIP, false); |
| |
| // swap focus to the last editor, which is the test editor |
| // with the test coolbar contribution |
| IEditorPart[] innerEditors = multiEditor.getInnerEditors(); |
| innerEditors[innerEditors.length - 1].setFocus(); |
| |
| chewUpEvents(); |
| |
| contribution = findMyCoolBar(page); |
| assertNotNull("It should be available now", contribution); |
| |
| // our test editor contribution should now be visible and |
| // enabled |
| validateIconState(contribution, ACTION_TOOLTIP, true); |
| |
| } |
| |
| /** |
| * Test that the outline view is updated when focus moves from an editor to |
| * the ant editor. |
| * |
| * Test: Open 2 files where the first is a text file and the second is an |
| * ant file. Set focus on the ant file, and the outline should be updated to |
| * reflect the buildfile outline. |
| * |
| * @throws Throwable |
| * on an error |
| */ |
| public void testTrackOutline() throws Throwable { |
| final String[] simpleFiles = { TEST01_TXT, TEST02_TXT, |
| TEST04_PROPERTIES, BUILD_XML, TEST03_ETEST }; |
| |
| IWorkbenchWindow window = fWorkbench.getActiveWorkbenchWindow(); |
| WorkbenchPage page = (WorkbenchPage) window.getActivePage(); |
| |
| IProject testProject = findOrCreateProject(PROJECT_NAME); |
| |
| MultiEditorInput input = generateEditorInput(simpleFiles, testProject); |
| |
| // catches the framework NPE |
| IEditorPart editor = openAndValidateEditor(page, input); |
| |
| // did we get a multieditor back? |
| assertTrue(editor instanceof MultiEditor); |
| MultiEditor multiEditor = (MultiEditor) editor; |
| |
| chewUpEvents(); |
| |
| // Swap to the second last editor, which should be the ant |
| // build editor. |
| IEditorPart[] innerEditors = multiEditor.getInnerEditors(); |
| innerEditors[innerEditors.length - 2].setFocus(); |
| chewUpEvents(); |
| |
| // get the outline view part |
| IViewPart outline = window.getActivePage().showView(CONTENT_OUTLINE); |
| assertNotNull(outline); |
| |
| // find out who is contributing the outline view. |
| IContributedContentsView view = (IContributedContentsView) outline |
| .getAdapter(IContributedContentsView.class); |
| IWorkbenchPart part = view.getContributingPart(); |
| assertNotNull("The Outline view has not been updated by the editor", |
| part); |
| assertTrue("The Outline view is not talking to an editor", |
| part instanceof IEditorPart); |
| |
| IEditorPart outlineEditor = (IEditorPart) part; |
| |
| // the active inner editor (the ant editor) should also |
| // be the outline editor contributor ... this works in |
| // 3.0, fails in 3.1 |
| assertEquals("The Outline view is not talking to the correct editor", |
| multiEditor.getActiveEditor(), outlineEditor); |
| |
| page.closeEditor(editor, false); |
| chewUpEvents(); |
| |
| view = (IContributedContentsView) outline |
| .getAdapter(IContributedContentsView.class); |
| assertNull(view.getContributingPart()); |
| } |
| |
| /** |
| * Return the test editor coolbar. |
| * |
| * @param page |
| * the workbench page |
| * @return the IContributionItem for the test editor cool bar. |
| */ |
| private IContributionItem findMyCoolBar(WorkbenchPage page) { |
| // listItems(page); |
| IContributionItem contribution = ((IActionBars2) page.getActionBars()) |
| .getCoolBarManager().find(TESTEDITOR_COOLBAR); |
| // assertNotNull(contribution); |
| |
| return contribution; |
| } |
| |
| /** |
| * Validate the state of an icon in the toolbar. |
| * |
| * @param contribution |
| * the high level contribution from the coolbar to look through |
| * @param tooltip |
| * the string that matches the action's tooltip |
| * @param state |
| * should it be true or false |
| */ |
| private void validateIconState(IContributionItem contribution, |
| String tooltip, boolean state) { |
| assertTrue("We might not have the contribution or expect it", |
| contribution != null || !state); |
| if (contribution == null) { |
| return; |
| } |
| |
| ToolBarManager toolBarManager = (ToolBarManager) ((ToolBarContributionItem) contribution) |
| .getToolBarManager(); |
| ToolBar bar = toolBarManager.getControl(); |
| |
| assertTrue("It's OK for bar to be null if we expect state to be false", |
| bar != null || !state); |
| if (bar == null) { |
| return; |
| } |
| |
| ToolItem[] items = bar.getItems(); |
| for (int i = 0; i < items.length; ++i) { |
| // System.err.println("Item: " + items[i].getToolTipText()); |
| if (tooltip.equals(items[i].getToolTipText())) { |
| assertEquals("Invalid icon state for " + tooltip, state, |
| items[i].getEnabled()); |
| return; |
| } |
| } |
| assertFalse("We haven't found our item", state); |
| } |
| |
| /** |
| * Create the project to work in. If it already exists, just open it. |
| * |
| * @param projectName |
| * the name of the project to create |
| * @return the newly opened project |
| * @throws CoreException |
| */ |
| private IProject findOrCreateProject(String projectName) |
| throws CoreException { |
| IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| IProject testProject = workspace.getRoot().getProject(projectName); |
| if (!testProject.exists()) { |
| testProject.create(null); |
| } |
| testProject.open(null); |
| return testProject; |
| } |
| |
| /** |
| * Internal printing method, only used for investigation. |
| * |
| * @param page |
| * the workbench page |
| */ |
| private void checkView(WorkbenchPage page) { |
| IViewPart[] views = page.getViews(); |
| for (int i = 0; i < views.length; ++i) { |
| System.err.println("view: " + views[i].getViewSite().getId() + "/" |
| + views[i].getViewSite().getRegisteredName()); |
| } |
| } |
| |
| /** |
| * Print the call history to console. Only used for investigation. |
| * |
| * @param history |
| * the editor call history object. |
| */ |
| private void listHistory(CallHistory history) { |
| history.printToConsole(); |
| } |
| |
| /** |
| * List bits and internals of the coolbar manager contribution items. Only |
| * used for investigation. |
| * |
| * @param page |
| * the workbench page |
| */ |
| private void listItems(WorkbenchPage page) { |
| checkView(page); |
| IContributionItem[] items = ((IActionBars2) page.getActionBars()) |
| .getCoolBarManager().getItems(); |
| System.err.println("Length: " + items.length); |
| for (int i = 0; i < items.length; ++i) { |
| System.err.println("" + items[i].isEnabled() + ":" |
| + items[i].isVisible() + " " + items[i].getId() + "\n\t" |
| + items[i].getClass().getName()); |
| if (items[i] instanceof ToolBarContributionItem) { |
| displayItem(items[i]); |
| } |
| } |
| System.err.println("----"); |
| } |
| |
| /** |
| * Display bits of contribution item internals to the console. Only used for |
| * investigation. |
| * |
| * @param contributionItem |
| * the IContributionItem to display |
| */ |
| private void displayItem(IContributionItem contributionItem) { |
| ToolBarManager toolBarManager = (ToolBarManager) ((ToolBarContributionItem) contributionItem) |
| .getToolBarManager(); |
| ToolBar bar = toolBarManager.getControl(); |
| if (bar == null) { |
| System.err.println("\tInfo-items: -1"); |
| } else { |
| System.err.println("\tInfo-items: " + bar.getItemCount() + "/" |
| + bar.getEnabled() + "/" + bar.isEnabled() + "/" |
| + bar.isVisible()); |
| ToolItem[] tools = bar.getItems(); |
| for (int t = 0; t < tools.length; ++t) { |
| System.err.println("\t\titem: " + tools[t].getEnabled() + " " |
| + tools[t].getToolTipText()); |
| } |
| } |
| } |
| |
| /** |
| * After an internal action, see if there are any outstanding SWT events. |
| */ |
| private void chewUpEvents() throws InterruptedException { |
| Thread.sleep(500); |
| Display display = Display.getCurrent(); |
| while (display.readAndDispatch()) |
| ; |
| } |
| |
| /** |
| * Open the test editor. It does basic validation that there is no |
| * NullPointerException during initialization. |
| * |
| * @param page |
| * the workbench page |
| * @param input |
| * the editor input with multiple files |
| * @return the MultiEditor |
| * @throws PartInitException |
| */ |
| private IEditorPart openAndValidateEditor(IWorkbenchPage page, |
| MultiEditorInput input) throws PartInitException { |
| |
| IEditorPart editorPart = null; |
| try { |
| setupErrorListener(); |
| editorPart = page |
| .openEditor(input, MultiEditorTest.TILED_EDITOR_ID); |
| assertNotNull(editorPart); |
| |
| // 3.1.0 only |
| // assertFalse("The editor never actualized", |
| // editorPart instanceof ErrorEditorPart); |
| |
| assertTrue("Creation error: " + fErrorListener.fErrorMsg, |
| fErrorListener.fNoError); |
| } finally { |
| removeErrorListener(); |
| } |
| return editorPart; |
| } |
| |
| /** |
| * Set up to catch any editor initialization exceptions. |
| * |
| */ |
| private void setupErrorListener() { |
| final ILog log = WorkbenchPlugin.getDefault().getLog(); |
| fErrorListener = new EditorErrorListener(); |
| log.addLogListener(fErrorListener); |
| } |
| |
| /** |
| * Remove the editor error listener. |
| */ |
| private void removeErrorListener() { |
| final ILog log = WorkbenchPlugin.getDefault().getLog(); |
| if (fErrorListener != null) { |
| log.removeLogListener(fErrorListener); |
| fErrorListener = null; |
| } |
| } |
| |
| /** |
| * Create the multi editor input in the given project. Creates the files in |
| * the project from template files in the classpath if they don't already |
| * exist. |
| * |
| * @param simpleFiles |
| * the array of filenames to copy over |
| * @param testProject |
| * the project to create the files in |
| * @return the editor input used to open the multieditor |
| * @throws CoreException |
| * @throws IOException |
| */ |
| private MultiEditorInput generateEditorInput(String[] simpleFiles, |
| IProject testProject) throws CoreException, IOException { |
| String[] ids = new String[simpleFiles.length]; |
| IEditorInput[] inputs = new IEditorInput[simpleFiles.length]; |
| IEditorRegistry registry = fWorkbench.getEditorRegistry(); |
| |
| for (int f = 0; f < simpleFiles.length; ++f) { |
| IFile f1 = testProject.getFile(simpleFiles[f]); |
| if (!f1.exists()) { |
| URL file = Platform.asLocalURL(TestPlugin.getDefault() |
| .getBundle().getEntry(DATA_FILES_DIR + simpleFiles[f])); |
| f1.create(file.openStream(), true, null); |
| } |
| ids[f] = registry.getDefaultEditor(f1.getName()).getId(); |
| inputs[f] = new FileEditorInput(f1); |
| } |
| |
| MultiEditorInput input = new MultiEditorInput(ids, inputs); |
| return input; |
| } |
| |
| /** |
| * Close any editors at the beginner of a test, so the test can be clean. |
| */ |
| protected void doSetUp() throws Exception { |
| super.doSetUp(); |
| IWorkbenchPage page = fWorkbench.getActiveWorkbenchWindow() |
| .getActivePage(); |
| page.closeAllEditors(false); |
| |
| } |
| |
| /** |
| * Listens for the standard message that indicates the MultiEditor failed |
| * ... usually caused by incorrect framework initialization that doesn't set |
| * the innerChildren. |
| * |
| * @since 3.1 |
| * |
| */ |
| public static class EditorErrorListener implements ILogListener { |
| public boolean fNoError = true; |
| |
| public String fErrorMsg = null; |
| |
| public void logging(IStatus status, String plugin) { |
| fNoError = false; |
| fErrorMsg = status.getMessage(); |
| Throwable ex = status.getException(); |
| if (ex != null) { |
| fErrorMsg += ": " + ex.getMessage(); |
| } |
| } |
| } |
| } |