/*
* Copyright 2010 Google 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.google.web.bindery.event.shared;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import junit.framework.AssertionFailedError;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.event.dom.client.DomEvent.Type;
import com.google.web.bindery.event.shared.testing.CountingEventBus;
public class SimplerEventBusTest extends HandlerTestBase {
public void testAddAndRemoveHandlers() {
CountingEventBus eventBus = new CountingEventBus(new SimplerEventBus());
eventBus.addHandler(MouseDownEvent.getType(), mouse1);
eventBus.addHandler(MouseDownEvent.getType(), mouse2);
HandlerRegistration reg1 = eventBus.addHandler(MouseDownEvent.getType(), adaptor1);
fireMouseDown(eventBus);
assertEquals(3, eventBus.getHandlerCount(MouseDownEvent.getType()));
assertFired(mouse1, mouse2, adaptor1);
eventBus.addHandler(MouseDownEvent.getType(), mouse3);
assertEquals(4, eventBus.getHandlerCount(MouseDownEvent.getType()));
eventBus.addHandler(MouseDownEvent.getType(), mouse1);
eventBus.addHandler(MouseDownEvent.getType(), mouse2);
HandlerRegistration reg2 = eventBus.addHandler(MouseDownEvent.getType(), adaptor1);
/*
* You can indeed add handlers twice, they will only be removed one at a
* time though.
*/
assertEquals(7, eventBus.getHandlerCount(MouseDownEvent.getType()));
eventBus.addHandler(ClickEvent.getType(), adaptor1);
eventBus.addHandler(ClickEvent.getType(), click1);
eventBus.addHandler(ClickEvent.getType(), click2);
assertEquals(7, eventBus.getHandlerCount(MouseDownEvent.getType()));
assertEquals(3, eventBus.getHandlerCount(ClickEvent.getType()));
reset();
fireMouseDown(eventBus);
assertFired(mouse1, mouse2, mouse3, adaptor1);
assertNotFired(click1, click2);
// Gets rid of first instance.
reg1.removeHandler();
fireMouseDown(eventBus);
assertFired(mouse1, mouse2, mouse3, adaptor1);
assertNotFired(click1, click2);
// Gets rid of second instance.
reg2.removeHandler();
reset();
fireMouseDown(eventBus);
assertFired(mouse1, mouse2, mouse3);
assertNotFired(adaptor1, click1, click2);
// Checks to see if click events are still working.
reset();
fireClickEvent(eventBus);
assertNotFired(mouse1, mouse2, mouse3);
assertFired(click1, click2, adaptor1);
}
public void testConcurrentAdd() {
final SimplerEventBus eventBus = new SimplerEventBus();
final MouseDownHandler two = new MouseDownHandler() {
public void onMouseDown(MouseDownEvent event) {
add(this);
}
};
MouseDownHandler one = new MouseDownHandler() {
public void onMouseDown(MouseDownEvent event) {
eventBus.addHandler(MouseDownEvent.getType(), two);
add(this);
}
};
eventBus.addHandler(MouseDownEvent.getType(), one);
eventBus.addHandler(MouseDownEvent.getType(), mouse1);
eventBus.addHandler(MouseDownEvent.getType(), mouse2);
eventBus.addHandler(MouseDownEvent.getType(), mouse3);
fireMouseDown(eventBus);
assertFired(one, two, mouse1, mouse2, mouse3);
reset();
fireMouseDown(eventBus);
assertFired(one, two, mouse1, mouse2, mouse3);
}
class ShyHandler implements MouseDownHandler {
HandlerRegistration r;
public void onMouseDown(MouseDownEvent event) {
add(this);
r.removeHandler();
}
}
public void testConcurrentRemove() {
final SimplerEventBus eventBus = new SimplerEventBus();
ShyHandler h = new ShyHandler();
eventBus.addHandler(MouseDownEvent.getType(), mouse1);
h.r = eventBus.addHandler(MouseDownEvent.getType(), h);
eventBus.addHandler(MouseDownEvent.getType(), mouse2);
eventBus.addHandler(MouseDownEvent.getType(), mouse3);
fireMouseDown(eventBus);
assertFired(h, mouse1, mouse2, mouse3);
reset();
fireMouseDown(eventBus);
assertFired(mouse1, mouse2, mouse3);
assertNotFired(h);
}
class SourcedHandler implements MouseDownHandler {
final String expectedSource;
SourcedHandler(String source) {
expectedSource = source;
}
public void onMouseDown(MouseDownEvent event) {
add(this);
assertEquals(expectedSource, event.getSource());
}
}
public void testAssertThrowsNpe() {
final SimplerEventBus eventBus = new SimplerEventBus();
try {
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
eventBus.addHandler(MouseDownEvent.getType(), mouse1);
}
});
fail("expected AssertionFailedError");
} catch (AssertionFailedError e) {
/* pass */
}
}
public void testNullChecks() {
final SimplerEventBus eventBus = new SimplerEventBus();
final Type<MouseDownHandler> type = MouseDownEvent.getType();
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
eventBus.addHandler(null, mouse1);
}
});
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
eventBus.addHandlerToSource(type, "foo", null);
}
});
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
eventBus.addHandlerToSource(type, null, mouse1);
}
});
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
eventBus.addHandlerToSource(null, "foo", mouse1);
}
});
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
eventBus.fireEvent(null);
}
});
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
eventBus.fireEventFromSource(null, "");
}
});
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
fireMouseDown(eventBus, null);
}
});
assertThrowsNpe(new ScheduledCommand() {
public void execute() {
eventBus.fireEventFromSource(null, "baker");
}
});
}
private void assertThrowsNpe(ScheduledCommand command) {
try {
command.execute();
fail("expected NullPointerException");
} catch (NullPointerException e) {
/* pass */
}
}
public void testFromSource() {
final SimplerEventBus eventBus = new SimplerEventBus();
SourcedHandler global = new SourcedHandler("able");
SourcedHandler able = new SourcedHandler("able");
SourcedHandler baker = new SourcedHandler("baker");
eventBus.addHandler(MouseDownEvent.getType(), global);
eventBus.addHandlerToSource(MouseDownEvent.getType(), "able", able);
eventBus.addHandlerToSource(MouseDownEvent.getType(), "baker", baker);
fireMouseDown(eventBus, "able");
assertFired(global, able);
assertNotFired(baker);
}
public void testNoSource() {
final SimplerEventBus eventBus = new SimplerEventBus();
SourcedHandler global = new SourcedHandler(null);
SourcedHandler able = new SourcedHandler("able");
SourcedHandler baker = new SourcedHandler("baker");
eventBus.addHandler(MouseDownEvent.getType(), global);
eventBus.addHandlerToSource(MouseDownEvent.getType(), "able", able);
eventBus.addHandlerToSource(MouseDownEvent.getType(), "baker", baker);
fireMouseDown(eventBus);
assertFired(global);
assertNotFired(able, baker);
}
public void testConcurrentAddAndRemoveByNastyUsersTryingToHurtUs() {
final SimplerEventBus eventBus = new SimplerEventBus();
final MouseDownHandler two = new MouseDownHandler() {
public void onMouseDown(MouseDownEvent event) {
add(this);
}
@Override
public String toString() {
return "two";
}
};
MouseDownHandler one = new MouseDownHandler() {
public void onMouseDown(MouseDownEvent event) {
eventBus.addHandler(MouseDownEvent.getType(), two).removeHandler();
eventBus.addHandler(MouseDownEvent.getType(), two).removeHandler();
add(this);
}
@Override
public String toString() {
return "one";
}
};
eventBus.addHandler(MouseDownEvent.getType(), one);
eventBus.addHandler(MouseDownEvent.getType(), mouse1);
eventBus.addHandler(MouseDownEvent.getType(), mouse2);
eventBus.addHandler(MouseDownEvent.getType(), mouse3);
eventBus.fireEvent(new MouseDownEvent() {
});
assertFired(one, mouse1, mouse2, mouse3);
assertNotFired(two);
reset();
eventBus.fireEvent(new MouseDownEvent() {
});
assertFired(one, mouse1, mouse2, mouse3);
assertNotFired(two);
}
public void testRemoveSelf() {
final SimplerEventBus eventBus = new SimplerEventBus();
MouseDownHandler h = new MouseDownHandler() {
HandlerRegistration reg = eventBus.addHandler(MouseDownEvent.getType(), this);
public void onMouseDown(MouseDownEvent event) {
add(this);
reg.removeHandler();
}
};
eventBus.fireEvent(new MouseDownEvent() {
});
assertFired(h);
reset();
eventBus.fireEvent(new MouseDownEvent() {
});
assertNotFired(h);
}
public void testNoDoubleRemove() {
final SimplerEventBus eventBus = new SimplerEventBus();
HandlerRegistration reg = eventBus.addHandler(MouseDownEvent.getType(), mouse1);
reg.removeHandler();
boolean assertsOn = getClass().desiredAssertionStatus();
if (assertsOn) {
try {
reg.removeHandler();
fail("Should have thrown on remove");
} catch (AssertionError e) { /* pass */
}
} else {
reg.removeHandler();
// Succeed on no assert failure
}
}
public void testConcurrentAddAfterRemoveIsNotClobbered() {
final SimplerEventBus eventBus = new SimplerEventBus();
MouseDownHandler one = new MouseDownHandler() {
HandlerRegistration reg = addIt();
public void onMouseDown(MouseDownEvent event) {
reg.removeHandler();
addIt();
add(this);
}
private HandlerRegistration addIt() {
return eventBus.addHandler(MouseDownEvent.getType(), mouse1);
}
};
eventBus.addHandler(MouseDownEvent.getType(), one);
eventBus.fireEvent(new MouseDownEvent() {
});
assertFired(one);
reset();
eventBus.fireEvent(new MouseDownEvent() {
});
assertFired(one, mouse1);
}
static class ThrowingHandler implements MouseDownHandler {
private final RuntimeException e;
public ThrowingHandler(RuntimeException e) {
this.e = e;
}
public void onMouseDown(MouseDownEvent event) {
throw e;
}
}
public void testHandlersThrow() {
RuntimeException exception1 = new RuntimeException("first exception");
RuntimeException exception2 = new RuntimeException("second exception");
final SimplerEventBus eventBus = new SimplerEventBus();
eventBus.addHandler(MouseDownEvent.getType(), mouse1);
eventBus.addHandler(MouseDownEvent.getType(), new ThrowingHandler(exception1));
eventBus.addHandler(MouseDownEvent.getType(), mouse2);
eventBus.addHandler(MouseDownEvent.getType(), new ThrowingHandler(exception2));
eventBus.addHandler(MouseDownEvent.getType(), mouse3);
MouseDownEvent event = new MouseDownEvent() {
};
try {
eventBus.fireEvent(event);
fail("eventBus should have thrown");
} catch (UmbrellaException e) {
Set<Throwable> causes = e.getCauses();
assertEquals("Exception should wrap the two thrown exceptions", 2, causes.size());
assertTrue("First exception should be under the umbrella", causes.contains(exception1));
assertTrue("Second exception should be under the umbrella", causes.contains(exception2));
}
/*
* Exception should not have prevented all three mouse handlers from getting
* the event.
*/
assertFired(mouse1, mouse2, mouse3);
}
public void testNullSourceOkay() {
SimplerEventBus reg = new SimplerEventBus();
MouseDownHandler handler = new MouseDownHandler() {
public void onMouseDown(MouseDownEvent event) {
add(this);
assertNull(event.getSource());
}
};
reg.addHandler(MouseDownEvent.getType(), handler);
reg.fireEvent(new MouseDownEvent() {
});
assertFired(handler);
}
public void testReentrantFiresAreQueuedUp() {
final SimplerEventBus reg = new SimplerEventBus();
// a flag to make our first handler cause a reentrant event
final boolean fireAgain[] = { true };
final List<Integer> orderOfFires = new ArrayList<Integer>();
// setup 1st handler that will conditionally be reentrant
reg.addHandler(ClickEvent.getType(), new ClickHandler() {
public void onClick(ClickEvent arg0) {
// make a reentrant event, e.g. a change handler caused another change event
orderOfFires.add(1);
if (fireAgain[0]) {
fireAgain[0] = false;
fireClickEvent(reg);
}
}
});
// setup 2nd handler to
reg.addHandler(ClickEvent.getType(), new ClickHandler() {
public void onClick(ClickEvent arg0) {
orderOfFires.add(2);
}
});
fireClickEvent(reg);
assertThat(orderOfFires, contains(1, 2, 1, 2));
}
public void testReentrantEventsAreFiredIfThereWasAnException() {
final SimplerEventBus reg = new SimplerEventBus();
// a flag to make our first handler cause a reentrant event
final boolean fireAgain[] = { true };
final List<Integer> orderOfFires = new ArrayList<Integer>();
// setup 1st handler that will conditionally be reentrant
reg.addHandler(ClickEvent.getType(), new ClickHandler() {
public void onClick(ClickEvent arg0) {
// make a reentrant event, e.g. a change handler caused another change event
orderOfFires.add(1);
if (fireAgain[0]) {
fireAgain[0] = false;
fireClickEvent(reg);
}
}
});
// setup 2nd handler to fail
reg.addHandler(ClickEvent.getType(), new ClickHandler() {
public void onClick(ClickEvent arg0) {
throw new RuntimeException("failed");
}
});
try {
fireClickEvent(reg);
} catch (UmbrellaException r) {
assertThat(r.getMessage(), is("2 exceptions caught: failed; failed"));
assertThat(r.getCauses().size(), is(2));
}
assertThat(orderOfFires, contains(1, 1));
}
public void testInfiniteRecurssionIsCaught() {
final SimplerEventBus reg = new SimplerEventBus();
reg.addHandler(ClickEvent.getType(), new ClickHandler() {
public void onClick(ClickEvent arg0) {
fireMouseDown(reg);
}
});
reg.addHandler(MouseDownEvent.getType(), new MouseDownHandler() {
public void onMouseDown(MouseDownEvent event) {
fireClickEvent(reg);
}
});
try {
fireClickEvent(reg);
fail();
} catch (IllegalStateException ise) {
assertThat(ise.getMessage(), startsWith("Detected loop"));
}
}
private void fireMouseDown(EventBus eventBus) {
eventBus.fireEvent(new MouseDownEvent() {
});
}
private void fireMouseDown(EventBus eventBus, Object source) {
eventBus.fireEventFromSource(new MouseDownEvent() {
}, source);
}
private void fireClickEvent(EventBus eventBus) {
eventBus.fireEvent(new ClickEvent() {
});
}
}