diff --git a/app/build.gradle b/app/build.gradle index 9a17300..f948d5f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,7 +22,10 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) + // Required -- JUnit 4 framework testCompile 'junit:junit:4.12' + // Optional -- Mockito framework + testCompile 'org.mockito:mockito-core:1.10.19' provided 'com.google.auto.value:auto-value:1.2' apt 'com.google.auto.value:auto-value:1.2' compile 'com.android.support:appcompat-v7:23.2.1' diff --git a/app/src/main/java/com/philliphsu/clock/Alarm.java b/app/src/main/java/com/philliphsu/clock/Alarm.java index b1d79d0..56438d1 100644 --- a/app/src/main/java/com/philliphsu/clock/Alarm.java +++ b/app/src/main/java/com/philliphsu/clock/Alarm.java @@ -1,5 +1,7 @@ package com.philliphsu.clock; +import android.support.annotation.NonNull; + import com.google.auto.value.AutoValue; import java.util.Calendar; @@ -38,13 +40,10 @@ public abstract class Alarm { /** Initializes a Builder to the same property values as this instance */ public abstract Builder toBuilder(); - public static void main(String[] args) { - Alarm a = Alarm.builder().build(); - } - public static Builder builder() { // Unfortunately, default values must be provided for generated Builders. // Fields that were not set when build() is called will throw an exception. + // TODO: How can QualityMatters get away with not setting defaults????? return new AutoValue_Alarm.Builder() .id(-1) .hour(0) @@ -55,17 +54,17 @@ public abstract class Alarm { .vibrates(false); } - public final void snooze(int minutes) { + public void snooze(int minutes) { if (minutes <= 0 || minutes > MAX_MINUTES_CAN_SNOOZE) throw new IllegalArgumentException("Cannot snooze for "+minutes+" minutes"); snoozingUntilMillis = System.currentTimeMillis() + minutes * 60000; } - public final long snoozingUntil() { + public long snoozingUntil() { return snoozingUntilMillis; } - public final boolean isSnoozed() { + public boolean isSnoozed() { if (snoozingUntilMillis <= System.currentTimeMillis()) { snoozingUntilMillis = 0; return false; @@ -73,31 +72,31 @@ public abstract class Alarm { return true; } - public final void setEnabled(boolean enabled) { + public void setEnabled(boolean enabled) { this.enabled = enabled; } - public final boolean isEnabled() { + public boolean isEnabled() { return enabled; } - public final void setRecurring(int day, boolean recurring) { + public void setRecurring(int day, boolean recurring) { checkDay(day); recurringDays()[day] = recurring; } - public final boolean isRecurring(int day) { + public boolean isRecurring(int day) { checkDay(day); return recurringDays()[day]; } - public final boolean hasRecurrence() { + public boolean hasRecurrence() { for (boolean b : recurringDays()) if (b) return true; return false; } - public final long ringsAt() { + public long ringsAt() { // Always with respect to the current date and time Calendar calendar = new GregorianCalendar(); calendar.set(Calendar.HOUR_OF_DAY, hour()); @@ -127,12 +126,12 @@ public abstract class Alarm { return calendar.getTimeInMillis(); } - public final long ringsIn() { + public long ringsIn() { return ringsAt() - System.currentTimeMillis(); } /** @return true if this Alarm will ring in the next {@code hours} hours */ - public final boolean ringsWithinHours(int hours) { + public boolean ringsWithinHours(int hours) { return ringsIn() <= hours * 3600000; } @@ -148,30 +147,39 @@ public abstract class Alarm { // and provide an "accumulating" method abstract boolean[] recurringDays(); public final Builder setRecurring(int day, boolean recurs) { + checkDay(day) recurringDays()[day] = recurs; return this; } */ public abstract Builder recurringDays(boolean[] recurringDays); - public abstract Builder label(String label); - public abstract Builder ringtone(String ringtone); + public abstract Builder label(@NonNull String label); + public abstract Builder ringtone(@NonNull String ringtone); public abstract Builder vibrates(boolean vibrates); // To enforce preconditions, split the build method into two. autoBuild() is hidden from // callers and is generated. You implement the public build(), which calls the generated // autoBuild() and performs your desired validations. /*not public*/abstract Alarm autoBuild(); - public final Alarm build() { + public Alarm build() { Alarm alarm = autoBuild(); - if (alarm.hour() < 0 || alarm.hour() > 23 || alarm.minutes() < 0 || alarm.minutes() > 59) { - throw new IllegalStateException("Hour and minutes invalid"); - } + checkTime(alarm.hour(), alarm.minutes()); return alarm; } } - private void checkDay(int day) { + private static void checkTime(int hour, int minutes) { + if (hour < 0 || hour > 23 || minutes < 0 || minutes > 59) { + throw new IllegalStateException("Hour and minutes invalid"); + } + } + + private static void checkDay(int day) { if (day < SUNDAY || day > SATURDAY) throw new IllegalArgumentException("Invalid day " + day); } + + public static void main(String[] args) { + + } } diff --git a/app/src/test/java/com/philliphsu/clock/AlarmTest.java b/app/src/test/java/com/philliphsu/clock/AlarmTest.java new file mode 100644 index 0000000..80438c7 --- /dev/null +++ b/app/src/test/java/com/philliphsu/clock/AlarmTest.java @@ -0,0 +1,122 @@ +package com.philliphsu.clock; + +import org.junit.Test; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +import static java.util.Calendar.DAY_OF_MONTH; +import static java.util.Calendar.DAY_OF_WEEK; +import static java.util.Calendar.HOUR_OF_DAY; +import static java.util.Calendar.MILLISECOND; +import static java.util.Calendar.MINUTE; +import static java.util.Calendar.MONTH; +import static java.util.Calendar.SECOND; +import static java.util.Calendar.YEAR; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Created by Phillip Hsu on 5/27/2016. + */ +public class AlarmTest { + + @Test + public void testRecurrence() { + Alarm alarm = Alarm.builder().build(); + + // Some true, some false + for (int i = Alarm.SUNDAY; i <= Alarm.SATURDAY; i++) { + alarm.setRecurring(i, i % 2 == 0); + assertTrue(alarm.isRecurring(i) == (i % 2 == 0)); + } + assertTrue(alarm.hasRecurrence()); + + // All false + for (int i = Alarm.SUNDAY; i <= Alarm.SATURDAY; i++) { + alarm.setRecurring(i, false); + assertFalse(alarm.isRecurring(i)); + } + assertFalse(alarm.hasRecurrence()); + } + + @Test + public void alarm_RingsAt_ReturnsCorrectRingTime() { + GregorianCalendar now = new GregorianCalendar(); + for (int h = 0; h < 24; h++) { + for (int m = 0; m < 60; m++) { + System.out.println(String.format("Testing %02d:%02d", h, m)); + int hC = now.get(HOUR_OF_DAY); // Current hour + int mC = now.get(MINUTE); // Current minute + Alarm a = Alarm.builder().hour(h).minutes(m).build(); + long calculatedRingTime; + if (h <= hC) { + if (m <= mC) { + calculatedRingTime = (23-hC+h)*3600000 + (60-mC+m)*60000; + } else { + calculatedRingTime = (m-mC)*60000; + if (h < hC) { + calculatedRingTime += (24-hC+h)*3600000; + } + } + } else { + if (m <= mC) { + calculatedRingTime = (h-hC-1)*3600000+(60-mC+m)*60000; + } else { + calculatedRingTime = (h-hC)*3600000+(m-mC)*60000; + } + } + now.setTimeInMillis(now.getTimeInMillis() + calculatedRingTime); + now.set(SECOND, 0); + now.set(MILLISECOND, 0); + assertEquals(a.ringsAt(), now.getTimeInMillis()); + // VERY IMPORTANT TO RESET AT THE END!!!! THIS TOOK A WHOLE FUCKING DAY OF BUG HUNTING!!! + now.setTimeInMillis(System.currentTimeMillis()); + } + } + } + + @Test + public void snoozeAlarm_AssertEquals_SnoozingUntilMillis_CorrespondsToWallClock() { + // Expected + Calendar cal = new GregorianCalendar(); + cal.add(MINUTE, 10); + // Actual + Calendar snoozeCal = new GregorianCalendar(); + Alarm alarm = Alarm.builder().build(); + alarm.snooze(10); + snoozeCal.setTimeInMillis(alarm.snoozingUntil()); + + assertEquals(cal.get(YEAR), snoozeCal.get(YEAR)); + assertEquals(cal.get(MONTH), snoozeCal.get(MONTH)); + assertEquals(cal.get(DAY_OF_MONTH), snoozeCal.get(DAY_OF_MONTH)); + assertEquals(cal.get(DAY_OF_WEEK), snoozeCal.get(DAY_OF_WEEK)); + assertEquals(cal.get(HOUR_OF_DAY), snoozeCal.get(HOUR_OF_DAY)); + assertEquals(cal.get(MINUTE), snoozeCal.get(MINUTE)); + assertEquals(cal.get(SECOND), snoozeCal.get(SECOND)); + // Milliseconds not required to be equal, because they will always + // have some difference + } + + @Test + public void snoozeAlarm_IsSnoozed_ReturnsTrue_ForAllMillisUpToButExcluding_SnoozingUntilMillis() { + Alarm alarm = Alarm.builder().build(); + alarm.snooze(1); + // If all iterations leading up to 20ms before the target time evaluate to true, + // that is good enough and we don't really care about the last 20ms. + while (alarm.snoozingUntil() - System.currentTimeMillis() > 20) { + assertTrue(alarm.isSnoozed()); + } + // Wait long enough so the target time passes. + try { + Thread.sleep(20); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + assertFalse(alarm.isSnoozed()); + // Check if the snoozingUntilMillis is cleared + assertEquals(0, alarm.snoozingUntil()); + } + } +}