diff --git a/gwtp-core/gwtp-mvp-client/src/main/java/com/gwtplatform/mvp/client/ViewImpl.java b/gwtp-core/gwtp-mvp-client/src/main/java/com/gwtplatform/mvp/client/ViewImpl.java index cb902754c6..cb6e3dc244 100644 --- a/gwtp-core/gwtp-mvp-client/src/main/java/com/gwtplatform/mvp/client/ViewImpl.java +++ b/gwtp-core/gwtp-mvp-client/src/main/java/com/gwtplatform/mvp/client/ViewImpl.java @@ -27,6 +27,7 @@ import com.google.gwt.user.client.ui.InsertPanel; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.Widget; + import com.gwtplatform.mvp.client.presenter.slots.IsSingleSlot; import com.gwtplatform.mvp.client.presenter.slots.OrderedSlot; import com.gwtplatform.mvp.client.presenter.slots.Slot; @@ -53,13 +54,28 @@ public abstract class ViewImpl implements View { public void addToSlot(Object slot, IsWidget content) { if (hasWidgetSlots.containsKey(slot)) { if (orderedSlots.containsKey(slot)) { - List>> list = orderedSlots.get(slot); - list.add((Comparable>) content); - Collections.sort(list); - int index = Collections.binarySearch(list, (Comparable>) content); - - InsertPanel insertPanel = (InsertPanel) hasWidgetSlots.get(slot); - insertPanel.insert(content.asWidget(), index); + final List>> list = orderedSlots.get(slot); + final int index = Collections.binarySearch(list, (Comparable>) content); + final int insertIdx; + if (index > 0) { + /** + * binary search returns the index of an equal item if found + * insert before it + */ + + insertIdx = index; + } else { + /** + * binary search returns -index - 1 if an equal item is not found + * where index is the "insertion point" + * reverse this operation and insert at the insertion point + */ + + insertIdx = -index - 1; + } + list.add(insertIdx, (Comparable>) content); + final InsertPanel insertPanel = (InsertPanel) hasWidgetSlots.get(slot); + insertPanel.insert(content.asWidget(), insertIdx); } else { hasWidgetSlots.get(slot).add(content.asWidget()); } @@ -115,11 +131,10 @@ public Widget asWidget() { *

* {@link HasOneWidget} has checked first. * - * @param slot the slot + * @param slot the slot * @param container the container must implement {@link HasOneWidget}. - * * @throws IllegalArgumentException if {@code container} implements neither of {@link HasOneWidget} or {@link - * HasWidgets}. + * HasWidgets}. */ protected void bindSlot(IsSingleSlot slot, Object container) { internalBindSlot(slot, container); @@ -128,7 +143,7 @@ protected void bindSlot(IsSingleSlot slot, Object container) { /** * Link a {@link Slot} to a container. * - * @param slot the slot + * @param slot the slot * @param container the container must implement HasWidgets. */ protected void bindSlot(Slot slot, HasWidgets container) { @@ -138,7 +153,7 @@ protected void bindSlot(Slot slot, HasWidgets container) { /** * Link an {@link OrderedSlot} to a container. * - * @param slot the slot + * @param slot the slot * @param container the container must implement {@link HasWidgets} & {@link InsertPanel}. */ protected void bindSlot(OrderedSlot slot, T container) { diff --git a/gwtp-core/gwtp-mvp-client/src/test/java/com/gwtplatform/mvp/client/ViewImplTest.java b/gwtp-core/gwtp-mvp-client/src/test/java/com/gwtplatform/mvp/client/ViewImplTest.java new file mode 100644 index 0000000000..7b20e76e56 --- /dev/null +++ b/gwtp-core/gwtp-mvp-client/src/test/java/com/gwtplatform/mvp/client/ViewImplTest.java @@ -0,0 +1,194 @@ +/* + * Copyright 2011 ArcBees Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.gwtplatform.mvp.client; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jukito.JukitoModule; +import org.jukito.JukitoRunner; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.google.gwt.junit.GWTMockUtilities; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.InsertPanel; +import com.google.gwt.user.client.ui.IsWidget; +import com.google.gwt.user.client.ui.Widget; + +import com.gwtplatform.mvp.client.presenter.slots.OrderedSlot; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link ViewImpl}. + * Created by Boris on 22/05/2016. + */ +@RunWith(JukitoRunner.class) +public class ViewImplTest { + + public static class Module extends JukitoModule { + @Override + protected void configureTest() { + GWTMockUtilities.disarm(); + forceMock(Widget.class); + } + } + + IsWidget widget; + MockInsertPanel container; + OrderedSlot slot; + TestViewImpl view; + + @Before + public void init() { + widget = mock(Widget.class); + container = mock(MockInsertPanel.class); + slot = mock(OrderedSlot.class); + + view = new TestViewImpl(); + + final Widget c = mock(Widget.class); + when(widget.asWidget()).thenReturn(c); + + view.initWidget(widget); + view.bindSlot(slot, container); + } + + @Test + public void testInsertSingle() { + final Widget c = mock(Widget.class); + final ComparableContent content = new ComparableContent("TEST", c); + view.addToSlot(slot, content); + + verify(container).insert(c, 0); + } + + @Test + public void testInsertBack() { + final Widget a = mock(Widget.class); + final Widget b = mock(Widget.class); + final Widget c = mock(Widget.class); + final ComparableContent aaa = new ComparableContent("AAA", a); + final ComparableContent bbb = new ComparableContent("BBB", b); + final ComparableContent ccc = new ComparableContent("CCC", c); + + view.addToSlot(slot, aaa); + view.addToSlot(slot, bbb); + view.addToSlot(slot, ccc); + + final InOrder inOrder = inOrder(container); + inOrder.verify(container).insert(a, 0); + inOrder.verify(container).insert(b, 1); + inOrder.verify(container).insert(c, 2); + } + + @Test + public void testInsertFront() { + final Widget a = mock(Widget.class); + final Widget b = mock(Widget.class); + final Widget c = mock(Widget.class); + final ComparableContent aaa = new ComparableContent("AAA", a); + final ComparableContent bbb = new ComparableContent("BBB", b); + final ComparableContent ccc = new ComparableContent("CCC", c); + + view.addToSlot(slot, ccc); + view.addToSlot(slot, bbb); + view.addToSlot(slot, aaa); + + final InOrder inOrder = inOrder(container); + inOrder.verify(container).insert(c, 0); + inOrder.verify(container).insert(b, 0); + inOrder.verify(container).insert(a, 0); + } + + @Test + public void testInsertRandomCheckOrder() { + final List names = new ArrayList<>(); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocationOnMock) throws Throwable { + final Widget w = (Widget) invocationOnMock.getArguments()[0]; + final int idx = (int) invocationOnMock.getArguments()[1]; + names.add(idx, w.getTitle()); + return null; + } + }).when(container).insert(any(Widget.class), anyInt()); + + for (int i = 0; i < 1000; ++i) { + final String name = Double.toString(Math.random()); + final Widget w = mock(Widget.class); + when(w.getTitle()).thenReturn(name); + final ComparableContent cc = new ComparableContent(name, w); + view.addToSlot(slot, cc); + } + + assertTrue("Container should have 1000 members.", names.size() == 1000); + final List namesCopy = new ArrayList<>(names); + Collections.sort(names); + assertEquals("Container elements should be sorted.", namesCopy, names); + } + + static class ComparableContent implements IsWidget, Comparable { + + private final String name; + private final Widget widget; + + ComparableContent(final String name, Widget widget) { + this.name = name; + this.widget = widget; + } + + @Override + public int compareTo(ComparableContent o) { + return name.compareTo(o.name); + } + + @Override + public Widget asWidget() { + return widget; + } + } + + interface MockInsertPanel extends InsertPanel, HasWidgets { + } + + static class TestViewImpl extends ViewImpl { + @Override + public void initWidget(IsWidget widget) { + super.initWidget(widget); + } + + @Override + public void bindSlot(OrderedSlot slot, T container) { + super.bindSlot(slot, container); + } + } +}