pebble/tests/fw/ui/recognizer/test_recognizer.c
2025-01-27 11:38:16 -08:00

518 lines
20 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 "clar.h"
#include "applib/ui/recognizer/recognizer.h"
#include "applib/ui/recognizer/recognizer_impl.h"
#include "applib/ui/recognizer/recognizer_manager.h"
#include "applib/ui/recognizer/recognizer_private.h"
#include "util/size.h"
// Stubs
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_logging.h"
#include "test_recognizer_impl.h"
static bool s_manager_state_change = false;
void recognizer_manager_handle_state_change(RecognizerManager *manager, Recognizer *changed) {
s_manager_state_change = true;
}
static TestImplData s_test_impl_data;
// setup and teardown
void test_recognizer__initialize(void) {
s_test_impl_data = (TestImplData) {};
}
void test_recognizer__cleanup(void) {
}
// tests
void test_recognizer__create_with_data(void) {
int sub_data;
void *dummy = &sub_data;
RecognizerImpl s_test_impl = {
.handle_touch_event = dummy,
.cancel = dummy,
.reset = dummy
};
Recognizer *r = recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
sizeof(s_test_impl_data), dummy,
&sub_data);
cl_assert(r != NULL);
cl_assert_equal_p(r->impl, &s_test_impl);
cl_assert_equal_m(r->impl_data, &s_test_impl_data, sizeof(s_test_impl_data));
cl_assert_equal_p(r->subscriber.event, dummy);
cl_assert_equal_p(r->subscriber.data, &sub_data);
cl_assert_equal_i(r->state, RecognizerState_Possible);
cl_assert_equal_i(r->flags, 0);
cl_assert_equal_p(r->simultaneous_with_cb, NULL);
cl_assert_equal_p(r->fail_after, NULL);
cl_assert_passert(recognizer_create_with_data(NULL, &s_test_impl_data,
sizeof(s_test_impl_data), dummy,
NULL));
cl_assert_passert(recognizer_create_with_data(&s_test_impl, NULL,
sizeof(s_test_impl_data), dummy,
NULL));
cl_assert_passert(recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
0, dummy, NULL));
cl_assert_equal_p(NULL, recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
sizeof(s_test_impl_data), NULL, NULL));
s_test_impl.handle_touch_event = NULL;
cl_assert_passert(recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
sizeof(s_test_impl_data), dummy,
NULL));
s_test_impl.handle_touch_event = dummy;
s_test_impl.reset = NULL;
cl_assert_passert(recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
sizeof(s_test_impl_data), dummy,
NULL));
s_test_impl.reset = dummy;
s_test_impl.cancel = NULL;
cl_assert_passert(recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
sizeof(s_test_impl_data), dummy,
NULL));
recognizer_destroy(r);
}
void test_recognizer__transition_state(void) {
RecognizerEvent event_type = -1;
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &event_type);
// Test that manager state change handler is called when not called from a touch event handler
recognizer_transition_state(r, RecognizerState_Failed);
cl_assert_equal_i(r->state, RecognizerState_Failed);
cl_assert(s_manager_state_change);
cl_assert_equal_i(event_type, -1);
r->state = RecognizerState_Possible;
s_manager_state_change = false;
recognizer_transition_state(r, RecognizerState_Completed);
cl_assert_equal_i(r->state, RecognizerState_Completed);
cl_assert(s_manager_state_change);
cl_assert_equal_i(event_type, RecognizerEvent_Completed);
s_manager_state_change = false;
r->handling_touch_event = true;
r->state = RecognizerState_Possible;
event_type = -1;
recognizer_transition_state(r, RecognizerState_Failed);
cl_assert(!s_manager_state_change);
cl_assert_equal_i(r->state, RecognizerState_Failed);
cl_assert_equal_i(event_type, -1);
// Test that invalid state transitions get caught by asserts
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Completed));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
r->state = RecognizerState_Possible;
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Updated));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Cancelled));
recognizer_transition_state(r, RecognizerState_Started);
cl_assert_equal_i(r->state, RecognizerState_Started);
cl_assert_equal_i(event_type, RecognizerEvent_Started);
cl_assert(!s_manager_state_change);
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Failed));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
recognizer_transition_state(r, RecognizerState_Updated);
cl_assert_equal_i(r->state, RecognizerState_Updated);
cl_assert_equal_i(event_type, RecognizerEvent_Updated);
cl_assert(!s_manager_state_change);
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Failed));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
event_type = -1;
recognizer_transition_state(r, RecognizerState_Updated);
cl_assert_equal_i(event_type, RecognizerEvent_Updated);
cl_assert_equal_i(r->state, RecognizerState_Updated);
cl_assert(!s_manager_state_change);
recognizer_transition_state(r, RecognizerState_Completed);
cl_assert_equal_i(event_type, RecognizerEvent_Completed);
cl_assert_equal_i(r->state, RecognizerState_Completed);
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Failed));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Updated));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Cancelled));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Completed));
r->state = RecognizerState_Updated;
recognizer_transition_state(r, RecognizerState_Cancelled);
cl_assert_equal_i(event_type, RecognizerEvent_Cancelled);
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
cl_assert(!s_manager_state_change);
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Failed));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Updated));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Cancelled));
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Completed));
r->state = RecognizerState_Started;
event_type = -1;
recognizer_transition_state(r, RecognizerState_Cancelled);
cl_assert_equal_i(event_type, RecognizerEvent_Cancelled);
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
cl_assert(!s_manager_state_change);
r->state = RecognizerState_Started;
recognizer_transition_state(r, RecognizerState_Completed);
cl_assert_equal_i(event_type, RecognizerEvent_Completed);
cl_assert_equal_i(r->state, RecognizerState_Completed);
cl_assert(!s_manager_state_change);
r->state = RecognizerState_Possible;
recognizer_transition_state(r, RecognizerState_Completed);
cl_assert_equal_i(r->state, RecognizerState_Completed);
cl_assert(!s_manager_state_change);
}
void test_recognizer__set_failed(void) {
bool failed = false;
s_test_impl_data.failed = &failed;
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, NULL);
recognizer_set_failed(r);
cl_assert_equal_i(r->state, RecognizerState_Failed);
cl_assert(failed);
// Failed -> Failed invalid transition
cl_assert_passert(recognizer_set_failed(r));
// (!Possible) -> Failed invalid transition
r->state = RecognizerState_Started;
cl_assert_passert(recognizer_set_failed(r));
r->state = RecognizerState_Completed;
cl_assert_passert(recognizer_set_failed(r));
}
static void prv_sub_destroy(const Recognizer *r) {
bool *destroyed = recognizer_get_user_data(r);
*destroyed = true;
}
void test_recognizer__destroy(void) {
bool impl_destroyed = false;
s_test_impl_data.destroyed = &impl_destroyed;
bool sub_destroyed = false;
Recognizer *r = test_recognizer_create(&s_test_impl_data, &sub_destroyed);
test_recognizer_enable_on_destroy();
recognizer_set_on_destroy(r, prv_sub_destroy);
// can't destroy a recognizer if it is owned
r->is_owned = true;
recognizer_destroy(r);
cl_assert_equal_b(impl_destroyed, false);
cl_assert_equal_b(sub_destroyed, false);
r->is_owned = false;
recognizer_destroy(r);
cl_assert_equal_b(impl_destroyed, true);
cl_assert_equal_b(sub_destroyed, true);
}
void test_recognizer__reset(void) {
bool reset = false;
bool cancelled = false;
s_test_impl_data.reset = &reset;
s_test_impl_data.cancelled = &cancelled;
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, NULL);
recognizer_reset(r);
cl_assert_equal_i(r->state, RecognizerState_Possible);
cl_assert_equal_b(reset, true);
cl_assert_equal_b(cancelled, false);
reset = false;
r->state = RecognizerState_Failed;
recognizer_reset(r);
cl_assert_equal_i(r->state, RecognizerState_Possible);
cl_assert_equal_b(reset, true);
cl_assert_equal_b(cancelled, false);
reset = false;
r->state = RecognizerState_Cancelled;
recognizer_reset(r);
cl_assert_equal_i(r->state, RecognizerState_Possible);
cl_assert_equal_b(reset, true);
cl_assert_equal_b(cancelled, false);
reset = false;
r->state = RecognizerState_Completed;
recognizer_reset(r);
cl_assert_equal_i(r->state, RecognizerState_Possible);
cl_assert_equal_b(reset, true);
cl_assert_equal_b(cancelled, false);
reset = false;
r->state = RecognizerState_Started;
recognizer_reset(r);
cl_assert_equal_i(r->state, RecognizerState_Possible);
cl_assert_equal_b(reset, true);
cl_assert_equal_b(cancelled, true);
reset = false;
cancelled = false;
r->state = RecognizerState_Updated;
recognizer_reset(r);
cl_assert_equal_i(r->state, RecognizerState_Possible);
cl_assert_equal_b(reset, true);
cl_assert_equal_b(cancelled, true);
}
void test_recognizer__cancel(void) {
bool cancelled = false;
s_test_impl_data.cancelled = &cancelled;
RecognizerEvent rec_event = -1;
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &rec_event);
recognizer_cancel(r);
cl_assert_equal_i(r->state, RecognizerState_Possible);
cl_assert_equal_b(cancelled, false);
cl_assert_equal_i(rec_event, -1);
r->state = RecognizerState_Failed;
recognizer_cancel(r);
cl_assert_equal_i(r->state, RecognizerState_Failed);
cl_assert_equal_b(cancelled, false);
cl_assert_equal_i(rec_event, -1);
r->state = RecognizerState_Cancelled;
recognizer_cancel(r);
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
cl_assert_equal_b(cancelled, false);
cl_assert_equal_i(rec_event, -1);
r->state = RecognizerState_Completed;
recognizer_cancel(r);
cl_assert_equal_i(r->state, RecognizerState_Completed);
cl_assert_equal_b(cancelled, false);
cl_assert_equal_i(rec_event, -1);
r->state = RecognizerState_Started;
recognizer_cancel(r);
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
cl_assert_equal_b(cancelled, true);
cl_assert_equal_i(rec_event, RecognizerEvent_Cancelled);
cancelled = false;
r->state = RecognizerState_Updated;
rec_event = -1;
recognizer_cancel(r);
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
cl_assert_equal_b(cancelled, true);
cl_assert_equal_i(rec_event, RecognizerEvent_Cancelled);
}
void test_recognizer__handle_touch_events(void) {
RecognizerEvent rec_event = -1;
TouchEvent last_touch_event = { .type = TouchEvent_Liftoff };
RecognizerState new_state;
bool updated = false;
s_test_impl_data.last_touch_event = &last_touch_event;
s_test_impl_data.new_state = &new_state;
s_test_impl_data.updated = &updated;
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &rec_event);
new_state = RecognizerState_Possible;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
cl_assert_equal_i(last_touch_event.type, TouchEvent_Touchdown);
cl_assert_equal_b(updated, false);
new_state = RecognizerState_Completed;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Liftoff });
cl_assert_equal_i(last_touch_event.type, TouchEvent_Liftoff);
cl_assert_equal_b(updated, true);
cl_assert_equal_i(rec_event, RecognizerEvent_Completed);
r->state = RecognizerState_Possible;
updated = false;
new_state = RecognizerState_Started;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
cl_assert_equal_i(last_touch_event.type, TouchEvent_Touchdown);
cl_assert_equal_b(updated, true);
cl_assert_equal_i(rec_event, RecognizerEvent_Started);
updated = false;
new_state = RecognizerState_Updated;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_PositionUpdate });
cl_assert_equal_i(last_touch_event.type, TouchEvent_PositionUpdate);
cl_assert_equal_b(updated, true);
cl_assert_equal_i(rec_event, RecognizerEvent_Updated);
updated = false;
new_state = RecognizerState_Cancelled;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Liftoff });
cl_assert_equal_i(last_touch_event.type, TouchEvent_Liftoff);
cl_assert_equal_b(updated, true);
cl_assert_equal_i(rec_event, RecognizerEvent_Cancelled);
// Should not pass touch events to recognizers that are not active
cl_assert_passert(recognizer_handle_touch_event(r, &(TouchEvent) {}));
// Should not pass null touch events
r->state = RecognizerState_Possible;
cl_assert_passert(recognizer_handle_touch_event(r, NULL));
}
void test_recognizer__handle_touch_events_fail_after(void) {
RecognizerEvent rec_event = -1;
RecognizerState new_state;
bool updated = false;
TouchEvent last_touch_event = { .type = TouchEvent_Liftoff };
s_test_impl_data.new_state = &new_state;
s_test_impl_data.updated = &updated;
s_test_impl_data.last_touch_event = &last_touch_event;
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &rec_event);
NEW_RECOGNIZER(fail) = test_recognizer_create(&s_test_impl_data, NULL);
recognizer_set_fail_after(r, fail);
new_state = RecognizerState_Completed;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
cl_assert_equal_i(last_touch_event.type, TouchEvent_Liftoff);
cl_assert_equal_b(updated, false);
fail->state = RecognizerState_Failed;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
cl_assert_equal_i(last_touch_event.type, TouchEvent_Touchdown);
cl_assert_equal_b(updated, true);
cl_assert_equal_i(rec_event, RecognizerEvent_Completed);
}
static bool prv_filter(const Recognizer *recognizer, const TouchEvent *touch_event) {
bool *allow = recognizer_get_user_data(recognizer);
return *allow;
}
void test_recognizer__handle_touch_events_filter_cb(void) {
RecognizerState new_state;
bool updated = false;
TouchEvent last_touch_event = { .type = TouchEvent_Liftoff };
s_test_impl_data.new_state = &new_state;
s_test_impl_data.updated = &updated;
s_test_impl_data.last_touch_event = &last_touch_event;
bool allow = false;
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &allow);
recognizer_set_touch_filter(r, prv_filter);
new_state = RecognizerState_Completed;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
cl_assert_equal_i(last_touch_event.type, TouchEvent_Liftoff);
cl_assert_equal_b(updated, false);
allow = true;
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
cl_assert_equal_i(last_touch_event.type, TouchEvent_Touchdown);
cl_assert_equal_b(updated, true);
cl_assert_equal_i(r->state, new_state);
}
bool s_simultaneous = false;
static bool prv_simultaneous_with_cb(const Recognizer *recognizer,
const Recognizer *simultaneous_with) {
return s_simultaneous;
}
void test_recognizer__set_simultaneous_with(void) {
NEW_RECOGNIZER(r1) = test_recognizer_create(&s_test_impl_data, NULL);
NEW_RECOGNIZER(r2) = test_recognizer_create(&s_test_impl_data, NULL);
s_simultaneous = false;
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, NULL));
cl_assert(!recognizer_should_evaluate_simultaneously(r1, NULL));
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, r2));
cl_assert(!recognizer_should_evaluate_simultaneously(r1, r2));
recognizer_set_simultaneous_with(r1, prv_simultaneous_with_cb);
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, NULL));
cl_assert(!recognizer_should_evaluate_simultaneously(r1, NULL));
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, r2));
cl_assert(!recognizer_should_evaluate_simultaneously(r1, r2));
s_simultaneous = true;
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, NULL));
cl_assert(!recognizer_should_evaluate_simultaneously(r1, NULL));
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, r2));
cl_assert(recognizer_should_evaluate_simultaneously(r1, r2));
}
void test_recognizer__add_remove_list(void) {
RecognizerList list = { NULL };
NEW_RECOGNIZER(r1) = test_recognizer_create(&s_test_impl_data, NULL);
NEW_RECOGNIZER(r2) = test_recognizer_create(&s_test_impl_data, NULL);
recognizer_add_to_list(r1, &list);
recognizer_add_to_list(r2, &list);
cl_assert_equal_i(list_count(list.node), 2);
cl_assert(list_contains(list.node, &r1->node));
cl_assert(list_contains(list.node, &r2->node));
cl_assert(r1->is_owned);
cl_assert(r2->is_owned);
recognizer_remove_from_list(r1, &list);
cl_assert(!list_contains(list.node, &r1->node));
cl_assert(!r1->is_owned);
recognizer_remove_from_list(r1, &list);
cl_assert(!r1->is_owned);
}
static int s_list_idx = 0;
static bool prv_list_iterator(Recognizer *recognizer, void *context) {
const char *names[] = { "R1", "R2", "R3" };
cl_assert(s_list_idx < ARRAY_LENGTH(names));
char s[20];
snprintf(s, sizeof(s), "%s != %s", recognizer->subscriber.data, names[s_list_idx]);
cl_assert_(strcmp(recognizer->subscriber.data, names[s_list_idx++]) == 0, s);
return (s_list_idx < *((int *)context));
}
void test_recognizer__list_iterate(void) {
NEW_RECOGNIZER(r1) = test_recognizer_create(&s_test_impl_data, "R1");
NEW_RECOGNIZER(r2) = test_recognizer_create(&s_test_impl_data, "R2");
NEW_RECOGNIZER(r3) = test_recognizer_create(&s_test_impl_data, "R3");
RecognizerList list = { NULL };
recognizer_add_to_list(r1, &list);
recognizer_add_to_list(r2, &list);
recognizer_add_to_list(r3, &list);
s_list_idx = 0;
int end = 4;
recognizer_list_iterate(&list, prv_list_iterator, &end);
cl_assert_equal_i(s_list_idx, 3);
end = 2;
s_list_idx = 0;
recognizer_list_iterate(&list, prv_list_iterator, &end);
cl_assert_equal_i(s_list_idx, 2);
}