pebble/tests/fw/services/timeline/test_timeline_peek_event.c
2025-01-27 11:38:16 -08:00

786 lines
30 KiB
C

/*
* Copyright 2024 Google LLC
*
* 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.
*/
#include "kernel/events.h"
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/timeline/event.h"
#include "services/normal/timeline/peek.h"
#include "services/normal/timeline/timeline.h"
#include "system/logging.h"
#include "clar.h"
#include "pebble_asserts.h"
// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_ancs.h"
#include "stubs_ancs_notifications.h"
#include "stubs_app_cache.h"
#include "stubs_app_install_manager.h"
#include "stubs_app_manager.h"
#include "stubs_blob_db.h"
#include "stubs_blob_db_sync.h"
#include "stubs_blob_db_sync_util.h"
#include "stubs_event_loop.h"
#include "stubs_event_service_client.h"
#include "stubs_hexdump.h"
#include "stubs_i18n.h"
#include "stubs_layout_layer.h"
#include "stubs_logging.h"
#include "stubs_modal_manager.h"
#include "stubs_mutex.h"
#include "stubs_notification_storage.h"
#include "stubs_notifications.h"
#include "stubs_passert.h"
#include "stubs_phone_call_util.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_regular_timer.h"
#include "stubs_reminder_db.h"
#include "stubs_session.h"
#include "stubs_sleep.h"
#include "stubs_system_task.h"
#include "stubs_task_watchdog.h"
#include "stubs_text_layer_flow.h"
#include "stubs_timeline.h"
#include "stubs_timeline_peek.h"
#include "stubs_timeline_pin_window.h"
#include "stubs_window_stack.h"
// Fakes
////////////////////////////////////////////////////////////////
#include "fake_new_timer.h"
#include "fake_pbl_malloc.h"
#include "fake_pebble_tasks.h"
#include "fake_rtc.h"
#include "fake_spi_flash.h"
#include "fake_settings_file.h"
#include "fake_events.h"
bool calendar_layout_verify(bool existing_attributes[]) {
return true;
}
bool weather_layout_verify(bool existing_attributes[]) {
return true;
}
const TimelineEventImpl *calendar_get_event_service(void) {
return NULL;
}
// Helpers
////////////////////////////////////////////////////////////////
typedef struct PeekTestData {
PebbleTimelinePeekEvent last_peek_event;
unsigned int num_peek_events;
} PeekTestData;
static PeekTestData s_data;
static PebbleTimelinePeekEvent prv_get_peek_event(void) {
return s_data.last_peek_event;
}
static void prv_event_handler(PebbleEvent *event) {
if (event->type == PEBBLE_TIMELINE_PEEK_EVENT) {
s_data.last_peek_event = event->timeline_peek;
s_data.num_peek_events++;
}
}
// Fake pins
////////////////////////////////////////////////////////////////
static Attribute title_attr = {
.id = AttributeIdTitle,
.cstring = "title",
};
static TimelineItem s_item1 = {
.header = {
.id = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 1 * SECONDS_PER_MINUTE,
.duration = 15,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem s_item2 = {
.header = {
.id = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 5 * SECONDS_PER_MINUTE,
.duration = 20,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem s_item3 = {
.header = {
.id = { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 9 * SECONDS_PER_MINUTE,
.duration = 5,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem s_future_item = {
.header = {
.id = { 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 100 * SECONDS_PER_MINUTE,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem s_short_future_item = {
.header = {
.id = { 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 100 * SECONDS_PER_MINUTE,
.duration = 5,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// not a calendar pin
static TimelineItem s_weather_item = {
.header = {
.id = { 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 10 * SECONDS_PER_MINUTE,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdWeather,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// add day pin
static TimelineItem s_all_day_item = {
.header = {
.id = { 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 100 * SECONDS_PER_MINUTE,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = true,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// 0-duration event
static TimelineItem s_point_item = {
.header = {
.id = { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 20 * SECONDS_PER_MINUTE,
.duration = 0,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdWeather,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// recurring calendar event 1
static TimelineItem s_recurring_calendar_item1 = {
.header = {
.id = { 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 50 * SECONDS_PER_MINUTE - SECONDS_PER_DAY,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// recurring calendar event 2
static TimelineItem s_recurring_calendar_item2 = {
.header = {
.id = { 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 50 * SECONDS_PER_MINUTE,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// recurring calendar event 3
static TimelineItem s_recurring_calendar_item3 = {
.header = {
.id = { 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 50 * SECONDS_PER_MINUTE + SECONDS_PER_DAY,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// back-to-back calendar event 1
static TimelineItem s_back_to_back_calendar_item1 = {
.header = {
.id = { 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 60 * SECONDS_PER_MINUTE,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// back-to-back calendar event 2
static TimelineItem s_back_to_back_calendar_item2 = {
.header = {
.id = { 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 90 * SECONDS_PER_MINUTE,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// Setup
////////////////////////////////////////////////////////////////
void test_timeline_peek_event__initialize(void) {
s_data = (PeekTestData){};
rtc_set_time(0);
fake_event_init();
fake_event_set_callback(prv_event_handler);
pin_db_init();
timeline_event_init();
}
void test_timeline_peek_event__cleanup(void) {
timeline_peek_set_show_before_time(TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S);
timeline_event_deinit();
stub_new_timer_cleanup();
fake_settings_file_reset();
}
// Tests
////////////////////////////////////////////////////////////////
typedef struct AddEventParams {
TimelineItem *item;
} AddEventParams;
#define ADD_EVENT(...) ({ \
AddEventParams params = { __VA_ARGS__ }; \
cl_assert(timeline_add(params.item)); \
timeline_event_handle_blobdb_event(); \
params.item; \
})
typedef struct CreateEventParams {
uint8_t id;
LayoutId layout;
time_t timestamp;
uint16_t duration;
bool all_day;
bool persistent;
} CreateEventParams;
#define DEFINE_EVENT(...) ({ \
CreateEventParams params = { __VA_ARGS__ }; \
TimelineItem item = { \
.header = { \
.type = TimelineItemTypePin, \
.id = { params.id, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00 , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, \
.layout = params.layout ?: LayoutIdCalendar, \
.persistent = params.persistent ? 1 : 0, \
.timestamp = params.timestamp, \
.all_day = params.all_day, \
.duration = params.duration, \
}, \
.attr_list = { \
.num_attributes = 1, \
.attributes = &title_attr, \
}, \
}; \
ADD_EVENT( .item = &item ); \
item; \
})
typedef struct CheckNoEventsParams {
unsigned int count;
bool is_future_empty;
} CheckNoEventsParams;
#define CHECK_NO_EVENTS(...) ({ \
CheckNoEventsParams params = { __VA_ARGS__ }; \
PebbleTimelinePeekEvent peek = prv_get_peek_event(); \
cl_assert_equal_i(s_data.num_peek_events, params.count); \
cl_assert_equal_uuid(peek.item_id ? *peek.item_id : UUID_INVALID, UUID_INVALID); \
cl_assert_equal_i(peek.time_type, TimelinePeekTimeType_None); \
cl_assert_equal_i(peek.num_concurrent, 0); \
cl_assert_equal_b(peek.is_future_empty, params.is_future_empty); \
cl_assert_equal_i(stub_new_timer_get_next(), TIMER_INVALID_ID); \
peek; \
})
typedef struct CheckEventParams {
unsigned int count;
Uuid item_id;
unsigned int num_concurrent;
unsigned int timeout_ms;
TimelinePeekTimeType time_type;
bool is_first_event;
} CheckEventParams;
#define CHECK_EVENT(...) ({ \
CheckEventParams params = { __VA_ARGS__ }; \
PebbleTimelinePeekEvent peek = prv_get_peek_event(); \
cl_assert_equal_i(s_data.num_peek_events, params.count); \
cl_assert_equal_uuid(peek.item_id ? *peek.item_id : UUID_INVALID, params.item_id); \
cl_assert_equal_i(peek.time_type, params.time_type); \
cl_assert_equal_i(peek.num_concurrent, params.num_concurrent); \
cl_assert_equal_b(peek.is_first_event, params.is_first_event); \
cl_assert_equal_b(peek.is_future_empty, false); \
const TimerID timer_id = stub_new_timer_get_next(); \
cl_assert(timer_id != TIMER_INVALID_ID); \
cl_assert_equal_i(stub_new_timer_timeout(timer_id), params.timeout_ms); \
peek; \
})
static void prv_invoke_timer(unsigned int timeout_s) {
fake_rtc_increment_time(timeout_s);
stub_new_timer_invoke(1 /* num_invoke */);
}
void test_timeline_peek_event__no_events(void) {
CHECK_NO_EVENTS( .count = 1, .is_future_empty = true );
}
void test_timeline_peek_event__calendar_event(void) {
ADD_EVENT( .item = &s_item1 );
CHECK_EVENT( .count = 2, .item_id = s_item1.header.id, .num_concurrent = 0,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
}
void test_timeline_peek_event__calendar_event_all_day(void) {
ADD_EVENT( .item = &s_all_day_item );
CHECK_NO_EVENTS( .count = 2, .is_future_empty = true );
}
void test_timeline_peek_event__weather_event(void) {
ADD_EVENT( .item = &s_weather_item );
CHECK_EVENT( .count = 2, .item_id = s_weather_item.header.id, .num_concurrent = 0,
.timeout_ms = s_weather_item.header.timestamp * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
}
void test_timeline_peek_event__concurrent_count_and_priority(void) {
// Test that num_concurrent increases accordingly
// Also test that upcoming items take priority
ADD_EVENT( .item = &s_item1 );
CHECK_EVENT( .count = 2, .item_id = s_item1.header.id, .num_concurrent = 0,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
ADD_EVENT( .item = &s_item2 );
CHECK_EVENT( .count = 3, .item_id = s_item2.header.id, .num_concurrent = 1,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart );
ADD_EVENT( .item = &s_item3 );
CHECK_EVENT( .count = 4, .item_id = s_item3.header.id, .num_concurrent = 2,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart );
// The future item is too far to increase the concurrent count
ADD_EVENT( .item = &s_future_item );
CHECK_EVENT( .count = 5, .item_id = s_item3.header.id, .num_concurrent = 2,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart );
}
void test_timeline_peek_event__before_upcoming_event(void) {
// Check that the event is about an upcoming item
ADD_EVENT( .item = &s_future_item );
CHECK_EVENT( .count = 2, .item_id = s_future_item.header.id, .num_concurrent = 0,
.timeout_ms =
((s_future_item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S) *
MS_PER_SECOND),
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
}
void test_timeline_peek_event__before_upcoming_event_custom_5min(void) {
// Check that the event is about an upcoming item at a custom 5min timeout
const unsigned int show_before_time_s = 5 * SECONDS_PER_MINUTE;
timeline_peek_set_show_before_time(show_before_time_s);
ADD_EVENT( .item = &s_future_item );
CHECK_EVENT( .count = 3, .item_id = s_future_item.header.id, .num_concurrent = 0,
.timeout_ms = ((s_future_item.header.timestamp - show_before_time_s) *
MS_PER_SECOND),
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
}
void test_timeline_peek_event__before_event_starts(void) {
// Check that the event is about an item that is about to start
rtc_set_time(s_future_item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S / 2);
ADD_EVENT( .item = &s_future_item );
CHECK_EVENT( .count = 2, .item_id = s_future_item.header.id, .num_concurrent = 0,
.timeout_ms = (TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S * MS_PER_SECOND) / 2,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
}
void test_timeline_peek_event__after_event_starts(void) {
// Check that the event is about an item about to pass the hide time
rtc_set_time(5 * SECONDS_PER_MINUTE);
ADD_EVENT( .item = &s_item1 );
CHECK_EVENT( .count = 2, .item_id = s_item1.header.id, .num_concurrent = 0,
.timeout_ms = ((TIMELINE_PEEK_HIDE_AFTER_TIME_S -
(rtc_get_time() - s_item1.header.timestamp)) * MS_PER_SECOND),
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
}
void test_timeline_peek_event__after_event_starts_short_event(void) {
// Check that for a short event, the timeout is the end of the item instead
rtc_set_time(10 * SECONDS_PER_MINUTE);
ADD_EVENT( .item = &s_item3 );
CHECK_EVENT( .count = 2, .item_id = s_item3.header.id, .num_concurrent = 0,
.timeout_ms = 4 * SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
}
void test_timeline_peek_event__after_event_passed_hide_time(void) {
// Check that there is no event if the last item passed the hide time
rtc_set_time(15 * SECONDS_PER_MINUTE);
ADD_EVENT( .item = &s_item2 );
CHECK_NO_EVENTS( .count = 2 );
}
void test_timeline_peek_event__after_event_passed_completely(void) {
rtc_set_time(30 * SECONDS_PER_MINUTE);
ADD_EVENT( .item = &s_item2 );
CHECK_NO_EVENTS( .count = 2, .is_future_empty = true );
}
void test_timeline_peek_event__dismiss_event(void) {
// Check that dismissing the last event causes no events to peek
TimelineItem *item = &s_future_item;
rtc_set_time(item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S / 2);
ADD_EVENT( .item = item );
CHECK_EVENT( .count = 2, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = (TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S * MS_PER_SECOND) / 2,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
// Simulate a timeline peek dismiss
cl_must_pass(pin_db_set_status_bits(&item->header.id, TimelineItemStatusDismissed));
timeline_event_refresh();
CHECK_NO_EVENTS( .count = 3 );
}
void test_timeline_peek_event__first_event_with_past_event(void) {
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = 20 * SECONDS_PER_MINUTE, .duration = 70 );
TimelineItem UNUSED item2 =
DEFINE_EVENT( .id = 0x02, .timestamp = -50 * SECONDS_PER_MINUTE, .duration = 30 );
unsigned int timeout_s = item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
}
void test_timeline_peek_event__first_event_with_all_day_event_before(void) {
// All day events show up if no timed event has yet passed
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = 20 * SECONDS_PER_MINUTE, .duration = 70 );
TimelineItem UNUSED item2 =
DEFINE_EVENT( .id = 0x02, .timestamp = 0, .duration = MINUTES_PER_DAY, .all_day = true );
unsigned int timeout_s = item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext );
}
void test_timeline_peek_event__first_event_with_all_day_event_after(void) {
// After a timed event has passed, all day events no longer show up for the day
rtc_set_time(SECONDS_PER_HOUR);
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = SECONDS_PER_HOUR + 20 * SECONDS_PER_MINUTE,
.duration = 70 );
TimelineItem UNUSED item2 =
DEFINE_EVENT( .id = 0x02, .timestamp = 0, .duration = MINUTES_PER_DAY, .all_day = true );
TimelineItem UNUSED item3 =
DEFINE_EVENT( .id = 0x03, .timestamp = 0, .duration = 10 );
unsigned int timeout_s = 600;
CHECK_EVENT( .count = 4, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
}
void test_timeline_peek_event__one_event_lifecycle(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted, None
TimelineItem *item = &s_future_item;
ADD_EVENT( .item = item );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 2, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 5 );
}
void test_timeline_peek_event__one_short_event_lifecycle(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted, None
TimelineItem *item = &s_short_future_item;
ADD_EVENT( .item = item );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 2, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = item->header.duration * SECONDS_PER_MINUTE;
CHECK_EVENT( .count = 4, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 5 );
}
void test_timeline_peek_event__0_duration_event_lifecycle(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted, None
TimelineItem *item = &s_point_item;
ADD_EVENT( .item = item );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 2, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 4 );
}
void test_timeline_peek_event__one_recurring_event_lifecycle(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted
TimelineItem *item = &s_recurring_calendar_item2;
ADD_EVENT( .item = &s_recurring_calendar_item1 );
ADD_EVENT( .item = item );
ADD_EVENT( .item = &s_recurring_calendar_item3 );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 5, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 6, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = (SECONDS_PER_DAY - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S -
TIMELINE_PEEK_HIDE_AFTER_TIME_S);
CHECK_EVENT( .count = 7, .item_id = s_recurring_calendar_item3.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext );
}
void test_timeline_peek_event__two_back_to_back_events(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted
TimelineItem *item = &s_back_to_back_calendar_item1;
ADD_EVENT( .item = item );
ADD_EVENT( .item = &s_back_to_back_calendar_item2 );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 5, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
item = &s_back_to_back_calendar_item2;
CHECK_EVENT( .count = 6, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext );
}
void test_timeline_peek_event__one_persistent_event_lifecycle(void) {
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = 20 * SECONDS_PER_MINUTE, .duration = 30,
.persistent = true );
unsigned int timeout_s = item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 2, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = item.header.duration * SECONDS_PER_MINUTE - TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 5, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 6, .is_future_empty = true );
}
void test_timeline_peek_event__upcoming_priotized_over_persistent_event_lifecycle(void) {
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = 20 * SECONDS_PER_MINUTE, .duration = 70,
.persistent = true );
TimelineItem item2 =
DEFINE_EVENT( .id = 0x02, .timestamp = 50 * SECONDS_PER_MINUTE, .duration = 30 );
unsigned int timeout_s = item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 5, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = 10 * SECONDS_PER_MINUTE; // time until the next event
CHECK_EVENT( .count = 6, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 7, .item_id = item2.header.id, .num_concurrent = 1,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 8, .item_id = item2.header.id, .num_concurrent = 1,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = 30 * SECONDS_PER_MINUTE; // time until persistent event ends
CHECK_EVENT( .count = 9, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 10, .is_future_empty = true );
}