import Toybox.System; // Юнит-тесты ядра (спека api_specs/02-core.md): зоны, метрики подхода, // таймер отдыха, гистерезис вибрации, клампы настроек, форматирование. // Запуск — TestEntry (watch-app) в симуляторе; проверки — CoreChecks. // Поле данных нельзя стартовать как приложение, поэтому тестовая сборка // — отдельный манифест test-manifest.xml c watch-app-входом. // ---------- ZoneCalc ---------- function zoneTanakaTest(c) { var z = new ZoneCalc(); // возраст 45: (2080 − 315)/10 = 176,5 — используется точное значение ×10 c.eq(z.maxBpm10For(0, 45), 1765, "Tanaka age 45 -> 1765 (x10)"); c.eq(z.maxBpm10For(180, 45), 1800, "manual max has priority"); c.eq(z.maxBpm10For(0, 5), 1765, "age out of range -> default 45"); c.eq(z.maxBpm10For(null, null), 1765, "nulls -> default 45"); } function zoneBoundariesTest(c) { var z = new ZoneCalc(); // maxBpm 180 (maxBpm10 1800): 50 % = 90, 70 % = 126, 85 % = 153 c.eq(z.zoneFor(89, 1800), z.ZONE_BLUE, "89 -> blue"); c.eq(z.zoneFor(90, 1800), z.ZONE_GREEN, "90 -> green"); c.eq(z.zoneFor(125, 1800), z.ZONE_GREEN, "125 -> green"); c.eq(z.zoneFor(126, 1800), z.ZONE_YELLOW, "126 -> yellow"); c.eq(z.zoneFor(152, 1800), z.ZONE_YELLOW, "152 -> yellow"); c.eq(z.zoneFor(153, 1800), z.ZONE_RED, "153 -> red"); c.eq(z.zoneFor(null, 1800), z.ZONE_NONE, "null hr -> none"); c.eq(z.zoneFor(0, 1800), z.ZONE_NONE, "zero hr -> none"); } // ---------- SetTracker ---------- function setAvgRoundingTest(c) { var s = new SetTracker(); s.onSample(100); s.onSample(101); s.onSample(101); c.eq(s.avgBpm(), 101, "100+101+101 -> 101 (round nearest)"); } function setNullSkippedTest(c) { var s = new SetTracker(); s.onSample(100); s.onSample(null); s.onSample(102); c.eq(s.avgBpm(), 101, "null samples skipped -> 101"); } function setLapResetsTest(c) { var s = new SetTracker(); s.onSample(150); c.eq(s.avgBpm(), 150, "before lap avg 150"); s.onLap(60000); c.isNull(s.avgBpm(), "lap resets samples"); s.onSample(120); c.eq(s.avgBpm(), 120, "after lap only new sample counted"); } function setElapsedTest(c) { var s = new SetTracker(); s.onLap(60000); c.eq(s.elapsedMs(75500), 15500, "elapsed from lap border"); } // ---------- RestTimer ---------- function restCountdownTest(c) { var r = new RestTimer(); c.eq(r.isResting(60000), false, "before start not active -> false"); r.start(60000, 120); c.eq(r.remainingMs(60000), 120000, "just started: full 120 s"); c.eq(r.remainingMs(120000), 60000, "half-way: 60 s left"); c.eq(r.remainingMs(180000), 0, "at end: 0"); c.eq(r.isResting(179999), true, "resting while remaining > 0"); c.eq(r.isResting(180000), false, "not resting at 0"); } function restEndFiresOnceTest(c) { var r = new RestTimer(); r.start(0, 120); c.eq(r.onEnd(119000), false, "not ended yet"); c.eq(r.onEnd(120000), true, "fires at end"); c.eq(r.onEnd(121000), false, "fires only once"); r.start(130000, 60); c.eq(r.onEnd(190000), true, "new start re-arms"); } // ---------- Vibe (гистерезис красной зоны) ---------- function vibeRedHysteresisTest(c) { var v = new Vibe(); c.eq(v.onZone(true, 0), true, "enter red -> vibrate"); c.eq(v.onZone(true, 30000), false, "still red <60 s -> no"); c.eq(v.onZone(true, 59999), false, "still red <60 s -> no"); c.eq(v.onZone(true, 61000), true, "still red >=60 s -> vibrate"); } function vibeRedResetTest(c) { var v = new Vibe(); c.eq(v.onZone(true, 0), true, "enter red -> vibrate"); c.eq(v.onZone(false, 10000), false, "leave red -> no vibrate"); c.eq(v.onZone(true, 20000), true, "re-enter red -> vibrate (reset)"); } // ---------- Format ---------- function mmssTest(c) { var f = new Format(); c.eq(f.mmss(0), "0:00", "0 -> 0:00"); c.eq(f.mmss(59999), "0:59", "59999 -> 0:59"); c.eq(f.mmss(60000), "1:00", "60000 -> 1:00"); c.eq(f.mmss(65000), "1:05", "65000 -> 1:05"); c.eq(f.mmss(600000), "10:00", "600000 -> 10:00"); c.eq(f.mmss(null), "0:00", "null -> 0:00"); c.eq(f.mmss(-5), "0:00", "negative -> 0:00"); } function divTest(c) { var f = new Format(); c.eq(f.div(5, 2), 2, "5/2 floor = 2"); c.eq(f.div(1, 2), 0, "1/2 floor = 0"); c.eq(f.div(600000, 1000), 600, "600000/1000 = 600"); } // ---------- FieldSettings (клампы) ---------- function clampTest(c) { var s = new FieldSettings(); c.eq(s.clamp(10, 30, 600, 120), 120, "rest below min -> default"); c.eq(s.clamp(600, 30, 600, 120), 600, "rest max ok"); c.eq(s.clamp(30, 30, 600, 120), 30, "rest min ok"); c.eq(s.clamp(null, 30, 600, 120), 120, "null -> default"); c.eq(s.clamp("120", 30, 600, 120), 120, "string -> default"); c.eq(s.clamp(-5, 0, 250, 0), 0, "hr below min -> default 0"); c.eq(s.clamp(250, 0, 250, 0), 250, "hr max ok"); c.eq(s.clamp(200, 14, 100, 45), 45, "age above max -> default"); c.eq(s.clamp(14, 14, 100, 45), 14, "age min ok"); }