Public Access
Add scrolling, countdown, stopwatch, images, seconds, multiplier and degree sign
- Text elements can scroll in a window at a set speed; clock, countdown and stopwatch can show seconds. Moving pages are sent as instant frames (new Nano command D1) and paced by the estimated flip time. - Countdown to a date and time (smart, days, days + time, with seconds), stopwatch (web UI, HA switch and reset button), image element (fixed or by Home Assistant state), multiplier for HA values, degree sign (char 127) in all fonts and the font converter. - Page format version 2; version 1 pages are converted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ca8d78ea8a
commit
fc6fa1a180
38 files changed
+1370
-277
No files matched your search
@@ -23,7 +23,7 @@ public:
|
||||
BROSE9323(uint8_t, uint8_t, uint8_t, uint16_t) {}
|
||||
|
||||
void begin() { log("begin"); }
|
||||
void display(bool force = false) { log("display", force); }
|
||||
void display(bool force = false, bool instant = false) { log("display", force, instant); }
|
||||
void drawPixel(int16_t x, int16_t y, uint16_t color) { log(direct ? "directPixel" : "pixel", x, y, color); }
|
||||
void fillScreen(uint16_t color) { log("fill", color); }
|
||||
void setDirect(bool d) { direct = d; }
|
||||
|
||||
+350
-11
@@ -24,6 +24,7 @@
|
||||
#include "logic/schedule.h"
|
||||
#include "logic/settings.h"
|
||||
#include "logic/status_led.h"
|
||||
#include "logic/stopwatch.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fakes
|
||||
@@ -53,12 +54,16 @@ class FakeFrames : public Frames
|
||||
{
|
||||
public:
|
||||
std::vector<std::string> shown;
|
||||
uint32_t wait = 0; // what dynamicPage() returns
|
||||
bool last_redraw = false;
|
||||
|
||||
void dynamicPage(const LocalTime &time) override
|
||||
uint32_t dynamicPage(const LocalTime &time, uint32_t, bool redraw) override
|
||||
{
|
||||
char s[24];
|
||||
snprintf(s, sizeof(s), "clock %02d:%02d", time.hour, time.minute);
|
||||
shown.push_back(s);
|
||||
last_redraw = redraw;
|
||||
return wait;
|
||||
}
|
||||
void page() override { shown.push_back("page"); }
|
||||
void live() override { shown.push_back("live"); }
|
||||
@@ -995,7 +1000,10 @@ static std::string textOf(const PageElement &e, const char *ha_value = nullptr)
|
||||
t.hour = 8;
|
||||
t.minute = 5;
|
||||
char out[64];
|
||||
elementText(e, t, ha_value, out, sizeof(out));
|
||||
ElementValues values;
|
||||
values.ha_value = ha_value;
|
||||
values.stopwatch_ms = 65000;
|
||||
elementText(e, t, values, out, sizeof(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1085,13 +1093,13 @@ void test_render_background_follows_the_slots()
|
||||
FakeResources res;
|
||||
DynamicPage page = twoSlotPage();
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
renderPage(page, at(9, 0), res, b);
|
||||
renderPage(page, at(9, 0), 0, res, b);
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 0, 0));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 1, 0));
|
||||
renderPage(page, at(18, 0), res, b);
|
||||
renderPage(page, at(18, 0), 0, res, b);
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 1, 0));
|
||||
strcpy(page.slots[1].bitmap, "Gone");
|
||||
renderPage(page, at(18, 0), res, b);
|
||||
renderPage(page, at(18, 0), 0, res, b);
|
||||
for (int i = 0; i < BITMAP_BYTES; i++)
|
||||
TEST_ASSERT_EQUAL_HEX8(0, b[i]); // blank
|
||||
}
|
||||
@@ -1111,12 +1119,12 @@ void test_render_overlays()
|
||||
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
e.overlay = Overlay::OnTop; // on a full background nothing changes
|
||||
renderPage(page, at(12, 0), res, b);
|
||||
renderPage(page, at(12, 0), 0, res, b);
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 10, 2));
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 12, 2));
|
||||
|
||||
e.overlay = Overlay::Box; // cleared area with one dot around it, then the text
|
||||
renderPage(page, at(12, 0), res, b);
|
||||
renderPage(page, at(12, 0), 0, res, b);
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 9, 1));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 10, 2));
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 12, 2));
|
||||
@@ -1125,7 +1133,7 @@ void test_render_overlays()
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 8, 2));
|
||||
|
||||
e.overlay = Overlay::Invert;
|
||||
renderPage(page, at(12, 0), res, b);
|
||||
renderPage(page, at(12, 0), 0, res, b);
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 10, 2));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 12, 2));
|
||||
}
|
||||
@@ -1144,7 +1152,7 @@ void test_render_alignment_and_boxes()
|
||||
page.elements[1].align = Align::Center;
|
||||
ElementBox boxes[2];
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
renderPage(page, at(12, 34), res, b, boxes);
|
||||
renderPage(page, at(12, 34), 0, res, b, boxes);
|
||||
TEST_ASSERT_EQUAL(84 - 29, boxes[0].x);
|
||||
TEST_ASSERT_EQUAL(29, boxes[0].width);
|
||||
TEST_ASSERT_EQUAL(8, boxes[0].height);
|
||||
@@ -1161,7 +1169,7 @@ void test_render_unknown_font_uses_normal()
|
||||
strcpy(page.elements[0].font, "Gone");
|
||||
ElementBox box;
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
renderPage(page, at(12, 34), res, b, &box);
|
||||
renderPage(page, at(12, 34), 0, res, b, &box);
|
||||
TEST_ASSERT_EQUAL(29, box.width);
|
||||
}
|
||||
|
||||
@@ -1179,7 +1187,7 @@ void test_builtin_clock_page_is_the_old_clock()
|
||||
FakeResources res;
|
||||
TEST_ASSERT_TRUE(builtinPage(BUILTIN_PAGE_CLOCK, page));
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
renderPage(page, at(12, 34), res, b);
|
||||
renderPage(page, at(12, 34), 0, res, b);
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 48, 4)); // the '1'
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 48, 10));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 48, 11));
|
||||
@@ -1191,6 +1199,325 @@ void test_default_page_is_the_builtin_clock()
|
||||
TEST_ASSERT_TRUE(builtinPage(defaults.page, page));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Seconds, countdown, stopwatch, multiplier, degree sign
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void test_clock_with_seconds()
|
||||
{
|
||||
PageElement e;
|
||||
e.format = CLOCK_HH_MM_SS;
|
||||
TEST_ASSERT_EQUAL_STRING("08:05:00", textOf(e).c_str());
|
||||
e.format = CLOCK_H_MM_SS;
|
||||
TEST_ASSERT_EQUAL_STRING("8:05:00", textOf(e).c_str());
|
||||
TEST_ASSERT_TRUE(elementShowsSeconds(e, LocalTime(), false));
|
||||
e.format = CLOCK_HH_MM;
|
||||
TEST_ASSERT_FALSE(elementShowsSeconds(e, LocalTime(), false));
|
||||
}
|
||||
|
||||
void test_countdown_target_parse_and_format()
|
||||
{
|
||||
uint32_t m = 0;
|
||||
TEST_ASSERT_TRUE(parseTarget("2000-01-01T00:00", m));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, m);
|
||||
TEST_ASSERT_TRUE(parseTarget("2026-12-24T18:30", m));
|
||||
char out[17];
|
||||
formatTarget(m, out);
|
||||
TEST_ASSERT_EQUAL_STRING("2026-12-24T18:30", out);
|
||||
TEST_ASSERT_FALSE(parseTarget("2026-02-31T10:00", m)); // no 31 February
|
||||
TEST_ASSERT_FALSE(parseTarget("2026-12-24 18:30", m));
|
||||
TEST_ASSERT_FALSE(parseTarget("1999-12-24T18:30", m));
|
||||
TEST_ASSERT_FALSE(parseTarget("2026-12-24T24:00", m));
|
||||
TEST_ASSERT_TRUE(parseTarget("2028-02-29T00:00", m)); // leap year
|
||||
|
||||
LocalTime t;
|
||||
t.year = 2026;
|
||||
t.month = 10;
|
||||
t.day = 15;
|
||||
t.hour = 8;
|
||||
t.minute = 5;
|
||||
TEST_ASSERT_TRUE(parseTarget("2026-10-15T08:05", m));
|
||||
TEST_ASSERT_EQUAL_UINT32(m, minutesSince2000(t));
|
||||
}
|
||||
|
||||
// Countdown from Thursday 15 October 2026, 08:05:00 (textOf) to `target`
|
||||
static std::string countdown(const char *target, uint8_t format, Language lang = Language::Dutch)
|
||||
{
|
||||
PageElement e;
|
||||
e.type = ElementType::Countdown;
|
||||
e.format = format;
|
||||
e.language = lang;
|
||||
TEST_ASSERT_TRUE(parseTarget(target, e.target));
|
||||
return textOf(e);
|
||||
}
|
||||
|
||||
void test_countdown_formats()
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("12 dagen", countdown("2026-10-27T09:00", COUNTDOWN_SMART).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("1 day", countdown("2026-10-16T09:00", COUNTDOWN_SMART, Language::English).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("5 uur", countdown("2026-10-15T13:30", COUNTDOWN_SMART).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("1 hour", countdown("2026-10-15T09:30", COUNTDOWN_SMART, Language::English).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("34 min", countdown("2026-10-15T08:39", COUNTDOWN_SMART).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("1 min", countdown("2026-10-15T08:06", COUNTDOWN_SMART).c_str());
|
||||
|
||||
// Calendar days: tomorrow 00:30 is 1 day away
|
||||
TEST_ASSERT_EQUAL_STRING("1 dag", countdown("2026-10-16T00:30", COUNTDOWN_DAYS).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("12 days", countdown("2026-10-27T01:00", COUNTDOWN_DAYS, Language::English).c_str());
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("12d 00:55", countdown("2026-10-27T09:00", COUNTDOWN_DAYS_TIME).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("05:25", countdown("2026-10-15T13:30", COUNTDOWN_DAYS_TIME).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("12d 00:55:00", countdown("2026-10-27T09:00", COUNTDOWN_SECONDS).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("05:25:00", countdown("2026-10-15T13:30", COUNTDOWN_SECONDS).c_str());
|
||||
|
||||
// Passed: the suffix, or 0
|
||||
TEST_ASSERT_EQUAL_STRING("0", countdown("2026-10-15T08:05", COUNTDOWN_SMART).c_str());
|
||||
PageElement e;
|
||||
e.type = ElementType::Countdown;
|
||||
strcpy(e.text, "Disney ");
|
||||
strcpy(e.suffix, "nu!");
|
||||
TEST_ASSERT_TRUE(parseTarget("2026-01-01T00:00", e.target));
|
||||
TEST_ASSERT_EQUAL_STRING("Disney nu!", textOf(e).c_str());
|
||||
}
|
||||
|
||||
void test_countdown_shows_seconds_only_when_needed()
|
||||
{
|
||||
PageElement e;
|
||||
e.type = ElementType::Countdown;
|
||||
LocalTime t;
|
||||
t.year = 2026;
|
||||
t.month = 10;
|
||||
t.day = 15;
|
||||
t.hour = 8;
|
||||
t.minute = 5;
|
||||
t.second = 30;
|
||||
TEST_ASSERT_TRUE(parseTarget("2026-10-15T08:06", e.target)); // 30 s left
|
||||
e.format = COUNTDOWN_SMART;
|
||||
TEST_ASSERT_TRUE(elementShowsSeconds(e, t, false));
|
||||
char out[32];
|
||||
elementText(e, t, ElementValues(), out, sizeof(out));
|
||||
TEST_ASSERT_EQUAL_STRING("0:30", out);
|
||||
e.format = COUNTDOWN_DAYS_TIME;
|
||||
TEST_ASSERT_FALSE(elementShowsSeconds(e, t, false));
|
||||
e.format = COUNTDOWN_SECONDS;
|
||||
TEST_ASSERT_TRUE(elementShowsSeconds(e, t, false));
|
||||
TEST_ASSERT_TRUE(parseTarget("2026-10-15T08:05", e.target)); // passed
|
||||
TEST_ASSERT_FALSE(elementShowsSeconds(e, t, false));
|
||||
}
|
||||
|
||||
void test_stopwatch()
|
||||
{
|
||||
Stopwatch sw;
|
||||
TEST_ASSERT_EQUAL_UINT32(0, sw.elapsed(5000));
|
||||
sw.start(1000);
|
||||
TEST_ASSERT_TRUE(sw.running());
|
||||
TEST_ASSERT_EQUAL_UINT32(2000, sw.elapsed(3000));
|
||||
sw.stop(4000);
|
||||
TEST_ASSERT_EQUAL_UINT32(3000, sw.elapsed(9000));
|
||||
sw.start(10000);
|
||||
sw.start(11000); // already running: no restart
|
||||
TEST_ASSERT_EQUAL_UINT32(5000, sw.elapsed(12000));
|
||||
sw.reset();
|
||||
TEST_ASSERT_FALSE(sw.running());
|
||||
TEST_ASSERT_EQUAL_UINT32(0, sw.elapsed(20000));
|
||||
sw.start(0xFFFFFF00u); // across the millis() overflow
|
||||
TEST_ASSERT_EQUAL_UINT32(0x200, sw.elapsed(0x100));
|
||||
|
||||
PageElement e; // textOf: 65 s on the stopwatch
|
||||
e.type = ElementType::Stopwatch;
|
||||
e.format = STOPWATCH_M_SS;
|
||||
TEST_ASSERT_EQUAL_STRING("1:05", textOf(e).c_str());
|
||||
e.format = STOPWATCH_H_MM;
|
||||
TEST_ASSERT_EQUAL_STRING("0:01", textOf(e).c_str());
|
||||
e.format = STOPWATCH_H_MM_SS;
|
||||
strcpy(e.text, "T ");
|
||||
TEST_ASSERT_EQUAL_STRING("T 0:01:05", textOf(e).c_str());
|
||||
TEST_ASSERT_TRUE(elementShowsSeconds(e, LocalTime(), true));
|
||||
TEST_ASSERT_FALSE(elementShowsSeconds(e, LocalTime(), false)); // stopped
|
||||
}
|
||||
|
||||
void test_home_assistant_multiplier()
|
||||
{
|
||||
PageElement e;
|
||||
e.type = ElementType::HaValue;
|
||||
e.factor = 1000;
|
||||
strcpy(e.suffix, " l");
|
||||
TEST_ASSERT_EQUAL_STRING("637 l", textOf(e, "0.637").c_str());
|
||||
e.factor = 0.001f;
|
||||
e.decimals = 1;
|
||||
strcpy(e.suffix, "kWh");
|
||||
TEST_ASSERT_EQUAL_STRING("26.3kWh", textOf(e, "26300").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("--kWh", textOf(e, "unknown").c_str());
|
||||
|
||||
DynamicPage page;
|
||||
page.element_count = 1;
|
||||
page.elements[0] = e;
|
||||
strcpy(page.elements[0].entity, "sensor.x");
|
||||
page.elements[0].factor = 0;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
}
|
||||
|
||||
void test_degree_sign_in_the_fonts()
|
||||
{
|
||||
const char degree[] = {DEGREE_SIGN, 0};
|
||||
TEST_ASSERT_EQUAL(4, textWidth(fontNamed("Normal"), degree));
|
||||
TEST_ASSERT_EQUAL(3, textWidth(fontNamed("Small"), degree));
|
||||
TEST_ASSERT_EQUAL(6, textWidth(fontNamed("Full height"), degree));
|
||||
|
||||
DynamicPage page;
|
||||
page.element_count = 1;
|
||||
page.elements[0].type = ElementType::Text;
|
||||
strcpy(page.elements[0].text, "17.5\x7f" "C");
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scrolling and images
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static DynamicPage scrollPage()
|
||||
{
|
||||
DynamicPage page;
|
||||
page.element_count = 1;
|
||||
PageElement &e = page.elements[0];
|
||||
e.type = ElementType::Text;
|
||||
strcpy(e.text, "A long text that does not fit");
|
||||
e.x = 10;
|
||||
e.y = 2;
|
||||
e.scroll_width = 30;
|
||||
e.scroll_speed = 10;
|
||||
return page;
|
||||
}
|
||||
|
||||
static bool anyDot(const uint8_t *b, int x0, int x1)
|
||||
{
|
||||
for (int y = 0; y < BITMAP_HEIGHT; y++)
|
||||
for (int x = x0; x < x1; x++)
|
||||
if (bitmapPixel(b, x, y))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void test_scrolling_text_moves_in_its_window()
|
||||
{
|
||||
FakeResources res;
|
||||
DynamicPage page = scrollPage();
|
||||
uint8_t a[BITMAP_BYTES], b[BITMAP_BYTES];
|
||||
ElementBox box;
|
||||
uint32_t wait = renderPage(page, at(12, 0), 0, res, a, &box);
|
||||
TEST_ASSERT_EQUAL(100, wait); // 10 dots per second
|
||||
TEST_ASSERT_EQUAL(10, box.x);
|
||||
TEST_ASSERT_EQUAL(30, box.width);
|
||||
TEST_ASSERT_FALSE(anyDot(a, 0, 10)); // clipped to the window
|
||||
TEST_ASSERT_FALSE(anyDot(a, 40, BITMAP_WIDTH));
|
||||
TEST_ASSERT_TRUE(anyDot(a, 10, 40));
|
||||
|
||||
TEST_ASSERT_EQUAL(60, renderPage(page, at(12, 0), 1040, res, b, &box)); // until 1100
|
||||
TEST_ASSERT_TRUE(memcmp(a, b, BITMAP_BYTES) != 0);
|
||||
// One round later the same image again: text width + gap
|
||||
int period = textWidth(fontNamed("Normal"), page.elements[0].text) + SCROLL_GAP;
|
||||
renderPage(page, at(12, 0), period * 100, res, b);
|
||||
TEST_ASSERT_EQUAL_MEMORY(a, b, BITMAP_BYTES);
|
||||
}
|
||||
|
||||
void test_short_text_in_a_window_does_not_scroll()
|
||||
{
|
||||
FakeResources res;
|
||||
DynamicPage page = scrollPage();
|
||||
strcpy(page.elements[0].text, "Hi");
|
||||
page.elements[0].align = Align::Right; // right edge of the window at x 10
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
ElementBox box;
|
||||
TEST_ASSERT_EQUAL(0, renderPage(page, at(12, 0), 0, res, b, &box));
|
||||
TEST_ASSERT_EQUAL(-20, box.x);
|
||||
TEST_ASSERT_TRUE(anyDot(b, 0, 10)); // the text at the right of the window
|
||||
}
|
||||
|
||||
void test_render_asks_for_the_next_second()
|
||||
{
|
||||
FakeResources res;
|
||||
DynamicPage page;
|
||||
page.element_count = 1;
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
LocalTime t = at(12, 0);
|
||||
t.millisecond = 300;
|
||||
TEST_ASSERT_EQUAL(0, renderPage(page, t, 0, res, b));
|
||||
page.elements[0].format = CLOCK_HH_MM_SS;
|
||||
TEST_ASSERT_EQUAL(700, renderPage(page, t, 0, res, b));
|
||||
}
|
||||
|
||||
class ImageResources : public FakeResources
|
||||
{
|
||||
public:
|
||||
bool background(const char *name, uint8_t *bitmap) override
|
||||
{
|
||||
memset(bitmap, 0, BITMAP_BYTES);
|
||||
if (strcmp(name, "icon sunny") == 0)
|
||||
{
|
||||
setBitmapPixel(bitmap, 1, 1, true);
|
||||
setBitmapPixel(bitmap, 3, 2, true);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "icon default") == 0)
|
||||
{
|
||||
setBitmapPixel(bitmap, 0, 0, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void test_image_element_by_state()
|
||||
{
|
||||
ImageResources res;
|
||||
DynamicPage page;
|
||||
page.element_count = 1;
|
||||
PageElement &e = page.elements[0];
|
||||
e.type = ElementType::Image;
|
||||
strcpy(e.text, "icon ");
|
||||
strcpy(e.entity, "weather.home");
|
||||
e.x = 50;
|
||||
e.y = 5;
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
ElementBox box;
|
||||
res.value = "sunny";
|
||||
renderPage(page, at(12, 0), 0, res, b, &box);
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 51, 6));
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 53, 7));
|
||||
TEST_ASSERT_EQUAL(51, box.x);
|
||||
TEST_ASSERT_EQUAL(6, box.y);
|
||||
TEST_ASSERT_EQUAL(3, box.width);
|
||||
TEST_ASSERT_EQUAL(2, box.height);
|
||||
|
||||
res.value = "rainy"; // no bitmap for it: the default
|
||||
renderPage(page, at(12, 0), 0, res, b, &box);
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 50, 5));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 51, 6));
|
||||
|
||||
e.entity[0] = '\0'; // fixed bitmap
|
||||
strcpy(e.text, "icon sunny");
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
renderPage(page, at(12, 0), 0, res, b);
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 51, 6));
|
||||
strcpy(e.text, "../x");
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
}
|
||||
|
||||
void test_moving_page_is_redrawn_when_due()
|
||||
{
|
||||
frames.wait = 250;
|
||||
run(12, 0);
|
||||
TEST_ASSERT_EQUAL(1, (int)frames.shown.size());
|
||||
TEST_ASSERT_TRUE(frames.last_redraw);
|
||||
run(12, 0, 200); // not yet (the run steps are 100 ms)
|
||||
TEST_ASSERT_EQUAL(2, (int)frames.shown.size());
|
||||
TEST_ASSERT_FALSE(frames.last_redraw);
|
||||
frames.wait = 0; // stops moving
|
||||
run(12, 0, 1000);
|
||||
TEST_ASSERT_EQUAL(3, (int)frames.shown.size());
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
@@ -1288,6 +1615,18 @@ int main()
|
||||
RUN_TEST(test_render_unknown_font_uses_normal);
|
||||
RUN_TEST(test_builtin_clock_page_is_the_old_clock);
|
||||
RUN_TEST(test_default_page_is_the_builtin_clock);
|
||||
RUN_TEST(test_clock_with_seconds);
|
||||
RUN_TEST(test_countdown_target_parse_and_format);
|
||||
RUN_TEST(test_countdown_formats);
|
||||
RUN_TEST(test_countdown_shows_seconds_only_when_needed);
|
||||
RUN_TEST(test_stopwatch);
|
||||
RUN_TEST(test_home_assistant_multiplier);
|
||||
RUN_TEST(test_degree_sign_in_the_fonts);
|
||||
RUN_TEST(test_scrolling_text_moves_in_its_window);
|
||||
RUN_TEST(test_short_text_in_a_window_does_not_scroll);
|
||||
RUN_TEST(test_render_asks_for_the_next_second);
|
||||
RUN_TEST(test_image_element_by_state);
|
||||
RUN_TEST(test_moving_page_is_redrawn_when_due);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
@@ -103,6 +103,27 @@ void test_display()
|
||||
send("D\n");
|
||||
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(0, display.last("display")->a); // not forced
|
||||
TEST_ASSERT_EQUAL(0, display.last("display")->b); // with the effect
|
||||
}
|
||||
|
||||
void test_display_instant_frame()
|
||||
{
|
||||
send("D1\n");
|
||||
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(0, display.last("display")->a);
|
||||
TEST_ASSERT_EQUAL(1, display.last("display")->b); // without the effect
|
||||
}
|
||||
|
||||
void test_refresh_waits_for_idle_after_instant_frames()
|
||||
{
|
||||
last_refresh = fake_millis;
|
||||
fake_millis += 120000;
|
||||
send("D1\n");
|
||||
TEST_ASSERT_EQUAL(1, display.count("display")); // no full redraw: more frames follow
|
||||
fake_millis += 3000;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(2, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(1, display.calls.back().a);
|
||||
}
|
||||
|
||||
void test_lowercase_and_uppercase_commands()
|
||||
@@ -498,6 +519,8 @@ int main()
|
||||
RUN_TEST(test_direct_pixel_flips_immediately_and_resets_direct_mode);
|
||||
RUN_TEST(test_fill);
|
||||
RUN_TEST(test_display);
|
||||
RUN_TEST(test_display_instant_frame);
|
||||
RUN_TEST(test_refresh_waits_for_idle_after_instant_frames);
|
||||
RUN_TEST(test_lowercase_and_uppercase_commands);
|
||||
RUN_TEST(test_command_split_over_several_reads_with_crlf);
|
||||
RUN_TEST(test_print_text_with_newline_escape);
|
||||
|
||||
Reference in new issue
Block a user