/**
* Wire
* Copyright (C) 2016 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.waz.zclient.core.api.scala;
import com.waz.zclient.testutils.MockObservable;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import static com.waz.zclient.core.api.scala.ModelObserver.Reason.FORCED_UPDATE;
import static com.waz.zclient.core.api.scala.ModelObserver.Reason.INTERNAL_CHANGE;
import static com.waz.zclient.core.api.scala.ModelObserver.Reason.NEW_MODEL;
import static com.waz.zclient.core.api.scala.ModelObserverTest.FailedMessages.shouldHaveBeenCalled;
import static com.waz.zclient.core.api.scala.ModelObserverTest.FailedMessages.updateReasonShouldHaveBeen;
import static junit.framework.Assert.assertEquals;
@SuppressWarnings("PMD")
public class ModelObserverTest {
TestObserver observer;
@Before
public void setup() {
observer = new TestObserver();
}
@Test
public void nullModelsShouldNotTriggerAnUpdate() {
MockObservable model = null;
observer.setAndUpdate(model);
observer.addAndUpdate(model);
assertEquals("The observer's update method should never have called", 0, observer.callsToUpdate);
}
@Test
public void setAndUpdateRemovesOldModels() {
MockObservable modelOld = new MockObservable(0);
observer.setAndUpdate(modelOld);
MockObservable modelNew = new MockObservable(1);
observer.setAndUpdate(modelNew);
assertEquals("The observer should only have one model", 1, observer.getModels().size());
assertEquals("The observer should be listening to modelNew", modelNew, observer.getModels().first());
}
@Test
public void addAndUpdateAddsNewModels() {
MockObservable model1 = new MockObservable(1);
MockObservable model2 = new MockObservable(2);
observer.addAndUpdate(model1);
observer.addAndUpdate(model2);
assertEquals("The observer should be listening to both models", 2, observer.getModels().size());
assertEquals(observer.getModels().first(), model1);
assertEquals(observer.getModels().last(), model2);
}
@Test
public void addAndUpdateOnlyAddsNonDuplicateModelsAndDoesntTriggerUpdate() {
MockObservable model1 = new MockObservable(1);
MockObservable modelDup = new MockObservable(1);
observer.addAndUpdate(model1);
observer.addAndUpdate(modelDup);
assertEquals("The observer should only be listening to one model", 1, observer.getModels().size());
assertEquals("The observer's update method should only have been called once", 1, observer.callsToUpdate);
}
@Test
public void setAndUpdateSameModelShouldNotTriggerNewUpdate() {
MockObservable model1 = new MockObservable(1);
MockObservable modelDup = new MockObservable(1);
observer.setAndUpdate(model1);
observer.setAndUpdate(modelDup);
assertEquals("The observer should only be listening to one model", 1, observer.getModels().size());
assertEquals("The observer's update method should only have been called once", 1, observer.callsToUpdate);
}
@Test
public void setAndUpdateNewModelsShouldBeUpdated() {
MockObservable model1 = new MockObservable(1);
MockObservable model2 = new MockObservable(2);
MockObservable model3 = new MockObservable(3);
observer.setAndUpdate(Arrays.asList(model1, model2));
assertEquals(shouldHaveBeenCalled("twice"), 2, observer.callsToUpdate);
observer.setAndUpdate(Arrays.asList(model1, model3));
assertEquals("The observer should be listening to 2 models", 2, observer.getModels().size());
assertEquals("The observer's update method should only have been called once more for the new model", 3, observer.callsToUpdate);
}
@Test
public void forceUpdatingPassesUpdateReason() {
MockObservable model = new MockObservable(1);
observer.setAndUpdate(model);
observer.forceUpdate();
assertEquals(shouldHaveBeenCalled("twice"), 2, observer.callsToUpdate);
assertEquals(updateReasonShouldHaveBeen(FORCED_UPDATE), FORCED_UPDATE, observer.lastUpdateReasons.pop());
}
@Test
public void internalModelUpdatePassesUpdateReason() {
MockObservable model = new MockObservable(1);
observer.setAndUpdate(model);
model.triggerInternalUpdate();
assertEquals(shouldHaveBeenCalled("twice"), 2, observer.callsToUpdate);
assertEquals(updateReasonShouldHaveBeen(INTERNAL_CHANGE), INTERNAL_CHANGE, observer.lastUpdateReasons.pop());
}
@Test
public void assigningNewModelPassesUpdateReason() {
MockObservable model1 = new MockObservable(1);
MockObservable model2 = new MockObservable(2);
observer.setAndUpdate(model1);
observer.setAndUpdate(model2);
assertEquals(shouldHaveBeenCalled("twice"), 2, observer.callsToUpdate);
assertEquals(updateReasonShouldHaveBeen(NEW_MODEL), NEW_MODEL, observer.lastUpdateReasons.pop());
}
@Test
public void mixtureOfUpdateReasonsWithSets() {
MockObservable model1 = new MockObservable(1);
MockObservable model2 = new MockObservable(2);
MockObservable model3 = new MockObservable(3);
observer.setAndUpdate(Arrays.asList(model1, model2));
assertEquals(shouldHaveBeenCalled("twice"), 2, observer.callsToUpdate);
assertEquals(updateReasonShouldHaveBeen(NEW_MODEL), NEW_MODEL, observer.lastUpdateReasons.pop());
assertEquals(updateReasonShouldHaveBeen(NEW_MODEL), NEW_MODEL, observer.lastUpdateReasons.pop());
model1.triggerInternalUpdate();
assertEquals(shouldHaveBeenCalled("three times"), 3, observer.callsToUpdate);
assertEquals(updateReasonShouldHaveBeen(INTERNAL_CHANGE), INTERNAL_CHANGE, observer.lastUpdateReasons.pop());
observer.setAndUpdate(Arrays.asList(model1, model3));
assertEquals(shouldHaveBeenCalled("four times"), 4, observer.callsToUpdate);
assertEquals(updateReasonShouldHaveBeen(NEW_MODEL), NEW_MODEL, observer.lastUpdateReasons.pop());
}
private class TestObserver extends ModelObserver<MockObservable> {
public int callsToUpdate = 0;
public Stack<Reason> lastUpdateReasons = new Stack<>();
@Override
public void updated(MockObservable model, Reason reason) {
callsToUpdate++;
lastUpdateReasons.add(reason);
}
public SortedSet<MockObservable> getModels() {
SortedSet<MockObservable> set = new TreeSet<>();
for (SingleModelObserver observer : observers) {
set.add(observer.getModel());
}
return set;
}
}
/**
* Commonly used failed test messages
*/
public static final class FailedMessages {
public static String shouldHaveBeenCalled(String times) {
return String.format("The observers update method should now have been called %s", times);
}
public static String updateReasonShouldHaveBeen(ModelObserver.Reason reason) {
return String.format("The update reason should be %s", reason.name());
}
}
}