[california/wip/725785-create-recurring] Firm up encoding/decoding BYDAY values w/ tests



commit 14865b836dd152f27d141f21df2fd7f7381f5422
Author: Jim Nelson <jim yorba org>
Date:   Fri Jun 20 13:25:16 2014 -0700

    Firm up encoding/decoding BYDAY values w/ tests

 src/collection/collection-iterable.vala      |   45 ++++++++-
 src/component/component-recurrence-rule.vala |  128 +++++++++++++++++---------
 src/tests/tests-quick-add-recurring.vala     |  109 +++++++++++++++++++++-
 3 files changed, 229 insertions(+), 53 deletions(-)
---
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index 1465a49..55863d8 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -215,7 +215,11 @@ public class Iterable<G> : Object {
         return (Gee.TreeSet<G>) add_all_to(new Gee.TreeSet<G>((owned) compare_func));
     }
     
-    public Gee.Map<K, G> add_all_to_map<K>(Gee.Map<K, G> c, Gee.MapFunc<K, G> key_func) {
+    /**
+     * Add this { link Iterable}'s values to an existing Gee.Map, with this Iterable's values as
+     * values for the map.
+     */
+    public Gee.Map<K, G> add_to_map_values<K>(Gee.Map<K, G> c, Gee.MapFunc<K, G> key_func) {
         while (i.next()) {
             G g = i  get();
             c  set(key_func(g), g);
@@ -223,16 +227,49 @@ public class Iterable<G> : Object {
         return c;
     }
     
-    public Gee.HashMap<K, G> to_hash_map<K>(Gee.MapFunc<K, G> key_func,
+    /**
+     * Add this { link Iterable}'s values to an existing Gee.Map, with this Iterable's values as
+     * keys for the map.
+     *
+     * @see add_to_map_keys
+     */
+    public Gee.Map<G, V> add_to_map_keys<V>(Gee.Map<G, V> map, Gee.MapFunc<V, G> value_func) {
+        while (i.next()) {
+            G g = i.get();
+            map.set(g, value_func(g));
+        }
+        
+        return map;
+    }
+    
+    /**
+     * Transform the { link Iterable} into a Gee.HashMap, with this Iterable's values as values
+     * for the map.
+     *
+     * @see add_to_map_values
+     */
+    public Gee.HashMap<K, G> to_hash_map_as_values<K>(Gee.MapFunc<K, G> key_func,
         owned Gee.HashDataFunc<K>? key_hash_func = null,
         owned Gee.EqualDataFunc<K>? key_equal_func = null,
         owned Gee.EqualDataFunc<G>? value_equal_func = null) {
-        return (Gee.HashMap<K, G>) add_all_to_map<K>(new Gee.HashMap<K, G>(
+        return (Gee.HashMap<K, G>) add_to_map_values<K>(new Gee.HashMap<K, G>(
             (owned) key_hash_func, (owned) key_equal_func, (owned) value_equal_func), key_func);
     }
     
     /**
-     * Convert the { link Iterable} into a plain string.
+     * Transform the { link Iterable} into a Gee.HashMap, with this Iterable's values as keys
+     * for the map.
+     */
+    public Gee.HashMap<G, V> to_hash_map_as_keys<V>(Gee.MapFunc<V, G> value_func,
+        owned Gee.HashDataFunc<G>? key_hash_func = null,
+        owned Gee.EqualDataFunc<G>? key_equal_func = null,
+        owned Gee.EqualDataFunc<V>? value_equal_func = null) {
+        return (Gee.HashMap<G, V>) add_to_map_keys<V>(new Gee.HashMap<G, V>(
+            (owned) key_hash_func, (owned) key_equal_func, (owned) value_equal_func), value_func);
+    }
+    
+    /**
+     * Convert the { link Iterable}'s values into a single plain string.
      *
      * If { link ToString} returns null or an empty string, nothing is appended to the final string.
      *
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
index 1ee658a..59b6f6a 100644
--- a/src/component/component-recurrence-rule.vala
+++ b/src/component/component-recurrence-rule.vala
@@ -270,50 +270,19 @@ public class RecurrenceRule : BaseObject {
     }
     
     /**
-     * Returns a read-only sorted set of BY rule settings for the specified { link ByRule}.
-     */
-    public Gee.SortedSet<int> get_by_rule(ByRule by_rule) {
-        switch (by_rule) {
-            case ByRule.SECOND:
-                return by_second.read_only_view;
-            
-            case ByRule.MINUTE:
-                return by_minute.read_only_view;
-            
-            case ByRule.HOUR:
-                return by_hour.read_only_view;
-            
-            case ByRule.DAY:
-                return by_day.read_only_view;
-            
-            case ByRule.MONTH_DAY:
-                return by_month_day.read_only_view;
-            
-            case ByRule.YEAR_DAY:
-                return by_year_day.read_only_view;
-            
-            case ByRule.WEEK_NUM:
-                return by_week_num.read_only_view;
-            
-            case ByRule.MONTH:
-                return by_month.read_only_view;
-            
-            case ByRule.SET_POS:
-                return by_set_pos.read_only_view;
-            
-            default:
-                assert_not_reached();
-        }
-    }
-    
-    /**
-     * Encode a { link Calendar.DayOfWeek} and its position (i.e. Second Thursday of the month,
-     * last Wednesday of the yar) into a value for { link set_by_rule} when using
+     * Encode a { link Calendar.DayOfWeek} and its position (i.e. second Thursday of the month,
+     * last Wednesday of the year) into a value for { link set_by_rule} when using
      * { link ByRule.DAY}.
      *
+     * For position, 1 = first, 2 = second, -1 = last, -2 = second to last, etc.
+     *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+     * operate according to this RRULE's { link freq}.
+     *
      * Use null for DayOfWeek and zero for position to mean "any" or "every".
      *
      * @see encode_days
+     * @see decode_day
      */
     public static int encode_day(Calendar.DayOfWeek? dow, int position) {
         int dow_value = (dow != null) ? dow.ordinal(Calendar.System.first_of_week) : 0;
@@ -322,17 +291,45 @@ public class RecurrenceRule : BaseObject {
     }
     
     /**
-     * Encode a Gee.Map of { link Calendar.DayOfWeek} and its position (i.e. Second Thursday of
-     * the month, last Wednesday of the year) into a value for { link set_by_rule} when using
-     * { link ByRule.DAY}.
+     * Decode the integer returned by { link get_by_rule} when { link ByRule.DAY} passed in.
+     *
+     * If null is returned for DayOfWeek or zero for position, that indicates "any" or "every".
+     * See { link encode_day} for more information.
+     *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+     * operate according to this RRULE's { link freq}.
+     *
+     * Returns false if the supplied value is definitely not encoded correctly.
+     */
+    public static bool decode_day(int value, out Calendar.DayOfWeek? dow, out int position) {
+        position = value / 7;
+        
+        dow = null;
+        int dow_value = value % 7;
+        if (dow_value != 0) {
+            try {
+                dow = Calendar.DayOfWeek.for(dow_value, Calendar.System.first_of_week);
+            } catch (CalendarError cal_err) {
+                debug("Unable to decode %d: %d is invalid day of week", value, dow_value);
+                
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Encode a Gee.Map of { link Calendar.DayOfWeek} and its position into a value for
+     * { link set_by_rule} when using { link ByRule.DAY}.
      *
      * Use null for DayOfWeek and zero for position to mean "any" or "every".
      *
-     * @encode_day
+     * @see encode_day
      */
-    public static Gee.Collection<int>? encode_days(Gee.Map<Calendar.DayOfWeek?, int>? day_values) {
+    public static Gee.Collection<int> encode_days(Gee.Map<Calendar.DayOfWeek?, int>? day_values) {
         if (day_values == null || day_values.size == 0)
-            return null;
+            return Gee.Collection.empty<int>();
         
         Gee.Collection<int> encoded = new Gee.ArrayList<int>();
         Gee.MapIterator<Calendar.DayOfWeek?, int> iter = day_values.map_iterator();
@@ -342,6 +339,31 @@ public class RecurrenceRule : BaseObject {
         return encoded;
     }
     
+    /**
+     * Decode a Gee.Collection of encoded { link ByRule.DAY} values into their positions and
+     * { link Calendar.DayOfWeek}.
+     *
+     * Invalid values are skipped.
+     *
+     * @see encode_day
+     * @see encode_days
+     * @see decode_day
+     */
+    public static Gee.Map<Calendar.DayOfWeek?, int> decode_days(Gee.Collection<int>? values) {
+        if (values == null || values.size == 0)
+            return Gee.Map.empty<Calendar.DayOfWeek?, int>();
+        
+        Gee.Map<Calendar.DayOfWeek?, int> decoded = new Gee.HashMap<Calendar.DayOfWeek?, int>();
+        foreach (int value in values) {
+            Calendar.DayOfWeek? dow;
+            int position;
+            if (decode_day(value, out dow, out position))
+                decoded.set(dow, position);
+        }
+        
+        return decoded;
+    }
+    
     private Gee.SortedSet<int> get_by_set(ByRule by_rule) {
         switch (by_rule) {
             case ByRule.SECOND:
@@ -377,8 +399,21 @@ public class RecurrenceRule : BaseObject {
     }
     
     /**
+     * Returns a read-only sorted set of BY rule settings for the specified { link ByRule}.
+     *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+     * operate according to their associated ByRule and this RRULE's { link freq}.
+     */
+    public Gee.SortedSet<int> get_by_rule(ByRule by_rule) {
+        return get_by_set(by_rule).read_only_view;
+    }
+    
+    /**
      * Replaces the existing set of values for the BY rules with the supplied values.
      *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+     * operate according to their associated ByRule and this RRULE's { link freq}.
+     *
      * Pass null or an empty Collection to clear the by-rules values.
      *
      * Use { link encode_days} when passing values for { link ByRule.DAY}.
@@ -399,6 +434,9 @@ public class RecurrenceRule : BaseObject {
     /**
      * Adds the supplied values to the existing set of values for the BY rules.
      *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+     * operate according to their associated ByRule and this RRULE's { link freq}.
+     *
      * Null or an empty Collection is a no-op.
      *
      * Use { link encode_days} when passing values for { link ByRule.DAY}.
diff --git a/src/tests/tests-quick-add-recurring.vala b/src/tests/tests-quick-add-recurring.vala
index c6ff4cd..4273a03 100644
--- a/src/tests/tests-quick-add-recurring.vala
+++ b/src/tests/tests-quick-add-recurring.vala
@@ -8,6 +8,12 @@ namespace California.Tests {
 
 private class QuickAddRecurring : UnitTest.Harness {
     public QuickAddRecurring() {
+        // ByRule.DAY encoding/decoding tests
+        add_case("encode-decode-day-every-week", encode_decode_day_every_week);
+        add_case("encode-decode-day-every-month", encode_decode_day_every_month);
+        add_case("encode-decode-days-every-week", encode_decode_days_every_week);
+        add_case("encode-decode-days-every-month", encode_decode_days_every_month);
+        
         // DAILY tests
         add_case("every-day", every_day);
         add_case("every-day-10-days", every_day_10_days);
@@ -19,6 +25,7 @@ private class QuickAddRecurring : UnitTest.Harness {
         // WEEKLY
         add_case("every-tuesday", every_tuesday);
         add_case("every-tuesday-thursday", every_tuesday_thursday);
+        add_case("every-tuesday-and-thursday", every_tuesday_and_thursday);
     }
     
     protected override void setup() throws Error {
@@ -31,6 +38,61 @@ private class QuickAddRecurring : UnitTest.Harness {
         Calendar.terminate();
     }
     
+    private bool encode_decode_day_every_week(out string? dump) throws Error {
+        int value = Component.RecurrenceRule.encode_day(Calendar.DayOfWeek.THU, 0);
+        
+        dump = "THU 0 -> %d".printf(value);
+        
+        Calendar.DayOfWeek? dow;
+        int position;
+        return Component.RecurrenceRule.decode_day(value, out dow, out position)
+            && dow != null
+            && dow.equal_to(Calendar.DayOfWeek.THU)
+            && position == 0;
+    }
+    
+    private bool encode_decode_day_every_month(out string? dump) throws Error {
+        int value = Component.RecurrenceRule.encode_day(Calendar.DayOfWeek.MON, 3);
+        
+        dump = "MON 3 -> %d".printf(value);
+        
+        Calendar.DayOfWeek? dow;
+        int position;
+        return Component.RecurrenceRule.decode_day(value, out dow, out position)
+            && dow != null
+            && dow.equal_to(Calendar.DayOfWeek.MON)
+            && position == 3;
+    }
+    
+    private bool encode_decode_days_every_week(out string? dump) throws Error {
+        Gee.Collection<int> values = Component.RecurrenceRule.encode_days(
+            iterate<Calendar.DayOfWeek?>(Calendar.DayOfWeek.TUE, 
Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0));
+        Gee.Map<Calendar.DayOfWeek?, int> dows = Component.RecurrenceRule.decode_days(values);
+        
+        dump = "values.size=%d size=%d".printf(values.size, dows.size);
+        
+        return dows.size == 2
+            && dows.contains(Calendar.DayOfWeek.TUE)
+            && dows.contains(Calendar.DayOfWeek.THU)
+            && dows[Calendar.DayOfWeek.TUE] == 0
+            && dows[Calendar.DayOfWeek.THU] == 0;
+    }
+    
+    private bool encode_decode_days_every_month(out string? dump) throws Error {
+        int iter = 1;
+        Gee.Collection<int> values = Component.RecurrenceRule.encode_days(
+            iterate<Calendar.DayOfWeek?>(Calendar.DayOfWeek.MON, 
Calendar.DayOfWeek.WED).to_hash_map_as_keys<int>(dow => iter++));
+        Gee.Map<Calendar.DayOfWeek?, int> dows = Component.RecurrenceRule.decode_days(values);
+        
+        dump = "values.size=%d size=%d".printf(values.size, dows.size);
+        
+        return dows.size == 2
+            && dows.contains(Calendar.DayOfWeek.MON)
+            && dows.contains(Calendar.DayOfWeek.WED)
+            && dows[Calendar.DayOfWeek.MON] == 1
+            && dows[Calendar.DayOfWeek.WED] == 2;
+    }
+    
     // Checks that an RRULE was generated,
     // the summary is       meeting at work
     // the location is      work
@@ -44,6 +106,7 @@ private class QuickAddRecurring : UnitTest.Harness {
         return event.rrule != null
             && event.summary == "meeting at work"
             && event.location == "work"
+            && !event.is_all_day
             && event.exact_time_span.start_exact_time.to_wall_time().equal_to(new Calendar.WallTime(10, 0, 
0));
     }
     
@@ -107,25 +170,63 @@ private class QuickAddRecurring : UnitTest.Harness {
     // WEEKLY
     //
     
+    private bool check_byrule_day(Component.Event event, Gee.Map<Calendar.DayOfWeek?, int> by_days) {
+        Gee.SortedSet<int> values = event.rrule.get_by_rule(Component.RecurrenceRule.ByRule.DAY);
+        if (values.size != by_days.size)
+            return false;
+        
+        foreach (int value in values) {
+            Calendar.DayOfWeek? dow;
+            int position;
+            if (!Component.RecurrenceRule.decode_day(value, out dow, out position))
+                return false;
+            
+            if (!by_days.has_key(dow) || by_days.get(dow) != position)
+                return false;
+        }
+        
+        return true;
+    }
+    
     private bool every_tuesday(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.TUE).to_hash_map_as_keys<int>(dow => 0);
+        
         Component.Event event;
         return basic("meeting at work at 10am every tuesday", out event, out dump)
             && event.rrule.is_weekly
             && event.rrule.interval == 1
             && !event.rrule.has_duration
-            && !event.is_all_day
-            && event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.TUE);
+            && event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.TUE)
+            && check_byrule_day(event, by_days);
     }
     
     private bool every_tuesday_thursday(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.TUE, Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0);
+        
         Component.Event event;
         return basic("meeting at work at 10am every tuesday, thursday", out event, out dump)
             && event.rrule.is_weekly
             && event.rrule.interval == 1
             && !event.rrule.has_duration
-            && !event.is_all_day
             && (event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.TUE)
-                || event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU));
+                || event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU))
+            && check_byrule_day(event, by_days);
+    }
+    
+    private bool every_tuesday_and_thursday(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.TUE, Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("meeting at work at 10am every tuesday and thursday", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && (event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.TUE)
+                || event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU))
+            && check_byrule_day(event, by_days);
     }
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]