diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2676546..bfb92bb 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -37,4 +37,13 @@ jobs: # Note: Assumes we're running on Ubuntu # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md - name: Build with Gradle - run: xvfb-run ./gradlew build -xsign -xpublish --warning-mode all + run: xvfb-run ./gradlew build jacocoTestReport -xsign -xpublish --warning-mode all + + - name: Submit coverage data to codecov + uses: codecov/codecov-action@v7 + if: matrix.java == '25' + with: + token: ${{ secrets.CODECOV_TOKEN }} + disable_search: true + files: ./AutoComplete/build/reports/jacoco/test/jacocoTestReport.xml + name: codecov diff --git a/AutoComplete/build.gradle b/AutoComplete/build.gradle index 5955396..3238cb9 100644 --- a/AutoComplete/build.gradle +++ b/AutoComplete/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -['base', 'distribution', 'maven-publish', 'signing'].each { apply plugin: it } +['base', 'jacoco', 'distribution', 'maven-publish', 'signing'].each { apply plugin: it } base { archivesName = 'autocomplete' @@ -14,6 +14,13 @@ dependencies { ext.isReleaseVersion = !project.version.endsWith('SNAPSHOT') +jacocoTestReport { + reports { + xml.required = true // codecov depends on xml format report + html.required = true + } +} + java { withSourcesJar() withJavadocJar() diff --git a/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java b/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java index be2c089..14f8ae6 100644 --- a/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java +++ b/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java @@ -349,7 +349,7 @@ protected void fireAutoCompletionEvent(AutoCompletionEvent.Type type) { * @see #setAutoActivationDelay(int) */ public int getAutoActivationDelay() { - return autoActivationListener.timer.getDelay(); + return autoActivationListener.timer.getInitialDelay(); } diff --git a/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletionStyleContext.java b/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletionStyleContext.java index 220ce1e..fd0cf36 100644 --- a/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletionStyleContext.java +++ b/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletionStyleContext.java @@ -5,16 +5,39 @@ package org.fife.ui.autocomplete; import java.awt.Color; +import javax.swing.UIManager; +import javax.swing.plaf.ColorUIResource; /** - * Manages the colors shared across the library. + * Manages the colors shared across the library. Default colors may be + * overridden by a {@code LookAndFeel} by registering a {@link Color} in + * {@link UIManager} under the relevant key. * * @author Robert Futrell * @version 1.0 */ public class AutoCompletionStyleContext { + /** + * The {@link UIManager} key for the color used to denote the ending + * caret position for parameterized completions. + */ + public static final String PROPERTY_PARAMETERIZED_COMPLETION_CURSOR_POSITION_COLOR = + "autocomplete.parameterizedCompletionCursorPositionColor"; + + /** + * The {@link UIManager} key for the color used to highlight copies of + * editable parameters in parameterized completions. + */ + public static final String PROPERTY_PARAMETER_COPY_COLOR = "autocomplete.parameterCopyColor"; + + /** + * The {@link UIManager} key for the color of the outline highlight used + * to denote editable parameters in parameterized completions. + */ + public static final String PROPERTY_PARAMETER_OUTLINE_COLOR = "autocomplete.parameterOutlineColor"; + /** * The color used to denote the ending caret position for parameterized * completions. @@ -38,9 +61,30 @@ public class AutoCompletionStyleContext { * Constructor. */ public AutoCompletionStyleContext() { - setParameterOutlineColor(Color.GRAY); - setParameterCopyColor(new Color(0xb4d7ff)); - setParameterizedCompletionCursorPositionColor(new Color(0x00b400)); + setParameterOutlineColor(getDefaultColor(PROPERTY_PARAMETER_OUTLINE_COLOR, Color.GRAY)); + setParameterCopyColor(getDefaultColor(PROPERTY_PARAMETER_COPY_COLOR, new Color(0xb4d7ff))); + setParameterizedCompletionCursorPositionColor( + getDefaultColor(PROPERTY_PARAMETERIZED_COMPLETION_CURSOR_POSITION_COLOR, new Color(0x00b400))); + } + + + /** + * Returns the color registered in {@link UIManager} under the specified + * key, or the specified default if none is registered. If no color is + * registered, {@code fallback} is cached in {@code UIManager} under + * {@code key} for future lookups. + * + * @param key The {@link UIManager} key to check. + * @param fallback The color to use, and cache, if none is registered. + * @return The color to use. + */ + private static Color getDefaultColor(String key, Color fallback) { + Color color = UIManager.getColor(key); + if (color == null) { + color = new ColorUIResource(fallback); + UIManager.put(key, color); + } + return color; } diff --git a/AutoComplete/src/main/java/org/fife/ui/autocomplete/SizeGrip.java b/AutoComplete/src/main/java/org/fife/ui/autocomplete/SizeGrip.java index 846d1b3..9291ff9 100644 --- a/AutoComplete/src/main/java/org/fife/ui/autocomplete/SizeGrip.java +++ b/AutoComplete/src/main/java/org/fife/ui/autocomplete/SizeGrip.java @@ -13,9 +13,7 @@ import java.awt.Point; import java.awt.Window; import java.awt.event.MouseEvent; -import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; import javax.imageio.ImageIO; import javax.swing.JPanel; @@ -23,12 +21,13 @@ import javax.swing.UIManager; import javax.swing.event.MouseInputAdapter; +import org.fife.ui.rsyntaxtextarea.OS; + /** * A component that allows its parent window to be resizable, similar to the * size grip seen on status bars. This is essentially a copy of the class with - * the same name in RSyntaxTextArea, but is duplicated to prevent a dependency - * on that library. + * the same name in RSyntaxTextArea. * * @author Robert Futrell * @version 1.0 @@ -70,21 +69,8 @@ public void applyComponentOrientation(ComponentOrientation o) { private Image createOSXSizeGrip() { ClassLoader cl = getClass().getClassLoader(); URL url = cl.getResource("org/fife/ui/autocomplete/osx_sizegrip.png"); - if (url==null) { - // We're not running in a jar - we may be debugging in Eclipse, - // for example - File f = new File("../AutoComplete/src/org/fife/ui/autocomplete/osx_sizegrip.png"); - if (f.isFile()) { - try { - url = f.toURI().toURL(); - } catch (MalformedURLException mue) { // Never happens - mue.printStackTrace(); - return null; - } - } - else { - return null; // Can't find resource or image file - } + if (url == null) { + return null; } Image image = null; try { @@ -178,7 +164,7 @@ public void updateUI() { super.updateUI(); // TODO: Key off of Aqua LaF, not just OS X, as this size grip looks // bad on other LaFs on Mac such as Nimbus. - if (System.getProperty("os.name").contains("OS X")) { + if (OS.get() == OS.MAC_OS_X) { if (osxSizeGrip==null) { osxSizeGrip = createOSXSizeGrip(); } diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompleteDescWindowTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompleteDescWindowTest.java new file mode 100644 index 0000000..88cb8a4 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompleteDescWindowTest.java @@ -0,0 +1,389 @@ +/* + * 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.GraphicsEnvironment; +import java.awt.event.ActionListener; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import javax.swing.Action; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JTextArea; +import javax.swing.Timer; +import javax.swing.event.HyperlinkEvent; + +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 AutoCompleteDescWindowTest { + + private JFrame frame; + + + @BeforeEach + void setUp() { + Assumptions.assumeFalse(GraphicsEnvironment.isHeadless()); + } + + + @AfterEach + void tearDown() { + if (frame != null) { + frame.dispose(); + frame = null; + } + } + + + private static Object getField(Object target, String name) throws ReflectiveOperationException { + Field field = AutoCompleteDescWindow.class.getDeclaredField(name); + field.setAccessible(true); + return field.get(target); + } + + + private static JEditorPane getDescArea(AutoCompleteDescWindow window) throws ReflectiveOperationException { + return (JEditorPane) getField(window, "descArea"); + } + + + /** + * {@code setDescriptionFor()} doesn't update the displayed description synchronously - + * it queues the completion on a 120ms {@code Timer} (to avoid bogging down fast scrolling + * through completions with slow-to-compute summaries). Rather than sleeping past that + * delay and pumping the AWT event queue, this fires the timer's action directly, which is + * deterministic and avoids any real-time flakiness. + */ + private static void fireDescriptionTimer(AutoCompleteDescWindow window) throws ReflectiveOperationException { + Timer timer = (Timer) getField(window, "timer"); + timer.stop(); + ActionListener action = (ActionListener) getField(window, "timerAction"); + action.actionPerformed(null); + } + + + private static AutoCompletion newInstalledAutoCompletion(CompletionProvider provider) { + AutoCompletion ac = new AutoCompletion(provider); + ac.install(new JTextArea()); + return ac; + } + + + @Test + void constructor_initialHistoryStateHasNoBackOrForward() { + AutoCompletion ac = newInstalledAutoCompletion(new DefaultCompletionProvider()); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + Assertions.assertFalse(window.copy(), "Nothing should be visible/selected yet"); + } + + + @Test + void copy_notVisible_returnsFalse() throws ReflectiveOperationException { + AutoCompletion ac = newInstalledAutoCompletion(new DefaultCompletionProvider()); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + getDescArea(window).selectAll(); + + Assertions.assertFalse(window.copy()); + } + + + @Test + void copy_visibleNoSelection_returnsFalse() { + AutoCompletion ac = newInstalledAutoCompletion(new DefaultCompletionProvider()); + frame = new JFrame(); + frame.setVisible(true); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(frame, ac); + + window.setVisible(true); + Assertions.assertFalse(window.copy()); + } + + + @Test + void copy_visibleWithSelection_returnsTrue() throws ReflectiveOperationException { + AutoCompletion ac = newInstalledAutoCompletion(new DefaultCompletionProvider()); + frame = new JFrame(); + frame.setVisible(true); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(frame, ac); + + window.setVisible(true); + getDescArea(window).setText("some description text"); + getDescArea(window).selectAll(); + + Assertions.assertTrue(window.copy()); + } + + + @Test + void setDescriptionFor_noSummaryAvailable_showsFallbackMessage() throws ReflectiveOperationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = newInstalledAutoCompletion(provider); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + BasicCompletion completion = new BasicCompletion(provider, "foo"); // no summary set + window.setDescriptionFor(completion); + fireDescriptionTimer(window); + + Assertions.assertTrue(getDescArea(window).getText().contains("")); + } + + + @Test + void setDescriptionFor_withSummary_showsSummaryText() throws ReflectiveOperationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = newInstalledAutoCompletion(provider); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + BasicCompletion completion = new BasicCompletion(provider, "foo", "short", "the full summary"); + window.setDescriptionFor(completion); + fireDescriptionTimer(window); + + Assertions.assertTrue(getDescArea(window).getText().contains("the full summary")); + } + + + @Test + void showSummaryFor_addsToHistoryLikeSetDescriptionFor() throws ReflectiveOperationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = newInstalledAutoCompletion(provider); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + BasicCompletion completion = new BasicCompletion(provider, "foo", "short", "summary one"); + window.showSummaryFor(completion, null); + fireDescriptionTimer(window); + + Assertions.assertTrue(getDescArea(window).getText().contains("summary one")); + } + + + @Test + void backAndForwardActions_navigateHistory() throws ReflectiveOperationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = newInstalledAutoCompletion(provider); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + Action backAction = (Action) getField(window, "backAction"); + Action forwardAction = (Action) getField(window, "forwardAction"); + + BasicCompletion first = new BasicCompletion(provider, "foo", "short", "summary one"); + BasicCompletion second = new BasicCompletion(provider, "bar", "short", "summary two"); + + window.setDescriptionFor(first); // addToHistory=false -> clears history, becomes entry 0 + fireDescriptionTimer(window); + Assertions.assertFalse(backAction.isEnabled()); + Assertions.assertFalse(forwardAction.isEnabled()); + + window.setDescriptionFor(second, true); // addToHistory=true -> becomes entry 1 + fireDescriptionTimer(window); + Assertions.assertTrue(getDescArea(window).getText().contains("summary two")); + Assertions.assertTrue(backAction.isEnabled()); + Assertions.assertFalse(forwardAction.isEnabled()); + + backAction.actionPerformed(null); + Assertions.assertTrue(getDescArea(window).getText().contains("summary one")); + Assertions.assertFalse(backAction.isEnabled()); + Assertions.assertTrue(forwardAction.isEnabled()); + + forwardAction.actionPerformed(null); + Assertions.assertTrue(getDescArea(window).getText().contains("summary two")); + Assertions.assertTrue(backAction.isEnabled()); + Assertions.assertFalse(forwardAction.isEnabled()); + } + + + @Test + void addToHistory_afterGoingBack_truncatesForwardHistory() throws ReflectiveOperationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = newInstalledAutoCompletion(provider); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + Action backAction = (Action) getField(window, "backAction"); + Action forwardAction = (Action) getField(window, "forwardAction"); + + BasicCompletion first = new BasicCompletion(provider, "foo", "short", "summary one"); + BasicCompletion second = new BasicCompletion(provider, "bar", "short", "summary two"); + BasicCompletion third = new BasicCompletion(provider, "baz", "short", "summary three"); + + window.setDescriptionFor(first); + fireDescriptionTimer(window); + window.setDescriptionFor(second, true); + fireDescriptionTimer(window); + + backAction.actionPerformed(null); // back to "first" + Assertions.assertTrue(forwardAction.isEnabled(), "Sanity check: forward history should exist before truncation"); + + window.setDescriptionFor(third, true); // should discard "second" from forward history + + fireDescriptionTimer(window); + Assertions.assertTrue(getDescArea(window).getText().contains("summary three")); + Assertions.assertFalse(forwardAction.isEnabled(), "Forward history should have been truncated"); + Assertions.assertTrue(backAction.isEnabled()); + + backAction.actionPerformed(null); + Assertions.assertTrue(getDescArea(window).getText().contains("summary one"), + "\"second\" should no longer be reachable in history"); + } + + + @Test + void setVisible_false_clearsHistoryAndDisablesActions() throws ReflectiveOperationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = newInstalledAutoCompletion(provider); + frame = new JFrame(); + frame.setVisible(true); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(frame, ac); + + Action backAction = (Action) getField(window, "backAction"); + Action forwardAction = (Action) getField(window, "forwardAction"); + + window.setVisible(true); + window.setDescriptionFor(new BasicCompletion(provider, "foo", "short", "summary one")); + fireDescriptionTimer(window); + window.setDescriptionFor(new BasicCompletion(provider, "bar", "short", "summary two"), true); + fireDescriptionTimer(window); + Assertions.assertTrue(backAction.isEnabled(), "Sanity check: history should be populated"); + + window.setVisible(false); + + Assertions.assertFalse(backAction.isEnabled()); + Assertions.assertFalse(forwardAction.isEnabled()); + } + + + @Test + void updateUI_doesNotThrowAndSyncsScrollPaneBackground() throws ReflectiveOperationException { + AutoCompletion ac = newInstalledAutoCompletion(new DefaultCompletionProvider()); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + Assertions.assertDoesNotThrow(window::updateUI); + + javax.swing.JScrollPane scrollPane = (javax.swing.JScrollPane) getField(window, "scrollPane"); + Assertions.assertEquals(getDescArea(window).getBackground(), scrollPane.getBackground()); + } + + + @Test + void hyperlinkUpdate_nonActivatedEventType_doesNothing() throws MalformedURLException { + AutoCompletion ac = newInstalledAutoCompletion(new DefaultCompletionProvider()); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + URL url = URI.create("https://example.com").toURL(); + HyperlinkEvent e = new HyperlinkEvent(window, HyperlinkEvent.EventType.ENTERED, url); + Assertions.assertDoesNotThrow(() -> window.hyperlinkUpdate(e)); + } + + + @Test + void hyperlinkUpdate_activatedWithUrl_invokesExternalUrlHandler() throws ReflectiveOperationException, + MalformedURLException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = newInstalledAutoCompletion(provider); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + java.util.concurrent.atomic.AtomicReference received = new java.util.concurrent.atomic.AtomicReference<>(); + ac.setExternalURLHandler((event, completion, callback) -> received.set(event)); + + // hyperlinkUpdate() looks up the currently-displayed history entry, so a description + // must actually have finished displaying (i.e. the timer fired) before a link click. + BasicCompletion completion = new BasicCompletion(provider, "foo", "short", "summary"); + window.setDescriptionFor(completion); + fireDescriptionTimer(window); + + URL url = URI.create("https://example.com/docs").toURL(); + HyperlinkEvent e = new HyperlinkEvent(window, HyperlinkEvent.EventType.ACTIVATED, url); + window.hyperlinkUpdate(e); + + Assertions.assertNotNull(received.get()); + Assertions.assertEquals(url, received.get().getURL()); + } + + + @Test + void hyperlinkUpdate_activatedWithUrl_linkRedirectorRewritesUrlBeforeHandler() + throws ReflectiveOperationException, MalformedURLException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + AutoCompletion ac = newInstalledAutoCompletion(provider); + AutoCompleteDescWindow window = new AutoCompleteDescWindow(new JFrame(), ac); + + java.util.concurrent.atomic.AtomicReference received = new java.util.concurrent.atomic.AtomicReference<>(); + ac.setExternalURLHandler((event, completion, callback) -> received.set(event.getURL())); + + BasicCompletion completion = new BasicCompletion(provider, "foo", "short", "summary"); + window.setDescriptionFor(completion); + fireDescriptionTimer(window); + + URL original = URI.create("https://example.com/original").toURL(); + URL redirected = URI.create("https://example.com/redirected").toURL(); + try { + AutoCompletion.setLinkRedirector(url -> redirected); + + HyperlinkEvent e = new HyperlinkEvent(window, HyperlinkEvent.EventType.ACTIVATED, original); + window.hyperlinkUpdate(e); + + Assertions.assertEquals(redirected, received.get()); + } finally { + AutoCompletion.setLinkRedirector(null); + } + } + + + /** + * The url==null branch (used for "in-doc" links, e.g. from XML-defined completions per + * c.xml) assumes it's parented by a real {@code AutoCompletePopupWindow} with a current + * selection - it casts {@code getParent()} directly. Driving that through the full + * {@code AutoCompletion}/popup pipeline (rather than constructing this window standalone) + * is the only realistic way to exercise it. + */ + @Test + void hyperlinkUpdate_activatedNoUrl_looksUpCompletionByInputTextViaParentPopup() + throws ReflectiveOperationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion source = new BasicCompletion(provider, "foo", "short", "links to bar"); + // Purely to force >1 match on the "foo" prefix below, so the popup (and thus the desc + // window) actually opens instead of auto-inserting the lone match. + BasicCompletion filler = new BasicCompletion(provider, "fooZ"); + BasicCompletion target = new BasicCompletion(provider, "bar", "short", "the bar summary"); + provider.addCompletion(source); + provider.addCompletion(filler); + provider.addCompletion(target); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + JTextArea 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(); + + AutoCompletePopupWindow popupWindow = ac.getPopupWindow(); + Assertions.assertSame(source, popupWindow.getSelection(), "Sanity check on which completion is selected"); + AutoCompleteDescWindow window = (AutoCompleteDescWindow) popupWindow.getDescWindow(); + fireDescriptionTimer(window); // let the initial description ("source") finish displaying + + HyperlinkEvent e = new HyperlinkEvent(window, HyperlinkEvent.EventType.ACTIVATED, null, "bar"); + window.hyperlinkUpdate(e); + fireDescriptionTimer(window); // the resulting setDescriptionFor() call is itself timer-based + + Assertions.assertTrue(getDescArea(window).getText().contains("the bar summary")); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionEventTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionEventTest.java new file mode 100644 index 0000000..c1c9a04 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionEventTest.java @@ -0,0 +1,52 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class AutoCompletionEventTest { + + + @Test + void constructor_popupShown_setsSourceAndType() { + AutoCompletion source = new AutoCompletion(new DefaultCompletionProvider()); + AutoCompletionEvent event = + new AutoCompletionEvent(source, AutoCompletionEvent.Type.POPUP_SHOWN); + Assertions.assertSame(source, event.getSource()); + Assertions.assertEquals(AutoCompletionEvent.Type.POPUP_SHOWN, event.getEventType()); + } + + + @Test + void constructor_popupHidden_setsSourceAndType() { + AutoCompletion source = new AutoCompletion(new DefaultCompletionProvider()); + AutoCompletionEvent event = + new AutoCompletionEvent(source, AutoCompletionEvent.Type.POPUP_HIDDEN); + Assertions.assertSame(source, event.getSource()); + Assertions.assertEquals(AutoCompletionEvent.Type.POPUP_HIDDEN, event.getEventType()); + } + + + @Test + void getAutoCompletion_returnsSourceCastAsAutoCompletion() { + AutoCompletion source = new AutoCompletion(new DefaultCompletionProvider()); + AutoCompletionEvent event = + new AutoCompletionEvent(source, AutoCompletionEvent.Type.POPUP_SHOWN); + Assertions.assertSame(source, event.getAutoCompletion()); + } + + + @Test + void constructor_nullSource_throwsIllegalArgumentException() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> new AutoCompletionEvent(null, AutoCompletionEvent.Type.POPUP_SHOWN)); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionStyleContextTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionStyleContextTest.java new file mode 100644 index 0000000..55adaee --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionStyleContextTest.java @@ -0,0 +1,109 @@ +/* + * 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 javax.swing.UIManager; +import javax.swing.plaf.ColorUIResource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +class AutoCompletionStyleContextTest { + + + @AfterEach + void tearDown() { + UIManager.put(AutoCompletionStyleContext.PROPERTY_PARAMETER_OUTLINE_COLOR, null); + UIManager.put(AutoCompletionStyleContext.PROPERTY_PARAMETER_COPY_COLOR, null); + UIManager.put(AutoCompletionStyleContext.PROPERTY_PARAMETERIZED_COMPLETION_CURSOR_POSITION_COLOR, null); + } + + + @Test + void constructor_noUiManagerValues_usesHardcodedDefaults() { + AutoCompletionStyleContext context = new AutoCompletionStyleContext(); + Assertions.assertEquals(Color.GRAY, context.getParameterOutlineColor()); + Assertions.assertEquals(new Color(0xb4d7ff), context.getParameterCopyColor()); + Assertions.assertEquals(new Color(0x00b400), context.getParameterizedCompletionCursorPositionColor()); + } + + + @Test + void constructor_noUiManagerValues_cachesDefaultsInUiManagerAsColorUIResource() { + new AutoCompletionStyleContext(); + + Color outline = UIManager.getColor(AutoCompletionStyleContext.PROPERTY_PARAMETER_OUTLINE_COLOR); + Color copy = UIManager.getColor(AutoCompletionStyleContext.PROPERTY_PARAMETER_COPY_COLOR); + Color cursor = UIManager.getColor( + AutoCompletionStyleContext.PROPERTY_PARAMETERIZED_COMPLETION_CURSOR_POSITION_COLOR); + + Assertions.assertInstanceOf(ColorUIResource.class, outline); + Assertions.assertInstanceOf(ColorUIResource.class, copy); + Assertions.assertInstanceOf(ColorUIResource.class, cursor); + Assertions.assertEquals(Color.GRAY, outline); + Assertions.assertEquals(new Color(0xb4d7ff), copy); + Assertions.assertEquals(new Color(0x00b400), cursor); + } + + + @Test + void constructor_uiManagerValuesPresent_usesUiManagerValuesInsteadOfDefaults() { + Color outline = new Color(1, 2, 3); + Color copy = new Color(4, 5, 6); + Color cursor = new Color(7, 8, 9); + UIManager.put(AutoCompletionStyleContext.PROPERTY_PARAMETER_OUTLINE_COLOR, outline); + UIManager.put(AutoCompletionStyleContext.PROPERTY_PARAMETER_COPY_COLOR, copy); + UIManager.put(AutoCompletionStyleContext.PROPERTY_PARAMETERIZED_COMPLETION_CURSOR_POSITION_COLOR, cursor); + + AutoCompletionStyleContext context = new AutoCompletionStyleContext(); + + Assertions.assertEquals(outline, context.getParameterOutlineColor()); + Assertions.assertEquals(copy, context.getParameterCopyColor()); + Assertions.assertEquals(cursor, context.getParameterizedCompletionCursorPositionColor()); + } + + + @Test + void constructor_uiManagerValuePresent_doesNotOverwriteExistingUiManagerValue() { + Color outline = new Color(1, 2, 3); + UIManager.put(AutoCompletionStyleContext.PROPERTY_PARAMETER_OUTLINE_COLOR, outline); + + new AutoCompletionStyleContext(); + + Assertions.assertSame(outline, + UIManager.getColor(AutoCompletionStyleContext.PROPERTY_PARAMETER_OUTLINE_COLOR)); + } + + + @Test + void getSetParameterCopyColor_roundTrips() { + AutoCompletionStyleContext context = new AutoCompletionStyleContext(); + Color color = new Color(10, 20, 30); + context.setParameterCopyColor(color); + Assertions.assertEquals(color, context.getParameterCopyColor()); + } + + + @Test + void getSetParameterOutlineColor_roundTrips() { + AutoCompletionStyleContext context = new AutoCompletionStyleContext(); + Color color = new Color(11, 21, 31); + context.setParameterOutlineColor(color); + Assertions.assertEquals(color, context.getParameterOutlineColor()); + } + + + @Test + void getSetParameterizedCompletionCursorPositionColor_roundTrips() { + AutoCompletionStyleContext context = new AutoCompletionStyleContext(); + Color color = new Color(12, 22, 32); + context.setParameterizedCompletionCursorPositionColor(color); + Assertions.assertEquals(color, context.getParameterizedCompletionCursorPositionColor()); + } + + +} 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 3dbaabf..ebf92a1 100644 --- a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java @@ -4,8 +4,12 @@ */ package org.fife.ui.autocomplete; +import java.awt.Color; +import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.InputMap; @@ -15,6 +19,7 @@ import javax.swing.text.DefaultCaret; import javax.swing.text.JTextComponent; +import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; @@ -457,4 +462,733 @@ public boolean isAutoActivateOkay(JTextComponent tc, int offs) { } + /** + * Realizes {@code textArea} in a visible, packed {@code JFrame}, which is required for any + * code path that calls {@code JTextComponent#modelToView2D(int)} (e.g. showing the popup or + * starting parameterized completion assistance). The frame is torn down in {@link #tearDown()}. + */ + private void realize(JTextArea textArea) { + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + } + + + @Test + void constructor_setsExpectedDefaults() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertTrue(ac.isAutoCompleteEnabled()); + Assertions.assertTrue(ac.getAutoCompleteSingleChoices()); + Assertions.assertFalse(ac.isAutoActivationEnabled()); + Assertions.assertFalse(ac.isParameterAssistanceEnabled()); + Assertions.assertTrue(ac.isHideOnCompletionProviderChange()); + Assertions.assertTrue(ac.isHideOnNoText()); + Assertions.assertEquals(300, ac.getParameterDescriptionTruncateThreshold()); + Assertions.assertEquals(AutoCompletion.getDefaultTriggerKey(), ac.getTriggerKey()); + Assertions.assertEquals(AutoCompletion.getDefaultDescWindowToggleKey(), ac.getDescWindowToggleKey()); + } + + + @Test + void getDefaultTriggerKey_isCtrlSpace() { + KeyStroke expected = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_SPACE, + java.awt.event.InputEvent.CTRL_DOWN_MASK); + Assertions.assertEquals(expected, AutoCompletion.getDefaultTriggerKey()); + } + + + @Test + void getDefaultDescWindowToggleKey_isCtrlShiftSpace() { + KeyStroke expected = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_SPACE, + java.awt.event.InputEvent.CTRL_DOWN_MASK | java.awt.event.InputEvent.SHIFT_DOWN_MASK); + Assertions.assertEquals(expected, AutoCompletion.getDefaultDescWindowToggleKey()); + } + + + @Test + void getStyleContext_returnsSameSingletonAcrossInstances() { + Assertions.assertSame(AutoCompletion.getStyleContext(), AutoCompletion.getStyleContext()); + } + + + @Test + void getSetAutoActivationDelay_roundTrips() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + ac.setAutoActivationDelay(500); + Assertions.assertEquals(500, ac.getAutoActivationDelay()); + } + + + @Test + void setAutoActivationDelay_negative_clampsToZero() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + ac.setAutoActivationDelay(-100); + Assertions.assertEquals(0, ac.getAutoActivationDelay()); + } + + + @Test + void getSetExternalURLHandler_roundTrips() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertNull(ac.getExternalURLHandler()); + ExternalURLHandler handler = (e, c, callback) -> { }; + ac.setExternalURLHandler(handler); + Assertions.assertSame(handler, ac.getExternalURLHandler()); + } + + + @Test + void getSetLinkRedirector_staticRoundTrips() { + Assertions.assertNull(AutoCompletion.getLinkRedirector()); + try { + LinkRedirector redirector = url -> url; + AutoCompletion.setLinkRedirector(redirector); + Assertions.assertSame(redirector, AutoCompletion.getLinkRedirector()); + } finally { + AutoCompletion.setLinkRedirector(null); + } + } + + + @Test + void getSetParamChoicesRenderer_roundTrips() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertNull(ac.getParamChoicesRenderer()); + javax.swing.DefaultListCellRenderer renderer = new javax.swing.DefaultListCellRenderer(); + ac.setParamChoicesRenderer(renderer); + Assertions.assertSame(renderer, ac.getParamChoicesRenderer()); + } + + + @Test + void getSetParameterDescriptionTruncateThreshold_roundTrips() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + ac.setParameterDescriptionTruncateThreshold(42); + Assertions.assertEquals(42, ac.getParameterDescriptionTruncateThreshold()); + } + + + @Test + void isSetParameterAssistanceEnabled_roundTrips() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + ac.setParameterAssistanceEnabled(true); + Assertions.assertTrue(ac.isParameterAssistanceEnabled()); + ac.setParameterAssistanceEnabled(false); + Assertions.assertFalse(ac.isParameterAssistanceEnabled()); + } + + + @Test + void isSetHideOnCompletionProviderChangeAndHideOnNoText_roundTrip() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + ac.setHideOnCompletionProviderChange(false); + Assertions.assertFalse(ac.isHideOnCompletionProviderChange()); + ac.setHideOnNoText(false); + Assertions.assertFalse(ac.isHideOnNoText()); + } + + + @Test + void getTextComponent_lifecycleAcrossInstallAndUninstall() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertNull(ac.getTextComponent()); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + Assertions.assertSame(textArea, ac.getTextComponent()); + + ac.uninstall(); + Assertions.assertNull(ac.getTextComponent()); + } + + + @Test + void getTextComponentOrientation_nullBeforeInstall_matchesAfterInstall() throws ReflectiveOperationException { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + java.lang.reflect.Method m = AutoCompletion.class.getDeclaredMethod("getTextComponentOrientation"); + m.setAccessible(true); + Assertions.assertNull(m.invoke(ac)); + + JTextArea textArea = new JTextArea(); + textArea.applyComponentOrientation(java.awt.ComponentOrientation.RIGHT_TO_LEFT); + ac.install(textArea); + Assertions.assertEquals(java.awt.ComponentOrientation.RIGHT_TO_LEFT, m.invoke(ac)); + } + + + @Test + void getLineOfCaret_returnsZeroBasedLineIndex() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + JTextArea textArea = new JTextArea("line0\nline1\nline2"); + ac.install(textArea); + + textArea.setCaretPosition(0); + Assertions.assertEquals(0, ac.refreshPopupWindow()); + + textArea.setCaretPosition(textArea.getText().indexOf("line2")); + Assertions.assertEquals(2, ac.refreshPopupWindow()); + } + + + @Test + void setCompletionProvider_null_throwsException() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertThrows(NullPointerException.class, () -> ac.setCompletionProvider(null)); + } + + + @Test + void setTriggerKey_null_throwsException() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertThrows(NullPointerException.class, () -> ac.setTriggerKey(null)); + } + + + @Test + void setDescWindowVisibility_null_throwsException() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertThrows(NullPointerException.class, () -> ac.setDescWindowVisibility(null)); + } + + + @Test + void setCompletionProvider_defaultBehavior_hidesVisiblePopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + Assertions.assertTrue(ac.isPopupVisible()); + + ac.setCompletionProvider(new DefaultCompletionProvider()); + Assertions.assertFalse(ac.isPopupVisible()); + } + + + @Test + void setCompletionProvider_hideOnCompletionProviderChangeFalse_leavesPopupVisible() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setHideOnCompletionProviderChange(false); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + Assertions.assertTrue(ac.isPopupVisible()); + + ac.setCompletionProvider(new DefaultCompletionProvider()); + Assertions.assertTrue(ac.isPopupVisible()); + } + + + @Test + void setAutoCompleteEnabled_false_hidesVisiblePopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + Assertions.assertTrue(ac.isPopupVisible()); + + ac.setAutoCompleteEnabled(false); + Assertions.assertFalse(ac.isPopupVisible()); + } + + + @Test + void setListCellRenderer_appliesToExistingPopupAndHidesIt() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + Assertions.assertTrue(ac.isPopupVisible()); + + CompletionCellRenderer renderer = new CompletionCellRenderer(); + ac.setListCellRenderer(renderer); + + Assertions.assertSame(renderer, ac.getPopupWindow().getListCellRenderer()); + Assertions.assertFalse(ac.isPopupVisible()); + } + + + @Test + void setChoicesWindowSize_resizesExistingPopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + ac.setChoicesWindowSize(500, 300); + Assertions.assertEquals(new Dimension(500, 300), ac.getPopupWindow().getSize()); + } + + + @Test + void setDescriptionWindowColor_appliesToExistingDescWindow() { + 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); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + Color color = new Color(1, 2, 3); + ac.setDescriptionWindowColor(color); + + Assertions.assertEquals(color, ac.getDescWindowColor()); + Assertions.assertEquals(color, ac.getPopupWindow().getDescWindow().getBackground()); + } + + + @Test + void refreshPopupWindow_multipleMatches_showsPopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + Assertions.assertTrue(ac.isPopupVisible()); + } + + + @Test + void refreshPopupWindow_singleMatch_autoCompleteSingleChoicesDisabled_showsPopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setAutoCompleteSingleChoices(false); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + Assertions.assertTrue(ac.isPopupVisible()); + } + + + @Test + void refreshPopupWindow_singleMatch_autoCompleteSingleChoicesEnabled_doesNotShowPopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + // A single choice with autoCompleteSingleChoices enabled (the default) is inserted + // asynchronously via SwingUtilities.invokeLater() rather than shown in a popup; see + // the class-level note on hard-to-test sections for why we don't assert the insertion itself. + Assertions.assertFalse(ac.isPopupVisible()); + } + + + @Test + void refreshPopupWindow_noMatches_hidesVisiblePopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + Assertions.assertTrue(ac.isPopupVisible()); + + textArea.setText("zzz"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + Assertions.assertFalse(ac.isPopupVisible()); + } + + + @Test + void refreshPopupWindow_emptyTextWithHideOnNoText_hidesVisiblePopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + Assertions.assertTrue(ac.isPopupVisible()); + + // Moving the caret back to the start of the (single-line) word leaves the popup's own + // CaretListener on the same line, so it re-triggers a completion refresh on its own - + // with the "already entered" text now empty, hideOnNoText kicks in and hides the popup + // without needing an explicit doCompletion() call (which would otherwise treat "" as + // "show all completions" and reopen the popup). + textArea.setCaretPosition(0); + + Assertions.assertFalse(ac.isPopupVisible()); + } + + + @Test + void hideChildWindows_falseWhenNothingVisible_trueWhenPopupVisible() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + Assertions.assertFalse(ac.hideChildWindows()); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + Assertions.assertTrue(ac.isPopupVisible()); + + Assertions.assertTrue(ac.hideChildWindows()); + Assertions.assertFalse(ac.isPopupVisible()); + } + + + @Test + void insertCompletion_nonParameterized_replacesAlreadyEnteredText() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foobar"); + provider.addCompletion(completion); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.insertCompletion(completion); + + Assertions.assertEquals("foobar", textArea.getText()); + Assertions.assertFalse(ac.hideChildWindows()); + } + + + @Test + void insertCompletion_parameterizedWithParams_parameterAssistanceEnabled_startsAssistance() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "void"); + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + completion.setParams(params); + 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); + + Assertions.assertTrue(ac.hideChildWindows(), + "Parameter completion assistance should have activated for a completion with params"); + } + + + @Test + void insertCompletion_parameterizedWithParams_parameterAssistanceDisabled_doesNotStartAssistance() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "void"); + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + completion.setParams(params); + provider.addCompletion(completion); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.insertCompletion(completion); + + Assertions.assertFalse(ac.hideChildWindows()); + } + + + // NOTE: In real usage, AutoCompletionEvents are fired from PopupWindowListener's + // componentShown()/componentHidden() callbacks, which the AWT toolkit dispatches + // asynchronously (even though the underlying Window's visibility state changes + // synchronously). Reliably observing those in a test requires pumping the shared, + // JVM-wide AWT event queue, which also picks up unrelated events (e.g. window + // focus/activation changes from realizing a JFrame) that can trigger other listeners + // (like hiding the popup on focus loss) with results that vary by platform and window + // manager. See the class-level note on hard-to-test sections. These tests instead call + // the protected fireAutoCompletionEvent() hook directly to deterministically test + // listener registration/removal/fan-out - the part of this feature that isn't tied to + // AWT event timing. + @Test + void addAutoCompletionListener_receivesFiredEvents() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + List received = new ArrayList<>(); + ac.addAutoCompletionListener(e -> received.add(e.getEventType())); + + ac.fireAutoCompletionEvent(AutoCompletionEvent.Type.POPUP_SHOWN); + ac.fireAutoCompletionEvent(AutoCompletionEvent.Type.POPUP_HIDDEN); + + Assertions.assertEquals( + List.of(AutoCompletionEvent.Type.POPUP_SHOWN, AutoCompletionEvent.Type.POPUP_HIDDEN), + received); + } + + + @Test + void addAutoCompletionListener_eventSourceIsTheAutoCompletionInstance() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + List received = new ArrayList<>(); + ac.addAutoCompletionListener(received::add); + + ac.fireAutoCompletionEvent(AutoCompletionEvent.Type.POPUP_SHOWN); + + Assertions.assertEquals(1, received.size()); + Assertions.assertSame(ac, received.get(0).getSource()); + } + + + @Test + void addAutoCompletionListener_multipleListeners_allNotified() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + List calls = new ArrayList<>(); + ac.addAutoCompletionListener(e -> calls.add("first")); + ac.addAutoCompletionListener(e -> calls.add("second")); + + ac.fireAutoCompletionEvent(AutoCompletionEvent.Type.POPUP_SHOWN); + + Assertions.assertEquals(2, calls.size()); + } + + + @Test + void removeAutoCompletionListener_stopsNotifications() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + List received = new ArrayList<>(); + AutoCompletionListener listener = e -> received.add(e.getEventType()); + ac.addAutoCompletionListener(listener); + ac.removeAutoCompletionListener(listener); + + ac.fireAutoCompletionEvent(AutoCompletionEvent.Type.POPUP_SHOWN); + + Assertions.assertTrue(received.isEmpty()); + } + + + @Test + void install_bindsTriggerKeyThatShowsPopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + fireKeyAction(textArea, ac.getTriggerKey()); + + Assertions.assertTrue(ac.isPopupVisible()); + } + + + @Test + void install_secondComponent_uninstallsFromFirst() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + JTextArea textArea1 = new JTextArea(); + KeyStroke trigger = ac.getTriggerKey(); + Object originalBinding = textArea1.getInputMap().get(trigger); + + ac.install(textArea1); + Assertions.assertSame(textArea1, ac.getTextComponent()); + + JTextArea textArea2 = new JTextArea(); + ac.install(textArea2); + + Assertions.assertSame(textArea2, ac.getTextComponent()); + Assertions.assertEquals(originalBinding, textArea1.getInputMap().get(trigger), + "Old text component's trigger key binding should be restored when moving to a new component"); + } + + + @Test + void uninstall_whenNotInstalled_doesNothing() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertDoesNotThrow(ac::uninstall); + Assertions.assertNull(ac.getTextComponent()); + } + + + @Test + void setTriggerKey_whileInstalled_movesBindingToNewKey() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + JTextArea textArea = new JTextArea(); + KeyStroke originalTrigger = ac.getTriggerKey(); + KeyStroke newTrigger = KeyStroke.getKeyStroke("ctrl alt X"); + + // Capture pre-install state for both keys so we can confirm install()/setTriggerKey() + // correctly restore whatever was there before, rather than assuming it was null. + Object originalBindingAtOldKeyPreInstall = textArea.getInputMap().get(originalTrigger); + Object originalBindingAtNewKeyPreInstall = textArea.getInputMap().get(newTrigger); + + ac.install(textArea); + Assertions.assertNotEquals(originalBindingAtOldKeyPreInstall, textArea.getInputMap().get(originalTrigger), + "install() should rebind the trigger key"); + + ac.setTriggerKey(newTrigger); + + Assertions.assertEquals(newTrigger, ac.getTriggerKey()); + Assertions.assertNotEquals(originalBindingAtNewKeyPreInstall, textArea.getInputMap().get(newTrigger), + "New trigger key should now be bound to the AutoComplete action"); + Assertions.assertEquals(originalBindingAtOldKeyPreInstall, textArea.getInputMap().get(originalTrigger), + "Old trigger key's original (pre-install) binding should be restored immediately" + + " when the trigger key changes"); + } + + + @Test + void parameterizedCompletionStartAction_popupNotVisible_insertsCharLiterally() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + + AutoCompletion ac = new AutoCompletion(provider); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + fireKeyAction(textArea, KeyStroke.getKeyStroke('(')); + + Assertions.assertEquals("(", textArea.getText()); + } + + + @Test + void parameterizedCompletionStartAction_popupVisibleWithParamCompletionSelected_startsAssistance() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + + FunctionCompletion fc = new FunctionCompletion(provider, "foo", "void"); + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + fc.setParams(params); + provider.addCompletion(fc); + provider.addCompletion(new BasicCompletion(provider, "fooZ")); + + 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.doCompletion(); + Assertions.assertTrue(ac.isPopupVisible()); + Assertions.assertSame(fc, ac.getPopupWindow().getSelection(), + "Sanity check: the function completion should be selected first"); + + fireKeyAction(textArea, KeyStroke.getKeyStroke('(')); + + Assertions.assertFalse(ac.isPopupVisible(), "Choices popup should be hidden once assistance starts"); + Assertions.assertTrue(ac.hideChildWindows(), + "Parameter completion assistance should have activated"); + } + + + @Test + void autoCompleteAction_disabled_doesNotShowPopup() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + provider.addCompletion(new BasicCompletion(provider, "foobar")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setAutoCompleteEnabled(false); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + fireKeyAction(textArea, ac.getTriggerKey()); + + Assertions.assertFalse(ac.isPopupVisible()); + Assertions.assertNull(ac.getPopupWindow()); + } + + } diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/BasicCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/BasicCompletionTest.java new file mode 100644 index 0000000..8fecf5a --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/BasicCompletionTest.java @@ -0,0 +1,149 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class BasicCompletionTest { + + + @Test + void constructor_replacementTextOnly() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertEquals("foo", completion.getReplacementText()); + Assertions.assertNull(completion.getShortDescription()); + Assertions.assertNull(completion.getSummary()); + } + + + @Test + void constructor_withShortDesc() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo", "short desc"); + Assertions.assertEquals("foo", completion.getReplacementText()); + Assertions.assertEquals("short desc", completion.getShortDescription()); + Assertions.assertNull(completion.getSummary()); + } + + + @Test + void constructor_withShortDescAndSummary() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo", "short desc", "summary"); + Assertions.assertEquals("foo", completion.getReplacementText()); + Assertions.assertEquals("short desc", completion.getShortDescription()); + Assertions.assertEquals("summary", completion.getSummary()); + } + + + @Test + void getInputText_returnsReplacementText() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertEquals("foo", completion.getInputText()); + } + + + @Test + void getProvider() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertSame(provider, completion.getProvider()); + } + + + @Test + void getSetIcon() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertNull(completion.getIcon()); + completion.setIcon(null); + Assertions.assertNull(completion.getIcon()); + } + + + @Test + void getSetRelevance() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertEquals(0, completion.getRelevance()); + completion.setRelevance(7); + Assertions.assertEquals(7, completion.getRelevance()); + } + + + @Test + void setShortDescription() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + completion.setShortDescription("new desc"); + Assertions.assertEquals("new desc", completion.getShortDescription()); + } + + + @Test + void setSummary() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + completion.setSummary("new summary"); + Assertions.assertEquals("new summary", completion.getSummary()); + } + + + @Test + void toString_noShortDescription() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertEquals("foo", completion.toString()); + } + + + @Test + void toString_withShortDescription() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo", "short desc"); + Assertions.assertEquals("foo - short desc", completion.toString()); + } + + + @Test + void compareTo_sameInstance() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertEquals(0, completion.compareTo(completion)); + } + + + @Test + void compareTo_null() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertEquals(-1, completion.compareTo(null)); + } + + + @Test + void compareTo_ignoresCase() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion1 = new BasicCompletion(provider, "foo"); + BasicCompletion completion2 = new BasicCompletion(provider, "FOO"); + Assertions.assertEquals(0, completion1.compareTo(completion2)); + } + + + @Test + void getToolTipText_defaultsToNull() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + Assertions.assertNull(completion.getToolTipText()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionCellRendererTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionCellRendererTest.java new file mode 100644 index 0000000..227db01 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionCellRendererTest.java @@ -0,0 +1,396 @@ +/* + * 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.Font; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import javax.swing.DefaultListCellRenderer; +import javax.swing.Icon; +import javax.swing.JList; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class CompletionCellRendererTest { + + + @AfterEach + void tearDown() { + CompletionCellRenderer.setAlternateBackground(null); + } + + + @Test + void constructor_noArg_showsTypesByDefault() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + Assertions.assertTrue(renderer.getShowTypes()); + Assertions.assertNull(renderer.getDelegateRenderer()); + } + + + @Test + void constructor_withDelegate_setsDelegateRenderer() { + DefaultListCellRenderer delegate = new DefaultListCellRenderer(); + CompletionCellRenderer renderer = new CompletionCellRenderer(delegate); + Assertions.assertSame(delegate, renderer.getDelegateRenderer()); + } + + + @Test + void delegateToSubstanceRenderer_substanceNotOnClasspath_throws() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + Assertions.assertThrows(Exception.class, renderer::delegateToSubstanceRenderer); + } + + + @Test + void getSetAlternateBackground_roundTrips() { + Assertions.assertNull(CompletionCellRenderer.getAlternateBackground()); + Color color = new Color(1, 2, 3); + CompletionCellRenderer.setAlternateBackground(color); + Assertions.assertEquals(color, CompletionCellRenderer.getAlternateBackground()); + } + + + @Test + void getSetDelegateRenderer_roundTrips() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultListCellRenderer delegate = new DefaultListCellRenderer(); + renderer.setDelegateRenderer(delegate); + Assertions.assertSame(delegate, renderer.getDelegateRenderer()); + } + + + @Test + void getSetDisplayFont_roundTrips() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + Assertions.assertNull(renderer.getDisplayFont()); + Font font = new Font(Font.MONOSPACED, Font.PLAIN, 14); + renderer.setDisplayFont(font); + Assertions.assertEquals(font, renderer.getDisplayFont()); + } + + + @Test + void getSetShowTypes_roundTrips() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + renderer.setShowTypes(false); + Assertions.assertFalse(renderer.getShowTypes()); + renderer.setShowTypes(true); + Assertions.assertTrue(renderer.getShowTypes()); + } + + + @Test + void setParamColor_null_isIgnored() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion fc = new FunctionCompletion(provider, "foo", "int"); + fc.setParams(java.util.List.of(new FunctionCompletion.Parameter("int", "a"))); + + renderer.setParamColor(null); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, fc, 0, false, false); + Assertions.assertTrue(renderer.getText().contains("#aa0077") + || renderer.getText().contains("color='#")); + } + + + @Test + void setTypeColor_appliesGivenColorToTypeText() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + renderer.setTypeColor(new Color(0x123456)); + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion vc = new VariableCompletion(provider, "myVar", "int"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, vc, 0, false, false); + + Assertions.assertTrue(renderer.getText().contains("#123456")); + } + + + @Test + void setTypeColor_null_isIgnored() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + renderer.setTypeColor(null); // Should not throw or change default + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion vc = new VariableCompletion(provider, "myVar", "int"); + + JList list = new JList<>(); + Assertions.assertDoesNotThrow(() -> + renderer.getListCellRendererComponent(list, vc, 0, false, false)); + } + + + @Test + void getListCellRendererComponent_functionCompletion_rendersNameAndParams() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion fc = new FunctionCompletion(provider, "foo", "int"); + fc.setParams(java.util.List.of(new FunctionCompletion.Parameter("int", "a"))); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, fc, 0, false, false); + + String text = renderer.getText(); + Assertions.assertTrue(text.contains("foo(")); + Assertions.assertTrue(text.contains(">int a)")); + Assertions.assertTrue(text.contains(" : ")); // return type appended + } + + + @Test + void getListCellRendererComponent_functionCompletion_showTypesFalse_omitsReturnType() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + renderer.setShowTypes(false); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion fc = new FunctionCompletion(provider, "foo", "int"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, fc, 0, false, false); + + Assertions.assertFalse(renderer.getText().contains(" : ")); + } + + + @Test + void getListCellRendererComponent_variableCompletion_rendersName() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion vc = new VariableCompletion(provider, "myVar", "int"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, vc, 0, false, false); + + Assertions.assertTrue(renderer.getText().contains("myVar")); + } + + + @Test + void getListCellRendererComponent_templateCompletion_rendersInputTextAndDescription() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion tc = new TemplateCompletion(provider, "for", "for-loop", "for (${i}) {}", + "a for loop", "summary"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, tc, 0, false, false); + + Assertions.assertTrue(renderer.getText().contains("for")); + Assertions.assertTrue(renderer.getText().contains("a for loop")); + } + + + @Test + void getListCellRendererComponent_markupTagCompletion_rendersName() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion mtc = new MarkupTagCompletion(provider, "div"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, mtc, 0, false, false); + + Assertions.assertTrue(renderer.getText().contains("div")); + } + + + @Test + void getListCellRendererComponent_basicCompletion_withShortDescription_rendersDescription() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo", "a short description"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, bc, 0, false, false); + + Assertions.assertTrue(renderer.getText().contains("foo")); + Assertions.assertTrue(renderer.getText().contains("a short description")); + } + + + @Test + void getListCellRendererComponent_basicCompletion_noShortDescription_rendersOnlyInputText() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, bc, 0, false, false); + + Assertions.assertTrue(renderer.getText().contains("foo")); + Assertions.assertFalse(renderer.getText().contains(" - ")); + } + + + @Test + void getListCellRendererComponent_customFontSet_overridesListFont() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + Font font = new Font(Font.MONOSPACED, Font.PLAIN, 20); + renderer.setDisplayFont(font); + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, bc, 0, false, false); + + Assertions.assertEquals(font, renderer.getFont()); + } + + + @Test + void getListCellRendererComponent_oddIndexWithAlternateBackground_setsAlternateBackground() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + Color altBg = new Color(5, 6, 7); + CompletionCellRenderer.setAlternateBackground(altBg); + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, bc, 1, false, false); + + Assertions.assertEquals(altBg, renderer.getBackground()); + } + + + @Test + void getListCellRendererComponent_selected_doesNotUseAlternateBackground() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + Color altBg = new Color(5, 6, 7); + CompletionCellRenderer.setAlternateBackground(altBg); + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, bc, 1, true, false); + + Assertions.assertNotEquals(altBg, renderer.getBackground()); + } + + + @Test + void getListCellRendererComponent_withDelegate_returnsDelegateWithMatchingTextAndIcon() { + DefaultListCellRenderer delegate = new DefaultListCellRenderer(); + CompletionCellRenderer renderer = new CompletionCellRenderer(delegate); + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo"); + + JList list = new JList<>(); + java.awt.Component result = renderer.getListCellRendererComponent(list, bc, 0, false, false); + + Assertions.assertSame(delegate, result); + Assertions.assertEquals(renderer.getText(), delegate.getText()); + } + + + @Test + void getEmptyIcon_isCachedAcrossCalls() throws ReflectiveOperationException { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + java.lang.reflect.Method m = CompletionCellRenderer.class.getDeclaredMethod("getEmptyIcon"); + m.setAccessible(true); + Icon first = (Icon) m.invoke(renderer); + Icon second = (Icon) m.invoke(renderer); + Assertions.assertNotNull(first); + Assertions.assertSame(first, second); + } + + + @Test + void getIcon_classpathResourceFound_returnsIcon() throws ReflectiveOperationException { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + java.lang.reflect.Method m = CompletionCellRenderer.class.getDeclaredMethod("getIcon", String.class); + m.setAccessible(true); + Icon icon = (Icon) m.invoke(renderer, "arrow_left.png"); + Assertions.assertNotNull(icon); + } + + + @Test + void setIconWithDefault_completionHasIcon_usesCompletionIcon() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + Icon icon = new EmptyIcon(8); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo"); + bc.setIcon(icon); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, bc, 0, false, false); + + Assertions.assertSame(icon, renderer.getIcon()); + } + + + @Test + void updateUI_withDelegate_doesNotThrow() { + DefaultListCellRenderer delegate = new DefaultListCellRenderer(); + CompletionCellRenderer renderer = new CompletionCellRenderer(delegate); + Assertions.assertDoesNotThrow(renderer::updateUI); + } + + + @Test + void updateUI_withoutDelegate_doesNotThrow() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + Assertions.assertDoesNotThrow(renderer::updateUI); + } + + + @Test + void paintComponent_noIcon_doesNotThrow() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo"); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, bc, 0, false, false); + renderer.setSize(100, 20); + + BufferedImage image = new BufferedImage(100, 20, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + try { + Assertions.assertDoesNotThrow(() -> renderer.paint(g)); + } finally { + g.dispose(); + } + } + + + @Test + void paintComponent_selectedWithIcon_doesNotThrow() { + CompletionCellRenderer renderer = new CompletionCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion bc = new BasicCompletion(provider, "foo"); + bc.setIcon(new EmptyIcon(16)); + + JList list = new JList<>(); + renderer.getListCellRendererComponent(list, bc, 0, true, false); + renderer.setSize(100, 20); + + BufferedImage image = new BufferedImage(100, 20, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + try { + Assertions.assertDoesNotThrow(() -> renderer.paint(g)); + } finally { + g.dispose(); + } + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionListModelTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionListModelTest.java new file mode 100644 index 0000000..9c51f45 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionListModelTest.java @@ -0,0 +1,172 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class CompletionListModelTest { + + + private static final class RecordingListener implements ListDataListener { + + private final List added = new ArrayList<>(); + private final List removed = new ArrayList<>(); + + @Override + public void intervalAdded(ListDataEvent e) { + added.add(e); + } + + @Override + public void intervalRemoved(ListDataEvent e) { + removed.add(e); + } + + @Override + public void contentsChanged(ListDataEvent e) { + // Not used by CompletionListModel. + } + } + + + @Test + void getSize_emptyByDefault() { + CompletionListModel model = new CompletionListModel(); + Assertions.assertEquals(0, model.getSize()); + } + + + @Test + void setContents_populatesModel() { + CompletionListModel model = new CompletionListModel(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion c1 = new BasicCompletion(provider, "a"); + BasicCompletion c2 = new BasicCompletion(provider, "b"); + + model.setContents(List.of(c1, c2)); + + Assertions.assertEquals(2, model.getSize()); + Assertions.assertSame(c1, model.getElementAt(0)); + Assertions.assertSame(c2, model.getElementAt(1)); + } + + + @Test + void setContents_firesIntervalAdded() { + CompletionListModel model = new CompletionListModel(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + RecordingListener listener = new RecordingListener(); + model.addListDataListener(listener); + + model.setContents(List.of( + new BasicCompletion(provider, "a"), + new BasicCompletion(provider, "b"), + new BasicCompletion(provider, "c"))); + + Assertions.assertEquals(1, listener.added.size()); + Assertions.assertEquals(0, listener.removed.size()); + ListDataEvent event = listener.added.get(0); + Assertions.assertEquals(0, event.getIndex0()); + Assertions.assertEquals(2, event.getIndex1()); + } + + + @Test + void setContents_emptyCollection_doesNotFireIntervalAdded() { + CompletionListModel model = new CompletionListModel(); + RecordingListener listener = new RecordingListener(); + model.addListDataListener(listener); + + model.setContents(List.of()); + + Assertions.assertTrue(listener.added.isEmpty()); + Assertions.assertEquals(0, model.getSize()); + } + + + @Test + void setContents_replacesPreviousContents() { + CompletionListModel model = new CompletionListModel(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion c1 = new BasicCompletion(provider, "a"); + BasicCompletion c2 = new BasicCompletion(provider, "b"); + + model.setContents(List.of(c1)); + model.setContents(List.of(c2)); + + Assertions.assertEquals(1, model.getSize()); + Assertions.assertSame(c2, model.getElementAt(0)); + } + + + @Test + void setContents_replacingNonEmptyContents_firesRemovedThenAdded() { + CompletionListModel model = new CompletionListModel(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + model.setContents(List.of( + new BasicCompletion(provider, "a"), + new BasicCompletion(provider, "b"))); + + RecordingListener listener = new RecordingListener(); + model.addListDataListener(listener); + + model.setContents(List.of(new BasicCompletion(provider, "c"))); + + Assertions.assertEquals(1, listener.removed.size()); + ListDataEvent removedEvent = listener.removed.get(0); + Assertions.assertEquals(0, removedEvent.getIndex0()); + Assertions.assertEquals(1, removedEvent.getIndex1()); + + Assertions.assertEquals(1, listener.added.size()); + ListDataEvent addedEvent = listener.added.get(0); + Assertions.assertEquals(0, addedEvent.getIndex0()); + Assertions.assertEquals(0, addedEvent.getIndex1()); + } + + + @Test + void clear_onEmptyModel_doesNotFireIntervalRemoved() { + CompletionListModel model = new CompletionListModel(); + RecordingListener listener = new RecordingListener(); + model.addListDataListener(listener); + + model.clear(); + + Assertions.assertTrue(listener.removed.isEmpty()); + } + + + @Test + void clear_onNonEmptyModel_firesIntervalRemovedAndEmptiesModel() { + CompletionListModel model = new CompletionListModel(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + model.setContents(List.of( + new BasicCompletion(provider, "a"), + new BasicCompletion(provider, "b"), + new BasicCompletion(provider, "c"))); + + RecordingListener listener = new RecordingListener(); + model.addListDataListener(listener); + + model.clear(); + + Assertions.assertEquals(0, model.getSize()); + Assertions.assertEquals(1, listener.removed.size()); + ListDataEvent event = listener.removed.get(0); + Assertions.assertEquals(0, event.getIndex0()); + Assertions.assertEquals(2, event.getIndex1()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionXMLParserTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionXMLParserTest.java new file mode 100644 index 0000000..6cd5645 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/CompletionXMLParserTest.java @@ -0,0 +1,358 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + + +class CompletionXMLParserTest { + + + private static final String DOCTYPE = + "\n" + + "\n"; + + + private static CompletionXMLParser parse(String xml, CompletionProvider provider) + throws IOException, SAXException, ParserConfigurationException { + return parse(xml, provider, null); + } + + + private static CompletionXMLParser parse(String xml, CompletionProvider provider, + ClassLoader cl) throws IOException, SAXException, ParserConfigurationException { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setValidating(true); + CompletionXMLParser handler = new CompletionXMLParser(provider, cl); + SAXParser saxParser = factory.newSAXParser(); + try (InputStream in = new ByteArrayInputStream( + xml.getBytes(StandardCharsets.UTF_8))) { + saxParser.parse(in, handler); + } + return handler; + } + + + @Test + void resolveEntity_returnsDtdFromClasspath() { + CompletionXMLParser parser = new CompletionXMLParser(new DefaultCompletionProvider()); + InputSource source = parser.resolveEntity("whatever", "whatever"); + Assertions.assertNotNull(source); + Assertions.assertNotNull(source.getByteStream()); + } + + + @Test + void constructor_defaultsHaveNoCompletions() { + CompletionXMLParser parser = new CompletionXMLParser(new DefaultCompletionProvider()); + Assertions.assertTrue(parser.getCompletions().isEmpty()); + Assertions.assertEquals(0, parser.getParamStartChar()); + Assertions.assertEquals(0, parser.getParamEndChar()); + Assertions.assertNull(parser.getParamSeparator()); + } + + + @Test + void parse_otherType_createsBasicCompletion() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "a description\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + List completions = parser.getCompletions(); + Assertions.assertEquals(1, completions.size()); + BasicCompletion completion = (BasicCompletion) completions.get(0); + Assertions.assertEquals("foo", completion.getReplacementText()); + Assertions.assertEquals("a description", completion.getSummary()); + } + + + @Test + void parse_constantType_createsVariableCompletion() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "the max value\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + List completions = parser.getCompletions(); + Assertions.assertEquals(1, completions.size()); + VariableCompletion completion = (VariableCompletion) completions.get(0); + Assertions.assertEquals("MAX", completion.getName()); + Assertions.assertEquals("int", completion.getType()); + Assertions.assertEquals("limits.h", completion.getDefinedIn()); + Assertions.assertEquals("the max value", completion.getShortDescription()); + } + + + @Test + void parse_tagType_createsMarkupTagCompletion() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "the id attribute\n" + + "\n" + + "\n" + + "a division\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + List completions = parser.getCompletions(); + Assertions.assertEquals(1, completions.size()); + MarkupTagCompletion completion = (MarkupTagCompletion) completions.get(0); + Assertions.assertEquals("div", completion.getName()); + Assertions.assertEquals("HTML5", completion.getDefinedIn()); + Assertions.assertEquals("a division", completion.getDescription()); + Assertions.assertEquals(1, completion.getAttributeCount()); + Assertions.assertEquals("id", completion.getAttribute(0).getName()); + Assertions.assertEquals("the id attribute", completion.getAttribute(0).getDescription()); + } + + + @Test + void parse_functionType_createsFunctionCompletion() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "the value\n" + + "\n" + + "\n" + + "absolute value\n" + + "the absolute value\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + List completions = parser.getCompletions(); + Assertions.assertEquals(1, completions.size()); + FunctionCompletion completion = (FunctionCompletion) completions.get(0); + Assertions.assertEquals("abs", completion.getName()); + Assertions.assertEquals("int", completion.getType()); + Assertions.assertEquals("stdlib.h", completion.getDefinedIn()); + Assertions.assertEquals("absolute value", completion.getShortDescription()); + Assertions.assertEquals("the absolute value", completion.getReturnValueDescription()); + Assertions.assertEquals(1, completion.getParamCount()); + Assertions.assertEquals("n", completion.getParam(0).getName()); + Assertions.assertEquals("int", completion.getParam(0).getType()); + Assertions.assertFalse(completion.getParam(0).isEndParam()); + } + + + @Test + void parse_functionParam_endParamTrue() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + FunctionCompletion completion = (FunctionCompletion) parser.getCompletions().get(0); + Assertions.assertTrue(completion.getParam(0).isEndParam()); + } + + + @Test + void parse_multipleKeywords() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + Assertions.assertEquals(3, parser.getCompletions().size()); + } + + + @Test + void parse_environmentElement_setsParameterListInfo() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + Assertions.assertEquals('(', parser.getParamStartChar()); + Assertions.assertEquals(')', parser.getParamEndChar()); + Assertions.assertEquals(", ", parser.getParamSeparator()); + } + + + @Test + void parse_customFunctionCompletionType_isUsed() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + FunctionCompletion completion = (FunctionCompletion) parser.getCompletions().get(0); + Assertions.assertInstanceOf(CustomFunctionCompletion.class, completion); + } + + + @Test + void parse_customFunctionCompletionType_badClassName_fallsBackToDefault() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider); + + FunctionCompletion completion = (FunctionCompletion) parser.getCompletions().get(0); + Assertions.assertEquals(FunctionCompletion.class, completion.getClass()); + } + + + @Test + void parse_unknownKeywordType_throwsInternalError() { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + Assertions.assertThrows(InternalError.class, () -> parse(xml, provider)); + } + + + @Test + void parse_missingRequiredAttribute_throwsSAXException() { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + // missing required "type" attribute + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + Assertions.assertThrows(SAXException.class, () -> parse(xml, provider)); + } + + + @Test + void reset_clearsCompletionsAndUsesNewProvider() throws Exception { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); + CompletionXMLParser parser = parse(xml, provider1); + Assertions.assertEquals(1, parser.getCompletions().size()); + + DefaultCompletionProvider provider2 = new DefaultCompletionProvider(); + parser.reset(provider2); + Assertions.assertTrue(parser.getCompletions().isEmpty()); + + CompletionXMLParser reparsed = parse(xml, provider2); + Assertions.assertEquals(1, reparsed.getCompletions().size()); + } + + + @Test + void setDefaultCompletionClassLoader_usedWhenNoneSpecifiedInConstructor() { + ClassLoader customLoader = getClass().getClassLoader(); + CompletionXMLParser.setDefaultCompletionClassLoader(customLoader); + try { + // Just verify this doesn't throw; the class loader is private state + // exercised indirectly via parsing with a custom function type. + Assertions.assertDoesNotThrow(() -> + new CompletionXMLParser(new DefaultCompletionProvider())); + } finally { + CompletionXMLParser.setDefaultCompletionClassLoader(null); + } + } + + + /** + * A custom {@code FunctionCompletion} used to verify that + * {@code CompletionXMLParser} can instantiate custom types specified via + * the {@code functionCompletionType} XML element. + */ + public static class CustomFunctionCompletion extends FunctionCompletion { + public CustomFunctionCompletion(CompletionProvider provider, String name, + String returnType) { + super(provider, name, returnType); + } + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/DefaultCompletionProviderTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/DefaultCompletionProviderTest.java new file mode 100644 index 0000000..149cd67 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/DefaultCompletionProviderTest.java @@ -0,0 +1,320 @@ +/* + * 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.Point; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import javax.swing.JTextArea; +import javax.swing.text.BadLocationException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + + +@ExtendWith(SwingRunnerExtension.class) +class DefaultCompletionProviderTest { + + + private static final String DOCTYPE = + "\n" + + "\n"; + + + @TempDir + private Path tempDir; + + + @Test + void constructor_noArg_hasNoCompletions() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + JTextArea textArea = new JTextArea("foo"); + textArea.setCaretPosition(3); + Assertions.assertTrue(provider.getCompletions(textArea).isEmpty()); + } + + + @Test + void constructor_wordArray_addsWordCompletions() { + DefaultCompletionProvider provider = new DefaultCompletionProvider( + new String[] { "foo", "bar" }); + JTextArea textArea = new JTextArea("f"); + textArea.setCaretPosition(1); + List completions = provider.getCompletions(textArea); + Assertions.assertEquals(1, completions.size()); + Assertions.assertEquals("foo", completions.get(0).getReplacementText()); + } + + + @Test + void constructor_nullWordArray_hasNoCompletions() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(null); + JTextArea textArea = new JTextArea("foo"); + textArea.setCaretPosition(3); + Assertions.assertTrue(provider.getCompletions(textArea).isEmpty()); + } + + + @Test + void getAlreadyEnteredText_returnsWordCharsBeforeCaret() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + JTextArea textArea = new JTextArea("foo bar"); + textArea.setCaretPosition(7); // end of "bar" + Assertions.assertEquals("bar", provider.getAlreadyEnteredText(textArea)); + } + + + @Test + void getAlreadyEnteredText_stopsAtNonWordChar() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + JTextArea textArea = new JTextArea("foo.bar"); + textArea.setCaretPosition(7); // end of "bar" + Assertions.assertEquals("bar", provider.getAlreadyEnteredText(textArea)); + } + + + @Test + void getAlreadyEnteredText_includesUnderscores() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + JTextArea textArea = new JTextArea("my_var"); + textArea.setCaretPosition(6); + Assertions.assertEquals("my_var", provider.getAlreadyEnteredText(textArea)); + } + + + @Test + void getAlreadyEnteredText_atStartOfLine_returnsEmptyString() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + JTextArea textArea = new JTextArea("foo"); + textArea.setCaretPosition(0); + Assertions.assertEquals("", provider.getAlreadyEnteredText(textArea)); + } + + + @Test + void isValidChar_lettersDigitsAndUnderscoreAreValid() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + JTextArea textArea = new JTextArea("a1_ ."); + // Indirectly verified via getAlreadyEnteredText, since isValidChar is protected. + textArea.setCaretPosition(3); // after "a1_" + Assertions.assertEquals("a1_", provider.getAlreadyEnteredText(textArea)); + } + + + @Test + void getCompletionsAt_returnsMatchingCompletions() throws BadLocationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + provider.addCompletion(completion); + + JTextArea textArea = new JTextArea("foo bar"); + textArea.setSize(300, 100); + + Point p = pointForOffset(textArea, 1); // inside "foo" + List results = provider.getCompletionsAt(textArea, p); + + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.size()); + Assertions.assertSame(completion, results.get(0)); + } + + + @Test + void getCompletionsAt_noMatch_returnsNull() throws BadLocationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + + JTextArea textArea = new JTextArea("foo bar"); + textArea.setSize(300, 100); + + Point p = pointForOffset(textArea, 5); // inside "bar", no completion defined + Assertions.assertNull(provider.getCompletionsAt(textArea, p)); + } + + + // NOTE: getCompletionsAt()'s "offset < 0 || offset >= document length" branch + // isn't exercised here, since JTextArea's viewToModel2D() always clamps + // out-of-bounds points to the nearest valid offset rather than returning + // one outside the document. + + + @Test + void getCompletionsAt_repeatedCallsAtSameOffset_returnCachedList() + throws BadLocationException { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo")); + + JTextArea textArea = new JTextArea("foo bar"); + textArea.setSize(300, 100); + Point p = pointForOffset(textArea, 1); + + List first = provider.getCompletionsAt(textArea, p); + List second = provider.getCompletionsAt(textArea, p); + + Assertions.assertSame(first, second); + } + + + @Test + void getParameterizedCompletions_parameterListNotConfigured_returnsNull() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + JTextArea textArea = new JTextArea("foo("); + textArea.setCaretPosition(4); + Assertions.assertNull(provider.getParameterizedCompletions(textArea)); + } + + + @Test + void getParameterizedCompletions_notEnoughCharsOnLine_returnsNull() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + JTextArea textArea = new JTextArea("("); + textArea.setCaretPosition(1); + Assertions.assertNull(provider.getParameterizedCompletions(textArea)); + } + + + @Test + void getParameterizedCompletions_matchingFunctionCompletion_isReturned() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion fc = new FunctionCompletion(provider, "foo", "void"); + provider.addCompletion(fc); + + JTextArea textArea = new JTextArea("foo("); + textArea.setCaretPosition(4); // right after '(' + + List results = provider.getParameterizedCompletions(textArea); + + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.size()); + Assertions.assertSame(fc, results.get(0)); + } + + + @Test + void getParameterizedCompletions_matchingNonParameterizedCompletion_returnsNull() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + provider.addCompletion(new BasicCompletion(provider, "foo")); + + JTextArea textArea = new JTextArea("foo("); + textArea.setCaretPosition(4); + + Assertions.assertNull(provider.getParameterizedCompletions(textArea)); + } + + + @Test + void loadFromXML_inputStream_addsCompletionsAndParamListSettings() throws IOException { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "a description\n" + + "\n" + + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.loadFromXML( + new java.io.ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + + JTextArea textArea = new JTextArea("f"); + textArea.setCaretPosition(1); + List completions = provider.getCompletions(textArea); + Assertions.assertEquals(1, completions.size()); + Assertions.assertEquals("foo", completions.get(0).getReplacementText()); + + Assertions.assertEquals('(', provider.getParameterListStart()); + Assertions.assertEquals(')', provider.getParameterListEnd()); + Assertions.assertEquals(", ", provider.getParameterListSeparator()); + } + + + @Test + void loadFromXML_file_addsCompletions() throws IOException { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + Path file = tempDir.resolve("test.xml"); + Files.writeString(file, xml, StandardCharsets.UTF_8); + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.loadFromXML(file.toFile()); + + JTextArea textArea = new JTextArea("b"); + textArea.setCaretPosition(1); + List completions = provider.getCompletions(textArea); + Assertions.assertEquals(1, completions.size()); + Assertions.assertEquals("bar", completions.get(0).getReplacementText()); + } + + + @Test + void loadFromXML_resourceString_fallsBackToFileSystemPath() throws IOException { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + Path file = tempDir.resolve("test2.xml"); + Files.writeString(file, xml, StandardCharsets.UTF_8); + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.loadFromXML(file.toAbsolutePath().toString()); + + JTextArea textArea = new JTextArea("b"); + textArea.setCaretPosition(1); + List completions = provider.getCompletions(textArea); + Assertions.assertEquals(1, completions.size()); + Assertions.assertEquals("baz", completions.get(0).getReplacementText()); + } + + + @Test + void loadFromXML_resourceString_noSuchResource_throwsIOException() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + Assertions.assertThrows(IOException.class, + () -> provider.loadFromXML("this/does/not/exist.xml")); + } + + + @Test + void loadFromXML_malformedXml_throwsIOException() { + String xml = DOCTYPE + + "\n" + + "\n" + + "\n" + // missing required "type" attribute + "\n" + + "\n"; + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + Assertions.assertThrows(IOException.class, () -> provider.loadFromXML( + new java.io.ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)))); + } + + + private static Point pointForOffset(JTextArea textArea, int offset) + throws BadLocationException { + Rectangle2D r = textArea.modelToView2D(offset); + return new Point((int) r.getCenterX(), (int) r.getCenterY()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/DelegatingCellRendererTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/DelegatingCellRendererTest.java new file mode 100644 index 0000000..88b75e5 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/DelegatingCellRendererTest.java @@ -0,0 +1,121 @@ +/* + * 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.Component; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class DelegatingCellRendererTest { + + + @Test + void getSetFallbackCellRenderer_roundTrips() { + DelegatingCellRenderer renderer = new DelegatingCellRenderer(); + Assertions.assertNull(renderer.getFallbackCellRenderer()); + + DefaultListCellRenderer fallback = new DefaultListCellRenderer(); + renderer.setFallbackCellRenderer(fallback); + Assertions.assertSame(fallback, renderer.getFallbackCellRenderer()); + } + + + @Test + void getListCellRendererComponent_providerHasRenderer_delegatesToProviderRenderer() { + DelegatingCellRenderer renderer = new DelegatingCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + + DefaultListCellRenderer providerRenderer = new DefaultListCellRenderer(); + provider.setListCellRenderer(providerRenderer); + + BasicCompletion completion = new BasicCompletion(provider, "foo"); + JList list = new JList<>(); + + Component result = renderer.getListCellRendererComponent(list, completion, 0, false, false); + + Assertions.assertSame(providerRenderer, result); + } + + + @Test + void getListCellRendererComponent_noProviderRendererOrFallback_usesSuperDefault() { + DelegatingCellRenderer renderer = new DelegatingCellRenderer(); + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + JList list = new JList<>(); + + Component result = renderer.getListCellRendererComponent(list, completion, 0, false, false); + + Assertions.assertSame(renderer, result); + } + + + @Test + void getListCellRendererComponent_noProviderRenderer_withFallback_usesFallback() { + DelegatingCellRenderer renderer = new DelegatingCellRenderer(); + DefaultListCellRenderer fallback = new DefaultListCellRenderer(); + renderer.setFallbackCellRenderer(fallback); + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(provider, "foo"); + JList list = new JList<>(); + + Component result = renderer.getListCellRendererComponent(list, completion, 0, false, false); + + Assertions.assertSame(fallback, result); + } + + + @Test + void updateUI_fallbackIsJComponentAndNotThis_delegatesUpdateUI() { + DelegatingCellRenderer renderer = new DelegatingCellRenderer(); + boolean[] updateUiCalled = {false}; + DefaultListCellRenderer fallback = new DefaultListCellRenderer() { + @Override + public void updateUI() { + super.updateUI(); + updateUiCalled[0] = true; + } + }; + renderer.setFallbackCellRenderer(fallback); + + renderer.updateUI(); + + Assertions.assertTrue(updateUiCalled[0]); + } + + + @Test + void updateUI_fallbackIsThis_doesNotRecurse() { + DelegatingCellRenderer renderer = new DelegatingCellRenderer(); + renderer.setFallbackCellRenderer(renderer); + Assertions.assertDoesNotThrow(renderer::updateUI); + } + + + @Test + void updateUI_fallbackIsNotJComponent_doesNotThrow() { + DelegatingCellRenderer renderer = new DelegatingCellRenderer(); + ListCellRenderer nonComponentFallback = + (list, value, index, isSelected, cellHasFocus) -> null; + renderer.setFallbackCellRenderer(nonComponentFallback); + Assertions.assertDoesNotThrow(renderer::updateUI); + } + + + @Test + void updateUI_noFallback_doesNotThrow() { + DelegatingCellRenderer renderer = new DelegatingCellRenderer(); + Assertions.assertDoesNotThrow(renderer::updateUI); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/EmptyIconTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/EmptyIconTest.java new file mode 100644 index 0000000..ba3cf1c --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/EmptyIconTest.java @@ -0,0 +1,47 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import java.io.Serializable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class EmptyIconTest { + + + @Test + void constructor_setsSize() { + EmptyIcon icon = new EmptyIcon(16); + Assertions.assertEquals(16, icon.getIconWidth()); + Assertions.assertEquals(16, icon.getIconHeight()); + } + + + @Test + void setSize_updatesWidthAndHeight() { + EmptyIcon icon = new EmptyIcon(16); + icon.setSize(32); + Assertions.assertEquals(32, icon.getIconWidth()); + Assertions.assertEquals(32, icon.getIconHeight()); + } + + + @Test + void paintIcon_doesNothingAndDoesNotThrow() { + EmptyIcon icon = new EmptyIcon(16); + Assertions.assertDoesNotThrow(() -> icon.paintIcon(null, null, 0, 0)); + } + + + @Test + void isSerializable() { + Assertions.assertInstanceOf(Serializable.class, new EmptyIcon(16)); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/FastListUITest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/FastListUITest.java new file mode 100644 index 0000000..7e160c1 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/FastListUITest.java @@ -0,0 +1,292 @@ +/* + * 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.Component; +import java.awt.Dimension; +import java.awt.Rectangle; +import javax.swing.DefaultListModel; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.ListCellRenderer; +import javax.swing.UIManager; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class FastListUITest { + + + /** + * A renderer whose preferred height differs for the first item versus + * all others, so tests can tell whether {@code FastListUI} estimated + * cell sizes from the first item alone, or computed them for real. + */ + private static final class VariableHeightRenderer extends JLabel + implements ListCellRenderer { + + private static final int FIRST_ITEM_HEIGHT = 20; + private static final int OTHER_ITEM_HEIGHT = 60; + + private int preferredHeight = FIRST_ITEM_HEIGHT; + + @Override + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + setText(String.valueOf(value)); + preferredHeight = index == 0 ? FIRST_ITEM_HEIGHT : OTHER_ITEM_HEIGHT; + return this; + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(100, preferredHeight); + } + } + + + private static JList createList(int itemCount) { + DefaultListModel model = new DefaultListModel<>(); + for (int i = 0; i < itemCount; i++) { + model.addElement("item " + i); + } + JList list = new JList<>(model); + list.setCellRenderer(new VariableHeightRenderer()); + return list; + } + + + @Test + void installUI_doesNotThrow() { + JList list = createList(5); + Assertions.assertDoesNotThrow(() -> list.setUI(new FastListUI())); + } + + + /** + * Reads {@code FastListUI}'s private {@code overriddenBackground}/{@code overriddenForeground} + * flags via reflection. These are the only direct evidence that the {@code if} branches in + * {@code installDefaults()} actually ran: {@code BasicListUI.installDefaults()} (called first, + * via {@code super}) backfills {@code list.getSelectionBackground()}/{@code Foreground()} from + * {@code UIManager} whenever the current value is {@code null} or a {@code UIResource} - + * which is true for a freshly-constructed {@code JList} even if you explicitly set the color to + * {@code null} beforehand. So on every real-world L&F that defines "List.selectionBackground"/ + * "List.selectionForeground" (i.e. everything except Nimbus), the value is never actually still + * {@code null} by the time {@code FastListUI}'s own check runs - asserting the resulting color is + * non-null doesn't prove {@code FastListUI}'s branch fired; it only proves {@code super} did its + * job. To genuinely exercise these branches, the relevant {@code UIManager} key must be removed + * first, simulating a L&F that doesn't define it (the Nimbus scenario this class's Javadoc + * describes). + */ + private static boolean getOverriddenFlag(FastListUI ui, String fieldName) throws ReflectiveOperationException { + java.lang.reflect.Field field = FastListUI.class.getDeclaredField(fieldName); + field.setAccessible(true); + return field.getBoolean(ui); + } + + + /** + * Removes {@code key} from the current Look and Feel's own {@code UIDefaults} table, so that + * {@code UIManager.getColor(key)} resolves to {@code null}. Note that {@code UIManager.put(key, + * null)} does *not* achieve this: per its docs, a {@code null} value there just removes any + * *override* sitting on top of the L&F's table, and the lookup then falls through to the L&F's + * own (still-present) value underneath - which is exactly the trap that produced the flawed + * tests this method replaces. + */ + private static Object removeLookAndFeelColor(String key) { + Object saved = UIManager.getLookAndFeelDefaults().get(key); + UIManager.getLookAndFeelDefaults().remove(key); + return saved; + } + + + private static void restoreLookAndFeelColor(String key, Object saved) { + UIManager.getLookAndFeelDefaults().put(key, saved); + } + + + @Test + void installDefaults_doesNotOverrideExistingSelectionColors() { + JList list = createList(5); + list.setSelectionBackground(Color.RED); + list.setSelectionForeground(Color.BLUE); + + list.setUI(new FastListUI()); + + Assertions.assertEquals(Color.RED, list.getSelectionBackground()); + Assertions.assertEquals(Color.BLUE, list.getSelectionForeground()); + } + + + @Test + void installDefaults_lookAndFeelDefinesNeitherSelectionColor_overridesBoth() throws ReflectiveOperationException { + JList list = createList(5); + + Object savedBg = removeLookAndFeelColor("List.selectionBackground"); + Object savedFg = removeLookAndFeelColor("List.selectionForeground"); + try { + FastListUI ui = new FastListUI(); + list.setUI(ui); + + Assertions.assertTrue(getOverriddenFlag(ui, "overriddenBackground")); + Assertions.assertTrue(getOverriddenFlag(ui, "overriddenForeground")); + // determineSelectionBackground()/Foreground() must return plain Colors, not + // ColorUIResources, so a later LAF switch can't silently wipe them back out + // (see the class-level comment on determineSelectionBackground()). + Assertions.assertFalse(list.getSelectionBackground() instanceof javax.swing.plaf.UIResource); + Assertions.assertFalse(list.getSelectionForeground() instanceof javax.swing.plaf.UIResource); + } finally { + restoreLookAndFeelColor("List.selectionBackground", savedBg); + restoreLookAndFeelColor("List.selectionForeground", savedFg); + } + } + + + @Test + void installDefaults_lookAndFeelDefinesOnlySelectionForeground_overridesOnlyBackground() + throws ReflectiveOperationException { + JList list = createList(5); + + Object savedBg = removeLookAndFeelColor("List.selectionBackground"); + try { + FastListUI ui = new FastListUI(); + list.setUI(ui); + + Assertions.assertTrue(getOverriddenFlag(ui, "overriddenBackground")); + Assertions.assertFalse(getOverriddenFlag(ui, "overriddenForeground")); + Assertions.assertNotNull(list.getSelectionBackground()); + } finally { + restoreLookAndFeelColor("List.selectionBackground", savedBg); + } + } + + + @Test + void installDefaults_lookAndFeelDefinesOnlySelectionBackground_overridesOnlyForeground() + throws ReflectiveOperationException { + JList list = createList(5); + + Object savedFg = removeLookAndFeelColor("List.selectionForeground"); + try { + FastListUI ui = new FastListUI(); + list.setUI(ui); + + Assertions.assertFalse(getOverriddenFlag(ui, "overriddenBackground")); + Assertions.assertTrue(getOverriddenFlag(ui, "overriddenForeground")); + Assertions.assertNotNull(list.getSelectionForeground()); + } finally { + restoreLookAndFeelColor("List.selectionForeground", savedFg); + } + } + + + /** + * {@code BasicListUI.uninstallDefaults()} (called via {@code super}) only clears + * {@code selectionBackground}/{@code Foreground} when they're {@code UIResource} instances - but + * the colors {@code FastListUI} installs are deliberately plain {@code Color}s (see + * {@code determineSelectionBackground()}/{@code Foreground()}), so {@code super} can't clean + * them up on its own. This is exactly why {@code FastListUI} needs its own + * {@code overriddenBackground}/{@code overriddenForeground}-guarded clearing logic. + */ + @Test + void uninstallDefaults_clearsColorsThisUiOverrode() { + JList list = createList(5); + + Object savedBg = removeLookAndFeelColor("List.selectionBackground"); + Object savedFg = removeLookAndFeelColor("List.selectionForeground"); + try { + FastListUI ui = new FastListUI(); + list.setUI(ui); + Assertions.assertNotNull(list.getSelectionBackground()); + Assertions.assertNotNull(list.getSelectionForeground()); + + list.setUI(null); // triggers uninstallUI() -> uninstallDefaults() + + Assertions.assertNull(list.getSelectionBackground()); + Assertions.assertNull(list.getSelectionForeground()); + } finally { + restoreLookAndFeelColor("List.selectionBackground", savedBg); + restoreLookAndFeelColor("List.selectionForeground", savedFg); + } + } + + + @Test + void uninstallDefaults_doesNotClearUserSuppliedColors() { + JList list = createList(5); + list.setSelectionBackground(Color.RED); + list.setSelectionForeground(Color.BLUE); + + list.setUI(new FastListUI()); + list.setUI(null); // triggers uninstallUI() -> uninstallDefaults() + + Assertions.assertEquals(Color.RED, list.getSelectionBackground()); + Assertions.assertEquals(Color.BLUE, list.getSelectionForeground()); + } + + + @Test + void updateLayoutState_belowEstimationThreshold_computesActualCellHeights() { + // FastListUI.ESTIMATION_THRESHOLD is 200; stay comfortably under it. + JList list = createList(10); + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setSize(300, 300); + list.setUI(new FastListUI()); + + Rectangle row0Bounds = list.getUI().getCellBounds(list, 0, 0); + Rectangle row1Bounds = list.getUI().getCellBounds(list, 1, 1); + + Assertions.assertEquals(VariableHeightRenderer.FIRST_ITEM_HEIGHT, row0Bounds.height); + Assertions.assertEquals(VariableHeightRenderer.OTHER_ITEM_HEIGHT, row1Bounds.height); + } + + + @Test + void updateLayoutState_atOrAboveEstimationThreshold_estimatesUniformCellHeightFromFirstItem() { + JList list = createList(250); + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setSize(300, 300); + list.setUI(new FastListUI()); + + Rectangle row0Bounds = list.getUI().getCellBounds(list, 0, 0); + Rectangle row1Bounds = list.getUI().getCellBounds(list, 1, 1); + + // Estimation assumes every row is as tall as the first row, even + // though row 1's renderer reports a taller preferred size. + Assertions.assertEquals(VariableHeightRenderer.FIRST_ITEM_HEIGHT, row0Bounds.height); + Assertions.assertEquals(VariableHeightRenderer.FIRST_ITEM_HEIGHT, row1Bounds.height); + } + + + @Test + void updateLayoutState_atOrAboveEstimationThreshold_usesViewportWidthWhenAvailable() { + JList list = createList(250); + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setSize(300, 300); + scrollPane.getViewport().setSize(250, 300); + list.setUI(new FastListUI()); + + // The estimated cell width feeds into the list's own preferred size + // (used for scrolling/layout purposes), not the value returned by + // getCellBounds() - that's derived from the list's own width instead. + Dimension preferredSize = list.getUI().getPreferredSize(list); + Assertions.assertEquals(250, preferredSize.width); + } + + + @Test + void updateLayoutState_atOrAboveEstimationThreshold_withoutViewportParent_doesNotThrow() { + JList list = createList(250); + list.setSize(300, 300); + Assertions.assertDoesNotThrow(() -> list.setUI(new FastListUI())); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/FunctionCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/FunctionCompletionTest.java new file mode 100644 index 0000000..4fb81fe --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/FunctionCompletionTest.java @@ -0,0 +1,288 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.JTextArea; + +import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class FunctionCompletionTest { + + + @Test + void constructor() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertEquals("foo", completion.getName()); + Assertions.assertEquals("int", completion.getType()); + Assertions.assertEquals(0, completion.getParamCount()); + } + + + @Test + void getSetReturnValueDescription() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertNull(completion.getReturnValueDescription()); + completion.setReturnValueDescription("returns something"); + Assertions.assertEquals("returns something", completion.getReturnValueDescription()); + } + + + @Test + void getSetParams() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + params.add(new Parameter("String", "b")); + completion.setParams(params); + + Assertions.assertEquals(2, completion.getParamCount()); + Assertions.assertEquals("a", completion.getParam(0).getName()); + Assertions.assertEquals("b", completion.getParam(1).getName()); + } + + + @Test + void setParams_null_doesNotThrowAndKeepsCountZero() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + completion.setParams(null); + Assertions.assertEquals(0, completion.getParamCount()); + } + + + @Test + void setParams_isDeepCopy() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + completion.setParams(params); + + params.add(new Parameter("int", "b")); + Assertions.assertEquals(1, completion.getParamCount()); + } + + + @Test + void getDefinitionString_noParams() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertEquals("int foo()", completion.getDefinitionString()); + } + + + @Test + void getDefinitionString_withParams() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + params.add(new Parameter("String", "b")); + completion.setParams(params); + + Assertions.assertEquals("int foo(int a, String b)", completion.getDefinitionString()); + } + + + @Test + void getDefinitionString_noReturnType() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", null); + Assertions.assertEquals("foo()", completion.getDefinitionString()); + } + + + @Test + void compareTo_sameInstance() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertEquals(0, completion.compareTo(completion)); + } + + + @Test + void compareTo_otherFunctionCompletion_byName() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion1 = new FunctionCompletion(provider, "abc", "int"); + FunctionCompletion completion2 = new FunctionCompletion(provider, "xyz", "int"); + Assertions.assertTrue(completion1.compareTo(completion2) < 0); + Assertions.assertTrue(completion2.compareTo(completion1) > 0); + } + + + @Test + void compareTo_otherFunctionCompletion_byParamCount() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion1 = new FunctionCompletion(provider, "foo", "int"); + FunctionCompletion completion2 = new FunctionCompletion(provider, "foo", "int"); + + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + completion2.setParams(params); + + Assertions.assertTrue(completion1.compareTo(completion2) < 0); + } + + + @Test + void compareTo_nonFunctionCompletion_fallsBackToSuper() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + BasicCompletion other = new BasicCompletion(provider, "foo"); + Assertions.assertEquals(0, completion.compareTo(other)); + } + + + @Test + void equals_basedOnCompareTo() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion1 = new FunctionCompletion(provider, "foo", "int"); + FunctionCompletion completion2 = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertEquals(completion1, completion2); + } + + + @Test + void equals_notEqualToDifferentName() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion1 = new FunctionCompletion(provider, "foo", "int"); + FunctionCompletion completion2 = new FunctionCompletion(provider, "bar", "int"); + Assertions.assertNotEquals(completion1, completion2); + } + + + @Test + void equals_notEqualToNonCompletion() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertNotEquals("foo", completion); + } + + + @Test + void hashCode_stableAcrossCalls() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertEquals(completion.hashCode(), completion.hashCode()); + } + + + @Test + void hashCode_incorporatesParamsAndReturnValueDescription() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + int hashBefore = completion.hashCode(); + + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + completion.setParams(params); + completion.setReturnValueDescription("a description"); + + Assertions.assertNotEquals(hashBefore, completion.hashCode()); + } + + + @Test + void getShowParameterToolTip_returnsTrue() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertTrue(completion.getShowParameterToolTip()); + } + + + @Test + void getSummary_noParamsOrReturnDesc() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + String summary = completion.getSummary(); + Assertions.assertTrue(summary.contains("int foo()")); + } + + + @Test + void getSummary_withParamsAndReturnDesc() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + + List params = new ArrayList<>(); + Parameter param = new Parameter("int", "a"); + param.setDescription("the a param"); + params.add(param); + completion.setParams(params); + completion.setReturnValueDescription("the return value"); + + String summary = completion.getSummary(); + Assertions.assertTrue(summary.contains("Parameters:")); + Assertions.assertTrue(summary.contains("the a param")); + Assertions.assertTrue(summary.contains("Returns:")); + Assertions.assertTrue(summary.contains("the return value")); + } + + + @Test + void getToolTipText_returnsSummary() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + Assertions.assertEquals(completion.getSummary(), completion.getToolTipText()); + } + + + @Test + void getInsertionInfo_noParams() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + + JTextArea textArea = new JTextArea(); + textArea.setCaretPosition(0); + + ParameterizedCompletionInsertionInfo info = + completion.getInsertionInfo(textArea, false); + + Assertions.assertEquals("()", info.getTextToInsert()); + } + + + @Test + void getInsertionInfo_withParams() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion completion = new FunctionCompletion(provider, "foo", "int"); + + List params = new ArrayList<>(); + params.add(new Parameter("int", "a")); + params.add(new Parameter("String", "b")); + completion.setParams(params); + + JTextArea textArea = new JTextArea(); + textArea.setCaretPosition(0); + + ParameterizedCompletionInsertionInfo info = + completion.getInsertionInfo(textArea, false); + + Assertions.assertEquals("(a, b)", info.getTextToInsert()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/LanguageAwareCompletionProviderTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/LanguageAwareCompletionProviderTest.java new file mode 100644 index 0000000..28c70c7 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/LanguageAwareCompletionProviderTest.java @@ -0,0 +1,494 @@ +/* + * 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.Point; +import java.awt.event.MouseEvent; +import java.awt.geom.Rectangle2D; +import java.util.List; +import javax.swing.JTextArea; +import javax.swing.text.BadLocationException; + +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +/** + * Tests {@link LanguageAwareCompletionProvider} using a real + * {@code RSyntaxTextArea} configured for Java, so token-type-based provider + * selection is exercised against a real tokenizer rather than a mock. The + * sample text and caret offsets below were chosen by inspecting the real + * Java {@code TokenMaker}'s output for this exact text, since token + * boundaries aren't obvious from the source text alone. + */ +@ExtendWith(SwingRunnerExtension.class) +class LanguageAwareCompletionProviderTest { + + /* + * Token layout for SAMPLE_TEXT (0-indexed offsets), as reported by the + * real Java TokenMaker: + * + * offs=0 RESERVED_WORD "class" + * offs=14 COMMENT_EOL "// line comment" (line ends at 29) + * offs=32 COMMENT_MULTILINE "/* multi" (spans lines 2-3) + * offs=56 COMMENT_DOCUMENTATION "/** doc comment *\/" (line ends at 74) + * offs=88 LITERAL_STRING_DOUBLE_QUOTE "\"hello\"" + * offs=103 IDENTIFIER "fo" + */ + private static final String SAMPLE_TEXT = + "class Foo {\n" + + " // line comment\n" + + " /* multi\n" + + " line */\n" + + " /** doc comment */\n" + + " String s = \"hello\";\n" + + " int fo = 1;\n" + + "}\n"; + + private static final int OFFS_RESERVED_WORD_MID = 2; // inside "class" + private static final int OFFS_TOKEN_START = 0; // start of "class" + private static final int OFFS_LINE_COMMENT_MID = 20; // inside "// line comment" + private static final int OFFS_LINE_COMMENT_EOL = 29; // end of that line (curToken == null) + private static final int OFFS_MULTILINE_COMMENT_MID = 35; + private static final int OFFS_DOC_COMMENT_MID = 60; + private static final int OFFS_DOC_COMMENT_EOL = 74; // end of doc comment line + private static final int OFFS_STRING_MID = 90; // inside "hello" + private static final int OFFS_IDENTIFIER_MID = 104; // inside "fo" + + + private static RSyntaxTextArea createTextArea() { + RSyntaxTextArea rsta = new RSyntaxTextArea(); + rsta.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + rsta.setText(SAMPLE_TEXT); + rsta.setSize(600, 400); + return rsta; + } + + + private static LanguageAwareCompletionProvider createFullyConfiguredProvider( + DefaultCompletionProvider defaultProvider) { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(defaultProvider); + provider.setStringCompletionProvider(new DefaultCompletionProvider()); + provider.setCommentCompletionProvider(new DefaultCompletionProvider()); + provider.setDocCommentCompletionProvider(new DefaultCompletionProvider()); + return provider; + } + + + @Test + void constructor_setsDefaultProvider() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(defaultProvider); + Assertions.assertSame(defaultProvider, provider.getDefaultCompletionProvider()); + } + + + @Test + void constructor_nullDefaultProvider_throwsNullPointerException() { + Assertions.assertThrows(NullPointerException.class, + () -> new LanguageAwareCompletionProvider(null)); + } + + + @Test + void protectedConstructor_subclassCanDeferSettingDefaultProvider() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + LanguageAwareCompletionProvider provider = new LanguageAwareCompletionProvider() { + { + setDefaultCompletionProvider(defaultProvider); + } + }; + Assertions.assertSame(defaultProvider, provider.getDefaultCompletionProvider()); + } + + + @Test + void setDefaultCompletionProvider_null_throwsNullPointerException() { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(new DefaultCompletionProvider()); + Assertions.assertThrows(NullPointerException.class, + () -> provider.setDefaultCompletionProvider(null)); + } + + + @Test + void clearParameterizedCompletionParams_alwaysThrows() { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(new DefaultCompletionProvider()); + Assertions.assertThrows(UnsupportedOperationException.class, + provider::clearParameterizedCompletionParams); + } + + + @Test + void setParameterizedCompletionParams_alwaysThrows() { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(new DefaultCompletionProvider()); + Assertions.assertThrows(UnsupportedOperationException.class, + () -> provider.setParameterizedCompletionParams('(', ", ", ')')); + } + + + @Test + void parameterListGetters_delegateToDefaultProvider() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.setParameterizedCompletionParams('(', ", ", ')'); + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(defaultProvider); + + Assertions.assertEquals('(', provider.getParameterListStart()); + Assertions.assertEquals(')', provider.getParameterListEnd()); + Assertions.assertEquals(", ", provider.getParameterListSeparator()); + } + + + @Test + void getSetCommentCompletionProvider() { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(new DefaultCompletionProvider()); + Assertions.assertNull(provider.getCommentCompletionProvider()); + DefaultCompletionProvider commentProvider = new DefaultCompletionProvider(); + provider.setCommentCompletionProvider(commentProvider); + Assertions.assertSame(commentProvider, provider.getCommentCompletionProvider()); + } + + + @Test + void getSetDocCommentCompletionProvider() { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(new DefaultCompletionProvider()); + Assertions.assertNull(provider.getDocCommentCompletionProvider()); + DefaultCompletionProvider docProvider = new DefaultCompletionProvider(); + provider.setDocCommentCompletionProvider(docProvider); + Assertions.assertSame(docProvider, provider.getDocCommentCompletionProvider()); + } + + + @Test + void getSetStringCompletionProvider() { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(new DefaultCompletionProvider()); + Assertions.assertNull(provider.getStringCompletionProvider()); + DefaultCompletionProvider stringProvider = new DefaultCompletionProvider(); + provider.setStringCompletionProvider(stringProvider); + Assertions.assertSame(stringProvider, provider.getStringCompletionProvider()); + } + + + @Test + void getAlreadyEnteredText_nonRSyntaxTextArea_returnsEmptyString() { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(new DefaultCompletionProvider()); + JTextArea plainTextArea = new JTextArea("foo"); + plainTextArea.setCaretPosition(3); + Assertions.assertEquals("", provider.getAlreadyEnteredText(plainTextArea)); + } + + + @Test + void getAlreadyEnteredText_codeRegion_delegatesToDefaultProvider() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_IDENTIFIER_MID); + + String expected = defaultProvider.getAlreadyEnteredText(rsta); + Assertions.assertEquals(expected, provider.getAlreadyEnteredText(rsta)); + Assertions.assertFalse(expected.isEmpty()); + } + + + @Test + void getCompletionsAt_noDefaultProvider_returnsNull() { + LanguageAwareCompletionProvider provider = new LanguageAwareCompletionProvider() { + // Deliberately never sets a default provider. + }; + RSyntaxTextArea rsta = createTextArea(); + Assertions.assertNull(provider.getCompletionsAt(rsta, new Point(0, 0))); + } + + + @Test + void getCompletionsAt_delegatesToDefaultProviderRegardlessOfTokenType() + throws BadLocationException { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + BasicCompletion completion = new BasicCompletion(defaultProvider, "fo"); + defaultProvider.addCompletion(completion); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + Point p = pointForOffset(rsta, OFFS_IDENTIFIER_MID); + + List results = provider.getCompletionsAt(rsta, p); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.size()); + Assertions.assertSame(completion, results.get(0)); + } + + + @Test + void getCompletionsImpl_nonRSyntaxTextArea_returnsEmptyList() { + LanguageAwareCompletionProvider provider = + new LanguageAwareCompletionProvider(new DefaultCompletionProvider()); + JTextArea plainTextArea = new JTextArea("foo"); + plainTextArea.setCaretPosition(3); + Assertions.assertTrue(provider.getCompletions(plainTextArea).isEmpty()); + } + + + @Test + void getCompletionsImpl_codeRegion_usesDefaultProvider() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.addCompletion(new BasicCompletion(defaultProvider, "foo")); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_IDENTIFIER_MID); + + List results = provider.getCompletions(rsta); + Assertions.assertEquals(1, results.size()); + Assertions.assertEquals("foo", results.get(0).getReplacementText()); + } + + + @Test + void getCompletionsImpl_stringRegion_usesStringProvider() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.addCompletion(new BasicCompletion(defaultProvider, "shouldNotMatch")); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + DefaultCompletionProvider stringProvider = + (DefaultCompletionProvider) provider.getStringCompletionProvider(); + stringProvider.addCompletion(new BasicCompletion(stringProvider, "hello")); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_STRING_MID); + + List results = provider.getCompletions(rsta); + Assertions.assertEquals(1, results.size()); + Assertions.assertEquals("hello", results.get(0).getReplacementText()); + } + + + @Test + void getCompletionsImpl_lineCommentRegion_usesCommentProvider() { + LanguageAwareCompletionProvider provider = + createFullyConfiguredProvider(new DefaultCompletionProvider()); + DefaultCompletionProvider commentProvider = + (DefaultCompletionProvider) provider.getCommentCompletionProvider(); + commentProvider.addCompletion(new BasicCompletion(commentProvider, "line")); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_LINE_COMMENT_MID); + + List results = provider.getCompletions(rsta); + Assertions.assertEquals(1, results.size()); + Assertions.assertEquals("line", results.get(0).getReplacementText()); + } + + + @Test + void getCompletionsImpl_endOfLineCommentToken_usesCommentProvider() { + // curToken is null here (end of line); getProviderFor() must fall back + // to doc.getLastTokenTypeOnLine() to figure out we're still in a comment. + LanguageAwareCompletionProvider provider = + createFullyConfiguredProvider(new DefaultCompletionProvider()); + DefaultCompletionProvider commentProvider = + (DefaultCompletionProvider) provider.getCommentCompletionProvider(); + // At the end of the line, the already-entered text is "comment" + // (the last word before the caret), not "line". + commentProvider.addCompletion(new BasicCompletion(commentProvider, "comment")); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_LINE_COMMENT_EOL); + + List results = provider.getCompletions(rsta); + Assertions.assertEquals(1, results.size()); + } + + + @Test + void getCompletionsImpl_multilineCommentRegion_usesCommentProvider() { + LanguageAwareCompletionProvider provider = + createFullyConfiguredProvider(new DefaultCompletionProvider()); + DefaultCompletionProvider commentProvider = + (DefaultCompletionProvider) provider.getCommentCompletionProvider(); + commentProvider.addCompletion(new BasicCompletion(commentProvider, "multi")); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_MULTILINE_COMMENT_MID); + + List results = provider.getCompletions(rsta); + Assertions.assertEquals(1, results.size()); + Assertions.assertEquals("multi", results.get(0).getReplacementText()); + } + + + @Test + void getCompletionsImpl_docCommentRegion_usesDocCommentProvider() { + LanguageAwareCompletionProvider provider = + createFullyConfiguredProvider(new DefaultCompletionProvider()); + DefaultCompletionProvider docProvider = + (DefaultCompletionProvider) provider.getDocCommentCompletionProvider(); + docProvider.addCompletion(new BasicCompletion(docProvider, "doc")); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_DOC_COMMENT_MID); + + List results = provider.getCompletions(rsta); + Assertions.assertEquals(1, results.size()); + Assertions.assertEquals("doc", results.get(0).getReplacementText()); + } + + + @Test + void getCompletionsImpl_endOfLineDocComment_usesDocCommentProvider() { + LanguageAwareCompletionProvider provider = + createFullyConfiguredProvider(new DefaultCompletionProvider()); + DefaultCompletionProvider docProvider = + (DefaultCompletionProvider) provider.getDocCommentCompletionProvider(); + docProvider.addCompletion(new BasicCompletion(docProvider, "doc")); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_DOC_COMMENT_EOL); + + List results = provider.getCompletions(rsta); + Assertions.assertEquals(1, results.size()); + } + + + @Test + void getCompletionsImpl_unsupportedTokenType_returnsEmptyList() { + // Caret is inside "class", a RESERVED_WORD - a token type + // getProviderFor() doesn't know how to map to any provider. + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.addCompletion(new BasicCompletion(defaultProvider, "class")); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_RESERVED_WORD_MID); + + Assertions.assertTrue(provider.getCompletions(rsta).isEmpty()); + } + + + @Test + void getCompletionsImpl_startOfToken_usesDefaultProvider() { + // dot == curToken.getOffset(): the "beginning of a new token" special case. + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.addCompletion(new BasicCompletion(defaultProvider, "class")); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_TOKEN_START); + + List results = provider.getCompletions(rsta); + Assertions.assertEquals(1, results.size()); + Assertions.assertEquals("class", results.get(0).getReplacementText()); + } + + + @Test + void getParameterizedCompletions_codeRegion_delegatesToDefaultProvider() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.setParameterizedCompletionParams('(', ", ", ')'); + FunctionCompletion fc = new FunctionCompletion(defaultProvider, "foo", "void"); + defaultProvider.addCompletion(fc); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = new RSyntaxTextArea(); + rsta.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + rsta.setText("foo("); + rsta.setCaretPosition(4); // right after '(' + + List results = provider.getParameterizedCompletions(rsta); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.size()); + Assertions.assertSame(fc, results.get(0)); + } + + + @Test + void getParameterizedCompletions_nonCodeRegion_returnsNull() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.setParameterizedCompletionParams('(', ", ", ')'); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_STRING_MID); + + Assertions.assertNull(provider.getParameterizedCompletions(rsta)); + } + + + @Test + void isAutoActivateOkay_codeRegion_delegatesToDefaultProvider() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.setAutoActivationRules(true, null); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_IDENTIFIER_MID); + + Assertions.assertTrue(provider.isAutoActivateOkay(rsta, OFFS_IDENTIFIER_MID)); + } + + @Test + void isAutoActivateOkay_unsupportedTokenType_returnsFalse() { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + defaultProvider.setAutoActivationRules(true, null); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + rsta.setCaretPosition(OFFS_RESERVED_WORD_MID); + + Assertions.assertFalse(provider.isAutoActivateOkay(rsta, OFFS_RESERVED_WORD_MID)); + } + + + @Test + void getToolTipText_matchingCompletion_returnsItsToolTipText() throws BadLocationException { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(defaultProvider, "fo", "int"); + defaultProvider.addCompletion(completion); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + Point p = pointForOffset(rsta, OFFS_IDENTIFIER_MID); + MouseEvent event = new MouseEvent(rsta, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), + 0, p.x, p.y, 0, false); + + Assertions.assertEquals(completion.getToolTipText(), provider.getToolTipText(rsta, event)); + } + + + @Test + void getToolTipText_noMatch_returnsNull() throws BadLocationException { + DefaultCompletionProvider defaultProvider = new DefaultCompletionProvider(); + LanguageAwareCompletionProvider provider = createFullyConfiguredProvider(defaultProvider); + + RSyntaxTextArea rsta = createTextArea(); + Point p = pointForOffset(rsta, OFFS_IDENTIFIER_MID); + MouseEvent event = new MouseEvent(rsta, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), + 0, p.x, p.y, 0, false); + + Assertions.assertNull(provider.getToolTipText(rsta, event)); + } + + + private static Point pointForOffset(RSyntaxTextArea textArea, int offset) + throws BadLocationException { + Rectangle2D r = textArea.modelToView2D(offset); + return new Point((int) r.getCenterX(), (int) r.getCenterY()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/MarkupTagCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/MarkupTagCompletionTest.java new file mode 100644 index 0000000..2f92615 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/MarkupTagCompletionTest.java @@ -0,0 +1,119 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import java.util.ArrayList; +import java.util.List; + +import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class MarkupTagCompletionTest { + + + @Test + void constructor() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion completion = new MarkupTagCompletion(provider, "div"); + Assertions.assertEquals("div", completion.getName()); + Assertions.assertEquals(0, completion.getAttributeCount()); + Assertions.assertNull(completion.getDescription()); + Assertions.assertNull(completion.getDefinedIn()); + } + + + @Test + void getReplacementText_returnsName() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion completion = new MarkupTagCompletion(provider, "div"); + Assertions.assertEquals("div", completion.getReplacementText()); + } + + + @Test + void getSetDescription() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion completion = new MarkupTagCompletion(provider, "div"); + completion.setDescription("a div element"); + Assertions.assertEquals("a div element", completion.getDescription()); + } + + + @Test + void getSetDefinedIn() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion completion = new MarkupTagCompletion(provider, "div"); + completion.setDefinedIn("HTML5"); + Assertions.assertEquals("HTML5", completion.getDefinedIn()); + } + + + @Test + void getSetAttributes() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion completion = new MarkupTagCompletion(provider, "div"); + + List attrs = new ArrayList<>(); + attrs.add(new Parameter("string", "id")); + attrs.add(new Parameter("string", "class")); + completion.setAttributes(attrs); + + Assertions.assertEquals(2, completion.getAttributeCount()); + Assertions.assertEquals("id", completion.getAttribute(0).getName()); + Assertions.assertEquals("class", completion.getAttribute(1).getName()); + Assertions.assertEquals(attrs.size(), completion.getAttributes().size()); + } + + + @Test + void setAttributes_isDeepCopy() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion completion = new MarkupTagCompletion(provider, "div"); + + List attrs = new ArrayList<>(); + attrs.add(new Parameter("string", "id")); + completion.setAttributes(attrs); + + attrs.add(new Parameter("string", "class")); + Assertions.assertEquals(1, completion.getAttributeCount()); + } + + + @Test + void getSummary_minimal() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion completion = new MarkupTagCompletion(provider, "div"); + String summary = completion.getSummary(); + Assertions.assertEquals("div", summary); + } + + + @Test + void getSummary_withDescriptionAttributesAndDefinedIn() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + MarkupTagCompletion completion = new MarkupTagCompletion(provider, "div"); + completion.setDescription("a div element"); + completion.setDefinedIn("HTML5"); + + List attrs = new ArrayList<>(); + Parameter attr = new Parameter("string", "id"); + attr.setDescription("the id attribute"); + attrs.add(attr); + completion.setAttributes(attrs); + + String summary = completion.getSummary(); + Assertions.assertTrue(summary.contains("a div element")); + Assertions.assertTrue(summary.contains("Attributes:")); + Assertions.assertTrue(summary.contains("the id attribute")); + Assertions.assertTrue(summary.contains("Defined in:")); + Assertions.assertTrue(summary.contains("HTML5")); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/OutlineHighlightPainterTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/OutlineHighlightPainterTest.java new file mode 100644 index 0000000..7d26bcd --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/OutlineHighlightPainterTest.java @@ -0,0 +1,89 @@ +/* + * 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.Graphics; +import java.awt.image.BufferedImage; +import javax.swing.JTextArea; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class OutlineHighlightPainterTest { + + + @Test + void constructor_nonNullColor() { + OutlineHighlightPainter painter = new OutlineHighlightPainter(Color.RED); + Assertions.assertEquals(Color.RED, painter.getColor()); + } + + + @Test + void constructor_nullColor_defaultsToBlack() { + OutlineHighlightPainter painter = new OutlineHighlightPainter(null); + Assertions.assertEquals(Color.BLACK, painter.getColor()); + } + + + @Test + void setColor_updatesColor() { + OutlineHighlightPainter painter = new OutlineHighlightPainter(Color.RED); + painter.setColor(Color.BLUE); + Assertions.assertEquals(Color.BLUE, painter.getColor()); + } + + + @Test + void setColor_null_throwsNullPointerException() { + OutlineHighlightPainter painter = new OutlineHighlightPainter(Color.RED); + Assertions.assertThrows(NullPointerException.class, () -> painter.setColor(null)); + } + + + @Test + void paintLayer_singleOffset_doesNotThrow() throws BadLocationException { + JTextArea textArea = new JTextArea("Hello world"); + textArea.setSize(200, 100); + OutlineHighlightPainter painter = new OutlineHighlightPainter(Color.RED); + + Highlighter highlighter = textArea.getHighlighter(); + Assertions.assertDoesNotThrow(() -> highlighter.addHighlight(2, 2, painter)); + + BufferedImage image = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + try { + Assertions.assertDoesNotThrow(() -> textArea.paint(g)); + } finally { + g.dispose(); + } + } + + + @Test + void paintLayer_range_doesNotThrow() throws BadLocationException { + JTextArea textArea = new JTextArea("Hello world"); + textArea.setSize(200, 100); + OutlineHighlightPainter painter = new OutlineHighlightPainter(Color.RED); + + Highlighter highlighter = textArea.getHighlighter(); + Assertions.assertDoesNotThrow(() -> highlighter.addHighlight(0, 5, painter)); + + BufferedImage image = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + try { + Assertions.assertDoesNotThrow(() -> textArea.paint(g)); + } finally { + g.dispose(); + } + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionContextTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionContextTest.java new file mode 100644 index 0000000..13af86f --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionContextTest.java @@ -0,0 +1,419 @@ +/* + * 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.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JFrame; +import javax.swing.JTextArea; +import javax.swing.KeyStroke; +import javax.swing.text.Highlighter.Highlight; + +import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter; +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 ParameterizedCompletionContextTest { + + private JFrame frame; + private JTextArea textArea; + private DefaultCompletionProvider provider; + private AutoCompletion ac; + + + @BeforeEach + void setUp() { + Assumptions.assumeFalse(GraphicsEnvironment.isHeadless()); + + provider = new DefaultCompletionProvider(); + provider.setParameterizedCompletionParams('(', ", ", ')'); + + ac = new AutoCompletion(provider); + ac.setParameterAssistanceEnabled(true); + + textArea = new JTextArea(); + ac.install(textArea); + + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + } + + + @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<>(); + for (String name : paramNames) { + params.add(new Parameter("int", name)); + } + fc.setParams(params); + return fc; + } + + + 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, "")); + } + + + private static Object getField(Object target, String name) throws ReflectiveOperationException { + Field field = ParameterizedCompletionContext.class.getDeclaredField(name); + field.setAccessible(true); + return field.get(target); + } + + + private static boolean isActive(ParameterizedCompletionContext ctx) throws ReflectiveOperationException { + return (boolean) getField(ctx, "active"); + } + + + private static Object getParamChoicesWindow(ParameterizedCompletionContext ctx) + throws ReflectiveOperationException { + return getField(ctx, "paramChoicesWindow"); + } + + + @Test + void activate_insertsParameterTextAndSelectsFirstParam() { + FunctionCompletion fc = newFunctionCompletion("a", "b"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + Assertions.assertEquals("(a, b)", textArea.getText()); + Assertions.assertEquals("a", textArea.getSelectedText()); + } + + + @Test + void activate_calledTwice_isIdempotent() { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + String textAfterFirstActivate = textArea.getText(); + + ctx.activate(); // Should be a no-op since already active + + Assertions.assertEquals(textAfterFirstActivate, textArea.getText()); + } + + + @Test + void getParameterHighlights_countsParamsPlusEndMarker() { + FunctionCompletion fc = newFunctionCompletion("a", "b"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + // 2 real parameter highlights, plus 1 zero-length "ending position" highlight. + Assertions.assertEquals(3, ctx.getParameterHighlights().size()); + } + + + @Test + void getArgumentText_offsetInParamHighlight_returnsCurrentParamText() { + FunctionCompletion fc = newFunctionCompletion("a", "b"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + List highlights = ctx.getParameterHighlights(); + // First two highlights are the real params ("a" and "b"); derive their offsets + // programmatically rather than hardcoding numbers tied to internal offset math. + Highlight aHighlight = highlights.get(0); + Highlight bHighlight = highlights.get(1); + + Assertions.assertEquals("a", ctx.getArgumentText(aHighlight.getStartOffset() + 1)); + Assertions.assertEquals("b", ctx.getArgumentText(bHighlight.getStartOffset() + 1)); + } + + + @Test + void getArgumentText_offsetNotInAnyHighlight_returnsNull() { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + Assertions.assertNull(ctx.getArgumentText(-5)); + } + + + @Test + void getArgumentText_noActiveHighlights_returnsNull() { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + // Never activated, so getParameterHighlights() is empty. + Assertions.assertNull(ctx.getArgumentText(0)); + } + + + @Test + void tabAndShiftTab_navigateBetweenParams() { + FunctionCompletion fc = newFunctionCompletion("a", "b"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + Assertions.assertEquals("a", textArea.getSelectedText()); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); + Assertions.assertEquals("b", textArea.getSelectedText()); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK)); + Assertions.assertEquals("a", textArea.getSelectedText()); + } + + + @Test + void deactivate_restoresKeyBindingsAndHidesWindows() throws ReflectiveOperationException { + KeyStroke tabKey = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0); + Object bindingBeforeActivate = textArea.getInputMap().get(tabKey); + + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + Assertions.assertNotEquals(bindingBeforeActivate, textArea.getInputMap().get(tabKey)); + Assertions.assertTrue(isActive(ctx)); + + ctx.deactivate(); + + Assertions.assertFalse(isActive(ctx)); + Assertions.assertEquals(bindingBeforeActivate, textArea.getInputMap().get(tabKey)); + } + + + @Test + void deactivate_whenNotActive_doesNothing() throws ReflectiveOperationException { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + Assertions.assertDoesNotThrow(ctx::deactivate); + Assertions.assertFalse(isActive(ctx)); + } + + + @Test + void caretMovedBeforeMinOffset_deactivatesAutomatically() throws ReflectiveOperationException { + FunctionCompletion fc = newFunctionCompletion("a", "b"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + Assertions.assertTrue(isActive(ctx)); + + // The minimum offset is right after the opening '(' (position 1); moving to the very + // start of the document is always below that, regardless of how minPos/maxPos track + // subsequent document edits. + textArea.setCaretPosition(0); + + Assertions.assertFalse(isActive(ctx)); + } + + + @Test + void focusLost_deactivates() throws ReflectiveOperationException { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + Assertions.assertTrue(isActive(ctx)); + + for (FocusListener l : textArea.getFocusListeners()) { + l.focusLost(new FocusEvent(textArea, FocusEvent.FOCUS_LOST)); + } + + Assertions.assertFalse(isActive(ctx)); + } + + + @Test + void escape_noChoicesWindowVisible_deactivates() throws ReflectiveOperationException { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + // Sanity check: no ParameterChoicesProvider is installed, so the choices window + // should not be showing. + Object choicesWindow = getParamChoicesWindow(ctx); + Assertions.assertFalse(((javax.swing.JWindow) choicesWindow).isVisible()); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); + + Assertions.assertFalse(isActive(ctx)); + } + + + @Test + void closingChar_typedWellBeforeEnd_insertsLiteralCharWithoutDeactivating() { + FunctionCompletion fc = newFunctionCompletion("a", "b"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); // Text is now "(a, b)"; selection is on "a" + + String textBefore = textArea.getText(); + fireKeyAction(textArea, KeyStroke.getKeyStroke(')')); + + // Typing ')' while still on an early parameter isn't "closing" the call - + // it should just insert a literal character (replacing the "a" selection). + Assertions.assertEquals(textBefore.length(), textArea.getText().length()); + Assertions.assertTrue(textArea.getText().contains(")")); + } + + + @Test + void closingChar_typedAtEndOfCompletion_deactivates() throws ReflectiveOperationException { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); // Text is now "(a)" + + textArea.setCaretPosition(textArea.getText().length()); + fireKeyAction(textArea, KeyStroke.getKeyStroke(')')); + + Assertions.assertFalse(isActive(ctx)); + } + + + @Test + void insertSelectedChoice_choicesWindowNotVisible_returnsFalse() { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + Assertions.assertFalse(ctx.insertSelectedChoice()); + } + + + @Test + void insertSelectedChoice_choiceAvailable_replacesParamAndMovesToNext() throws ReflectiveOperationException { + BasicCompletion choice1 = new BasicCompletion(provider, "CHOICE_ONE"); + BasicCompletion choice2 = new BasicCompletion(provider, "CHOICE_TWO"); + provider.setParameterChoicesProvider((tc, param) -> List.of(choice1, choice2)); + + FunctionCompletion fc = newFunctionCompletion("a", "b"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + Object choicesWindow = getParamChoicesWindow(ctx); + Assertions.assertTrue(((javax.swing.JWindow) choicesWindow).isVisible(), + "Sanity check: choices should be showing for the first parameter"); + + boolean inserted = ctx.insertSelectedChoice(); + + Assertions.assertTrue(inserted); + Assertions.assertTrue(textArea.getText().contains("CHOICE_ONE")); + // Selection should have moved on to the next parameter ("b"). + Assertions.assertEquals("b", textArea.getSelectedText()); + } + + + @Test + void upDownArrows_choicesWindowVisible_changeSelectedChoice() throws ReflectiveOperationException { + BasicCompletion choice1 = new BasicCompletion(provider, "CHOICE_ONE"); + BasicCompletion choice2 = new BasicCompletion(provider, "CHOICE_TWO"); + provider.setParameterChoicesProvider((tc, param) -> List.of(choice1, choice2)); + + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + ParameterizedCompletionChoicesWindow choicesWindow = + (ParameterizedCompletionChoicesWindow) getParamChoicesWindow(ctx); + Assertions.assertEquals("CHOICE_ONE", choicesWindow.getSelectedChoice()); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); + Assertions.assertEquals("CHOICE_TWO", choicesWindow.getSelectedChoice()); + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)); + Assertions.assertEquals("CHOICE_ONE", choicesWindow.getSelectedChoice()); + } + + + @Test + void updateUI_doesNotThrow() { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); + + Assertions.assertDoesNotThrow(ctx::updateUI); + } + + + @Test + void updateUI_neverActivated_doesNotThrow() { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + Assertions.assertDoesNotThrow(ctx::updateUI); + } + + + @Test + void enter_noChoiceSelected_movesToDefaultEndOffset() { + FunctionCompletion fc = newFunctionCompletion("a"); + ParameterizedCompletionContext ctx = new ParameterizedCompletionContext(frame, ac, fc); + + textArea.setCaretPosition(0); + ctx.activate(); // Text is now "(a)"; selection is on "a" + + fireKeyAction(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); + + Assertions.assertEquals(textArea.getText().length(), textArea.getCaretPosition()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionInsertionInfoTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionInsertionInfoTest.java new file mode 100644 index 0000000..df40282 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionInsertionInfoTest.java @@ -0,0 +1,132 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import javax.swing.text.Position; + +import org.fife.ui.autocomplete.ParameterizedCompletionInsertionInfo.ReplacementCopy; +import org.fife.ui.rtextarea.DocumentRange; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +class ParameterizedCompletionInsertionInfoTest { + + + private static Position fixedPosition(int offset) { + return () -> offset; + } + + + @Test + void constructor_hasNoSelectionByDefault() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + Assertions.assertFalse(info.hasSelection()); + Assertions.assertEquals(0, info.getSelectionStart()); + Assertions.assertEquals(0, info.getSelectionEnd()); + } + + + @Test + void constructor_hasNoReplacementsOrCopiesByDefault() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + Assertions.assertEquals(0, info.getReplacementCount()); + Assertions.assertEquals(0, info.getReplacementCopyCount()); + } + + + @Test + void getSetTextToInsert_roundTrips() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + Assertions.assertNull(info.getTextToInsert()); + info.setTextToInsert("foo()"); + Assertions.assertEquals("foo()", info.getTextToInsert()); + } + + + @Test + void setInitialSelection_setsStartAndEndAndHasSelection() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + info.setInitialSelection(3, 7); + Assertions.assertEquals(3, info.getSelectionStart()); + Assertions.assertEquals(7, info.getSelectionEnd()); + Assertions.assertTrue(info.hasSelection()); + } + + + @Test + void setInitialSelection_equalStartAndEnd_hasNoSelection() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + info.setInitialSelection(5, 5); + Assertions.assertFalse(info.hasSelection()); + } + + + @Test + void setCaretRange_getMinOffsetAndMaxOffset_roundTrip() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + Position maxOffs = fixedPosition(10); + info.setCaretRange(2, maxOffs); + Assertions.assertEquals(2, info.getMinOffset()); + Assertions.assertSame(maxOffs, info.getMaxOffset()); + } + + + @Test + void getDefaultEndOffs_noExplicitValueSet_fallsBackToMaxOffset() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + info.setCaretRange(0, fixedPosition(12)); + Assertions.assertEquals(12, info.getDefaultEndOffs()); + } + + + @Test + void setDefaultEndOffs_overridesMaxOffsetFallback() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + info.setCaretRange(0, fixedPosition(12)); + info.setDefaultEndOffs(20); + Assertions.assertEquals(20, info.getDefaultEndOffs()); + } + + + @Test + void addReplacementLocation_addsRetrievableRange() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + info.addReplacementLocation(1, 4); + info.addReplacementLocation(6, 9); + + Assertions.assertEquals(2, info.getReplacementCount()); + + DocumentRange first = info.getReplacementLocation(0); + Assertions.assertEquals(1, first.getStartOffset()); + Assertions.assertEquals(4, first.getEndOffset()); + + DocumentRange second = info.getReplacementLocation(1); + Assertions.assertEquals(6, second.getStartOffset()); + Assertions.assertEquals(9, second.getEndOffset()); + } + + + @Test + void addReplacementCopy_addsRetrievableCopy() { + ParameterizedCompletionInsertionInfo info = new ParameterizedCompletionInsertionInfo(); + info.addReplacementCopy("a", 1, 2); + info.addReplacementCopy("b", 3, 4); + + Assertions.assertEquals(2, info.getReplacementCopyCount()); + + ReplacementCopy first = info.getReplacementCopy(0); + Assertions.assertEquals("a", first.getId()); + Assertions.assertEquals(1, first.getStart()); + Assertions.assertEquals(2, first.getEnd()); + + ReplacementCopy second = info.getReplacementCopy(1); + Assertions.assertEquals("b", second.getId()); + Assertions.assertEquals(3, second.getStart()); + Assertions.assertEquals(4, second.getEnd()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionTest.java new file mode 100644 index 0000000..fe8bb5d --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ParameterizedCompletionTest.java @@ -0,0 +1,99 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +/** + * Unit tests for {@link ParameterizedCompletion}. The interface itself has + * no behavior of its own (its methods are exercised via implementations such + * as {@code FunctionCompletion} and {@code TemplateCompletion}, which have + * their own test classes), so these tests focus on the nested + * {@link Parameter} class. + */ +@ExtendWith(SwingRunnerExtension.class) +class ParameterizedCompletionTest { + + + @Test + void constructor_twoArg_defaultsIsEndParamToFalse() { + Parameter param = new Parameter("int", "a"); + Assertions.assertEquals("int", param.getType()); + Assertions.assertEquals("a", param.getName()); + Assertions.assertFalse(param.isEndParam()); + } + + + @Test + void constructor_threeArg_setsIsEndParam() { + Parameter param = new Parameter("int", "a", true); + Assertions.assertTrue(param.isEndParam()); + } + + + @Test + void constructor_nullType_getTypeReturnsNull() { + Parameter param = new Parameter(null, "a"); + Assertions.assertNull(param.getType()); + Assertions.assertNull(param.getTypeObject()); + } + + + @Test + void getType_nonStringTypeObject_usesToString() { + Object type = new Object() { + @Override + public String toString() { + return "CustomType"; + } + }; + Parameter param = new Parameter(type, "a"); + Assertions.assertEquals("CustomType", param.getType()); + Assertions.assertSame(type, param.getTypeObject()); + } + + + @Test + void getSetDescription_roundTrips() { + Parameter param = new Parameter("int", "a"); + Assertions.assertNull(param.getDescription()); + param.setDescription("the a param"); + Assertions.assertEquals("the a param", param.getDescription()); + } + + + @Test + void toString_typeAndName_separatedBySpace() { + Parameter param = new Parameter("int", "a"); + Assertions.assertEquals("int a", param.toString()); + } + + + @Test + void toString_typeOnly_noTrailingSpace() { + Parameter param = new Parameter("int", null); + Assertions.assertEquals("int", param.toString()); + } + + + @Test + void toString_nameOnly_noLeadingSpace() { + Parameter param = new Parameter(null, "a"); + Assertions.assertEquals("a", param.toString()); + } + + + @Test + void toString_neitherTypeNorName_empty() { + Parameter param = new Parameter(null, null); + Assertions.assertEquals("", param.toString()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/RoundRobinAutoCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/RoundRobinAutoCompletionTest.java new file mode 100644 index 0000000..b26d1bf --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/RoundRobinAutoCompletionTest.java @@ -0,0 +1,217 @@ +/* + * 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.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import javax.swing.Action; +import javax.swing.JFrame; +import javax.swing.JTextArea; +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 RoundRobinAutoCompletionTest { + + private JFrame frame; + + + @BeforeEach + void setUp() { + Assumptions.assumeFalse(GraphicsEnvironment.isHeadless()); + } + + + @AfterEach + void tearDown() { + if (frame != null) { + frame.dispose(); + frame = null; + } + } + + + private void realize(JTextArea textArea) { + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + } + + + @Test + void constructor_addsProviderToCycle() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider); + Assertions.assertSame(provider, ac.getCompletionProvider()); + + // advancing with a single provider in the cycle should return to itself + Assertions.assertTrue(ac.advanceProvider()); + Assertions.assertSame(provider, ac.getCompletionProvider()); + } + + + @Test + void constructor_setsRoundRobinRequiredFlags() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider); + + Assertions.assertFalse(ac.isHideOnCompletionProviderChange()); + Assertions.assertFalse(ac.isHideOnNoText()); + Assertions.assertFalse(ac.getAutoCompleteSingleChoices()); + } + + + @Test + void addCompletionProvider_addsToCycleWithoutChangingCurrentProvider() { + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); + DefaultCompletionProvider provider2 = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider1); + + ac.addCompletionProvider(provider2); + + Assertions.assertSame(provider1, ac.getCompletionProvider()); + } + + + @Test + void advanceProvider_cyclesThroughProvidersInOrder() { + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); + DefaultCompletionProvider provider2 = new DefaultCompletionProvider(); + DefaultCompletionProvider provider3 = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider1); + ac.addCompletionProvider(provider2); + ac.addCompletionProvider(provider3); + + Assertions.assertFalse(ac.advanceProvider()); + Assertions.assertSame(provider2, ac.getCompletionProvider()); + + Assertions.assertFalse(ac.advanceProvider()); + Assertions.assertSame(provider3, ac.getCompletionProvider()); + + // Wraps back around to the first (default) provider + Assertions.assertTrue(ac.advanceProvider()); + Assertions.assertSame(provider1, ac.getCompletionProvider()); + } + + + @Test + void resetProvider_currentIsNotDefault_switchesBackToDefault() { + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); + DefaultCompletionProvider provider2 = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider1); + ac.addCompletionProvider(provider2); + + ac.advanceProvider(); + Assertions.assertSame(provider2, ac.getCompletionProvider()); + + ac.resetProvider(); + Assertions.assertSame(provider1, ac.getCompletionProvider()); + } + + + @Test + void resetProvider_currentIsAlreadyDefault_doesNothing() { + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider1); + + ac.resetProvider(); + + Assertions.assertSame(provider1, ac.getCompletionProvider()); + } + + + @Test + void createAutoCompleteAction_returnsNonNullAction() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider); + Assertions.assertNotNull(ac.createAutoCompleteAction()); + } + + + @Test + void cycleAction_popupNotVisible_resetsToDefaultProvider() { + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); + DefaultCompletionProvider provider2 = new DefaultCompletionProvider(); + provider1.addCompletion(new BasicCompletion(provider1, "fromProvider1")); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider1); + ac.addCompletionProvider(provider2); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + // Manually move off of the default provider, as if a prior cycle left it there. + ac.advanceProvider(); + Assertions.assertSame(provider2, ac.getCompletionProvider()); + + Action action = ac.createAutoCompleteAction(); + action.actionPerformed(new ActionEvent(textArea, ActionEvent.ACTION_PERFORMED, "")); + + Assertions.assertSame(provider1, ac.getCompletionProvider()); + } + + + @Test + void cycleAction_currentProviderHasNoCompletions_advancesToNextWithCompletions() { + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); // no completions added + DefaultCompletionProvider provider2 = new DefaultCompletionProvider(); + provider2.addCompletion(new BasicCompletion(provider2, "fromProvider2")); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider1); + ac.addCompletionProvider(provider2); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + realize(textArea); + + Action action = ac.createAutoCompleteAction(); + action.actionPerformed(new ActionEvent(textArea, ActionEvent.ACTION_PERFORMED, "")); + + Assertions.assertSame(provider2, ac.getCompletionProvider()); + } + + + @Test + void cycleAction_noProviderHasCompletions_endsOnLastProviderInCycle() { + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); + DefaultCompletionProvider provider2 = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider1); + ac.addCompletionProvider(provider2); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + Action action = ac.createAutoCompleteAction(); + Assertions.assertDoesNotThrow(() -> + action.actionPerformed(new ActionEvent(textArea, ActionEvent.ACTION_PERFORMED, ""))); + + Assertions.assertSame(provider2, ac.getCompletionProvider()); + } + + + @Test + void cycleAction_autoCompleteDisabled_doesNotAdvanceOrReset() { + DefaultCompletionProvider provider1 = new DefaultCompletionProvider(); + DefaultCompletionProvider provider2 = new DefaultCompletionProvider(); + RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(provider1); + ac.addCompletionProvider(provider2); + ac.setAutoCompleteEnabled(false); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + Action action = ac.createAutoCompleteAction(); + action.actionPerformed(new ActionEvent(textArea, ActionEvent.ACTION_PERFORMED, "")); + + Assertions.assertSame(provider1, ac.getCompletionProvider()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/ShorthandCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ShorthandCompletionTest.java new file mode 100644 index 0000000..6ea2d68 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/ShorthandCompletionTest.java @@ -0,0 +1,87 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class ShorthandCompletionTest { + + + @Test + void constructor_twoArg() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + ShorthandCompletion completion = new ShorthandCompletion(provider, + "sysout", "System.out.println("); + Assertions.assertEquals("sysout", completion.getInputText()); + Assertions.assertEquals("System.out.println(", completion.getReplacementText()); + Assertions.assertNull(completion.getShortDescription()); + } + + + @Test + void constructor_withShortDesc() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + ShorthandCompletion completion = new ShorthandCompletion(provider, + "sysout", "System.out.println(", "short desc"); + Assertions.assertEquals("sysout", completion.getInputText()); + Assertions.assertEquals("System.out.println(", completion.getReplacementText()); + Assertions.assertEquals("short desc", completion.getShortDescription()); + } + + + @Test + void constructor_withShortDescAndSummary() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + ShorthandCompletion completion = new ShorthandCompletion(provider, + "sysout", "System.out.println(", "short desc", "summary"); + Assertions.assertEquals("sysout", completion.getInputText()); + Assertions.assertEquals("System.out.println(", completion.getReplacementText()); + Assertions.assertEquals("short desc", completion.getShortDescription()); + Assertions.assertEquals("summary", completion.getSummary()); + } + + + @Test + void getSummary_returnsExplicitSummaryIfSet() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + ShorthandCompletion completion = new ShorthandCompletion(provider, + "sysout", "System.out.println(", "short desc", "explicit summary"); + Assertions.assertEquals("explicit summary", completion.getSummary()); + } + + + @Test + void getSummary_generatedFromReplacementTextWhenNoSummarySet() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + ShorthandCompletion completion = new ShorthandCompletion(provider, + "sysout", "System.out.println("); + String expected = "System.out.println("; + Assertions.assertEquals(expected, completion.getSummary()); + } + + + @Test + void getSummaryBody() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + ShorthandCompletion completion = new ShorthandCompletion(provider, + "sysout", "System.out.println("); + Assertions.assertEquals("System.out.println(", completion.getSummaryBody()); + } + + + @Test + void toString_usesInputTextNotReplacementText() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + ShorthandCompletion completion = new ShorthandCompletion(provider, + "sysout", "System.out.println("); + Assertions.assertEquals("sysout", completion.toString()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/SizeGripTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/SizeGripTest.java new file mode 100644 index 0000000..49e0bbf --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/SizeGripTest.java @@ -0,0 +1,178 @@ +/* + * 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.ComponentOrientation; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.lang.reflect.Field; +import org.fife.ui.rsyntaxtextarea.OS; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + + +@ExtendWith(SwingRunnerExtension.class) +class SizeGripTest { + + + private static Image getOsxSizeGrip(SizeGrip grip) throws ReflectiveOperationException { + Field field = SizeGrip.class.getDeclaredField("osxSizeGrip"); + field.setAccessible(true); + return (Image) field.get(grip); + } + + + /** + * Creates a {@code SizeGrip} while {@link OS#get()} is mocked to return + * the given value. This is necessary since {@code JPanel}'s constructor + * calls {@code updateUI()} itself, so simply mocking around a later, + * explicit call to {@code updateUI()} isn't enough to deterministically + * test either branch - the constructor's implicit call would already run + * against the real, unmocked OS. + */ + private static SizeGrip newSizeGrip(OS os) { + try (MockedStatic mockedOs = Mockito.mockStatic(OS.class)) { + mockedOs.when(OS::get).thenReturn(os); + return new SizeGrip(); + } + } + + + private static void updateUiWithOs(SizeGrip grip, OS os) { + try (MockedStatic mockedOs = Mockito.mockStatic(OS.class)) { + mockedOs.when(OS::get).thenReturn(os); + grip.updateUI(); + } + } + + + @Test + void constructor_setsPreferredSize() { + SizeGrip grip = new SizeGrip(); + Assertions.assertEquals(new Dimension(16, 16), grip.getPreferredSize()); + } + + + @Test + void applyComponentOrientation_ltr_setsNwResizeCursor() { + SizeGrip grip = new SizeGrip(); + grip.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); + Assertions.assertEquals(Cursor.NW_RESIZE_CURSOR, grip.getCursor().getType()); + } + + + @Test + void applyComponentOrientation_rtl_setsNeResizeCursor() { + SizeGrip grip = new SizeGrip(); + grip.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); + Assertions.assertEquals(Cursor.NE_RESIZE_CURSOR, grip.getCursor().getType()); + } + + + @Test + void possiblyFixCursor_onlyChangesCursorWhenNeeded() { + SizeGrip grip = new SizeGrip(); + grip.setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR)); + grip.possiblyFixCursor(true); + Assertions.assertEquals(Cursor.NW_RESIZE_CURSOR, grip.getCursor().getType()); + grip.possiblyFixCursor(false); + Assertions.assertEquals(Cursor.NE_RESIZE_CURSOR, grip.getCursor().getType()); + } + + + @Test + void updateUI_doesNotThrow() { + SizeGrip grip = new SizeGrip(); + Assertions.assertDoesNotThrow(grip::updateUI); + } + + + @Test + void updateUI_macOs_loadsOsxSizeGripImage() throws ReflectiveOperationException { + // Start on a non-mac OS so the field is deterministically null before + // the assertion, regardless of the OS actually running this test. + SizeGrip grip = newSizeGrip(OS.LINUX); + Assertions.assertNull(getOsxSizeGrip(grip)); + + updateUiWithOs(grip, OS.MAC_OS_X); + Assertions.assertNotNull(getOsxSizeGrip(grip)); + } + + + @Test + void updateUI_nonMacOs_leavesOsxSizeGripImageNull() throws ReflectiveOperationException { + SizeGrip grip = newSizeGrip(OS.LINUX); + Assertions.assertNull(getOsxSizeGrip(grip)); + + updateUiWithOs(grip, OS.WINDOWS); + Assertions.assertNull(getOsxSizeGrip(grip)); + } + + + @Test + void updateUI_switchingFromMacToNonMac_clearsOsxSizeGripImage() + throws ReflectiveOperationException { + SizeGrip grip = newSizeGrip(OS.MAC_OS_X); + Assertions.assertNotNull(getOsxSizeGrip(grip)); + + updateUiWithOs(grip, OS.WINDOWS); + Assertions.assertNull(getOsxSizeGrip(grip)); + } + + + @Test + void paintComponent_nonMacOs_ltr_doesNotThrow() throws ReflectiveOperationException { + SizeGrip grip = newSizeGrip(OS.LINUX); + Assertions.assertNull(getOsxSizeGrip(grip)); // sanity check: fillRect path + grip.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); + grip.setSize(16, 16); + BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + try { + Assertions.assertDoesNotThrow(() -> grip.paint(g)); + } finally { + g.dispose(); + } + } + + + @Test + void paintComponent_nonMacOs_rtl_doesNotThrow() throws ReflectiveOperationException { + SizeGrip grip = newSizeGrip(OS.LINUX); + Assertions.assertNull(getOsxSizeGrip(grip)); // sanity check: fillRect path + grip.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); + grip.setSize(16, 16); + BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + try { + Assertions.assertDoesNotThrow(() -> grip.paint(g)); + } finally { + g.dispose(); + } + } + + + @Test + void paintComponent_macOs_doesNotThrow() throws ReflectiveOperationException { + SizeGrip grip = newSizeGrip(OS.MAC_OS_X); + Assertions.assertNotNull(getOsxSizeGrip(grip)); // sanity check: drawImage path + grip.setSize(16, 16); + BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + try { + Assertions.assertDoesNotThrow(() -> grip.paint(g)); + } finally { + g.dispose(); + } + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/TemplateCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/TemplateCompletionTest.java new file mode 100644 index 0000000..e20e059 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/TemplateCompletionTest.java @@ -0,0 +1,148 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import javax.swing.JTextArea; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class TemplateCompletionTest { + + + @Test + void constructor_fourArg() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "for", "for-loop", "for (int ${i} = 0; ${i} < 10; ${i}++) {\n\t${cursor}\n}"); + Assertions.assertEquals("for", completion.getInputText()); + Assertions.assertEquals("for-loop", completion.getDefinitionString()); + Assertions.assertNull(completion.getShortDescription()); + Assertions.assertNull(completion.getSummary()); + } + + + @Test + void constructor_sixArg() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "for", "for-loop", "for (${i}) {\n\t${cursor}\n}", "short desc", "summary"); + Assertions.assertEquals("for", completion.getInputText()); + Assertions.assertEquals("for-loop", completion.getDefinitionString()); + Assertions.assertEquals("short desc", completion.getShortDescription()); + Assertions.assertEquals("summary", completion.getSummary()); + } + + + @Test + void getReplacementText_alwaysNull() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "for", "for-loop", "plain text template"); + Assertions.assertNull(completion.getReplacementText()); + } + + + @Test + void getShowParameterToolTip_alwaysFalse() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "for", "for-loop", "plain text template"); + Assertions.assertFalse(completion.getShowParameterToolTip()); + } + + + @Test + void getParamCount_noParams() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "foo", "foo desc", "plain text, no params"); + Assertions.assertEquals(0, completion.getParamCount()); + } + + + @Test + void getParamCount_withParams_cursorNotCountedAsParam() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "for", "for-loop", "for (int ${i} = 0; ${i} < ${max}; ${i}++) {\n\t${cursor}\n}"); + // "i" and "max" are unique params; repeats of "i" become ParamCopy pieces. + Assertions.assertEquals(2, completion.getParamCount()); + Assertions.assertEquals("i", completion.getParam(0).getName()); + Assertions.assertEquals("max", completion.getParam(1).getName()); + } + + + @Test + void setShortDescription() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "for", "for-loop", "plain text template"); + completion.setShortDescription("new short desc"); + Assertions.assertEquals("new short desc", completion.getShortDescription()); + } + + + @Test + void toString_returnsDefinitionString() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "for", "for-loop", "plain text template"); + Assertions.assertEquals("for-loop", completion.toString()); + } + + + @Test + void getInsertionInfo_plainText() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "foo", "foo desc", "System.out.println();"); + + JTextArea textArea = new JTextArea(); + textArea.setCaretPosition(0); + + ParameterizedCompletionInsertionInfo info = + completion.getInsertionInfo(textArea, false); + + Assertions.assertEquals("System.out.println();", info.getTextToInsert()); + } + + + @Test + void getInsertionInfo_dollarDollarEscapesToSingleDollar() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "foo", "foo desc", "cost is $$5"); + + JTextArea textArea = new JTextArea(); + textArea.setCaretPosition(0); + + ParameterizedCompletionInsertionInfo info = + completion.getInsertionInfo(textArea, false); + + Assertions.assertEquals("cost is $5", info.getTextToInsert()); + } + + + @Test + void getInsertionInfo_withParam() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + TemplateCompletion completion = new TemplateCompletion(provider, + "foo", "foo desc", "int ${name} = 0;"); + + JTextArea textArea = new JTextArea(); + textArea.setCaretPosition(0); + + ParameterizedCompletionInsertionInfo info = + completion.getInsertionInfo(textArea, false); + + Assertions.assertEquals("int name = 0;", info.getTextToInsert()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/TemplatePieceTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/TemplatePieceTest.java new file mode 100644 index 0000000..2756462 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/TemplatePieceTest.java @@ -0,0 +1,56 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +class TemplatePieceTest { + + + @Test + void text_getText_returnsConstructorValue() { + TemplatePiece.Text piece = new TemplatePiece.Text("foo"); + Assertions.assertEquals("foo", piece.getText()); + } + + + @Test + void text_toString_includesText() { + TemplatePiece.Text piece = new TemplatePiece.Text("foo"); + Assertions.assertEquals("[TemplatePiece.Text: text=foo]", piece.toString()); + } + + + @Test + void param_getText_returnsConstructorValue() { + TemplatePiece.Param piece = new TemplatePiece.Param("bar"); + Assertions.assertEquals("bar", piece.getText()); + } + + + @Test + void param_toString_includesText() { + TemplatePiece.Param piece = new TemplatePiece.Param("bar"); + Assertions.assertEquals("[TemplatePiece.Param: param=bar]", piece.toString()); + } + + + @Test + void paramCopy_getText_returnsConstructorValue() { + TemplatePiece.ParamCopy piece = new TemplatePiece.ParamCopy("baz"); + Assertions.assertEquals("baz", piece.getText()); + } + + + @Test + void paramCopy_toString_includesText() { + TemplatePiece.ParamCopy piece = new TemplatePiece.ParamCopy("baz"); + Assertions.assertEquals("[TemplatePiece.ParamCopy: param=baz]", piece.toString()); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/TipUtilTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/TipUtilTest.java new file mode 100644 index 0000000..217e34a --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/TipUtilTest.java @@ -0,0 +1,420 @@ +/* + * 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.Font; +import java.awt.SystemColor; +import javax.swing.BorderFactory; +import javax.swing.JEditorPane; +import javax.swing.JToolTip; +import javax.swing.LookAndFeel; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import javax.swing.plaf.ColorUIResource; +import javax.swing.plaf.basic.BasicEditorPaneUI; +import javax.swing.text.AttributeSet; +import javax.swing.text.html.CSS; +import javax.swing.text.html.HTMLDocument; + +import org.fife.ui.rsyntaxtextarea.HtmlUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + + +/** + * Tests {@link TipUtil}. Most of {@code TipUtil}'s behavior branches on the + * current {@code LookAndFeel} and various {@code UIManager} defaults, so + * these tests mock {@code UIManager} (with real methods as the default + * answer, so only explicitly stubbed keys are overridden) to exercise each + * branch deterministically, independent of whatever Look and Feel happens to + * be installed on the machine running the tests. + */ +@ExtendWith(SwingRunnerExtension.class) +class TipUtilTest { + + + private static LookAndFeel mockLookAndFeel(String name) { + LookAndFeel laf = Mockito.mock(LookAndFeel.class); + Mockito.when(laf.getName()).thenReturn(name); + return laf; + } + + + private static JEditorPane createHtmlEditorPane() { + JEditorPane pane = new JEditorPane(); + pane.setContentType("text/html"); + pane.setText("Hi"); + return pane; + } + + + // ---- getToolTipBackground() ---- + + + @Test + void getToolTipBackground_nonNimbus_convertsColorUIResourceToPlainColor() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getColor("ToolTip.background")) + .thenReturn(new ColorUIResource(Color.RED)); + + Color result = TipUtil.getToolTipBackground(); + Assertions.assertEquals(Color.RED.getRGB(), result.getRGB()); + Assertions.assertFalse(result instanceof ColorUIResource); + } + } + + + @Test + void getToolTipBackground_nimbus_usesInfoColorRegardlessOfToolTipBackground() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Nimbus"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getColor("ToolTip.background")).thenReturn(Color.RED); + ui.when(() -> UIManager.getColor("info")).thenReturn(new ColorUIResource(Color.GREEN)); + + Color result = TipUtil.getToolTipBackground(); + Assertions.assertEquals(Color.GREEN.getRGB(), result.getRGB()); + } + } + + + @Test + void getToolTipBackground_toolTipAndInfoBothNull_fallsBackToSystemColorInfo() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getColor("ToolTip.background")).thenReturn(null); + ui.when(() -> UIManager.getColor("info")).thenReturn(null); + + Assertions.assertEquals(SystemColor.info, TipUtil.getToolTipBackground()); + } + } + + + // ---- getToolTipBorder() ---- + + + @Test + void getToolTipBorder_nonNimbus_returnsToolTipBorder() { + Border expected = BorderFactory.createEmptyBorder(1, 1, 1, 1); + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getBorder("ToolTip.border")).thenReturn(expected); + + Assertions.assertSame(expected, TipUtil.getToolTipBorder()); + } + } + + + @Test + void getToolTipBorder_toolTipBorderNull_usesNimbusBorder() { + Border nimbusBorder = BorderFactory.createEmptyBorder(2, 2, 2, 2); + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getBorder("ToolTip.border")).thenReturn(null); + ui.when(() -> UIManager.getBorder("nimbusBorder")).thenReturn(nimbusBorder); + + Assertions.assertSame(nimbusBorder, TipUtil.getToolTipBorder()); + } + } + + + @Test + void getToolTipBorder_nimbus_usesNimbusBorderRegardlessOfToolTipBorder() { + Border toolTipBorder = BorderFactory.createEmptyBorder(1, 1, 1, 1); + Border nimbusBorder = BorderFactory.createEmptyBorder(2, 2, 2, 2); + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Nimbus"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getBorder("ToolTip.border")).thenReturn(toolTipBorder); + ui.when(() -> UIManager.getBorder("nimbusBorder")).thenReturn(nimbusBorder); + + Assertions.assertSame(nimbusBorder, TipUtil.getToolTipBorder()); + } + } + + + @Test + void getToolTipBorder_bothNull_fallsBackToLineBorder() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getBorder("ToolTip.border")).thenReturn(null); + ui.when(() -> UIManager.getBorder("nimbusBorder")).thenReturn(null); + + Border result = TipUtil.getToolTipBorder(); + Assertions.assertInstanceOf(LineBorder.class, result); + Assertions.assertEquals(SystemColor.controlDkShadow, ((LineBorder) result).getLineColor()); + } + } + + + // ---- getToolTipHyperlinkForeground() ---- + + + @Test + void getToolTipHyperlinkForeground_nonNimbus_darkForeground_returnsBlue() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getColor("ToolTip.foreground")).thenReturn(Color.BLACK); + + Assertions.assertEquals(Color.BLUE, TipUtil.getToolTipHyperlinkForeground()); + } + } + + + @Test + void getToolTipHyperlinkForeground_nonNimbus_lightForeground_returnsLightHyperlinkColor() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getColor("ToolTip.foreground")).thenReturn(Color.WHITE); + + Assertions.assertEquals(Util.LIGHT_HYPERLINK_FG, TipUtil.getToolTipHyperlinkForeground()); + } + } + + + @Test + void getToolTipHyperlinkForeground_nullToolTipForeground_fallsBackToJToolTipDefault() { + // The method's fallback (new JToolTip().getForeground()) itself queries + // "ToolTip.foreground" internally during UI installation, so the mocked + // key must return something non-null on that second lookup too - we + // don't care what, since we only care that the fallback path is taken + // and its result correctly feeds into isLightForeground(). + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getColor("ToolTip.foreground")).thenReturn(null, Color.BLACK); + + Assertions.assertEquals(Color.BLUE, TipUtil.getToolTipHyperlinkForeground()); + } + } + + + @Test + void getToolTipHyperlinkForeground_nimbus_usesJToolTipDefaultRegardlessOfToolTipForeground() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Nimbus"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + // First call (the method's own) returns non-null RED, but Nimbus + // forces the JToolTip fallback anyway; that fallback's own internal + // lookup (second call) resolves to WHITE. + ui.when(() -> UIManager.getColor("ToolTip.foreground")).thenReturn(Color.RED, Color.WHITE); + + Assertions.assertEquals(Util.LIGHT_HYPERLINK_FG, TipUtil.getToolTipHyperlinkForeground()); + } + } + + + // ---- tweakTipEditorPane() ---- + + + @Test + void tweakTipEditorPane_makesPaneNonEditableWithPaddingAndVisibleSelection() { + JEditorPane pane = createHtmlEditorPane(); + + TipUtil.tweakTipEditorPane(pane); + + Assertions.assertFalse(pane.isEditable()); + Assertions.assertInstanceOf(EmptyBorder.class, pane.getBorder()); + Assertions.assertEquals(new java.awt.Insets(5, 5, 5, 5), + ((EmptyBorder) pane.getBorder()).getBorderInsets()); + Assertions.assertTrue(pane.getCaret().isSelectionVisible()); + } + + + @Test + void tweakTipEditorPane_foreground_usesToolTipForegroundWhenNonNull() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getColor("ToolTip.foreground")).thenReturn(Color.RED); + + JEditorPane pane = createHtmlEditorPane(); + TipUtil.tweakTipEditorPane(pane); + + Assertions.assertEquals(Color.RED, pane.getForeground()); + } + } + + + @Test + void tweakTipEditorPane_foreground_fallsBackToLabelForegroundWhenToolTipForegroundNull() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + // 1st call is the pane's own foreground lookup (null -> triggers + // the Label.foreground fallback); the 2nd is made internally by the + // getToolTipHyperlinkForeground() call later in the same method, and + // just needs to be non-null to avoid an unrelated NPE in JToolTip. + ui.when(() -> UIManager.getColor("ToolTip.foreground")).thenReturn(null, Color.BLACK); + ui.when(() -> UIManager.getColor("Label.foreground")).thenReturn(Color.GREEN); + + JEditorPane pane = createHtmlEditorPane(); + TipUtil.tweakTipEditorPane(pane); + + Assertions.assertEquals(Color.GREEN, pane.getForeground()); + } + } + + + @Test + void tweakTipEditorPane_foreground_fallsBackToSystemColorTextTextWhenBothNull() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getColor("ToolTip.foreground")).thenReturn(null, Color.BLACK); + ui.when(() -> UIManager.getColor("Label.foreground")).thenReturn(null); + + JEditorPane pane = createHtmlEditorPane(); + TipUtil.tweakTipEditorPane(pane); + + Assertions.assertEquals(SystemColor.textText, pane.getForeground()); + } + } + + + @Test + void tweakTipEditorPane_background_usesToolTipBackground() { + Color expectedBg = TipUtil.getToolTipBackground(); + + JEditorPane pane = createHtmlEditorPane(); + TipUtil.tweakTipEditorPane(pane); + + Assertions.assertEquals(expectedBg, pane.getBackground()); + } + + + @Test + void tweakTipEditorPane_font_usesLabelFontWhenNonNull() { + // tweakTipEditorPane() never calls textArea.setFont(); it only adds a + // "body" CSS rule to the HTMLDocument's stylesheet reflecting the font, + // so that's what must be inspected here, not pane.getFont(). + Font customFont = new Font("Monospaced", Font.BOLD, 20); + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getFont("Label.font")).thenReturn(customFont); + + JEditorPane pane = createHtmlEditorPane(); + TipUtil.tweakTipEditorPane(pane); + + AttributeSet bodyRule = ((HTMLDocument) pane.getDocument()).getStyleSheet().getRule("body"); + Assertions.assertEquals("Monospaced", bodyRule.getAttribute(CSS.Attribute.FONT_FAMILY).toString()); + Assertions.assertEquals("20pt", bodyRule.getAttribute(CSS.Attribute.FONT_SIZE).toString()); + } + } + + + @Test + void tweakTipEditorPane_font_fallsBackToSansSerif12WhenLabelFontNull() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Metal"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + ui.when(() -> UIManager.getFont("Label.font")).thenReturn(null); + + JEditorPane pane = createHtmlEditorPane(); + TipUtil.tweakTipEditorPane(pane); + + AttributeSet bodyRule = ((HTMLDocument) pane.getDocument()).getStyleSheet().getRule("body"); + Assertions.assertEquals("SansSerif", bodyRule.getAttribute(CSS.Attribute.FONT_FAMILY).toString()); + Assertions.assertEquals("12pt", bodyRule.getAttribute(CSS.Attribute.FONT_SIZE).toString()); + } + } + + + @Test + void tweakTipEditorPane_nimbus_swapsUiAndPreservesSelectionColors() { + try (MockedStatic ui = + Mockito.mockStatic(UIManager.class, Mockito.CALLS_REAL_METHODS)) { + LookAndFeel laf = mockLookAndFeel("Nimbus"); + ui.when(UIManager::getLookAndFeel).thenReturn(laf); + + JEditorPane pane = createHtmlEditorPane(); + pane.setSelectionColor(Color.CYAN); + pane.setSelectedTextColor(Color.MAGENTA); + + TipUtil.tweakTipEditorPane(pane); + + Assertions.assertInstanceOf(BasicEditorPaneUI.class, pane.getUI()); + Assertions.assertEquals(Color.CYAN, pane.getSelectionColor()); + Assertions.assertEquals(Color.MAGENTA, pane.getSelectedTextColor()); + } + } + + + @Test + void tweakTipEditorPane_addsBodyAndLinkStyleRulesReflectingComputedColorsAndFont() { + JEditorPane pane = createHtmlEditorPane(); + + TipUtil.tweakTipEditorPane(pane); + + HTMLDocument doc = (HTMLDocument) pane.getDocument(); + + AttributeSet bodyRule = doc.getStyleSheet().getRule("body"); + Assertions.assertNotNull(bodyRule); + Assertions.assertEquals(HtmlUtil.getHexString(pane.getForeground()), + bodyRule.getAttribute(CSS.Attribute.COLOR).toString()); + + // tweakTipEditorPane() never calls textArea.setFont(); the "body" rule's + // font instead mirrors "Label.font" (or its SansSerif/12 fallback), so + // that's what must be compared against here, not pane.getFont(). + Font expectedFont = UIManager.getFont("Label.font"); + if (expectedFont == null) { + expectedFont = new Font("SansSerif", Font.PLAIN, 12); + } + Assertions.assertEquals(expectedFont.getFamily(), + bodyRule.getAttribute(CSS.Attribute.FONT_FAMILY).toString()); + + AttributeSet linkRule = doc.getStyleSheet().getRule("a"); + Assertions.assertNotNull(linkRule); + Assertions.assertEquals(HtmlUtil.getHexString(TipUtil.getToolTipHyperlinkForeground()), + linkRule.getAttribute(CSS.Attribute.COLOR).toString()); + } + + + @Test + void tweakTipEditorPane_bulletResourceExists_addsListStyleRule() { + // Sanity check that the "ul { list-style-image }" rule is added when + // the bundled bullet_black.png resource is on the classpath, which it + // always is for this module's own build. + JEditorPane pane = createHtmlEditorPane(); + + TipUtil.tweakTipEditorPane(pane); + + HTMLDocument doc = (HTMLDocument) pane.getDocument(); + Assertions.assertNotNull(doc.getStyleSheet().getRule("ul")); + } + + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/UtilTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/UtilTest.java index 2cfa3c7..cfc126d 100644 --- a/AutoComplete/src/test/java/org/fife/ui/autocomplete/UtilTest.java +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/UtilTest.java @@ -4,12 +4,52 @@ */ package org.fife.ui.autocomplete; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Rectangle; +import java.io.IOException; +import java.net.URI; +import javax.swing.UIManager; + import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; class UtilTest { + private static final URI VALID_URI = URI.create("https://example.com/"); + + /** + * The {@code Desktop} instance {@code Util.browse()} will use for the + * remainder of the JVM's lifetime, once installed by + * {@link #primeDesktopCache()}. + */ + private static final Desktop MOCK_DESKTOP = Mockito.mock(Desktop.class); + + + /** + * {@code Util.browse()} lazily caches the result of + * {@code Desktop.isDesktopSupported()} / {@code Desktop.getDesktop()} in + * private static fields the very first time it's called, and never + * re-checks afterward. To keep every {@code browse()} test + * deterministic (and to make sure we never launch a real browser during + * a test run), we install a mock {@code Desktop} before any test in this + * class runs, priming that cache while {@code Desktop} is mocked. All + * {@code browse()} tests below then reuse this same cached mock. + */ + @BeforeAll + static void primeDesktopCache() { + try (MockedStatic desktop = Mockito.mockStatic(Desktop.class)) { + desktop.when(Desktop::isDesktopSupported).thenReturn(true); + desktop.when(Desktop::getDesktop).thenReturn(MOCK_DESKTOP); + Util.browse(URI.create("about:blank")); // Triggers the one-time caching. + } + } + + @Test void startsWithIgnoreCase_happyPath() { @@ -25,10 +65,187 @@ void startsWithIgnoreCase_happyPath() { @Test void startsWithIgnoreCase_tricky_iWithoutDot() { - Assertions.assertTrue(Util.startsWithIgnoreCase("\u0131", "i")); - Assertions.assertTrue(Util.startsWithIgnoreCase("\u0131", "I")); - Assertions.assertTrue(Util.startsWithIgnoreCase("i", "\u0131")); - Assertions.assertTrue(Util.startsWithIgnoreCase("I", "\u0131")); + Assertions.assertTrue(Util.startsWithIgnoreCase("ı", "i")); + Assertions.assertTrue(Util.startsWithIgnoreCase("ı", "I")); + Assertions.assertTrue(Util.startsWithIgnoreCase("i", "ı")); + Assertions.assertTrue(Util.startsWithIgnoreCase("I", "ı")); + } + + + @Test + void startsWithIgnoreCase_strShorterThanPrefix_returnsFalse() { + Assertions.assertFalse(Util.startsWithIgnoreCase("Hi", "Hello")); + } + + + @Test + void browse_nullUri_returnsFalse() { + Assertions.assertFalse(Util.browse(null)); + } + + + @Test + void browse_success_returnsTrue() throws IOException { + URI uri = URI.create("https://example.com/success"); + Mockito.doNothing().when(MOCK_DESKTOP).browse(uri); + Assertions.assertTrue(Util.browse(uri)); + Mockito.verify(MOCK_DESKTOP).browse(uri); + } + + + @Test + void browse_checkedExceptionFromDesktop_isSwallowed_returnsFalse() throws IOException { + URI uri = URI.create("https://example.com/checked-exception"); + Mockito.doThrow(new IOException("boom")).when(MOCK_DESKTOP).browse(uri); + Assertions.assertFalse(Util.browse(uri)); + } + + + @Test + void browse_runtimeExceptionFromDesktop_isRethrown() throws IOException { + URI uri = URI.create("https://example.com/runtime-exception"); + Mockito.doThrow(new IllegalStateException("boom")).when(MOCK_DESKTOP).browse(uri); + Assertions.assertThrows(IllegalStateException.class, () -> Util.browse(uri)); + } + + + @Test + void getScreenBoundsForPoint_pointOnAMonitor_returnsThatMonitorsBounds() { + Rectangle bounds = Util.getScreenBoundsForPoint(0, 0); + Assertions.assertNotNull(bounds); + Assertions.assertTrue(bounds.contains(0, 0)); + } + + + @Test + void getScreenBoundsForPoint_pointOffAllMonitors_fallsBackToMaximumWindowBounds() { + // No real display configuration should contain a point this far out. + Rectangle bounds = Util.getScreenBoundsForPoint(Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2); + Assertions.assertNotNull(bounds); + } + + + @Test + void getShouldAllowDecoratingMainAutoCompleteWindows_unset_returnsFalse() { + String propName = Util.PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS; + String orig = System.clearProperty(propName); + try { + Assertions.assertFalse(Util.getShouldAllowDecoratingMainAutoCompleteWindows()); + } finally { + if (orig != null) { + System.setProperty(propName, orig); + } + } + } + + + @Test + void getShouldAllowDecoratingMainAutoCompleteWindows_setTrue_returnsTrue() { + String propName = Util.PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS; + String orig = System.getProperty(propName); + try { + System.setProperty(propName, "true"); + Assertions.assertTrue(Util.getShouldAllowDecoratingMainAutoCompleteWindows()); + } finally { + if (orig == null) { + System.clearProperty(propName); + } else { + System.setProperty(propName, orig); + } + } + } + + + @Test + void getUseSubstanceRenderers_defaultsToTrue() { + // USE_SUBSTANCE_RENDERERS is computed once in a static initializer + // from a system property, so only the value observed at class-load + // time (with the property unset, in this build) can be verified here. + Assertions.assertTrue(Util.getUseSubstanceRenderers()); + } + + + @Test + void isLightForeground_allChannelsBright_returnsTrue() { + Assertions.assertTrue(Util.isLightForeground(Color.WHITE)); + } + + + @Test + void isLightForeground_allChannelsDark_returnsFalse() { + Assertions.assertFalse(Util.isLightForeground(Color.BLACK)); + } + + + @Test + void isLightForeground_oneChannelDark_returnsFalse() { + Assertions.assertFalse(Util.isLightForeground(new Color(0xFF, 0xFF, 0x00))); + } + + + @Test + void isLightForeground_boundaryValue_isExclusive() { + // isLightForeground() requires each channel to be strictly > 0xa0. + Assertions.assertFalse(Util.isLightForeground(new Color(0xa0, 0xa0, 0xa0))); + Assertions.assertTrue(Util.isLightForeground(new Color(0xa1, 0xa1, 0xa1))); + } + + + @Test + void stripHtml_null_returnsNull() { + Assertions.assertNull(Util.stripHtml(null)); + } + + + @Test + void stripHtml_doesNotStartWithHtmlTag_returnsUnchanged() { + String text = "bold but no leading tag"; + Assertions.assertEquals(text, Util.stripHtml(text)); + } + + + @Test + void stripHtml_startsWithHtmlTag_stripsAllTags() { + String text = "Hello world!"; + Assertions.assertEquals("Hello world!", Util.stripHtml(text)); + } + + + @Test + void stripHtml_noTagsAfterHtmlPrefix_returnsTextUnchangedMinusPrefixTag() { + Assertions.assertEquals("Hello world", Util.stripHtml("Hello world")); + } + + + private static final class UiManagerOverride implements AutoCloseable { + + private final Object original; + + UiManagerOverride(Color labelForeground) { + original = UIManager.get("Label.foreground"); + UIManager.put("Label.foreground", labelForeground); + } + + @Override + public void close() { + UIManager.put("Label.foreground", original); + } + } + + + @Test + void getHyperlinkForeground_lightLabelForeground_returnsLightHyperlinkColor() { + try (UiManagerOverride ignored = new UiManagerOverride(Color.WHITE)) { + Assertions.assertEquals(Util.LIGHT_HYPERLINK_FG, Util.getHyperlinkForeground()); + } + } + + + @Test + void getHyperlinkForeground_darkLabelForeground_returnsBlue() { + try (UiManagerOverride ignored = new UiManagerOverride(Color.BLACK)) { + Assertions.assertEquals(Color.BLUE, Util.getHyperlinkForeground()); + } } diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/VariableCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/VariableCompletionTest.java new file mode 100644 index 0000000..fa8f8f9 --- /dev/null +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/VariableCompletionTest.java @@ -0,0 +1,118 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(SwingRunnerExtension.class) +class VariableCompletionTest { + + + @Test + void constructor() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + Assertions.assertEquals("foo", completion.getName()); + Assertions.assertEquals("int", completion.getType()); + Assertions.assertNull(completion.getDefinedIn()); + } + + + @Test + void getDefinitionString_withType() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + Assertions.assertEquals("int foo", completion.getDefinitionString()); + } + + + @Test + void getDefinitionString_noType() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "NULL", null); + Assertions.assertEquals("NULL", completion.getDefinitionString()); + } + + + @Test + void getName_returnsReplacementText() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + Assertions.assertEquals(completion.getReplacementText(), completion.getName()); + } + + + @Test + void getSetDefinedIn() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + completion.setDefinedIn("SomeClass"); + Assertions.assertEquals("SomeClass", completion.getDefinedIn()); + } + + + @Test + void getSummary_minimal() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + String summary = completion.getSummary(); + Assertions.assertEquals("int foo", summary); + } + + + @Test + void getSummary_withDescriptionAndDefinedIn() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + completion.setShortDescription("a description"); + completion.setDefinedIn("SomeClass"); + String summary = completion.getSummary(); + Assertions.assertTrue(summary.contains("a description")); + Assertions.assertTrue(summary.contains("Defined in:")); + Assertions.assertTrue(summary.contains("SomeClass")); + } + + + @Test + void getToolTipText_returnsDefinitionString() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + Assertions.assertEquals(completion.getDefinitionString(), completion.getToolTipText()); + } + + + @Test + void possiblyAddDescription_returnsFalseWhenNoDescription() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + StringBuilder sb = new StringBuilder(); + Assertions.assertFalse(completion.possiblyAddDescription(sb)); + Assertions.assertEquals(0, sb.length()); + } + + + @Test + void possiblyAddDescription_returnsTrueWhenDescriptionSet() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + completion.setShortDescription("a description"); + StringBuilder sb = new StringBuilder(); + Assertions.assertTrue(completion.possiblyAddDescription(sb)); + Assertions.assertTrue(sb.toString().contains("a description")); + } + + + @Test + void toString_returnsName() { + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + VariableCompletion completion = new VariableCompletion(provider, "foo", "int"); + Assertions.assertEquals("foo", completion.toString()); + } + + +} diff --git a/README.md b/README.md index 2c1a5a3..5c189da 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ![Java Build](https://github.com/bobbylight/AutoComplete/actions/workflows/gradle.yml/badge.svg) ![Java Build](https://github.com/bobbylight/AutoComplete/actions/workflows/codeql-analysis.yml/badge.svg) ![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.fifesoft/autocomplete/badge.svg) +[![codecov](https://codecov.io/gh/bobbylight/AutoComplete/graph/badge.svg?token=GparmvFgS9)](https://codecov.io/gh/bobbylight/AutoComplete) AutoComplete is a code completion library for Swing JTextComponents, with enhanced functionality available for instances of [RSyntaxTextArea](https://github.com/bobbylight/RSyntaxTextArea). diff --git a/build.gradle b/build.gradle index 6860341..686eca4 100644 --- a/build.gradle +++ b/build.gradle @@ -84,6 +84,7 @@ subprojects { testImplementation platform('org.junit:junit-bom:6.1.3') testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.mockito:mockito-core:5.23.0' } compileJava {