mirror of
https://github.com/google/pebble.git
synced 2025-03-21 11:21:21 +00:00
768 lines
28 KiB
C
768 lines
28 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 "test_jerry_port_common.h"
|
|
#include "test_rocky_common.h"
|
|
|
|
#include "applib/graphics/gtypes.h"
|
|
#include "applib/rockyjs/api/rocky_api.h"
|
|
#include "applib/rockyjs/api/rocky_api_global.h"
|
|
#include "applib/rockyjs/api/rocky_api_graphics.h"
|
|
#include "applib/rockyjs/api/rocky_api_graphics_text.h"
|
|
#include "applib/rockyjs/pbl_jerry_port.h"
|
|
#include "util/trig.h"
|
|
|
|
// Standard
|
|
#include "string.h"
|
|
|
|
// Fakes
|
|
#include "fake_app_timer.h"
|
|
#include "fake_time.h"
|
|
|
|
// Stubs
|
|
#include "stubs_app_manager.h"
|
|
#include "stubs_app_state.h"
|
|
#include "stubs_logging.h"
|
|
#include "stubs_passert.h"
|
|
#include "stubs_pbl_malloc.h"
|
|
#include "stubs_resources.h"
|
|
#include "stubs_sleep.h"
|
|
#include "stubs_serial.h"
|
|
#include "stubs_syscalls.h"
|
|
#include "stubs_sys_exit.h"
|
|
|
|
size_t heap_bytes_free(void) {
|
|
return 123456;
|
|
}
|
|
|
|
static Window s_app_window_stack_get_top_window;
|
|
Window *app_window_stack_get_top_window() {
|
|
return &s_app_window_stack_get_top_window;
|
|
}
|
|
|
|
void rocky_api_graphics_path2d_add_canvas_methods(jerry_value_t obj) {}
|
|
void rocky_api_graphics_path2d_cleanup(void) {}
|
|
void rocky_api_graphics_path2d_reset_state(void) {}
|
|
|
|
GContext s_context;
|
|
|
|
// mocks
|
|
static MockCallRecordings s_graphics_context_set_fill_color;
|
|
void graphics_context_set_fill_color(GContext* ctx, GColor color) {
|
|
record_mock_call(s_graphics_context_set_fill_color) {
|
|
.ctx = ctx, .color = color,
|
|
};
|
|
ctx->draw_state.fill_color = color;
|
|
}
|
|
|
|
static MockCallRecordings s_graphics_context_set_stroke_color;
|
|
void graphics_context_set_stroke_color(GContext* ctx, GColor color) {
|
|
record_mock_call(s_graphics_context_set_stroke_color) {
|
|
.ctx = ctx, .color = color,
|
|
};
|
|
ctx->draw_state.stroke_color = color;
|
|
}
|
|
|
|
static MockCallRecordings s_graphics_context_set_stroke_width;
|
|
void graphics_context_set_stroke_width(GContext* ctx, uint8_t stroke_width) {
|
|
record_mock_call(s_graphics_context_set_stroke_width) {
|
|
.ctx = ctx, .width = stroke_width,
|
|
};
|
|
ctx->draw_state.stroke_width = stroke_width;
|
|
}
|
|
|
|
static MockCallRecordings s_graphics_fill_rect;
|
|
static GColor s_graphics_fill_rect__color;
|
|
void graphics_fill_rect(GContext *ctx, const GRect *rect) {
|
|
s_graphics_fill_rect__color = s_context.draw_state.fill_color;
|
|
record_mock_call(s_graphics_fill_rect) {
|
|
.ctx = ctx, .rect = *rect,
|
|
};
|
|
}
|
|
|
|
static MockCallRecordings s_graphics_draw_rect_precise;
|
|
void graphics_draw_rect_precise(GContext *ctx, const GRectPrecise *rect) {
|
|
record_mock_call(s_graphics_draw_rect_precise) {
|
|
.ctx = ctx, .prect = *rect,
|
|
};
|
|
}
|
|
|
|
static MockCallRecordings s_graphics_fill_radial_precise_internal;
|
|
void graphics_fill_radial_precise_internal(GContext *ctx, GPointPrecise center,
|
|
Fixed_S16_3 radius_inner, Fixed_S16_3 radius_outer,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
record_mock_call(s_graphics_fill_radial_precise_internal) {
|
|
.ctx = ctx,
|
|
.fill_radial_precise.center = center,
|
|
.fill_radial_precise.radius_inner = radius_inner,
|
|
.fill_radial_precise.radius_outer = radius_outer,
|
|
.fill_radial_precise.angle_start = angle_start,
|
|
.fill_radial_precise.angle_end = angle_end,
|
|
};
|
|
}
|
|
|
|
|
|
void graphics_fill_round_rect_by_value(GContext *ctx, GRect rect, uint16_t corner_radius,
|
|
GCornerMask corner_mask) {}
|
|
static MockCallRecordings s_layer_mark_dirty;
|
|
void layer_mark_dirty(Layer *layer) {
|
|
record_mock_call(s_layer_mark_dirty){
|
|
.layer = layer
|
|
};
|
|
}
|
|
|
|
static MockCallRecordings s_fonts_get_system_font;
|
|
GFont s_fonts_get_system_font__result;
|
|
GFont fonts_get_system_font(const char *font_key) {
|
|
record_mock_call(s_fonts_get_system_font){
|
|
.font_key = font_key
|
|
};
|
|
return s_fonts_get_system_font__result;
|
|
}
|
|
|
|
static MockCallRecordings s_graphics_draw_text;
|
|
void graphics_draw_text(GContext *ctx, const char *text, GFont const font, const GRect box,
|
|
const GTextOverflowMode overflow_mode, const GTextAlignment alignment,
|
|
GTextAttributes *text_attributes) {
|
|
record_mock_call(s_graphics_draw_text){
|
|
.draw_text.box = box,
|
|
.draw_text.color = ctx->draw_state.text_color,
|
|
};
|
|
strncpy(s_graphics_draw_text.last_call.draw_text.text,
|
|
text, sizeof(s_graphics_draw_text.last_call.draw_text.text));
|
|
}
|
|
|
|
static MockCallRecordings s_graphics_text_attributes_destroy;
|
|
void graphics_text_attributes_destroy(GTextAttributes *text_attributes) {
|
|
record_mock_call(s_graphics_text_attributes_destroy){};
|
|
}
|
|
|
|
static MockCallRecordings s_graphics_text_layout_get_max_used_size;
|
|
static GSize s_graphics_text_layout_get_max_used_size__result;
|
|
GSize graphics_text_layout_get_max_used_size(GContext *ctx, const char *text,
|
|
GFont const font, const GRect box,
|
|
const GTextOverflowMode overflow_mode,
|
|
const GTextAlignment alignment,
|
|
GTextLayoutCacheRef layout) {
|
|
record_mock_call(s_graphics_text_layout_get_max_used_size){
|
|
.max_used_size.font = font,
|
|
.max_used_size.box = box,
|
|
.max_used_size.overflow_mode = overflow_mode,
|
|
.max_used_size.alignment = alignment,
|
|
};
|
|
strncpy(s_graphics_text_layout_get_max_used_size.last_call.max_used_size.text,
|
|
text, sizeof(s_graphics_text_layout_get_max_used_size.last_call.max_used_size.text));
|
|
|
|
return s_graphics_text_layout_get_max_used_size__result;
|
|
}
|
|
|
|
void test_rocky_api_graphics__initialize(void) {
|
|
fake_app_timer_init();
|
|
rocky_runtime_context_init();
|
|
jerry_init(JERRY_INIT_EMPTY);
|
|
|
|
s_app_window_stack_get_top_window = (Window){};
|
|
s_context = (GContext){};
|
|
s_app_state_get_graphics_context = &s_context;
|
|
s_app_event_loop_callback = NULL;
|
|
|
|
s_graphics_context_set_stroke_color = (MockCallRecordings){0};
|
|
s_graphics_context_set_stroke_width = (MockCallRecordings){0};
|
|
s_graphics_context_set_fill_color = (MockCallRecordings){0};
|
|
s_graphics_fill_rect = (MockCallRecordings){0};
|
|
s_graphics_fill_rect__color = GColorClear;
|
|
s_graphics_draw_rect_precise = (MockCallRecordings){0};
|
|
s_graphics_fill_radial_precise_internal = (MockCallRecordings){0};
|
|
s_layer_mark_dirty = (MockCallRecordings){0};
|
|
s_fonts_get_system_font = (MockCallRecordings){0};
|
|
s_graphics_draw_text = (MockCallRecordings){0};
|
|
s_graphics_text_attributes_destroy = (MockCallRecordings){0};
|
|
s_graphics_text_layout_get_max_used_size = (MockCallRecordings){0};
|
|
s_graphics_text_layout_get_max_used_size__result = (GSize){0};
|
|
}
|
|
|
|
void test_rocky_api_graphics__cleanup(void) {
|
|
fake_app_timer_deinit();
|
|
|
|
// some tests deinitialize the engine, avoid double de-init
|
|
if (app_state_get_rocky_runtime_context() != NULL) {
|
|
jerry_cleanup();
|
|
rocky_runtime_context_deinit();
|
|
}
|
|
}
|
|
|
|
static const RockyGlobalAPI *s_graphics_api[] = {
|
|
&GRAPHIC_APIS,
|
|
NULL,
|
|
};
|
|
|
|
extern RockyAPITextState s_rocky_text_state;
|
|
|
|
void test_rocky_api_graphics__handles_text_state(void) {
|
|
cl_assert_equal_i(0, s_fonts_get_system_font.call_count);
|
|
cl_assert_equal_i(0, s_graphics_text_attributes_destroy.call_count);
|
|
rocky_global_init(s_graphics_api);
|
|
cl_assert_equal_i(1, s_fonts_get_system_font.call_count);
|
|
cl_assert_equal_i(0, s_graphics_text_attributes_destroy.call_count);
|
|
rocky_global_deinit();
|
|
cl_assert_equal_i(1, s_fonts_get_system_font.call_count);
|
|
cl_assert_equal_i(0, s_graphics_text_attributes_destroy.call_count);
|
|
|
|
s_rocky_text_state.text_attributes = (GTextAttributes *)123;
|
|
rocky_global_deinit();
|
|
cl_assert_equal_i(1, s_fonts_get_system_font.call_count);
|
|
cl_assert_equal_i(1, s_graphics_text_attributes_destroy.call_count);
|
|
}
|
|
|
|
void test_rocky_api_graphics__request_draw(void) {
|
|
rocky_global_init(s_graphics_api);
|
|
|
|
cl_assert_equal_i(0, s_layer_mark_dirty.call_count);
|
|
EXECUTE_SCRIPT("_rocky.requestDraw();");
|
|
cl_assert_equal_i(1, s_layer_mark_dirty.call_count);
|
|
cl_assert_equal_p(&s_app_window_stack_get_top_window.layer, s_layer_mark_dirty.last_call.layer);
|
|
}
|
|
|
|
void test_rocky_api_graphics__provides_draw_event(void) {
|
|
rocky_global_init(s_graphics_api);
|
|
|
|
cl_assert_equal_b(false, rocky_global_has_event_handlers("draw"));
|
|
EXECUTE_SCRIPT("_rocky.on('draw', function() {});");
|
|
cl_assert_equal_b(true, rocky_global_has_event_handlers("draw"));
|
|
}
|
|
|
|
void test_rocky_api_graphics__draw_event_has_ctx(void) {
|
|
rocky_global_init(s_graphics_api);
|
|
|
|
EXECUTE_SCRIPT(
|
|
"var event = null;\n"
|
|
"_rocky.on('draw', function(e) {event = e;});"
|
|
);
|
|
|
|
const jerry_value_t event_null = prv_js_global_get_value("event");
|
|
cl_assert_equal_b(true, jerry_value_is_null(event_null));
|
|
jerry_release_value(event_null);
|
|
|
|
Layer *l = &app_window_stack_get_top_window()->layer;
|
|
l->update_proc(l, NULL);
|
|
const jerry_value_t event = prv_js_global_get_value("event");
|
|
cl_assert_equal_b(true, jerry_value_is_object(event));
|
|
|
|
const jerry_value_t context_2d = jerry_get_object_field(event, "context");
|
|
cl_assert_equal_b(true, jerry_value_is_object(context_2d));
|
|
jerry_release_value(context_2d);
|
|
jerry_release_value(event);
|
|
}
|
|
|
|
jerry_value_t prv_create_canvas_context_2d_for_layer(Layer *layer);
|
|
void layer_get_unobstructed_bounds(const Layer *layer, GRect *bounds_out) {
|
|
*bounds_out = GRect(5, 6, 7, 8);
|
|
}
|
|
|
|
void test_rocky_api_graphics__canvas_offers_size(void) {
|
|
rocky_global_init(s_graphics_api);
|
|
|
|
Layer l = {.bounds = GRect(1, 2, 3, 4)};
|
|
const jerry_value_t ctx = prv_create_canvas_context_2d_for_layer(&l);
|
|
jerry_set_object_field(jerry_get_global_object(), "ctx", ctx);
|
|
|
|
EXECUTE_SCRIPT(
|
|
"var w = ctx.canvas.clientWidth;\n"
|
|
"var h = ctx.canvas.clientHeight;\n"
|
|
"var uol = ctx.canvas.unobstructedLeft;\n"
|
|
"var uot = ctx.canvas.unobstructedTop;\n"
|
|
"var uow = ctx.canvas.unobstructedWidth;\n"
|
|
"var uoh = ctx.canvas.unobstructedHeight;\n"
|
|
);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("w", 3);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("h", 4);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("uol", 5);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("uot", 6);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("uow", 7);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("uoh", 8);
|
|
}
|
|
|
|
static const jerry_value_t prv_global_init_and_set_ctx(void) {
|
|
rocky_global_init(s_graphics_api);
|
|
|
|
// make this easily testable by putting it int JS context as global
|
|
Layer l = {.bounds = GRect(0, 0, 144, 168)};
|
|
const jerry_value_t ctx = prv_create_canvas_context_2d_for_layer(&l);
|
|
cl_assert_equal_b(jerry_value_is_object(ctx), true);
|
|
jerry_set_object_field(jerry_get_global_object(), "ctx", ctx);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
void test_rocky_api_graphics__drawing_rects(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
s_context.draw_state.fill_color = GColorJaegerGreen;
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.clearRect(1, 2, 3, 4);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_fill_rect.call_count);
|
|
cl_assert_equal_rect(GRect(1, 2, 3, 4), s_graphics_fill_rect.last_call.rect);
|
|
cl_assert_equal_i(GColorBlackARGB8, s_graphics_fill_rect__color.argb);
|
|
cl_assert_equal_i(GColorJaegerGreenARGB8, s_context.draw_state.fill_color.argb);
|
|
|
|
s_graphics_fill_rect = (MockCallRecordings){};
|
|
EXECUTE_SCRIPT(
|
|
"ctx.fillRect(5, 6, 7, 8);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_fill_rect.call_count);
|
|
cl_assert_equal_rect(GRect(5, 6, 7, 8), s_graphics_fill_rect.last_call.rect);
|
|
|
|
s_graphics_draw_rect_precise = (MockCallRecordings){};
|
|
EXECUTE_SCRIPT(
|
|
"ctx.strokeRect(9, 10.2, 11.5, 12.8);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_draw_rect_precise.call_count);
|
|
GRectPrecise expected_rect = {(int)(8.5*8), 78, (int)(11.5*8), (int)(12.8*8)};
|
|
cl_assert_equal_rect_precise(expected_rect, s_graphics_draw_rect_precise.last_call.prect);
|
|
}
|
|
|
|
#define PP(x, y) (GPointPrecise( \
|
|
(int16_t)((x) * FIXED_S16_3_FACTOR), \
|
|
(int16_t)((y) * FIXED_S16_3_FACTOR)))
|
|
|
|
void test_rocky_api_graphics__fill_radial(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.rockyFillRadial(30, 40, 10, 20, 0, Math.PI);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_fill_radial_precise_internal.call_count);
|
|
MockCallRecording *const lc = &s_graphics_fill_radial_precise_internal.last_call;
|
|
cl_assert_equal_point_precise(PP(29.5, 39.5), lc->fill_radial_precise.center);
|
|
cl_assert_equal_i(10*8, lc->fill_radial_precise.radius_inner.raw_value);
|
|
cl_assert_equal_i(20*8, lc->fill_radial_precise.radius_outer.raw_value);
|
|
cl_assert_equal_i(TRIG_MAX_ANGLE * 1 / 4, lc->fill_radial_precise.angle_start);
|
|
cl_assert_equal_i(TRIG_MAX_ANGLE * 3 / 4, lc->fill_radial_precise.angle_end);
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.rockyFillRadial(30, 40, 10, 30, 0, 2 * Math.PI);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(2, s_graphics_fill_radial_precise_internal.call_count);
|
|
cl_assert_equal_point_precise(PP(29.5, 39.5), lc->fill_radial_precise.center);
|
|
cl_assert_equal_i(10*8, lc->fill_radial_precise.radius_inner.raw_value);
|
|
cl_assert_equal_i(30*8, lc->fill_radial_precise.radius_outer.raw_value);
|
|
cl_assert_equal_i(TRIG_MAX_ANGLE * 1 / 4, lc->fill_radial_precise.angle_start);
|
|
cl_assert_equal_i(TRIG_MAX_ANGLE * 5 / 4, lc->fill_radial_precise.angle_end);
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.rockyFillRadial(30.5, 40.1, 30, 10, 0, 2 * Math.PI);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(3, s_graphics_fill_radial_precise_internal.call_count);
|
|
cl_assert_equal_point_precise(PP(30, 39.625), lc->fill_radial_precise.center);
|
|
cl_assert_equal_i(10*8, lc->fill_radial_precise.radius_inner.raw_value);
|
|
cl_assert_equal_i(30*8, lc->fill_radial_precise.radius_outer.raw_value);
|
|
}
|
|
|
|
void test_rocky_api_graphics__fill_radial_not_enough_args(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
EXECUTE_SCRIPT_EXPECT_ERROR(
|
|
"ctx.rockyFillRadial(30, 40, 10, 20, 0);\n",
|
|
"TypeError: Not enough arguments"
|
|
);
|
|
}
|
|
|
|
void test_rocky_api_graphics__fill_radial_type_error(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
EXECUTE_SCRIPT_EXPECT_ERROR(
|
|
"ctx.rockyFillRadial(30, 40, 10, 20, 0, false);\n",
|
|
"TypeError: Argument at index 5 is not a Number"
|
|
);
|
|
}
|
|
|
|
void test_rocky_api_graphics__fill_radial_range_check(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
EXECUTE_SCRIPT_EXPECT_ERROR(
|
|
"ctx.rockyFillRadial(4096, 40, 10, 20, 0, false);\n",
|
|
"TypeError: Argument at index 0 is invalid: Value out of bounds for native type"
|
|
);
|
|
}
|
|
|
|
void test_rocky_api_graphics__fill_radial_zero_radius(void) {
|
|
prv_global_init_and_set_ctx();
|
|
// inner radius = 0
|
|
EXECUTE_SCRIPT(
|
|
"ctx.rockyFillRadial(30, 40, 0, 20, 0, Math.PI);\n"
|
|
);
|
|
MockCallRecording *const lc = &s_graphics_fill_radial_precise_internal.last_call;
|
|
cl_assert_equal_i(1, s_graphics_fill_radial_precise_internal.call_count);
|
|
cl_assert_equal_point_precise((PP(29.5, 39.5)), lc->fill_radial_precise.center);
|
|
cl_assert_equal_i(0, lc->fill_radial_precise.radius_inner.raw_value);
|
|
cl_assert_equal_i(20 * 8, lc->fill_radial_precise.radius_outer.raw_value);
|
|
|
|
// inner radius capped to >= 0
|
|
EXECUTE_SCRIPT(
|
|
"ctx.rockyFillRadial(30, 40, -10, 20, 0, Math.PI);\n"
|
|
);
|
|
cl_assert_equal_i(2, s_graphics_fill_radial_precise_internal.call_count);
|
|
cl_assert_equal_point_precise((PP(29.5, 39.5)), lc->fill_radial_precise.center);
|
|
cl_assert_equal_i(0, lc->fill_radial_precise.radius_inner.raw_value);
|
|
cl_assert_equal_i(20 * 8, lc->fill_radial_precise.radius_outer.raw_value);
|
|
|
|
// outer radius capped to >= 0
|
|
EXECUTE_SCRIPT(
|
|
"ctx.rockyFillRadial(30, 40, -10, -20, 0, Math.PI);\n"
|
|
);
|
|
cl_assert_equal_i(3, s_graphics_fill_radial_precise_internal.call_count);
|
|
cl_assert_equal_point_precise((PP(29.5, 39.5)), lc->fill_radial_precise.center);
|
|
cl_assert_equal_i(0, lc->fill_radial_precise.radius_inner.raw_value);
|
|
cl_assert_equal_i(0, lc->fill_radial_precise.radius_outer.raw_value);
|
|
}
|
|
|
|
void test_rocky_api_graphics__line_styles(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.lineWidth = 8;\n"
|
|
"var w = ctx.lineWidth;\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_context_set_stroke_width.call_count);
|
|
cl_assert_equal_i(8, s_graphics_context_set_stroke_width.last_call.width);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("w", s_graphics_context_set_stroke_width.last_call.width);
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.lineWidth = 2.1;\n"
|
|
"var w = ctx.lineWidth;\n"
|
|
);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("w", 2);
|
|
|
|
EXECUTE_SCRIPT_EXPECT_ERROR(
|
|
"ctx.lineWidth = -4;\n",
|
|
"TypeError: Argument at index 0 is invalid: Value out of bounds for native type"
|
|
);
|
|
EXECUTE_SCRIPT("var w = ctx.lineWidth;\n");
|
|
ASSERT_JS_GLOBAL_EQUALS_I("w", 2);
|
|
}
|
|
|
|
void test_rocky_api_graphics__line_styles_check_bounds(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
EXECUTE_SCRIPT_EXPECT_ERROR(
|
|
"ctx.lineWidth = -1;",
|
|
"TypeError: Argument at index 0 is invalid: Value out of bounds for native type"
|
|
);
|
|
|
|
EXECUTE_SCRIPT_EXPECT_ERROR(
|
|
"ctx.lineWidth = 256;",
|
|
"TypeError: Argument at index 0 is invalid: Value out of bounds for native type"
|
|
);
|
|
}
|
|
|
|
void test_rocky_api_graphics__fill_and_stroke_styles(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.fillStyle = '#f00';\n"
|
|
"ctx.strokeStyle = 'white';\n"
|
|
"var c = ctx.fillStyle;\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_context_set_fill_color.call_count);
|
|
cl_assert_equal_i(GColorRedARGB8, s_graphics_context_set_fill_color.last_call.color.argb);
|
|
cl_assert_equal_i(1, s_graphics_context_set_stroke_color.call_count);
|
|
cl_assert_equal_i(GColorWhiteARGB8, s_graphics_context_set_stroke_color.last_call.color.argb);
|
|
|
|
// ignores invalid values
|
|
EXECUTE_SCRIPT(
|
|
"ctx.fillStyle = 'unknown';\n"
|
|
"ctx.strokeStyle = '4%2F';\n"
|
|
);
|
|
cl_assert_equal_i(1, s_graphics_context_set_fill_color.call_count);
|
|
cl_assert_equal_i(1, s_graphics_context_set_stroke_color.call_count);
|
|
}
|
|
|
|
void test_rocky_api_graphics__canvas_state(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
// calling restore if nothing was stored is a no-op
|
|
s_context.draw_state.fill_color.argb = 1;
|
|
EXECUTE_SCRIPT("ctx.restore()\n");
|
|
cl_assert_equal_i(1, s_context.draw_state.fill_color.argb);
|
|
|
|
EXECUTE_SCRIPT("ctx.save()\n"); // 1
|
|
s_context.draw_state.fill_color.argb = 2;
|
|
EXECUTE_SCRIPT("ctx.save()\n"); // 2
|
|
s_context.draw_state.fill_color.argb = 3;
|
|
|
|
EXECUTE_SCRIPT("ctx.restore()\n"); // -> 2 (one element left)
|
|
cl_assert_equal_i(2, s_context.draw_state.fill_color.argb);
|
|
|
|
EXECUTE_SCRIPT("ctx.restore()\n"); // -> 1 (no element left)
|
|
cl_assert_equal_i(1, s_context.draw_state.fill_color.argb);
|
|
|
|
EXECUTE_SCRIPT("ctx.restore()\n"); // no-op
|
|
cl_assert_equal_i(1, s_context.draw_state.fill_color.argb);
|
|
}
|
|
|
|
static const int16_t large_int = 10000;
|
|
|
|
void test_rocky_api_graphics__fill_text(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
// we do this in C and not JS as color binding is not linked in this unit-test
|
|
// what we want to test though is that the text color is taken from fill color
|
|
rocky_api_graphics_get_gcontext()->draw_state.fill_color = GColorRed;
|
|
EXECUTE_SCRIPT(
|
|
"ctx.fillText('some text', 10, 10);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_draw_text.call_count);
|
|
cl_assert_equal_s("some text", s_graphics_draw_text.last_call.draw_text.text);
|
|
cl_assert_equal_i(GColorRedARGB8, s_graphics_draw_text.last_call.draw_text.color.argb);
|
|
cl_assert_equal_rect((GRect(10, 10, large_int, large_int)),
|
|
s_graphics_draw_text.last_call.draw_text.box);
|
|
|
|
rocky_api_graphics_get_gcontext()->draw_state.fill_color = GColorBlue;
|
|
EXECUTE_SCRIPT(
|
|
"ctx.fillText('more text', -10.5, 5000, 60);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(2, s_graphics_draw_text.call_count);
|
|
cl_assert_equal_s("more text", s_graphics_draw_text.last_call.draw_text.text);
|
|
cl_assert_equal_i(GColorBlueARGB8, s_graphics_draw_text.last_call.draw_text.color.argb);
|
|
cl_assert_equal_rect((GRect(-11, 5000, 60, large_int)),
|
|
s_graphics_draw_text.last_call.draw_text.box);
|
|
}
|
|
|
|
void test_rocky_api_graphics__fill_text_coordinates(void) {
|
|
prv_global_init_and_set_ctx();
|
|
EXECUTE_SCRIPT("ctx.fillText('some text', 0, 1.5);");
|
|
cl_assert_equal_rect((GRect(0, 2, large_int, large_int)),
|
|
s_graphics_draw_text.last_call.draw_text.box);
|
|
|
|
EXECUTE_SCRIPT("ctx.fillText('some text', -0.2, 1.2, 10.5);");
|
|
cl_assert_equal_rect((GRect(0, 1, 11, large_int)),
|
|
s_graphics_draw_text.last_call.draw_text.box);
|
|
|
|
EXECUTE_SCRIPT("ctx.fillText('some text', -0.5, 1.2, -0.5);");
|
|
cl_assert_equal_rect((GRect(-1, 1, -1, large_int)),
|
|
s_graphics_draw_text.last_call.draw_text.box);
|
|
}
|
|
|
|
void test_rocky_api_graphics__fill_text_aligned(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
// we do this in C and not JS as color binding is not linked in this unit-test
|
|
// what we want to test though is that the text color is taken from fill color
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.textAlign = 'left';\n"
|
|
"ctx.fillText('some text', 100, 100);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_draw_text.call_count);
|
|
cl_assert_equal_rect((GRect(100, 100, large_int, large_int)),
|
|
s_graphics_draw_text.last_call.draw_text.box);
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.textAlign = 'center';\n"
|
|
"ctx.fillText('some text', 100, 100);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(2, s_graphics_draw_text.call_count);
|
|
cl_assert_equal_rect((GRect(-4900, 100, large_int, large_int)),
|
|
s_graphics_draw_text.last_call.draw_text.box);
|
|
|
|
EXECUTE_SCRIPT(
|
|
"ctx.textAlign = 'right';\n"
|
|
"ctx.fillText('some text', 100, 100);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(3, s_graphics_draw_text.call_count);
|
|
cl_assert_equal_rect((GRect(-9900, 100, large_int, large_int)),
|
|
s_graphics_draw_text.last_call.draw_text.box);
|
|
}
|
|
|
|
void test_rocky_api_graphics__text_align(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
// intial value
|
|
cl_assert_equal_i(GTextAlignmentLeft, s_rocky_text_state.alignment);
|
|
|
|
s_rocky_text_state.alignment = (GTextAlignment)-1;
|
|
// unsupported values don't change the value
|
|
EXECUTE_SCRIPT("ctx.textAlign = 123;\n");
|
|
cl_assert_equal_i(-1, s_rocky_text_state.alignment);
|
|
EXECUTE_SCRIPT("ctx.textAlign = 'unknown';\n");
|
|
cl_assert_equal_i(-1, s_rocky_text_state.alignment);
|
|
|
|
|
|
EXECUTE_SCRIPT("ctx.textAlign = 'left';\nvar a = ctx.textAlign;\n");
|
|
cl_assert_equal_i(GTextAlignmentLeft, s_rocky_text_state.alignment);
|
|
ASSERT_JS_GLOBAL_EQUALS_S("a", "left");
|
|
|
|
EXECUTE_SCRIPT("ctx.textAlign = 'right';\nvar a = ctx.textAlign;\n");
|
|
cl_assert_equal_i(GTextAlignmentRight, s_rocky_text_state.alignment);
|
|
ASSERT_JS_GLOBAL_EQUALS_S("a", "right");
|
|
|
|
EXECUTE_SCRIPT("ctx.textAlign = 'center';\nvar a = ctx.textAlign;\n");
|
|
cl_assert_equal_i(GTextAlignmentCenter, s_rocky_text_state.alignment);
|
|
ASSERT_JS_GLOBAL_EQUALS_S("a", "center");
|
|
|
|
// we only support LTR
|
|
EXECUTE_SCRIPT("ctx.textAlign = 'start';\nvar a = ctx.textAlign;\n");
|
|
cl_assert_equal_i(GTextAlignmentLeft, s_rocky_text_state.alignment);
|
|
ASSERT_JS_GLOBAL_EQUALS_S("a", "left");
|
|
|
|
EXECUTE_SCRIPT("ctx.textAlign = 'end';\nvar a = ctx.textAlign;\n");
|
|
cl_assert_equal_i(GTextAlignmentRight, s_rocky_text_state.alignment);
|
|
ASSERT_JS_GLOBAL_EQUALS_S("a", "right");
|
|
}
|
|
|
|
void test_rocky_api_graphics__text_font(void) {
|
|
cl_assert_equal_i(0, s_fonts_get_system_font.call_count);
|
|
s_fonts_get_system_font__result = (GFont)123;
|
|
rocky_global_init(s_graphics_api);
|
|
cl_assert_equal_i(1, s_fonts_get_system_font.call_count);
|
|
cl_assert_equal_p((GFont)123, s_rocky_text_state.font);
|
|
|
|
// make this easily testable by putting it int JS context as global
|
|
Layer l = {.bounds = GRect(0, 0, 144, 168)};
|
|
const jerry_value_t ctx = prv_create_canvas_context_2d_for_layer(&l);
|
|
jerry_set_object_field(jerry_get_global_object(), "ctx", ctx);
|
|
|
|
|
|
s_rocky_text_state.font = (GFont)-1;
|
|
// unsupported values don't change the value
|
|
EXECUTE_SCRIPT("ctx.font = 123;\n");
|
|
cl_assert_equal_p((GFont)-1, s_rocky_text_state.font);
|
|
EXECUTE_SCRIPT("ctx.font = 'unknown';\n");
|
|
cl_assert_equal_p((GFont)-1, s_rocky_text_state.font);
|
|
cl_assert_equal_i(1, s_fonts_get_system_font.call_count);
|
|
|
|
EXECUTE_SCRIPT("ctx.font = '14px bold Gothic';\n");
|
|
cl_assert_equal_i(2, s_fonts_get_system_font.call_count);
|
|
cl_assert_equal_p(FONT_KEY_GOTHIC_14_BOLD, s_fonts_get_system_font.last_call.font_key);
|
|
|
|
EXECUTE_SCRIPT("ctx.font = '28px Gothic';\nvar f = ctx.font;\n");
|
|
ASSERT_JS_GLOBAL_EQUALS_S("f", "28px Gothic");
|
|
}
|
|
|
|
extern T_STATIC void prv_graphics_color_to_char_buffer(GColor8 color, char *buf_out);
|
|
|
|
#define TEST_COLOR_STRING(gcolor, expect_str) do { \
|
|
char buf[12]; \
|
|
prv_graphics_color_to_char_buffer(gcolor, buf); \
|
|
cl_assert_equal_s(buf, expect_str); \
|
|
} while(0);
|
|
|
|
void test_rocky_api_graphics__color_names(void) {
|
|
TEST_COLOR_STRING(GColorClear, "transparent");
|
|
TEST_COLOR_STRING((GColor){ .a = 1 }, "transparent");
|
|
TEST_COLOR_STRING(GColorRed, "#FF0000");
|
|
TEST_COLOR_STRING(GColorMalachite, "#00FF55");
|
|
}
|
|
|
|
extern T_STATIC const RockyAPISystemFontDefinition s_font_definitions[];
|
|
bool prv_font_definition_from_value(jerry_value_t value, RockyAPISystemFontDefinition **result);
|
|
|
|
void test_rocky_api_graphics__text_font_names_unique(void) {
|
|
rocky_global_init(s_graphics_api);
|
|
|
|
const RockyAPISystemFontDefinition *def = s_font_definitions;
|
|
while (def->js_name) {
|
|
const jerry_value_t name_js = jerry_create_string((jerry_char_t *)def->js_name);
|
|
RockyAPISystemFontDefinition *cmp_def = NULL;
|
|
bool actual = prv_font_definition_from_value(name_js, &cmp_def);
|
|
cl_assert_equal_b(true, actual);
|
|
cl_assert_equal_s(cmp_def->res_key, def->res_key);
|
|
jerry_release_value(name_js);
|
|
def++;
|
|
}
|
|
}
|
|
|
|
void test_rocky_api_graphics__measure_text(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
// fill text_state with unique values we can test against
|
|
s_rocky_text_state = (RockyAPITextState) {
|
|
.font = (GFont)-1,
|
|
.overflow_mode = (GTextOverflowMode)-2,
|
|
.alignment = (GTextAlignment)-3,
|
|
.text_attributes = (GTextAttributes *)-4,
|
|
};
|
|
|
|
s_graphics_text_layout_get_max_used_size__result = GSize(123, 456);
|
|
EXECUTE_SCRIPT(
|
|
"var tm = ctx.measureText('foo');\n"
|
|
"var tm_w = tm.width;\n"
|
|
"var tm_h = tm.height;\n"
|
|
);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("tm_w", 123);
|
|
ASSERT_JS_GLOBAL_EQUALS_I("tm_h", 456);
|
|
|
|
cl_assert_equal_i(1, s_graphics_text_layout_get_max_used_size.call_count);
|
|
const MockCallRecording *lc = &s_graphics_text_layout_get_max_used_size.last_call;
|
|
cl_assert_equal_s("foo", lc->max_used_size.text);
|
|
cl_assert_equal_p(s_rocky_text_state.font, lc->max_used_size.font);
|
|
cl_assert_equal_rect((GRect(0, 0, INT16_MAX, INT16_MAX)), lc->max_used_size.box);
|
|
cl_assert_equal_i(s_rocky_text_state.overflow_mode, lc->max_used_size.overflow_mode);
|
|
cl_assert_equal_i(s_rocky_text_state.alignment, lc->max_used_size.alignment);
|
|
}
|
|
|
|
void test_rocky_api_graphics__state_initialized_between_renders(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
// fill text_state with unique values we can test against
|
|
s_rocky_text_state = (RockyAPITextState) {
|
|
.font = (GFont)-1,
|
|
.overflow_mode = (GTextOverflowMode)-2,
|
|
.alignment = (GTextAlignment)-3,
|
|
.text_attributes = (GTextAttributes *)-4,
|
|
};
|
|
|
|
EXECUTE_SCRIPT("_rocky.on('draw', function(e) {});");
|
|
Layer *l = &app_window_stack_get_top_window()->layer;
|
|
l->update_proc(l, NULL);
|
|
|
|
cl_assert_equal_i(1, s_fonts_get_system_font.call_count);
|
|
cl_assert_equal_i(GTextAlignmentLeft, s_rocky_text_state.alignment);
|
|
cl_assert_equal_i(GTextOverflowModeWordWrap, s_rocky_text_state.overflow_mode);
|
|
cl_assert_equal_p(NULL, s_rocky_text_state.text_attributes);
|
|
}
|
|
|
|
void test_rocky_api_graphics__context_2d_prototype_wrap_function(void) {
|
|
prv_global_init_and_set_ctx();
|
|
|
|
EXECUTE_SCRIPT("var origFillRect = _rocky.CanvasRenderingContext2D.prototype.fillRect;\n"
|
|
"_rocky.CanvasRenderingContext2D.prototype.fillRect = function(x, y, w, h) {\n"
|
|
" w *= 2;\n"
|
|
" h *= 2;\n"
|
|
" origFillRect.call(this, x, y, w, h);\n"
|
|
"};\n"
|
|
"ctx.fillRect(5, 6, 7, 8);\n"
|
|
);
|
|
|
|
cl_assert_equal_i(1, s_graphics_fill_rect.call_count);
|
|
cl_assert_equal_rect(GRect(5, 6, 7 * 2, 8 * 2), s_graphics_fill_rect.last_call.rect);
|
|
}
|