chore: lsp + format
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
// Host doubles for every provider, deliberately dumb: they record what a binding asked
|
||||
// for and hand back canned values, so a test asserts the marshalling rather than a device.
|
||||
// Host doubles for every provider, deliberately dumb: they record what a
|
||||
// binding asked for and hand back canned values, so a test asserts the
|
||||
// marshalling rather than a device.
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -56,7 +57,9 @@ struct Fs : FsProvider {
|
||||
size_t at = 0;
|
||||
int32_t read(char* out, int32_t maxBytes) override {
|
||||
const size_t remaining = content.size() - at;
|
||||
const size_t count = remaining < static_cast<size_t>(maxBytes) ? remaining : static_cast<size_t>(maxBytes);
|
||||
const size_t count = remaining < static_cast<size_t>(maxBytes)
|
||||
? remaining
|
||||
: static_cast<size_t>(maxBytes);
|
||||
content.copy(out, count, at);
|
||||
at += count;
|
||||
return static_cast<int32_t>(count);
|
||||
@@ -64,47 +67,65 @@ struct Fs : FsProvider {
|
||||
};
|
||||
|
||||
FileReader* openRead(const std::string& path) override {
|
||||
const std::map<std::string, std::string>::const_iterator found = files.find(path);
|
||||
if (found == files.end()) return nullptr;
|
||||
const std::map<std::string, std::string>::const_iterator found =
|
||||
files.find(path);
|
||||
if (found == files.end())
|
||||
return nullptr;
|
||||
Reader* reader = new Reader();
|
||||
reader->content = found->second;
|
||||
return reader;
|
||||
}
|
||||
|
||||
bool exists(const std::string& path) const override { return files.count(path) != 0; }
|
||||
bool exists(const std::string& path) const override {
|
||||
return files.count(path) != 0;
|
||||
}
|
||||
Status fileSize(const std::string& path, int32_t& size) const override {
|
||||
const std::map<std::string, std::string>::const_iterator found = files.find(path);
|
||||
if (found == files.end()) return Status::failure("no such file");
|
||||
const std::map<std::string, std::string>::const_iterator found =
|
||||
files.find(path);
|
||||
if (found == files.end())
|
||||
return Status::failure("no such file");
|
||||
size = static_cast<int32_t>(found->second.size());
|
||||
return Status::success();
|
||||
}
|
||||
Status listDirs(const std::string&, std::vector<std::string>& names) const override {
|
||||
Status listDirs(const std::string&,
|
||||
std::vector<std::string>& names) const override {
|
||||
names.push_back("apps");
|
||||
return Status::success();
|
||||
}
|
||||
Status listFiles(const std::string&, std::vector<std::string>& names) const override {
|
||||
Status listFiles(const std::string&,
|
||||
std::vector<std::string>& names) const override {
|
||||
names.push_back("main.lua");
|
||||
return Status::success();
|
||||
}
|
||||
Status mkdir(const std::string&) override { return Status::success(); }
|
||||
Status readFile(const std::string& path, int32_t maxBytes, std::string& content) const override {
|
||||
const std::map<std::string, std::string>::const_iterator found = files.find(path);
|
||||
if (found == files.end()) return Status::failure("no such file");
|
||||
if (static_cast<int32_t>(found->second.size()) > maxBytes) return Status::failure("too large");
|
||||
Status readFile(const std::string& path, int32_t maxBytes,
|
||||
std::string& content) const override {
|
||||
const std::map<std::string, std::string>::const_iterator found =
|
||||
files.find(path);
|
||||
if (found == files.end())
|
||||
return Status::failure("no such file");
|
||||
if (static_cast<int32_t>(found->second.size()) > maxBytes)
|
||||
return Status::failure("too large");
|
||||
content = found->second;
|
||||
return Status::success();
|
||||
}
|
||||
Status readLineAt(const std::string& path, int32_t offset, int32_t, bool& found, std::string& line,
|
||||
Status readLineAt(const std::string& path, int32_t offset, int32_t,
|
||||
bool& found, std::string& line,
|
||||
int32_t& nextOffset) const override {
|
||||
const std::map<std::string, std::string>::const_iterator file = files.find(path);
|
||||
if (file == files.end()) return Status::failure("no such file");
|
||||
const std::map<std::string, std::string>::const_iterator file =
|
||||
files.find(path);
|
||||
if (file == files.end())
|
||||
return Status::failure("no such file");
|
||||
if (offset >= static_cast<int32_t>(file->second.size())) {
|
||||
found = false;
|
||||
return Status::success();
|
||||
}
|
||||
const size_t end = file->second.find('\n', offset);
|
||||
line = file->second.substr(offset, end == std::string::npos ? std::string::npos : end - offset);
|
||||
nextOffset = end == std::string::npos ? static_cast<int32_t>(file->second.size()) : static_cast<int32_t>(end + 1);
|
||||
line = file->second.substr(
|
||||
offset, end == std::string::npos ? std::string::npos : end - offset);
|
||||
nextOffset = end == std::string::npos
|
||||
? static_cast<int32_t>(file->second.size())
|
||||
: static_cast<int32_t>(end + 1);
|
||||
found = true;
|
||||
return Status::success();
|
||||
}
|
||||
@@ -113,8 +134,11 @@ struct Fs : FsProvider {
|
||||
return Status::success();
|
||||
}
|
||||
Status removeTree(const std::string&) override { return Status::success(); }
|
||||
Status rename(const std::string&, const std::string&) override { return Status::success(); }
|
||||
Status writeFile(const std::string& path, const std::string& content) override {
|
||||
Status rename(const std::string&, const std::string&) override {
|
||||
return Status::success();
|
||||
}
|
||||
Status writeFile(const std::string& path,
|
||||
const std::string& content) override {
|
||||
files[path] = content;
|
||||
written = content;
|
||||
return Status::success();
|
||||
@@ -135,30 +159,47 @@ struct Gui : GuiProvider {
|
||||
int32_t height() const override { return 240; }
|
||||
int32_t rotation() const override { return degrees; }
|
||||
void setRotation(int32_t value) override { degrees = value; }
|
||||
int32_t color(int32_t r, int32_t g, int32_t b) const override { return (r << 16) | (g << 8) | b; }
|
||||
int32_t color(int32_t r, int32_t g, int32_t b) const override {
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
void clear(int32_t) override { trace += "clear;"; }
|
||||
void fillRect(int32_t, int32_t, int32_t, int32_t, int32_t) override { trace += "fillRect;"; }
|
||||
void drawRect(int32_t, int32_t, int32_t, int32_t, int32_t) override { trace += "drawRect;"; }
|
||||
void drawLine(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) override { trace += "drawLine;"; }
|
||||
void fillRect(int32_t, int32_t, int32_t, int32_t, int32_t) override {
|
||||
trace += "fillRect;";
|
||||
}
|
||||
void drawRect(int32_t, int32_t, int32_t, int32_t, int32_t) override {
|
||||
trace += "drawRect;";
|
||||
}
|
||||
void drawLine(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) override {
|
||||
trace += "drawLine;";
|
||||
}
|
||||
void drawPixel(int32_t, int32_t, int32_t) override { trace += "drawPixel;"; }
|
||||
void drawCircle(int32_t, int32_t, int32_t, int32_t, int32_t) override { trace += "drawCircle;"; }
|
||||
void fillCircle(int32_t, int32_t, int32_t, int32_t, const int32_t*) override { trace += "fillCircle;"; }
|
||||
void roundRect(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, const int32_t* top, const int32_t* bottom,
|
||||
void drawCircle(int32_t, int32_t, int32_t, int32_t, int32_t) override {
|
||||
trace += "drawCircle;";
|
||||
}
|
||||
void fillCircle(int32_t, int32_t, int32_t, int32_t, const int32_t*) override {
|
||||
trace += "fillCircle;";
|
||||
}
|
||||
void roundRect(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t,
|
||||
const int32_t* top, const int32_t* bottom,
|
||||
const int32_t* border) override {
|
||||
if (top && bottom && *top != *bottom) gradient = true;
|
||||
if (top && bottom && *top != *bottom)
|
||||
gradient = true;
|
||||
trace += "roundRect(";
|
||||
trace += top ? "fill" : "-";
|
||||
trace += border ? ",border" : ",-";
|
||||
trace += ");";
|
||||
}
|
||||
void setFullscreen(bool on) override { fullscreen = on; }
|
||||
// Stands in for an e-ink panel, where a second commit is a second visible refresh.
|
||||
// Stands in for an e-ink panel, where a second commit is a second visible
|
||||
// refresh.
|
||||
void commit() override { commits++; }
|
||||
int commits = 0;
|
||||
void fillPolygon(const int32_t*, const int32_t*, size_t count, int32_t) override {
|
||||
void fillPolygon(const int32_t*, const int32_t*, size_t count,
|
||||
int32_t) override {
|
||||
trace += "fillPolygon" + std::to_string(count) + ";";
|
||||
}
|
||||
Status drawBmp(const std::string&, const int32_t*, const int32_t*, const int32_t*, const int32_t*) override {
|
||||
Status drawBmp(const std::string&, const int32_t*, const int32_t*,
|
||||
const int32_t*, const int32_t*) override {
|
||||
trace += "drawBmp;";
|
||||
return Status::success();
|
||||
}
|
||||
@@ -166,7 +207,8 @@ struct Gui : GuiProvider {
|
||||
return static_cast<int32_t>(text.size()) * 8;
|
||||
}
|
||||
int32_t fontHeight(int32_t, int32_t) const override { return 16; }
|
||||
void drawText(int32_t, int32_t, int32_t, const std::string& text, int32_t, int32_t, const int32_t*) override {
|
||||
void drawText(int32_t, int32_t, int32_t, const std::string& text, int32_t,
|
||||
int32_t, const int32_t*) override {
|
||||
trace += "drawText(" + text + ");";
|
||||
}
|
||||
};
|
||||
@@ -177,8 +219,10 @@ struct Http : HttpProvider {
|
||||
std::vector<HttpHeader> headers;
|
||||
int32_t limit = 0;
|
||||
|
||||
Status request(const std::string& nextMethod, const std::string&, const std::string& nextBody,
|
||||
const std::vector<HttpHeader>& nextHeaders, int32_t maxBytes, HttpResponse& response) override {
|
||||
Status request(const std::string& nextMethod, const std::string&,
|
||||
const std::string& nextBody,
|
||||
const std::vector<HttpHeader>& nextHeaders, int32_t maxBytes,
|
||||
HttpResponse& response) override {
|
||||
method = nextMethod;
|
||||
body = nextBody;
|
||||
headers = nextHeaders;
|
||||
@@ -187,7 +231,8 @@ struct Http : HttpProvider {
|
||||
response.body = "missing";
|
||||
return Status::success();
|
||||
}
|
||||
Status download(const std::string&, const std::string&, const HttpDownload& options, int32_t& bytesWritten) override {
|
||||
Status download(const std::string&, const std::string&,
|
||||
const HttpDownload& options, int32_t& bytesWritten) override {
|
||||
bytesWritten = options.maxBytes;
|
||||
return Status::success();
|
||||
}
|
||||
@@ -242,15 +287,19 @@ struct Ble : BleProvider {
|
||||
Status connect(const std::string&) override { return Status::success(); }
|
||||
void disconnect() override {}
|
||||
bool isConnected() const override { return true; }
|
||||
Status read(const std::string&, const std::string&, std::string& out) override {
|
||||
Status read(const std::string&, const std::string&,
|
||||
std::string& out) override {
|
||||
out = std::string("a\0b", 3);
|
||||
return Status::success();
|
||||
}
|
||||
Status write(const std::string&, const std::string&, const std::string& next) override {
|
||||
Status write(const std::string&, const std::string&,
|
||||
const std::string& next) override {
|
||||
value = next;
|
||||
return Status::success();
|
||||
}
|
||||
Status startAdvertising(const std::string*) override { return Status::success(); }
|
||||
Status startAdvertising(const std::string*) override {
|
||||
return Status::success();
|
||||
}
|
||||
void stopAdvertising() override {}
|
||||
};
|
||||
|
||||
@@ -269,7 +318,8 @@ struct Touch : TouchProvider {
|
||||
return down;
|
||||
}
|
||||
bool isTouched() const override { return down; }
|
||||
Status setCalibration(int32_t x0, int32_t y0, int32_t x1, int32_t y1) override {
|
||||
Status setCalibration(int32_t x0, int32_t y0, int32_t x1,
|
||||
int32_t y1) override {
|
||||
calibration[0] = x0;
|
||||
calibration[1] = y0;
|
||||
calibration[2] = x1;
|
||||
@@ -288,7 +338,9 @@ struct Buttons : ButtonsProvider {
|
||||
return roles;
|
||||
}
|
||||
bool isAnyPressed() const override { return false; }
|
||||
bool isPressed(const std::string& button) const override { return button == "confirm"; }
|
||||
bool isPressed(const std::string& button) const override {
|
||||
return button == "confirm";
|
||||
}
|
||||
bool wasPressed(const std::string&) const override { return false; }
|
||||
bool wasReleased(const std::string&) const override { return false; }
|
||||
};
|
||||
@@ -324,4 +376,4 @@ struct Bench {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fake
|
||||
} // namespace fake
|
||||
|
||||
@@ -25,7 +25,7 @@ void expectError(lua_State* state, const char* chunk) {
|
||||
lua_pop(state, 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
fake::Bench bench;
|
||||
@@ -37,88 +37,82 @@ int main() {
|
||||
assert(bench.log.level == esp32lua::LogLevel::Info);
|
||||
assert(bench.log.message == "shared runtime");
|
||||
|
||||
run(state,
|
||||
"assert(settings.getRotation() == 0)\n"
|
||||
"assert(settings.setRotation(90))\n"
|
||||
"assert(settings.getTimezone() == 'UTC0')\n"
|
||||
"assert(settings.setTimezone('EST5EDT'))");
|
||||
run(state, "assert(settings.getRotation() == 0)\n"
|
||||
"assert(settings.setRotation(90))\n"
|
||||
"assert(settings.getTimezone() == 'UTC0')\n"
|
||||
"assert(settings.setTimezone('EST5EDT'))");
|
||||
assert(bench.settings.degrees == 90);
|
||||
assert(bench.settings.tz == "EST5EDT");
|
||||
expectError(state, "settings.setRotation(45)");
|
||||
|
||||
run(state,
|
||||
"assert(sys.getAPIVersion() == 1)\n"
|
||||
"assert(sys.hasFeature('touch') and sys.hasFeature('buttons'))\n"
|
||||
"assert(not sys.hasFeature('eink'))\n"
|
||||
"local free, total, largest = sys.getMemory()\n"
|
||||
"assert(free == 100 and total == 200 and largest == 50)");
|
||||
run(state, "assert(sys.getAPIVersion() == 1)\n"
|
||||
"assert(sys.hasFeature('touch') and sys.hasFeature('buttons'))\n"
|
||||
"assert(not sys.hasFeature('eink'))\n"
|
||||
"local free, total, largest = sys.getMemory()\n"
|
||||
"assert(free == 100 and total == 200 and largest == 50)");
|
||||
|
||||
bench.fs.files["/notes.txt"] = "first\nsecond";
|
||||
run(state,
|
||||
"assert(fs.MAX_READ_BYTES == 65536)\n"
|
||||
"assert(fs.exists('/notes.txt') and not fs.exists('/missing'))\n"
|
||||
"assert(fs.fileSize('/notes.txt') == 12)\n"
|
||||
"local size, err = fs.fileSize('/missing')\n"
|
||||
"assert(size == nil and err == 'no such file')\n"
|
||||
"assert(fs.readFile('/notes.txt', 64) == 'first\\nsecond')\n"
|
||||
"local line, nextOffset = fs.readLineAt('/notes.txt', 0, 64)\n"
|
||||
"assert(line == 'first' and nextOffset == 6)\n"
|
||||
"assert(fs.readLineAt('/notes.txt', 12, 64) == nil)\n"
|
||||
"assert(fs.listFiles('/')[1] == 'main.lua')\n"
|
||||
"assert(fs.writeFile('/out.bin', 'a\\0b'))");
|
||||
run(state, "assert(fs.MAX_READ_BYTES == 65536)\n"
|
||||
"assert(fs.exists('/notes.txt') and not fs.exists('/missing'))\n"
|
||||
"assert(fs.fileSize('/notes.txt') == 12)\n"
|
||||
"local size, err = fs.fileSize('/missing')\n"
|
||||
"assert(size == nil and err == 'no such file')\n"
|
||||
"assert(fs.readFile('/notes.txt', 64) == 'first\\nsecond')\n"
|
||||
"local line, nextOffset = fs.readLineAt('/notes.txt', 0, 64)\n"
|
||||
"assert(line == 'first' and nextOffset == 6)\n"
|
||||
"assert(fs.readLineAt('/notes.txt', 12, 64) == nil)\n"
|
||||
"assert(fs.listFiles('/')[1] == 'main.lua')\n"
|
||||
"assert(fs.writeFile('/out.bin', 'a\\0b'))");
|
||||
assert(bench.fs.written.size() == 3);
|
||||
expectError(state, "fs.readFile('/notes.txt', 999999)");
|
||||
|
||||
run(state,
|
||||
"assert(gui.getWidth() == 320 and gui.getHeight() == 240)\n"
|
||||
"assert(gui.FONT_UI == 2 and gui.STYLE_BOLD == 1)\n"
|
||||
"assert(gui.color(255, 0, 0) == 0xFF0000)\n"
|
||||
"gui.setRotation(180)\n"
|
||||
"gui.clear()\n"
|
||||
"gui.fillPolygon({1, 2, 3}, {4, 5, 6}, 0)\n"
|
||||
"gui.drawText(gui.FONT_UI, 0, 0, 'hi')");
|
||||
run(state, "assert(gui.getWidth() == 320 and gui.getHeight() == 240)\n"
|
||||
"assert(gui.FONT_UI == 2 and gui.STYLE_BOLD == 1)\n"
|
||||
"assert(gui.color(255, 0, 0) == 0xFF0000)\n"
|
||||
"gui.setRotation(180)\n"
|
||||
"gui.clear()\n"
|
||||
"gui.fillPolygon({1, 2, 3}, {4, 5, 6}, 0)\n"
|
||||
"gui.drawText(gui.FONT_UI, 0, 0, 'hi')");
|
||||
assert(bench.gui.degrees == 180);
|
||||
assert(bench.gui.trace == "clear;fillPolygon3;drawText(hi);");
|
||||
expectError(state, "gui.fillPolygon({1, 2}, {3}, 0)");
|
||||
expectError(state, "gui.color(300, 0, 0)");
|
||||
|
||||
run(state,
|
||||
"local response = http.get('https://example.test', {maxBytes = 16, headers = {Accept = 'text/plain'}})\n"
|
||||
"assert(response.status == 404 and response.body == 'missing')\n"
|
||||
"assert(http.urlencode('a b/c~') == 'a%20b%2Fc~')\n"
|
||||
"assert(http.download('https://example.test/f', '/f', {maxBytes = 32}) == 32)");
|
||||
run(state, "local response = http.get('https://example.test', {maxBytes = "
|
||||
"16, headers = {Accept = 'text/plain'}})\n"
|
||||
"assert(response.status == 404 and response.body == 'missing')\n"
|
||||
"assert(http.urlencode('a b/c~') == 'a%20b%2Fc~')\n"
|
||||
"assert(http.download('https://example.test/f', '/f', {maxBytes = "
|
||||
"32}) == 32)");
|
||||
assert(bench.http.method == "GET" && bench.http.limit == 16);
|
||||
assert(bench.http.headers.size() == 1 && bench.http.headers[0].name == "Accept");
|
||||
assert(bench.http.headers.size() == 1 &&
|
||||
bench.http.headers[0].name == "Accept");
|
||||
expectError(state, "http.get('https://example.test', {maxBytes = 999999})");
|
||||
|
||||
run(state,
|
||||
"assert(wifi.scan()[1].ssid == 'home')\n"
|
||||
"assert(wifi.isConnected())\n"
|
||||
"assert(wifi.getLocalIP() == '192.168.1.5')\n"
|
||||
"assert(wifi.getStatus().state == 'connected')\n"
|
||||
"assert(wifi.connect())");
|
||||
run(state, "assert(wifi.scan()[1].ssid == 'home')\n"
|
||||
"assert(wifi.isConnected())\n"
|
||||
"assert(wifi.getLocalIP() == '192.168.1.5')\n"
|
||||
"assert(wifi.getStatus().state == 'connected')\n"
|
||||
"assert(wifi.connect())");
|
||||
assert(bench.wifi.savedReconnect);
|
||||
|
||||
run(state,
|
||||
"assert(ble.scan()[1].address == 'aa:bb')\n"
|
||||
"assert(#ble.read('svc', 'chr') == 3)\n"
|
||||
"assert(ble.write('svc', 'chr', 'x\\0y'))");
|
||||
run(state, "assert(ble.scan()[1].address == 'aa:bb')\n"
|
||||
"assert(#ble.read('svc', 'chr') == 3)\n"
|
||||
"assert(ble.write('svc', 'chr', 'x\\0y'))");
|
||||
assert(bench.ble.duration == 3000);
|
||||
assert(bench.ble.value.size() == 3);
|
||||
|
||||
run(state,
|
||||
"fired = 0\n"
|
||||
"once = timer.after(10, function() fired = fired + 1 end)\n"
|
||||
"repeated = timer.every(5, function() fired = fired + 10 end)");
|
||||
run(state, "fired = 0\n"
|
||||
"once = timer.after(10, function() fired = fired + 1 end)\n"
|
||||
"repeated = timer.every(5, function() fired = fired + 10 end)");
|
||||
assert(bench.timer.scheduled.size() == 2);
|
||||
runtime.callTimer(bench.timer.scheduled[0]);
|
||||
runtime.callTimer(bench.timer.scheduled[0]); // a one-shot never fires twice
|
||||
runtime.callTimer(bench.timer.scheduled[0]); // a one-shot never fires twice
|
||||
runtime.callTimer(bench.timer.scheduled[1]);
|
||||
runtime.callTimer(bench.timer.scheduled[1]);
|
||||
run(state,
|
||||
"assert(fired == 21)\n"
|
||||
"assert(timer.cancel(repeated))\n"
|
||||
"assert(not timer.cancel(repeated))");
|
||||
run(state, "assert(fired == 21)\n"
|
||||
"assert(timer.cancel(repeated))\n"
|
||||
"assert(not timer.cancel(repeated))");
|
||||
assert(bench.timer.cancelled.size() == 1);
|
||||
expectError(state, "timer.after(0, function() end)");
|
||||
|
||||
@@ -128,24 +122,28 @@ int main() {
|
||||
"assert(x == 10 and y == 20)\n"
|
||||
"assert(input.isTouched())\n"
|
||||
"assert(input.isPressed('confirm') and not input.isPressed('back'))\n"
|
||||
"assert(#input.getButtons() == 4 and input.getButtons()[3] == 'confirm')");
|
||||
"assert(#input.getButtons() == 4 and input.getButtons()[3] == "
|
||||
"'confirm')");
|
||||
assert(bench.touch.calibration[3] == 400);
|
||||
|
||||
// Chrome control and gradients are core: an e-ink provider flattens what it cannot show.
|
||||
run(state,
|
||||
"gui.setFullscreen(true)\n"
|
||||
"gui.roundRect(0, 0, 10, 10, 4, 0, 0xFF, 0x00)\n"
|
||||
"gui.roundRect(0, 0, 10, 10, 4, 0, nil, nil, 0xFF)");
|
||||
// Chrome control and gradients are core: an e-ink provider flattens what it
|
||||
// cannot show.
|
||||
run(state, "gui.setFullscreen(true)\n"
|
||||
"gui.roundRect(0, 0, 10, 10, 4, 0, 0xFF, 0x00)\n"
|
||||
"gui.roundRect(0, 0, 10, 10, 4, 0, nil, nil, 0xFF)");
|
||||
assert(bench.gui.fullscreen && bench.gui.gradient);
|
||||
assert(bench.gui.trace.find("roundRect(-,border);") != std::string::npos);
|
||||
|
||||
bench.gui.trace.clear();
|
||||
run(state,
|
||||
"node.reset()\n"
|
||||
"local root = node.create(nil, {type = 'box', w = 'fill', h = 'fill', pad = 4, gap = 2})\n"
|
||||
"local root = node.create(nil, {type = 'box', w = 'fill', h = 'fill', "
|
||||
"pad = 4, gap = 2})\n"
|
||||
"local label = node.create(root, {type = 'text', label = 'Hello'})\n"
|
||||
"local button = node.create(root, {type = 'button', h = 40, interactive = true})\n"
|
||||
"node.setStyle(root, {background = 0xFFFFFF, fill = 0xFFFFFF, color = 0, face = 0xEEEEEE,\n"
|
||||
"local button = node.create(root, {type = 'button', h = 40, interactive "
|
||||
"= true})\n"
|
||||
"node.setStyle(root, {background = 0xFFFFFF, fill = 0xFFFFFF, color = 0, "
|
||||
"face = 0xEEEEEE,\n"
|
||||
" border = 0x333333, focusColor = 0xFF0000})\n"
|
||||
"assert(node.layout(root, 0, 0, 320, 240))\n"
|
||||
"local x, y, w, h = node.getRect(label)\n"
|
||||
@@ -169,17 +167,20 @@ int main() {
|
||||
|
||||
run(state,
|
||||
"node.reset()\n"
|
||||
"local root = node.create(nil, {type = 'custom', w = 'fill', h = 'fill'})\n"
|
||||
"local root = node.create(nil, {type = 'custom', w = 'fill', h = "
|
||||
"'fill'})\n"
|
||||
"painted = 0\n"
|
||||
"node.setPainter(function(id, x, y, w, h) painted = painted + w end)\n"
|
||||
"assert(node.layout(root, 0, 0, 320, 240))\n"
|
||||
"node.draw(root)\n"
|
||||
"assert(painted == 320)");
|
||||
|
||||
// Callbacks: only init failing stops an app, and a release fires the tap alias after the up.
|
||||
// Callbacks: only init failing stops an app, and a release fires the tap
|
||||
// alias after the up.
|
||||
run(state,
|
||||
"events = {}\n"
|
||||
"local function note(name) return function(a) events[#events + 1] = name .. ':' .. tostring(a) end end\n"
|
||||
"local function note(name) return function(a) events[#events + 1] = name "
|
||||
".. ':' .. tostring(a) end end\n"
|
||||
"function init(arg) events[#events + 1] = 'init:' .. tostring(arg) end\n"
|
||||
"function draw(delta) events[#events + 1] = 'draw:' .. delta end\n"
|
||||
"on_touch_down = note('down')\n"
|
||||
@@ -191,20 +192,21 @@ int main() {
|
||||
assert(runtime.callInit("book.epub"));
|
||||
runtime.callDraw(33);
|
||||
runtime.callTouch(esp32lua::TouchPhase::Down, 5, 6);
|
||||
runtime.callTouch(esp32lua::TouchPhase::Move, 5, 7); // the app defines no on_touch_move
|
||||
runtime.callTouch(esp32lua::TouchPhase::Move, 5,
|
||||
7); // the app defines no on_touch_move
|
||||
runtime.callTouch(esp32lua::TouchPhase::Up, 5, 8);
|
||||
runtime.callButton("confirm", false);
|
||||
run(state,
|
||||
"assert(table.concat(events, ' ') == "
|
||||
"'init:book.epub draw:33 down:5 up:5 tap:5 bup:confirm btap:confirm')");
|
||||
// One commit per visit to the app, so six calls and not seven: the release and its tap alias
|
||||
// are one visible change, and the move nobody handled still ends a batch.
|
||||
// One commit per visit to the app, so six calls and not seven: the release
|
||||
// and its tap alias are one visible change, and the move nobody handled still
|
||||
// ends a batch.
|
||||
assert(bench.gui.commits == 6);
|
||||
|
||||
// A timer firing inside draw is still one batch.
|
||||
run(state,
|
||||
"function draw() timer.after(1, function() end) end\n"
|
||||
"nested = timer.after(1, function() draw() end)");
|
||||
run(state, "function draw() timer.after(1, function() end) end\n"
|
||||
"nested = timer.after(1, function() draw() end)");
|
||||
bench.gui.commits = 0;
|
||||
runtime.callTimer(bench.timer.scheduled.back());
|
||||
assert(bench.gui.commits == 1);
|
||||
@@ -213,10 +215,11 @@ int main() {
|
||||
assert(!runtime.callInit(""));
|
||||
assert(bench.log.message.find("init: ") == 0);
|
||||
run(state, "function draw() error('kaboom') end");
|
||||
runtime.callDraw(1); // a failed frame logs and the app keeps running
|
||||
runtime.callDraw(1); // a failed frame logs and the app keeps running
|
||||
assert(bench.log.message.find("draw: ") == 0);
|
||||
|
||||
// A feature callback without its provider is a wiring bug, not a silent no-op.
|
||||
// A feature callback without its provider is a wiring bug, not a silent
|
||||
// no-op.
|
||||
{
|
||||
fake::Bench headless;
|
||||
esp32lua::Providers providers = headless.providers();
|
||||
@@ -225,17 +228,21 @@ int main() {
|
||||
assert(noTouch.open());
|
||||
noTouch.callTouch(esp32lua::TouchPhase::Down, 1, 1);
|
||||
assert(headless.log.message == "callTouch without a touch provider");
|
||||
run(noTouch.state(), "assert(input.getTouch == nil and input.isPressed ~= nil)");
|
||||
run(noTouch.state(),
|
||||
"assert(input.getTouch == nil and input.isPressed ~= nil)");
|
||||
}
|
||||
|
||||
// App loading: a fresh state per app, require reaching the app directory and /.lua/lib, and
|
||||
// navigation applied between batches rather than inside a callback.
|
||||
// App loading: a fresh state per app, require reaching the app directory and
|
||||
// /.lua/lib, and navigation applied between batches rather than inside a
|
||||
// callback.
|
||||
{
|
||||
fake::Bench host;
|
||||
host.fs.files["/.lua/lib/greet.lua"] = "return {hello = function() return 'hi' end}";
|
||||
host.fs.files["/.lua/lib/greet.lua"] =
|
||||
"return {hello = function() return 'hi' end}";
|
||||
host.fs.files["/.lua/apps/Home/main.lua"] =
|
||||
"local greet = require('greet')\n"
|
||||
"function init(arg) started = greet.hello() .. ':' .. tostring(arg) end";
|
||||
"function init(arg) started = greet.hello() .. ':' .. tostring(arg) "
|
||||
"end";
|
||||
host.fs.files["/.lua/apps/Reader/main.lua"] =
|
||||
"local page = require('page')\n"
|
||||
"function init(arg) started = page.name .. ':' .. arg end";
|
||||
@@ -251,7 +258,8 @@ int main() {
|
||||
// A subapp shares the app ID, so both routes share one data directory.
|
||||
run(app.state(), "sys.launch('Reader', 'book.epub')");
|
||||
assert(app.hasPendingNavigation());
|
||||
run(app.state(), "assert(started == 'hi:')"); // the current app keeps running until applied
|
||||
run(app.state(), "assert(started == 'hi:')"); // the current app keeps
|
||||
// running until applied
|
||||
assert(app.applyPendingNavigation());
|
||||
run(app.state(), "assert(started == 'page:book.epub')");
|
||||
run(app.state(), "sys.launch('Reader/Notes')");
|
||||
@@ -267,7 +275,8 @@ int main() {
|
||||
run(app.state(), "sys.back()");
|
||||
assert(app.applyPendingNavigation() && app.appPath() == "Home");
|
||||
|
||||
// sys.replace does not grow history, so back from it still reaches the launcher.
|
||||
// sys.replace does not grow history, so back from it still reaches the
|
||||
// launcher.
|
||||
run(app.state(), "sys.replace('Reader', 'other.epub')");
|
||||
assert(app.applyPendingNavigation() && app.appPath() == "Reader");
|
||||
run(app.state(), "sys.back()");
|
||||
@@ -276,7 +285,8 @@ int main() {
|
||||
// A missing app, a broken app, and a traversal all leave nothing running.
|
||||
assert(!app.startApp("Absent"));
|
||||
assert(!app.hasApp() && app.state() == nullptr);
|
||||
host.fs.files["/.lua/apps/Broken/main.lua"] = "function init() error('nope') end";
|
||||
host.fs.files["/.lua/apps/Broken/main.lua"] =
|
||||
"function init() error('nope') end";
|
||||
assert(!app.startApp("Broken"));
|
||||
assert(!app.hasApp());
|
||||
assert(!app.startApp("../secrets"));
|
||||
|
||||
Reference in New Issue
Block a user