1 /*******************************************************************************
2  * Copyright (c) 2016-2017 Red Hat Inc. and others
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     Mickael Istria, Sopot Cela (Red Hat Inc.)
13  *******************************************************************************/
14 package org.eclipse.ui.genericeditor.tests;
15 
16 import static org.junit.Assert.assertEquals;
17 import static org.junit.Assert.assertNotNull;
18 import static org.junit.Assert.assertTrue;
19 
20 import java.util.Arrays;
21 import java.util.Set;
22 import java.util.stream.Collectors;
23 
24 import org.junit.After;
25 import org.junit.Test;
26 
27 import org.eclipse.swt.SWT;
28 import org.eclipse.swt.custom.ST;
29 import org.eclipse.swt.custom.StyledText;
30 import org.eclipse.swt.widgets.Composite;
31 import org.eclipse.swt.widgets.Control;
32 import org.eclipse.swt.widgets.Display;
33 import org.eclipse.swt.widgets.Event;
34 import org.eclipse.swt.widgets.Shell;
35 import org.eclipse.swt.widgets.Table;
36 import org.eclipse.swt.widgets.TableItem;
37 import org.eclipse.swt.widgets.Widget;
38 
39 import org.eclipse.jface.text.ITextSelection;
40 import org.eclipse.jface.text.contentassist.ICompletionProposal;
41 import org.eclipse.jface.text.tests.util.DisplayHelper;
42 
43 import org.eclipse.ui.genericeditor.tests.contributions.BarContentAssistProcessor;
44 import org.eclipse.ui.genericeditor.tests.contributions.EnabledPropertyTester;
45 import org.eclipse.ui.genericeditor.tests.contributions.LongRunningBarContentAssistProcessor;
46 
47 import org.eclipse.ui.texteditor.ContentAssistAction;
48 import org.eclipse.ui.texteditor.ITextEditorActionConstants;
49 
50 /**
51  * @since 1.0
52  */
53 public class CompletionTest extends AbstratGenericEditorTest {
54 
55 	private Shell completionShell;
56 
57 	@Test
testCompletion()58 	public void testCompletion() throws Exception {
59 		final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
60 		editor.selectAndReveal(3, 0);
61 		openConentAssist();
62 		this.completionShell= findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
63 		final Table completionProposalList = findCompletionSelectionControl(completionShell);
64 		checkCompletionContent(completionProposalList);
65 		// TODO find a way to actually trigger completion and verify result against Editor content
66 		// Assert.assertEquals("Completion didn't complete", "bars are good for a beer.", ((StyledText)editor.getAdapter(Control.class)).getText());
67 	}
68 
69 	@Test
testCompletionUsingViewerSelection()70 	public void testCompletionUsingViewerSelection() throws Exception {
71 		final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
72 		editor.getDocumentProvider().getDocument(editor.getEditorInput()).set("abc");
73 		editor.selectAndReveal(0, 3);
74 		openConentAssist();
75 		this.completionShell= findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
76 		final Table completionProposalList = findCompletionSelectionControl(completionShell);
77 		assertTrue(new DisplayHelper() {
78 			@Override
79 			protected boolean condition() {
80 				return Arrays.stream(completionProposalList.getItems()).map(TableItem::getText).anyMatch("ABC"::equals);
81 			}
82 		}.waitForCondition(completionProposalList.getDisplay(), 200));
83 	}
84 
85 	@Test
testEnabledWhenCompletion()86 	public void testEnabledWhenCompletion() throws Exception {
87 		// Confirm that when disabled, a completion shell is present
88 		EnabledPropertyTester.setEnabled(false);
89 		createAndOpenFile("enabledWhen.txt", "bar 'bar'");
90 		final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
91 		editor.selectAndReveal(3, 0);
92 		openConentAssist();
93 		Shell[] afterShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells())
94 				.filter(Shell::isVisible)
95 				.filter(shell -> !beforeShells.contains(shell))
96 				.toArray(Shell[]::new);
97 		assertEquals("A new shell was found", 0, afterShells.length);
98 		cleanFileAndEditor();
99 
100 		// Confirm that when enabled, a completion shell is present
101 		EnabledPropertyTester.setEnabled(true);
102 		createAndOpenFile("enabledWhen.txt", "bar 'bar'");
103 		final Set<Shell> beforeEnabledShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
104 		editor.selectAndReveal(3, 0);
105 		openConentAssist();
106 		assertNotNull(findNewShell(beforeEnabledShells, editor.getSite().getShell().getDisplay()));
107 	}
108 
openConentAssist()109 	private void openConentAssist() {
110 		ContentAssistAction action = (ContentAssistAction) editor.getAction(ITextEditorActionConstants.CONTENT_ASSIST);
111 		action.update();
112 		action.run();
113 		waitAndDispatch(100);
114 	}
115 
116 	/**
117 	 * Checks that completion behaves as expected:
118 	 * 1. Computing is shown instantaneously
119 	 * 2. 1st proposal shown instantaneously
120 	 * 3. 2s later, 2nd proposal is shown
121 	 * @param completionProposalList the completion list
122 	 */
checkCompletionContent(final Table completionProposalList)123 	private void checkCompletionContent(final Table completionProposalList) {
124 		// should be instantaneous, but happens to go asynchronous on CI so let's allow a wait
125 		assertTrue(new DisplayHelper() {
126 			@Override
127 			protected boolean condition() {
128 				return completionProposalList.getItemCount() == 2;
129 			}
130 		}.waitForCondition(completionProposalList.getDisplay(), 200));
131 		final TableItem computingItem = completionProposalList.getItem(0);
132 		assertTrue("Missing computing info entry", computingItem.getText().contains("Computing")); //$NON-NLS-1$ //$NON-NLS-2$
133 		TableItem completionProposalItem = completionProposalList.getItem(1);
134 		final ICompletionProposal selectedProposal = (ICompletionProposal)completionProposalItem.getData();
135 		assertTrue("Incorrect proposal content", BarContentAssistProcessor.PROPOSAL.endsWith(selectedProposal .getDisplayString()));
136 		completionProposalList.setSelection(completionProposalItem);
137 		// asynchronous
138 		new DisplayHelper() {
139 			@Override
140 			protected boolean condition() {
141 				return completionProposalList.getItem(0) != computingItem && completionProposalList.getItemCount() == 2;
142 			}
143 		}.waitForCondition(completionProposalList.getDisplay(), LongRunningBarContentAssistProcessor.DELAY + 200);
144 		completionProposalItem = completionProposalList.getItem(0);
145 		assertTrue("Proposal content seems incorrect", BarContentAssistProcessor.PROPOSAL.endsWith(((ICompletionProposal)completionProposalItem.getData()).getDisplayString()));
146 		TableItem otherProposalItem = completionProposalList.getItem(1);
147 		assertTrue("Proposal content seems incorrect", LongRunningBarContentAssistProcessor.PROPOSAL.endsWith(((ICompletionProposal)otherProposalItem.getData()).getDisplayString()));
148 		assertEquals("Addition of completion proposal should keep selection", selectedProposal, completionProposalList.getSelection()[0].getData());
149 	}
150 
findNewShell(Set<Shell> beforeShells, Display display)151 	public static Shell findNewShell(Set<Shell> beforeShells, Display display) {
152 		Shell[] afterShells = Arrays.stream(display.getShells())
153 				.filter(Shell::isVisible)
154 				.filter(shell -> !beforeShells.contains(shell))
155 				.toArray(Shell[]::new);
156 		assertEquals("No new shell found", 1, afterShells.length);
157 		return afterShells[0];
158 	}
159 
160 	@Test
testCompletionFreeze_bug521484()161 	public void testCompletionFreeze_bug521484() throws Exception {
162 		final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
163 		editor.selectAndReveal(3, 0);
164 		openConentAssist();
165 		this.completionShell= findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
166 		final Table completionProposalList = findCompletionSelectionControl(this.completionShell);
167 		// should be instantaneous, but happens to go asynchronous on CI so let's allow a wait
168 		new DisplayHelper() {
169 			@Override
170 			protected boolean condition() {
171 				return completionProposalList.getItemCount() == 2;
172 			}
173 		}.waitForCondition(completionShell.getDisplay(), 200);
174 		assertEquals(2, completionProposalList.getItemCount());
175 		final TableItem computingItem = completionProposalList.getItem(0);
176 		assertTrue("Missing computing info entry", computingItem.getText().contains("Computing")); //$NON-NLS-1$ //$NON-NLS-2$
177 		// Some processors are long running, moving cursor can cause freeze (bug 521484)
178 		// asynchronous
179 		long timestamp = System.currentTimeMillis();
180 		emulatePressLeftArrowKey();
181 		DisplayHelper.sleep(editor.getSite().getShell().getDisplay(), 200); //give time to process events
182 		long processingDuration = System.currentTimeMillis() - timestamp;
183 		assertTrue("UI Thread frozen for " + processingDuration + "ms", processingDuration < LongRunningBarContentAssistProcessor.DELAY);
184 	}
185 
186 	@Test
testMoveCaretBackUsesAllProcessors_bug522255()187 	public void testMoveCaretBackUsesAllProcessors_bug522255() throws Exception {
188 		final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
189 		testCompletion();
190 		emulatePressLeftArrowKey();
191 		DisplayHelper.sleep(editor.getSite().getShell().getDisplay(), LongRunningBarContentAssistProcessor.DELAY + 500); // adding delay is a workaround for bug521484, use only 100ms without the bug
192 		this.completionShell= findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
193 		final Table completionProposalList = findCompletionSelectionControl(this.completionShell);
194 		assertEquals("Missing proposals from a Processor", 2, completionProposalList.getItemCount()); // replace with line below when #5214894 is done
195 		// checkCompletionContent(completionProposalList); // use this instead of assert above when #521484 is done
196 	}
197 
emulatePressLeftArrowKey()198 	private void emulatePressLeftArrowKey() {
199 		editor.selectAndReveal(((ITextSelection)editor.getSelectionProvider().getSelection()).getOffset() - 1, 0);
200 		StyledText styledText = (StyledText) editor.getAdapter(Control.class);
201 		Event e = new Event();
202 		e.type = ST.VerifyKey;
203 		e.widget = styledText;
204 		e.keyCode = SWT.ARROW_LEFT;
205 		e.display = styledText.getDisplay();
206 		styledText.notifyListeners(ST.VerifyKey, e);
207 	}
208 
findCompletionSelectionControl(Widget control)209 	public static Table findCompletionSelectionControl(Widget control) {
210 		if (control instanceof Table) {
211 			return (Table)control;
212 		} else if (control instanceof Composite) {
213 			for (Widget child : ((Composite)control).getChildren()) {
214 				Table res = findCompletionSelectionControl(child);
215 				if (res != null) {
216 					return res;
217 				}
218 			}
219 		}
220 		return null;
221 	}
222 
223 	@After
closeShell()224 	public void closeShell() {
225 		if (this.completionShell != null && !completionShell.isDisposed()) {
226 			completionShell.close();
227 		}
228 	}
229 }
230