aboutsummaryrefslogtreecommitdiff
path: root/test/ui
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-19 22:28:09 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-19 22:28:09 +0200
commit2373b6f02ef027a423762d5d6349cd9e0f36090e (patch)
tree7509b0b1905d7c593074bba8ed4c94e30e437604 /test/ui
parent35042ff04ffc50e55ff59a385849442be4429d6b (diff)
test(ui): drive the simulator with a scripted flow and check every screen
tools/ui_flow.py clicks buttons and taps from the device files, waits for DIAG shown lines and compares 10 screens per watch with goldens on four test and three spot-check devices. Adds the fixture-wrap generator, UI logic tests and driver tests; the launcher opens apps from their glance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'test/ui')
-rw-r--r--test/ui/UiLogicTest.mc92
1 files changed, 92 insertions, 0 deletions
diff --git a/test/ui/UiLogicTest.mc b/test/ui/UiLogicTest.mc
new file mode 100644
index 0000000..01b7dae
--- /dev/null
+++ b/test/ui/UiLogicTest.mc
@@ -0,0 +1,92 @@
+// Author: cirakg & claude ai
+// Summary: Tests for the UI's logic without a screen: the code cache, code grouping, text helpers and option defaults.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+function codeCacheSplitsCodesForReading(logger as Test.Logger) as Boolean {
+ var cases = [["123456", "123|456"], ["1234567", "123|4567"], ["12345678", "1234|5678"], ["R7VXK", "R7VXK"]];
+ for (var i = 0; i < cases.size(); i++) {
+ var c = cases[i] as Array<String>;
+ var parts = CodeCache.split(c[0]);
+ var joined = parts.size() == 2 ? parts[0] + "|" + parts[1] : parts[0];
+ if (!TestSupport.sameString(logger, "split " + c[0], joined, c[1])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// The cache gives the same codes as the OTP module, keeps them until the
+// period ends, and learns the period and cost from the encrypted part.
+(:test)
+function codeCacheComputesAndExpiresCodes(logger as Test.Logger) as Boolean {
+ var vault = new Vault(new MemoryBackend(), new FakeTime());
+ var e = StorageFixtures.totp("Cache");
+ var secret = Bytes.copy(e.secret as ByteArray);
+ var id = vault.add(e);
+ var cache = new CodeCache(vault);
+ var now = 1800000026l; // 34 s left in the fixture's 60 s period
+ var expected = Otp.totp(Hashes.SHA256, secret, now, 60, 8);
+ var next = Otp.totp(Hashes.SHA256, secret, now + 60, 60, 8);
+ var unknownCost = cache.costOf(e);
+ var code = cache.code(id, now, 0);
+ var stillCached = cache.cached(id, now + 33) != null;
+ var expired = cache.cached(id, now + 34) == null;
+ return TestSupport.sameString(logger, "current", code, expected)
+ && TestSupport.sameString(logger, "next", cache.code(id, now, 1), next)
+ && stillCached && expired && cache.periodOf(id) == 60
+ && unknownCost == CodeCache.COST_SHA512 && cache.costOf(e) == CodeCache.COST_NATIVE;
+}
+
+(:test)
+function codeCacheHotpAndInvalidate(logger as Test.Logger) as Boolean {
+ var vault = new Vault(new MemoryBackend(), new FakeTime());
+ var id = vault.add(StorageFixtures.hotp("Counter", 5l));
+ var cache = new CodeCache(vault);
+ var first = cache.code(id, 0l, 0);
+ vault.setCounter(id, 6l);
+ var stale = cache.code(id, 0l, 0);
+ cache.invalidate(id);
+ var fresh = cache.code(id, 0l, 0);
+ // RFC 4226 Appendix D: counters 5 and 6.
+ return first.equals("254676") && stale.equals("254676") && fresh.equals("287922");
+}
+
+(:test)
+function codeCacheRefusesLockedEntriesWhileLocked(logger as Test.Logger) as Boolean {
+ var vault = new Vault(new MemoryBackend(), new FakeTime());
+ StorageFixtures.setPin(vault, StorageFixtures.PIN);
+ var e = StorageFixtures.totp("Locked");
+ e.locked = true;
+ var id = vault.add(e);
+ var cache = new CodeCache(vault);
+ var unlockedCode = cache.code(id, 1800000026l, 0);
+ vault.lock();
+ cache.clear();
+ var code = 0;
+ try {
+ cache.code(id, 1800000026l, 0);
+ } catch (err instanceof VaultError) {
+ code = err.code;
+ }
+ return unlockedCode.length() == 8 && code == VaultError.LOCKED && cache.cached(id, 1800000026l) == null;
+}
+
+(:test)
+function uiTextHelpers(logger as Test.Logger) as Boolean {
+ var parts = Ui.split("a b c", " ");
+ return Ui.isDigits("123 456") && !Ui.isDigits("12a") && !Ui.isDigits("") && !Ui.isDigits("------")
+ && parts.size() == 4 && parts[0].equals("a") && parts[2].equals("") && parts[3].equals("c")
+ && Ui.minutesSeconds(90).equals("1:30") && Ui.minutesSeconds(3600).equals("60:00") && Ui.minutesSeconds(5).equals("0:05");
+}
+
+// resources/settings/properties.xml defaults give README 10.2's defaults.
+(:test)
+function optionsDefaults(logger as Test.Logger) as Boolean {
+ var o = new Options();
+ o.load();
+ return o.autoLockMs == 60000 && !o.eraseAfterFailures && o.timeOffset == 0 && o.codeSize == Options.CODE_LARGE
+ && o.theme == Options.THEME_DARK && o.showGroups && !o.encryptNames && !o.glanceCode && o.fullBrightness;
+}