Files
slate32/test/settings_calibration.lua
T
evan 1aa3a59ca9 feat: give /.lua/main.lua the whole screen and the app contract
The firmware knew where apps, data and modules lived, called four globals,
and painted a status bar into a strip it clipped every app out of. None of
that was its business, and the viewport made the bar something an app could
neither compose with nor replace.

It now loads one file. main.lua mounts the route inside its own node tree,
so the bar is a sibling of the app rather than chrome painted over it: one
layout, one hit test, no inset arithmetic and no invalidation flags on the
C++ side. Apps and main.lua are tables -- they share a lua_State, so globals
would collide -- and a screen changes by rebuilding from node().
2026-08-04 21:15:21 -04:00

141 lines
5.3 KiB
Lua

-- Run: lua test/settings_calibration.lua
-- Drives the settings app on a desktop Lua, through the shared fake device. Controls are
-- pressed by label rather than by coordinate: where a row lands is layout, and layout is
-- asserted in test/ui_layout_test.cpp against the C++ the panel actually runs.
package.path = "sdcard/.lua/lib/?.lua;test/?.lua;" .. package.path
local device = require("fake_device").install()
local SETTINGS = "sdcard/.lua/apps/Settings/main.lua"
local app = device.start(SETTINGS)
local tapRow = device.tap
-- Relaunching is how a test gets a clean screen, because that is what the firmware does:
-- a new app is a new state that builds itself from nothing.
local function restart()
app = device.start(SETTINGS)
end
-- A perfectly linear panel spanning raw 200..3800 over 320x480 must round-trip
-- to those same extremes from the two inset samples.
local inset, w, h = 30, 320, 480
local function raw(pixel, size)
return 200 + (3800 - 200) * pixel / size
end
local s1 = { x = raw(inset, w), y = raw(inset, h) }
local s2 = { x = raw(w - inset, w), y = raw(h - inset, h) }
local x0, y0, x1, y1 = app.computeCalibration(s1, s2, w, h, inset)
assert(math.abs(x0 - 200) <= 1, "x0 " .. x0)
assert(math.abs(y0 - 200) <= 1, "y0 " .. y0)
assert(math.abs(x1 - 3800) <= 1, "x1 " .. x1)
assert(math.abs(y1 - 3800) <= 1, "y1 " .. y1)
-- A flipped panel (raw decreasing with pixel) must yield a descending range.
local s3 = { x = 3800 - raw(inset, w) + 200, y = s1.y }
local s4 = { x = 3800 - raw(w - inset, w) + 200, y = s2.y }
local fx0, _, fx1 = app.computeCalibration(s3, s4, w, h, inset)
assert(fx0 > fx1, "flipped axis should descend")
-- Timezone cycles through the picker list.
local zones = require "timezones"
restart()
tapRow "Timezone"
assert(settings.getTimezone() == zones[2].tz, "timezone " .. settings.getTimezone())
-- Cycling rebuilds the screen, which is the only way a screen changes now, so the card
-- shows the new zone and the menu is still the same size.
local beforeCycle = node.getCount()
assert(device.labelled(zones[2].name), "timezone card did not take its new value")
assert(node.getCount() == beforeCycle, "the rebuilt menu grew")
tapRow "Timezone"
assert(settings.getTimezone() == zones[3].tz, "timezone " .. settings.getTimezone())
-- Rotation cycles through the four quarter turns and wraps back to 0.
restart()
for _, expected in ipairs { 90, 180, 270, 0 } do
tapRow "Rotation"
assert(settings.getRotation() == expected, "rotation " .. settings.getRotation())
end
-- Calibration collects one sample per target and saves on the second release.
restart()
tapRow "Calibrate"
app.tick() -- release after the menu tap arms sampling
device.raw = { s1.x, s1.y }
app.tick()
device.raw = nil
app.tick()
device.raw = { s2.x, s2.y }
app.tick()
device.raw = nil
app.tick()
local saved = device.calibration
assert(saved, "calibration was not saved")
assert(math.abs(saved[1] - 200) <= 1, "saved x0 " .. saved[1])
-- WiFi controls reflect connection state without exposing configured network details on the card.
device.status = { state = "disconnected", ssid = "", ip = "", rssi = 0 }
restart()
tapRow "WiFi"
assert(not device.find "connect" and not device.find "disconnect", "unconfigured wifi has no toggle")
device.status = { state = "disconnected", ssid = "saved", ip = "", rssi = 0 }
restart()
tapRow "WiFi"
assert(device.find "connect" and not device.find "disconnect", "configured wifi can reconnect")
tapRow "connect"
assert(device.wifiReconnect, "wifi reconnect should use saved credentials")
device.status = { state = "connected", ssid = "saved", ip = "192.168.1.2", rssi = -40 }
restart()
tapRow "WiFi"
assert(device.find "disconnect" and not device.find "connect", "connected wifi can disconnect")
tapRow "disconnect"
assert(device.status.state == "disconnected" and device.status.ssid == "saved", "disconnect preserves wifi intent")
-- Open networks connect directly from scan results.
restart()
device.networks = { { ssid = "qemu", rssi = -25, secure = false } }
tapRow "WiFi"
tapRow "scan networks"
app.tick()
tapRow "qemu"
assert(device.connected, "open network should connect without a keyboard")
assert(device.connected[1] == "qemu" and device.connected[2] == "", "open wifi connect")
-- Secure networks route through the keyboard and preserve typed punctuation.
restart()
device.networks = { { ssid = "secure", rssi = -40, secure = true } }
tapRow "WiFi"
tapRow "scan networks"
app.tick()
tapRow "secure"
app.tick()
-- The keyboard is one custom node with no child per key, so its keys are reached through
-- its own geometry rather than by label. getRect() answers the same box to the test and
-- to the widget, which is what makes the two agree on where "q" is.
local keyboard = require "keyboard"
local board = device.findKind "custom"
assert(board, "the secure network did not open a keyboard")
local boardRect = { x = 0, y = 0, w = 10000, h = 10000 }
local function typeKey(label)
local pressed
keyboard.eachKey("lower", boardRect, function(_, keyLabel, x, y, width)
if keyLabel == label and not pressed then
pressed = { x = x + width // 2, y = y + 15 }
end
end)
assert(pressed, "no key labelled " .. label)
device.press(board, pressed.x, pressed.y)
end
for key in ("qemuqemu"):gmatch "." do
typeKey(key)
end
typeKey "OK"
assert(device.connected[1] == "secure" and device.connected[2] == "qemuqemu", "secure wifi password")
print "ok"