diff --git a/assert/assert-filter/src/main/java/com/mastercard/test/flow/assrt/filter/gui/FlowPanel.java b/assert/assert-filter/src/main/java/com/mastercard/test/flow/assrt/filter/gui/FlowPanel.java index 8727ce9bf6..c8df691bf5 100644 --- a/assert/assert-filter/src/main/java/com/mastercard/test/flow/assrt/filter/gui/FlowPanel.java +++ b/assert/assert-filter/src/main/java/com/mastercard/test/flow/assrt/filter/gui/FlowPanel.java @@ -11,14 +11,18 @@ import java.awt.Font; import java.awt.GridBagLayout; import java.awt.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; import java.util.regex.Pattern; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -43,7 +47,10 @@ class FlowPanel extends JPanel { private static final long serialVersionUID = 1L; - private final transient Filter filter; + /** + * The {@link Filter} that is maniupated by this widget + */ + final transient Filter filter; /** * Map from list content to flow @@ -81,6 +88,10 @@ class FlowPanel extends JPanel { } return d; }; + + /** + * Invoked when the {@link Filter} changes + */ private transient Runnable updateListener = () -> { // no-op }; @@ -125,6 +136,13 @@ class FlowPanel extends JPanel { include.setEnabled( !enabledFlows.isSelectionEmpty() || !disabledFlows.isSelectionEmpty() ); exclude.setEnabled( !enabledFlows.isSelectionEmpty() ); } ); + setDoubleClickAction( enabledFlows, ( set, index ) -> { + if( set.isEmpty() ) { + listedFlows.values().forEach( f -> set.add( f.index ) ); + } + set.remove( index ); + } ); + disabledFlows.addListSelectionListener( lse -> { if( !clearing.get() ) { clearing.set( true ); @@ -134,6 +152,7 @@ class FlowPanel extends JPanel { include.setEnabled( !enabledFlows.isSelectionEmpty() || !disabledFlows.isSelectionEmpty() ); exclude.setEnabled( !enabledFlows.isSelectionEmpty() ); } ); + setDoubleClickAction( disabledFlows, Set::add ); include.setToolTipText( "Enable selected flows" ); include.addActionListener( ac -> { @@ -226,6 +245,29 @@ class FlowPanel extends JPanel { } + private void setDoubleClickAction( JList list, + BiConsumer, Integer> action ) { + list.addMouseListener( new MouseAdapter() { + @Override + public void mouseClicked( MouseEvent e ) { + if( e.getClickCount() == 2 ) { + Optional.of( e.getPoint() ) + .map( list::locationToIndex ) + .map( list.getModel()::getElementAt ) + .map( listedFlows::get ) + .map( ifl -> ifl.index ) + .ifPresent( idx -> { + Set indices = filter.indices(); + action.accept( indices, idx ); + filter.indices( indices ); + updateListener.run(); + refresh(); + } ); + } + } + } ); + } + /** * @param l Called whenever the flow selection is changed */ diff --git a/assert/assert-filter/src/main/java/com/mastercard/test/flow/assrt/filter/gui/TagPanel.java b/assert/assert-filter/src/main/java/com/mastercard/test/flow/assrt/filter/gui/TagPanel.java index 2c30e53179..27ac88d173 100644 --- a/assert/assert-filter/src/main/java/com/mastercard/test/flow/assrt/filter/gui/TagPanel.java +++ b/assert/assert-filter/src/main/java/com/mastercard/test/flow/assrt/filter/gui/TagPanel.java @@ -10,12 +10,15 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Set; import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -39,7 +42,10 @@ class TagPanel extends JPanel { private static final long serialVersionUID = 1L; - private final transient Filter filter; + /** + * The {@link Filter} being manipulated by this widget + */ + final transient Filter filter; private final JList availableTags = new JList<>(); private final JList includedTags = new JList<>(); @@ -60,7 +66,10 @@ class TagPanel extends JPanel { */ transient Predicate tagLimit = t -> true; - private transient Runnable updateListener = () -> { + /** + * Invoked when the {@link Filter} changes + */ + transient Runnable updateListener = () -> { // no-op }; @@ -119,6 +128,8 @@ class TagPanel extends JPanel { avin.setEnabled( !availableTags.isSelectionEmpty() || !includedTags.isSelectionEmpty() ); avex.setEnabled( !availableTags.isSelectionEmpty() || !excludedTags.isSelectionEmpty() ); } ); + setDoubleClickAction( availableTags, Set::add, null ); + includedTags.addListSelectionListener( lse -> { if( !clearing.get() ) { clearing.set( true ); @@ -129,6 +140,8 @@ class TagPanel extends JPanel { avin.setEnabled( !availableTags.isSelectionEmpty() || !includedTags.isSelectionEmpty() ); inex.setEnabled( !includedTags.isSelectionEmpty() || !excludedTags.isSelectionEmpty() ); } ); + setDoubleClickAction( includedTags, Set::remove, null ); + excludedTags.addListSelectionListener( lse -> { if( !clearing.get() ) { clearing.set( true ); @@ -139,6 +152,7 @@ class TagPanel extends JPanel { avex.setEnabled( !availableTags.isSelectionEmpty() || !excludedTags.isSelectionEmpty() ); inex.setEnabled( !includedTags.isSelectionEmpty() || !excludedTags.isSelectionEmpty() ); } ); + setDoubleClickAction( excludedTags, null, Set::remove ); avin.setToolTipText( SWAP_SELECTED_TAGS ); avin.addActionListener( ac -> { @@ -246,6 +260,35 @@ class TagPanel extends JPanel { refresh(); } + private void setDoubleClickAction( JList list, + BiConsumer, String> includeAction, + BiConsumer, String> excludeAction ) { + list.addMouseListener( new MouseAdapter() { + @Override + public void mouseClicked( MouseEvent e ) { + if( e.getClickCount() == 2 ) { + String item = list.getModel().getElementAt( + list.locationToIndex( e.getPoint() ) ); + + if( includeAction != null ) { + Set s = filter.includedTags(); + includeAction.accept( s, item ); + filter.includedTags( s ); + } + + if( excludeAction != null ) { + Set s = filter.excludedTags(); + excludeAction.accept( s, item ); + filter.excludedTags( s ); + } + + updateListener.run(); + refresh(); + } + } + } ); + } + /** * @param l executed whenever the filter is updated */ diff --git a/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FilterGuiHarness.java b/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FilterGuiHarness.java index 6841debe1c..992a93e861 100644 --- a/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FilterGuiHarness.java +++ b/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FilterGuiHarness.java @@ -121,6 +121,20 @@ public FilterGuiHarness selectTags( TagList in, String... tags ) { return this; } + /** + * Double-clicks on a list item + * + * @param in The list that the item resides in + * @param tag The item to double-click + * @return this + */ + public FilterGuiHarness doubleClick( TagList in, String tag ) { + interactions.add( ( f, m ) -> { + f.list( in.widgetName ).item( tag ).doubleClick(); + } ); + return this; + } + /** * Clicks one of the tags swap buttons * @@ -188,6 +202,20 @@ public FilterGuiHarness selectFlows( FlowList list, String... flows ) { return this; } + /** + * Double-clicks on a list item + * + * @param in The list that the item resides in + * @param flow The item to double-click + * @return this + */ + public FilterGuiHarness doubleClick( FlowList in, String flow ) { + interactions.add( ( f, m ) -> { + f.list( in.widgetName ).item( FilterGuiHarness.toRendered( flow ) ).doubleClick(); + } ); + return this; + } + private static final Pattern TO_RENDERED = Pattern.compile( "(.*) \\| (.*)" ); diff --git a/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FilterGuiTest.java b/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FilterGuiTest.java index d75561ed05..53f20cbb92 100644 --- a/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FilterGuiTest.java +++ b/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FilterGuiTest.java @@ -7,7 +7,9 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import com.mastercard.test.flow.assrt.filter.Filter; import com.mastercard.test.flow.assrt.filter.gui.FilterGuiHarness.FlowList; import com.mastercard.test.flow.assrt.filter.gui.FilterGuiHarness.TagList; import com.mastercard.test.flow.assrt.filter.mock.Mdl; @@ -234,4 +236,20 @@ void combined() { .expectFilterResults( "fourth [bar, foo]" ); } + + /** + * Throws up a gui instance for you to fiddle with for manual testing + */ + @Test() + @EnabledIfSystemProperty(named = "manual", matches = "true") + void manual() { + Mdl mdl = new Mdl().withFlows( + "first [foo, bar]", + "second [bar, baz]", + "third [baz, oof]", + "fourth [foo, bar]" ); + Filter filter = new Filter( mdl ); + FilterGui gui = new FilterGui( filter ); + gui.blockForInput(); + } } diff --git a/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FlowPanelTest.java b/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FlowPanelTest.java index cc2e264322..68c3c49ac6 100644 --- a/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FlowPanelTest.java +++ b/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/FlowPanelTest.java @@ -245,6 +245,54 @@ void rebuild() { .on( mdl ); } + /** + * Shows how flows can be moved between the enabled and disabled set by + * double-clicking + */ + @Test + void doubleClick() { + + Mdl mdl = new Mdl().withFlows( + "bcd []", + "fga []", + "jkl []", + "mne []" ); + + new FilterGuiHarness() + .buildFlows() + .doubleClick( FlowList.ENABLED, "jkl | " ) + .expect( "before", + "┌─ avail… ─┐┌┈┈┈┐┌─ inclu… ─┐┌─ disable… ─┐┌┈┈┈┐┌─ enabled_flo… ─┐╔═════╗", + "│ │┊ _ ┊│ ││ jkl | │┊ _ ┊│ bcd | │║ ║", + "│ │└┈┈┈┘└──────────┘│ │┊ ┊│ fga | │║ ║", + "│ │ ┌┈┈┈┈┈┈┈┈┈┈┐│ │└┈┈┈┘│ mne | │║ ║", + "│ │ ┊ _ ┊│ │┌┈┈┈┐│ │║ ║", + "│ │ └┈┈┈┈┈┈┈┈┈┈┘│ │┊ ┊│ │║ Run ║", + "│ │┌┈┈┈┐┌─ exclu… ─┐│ │┊ _ ┊│ │║ ║", + "│ │┊ _ ┊│ ││ │┊ ┊│ │║ ║", + "└──────────┘└┈┈┈┘└──────────┘└────────────┘└┈┈┈┘└────────────────┘║ ║", + "┌┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐╔═══════════════════════════════════╗║ ║", + "┊ Reset ┊║ Reset ║║ ║", + "└┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘╚═══════════════════════════════════╝╚═════╝" ) + .doubleClick( FlowList.DISABLED, "jkl | " ) + .doubleClick( FlowList.ENABLED, "bcd | " ) + .expect( "after", + "┌─ avail… ─┐┌┈┈┈┐┌─ inclu… ─┐┌─ disable… ─┐┌┈┈┈┐┌─ enabled_flo… ─┐╔═════╗", + "│ │┊ _ ┊│ ││ bcd | │┊ _ ┊│ fga | │║ ║", + "│ │└┈┈┈┘└──────────┘│ │┊ ┊│ jkl | │║ ║", + "│ │ ┌┈┈┈┈┈┈┈┈┈┈┐│ │└┈┈┈┘│ mne | │║ ║", + "│ │ ┊ _ ┊│ │┌┈┈┈┐│ │║ ║", + "│ │ └┈┈┈┈┈┈┈┈┈┈┘│ │┊ ┊│ │║ Run ║", + "│ │┌┈┈┈┐┌─ exclu… ─┐│ │┊ _ ┊│ │║ ║", + "│ │┊ _ ┊│ ││ │┊ ┊│ │║ ║", + "└──────────┘└┈┈┈┘└──────────┘└────────────┘└┈┈┈┘└────────────────┘║ ║", + "┌┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐╔═══════════════════════════════════╗║ ║", + "┊ Reset ┊║ Reset ║║ ║", + "└┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘╚═══════════════════════════════════╝╚═════╝" ) + .close() + .on( mdl ); + } + /** * Shows how the filter input shuffles the tag and flow lists */ diff --git a/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/TagPanelTest.java b/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/TagPanelTest.java index cfdadba785..be26c7a31a 100644 --- a/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/TagPanelTest.java +++ b/assert/assert-filter/src/test/java/com/mastercard/test/flow/assrt/filter/gui/TagPanelTest.java @@ -153,6 +153,54 @@ void selection() { .on( mdl ); } + /** + * Shows that tags can be moved between sets by double-clicking + */ + @Test + void doubleClick() { + Mdl mdl = new Mdl().withFlows( + "first [foo, bar]", + "second [bar, baz]", + "third [baz, oof]" ); + + new FilterGuiHarness() + .selectTags( TagList.AVAILABLE, "bar" ) + .moveTo( TagList.INCLUDED ) + .selectTags( TagList.AVAILABLE, "oof" ) + .moveTo( TagList.EXCLUDED ) + .expect( "before", + "┌─ avail… ─┐┌┈┈┈┐┌─ inclu… ─┐╔═══════╗┌┈┈┈┈┈┐", + "│ baz │┊ _ ┊│ bar │║ ║┊ ┊", + "│ foo │└┈┈┈┘└──────────┘║ ║┊ ┊", + "│ │ ┌┈┈┈┈┈┈┈┈┈┈┐║ ║┊ ┊", + "│ │ ┊ _ ┊║ ║┊ ┊", + "│ │ └┈┈┈┈┈┈┈┈┈┈┘║ Build ║┊ Run ┊", + "│ │┌┈┈┈┐┌─ exclu… ─┐║ ║┊ ┊", + "│ │┊ _ ┊│ oof │║ ║┊ ┊", + "└──────────┘└┈┈┈┘└──────────┘║ ║┊ ┊", + "╔═══════════════════════════╗║ ║┊ ┊", + "║ Reset ║║ ║┊ ┊", + "╚═══════════════════════════╝╚═══════╝└┈┈┈┈┈┘" ) + .doubleClick( TagList.AVAILABLE, "baz" ) + .doubleClick( TagList.INCLUDED, "bar" ) + .doubleClick( TagList.EXCLUDED, "oof" ) + .expect( "after", + "┌─ avail… ─┐┌┈┈┈┐┌─ inclu… ─┐╔═══════╗┌┈┈┈┈┈┐", + "│ bar │┊ _ ┊│ baz │║ ║┊ ┊", + "│ foo │└┈┈┈┘└──────────┘║ ║┊ ┊", + "│ oof │ ┌┈┈┈┈┈┈┈┈┈┈┐║ ║┊ ┊", + "│ │ ┊ _ ┊║ ║┊ ┊", + "│ │ └┈┈┈┈┈┈┈┈┈┈┘║ Build ║┊ Run ┊", + "│ │┌┈┈┈┐┌─ exclu… ─┐║ ║┊ ┊", + "│ │┊ _ ┊│ │║ ║┊ ┊", + "└──────────┘└┈┈┈┘└──────────┘║ ║┊ ┊", + "╔═══════════════════════════╗║ ║┊ ┊", + "║ Reset ║║ ║┊ ┊", + "╚═══════════════════════════╝╚═══════╝└┈┈┈┈┈┘" ) + .close() + .on( mdl ); + } + /** * Exercises the tag reset button */