mirror of
https://github.com/alexhopeoconnor/WiFiManager.git
synced 2026-10-22 02:32:10 +10:00
Portal customization API v2, SPA hardening, and test cleanup
- Group portal state into brand/pages/actions/layout structs; portal* API - Bootstrap JSON: nested brand/context/pages/actions/layout; param kind - Handlers: PROGMEM default CSS when no override/append; dynamic CSS path - PortalAppJS: portal timeout countdown; safe WiFi scan DOM; serialized scan-status polling; fetch error handling; wm:view-changed on redirects - CSS: scan list icon alignment; wm-status for timeout - Tests: remove low-value/no-op tests; trim fragile JS substring checks
This commit is contained in:
@@ -8,7 +8,6 @@ TestCase tests[] = {
|
||||
// Basic tests
|
||||
TEST_ENTRY(test_basic_wifimanager_instantiation),
|
||||
TEST_ENTRY(test_reset_settings),
|
||||
TEST_ENTRY(test_disconnect),
|
||||
// JSON API shape (buildApiInfoJson is large — run with fresh heap, before stress tests)
|
||||
TEST_ENTRY(test_captive_redirect_host_rule),
|
||||
TEST_ENTRY(test_api_wifi_meta_json_shape),
|
||||
@@ -17,9 +16,7 @@ TestCase tests[] = {
|
||||
TEST_ENTRY(test_api_status_json_shape),
|
||||
|
||||
// Configuration tests
|
||||
TEST_ENTRY(test_configuration_setters),
|
||||
TEST_ENTRY(test_set_and_get_hostname),
|
||||
TEST_ENTRY(test_wifi_ap_configuration_setters),
|
||||
TEST_ENTRY(test_get_default_ap_name),
|
||||
TEST_ENTRY(test_get_wifi_status_string),
|
||||
TEST_ENTRY(test_get_mode_string),
|
||||
@@ -37,19 +34,18 @@ TestCase tests[] = {
|
||||
TEST_ENTRY(test_config_portal_already_active),
|
||||
TEST_ENTRY(test_get_config_portal_ssid),
|
||||
TEST_ENTRY(test_bootstrap_json_portal_feature_flags),
|
||||
TEST_ENTRY(test_bootstrap_json_contract_v2),
|
||||
TEST_ENTRY(test_bootstrap_json_snapshot_consistency),
|
||||
TEST_ENTRY(test_root_render_interleaved_context_isolation),
|
||||
|
||||
// Non-blocking tests
|
||||
TEST_ENTRY(test_nonblocking_process),
|
||||
TEST_ENTRY(test_client_check_setters),
|
||||
TEST_ENTRY(test_nonblocking_timeout_behavior),
|
||||
TEST_ENTRY(test_process_required_for_timeout),
|
||||
|
||||
// Callback tests
|
||||
TEST_ENTRY(test_ap_callback),
|
||||
TEST_ENTRY(test_web_server_callback),
|
||||
TEST_ENTRY(test_callback_registration),
|
||||
TEST_ENTRY(test_multiple_callbacks),
|
||||
|
||||
// Parameter tests
|
||||
@@ -64,21 +60,11 @@ TestCase tests[] = {
|
||||
|
||||
// Static IP tests
|
||||
TEST_ENTRY(test_set_ap_static_ip_config),
|
||||
TEST_ENTRY(test_sta_static_ip_configuration),
|
||||
TEST_ENTRY(test_ap_static_ip_application),
|
||||
|
||||
// Advanced options tests
|
||||
TEST_ENTRY(test_connection_behavior_options),
|
||||
TEST_ENTRY(test_ui_display_options),
|
||||
TEST_ENTRY(test_ui_customization),
|
||||
TEST_ENTRY(test_debug_soft_ap_config),
|
||||
TEST_ENTRY(test_debug_platform_info),
|
||||
|
||||
// WiFi connection tests
|
||||
TEST_ENTRY(test_autoconnect_fallback_to_portal),
|
||||
TEST_ENTRY(test_connectwifi_ssid_not_found),
|
||||
TEST_ENTRY(test_connectwifi_retry_count),
|
||||
TEST_ENTRY(test_connectwifi_timeout_setting),
|
||||
TEST_ENTRY(test_connection_state_transitions),
|
||||
TEST_ENTRY(test_autoconnect_with_timeout),
|
||||
|
||||
@@ -94,10 +80,6 @@ TestCase tests[] = {
|
||||
TEST_ENTRY(test_dns_server_lifecycle_cycles),
|
||||
|
||||
// Error condition tests
|
||||
TEST_ENTRY(test_invalid_ap_password_too_short),
|
||||
TEST_ENTRY(test_invalid_ap_password_too_long),
|
||||
TEST_ENTRY(test_empty_ssid),
|
||||
TEST_ENTRY(test_very_long_ssid),
|
||||
TEST_ENTRY(test_connection_failure_handling),
|
||||
TEST_ENTRY(test_resource_cleanup_after_error),
|
||||
TEST_ENTRY(test_multiple_rapid_start_stop),
|
||||
@@ -120,6 +102,7 @@ TestCase tests[] = {
|
||||
|
||||
// Template rendering tests
|
||||
TEST_ENTRY(test_shell_template_renders_core_placeholders),
|
||||
TEST_ENTRY(test_shell_template_renders_dynamic_styles_with_percent_values),
|
||||
|
||||
// State transition tests
|
||||
TEST_ENTRY(test_portal_to_connected_transition),
|
||||
|
||||
@@ -18,12 +18,9 @@ struct TestCase {
|
||||
// Basic tests
|
||||
void test_basic_wifimanager_instantiation();
|
||||
void test_reset_settings();
|
||||
void test_disconnect();
|
||||
|
||||
// Configuration tests
|
||||
void test_configuration_setters();
|
||||
void test_set_and_get_hostname();
|
||||
void test_wifi_ap_configuration_setters();
|
||||
void test_get_default_ap_name();
|
||||
void test_get_wifi_status_string();
|
||||
void test_get_mode_string();
|
||||
@@ -41,19 +38,18 @@ void test_config_portal_multiple_start_stop();
|
||||
void test_config_portal_already_active();
|
||||
void test_get_config_portal_ssid();
|
||||
void test_bootstrap_json_portal_feature_flags();
|
||||
void test_bootstrap_json_contract_v2();
|
||||
void test_bootstrap_json_snapshot_consistency();
|
||||
void test_root_render_interleaved_context_isolation();
|
||||
|
||||
// Non-blocking tests
|
||||
void test_nonblocking_process();
|
||||
void test_client_check_setters();
|
||||
void test_nonblocking_timeout_behavior();
|
||||
void test_process_required_for_timeout();
|
||||
|
||||
// Callback tests
|
||||
void test_ap_callback();
|
||||
void test_web_server_callback();
|
||||
void test_callback_registration();
|
||||
void test_multiple_callbacks();
|
||||
|
||||
// Parameter tests
|
||||
@@ -68,21 +64,13 @@ void test_parameter_placeholder();
|
||||
|
||||
// Static IP tests
|
||||
void test_set_ap_static_ip_config();
|
||||
void test_sta_static_ip_configuration();
|
||||
void test_ap_static_ip_application();
|
||||
|
||||
// Advanced options tests
|
||||
void test_connection_behavior_options();
|
||||
void test_ui_display_options();
|
||||
void test_ui_customization();
|
||||
void test_debug_soft_ap_config();
|
||||
void test_debug_platform_info();
|
||||
|
||||
// WiFi connection tests
|
||||
void test_autoconnect_fallback_to_portal();
|
||||
void test_connectwifi_ssid_not_found();
|
||||
void test_connectwifi_retry_count();
|
||||
void test_connectwifi_timeout_setting();
|
||||
void test_connection_state_transitions();
|
||||
void test_autoconnect_with_timeout();
|
||||
|
||||
@@ -98,10 +86,6 @@ void test_dns_server_cleanup();
|
||||
void test_dns_server_lifecycle_cycles();
|
||||
|
||||
// Error condition tests
|
||||
void test_invalid_ap_password_too_short();
|
||||
void test_invalid_ap_password_too_long();
|
||||
void test_empty_ssid();
|
||||
void test_very_long_ssid();
|
||||
void test_connection_failure_handling();
|
||||
void test_resource_cleanup_after_error();
|
||||
void test_multiple_rapid_start_stop();
|
||||
@@ -124,6 +108,7 @@ void test_scan_generation_invalidated_on_reset();
|
||||
|
||||
// Template rendering tests
|
||||
void test_shell_template_renders_core_placeholders();
|
||||
void test_shell_template_renders_dynamic_styles_with_percent_values();
|
||||
|
||||
// API JSON + captive helper tests
|
||||
void test_captive_redirect_host_rule();
|
||||
|
||||
@@ -27,6 +27,18 @@ void test_connection_behavior_options() {
|
||||
wm.setEnableConfigPortal(false);
|
||||
wm.setDisableConfigPortal(true);
|
||||
wm.setDisableConfigPortal(false);
|
||||
wm.portalSetBehaviorConnectOnSave(true);
|
||||
wm.portalSetBehaviorConnectOnSave(false);
|
||||
wm.portalSetBehaviorExitAllowed(true);
|
||||
wm.portalSetBehaviorExitAllowed(false);
|
||||
wm.portalSetBehaviorConnectTimeoutSeconds(15);
|
||||
wm.portalSetBehaviorPortalTimeoutSeconds(20);
|
||||
wm.portalSetBehaviorAutoReconnect(true);
|
||||
wm.portalSetBehaviorAutoReconnect(false);
|
||||
wm.portalSetBehaviorApClientCheck(true);
|
||||
wm.portalSetBehaviorApClientCheck(false);
|
||||
wm.portalSetBehaviorWebClientCheck(true);
|
||||
wm.portalSetBehaviorWebClientCheck(false);
|
||||
wm.preloadWiFi("TestSSID", "TestPassword");
|
||||
|
||||
// All setters executed without crash
|
||||
@@ -42,16 +54,27 @@ void test_ui_display_options() {
|
||||
WiFiManager wm;
|
||||
|
||||
// Test display toggles
|
||||
wm.setShowPassword(true);
|
||||
wm.setShowPassword(false);
|
||||
wm.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Actual);
|
||||
wm.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Masked);
|
||||
wm.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Hidden);
|
||||
wm.setScanDispPerc(true);
|
||||
wm.setScanDispPerc(false);
|
||||
wm.setShowInfoErase(true);
|
||||
wm.setShowInfoErase(false);
|
||||
wm.setShowInfoUpdate(true);
|
||||
wm.setShowInfoUpdate(false);
|
||||
wm.setParametersEmbeddedInWifi(true);
|
||||
wm.setParametersEmbeddedInWifi(false);
|
||||
wm.portalSetActionEraseVisible(true);
|
||||
wm.portalSetActionEraseVisible(false);
|
||||
wm.portalSetActionRestartVisible(true);
|
||||
wm.portalSetActionRestartVisible(false);
|
||||
wm.portalSetActionExitVisible(true);
|
||||
wm.portalSetActionExitVisible(false);
|
||||
wm.portalSetActionCloseCaptiveVisible(true);
|
||||
wm.portalSetActionCloseCaptiveVisible(false);
|
||||
wm.portalSetPageUpdateVisible(true);
|
||||
wm.portalSetPageUpdateVisible(false);
|
||||
wm.portalSetPageSetupVisible(true);
|
||||
wm.portalSetPageSetupVisible(false);
|
||||
wm.portalSetLayoutParamsLocation(PortalParamsLocation::WiFiPage);
|
||||
wm.portalSetLayoutParamsLocation(PortalParamsLocation::SetupPage);
|
||||
wm.portalSetActionBackVisible(true);
|
||||
wm.portalSetActionBackVisible(false);
|
||||
|
||||
// All setters executed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UI display options executed without crash");
|
||||
@@ -66,7 +89,7 @@ void test_ui_customization() {
|
||||
WiFiManager wm;
|
||||
|
||||
// Minimal customization retained: title
|
||||
wm.setTitle("MyDevice");
|
||||
wm.portalSetBrandTitle("MyDevice");
|
||||
|
||||
// All setters executed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UI customization options executed without crash");
|
||||
|
||||
@@ -29,6 +29,14 @@ void test_api_wifi_meta_json_shape() {
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"canRefreshScan\":true")));
|
||||
|
||||
WiFiManagerParameter fieldParam("mqtt_host", "MQTT host", "broker.local", 64, "placeholder='broker.local'");
|
||||
WiFiManagerParameter htmlParam("<div class='wm-callout wm-callout--info'><p>GPS settings...</p></div>");
|
||||
wm.portalAddParameter(&fieldParam);
|
||||
wm.portalAddParameter(&htmlParam);
|
||||
j = handlers.buildApiWifiMetaJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"kind\":\"field\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"kind\":\"html\"")));
|
||||
|
||||
Serial.println("[TEST] WiFi meta JSON shape test completed successfully");
|
||||
}
|
||||
|
||||
@@ -37,6 +45,12 @@ void test_api_info_json_shape() {
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
PortalInfoSection battery;
|
||||
battery.id = "battery";
|
||||
battery.title = "Battery";
|
||||
battery.items.push_back({"soc", "State of charge", "84%"});
|
||||
battery.items.push_back({"voltage", "Voltage", "13.2V"});
|
||||
wm.portalAddInfoSection(battery);
|
||||
String j = handlers.buildApiInfoJson();
|
||||
const char* p = j.c_str();
|
||||
// Shape checks only: "status" is a nested object (},\n"device" not ],"device"); require
|
||||
@@ -48,6 +62,10 @@ void test_api_info_json_shape() {
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"status\":"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"connected\":"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "},\"device\":["));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"extraSections\":"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"id\":\"battery\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"title\":\"Battery\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"label\":\"State of charge\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"showUpdate\":"));
|
||||
|
||||
Serial.println("[TEST] API info JSON shape test completed successfully");
|
||||
|
||||
@@ -9,10 +9,7 @@ void test_basic_wifimanager_instantiation() {
|
||||
|
||||
// Test default constructor
|
||||
WiFiManager wm;
|
||||
|
||||
// Verify instance created (basic sanity check)
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "WiFiManager instance created successfully");
|
||||
|
||||
|
||||
// Test that we can call basic methods without crashing
|
||||
String defaultName = wm.getDefaultAPName();
|
||||
TEST_ASSERT_GREATER_THAN(0, defaultName.length());
|
||||
@@ -69,9 +66,6 @@ void test_disconnect() {
|
||||
// Can be called multiple times safely
|
||||
(void)wm.disconnect();
|
||||
|
||||
// Verify no crash (results may be true/false depending on WiFi state)
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "disconnect() executed without crash");
|
||||
|
||||
Serial.println("[TEST] disconnect() test completed successfully");
|
||||
}
|
||||
|
||||
|
||||
@@ -36,26 +36,18 @@ void test_configuration_setters() {
|
||||
wm.setShowDnsFields(false);
|
||||
|
||||
// Test portal behavior
|
||||
wm.setCaptivePortalEnable(true);
|
||||
wm.setCaptivePortalEnable(false);
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(true);
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(false);
|
||||
|
||||
// Test custom title (no getter available, but we can verify it doesn't crash)
|
||||
wm.setTitle("TestTitle");
|
||||
|
||||
// Verify some settings can be retrieved (if getters exist)
|
||||
// Note: Not all setters have getters, but we verify they don't crash
|
||||
// All setters executed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "All configuration setters executed without crash");
|
||||
|
||||
// Verify setters can be called multiple times with different values
|
||||
// Test custom title path
|
||||
wm.portalSetBrandTitle("TestTitle");
|
||||
|
||||
// Call setters multiple times with different values.
|
||||
wm.setConfigPortalTimeout(60);
|
||||
wm.setConnectTimeout(30);
|
||||
wm.setHttpPort(8080);
|
||||
wm.setMinimumSignalQuality(50);
|
||||
|
||||
// Verify no crash after multiple calls
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Configuration setters can be called multiple times");
|
||||
|
||||
Serial.println("[TEST] Configuration setters test completed successfully");
|
||||
}
|
||||
|
||||
@@ -76,7 +68,6 @@ void test_set_and_get_hostname() {
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.setHostname("trailing-"), "Hostnames cannot end with a hyphen");
|
||||
|
||||
(void)wm.getWiFiHostname();
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "getWiFiHostname() executed after hostname validation checks");
|
||||
|
||||
Serial.println("[TEST] setHostname() and getWiFiHostname() test completed successfully");
|
||||
}
|
||||
@@ -108,9 +99,6 @@ void test_wifi_ap_configuration_setters() {
|
||||
wm.setWiFiAutoReconnect(true);
|
||||
wm.setWiFiAutoReconnect(false);
|
||||
|
||||
// All setters executed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "All WiFi AP configuration setters executed without crash");
|
||||
|
||||
Serial.println("[TEST] WiFi AP configuration setters test completed successfully");
|
||||
}
|
||||
|
||||
|
||||
@@ -172,10 +172,7 @@ void test_multiple_rapid_start_stop() {
|
||||
wm.process();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// Should not crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Multiple rapid start/stop calls handled without crash");
|
||||
|
||||
|
||||
// Final state should be stopped
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ void test_parameter_add_and_retrieve() {
|
||||
|
||||
// 1. Add parameters
|
||||
WiFiManagerParameter param("server", "Server", "default", 40);
|
||||
wm.addParameter(¶m);
|
||||
wm.portalAddParameter(¶m);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, wm.getParametersCount());
|
||||
|
||||
@@ -135,8 +135,8 @@ void test_portal_with_parameters_and_infrastructure() {
|
||||
// Add parameters
|
||||
WiFiManagerParameter p1("server", "Server", "192.168.1.1", 40);
|
||||
WiFiManagerParameter p2("port", "Port", "1883", 6);
|
||||
wm.addParameter(&p1);
|
||||
wm.addParameter(&p2);
|
||||
wm.portalAddParameter(&p1);
|
||||
wm.portalAddParameter(&p2);
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
|
||||
@@ -44,7 +44,7 @@ void test_parameter_value_persistence() {
|
||||
WiFiManager wm;
|
||||
|
||||
WiFiManagerParameter param("server", "Server", "default", 40);
|
||||
wm.addParameter(¶m);
|
||||
wm.portalAddParameter(¶m);
|
||||
|
||||
// Set value
|
||||
param.setValue("persisted_value", 40);
|
||||
@@ -87,9 +87,9 @@ void test_multiple_parameters_different_values() {
|
||||
WiFiManagerParameter p2("port", "Port", "1883", 6);
|
||||
WiFiManagerParameter p3("key", "Key", "default_key", 32);
|
||||
|
||||
wm.addParameter(&p1);
|
||||
wm.addParameter(&p2);
|
||||
wm.addParameter(&p3);
|
||||
wm.portalAddParameter(&p1);
|
||||
wm.portalAddParameter(&p2);
|
||||
wm.portalAddParameter(&p3);
|
||||
|
||||
// Set different values
|
||||
p1.setValue("10.0.0.1", 40);
|
||||
|
||||
@@ -20,7 +20,7 @@ void test_create_parameter() {
|
||||
}
|
||||
|
||||
void test_add_parameter() {
|
||||
Serial.println("[TEST] Testing addParameter()...");
|
||||
Serial.println("[TEST] Testing portalAddParameter()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
@@ -28,16 +28,18 @@ void test_add_parameter() {
|
||||
WiFiManagerParameter param2("param2", "Parameter 2", "value2", 40);
|
||||
|
||||
// Add parameters
|
||||
bool result1 = wm.addParameter(¶m1);
|
||||
bool result2 = wm.addParameter(¶m2);
|
||||
bool result1 = wm.portalAddParameter(¶m1);
|
||||
bool result2 = wm.portalAddParameter(¶m2);
|
||||
|
||||
TEST_ASSERT_TRUE(result1);
|
||||
TEST_ASSERT_TRUE(result2);
|
||||
|
||||
// Verify parameters are stored
|
||||
TEST_ASSERT_EQUAL(2, wm.getParametersCount());
|
||||
wm.portalClearParameters();
|
||||
TEST_ASSERT_EQUAL(0, wm.getParametersCount());
|
||||
|
||||
Serial.println("[TEST] addParameter() test completed successfully");
|
||||
Serial.println("[TEST] portalAddParameter() test completed successfully");
|
||||
}
|
||||
|
||||
void test_get_parameters() {
|
||||
@@ -49,9 +51,9 @@ void test_get_parameters() {
|
||||
WiFiManagerParameter param2("param2", "Parameter 2", "value2", 40);
|
||||
WiFiManagerParameter param3("param3", "Parameter 3", "value3", 40);
|
||||
|
||||
wm.addParameter(¶m1);
|
||||
wm.addParameter(¶m2);
|
||||
wm.addParameter(¶m3);
|
||||
wm.portalAddParameter(¶m1);
|
||||
wm.portalAddParameter(¶m2);
|
||||
wm.portalAddParameter(¶m3);
|
||||
|
||||
// Get count
|
||||
int count = wm.getParametersCount();
|
||||
@@ -121,9 +123,9 @@ void test_multiple_parameters() {
|
||||
WiFiManagerParameter p2("mqtt_port", "MQTT Port", "1883", 6);
|
||||
WiFiManagerParameter p3("api_key", "API Key", "default_key", 32);
|
||||
|
||||
wm.addParameter(&p1);
|
||||
wm.addParameter(&p2);
|
||||
wm.addParameter(&p3);
|
||||
wm.portalAddParameter(&p1);
|
||||
wm.portalAddParameter(&p2);
|
||||
wm.portalAddParameter(&p3);
|
||||
|
||||
TEST_ASSERT_EQUAL(3, wm.getParametersCount());
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ size_t dynamicStringLengthGetter(const char* data, void* /*userData*/) {
|
||||
return data ? strlen(data) : 0;
|
||||
}
|
||||
|
||||
void configureDescriptor(DynamicTemplateDescriptor& descriptor, String& value) {
|
||||
void configureDescriptor(DynamicDataDescriptor& descriptor, String& value) {
|
||||
descriptor.getter = &dynamicStringGetter;
|
||||
descriptor.getLength = &dynamicStringLengthGetter;
|
||||
descriptor.userData = &value;
|
||||
@@ -62,20 +62,77 @@ void test_bootstrap_json_portal_feature_flags() {
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
|
||||
String idleBootstrap = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_EQUAL(-1, idleBootstrap.indexOf(F("\"showCloseCaptive\":true")));
|
||||
TEST_ASSERT_EQUAL(-1, idleBootstrap.indexOf(F("\"closeCaptive\":{\"visible\":true")));
|
||||
|
||||
wm.wmTestSetPortalActive(true);
|
||||
wm.setCaptivePortalEnable(true);
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(true);
|
||||
|
||||
String apBootstrap = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"showCloseCaptive\":true")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"showExitPortal\":true")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"closeCaptive\":{\"visible\":true")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"exitPortal\":{\"visible\":true")));
|
||||
|
||||
wm.wmTestSetPortalActive(false);
|
||||
|
||||
Serial.println("[TEST] Bootstrap portal feature flags test completed successfully");
|
||||
}
|
||||
|
||||
void test_bootstrap_json_contract_v2() {
|
||||
Serial.println("[TEST] Testing bootstrap JSON v2 contract...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
|
||||
wm.portalSetBrandTitle("Solar Battery Monitor Setup");
|
||||
wm.portalSetContextIdentityText("Solar Battery Monitor");
|
||||
wm.portalSetBrandHomeIntro("Connect this device to WiFi and finish setup.");
|
||||
wm.portalSetBrandLogoSvg("<svg viewBox='0 0 24 24'></svg>");
|
||||
wm.portalSetPageInfoVisible(true);
|
||||
wm.portalSetPageUpdateVisible(false);
|
||||
wm.portalSetPageSetupVisible(true);
|
||||
wm.portalSetActionEraseVisible(false);
|
||||
wm.portalSetActionBackVisible(true);
|
||||
wm.portalSetLayoutParamsLocation(PortalParamsLocation::SetupPage);
|
||||
wm.setConfigPortalTimeout(90);
|
||||
|
||||
PortalHomeCard card;
|
||||
card.id = "solar";
|
||||
card.title = "Solar summary";
|
||||
card.kind = PortalHomeCardKind::KeyValue;
|
||||
card.items.push_back({"pv", "PV input", "420W"});
|
||||
wm.portalAddHomeCard(card);
|
||||
|
||||
String j = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"contractVersion\":2")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"brand\":{")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"title\":\"Solar Battery Monitor Setup\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"homeIntro\":\"Connect this device to WiFi and finish setup.\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"logoSvg\":\"<svg viewBox='0 0 24 24'></svg>\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"context\":{")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"identityText\":\"Solar Battery Monitor\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"pages\":{")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"setup\":{\"visible\":true}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\":{")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"erase\":{\"visible\":false}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"back\":{\"visible\":true}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"layout\":{\"paramsLocation\":\"setup\"}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"extraHomeCards\":[")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"id\":\"solar\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"kind\":\"kv\"")));
|
||||
|
||||
wm.startWebPortal();
|
||||
j = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"portalActive\":true")));
|
||||
wm.stopWebPortal();
|
||||
|
||||
wm.wmTestSetPortalActive(true);
|
||||
j = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"portalTimeoutSecondsRemaining\":")));
|
||||
TEST_ASSERT_EQUAL(-1, j.indexOf(F("\"portalTimeoutSecondsRemaining\":0")));
|
||||
wm.wmTestSetPortalActive(false);
|
||||
|
||||
Serial.println("[TEST] Bootstrap JSON v2 contract test completed successfully");
|
||||
}
|
||||
|
||||
void test_bootstrap_json_snapshot_consistency() {
|
||||
Serial.println("[TEST] Testing bootstrap JSON snapshot consistency...");
|
||||
|
||||
@@ -106,32 +163,34 @@ void test_root_render_interleaved_context_isolation() {
|
||||
|
||||
registryA.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
|
||||
registryA.registerProgmemData("%PAGE_TITLE%", kTestTitle);
|
||||
registryA.registerProgmemData("%PORTAL_APPEND_JS%", kEmptyTemplateChunk);
|
||||
|
||||
registryB.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
|
||||
registryB.registerProgmemData("%PAGE_TITLE%", kTestTitle);
|
||||
registryB.registerProgmemData("%PORTAL_APPEND_JS%", kEmptyTemplateChunk);
|
||||
|
||||
String bootstrapA = F("{\"ctx\":\"A\"}");
|
||||
String appJsA = F("// shell A");
|
||||
String bootstrapB = F("{\"ctx\":\"B\"}");
|
||||
String appJsB = F("// shell B");
|
||||
|
||||
DynamicTemplateDescriptor bootstrapDescriptorA{};
|
||||
DynamicTemplateDescriptor appJsDescriptorA{};
|
||||
DynamicTemplateDescriptor bootstrapDescriptorB{};
|
||||
DynamicTemplateDescriptor appJsDescriptorB{};
|
||||
DynamicDataDescriptor bootstrapDescriptorA{};
|
||||
DynamicDataDescriptor appJsDescriptorA{};
|
||||
DynamicDataDescriptor bootstrapDescriptorB{};
|
||||
DynamicDataDescriptor appJsDescriptorB{};
|
||||
|
||||
configureDescriptor(bootstrapDescriptorA, bootstrapA);
|
||||
configureDescriptor(appJsDescriptorA, appJsA);
|
||||
configureDescriptor(bootstrapDescriptorB, bootstrapB);
|
||||
configureDescriptor(appJsDescriptorB, appJsB);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%BOOTSTRAP_JSON%", &bootstrapDescriptorA),
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicData("%BOOTSTRAP_JSON%", &bootstrapDescriptorA),
|
||||
"Registry A bootstrap placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%PORTAL_APP_JS%", &appJsDescriptorA),
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicData("%PORTAL_APP_JS%", &appJsDescriptorA),
|
||||
"Registry A app JS placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%BOOTSTRAP_JSON%", &bootstrapDescriptorB),
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicData("%BOOTSTRAP_JSON%", &bootstrapDescriptorB),
|
||||
"Registry B bootstrap placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%PORTAL_APP_JS%", &appJsDescriptorB),
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicData("%PORTAL_APP_JS%", &appJsDescriptorB),
|
||||
"Registry B app JS placeholder should register");
|
||||
|
||||
TemplateContext contextA;
|
||||
|
||||
@@ -112,7 +112,7 @@ void test_multiple_parameters_stress() {
|
||||
sprintf(id, "param%d", i);
|
||||
sprintf(label, "Parameter %d", i);
|
||||
params[i] = new WiFiManagerParameter(id, label, "default", 40);
|
||||
wm.addParameter(params[i]);
|
||||
wm.portalAddParameter(params[i]);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL(20, wm.getParametersCount());
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <Arduino.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "templates/RootShell.h"
|
||||
#include "templates/PortalAppJS.h"
|
||||
#include "../test_main.h"
|
||||
|
||||
namespace {
|
||||
@@ -27,6 +28,22 @@ const char kEmpty[] PROGMEM = "";
|
||||
const char kDocTitle[] PROGMEM = "Config ESP";
|
||||
const char kBootstrapJson[] PROGMEM = "{\"title\":\"Test\"}";
|
||||
const char kPortalAppJs[] PROGMEM = "console.log('portal');";
|
||||
const char kPortalAppendJs[] PROGMEM = "";
|
||||
|
||||
const char* dynamicStringGetter(void* userData) {
|
||||
const auto* value = static_cast<const String*>(userData);
|
||||
return value ? value->c_str() : "";
|
||||
}
|
||||
|
||||
size_t dynamicStringLengthGetter(const char* data, void* /*userData*/) {
|
||||
return data ? strlen(data) : 0;
|
||||
}
|
||||
|
||||
void configureDescriptor(DynamicDataDescriptor& descriptor, String& value) {
|
||||
descriptor.getter = &dynamicStringGetter;
|
||||
descriptor.getLength = &dynamicStringLengthGetter;
|
||||
descriptor.userData = &value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -38,6 +55,7 @@ void test_shell_template_renders_core_placeholders() {
|
||||
registry.registerProgmemData("%PAGE_TITLE%", kDocTitle);
|
||||
registry.registerProgmemData("%BOOTSTRAP_JSON%", kBootstrapJson);
|
||||
registry.registerProgmemData("%PORTAL_APP_JS%", kPortalAppJs);
|
||||
registry.registerProgmemData("%PORTAL_APPEND_JS%", kPortalAppendJs);
|
||||
|
||||
String output = renderTemplate(WM_ROOT_SHELL_TEMPLATE, registry);
|
||||
|
||||
@@ -51,3 +69,36 @@ void test_shell_template_renders_core_placeholders() {
|
||||
|
||||
Serial.println("[TEST] Portal shell template rendering test completed successfully");
|
||||
}
|
||||
|
||||
void test_shell_template_renders_dynamic_styles_with_percent_values() {
|
||||
Serial.println("[TEST] Testing portal shell dynamic styles placeholder with percent values...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
String styles = F("<style>body{width:100%;max-width:92%}</style>");
|
||||
String title = F("Config ESP");
|
||||
String bootstrap = F("{\"title\":\"Test\"}");
|
||||
String appendJs = F("");
|
||||
DynamicDataDescriptor stylesDescriptor{};
|
||||
DynamicDataDescriptor titleDescriptor{};
|
||||
DynamicDataDescriptor bootstrapDescriptor{};
|
||||
DynamicDataDescriptor appendJsDescriptor{};
|
||||
|
||||
configureDescriptor(stylesDescriptor, styles);
|
||||
configureDescriptor(titleDescriptor, title);
|
||||
configureDescriptor(bootstrapDescriptor, bootstrap);
|
||||
configureDescriptor(appendJsDescriptor, appendJs);
|
||||
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%STYLES%", &stylesDescriptor));
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%PAGE_TITLE%", &titleDescriptor));
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%BOOTSTRAP_JSON%", &bootstrapDescriptor));
|
||||
TEST_ASSERT_TRUE(registry.registerProgmemData("%PORTAL_APP_JS%", kPortalAppJs));
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%PORTAL_APPEND_JS%", &appendJsDescriptor));
|
||||
|
||||
String output = renderTemplate(WM_ROOT_SHELL_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<style>body{width:100%;max-width:92%}</style>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<title>Config ESP</title>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("{\"title\":\"Test\"}"));
|
||||
|
||||
Serial.println("[TEST] Dynamic styles placeholder percent rendering test completed successfully");
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ void test_scan_status_checking() {
|
||||
TEST_ASSERT_EQUAL(static_cast<size_t>(2), wm.getScanResults().size());
|
||||
TEST_ASSERT_EQUAL_STRING("Office", wm.getScanResults()[0].ssid.c_str());
|
||||
#else
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UNIT_TEST helpers unavailable");
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Cached scan snapshot state test completed successfully");
|
||||
@@ -79,7 +79,7 @@ void test_scan_completion_wait() {
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.hasValidScanResults(),
|
||||
"Timed out scans should not leave cached results marked valid");
|
||||
#else
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UNIT_TEST helpers unavailable");
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Scan timeout transition test completed successfully");
|
||||
@@ -101,7 +101,7 @@ void test_scan_cancels_when_connect_pending() {
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.getScanSnapshot().schedulePending,
|
||||
"Connect flow should cancel any in-flight async scan");
|
||||
#else
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UNIT_TEST helpers unavailable");
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Scan cancellation during connect test completed successfully");
|
||||
@@ -122,7 +122,7 @@ void test_scan_cancels_when_lifecycle_blocked() {
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.getScanSnapshot().schedulePending,
|
||||
"Lifecycle blocking should leave scan engine idle");
|
||||
#else
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UNIT_TEST helpers unavailable");
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Scan cancellation during lifecycle block test completed successfully");
|
||||
@@ -144,7 +144,7 @@ void test_scan_generation_invalidated_on_reset() {
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.getScanSnapshot().completionPending,
|
||||
"Resetting scan state should clear pending completion callbacks");
|
||||
#else
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UNIT_TEST helpers unavailable");
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Scan generation invalidation on reset test completed successfully");
|
||||
|
||||
Reference in New Issue
Block a user