mirror of
https://github.com/alexhopeoconnor/WiFiManager.git
synced 2026-10-04 02:48:13 +10:00
Portal SPA: single shell, JSON /api surface, remove legacy portal routes
Replace multi-page portal with hash-routed SPA, DFTE shell placeholders, and data-first JSON APIs. Remove legacy routes, root-template override path, and HTML-derived info pipeline. Add bootstrap feature flags, in-DOM toast/dialog UX, OTA via POST /u, and tests for shell, bootstrap, API JSON, and captive host rule. Rewrite README as fork disclaimer and modernization overview.
This commit is contained in:
@@ -30,8 +30,8 @@ TestCase tests[] = {
|
||||
TEST_ENTRY(test_config_portal_multiple_start_stop),
|
||||
TEST_ENTRY(test_config_portal_already_active),
|
||||
TEST_ENTRY(test_get_config_portal_ssid),
|
||||
TEST_ENTRY(test_root_render_menu_state_transitions),
|
||||
TEST_ENTRY(test_root_render_snapshot_consistency),
|
||||
TEST_ENTRY(test_bootstrap_json_portal_feature_flags),
|
||||
TEST_ENTRY(test_bootstrap_json_snapshot_consistency),
|
||||
TEST_ENTRY(test_root_render_interleaved_context_isolation),
|
||||
|
||||
// Non-blocking tests
|
||||
@@ -113,15 +113,15 @@ TestCase tests[] = {
|
||||
TEST_ENTRY(test_scan_generation_invalidated_on_reset),
|
||||
|
||||
// Template rendering tests
|
||||
TEST_ENTRY(test_page_shell_supports_page_assets),
|
||||
TEST_ENTRY(test_root_template_renders_core_placeholders),
|
||||
TEST_ENTRY(test_wifi_template_renders_scan_container_and_form_fields),
|
||||
TEST_ENTRY(test_message_template_renders_body_and_actions),
|
||||
TEST_ENTRY(test_param_template_renders_shared_actions),
|
||||
TEST_ENTRY(test_info_template_renders_sections_and_footer),
|
||||
TEST_ENTRY(test_fragment_action_form_renders_shared_button_markup),
|
||||
TEST_ENTRY(test_fragment_info_row_and_section_render),
|
||||
TEST_ENTRY(test_fragment_centered_button_supports_extra_attrs),
|
||||
TEST_ENTRY(test_shell_template_renders_core_placeholders),
|
||||
|
||||
TEST_ENTRY(test_captive_redirect_host_rule),
|
||||
TEST_ENTRY(test_api_wifi_meta_json_shape),
|
||||
TEST_ENTRY(test_api_info_json_shape),
|
||||
TEST_ENTRY(test_api_params_json_shape),
|
||||
TEST_ENTRY(test_api_status_json_shape),
|
||||
TEST_ENTRY(test_api_action_response_json_literals),
|
||||
TEST_ENTRY(test_api_ota_done_success_json_literal),
|
||||
|
||||
// State transition tests
|
||||
TEST_ENTRY(test_portal_to_connected_transition),
|
||||
|
||||
@@ -40,8 +40,8 @@ void test_stop_web_portal();
|
||||
void test_config_portal_multiple_start_stop();
|
||||
void test_config_portal_already_active();
|
||||
void test_get_config_portal_ssid();
|
||||
void test_root_render_menu_state_transitions();
|
||||
void test_root_render_snapshot_consistency();
|
||||
void test_bootstrap_json_portal_feature_flags();
|
||||
void test_bootstrap_json_snapshot_consistency();
|
||||
void test_root_render_interleaved_context_isolation();
|
||||
|
||||
// Non-blocking tests
|
||||
@@ -123,15 +123,16 @@ void test_scan_cancels_when_lifecycle_blocked();
|
||||
void test_scan_generation_invalidated_on_reset();
|
||||
|
||||
// Template rendering tests
|
||||
void test_page_shell_supports_page_assets();
|
||||
void test_root_template_renders_core_placeholders();
|
||||
void test_wifi_template_renders_scan_container_and_form_fields();
|
||||
void test_message_template_renders_body_and_actions();
|
||||
void test_param_template_renders_shared_actions();
|
||||
void test_info_template_renders_sections_and_footer();
|
||||
void test_fragment_action_form_renders_shared_button_markup();
|
||||
void test_fragment_info_row_and_section_render();
|
||||
void test_fragment_centered_button_supports_extra_attrs();
|
||||
void test_shell_template_renders_core_placeholders();
|
||||
|
||||
// API JSON + captive helper tests
|
||||
void test_captive_redirect_host_rule();
|
||||
void test_api_wifi_meta_json_shape();
|
||||
void test_api_info_json_shape();
|
||||
void test_api_params_json_shape();
|
||||
void test_api_status_json_shape();
|
||||
void test_api_action_response_json_literals();
|
||||
void test_api_ota_done_success_json_literal();
|
||||
|
||||
// State transition tests
|
||||
void test_portal_to_connected_transition();
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerHandlers.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
void test_captive_redirect_host_rule() {
|
||||
Serial.println("[TEST] Testing captive redirect host comparison...");
|
||||
|
||||
TEST_ASSERT_TRUE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("captive.apple.com", "192.168.4.1"));
|
||||
TEST_ASSERT_TRUE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("192.168.4.2", "192.168.4.1"));
|
||||
TEST_ASSERT_FALSE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("192.168.4.1", "192.168.4.1"));
|
||||
TEST_ASSERT_FALSE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("example.com", ""));
|
||||
|
||||
Serial.println("[TEST] Captive redirect host rule test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_wifi_meta_json_shape() {
|
||||
Serial.println("[TEST] Testing /api/wifi/meta JSON shape...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
String j = handlers.buildApiWifiMetaJson();
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"wifiFields\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"staticFields\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"canRefreshScan\":true")));
|
||||
|
||||
Serial.println("[TEST] WiFi meta JSON shape test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_info_json_shape() {
|
||||
Serial.println("[TEST] Testing /api/info JSON shape...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
String j = handlers.buildApiInfoJson();
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"status\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"connected\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"device\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"wifi\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"about\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"showUpdate\"")));
|
||||
|
||||
Serial.println("[TEST] API info JSON shape test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_params_json_shape() {
|
||||
Serial.println("[TEST] Testing /api/params JSON shape...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
String j = handlers.buildApiParamsGetJson();
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"showBack\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\":[")));
|
||||
|
||||
Serial.println("[TEST] API params JSON shape test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_status_json_shape() {
|
||||
Serial.println("[TEST] Testing /api/status JSON shape...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
String j = handlers.buildApiStatusJson();
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"text\"")));
|
||||
TEST_ASSERT_EQUAL('{', j.charAt(0));
|
||||
TEST_ASSERT_EQUAL('}', j.charAt(j.length() - 1));
|
||||
|
||||
Serial.println("[TEST] API status JSON shape test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_action_response_json_literals() {
|
||||
Serial.println("[TEST] Testing POST /api action JSON payloads...");
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"{\"ok\":true,\"message\":\"Restart scheduled\"}",
|
||||
WiFiManagerHandlers::jsonApiDeviceRestartScheduled().c_str());
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"{\"ok\":true,\"message\":\"Setup saved\"}",
|
||||
WiFiManagerHandlers::jsonApiParamsSaveOk().c_str());
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"{\"ok\":true,\"message\":\"Captive portal detection disabled\"}",
|
||||
WiFiManagerHandlers::jsonApiPortalCloseOk().c_str());
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"{\"ok\":true,\"message\":\"Exiting portal\"}",
|
||||
WiFiManagerHandlers::jsonApiPortalExitOk().c_str());
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"{\"ok\":false,\"message\":\"Exit not allowed\"}",
|
||||
WiFiManagerHandlers::jsonApiPortalExitForbidden().c_str());
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"{\"ok\":true,\"message\":\"WiFi configuration erased. Device will restart shortly.\"}",
|
||||
WiFiManagerHandlers::jsonApiEraseResponse(true).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"{\"ok\":false,\"message\":\"Erase failed\"}",
|
||||
WiFiManagerHandlers::jsonApiEraseResponse(false).c_str());
|
||||
|
||||
Serial.println("[TEST] Action response JSON literals test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_ota_done_success_json_literal() {
|
||||
Serial.println("[TEST] Testing POST /u completion success JSON...");
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"{\"ok\":true,\"message\":\"Firmware updated. Restarting...\"}",
|
||||
WiFiManagerHandlers::jsonApiOtaUpdateSuccess().c_str());
|
||||
|
||||
Serial.println("[TEST] OTA success JSON literal test completed successfully");
|
||||
}
|
||||
@@ -3,13 +3,12 @@
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerHandlers.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "templates/RootSelector.h"
|
||||
#include "templates/RootShell.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char kEmptyTemplateChunk[] PROGMEM = "";
|
||||
const char kTestTitle[] PROGMEM = "WiFiManager";
|
||||
const char kTestSubtitle[] PROGMEM = "Root Test";
|
||||
|
||||
const char* dynamicStringGetter(void* userData) {
|
||||
const auto* value = static_cast<const String*>(userData);
|
||||
@@ -56,37 +55,29 @@ void renderContextsInterleaved(TemplateContext& first, String& firstOutput,
|
||||
|
||||
} // namespace
|
||||
|
||||
void test_root_render_menu_state_transitions() {
|
||||
Serial.println("[TEST] Testing root menu state transitions...");
|
||||
void test_bootstrap_json_portal_feature_flags() {
|
||||
Serial.println("[TEST] Testing bootstrap JSON portal feature flags...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
|
||||
String menuBefore;
|
||||
handlers.getMenuOut(&menuBefore);
|
||||
TEST_ASSERT_EQUAL(-1, menuBefore.indexOf("/close"));
|
||||
String idleBootstrap = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_EQUAL(-1, idleBootstrap.indexOf(F("\"showCloseCaptive\":true")));
|
||||
|
||||
wm.setConfigPortalTimeout(5);
|
||||
wm.startConfigPortal("RootStateAP");
|
||||
wm.process();
|
||||
delay(100);
|
||||
wm.wmTestSetPortalActive(true);
|
||||
wm.setCaptivePortalEnable(true);
|
||||
|
||||
String menuActive;
|
||||
handlers.getMenuOut(&menuActive);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, menuActive.indexOf("/close"));
|
||||
String apBootstrap = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"showCloseCaptive\":true")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"showExitPortal\":true")));
|
||||
|
||||
wm.stopConfigPortal();
|
||||
delay(100);
|
||||
wm.wmTestSetPortalActive(false);
|
||||
|
||||
String menuAfter;
|
||||
handlers.getMenuOut(&menuAfter);
|
||||
TEST_ASSERT_EQUAL(-1, menuAfter.indexOf("/close"));
|
||||
|
||||
Serial.println("[TEST] Root menu state transitions test completed successfully");
|
||||
Serial.println("[TEST] Bootstrap portal feature flags test completed successfully");
|
||||
}
|
||||
|
||||
void test_root_render_snapshot_consistency() {
|
||||
Serial.println("[TEST] Testing root render snapshot consistency...");
|
||||
void test_bootstrap_json_snapshot_consistency() {
|
||||
Serial.println("[TEST] Testing bootstrap JSON snapshot consistency...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
@@ -96,24 +87,15 @@ void test_root_render_snapshot_consistency() {
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
String menuSnapshot1;
|
||||
String statusSnapshot1;
|
||||
handlers.getMenuOut(&menuSnapshot1);
|
||||
handlers.reportStatus(statusSnapshot1);
|
||||
String snap1 = handlers.buildPortalBootstrapJson();
|
||||
String snap2 = handlers.buildPortalBootstrapJson();
|
||||
|
||||
String menuSnapshot2;
|
||||
String statusSnapshot2;
|
||||
handlers.getMenuOut(&menuSnapshot2);
|
||||
handlers.reportStatus(statusSnapshot2);
|
||||
|
||||
TEST_ASSERT_GREATER_THAN(0, menuSnapshot1.length());
|
||||
TEST_ASSERT_GREATER_THAN(0, statusSnapshot1.length());
|
||||
TEST_ASSERT_EQUAL_STRING(menuSnapshot1.c_str(), menuSnapshot2.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING(statusSnapshot1.c_str(), statusSnapshot2.c_str());
|
||||
TEST_ASSERT_GREATER_THAN(32, snap1.length());
|
||||
TEST_ASSERT_EQUAL_STRING(snap1.c_str(), snap2.c_str());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Root render snapshot consistency test completed successfully");
|
||||
Serial.println("[TEST] Bootstrap JSON snapshot consistency test completed successfully");
|
||||
}
|
||||
|
||||
void test_root_render_interleaved_context_isolation() {
|
||||
@@ -125,58 +107,56 @@ void test_root_render_interleaved_context_isolation() {
|
||||
registryA.registerProgmemData("%SCRIPTS%", kEmptyTemplateChunk);
|
||||
registryA.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
|
||||
registryA.registerProgmemData("%PAGE_TITLE%", kTestTitle);
|
||||
registryA.registerProgmemData("%SUBTITLE%", kTestSubtitle);
|
||||
|
||||
registryB.registerProgmemData("%SCRIPTS%", kEmptyTemplateChunk);
|
||||
registryB.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
|
||||
registryB.registerProgmemData("%PAGE_TITLE%", kTestTitle);
|
||||
registryB.registerProgmemData("%SUBTITLE%", kTestSubtitle);
|
||||
|
||||
String menuA = F("<div>Menu A</div>");
|
||||
String statusA = F("<div>Status A</div>");
|
||||
String menuB = F("<div>Menu B</div>");
|
||||
String statusB = F("<div>Status B</div>");
|
||||
String bootstrapA = F("{\"ctx\":\"A\"}");
|
||||
String appJsA = F("// shell A");
|
||||
String bootstrapB = F("{\"ctx\":\"B\"}");
|
||||
String appJsB = F("// shell B");
|
||||
|
||||
DynamicTemplateDescriptor menuDescriptorA{};
|
||||
DynamicTemplateDescriptor statusDescriptorA{};
|
||||
DynamicTemplateDescriptor menuDescriptorB{};
|
||||
DynamicTemplateDescriptor statusDescriptorB{};
|
||||
DynamicTemplateDescriptor bootstrapDescriptorA{};
|
||||
DynamicTemplateDescriptor appJsDescriptorA{};
|
||||
DynamicTemplateDescriptor bootstrapDescriptorB{};
|
||||
DynamicTemplateDescriptor appJsDescriptorB{};
|
||||
|
||||
configureDescriptor(menuDescriptorA, menuA);
|
||||
configureDescriptor(statusDescriptorA, statusA);
|
||||
configureDescriptor(menuDescriptorB, menuB);
|
||||
configureDescriptor(statusDescriptorB, statusB);
|
||||
configureDescriptor(bootstrapDescriptorA, bootstrapA);
|
||||
configureDescriptor(appJsDescriptorA, appJsA);
|
||||
configureDescriptor(bootstrapDescriptorB, bootstrapB);
|
||||
configureDescriptor(appJsDescriptorB, appJsB);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%MENU%", &menuDescriptorA),
|
||||
"Registry A menu placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%STATUS%", &statusDescriptorA),
|
||||
"Registry A status placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%MENU%", &menuDescriptorB),
|
||||
"Registry B menu placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%STATUS%", &statusDescriptorB),
|
||||
"Registry B status placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%BOOTSTRAP_JSON%", &bootstrapDescriptorA),
|
||||
"Registry A bootstrap placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%PORTAL_APP_JS%", &appJsDescriptorA),
|
||||
"Registry A app JS placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%BOOTSTRAP_JSON%", &bootstrapDescriptorB),
|
||||
"Registry B bootstrap placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%PORTAL_APP_JS%", &appJsDescriptorB),
|
||||
"Registry B app JS placeholder should register");
|
||||
|
||||
TemplateContext contextA;
|
||||
TemplateContext contextB;
|
||||
contextA.setRegistry(®istryA);
|
||||
contextB.setRegistry(®istryB);
|
||||
|
||||
TemplateRenderer::initializeContext(contextA, WM_ROOT_TEMPLATE);
|
||||
TemplateRenderer::initializeContext(contextB, WM_ROOT_TEMPLATE);
|
||||
TemplateRenderer::initializeContext(contextA, WM_ROOT_SHELL_TEMPLATE);
|
||||
TemplateRenderer::initializeContext(contextB, WM_ROOT_SHELL_TEMPLATE);
|
||||
|
||||
String outputA;
|
||||
String outputB;
|
||||
renderContextsInterleaved(contextA, outputA, contextB, outputB);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("Menu A"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("Status A"));
|
||||
TEST_ASSERT_EQUAL(-1, outputA.indexOf("Menu B"));
|
||||
TEST_ASSERT_EQUAL(-1, outputA.indexOf("Status B"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("{\"ctx\":\"A\"}"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("// shell A"));
|
||||
TEST_ASSERT_EQUAL(-1, outputA.indexOf("{\"ctx\":\"B\"}"));
|
||||
TEST_ASSERT_EQUAL(-1, outputA.indexOf("// shell B"));
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("Menu B"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("Status B"));
|
||||
TEST_ASSERT_EQUAL(-1, outputB.indexOf("Menu A"));
|
||||
TEST_ASSERT_EQUAL(-1, outputB.indexOf("Status A"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("{\"ctx\":\"B\"}"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("// shell B"));
|
||||
TEST_ASSERT_EQUAL(-1, outputB.indexOf("{\"ctx\":\"A\"}"));
|
||||
TEST_ASSERT_EQUAL(-1, outputB.indexOf("// shell A"));
|
||||
|
||||
Serial.println("[TEST] Root render interleaved context isolation test completed successfully");
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "templates/Fragments.h"
|
||||
#include "templates/Info.h"
|
||||
#include "templates/Param.h"
|
||||
#include "templates/PageShell.h"
|
||||
#include "templates/Root.h"
|
||||
#include "templates/WiFi.h"
|
||||
#include "templates/Message.h"
|
||||
#include "templates/RootShell.h"
|
||||
#include "../test_main.h"
|
||||
|
||||
namespace {
|
||||
@@ -31,221 +25,30 @@ String renderTemplate(const char* templateData, PlaceholderRegistry& registry) {
|
||||
|
||||
const char kEmpty[] PROGMEM = "";
|
||||
const char kDocTitle[] PROGMEM = "Config ESP";
|
||||
const char kBodyClass[] PROGMEM = "wifi";
|
||||
const char kPageScripts[] PROGMEM = "<script>console.log('wifi page');</script>";
|
||||
const char kPageStyles[] PROGMEM = "<style>.wifi{color:#000;}</style>";
|
||||
const char kPageContent[] PROGMEM = "<div>Body</div>";
|
||||
const char kScanContent[] PROGMEM = "Scanning for networks...<br/><br/>";
|
||||
const char kSsidPlaceholder[] PROGMEM = "Office";
|
||||
const char kPasswordPlaceholder[] PROGMEM = "********";
|
||||
const char kStaticFields[] PROGMEM = "<hr><br/><label for='ip'>Static IP</label>";
|
||||
const char kParamSection[] PROGMEM = "<hr><br/><label for='param_0'>Token</label>";
|
||||
const char kFormActions[] PROGMEM = "<br/><br/><button type='submit'>Save</button>";
|
||||
const char kPageActions[] PROGMEM = "<br/><div class='c'><button id='refresh-btn' type='button' onclick='return refreshScan()'>Refresh</button></div><hr><br/><form action='/' method='get'><button>Back</button></form>";
|
||||
const char kMessageActions[] PROGMEM = "<hr><br/><form action='/' method='get'><button>Back</button></form>";
|
||||
const char kStatus[] PROGMEM = "<div class='msg'>Status</div>";
|
||||
const char kMessageBody[] PROGMEM = "<div class='msg S'>Saved</div>";
|
||||
const char kInfoSection[] PROGMEM = "<h3>About</h3><hr><dl><dt>Build date</dt><dd>today</dd></dl>";
|
||||
const char kInfoFooter[] PROGMEM = "<hr><br/><form action='/update' method='get'><button>Update</button></form>";
|
||||
const char kPageTitle[] PROGMEM = "WiFiManager";
|
||||
const char kSubtitle[] PROGMEM = "Setup";
|
||||
const char kMenu[] PROGMEM = "<form><button>Configure WiFi</button></form>";
|
||||
const char kActionPrefix[] PROGMEM = "<hr><br/>";
|
||||
const char kActionPath[] PROGMEM = "/update";
|
||||
const char kActionMethod[] PROGMEM = "get";
|
||||
const char kActionLabel[] PROGMEM = "Update";
|
||||
const char kActionClassAttr[] PROGMEM = " class='D'";
|
||||
const char kActionSuffix[] PROGMEM = "<br/>\n";
|
||||
const char kButtonId[] PROGMEM = "refresh-btn";
|
||||
const char kButtonType[] PROGMEM = "button";
|
||||
const char kButtonOnClick[] PROGMEM = "return refreshScan()";
|
||||
const char kButtonExtraAttrs[] PROGMEM = " data-skip-initial-scan='true'";
|
||||
const char kInfoLabel[] PROGMEM = "SDK version";
|
||||
const char kInfoValue[] PROGMEM = "3.1.2";
|
||||
const char kSectionTitle[] PROGMEM = "Device";
|
||||
const char kSectionRows[] PROGMEM = "<dt>Chip ID</dt><dd>abc</dd>";
|
||||
const char kBootstrapJson[] PROGMEM = "{\"title\":\"Test\"}";
|
||||
const char kPortalAppJs[] PROGMEM = "console.log('portal');";
|
||||
|
||||
} // namespace
|
||||
|
||||
void test_page_shell_supports_page_assets() {
|
||||
Serial.println("[TEST] Testing page shell page-scoped assets...");
|
||||
|
||||
PlaceholderRegistry registry(12);
|
||||
registry.registerProgmemData("%SCRIPTS%", kEmpty);
|
||||
registry.registerProgmemData("%PAGE_SCRIPTS%", kPageScripts);
|
||||
registry.registerProgmemData("%STYLES%", kEmpty);
|
||||
registry.registerProgmemData("%PAGE_STYLES%", kPageStyles);
|
||||
registry.registerProgmemData("%DOC_TITLE%", kDocTitle);
|
||||
registry.registerProgmemData("%BODY_CLASS%", kBodyClass);
|
||||
registry.registerProgmemData("%PAGE_CONTENT%", kPageContent);
|
||||
|
||||
String output = renderTemplate(WM_PAGE_SHELL_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("wifi page"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf(".wifi{color:#000;}"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<div>Body</div>"));
|
||||
|
||||
Serial.println("[TEST] Page shell page-scoped assets test completed successfully");
|
||||
}
|
||||
|
||||
void test_wifi_template_renders_scan_container_and_form_fields() {
|
||||
Serial.println("[TEST] Testing WiFi page template rendering...");
|
||||
|
||||
PlaceholderRegistry registry(16);
|
||||
registry.registerProgmemData("%WIFI_SCAN_CONTENT%", kScanContent);
|
||||
registry.registerProgmemData("%WIFI_SSID_PLACEHOLDER%", kSsidPlaceholder);
|
||||
registry.registerProgmemData("%WIFI_PASSWORD_PLACEHOLDER%", kPasswordPlaceholder);
|
||||
registry.registerProgmemData("%WIFI_STATIC_FIELDS%", kStaticFields);
|
||||
registry.registerProgmemData("%WIFI_PARAM_SECTION%", kParamSection);
|
||||
registry.registerProgmemData("%WIFI_FORM_ACTIONS%", kFormActions);
|
||||
registry.registerProgmemData("%WIFI_PAGE_ACTIONS%", kPageActions);
|
||||
registry.registerProgmemData("%WIFI_STATUS%", kStatus);
|
||||
|
||||
String output = renderTemplate(WM_WIFI_CONTENT_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='scan-results'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Scanning for networks..."));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("placeholder='Office'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='refresh-btn'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Token"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Save"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Back"));
|
||||
|
||||
Serial.println("[TEST] WiFi page template rendering test completed successfully");
|
||||
}
|
||||
|
||||
void test_message_template_renders_body_and_actions() {
|
||||
Serial.println("[TEST] Testing message template rendering...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
registry.registerProgmemData("%MESSAGE_BODY%", kMessageBody);
|
||||
registry.registerProgmemData("%MESSAGE_ACTIONS%", kMessageActions);
|
||||
|
||||
String output = renderTemplate(WM_MESSAGE_CONTENT_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Saved"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Back"));
|
||||
|
||||
Serial.println("[TEST] Message template rendering test completed successfully");
|
||||
}
|
||||
|
||||
void test_param_template_renders_shared_actions() {
|
||||
Serial.println("[TEST] Testing param page template rendering...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
registry.registerProgmemData("%PARAM_FIELDS%", kParamSection);
|
||||
registry.registerProgmemData("%PARAM_FORM_ACTIONS%", kFormActions);
|
||||
registry.registerProgmemData("%PARAM_PAGE_ACTIONS%", kMessageActions);
|
||||
registry.registerProgmemData("%PARAM_STATUS%", kStatus);
|
||||
|
||||
String output = renderTemplate(WM_PARAM_CONTENT_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Token"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Save"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Back"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Status"));
|
||||
|
||||
Serial.println("[TEST] Param page template rendering test completed successfully");
|
||||
}
|
||||
|
||||
void test_info_template_renders_sections_and_footer() {
|
||||
Serial.println("[TEST] Testing info page template rendering...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
registry.registerProgmemData("%INFO_STATUS%", kStatus);
|
||||
registry.registerProgmemData("%INFO_DEVICE_SECTION%", kInfoSection);
|
||||
registry.registerProgmemData("%INFO_WIFI_SECTION%", kInfoSection);
|
||||
registry.registerProgmemData("%INFO_ABOUT_SECTION%", kInfoSection);
|
||||
registry.registerProgmemData("%INFO_FOOTER%", kInfoFooter);
|
||||
|
||||
String output = renderTemplate(WM_INFO_CONTENT_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Status"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Build date"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Update"));
|
||||
|
||||
Serial.println("[TEST] Info page template rendering test completed successfully");
|
||||
}
|
||||
|
||||
void test_fragment_action_form_renders_shared_button_markup() {
|
||||
Serial.println("[TEST] Testing shared action fragment rendering...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
registry.registerProgmemData("%ACTION_PREFIX%", kActionPrefix);
|
||||
registry.registerProgmemData("%ACTION%", kActionPath);
|
||||
registry.registerProgmemData("%METHOD%", kActionMethod);
|
||||
registry.registerProgmemData("%BUTTON_LABEL%", kActionLabel);
|
||||
registry.registerProgmemData("%BUTTON_CLASS_ATTR%", kActionClassAttr);
|
||||
registry.registerProgmemData("%ACTION_SUFFIX%", kActionSuffix);
|
||||
|
||||
String output = renderTemplate(WM_ACTION_FORM_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("action='/update'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("class='D'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf(">Update</button>"));
|
||||
|
||||
Serial.println("[TEST] Shared action fragment rendering test completed successfully");
|
||||
}
|
||||
|
||||
void test_fragment_info_row_and_section_render() {
|
||||
Serial.println("[TEST] Testing shared info fragment rendering...");
|
||||
|
||||
PlaceholderRegistry rowRegistry(4);
|
||||
rowRegistry.registerProgmemData("%INFO_LABEL%", kInfoLabel);
|
||||
rowRegistry.registerProgmemData("%INFO_VALUE%", kInfoValue);
|
||||
|
||||
String rowOutput = renderTemplate(WM_INFO_ROW_TEMPLATE, rowRegistry);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, rowOutput.indexOf("<dt>SDK version</dt><dd>3.1.2</dd>"));
|
||||
|
||||
PlaceholderRegistry sectionRegistry(6);
|
||||
sectionRegistry.registerProgmemData("%SECTION_PREFIX%", kEmpty);
|
||||
sectionRegistry.registerProgmemData("%SECTION_TITLE%", kSectionTitle);
|
||||
sectionRegistry.registerProgmemData("%SECTION_ROWS%", kSectionRows);
|
||||
sectionRegistry.registerProgmemData("%SECTION_SUFFIX%", kActionSuffix);
|
||||
|
||||
String sectionOutput = renderTemplate(WM_INFO_SECTION_TEMPLATE, sectionRegistry);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, sectionOutput.indexOf("<h3>Device</h3>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, sectionOutput.indexOf("<dt>Chip ID</dt><dd>abc</dd>"));
|
||||
|
||||
Serial.println("[TEST] Shared info fragment rendering test completed successfully");
|
||||
}
|
||||
|
||||
void test_fragment_centered_button_supports_extra_attrs() {
|
||||
Serial.println("[TEST] Testing centered button fragment extra attrs...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
registry.registerProgmemData("%BUTTON_ID%", kButtonId);
|
||||
registry.registerProgmemData("%BUTTON_TYPE%", kButtonType);
|
||||
registry.registerProgmemData("%BUTTON_ONCLICK%", kButtonOnClick);
|
||||
registry.registerProgmemData("%BUTTON_LABEL%", kActionLabel);
|
||||
registry.registerProgmemData("%BUTTON_EXTRA_ATTRS%", kButtonExtraAttrs);
|
||||
|
||||
String output = renderTemplate(WM_CENTERED_BUTTON_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='refresh-btn'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("data-skip-initial-scan='true'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf(">Update</button>"));
|
||||
|
||||
Serial.println("[TEST] Centered button fragment extra attrs test completed successfully");
|
||||
}
|
||||
|
||||
void test_root_template_renders_core_placeholders() {
|
||||
Serial.println("[TEST] Testing root template rendering...");
|
||||
void test_shell_template_renders_core_placeholders() {
|
||||
Serial.println("[TEST] Testing portal shell template rendering...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
registry.registerProgmemData("%SCRIPTS%", kEmpty);
|
||||
registry.registerProgmemData("%STYLES%", kEmpty);
|
||||
registry.registerProgmemData("%PAGE_TITLE%", kPageTitle);
|
||||
registry.registerProgmemData("%SUBTITLE%", kSubtitle);
|
||||
registry.registerProgmemData("%MENU%", kMenu);
|
||||
registry.registerProgmemData("%STATUS%", kStatus);
|
||||
registry.registerProgmemData("%PAGE_TITLE%", kDocTitle);
|
||||
registry.registerProgmemData("%BOOTSTRAP_JSON%", kBootstrapJson);
|
||||
registry.registerProgmemData("%PORTAL_APP_JS%", kPortalAppJs);
|
||||
|
||||
String output = renderTemplate(WM_ROOT_TEMPLATE, registry);
|
||||
String output = renderTemplate(WM_ROOT_SHELL_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("WiFiManager"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Setup"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Configure WiFi"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Status"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<title>Config ESP</title>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<div id='app'></div>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-toast'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-dialog'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-bootstrap'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("{\"title\":\"Test\"}"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("console.log('portal')"));
|
||||
|
||||
Serial.println("[TEST] Root template rendering test completed successfully");
|
||||
Serial.println("[TEST] Portal shell template rendering test completed successfully");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user