diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletePopupWindowTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletePopupWindowTest.java new file mode 100644 index 0000000..e40b473 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletePopupWindowTest.java @@ -0,0 +1,466 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; + +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.DefaultListCellRenderer; +import javax.swing.InputMap; +import javax.swing.JFrame; +import javax.swing.JTextArea; +import javax.swing.KeyStroke; +import javax.swing.ListCellRenderer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class AutoCompletePopupWindowTest { + + private JFrame frame; + private JTextArea textArea; + private DefaultCompletionProvider provider; + private AutoCompletion ac; + private AutoCompletePopupWindow popupWindow; + + + @BeforeEach + void setUp() { + Assumptions.assumeFalse(GraphicsEnvironment.isHeadless()); + } + + + @AfterEach + void tearDown() { + if (frame != null) { + frame.dispose(); + frame = null; + } + } + + + /** + * Realizes a text area with an active, visible popup window containing three matches + * for "foo". Called explicitly at the start of each test, rather than from + * {@code @BeforeEach}, since {@link SwingRunnerExtension} only runs {@code @Test} methods + * on the EDT - window creation/visibility changes made here need to happen on the EDT too, + * alongside the assertions that depend on them. + */ + private void showPopup() { + + provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo", "foo's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobaz", "foobaz's summary")); + + ac = new AutoCompletion(provider); + ac.setAutoCompleteEnabled(true); + ac.setAutoCompleteSingleChoices(false); + + textArea = new JTextArea(); + ac.install(textArea); + + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + popupWindow = ac.getPopupWindow(); + Assertions.assertNotNull(popupWindow, "Sanity check: popup should be visible with 3 matches"); + } + + + private static void fireKeyAction(JTextArea textArea, KeyStroke ks) { + InputMap im = textArea.getInputMap(); + ActionMap am = textArea.getActionMap(); + Object key = im.get(ks); + Assertions.assertNotNull(key, "No action bound to keystroke " + ks); + Action action = am.get(key); + Assertions.assertNotNull(action, "No action found for key " + key); + action.actionPerformed(new ActionEvent(textArea, ActionEvent.ACTION_PERFORMED, "")); + } + + + @Test + void getSelection_returnsFirstItemSelectedByDefault() { + showPopup(); + + Assertions.assertEquals("foo", popupWindow.getSelection().getReplacementText()); + } + + + @Test + void downAction_movesSelectionToNextItemAndWrapsAround() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); + Assertions.assertEquals("foobar", popupWindow.getSelection().getReplacementText()); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); + Assertions.assertEquals("foobaz", popupWindow.getSelection().getReplacementText()); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); + Assertions.assertEquals("foo", popupWindow.getSelection().getReplacementText(), + "Selection should wrap back around to the first item"); + } + + + @Test + void upAction_wrapsToLastItemFromFirst() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)); + Assertions.assertEquals("foobaz", popupWindow.getSelection().getReplacementText(), + "Selection should wrap to the last item when moving up from the first"); + } + + + @Test + void endAction_selectsLastItem() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0)); + Assertions.assertEquals("foobaz", popupWindow.getSelection().getReplacementText()); + } + + + @Test + void homeAction_selectsFirstItem() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0)); + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0)); + Assertions.assertEquals("foo", popupWindow.getSelection().getReplacementText()); + } + + + @Test + void pageDownAction_movesToLastItemWhenVisibleRowsExceedRemaining() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)); + Assertions.assertEquals("foobaz", popupWindow.getSelection().getReplacementText()); + } + + + @Test + void pageUpAction_movesToFirstItemWhenVisibleRowsExceedIndex() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0)); + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)); + Assertions.assertEquals("foo", popupWindow.getSelection().getReplacementText()); + } + + + @Test + void enterAction_insertsSelectedCompletionAndHidesPopup() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); + + Assertions.assertEquals("foobar", textArea.getText()); + Assertions.assertFalse(popupWindow.isVisible()); + } + + + @Test + void tabAction_alsoInsertsSelectedCompletion() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); + Assertions.assertEquals("foo", textArea.getText()); + } + + + @Test + void escapeAction_hidesPopupWithoutInsertingText() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); + Assertions.assertFalse(popupWindow.isVisible()); + Assertions.assertEquals("foo", textArea.getText()); + } + + + @Test + void getSelection_afterHiding_returnsLastSelection() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); + + Assertions.assertFalse(popupWindow.isVisible()); + Assertions.assertEquals("foobar", popupWindow.getSelection().getReplacementText(), + "getSelection() should remember the last selection even once hidden"); + } + + + @Test + void mouseClicked_doubleClick_insertsSelectedCompletion() { + showPopup(); + + MouseEvent doubleClick = new MouseEvent(popupWindow, MouseEvent.MOUSE_CLICKED, + System.currentTimeMillis(), 0, 0, 0, 2, false); + popupWindow.mouseClicked(doubleClick); + + Assertions.assertEquals("foo", textArea.getText()); + } + + + @Test + void mouseClicked_singleClick_doesNotInsertCompletion() { + showPopup(); + + MouseEvent singleClick = new MouseEvent(popupWindow, MouseEvent.MOUSE_CLICKED, + System.currentTimeMillis(), 0, 0, 0, 1, false); + popupWindow.mouseClicked(singleClick); + + Assertions.assertEquals("foo", textArea.getText()); + Assertions.assertTrue(popupWindow.isVisible()); + } + + + @Test + void setCompletions_selectsFirstItem() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); + Assertions.assertEquals("foobar", popupWindow.getSelection().getReplacementText()); + + popupWindow.setCompletions(java.util.List.of( + new BasicCompletion(provider, "zzz"), + new BasicCompletion(provider, "zzzTop"))); + + Assertions.assertEquals("zzz", popupWindow.getSelection().getReplacementText()); + } + + + @Test + void getSetListCellRenderer_roundTrips() { + showPopup(); + + ListCellRenderer renderer = new DefaultListCellRenderer(); + popupWindow.setListCellRenderer(renderer); + Assertions.assertSame(renderer, popupWindow.getListCellRenderer()); + } + + + @Test + void getDescriptionWindowColor_beforeDescWindowCreated_returnsNullByDefault() { + showPopup(); + + Assertions.assertNull(popupWindow.getDescriptionWindowColor()); + } + + + @Test + void setDescriptionWindowColor_beforeDescWindowCreated_appliedWhenCreated() { + showPopup(); + + Color color = new Color(10, 20, 30); + popupWindow.setDescriptionWindowColor(color); + Assertions.assertEquals(color, popupWindow.getDescriptionWindowColor()); + + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.doCompletion(); + + Assertions.assertEquals(color, popupWindow.getDescWindow().getBackground()); + } + + + @Test + void setDescriptionWindowSize_beforeDescWindowCreated_appliedWhenCreated() { + showPopup(); + + Dimension size = new Dimension(444, 111); + popupWindow.setDescriptionWindowSize(size); + + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.doCompletion(); + + Assertions.assertEquals(size, popupWindow.getDescWindow().getSize()); + } + + + @Test + void setDescriptionWindowSize_afterDescWindowCreated_resizesImmediately() { + showPopup(); + + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.doCompletion(); + Assertions.assertNotNull(popupWindow.getDescWindow()); + + Dimension size = new Dimension(321, 123); + popupWindow.setDescriptionWindowSize(size); + + Assertions.assertEquals(size, popupWindow.getDescWindow().getSize()); + } + + + @Test + void getDescWindow_nullUntilShown() { + showPopup(); + + Assertions.assertNull(popupWindow.getDescWindow()); + } + + + @Test + void disposeDescWindow_whenNoneCreated_doesNotThrow() { + showPopup(); + + Assertions.assertDoesNotThrow(popupWindow::disposeDescWindow); + } + + + @Test + void disposeDescWindow_disposesAndClearsReference() { + showPopup(); + + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.doCompletion(); + AbstractDescWindow descWindow = popupWindow.getDescWindow(); + Assertions.assertNotNull(descWindow); + + popupWindow.disposeDescWindow(); + + Assertions.assertNull(popupWindow.getDescWindow()); + Assertions.assertFalse(descWindow.isDisplayable()); + } + + + @Test + void toggleDescriptionWindow_notOnDemand_doesNothing() { + showPopup(); + + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.doCompletion(); + + popupWindow.toggleDescriptionWindow(); + Assertions.assertNotNull(popupWindow.getDescWindow()); + Assertions.assertTrue(popupWindow.getDescWindow().isVisible()); + } + + + @Test + void toggleDescriptionWindow_notVisible_doesNothing() { + showPopup(); + + ac.setDescWindowVisibility(DescWindowVisibility.ON_DEMAND); + popupWindow.setVisible(false); + + Assertions.assertDoesNotThrow(popupWindow::toggleDescriptionWindow); + Assertions.assertNull(popupWindow.getDescWindow()); + } + + + @Test + void installAndUninstallDescWindowToggleKey_addsAndRemovesBinding() { + showPopup(); + + JTextArea otherTextArea = new JTextArea(); + KeyStroke ks = KeyStroke.getKeyStroke("ctrl alt T"); + InputMap im = otherTextArea.getInputMap(); + ActionMap am = otherTextArea.getActionMap(); + + Assertions.assertNull(im.get(ks)); + + AutoCompletePopupWindow.installDescWindowToggleKey(ac, otherTextArea, ks); + Assertions.assertNotNull(im.get(ks)); + Assertions.assertNotNull(am.get(im.get(ks))); + + AutoCompletePopupWindow.uninstallDescWindowToggleKey(otherTextArea, ks); + Assertions.assertNull(im.get(ks)); + } + + + @Test + void updateUI_doesNotThrow() { + showPopup(); + + Assertions.assertDoesNotThrow(popupWindow::updateUI); + } + + + @Test + void updateUI_withDescWindow_doesNotThrow() { + showPopup(); + + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.doCompletion(); + Assertions.assertDoesNotThrow(popupWindow::updateUI); + } + + + @Test + void setVisible_false_clearsListModelAndRemembersSelection() { + showPopup(); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); + Completion expected = popupWindow.getSelection(); + + popupWindow.setVisible(false); + + Assertions.assertFalse(popupWindow.isVisible()); + Assertions.assertEquals(expected, popupWindow.getSelection()); + } + + + @Test + void setVisible_true_reInstallsKeyBindingsAndSelectsFirstItem() { + showPopup(); + + popupWindow.setVisible(false); + Assertions.assertFalse(popupWindow.isVisible()); + + ac.doCompletion(); + AutoCompletePopupWindow newPopupWindow = ac.getPopupWindow(); + Assertions.assertTrue(newPopupWindow.isVisible()); + Assertions.assertEquals("foo", newPopupWindow.getSelection().getReplacementText()); + } + + + @Test + void copyAction_noDescWindowVisible_doesNotThrow() { + showPopup(); + + Assertions.assertDoesNotThrow(() -> + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_C, + java.awt.Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()))); + } + + + @Test + void leftRightActions_atCaretBounds_doNotThrow() { + showPopup(); + + textArea.setCaretPosition(0); + Assertions.assertDoesNotThrow(() -> + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0))); + + textArea.setCaretPosition(textArea.getDocument().getLength()); + Assertions.assertDoesNotThrow(() -> + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0))); + } + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java index ebf92a1..5003fbc 100644 --- a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java @@ -1170,6 +1170,130 @@ void parameterizedCompletionStartAction_popupVisibleWithParamCompletionSelected_ } + @Test + void getCompletionProvider_returnsProviderPassedToConstructor() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = new AutoCompletion(provider); + Assertions.assertSame(provider, ac.getCompletionProvider()); + + DefaultCompletionProvider newProvider = new DefaultCompletionProvider(); + ac.setCompletionProvider(newProvider); + Assertions.assertSame(newProvider, ac.getCompletionProvider()); + } + + + @Test + void getSetDescWindowFactory_roundTripsAndIsUsedToCreateDescWindow() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo", "foo's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); + + AutoCompletion ac = new AutoCompletion(provider); + Assertions.assertNull(ac.getDescWindowFactory()); + + AutoCompleteDescWindow[] created = new AutoCompleteDescWindow[1]; + java.util.function.Function factory = w -> { + created[0] = new AutoCompleteDescWindow(w, ac); + return created[0]; + }; + ac.setDescWindowFactory(factory); + Assertions.assertSame(factory, ac.getDescWindowFactory()); + + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + Assertions.assertSame(created[0], ac.getPopupWindow().getDescWindow(), + "The custom factory should have been used to create the description window"); + } + + + @Test + void setDescriptionWindowSize_beforePopupCreated_appliedWhenCreated() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo", "foo's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.setDescriptionWindowSize(444, 111); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + Assertions.assertEquals(new Dimension(444, 111), ac.getPopupWindow().getDescWindow().getSize()); + } + + + @Test + void isSetAutoActivationEnabled_installedComponent_addsAndRemovesListener() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertFalse(ac.isAutoActivationEnabled()); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + ac.setAutoActivationEnabled(true); + Assertions.assertTrue(ac.isAutoActivationEnabled()); + + ac.setAutoActivationEnabled(false); + Assertions.assertFalse(ac.isAutoActivationEnabled()); + } + + + @Test + void startParameterizedCompletionAssistance_noParamsAndTypedStartChar_startsTemplateAssistance() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "void"); + provider.addCompletion(completion); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setParameterAssistanceEnabled(true); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.insertCompletion(completion, true); + + Assertions.assertTrue(ac.hideChildWindows(), + "Assistance should start even with no params, since the start char was typed"); + } + + + @Test + void startParameterizedCompletionAssistance_noParamsAndStartCharNotTyped_insertsParensLiterally() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "void"); + provider.addCompletion(completion); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setParameterAssistanceEnabled(true); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.insertCompletion(completion, false); + + Assertions.assertEquals("foo()", textArea.getText()); + Assertions.assertFalse(ac.hideChildWindows()); + } + + @Test void autoCompleteAction_disabled_doesNotShowPopup() { DefaultCompletionProvider provider = new DefaultCompletionProvider(); diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionContextTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionContextTest.java index 13af86f..d163bea 100644 --- a/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionContextTest.java +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionContextTest.java @@ -42,6 +42,26 @@ class ParameterizedCompletionContextTest { @BeforeEach void setUp() { Assumptions.assumeFalse(GraphicsEnvironment.isHeadless()); + } + + + @AfterEach + void tearDown() { + if (frame != null) { + frame.dispose(); + frame = null; + } + } + + + /** + * Realizes a text area with parameterized completion assistance installed. Called + * explicitly at the start of each test, rather than from {@code @BeforeEach}, since + * {@link SwingRunnerExtension} only runs {@code @Test} methods on the EDT - window + * creation/visibility changes made here need to happen on the EDT too, alongside the + * assertions that depend on them. + */ + private void realize() { provider = new DefaultCompletionProvider(); provider.setParameterizedCompletionParams('(', ", ", ')'); @@ -59,15 +79,6 @@ void setUp() { } - @AfterEach - void tearDown() { - if (frame != null) { - frame.dispose(); - frame = null; - } - } - - private FunctionCompletion newFunctionCompletion(String... paramNames) { FunctionCompletion fc = new FunctionCompletion(provider, "foo", "void"); List params = new ArrayList<>(); @@ -110,6 +121,8 @@ private static Object getParamChoicesWindow(ParameterizedCompletionContext ctx) @Test void activate_insertsParameterTextAndSelectsFirstParam() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a", "b"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -123,6 +136,8 @@ void activate_insertsParameterTextAndSelectsFirstParam() { @Test void activate_calledTwice_isIdempotent() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -138,6 +153,8 @@ void activate_calledTwice_isIdempotent() { @Test void getParameterHighlights_countsParamsPlusEndMarker() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a", "b"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -151,6 +168,8 @@ void getParameterHighlights_countsParamsPlusEndMarker() { @Test void getArgumentText_offsetInParamHighlight_returnsCurrentParamText() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a", "b"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -170,6 +189,8 @@ void getArgumentText_offsetInParamHighlight_returnsCurrentParamText() { @Test void getArgumentText_offsetNotInAnyHighlight_returnsNull() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -182,6 +203,8 @@ void getArgumentText_offsetNotInAnyHighlight_returnsNull() { @Test void getArgumentText_noActiveHighlights_returnsNull() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); // Never activated, so getParameterHighlights() is empty. @@ -191,6 +214,8 @@ void getArgumentText_noActiveHighlights_returnsNull() { @Test void tabAndShiftTab_navigateBetweenParams() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a", "b"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -208,6 +233,8 @@ void tabAndShiftTab_navigateBetweenParams() { @Test void deactivate_restoresKeyBindingsAndHidesWindows() throws ReflectiveOperationException { + realize(); + KeyStroke tabKey = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0); Object bindingBeforeActivate = textArea.getInputMap().get(tabKey); @@ -228,6 +255,8 @@ void deactivate_restoresKeyBindingsAndHidesWindows() throws ReflectiveOperationE @Test void deactivate_whenNotActive_doesNothing() throws ReflectiveOperationException { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); Assertions.assertDoesNotThrow(ctx::deactivate); @@ -237,6 +266,8 @@ void deactivate_whenNotActive_doesNothing() throws ReflectiveOperationException @Test void caretMovedBeforeMinOffset_deactivatesAutomatically() throws ReflectiveOperationException { + realize(); + FunctionCompletion fc = newFunctionCompletion("a", "b"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -255,6 +286,8 @@ void caretMovedBeforeMinOffset_deactivatesAutomatically() throws ReflectiveOpera @Test void focusLost_deactivates() throws ReflectiveOperationException { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -272,6 +305,8 @@ void focusLost_deactivates() throws ReflectiveOperationException { @Test void escape_noChoicesWindowVisible_deactivates() throws ReflectiveOperationException { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -290,6 +325,8 @@ void escape_noChoicesWindowVisible_deactivates() throws ReflectiveOperationExcep @Test void closingChar_typedWellBeforeEnd_insertsLiteralCharWithoutDeactivating() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a", "b"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -308,6 +345,8 @@ void closingChar_typedWellBeforeEnd_insertsLiteralCharWithoutDeactivating() { @Test void closingChar_typedAtEndOfCompletion_deactivates() throws ReflectiveOperationException { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -323,6 +362,8 @@ void closingChar_typedAtEndOfCompletion_deactivates() throws ReflectiveOperation @Test void insertSelectedChoice_choicesWindowNotVisible_returnsFalse() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -335,6 +376,8 @@ void insertSelectedChoice_choicesWindowNotVisible_returnsFalse() { @Test void insertSelectedChoice_choiceAvailable_replacesParamAndMovesToNext() throws ReflectiveOperationException { + realize(); + BasicCompletion choice1 = new BasicCompletion(provider, "CHOICE_ONE"); BasicCompletion choice2 = new BasicCompletion(provider, "CHOICE_TWO"); provider.setParameterChoicesProvider((tc, param) -> List.of(choice1, choice2)); @@ -360,6 +403,8 @@ void insertSelectedChoice_choiceAvailable_replacesParamAndMovesToNext() throws R @Test void upDownArrows_choicesWindowVisible_changeSelectedChoice() throws ReflectiveOperationException { + realize(); + BasicCompletion choice1 = new BasicCompletion(provider, "CHOICE_ONE"); BasicCompletion choice2 = new BasicCompletion(provider, "CHOICE_TWO"); provider.setParameterChoicesProvider((tc, param) -> List.of(choice1, choice2)); @@ -384,6 +429,8 @@ void upDownArrows_choicesWindowVisible_changeSelectedChoice() throws ReflectiveO @Test void updateUI_doesNotThrow() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); @@ -396,14 +443,61 @@ void updateUI_doesNotThrow() { @Test void updateUI_neverActivated_doesNotThrow() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); Assertions.assertDoesNotThrow(ctx::updateUI); } + @Test + void upDownArrows_choicesWindowNotVisible_fallsBackToOldAction() { + realize(); + + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + // No ParameterChoicesProvider installed, so the choices window is never shown; + // install our own fallback action to confirm it's invoked in that case. + InputMap im = textArea.getInputMap(); + ActionMap am = textArea.getActionMap(); + KeyStroke downKs = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0); + Object key = im.get(downKs); + Action installedAction = am.get(key); + Assertions.assertDoesNotThrow(() -> + installedAction.actionPerformed(new ActionEvent(textArea, ActionEvent.ACTION_PERFORMED, ""))); + } + + + @Test + void closingChar_partOfArgumentBeingTyped_insertsLiteralWithoutDeactivating() { + realize(); + + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); // Text is now "(a)"; selection ("a") is the last/only argument + + // Simulate the user typing an unbalanced opening paren as part of their argument + // while positioned at the end of it, e.g. typing "f(x" in place of "a". + textArea.replaceSelection("f(x"); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(')')); + + Assertions.assertTrue(textArea.getText().contains("f(x)"), + "Closing paren should be treated as part of the argument, not the call's end"); + } + + @Test void enter_noChoiceSelected_movesToDefaultEndOffset() { + realize(); + FunctionCompletion fc = newFunctionCompletion("a"); ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc);