mirror of
https://github.com/alexhopeoconnor/WiFiManager.git
synced 2026-10-22 02:32:10 +10:00
Release v3.1.0
This commit is contained in:
@@ -20,11 +20,13 @@ lib_deps =
|
||||
|
||||
[env:esp32]
|
||||
extends = common
|
||||
platform = espressif32@6.13.0
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
board = esp32dev
|
||||
build_unflags = -std=gnu++11
|
||||
build_flags =
|
||||
-std=gnu++14
|
||||
-DSOC_WIFI_SUPPORTED=1
|
||||
-I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src
|
||||
-DWM_LOG_LEVEL=3
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
|
||||
@@ -1,9 +1,45 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
namespace {
|
||||
const char kTitle[] PROGMEM = "Set up Compile Fixture";
|
||||
const char kIdentity[] PROGMEM = "WiFiManager";
|
||||
const char kIntro[] PROGMEM = "A branded portal compile check.";
|
||||
const char kLogoAlt[] PROGMEM = "WiFiManager";
|
||||
const char kLogo[] PROGMEM = "<svg viewBox='0 0 24 24'></svg>";
|
||||
const char kPage[] PROGMEM = "#f4f7f3";
|
||||
const char kSurface[] PROGMEM = "#ffffff";
|
||||
const char kText[] PROGMEM = "#1c251e";
|
||||
const char kMuted[] PROGMEM = "#607064";
|
||||
const char kBorder[] PROGMEM = "#d6e0d7";
|
||||
const char kAccent[] PROGMEM = "#347a45";
|
||||
const char kAccentHover[] PROGMEM = "#245a32";
|
||||
const char kAccentText[] PROGMEM = "#ffffff";
|
||||
|
||||
const WiFiManagerPortalConfig kPortalConfig = {
|
||||
WiFiManagerPortalText::progmem(kTitle),
|
||||
WiFiManagerPortalText::progmem(kIdentity),
|
||||
WiFiManagerPortalText::progmem(kIntro),
|
||||
WiFiManagerPortalAsset::svgFromProgmem(kLogo),
|
||||
WiFiManagerPortalText::progmem(kLogoAlt),
|
||||
{
|
||||
WiFiManagerPortalText::progmem(kPage),
|
||||
WiFiManagerPortalText::progmem(kSurface),
|
||||
WiFiManagerPortalText::progmem(kText),
|
||||
WiFiManagerPortalText::progmem(kMuted),
|
||||
WiFiManagerPortalText::progmem(kBorder),
|
||||
WiFiManagerPortalText::progmem(kAccent),
|
||||
WiFiManagerPortalText::progmem(kAccentHover),
|
||||
WiFiManagerPortalText::progmem(kAccentText),
|
||||
{}, {}, {}, 10, 6,
|
||||
},
|
||||
};
|
||||
} // namespace
|
||||
|
||||
WiFiManager wifiManager;
|
||||
|
||||
void setup() {
|
||||
wifiManager.setPortalConfig(kPortalConfig);
|
||||
wifiManager.setConfigPortalTimeout(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,11 @@ 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_portal_default_presentation),
|
||||
TEST_ENTRY(test_bootstrap_json_contract_v2),
|
||||
TEST_ENTRY(test_bootstrap_json_snapshot_consistency),
|
||||
TEST_ENTRY(test_root_render_interleaved_context_isolation),
|
||||
TEST_ENTRY(test_portal_presentation_configuration),
|
||||
|
||||
// Non-blocking tests
|
||||
TEST_ENTRY(test_nonblocking_process),
|
||||
@@ -104,7 +106,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),
|
||||
TEST_ENTRY(test_shell_template_renders_dynamic_theme_with_percent_values),
|
||||
|
||||
// State transition tests
|
||||
TEST_ENTRY(test_portal_to_connected_transition),
|
||||
|
||||
@@ -38,9 +38,11 @@ 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_portal_default_presentation();
|
||||
void test_bootstrap_json_contract_v2();
|
||||
void test_bootstrap_json_snapshot_consistency();
|
||||
void test_root_render_interleaved_context_isolation();
|
||||
void test_portal_presentation_configuration();
|
||||
|
||||
// Non-blocking tests
|
||||
void test_nonblocking_process();
|
||||
@@ -108,7 +110,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();
|
||||
void test_shell_template_renders_dynamic_theme_with_percent_values();
|
||||
|
||||
// API JSON + captive helper tests
|
||||
void test_captive_redirect_host_rule();
|
||||
|
||||
@@ -88,8 +88,10 @@ void test_ui_customization() {
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Minimal customization retained: title
|
||||
wm.portalSetBrandTitle("MyDevice");
|
||||
// Minimal customization uses the complete static portal configuration.
|
||||
WiFiManagerPortalConfig portal;
|
||||
portal.title = WiFiManagerPortalText::ram("MyDevice");
|
||||
TEST_ASSERT_TRUE(wm.setPortalConfig(portal));
|
||||
|
||||
// All setters executed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UI customization options executed without crash");
|
||||
|
||||
@@ -39,8 +39,10 @@ void test_configuration_setters() {
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(true);
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(false);
|
||||
|
||||
// Test custom title path
|
||||
wm.portalSetBrandTitle("TestTitle");
|
||||
// Test the typed portal presentation path.
|
||||
WiFiManagerPortalConfig portal;
|
||||
portal.title = WiFiManagerPortalText::ram("TestTitle");
|
||||
TEST_ASSERT_TRUE(wm.setPortalConfig(portal));
|
||||
|
||||
// Call setters multiple times with different values.
|
||||
wm.setConfigPortalTimeout(60);
|
||||
|
||||
@@ -76,16 +76,29 @@ void test_bootstrap_json_portal_feature_flags() {
|
||||
Serial.println("[TEST] Bootstrap portal feature flags test completed successfully");
|
||||
}
|
||||
|
||||
void test_portal_default_presentation() {
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
|
||||
const String bootstrap = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, bootstrap.indexOf(F("\"title\":\"WiFiManager\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, bootstrap.indexOf(F("\"homeIntro\":\"\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, bootstrap.indexOf(F("\"logoSvg\":\"\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, bootstrap.indexOf(F("\"logoAltText\":\"\"")));
|
||||
}
|
||||
|
||||
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>");
|
||||
WiFiManagerPortalConfig portal;
|
||||
portal.title = WiFiManagerPortalText::ram("Solar Battery Monitor Setup");
|
||||
portal.identityText = WiFiManagerPortalText::ram("Solar Battery Monitor");
|
||||
portal.homeIntro = WiFiManagerPortalText::ram("Connect this device to WiFi and finish setup.");
|
||||
portal.logo = WiFiManagerPortalAsset::svgFromRam("<svg viewBox='0 0 24 24'></svg>");
|
||||
TEST_ASSERT_TRUE(wm.setPortalConfig(portal));
|
||||
wm.portalSetPageInfoVisible(true);
|
||||
wm.portalSetPageUpdateVisible(false);
|
||||
wm.portalSetPageSetupVisible(true);
|
||||
@@ -163,11 +176,11 @@ void test_root_render_interleaved_context_isolation() {
|
||||
|
||||
registryA.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
|
||||
registryA.registerProgmemData("%PAGE_TITLE%", kTestTitle);
|
||||
registryA.registerProgmemData("%PORTAL_APPEND_JS%", kEmptyTemplateChunk);
|
||||
registryA.registerProgmemData("%PORTAL_THEME%", kEmptyTemplateChunk);
|
||||
|
||||
registryB.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
|
||||
registryB.registerProgmemData("%PAGE_TITLE%", kTestTitle);
|
||||
registryB.registerProgmemData("%PORTAL_APPEND_JS%", kEmptyTemplateChunk);
|
||||
registryB.registerProgmemData("%PORTAL_THEME%", kEmptyTemplateChunk);
|
||||
|
||||
String bootstrapA = F("{\"ctx\":\"A\"}");
|
||||
String appJsA = F("// shell A");
|
||||
@@ -217,3 +230,51 @@ void test_root_render_interleaved_context_isolation() {
|
||||
|
||||
Serial.println("[TEST] Root render interleaved context isolation test completed successfully");
|
||||
}
|
||||
|
||||
void test_portal_presentation_configuration() {
|
||||
Serial.println("[TEST] Testing portal presentation configuration...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
WiFiManagerPortalConfig config;
|
||||
config.title = WiFiManagerPortalText::ram("Set up Temperature Monitor");
|
||||
config.identityText = WiFiManagerPortalText::ram("Tree");
|
||||
config.homeIntro = WiFiManagerPortalText::ram("Connect this device to WiFi.");
|
||||
config.logo = WiFiManagerPortalAsset::svgFromRam("<svg viewBox='0 0 24 24'></svg>");
|
||||
config.logoAltText = WiFiManagerPortalText::ram("Tree logo");
|
||||
config.theme.pageBackground = WiFiManagerPortalText::ram("#f4f7f3");
|
||||
config.theme.surface = WiFiManagerPortalText::ram("#ffffff");
|
||||
config.theme.text = WiFiManagerPortalText::ram("#1c251e");
|
||||
config.theme.mutedText = WiFiManagerPortalText::ram("#607064");
|
||||
config.theme.border = WiFiManagerPortalText::ram("#d6e0d7");
|
||||
config.theme.accent = WiFiManagerPortalText::ram("#347a45");
|
||||
config.theme.accentHover = WiFiManagerPortalText::ram("#245a32");
|
||||
config.theme.accentText = WiFiManagerPortalText::ram("#ffffff");
|
||||
config.theme.success = WiFiManagerPortalText::ram("#2f855a");
|
||||
config.theme.danger = WiFiManagerPortalText::ram("#c53030");
|
||||
config.theme.dangerHover = WiFiManagerPortalText::ram("#9b2c2c");
|
||||
config.theme.cornerRadiusPx = 10;
|
||||
config.theme.smallCornerRadiusPx = 6;
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.setPortalConfig(config),
|
||||
"A complete setup-time portal configuration should be accepted");
|
||||
|
||||
String bootstrap = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, bootstrap.indexOf(F("\"title\":\"Set up Temperature Monitor\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, bootstrap.indexOf(F("\"identityText\":\"Tree\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, bootstrap.indexOf(F("\"logoAltText\":\"Tree logo\"")));
|
||||
|
||||
WiFiManagerPortalConfig unsafeConfig = config;
|
||||
unsafeConfig.theme.accent = WiFiManagerPortalText::ram("#347a45;body{display:none}");
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.setPortalConfig(unsafeConfig),
|
||||
"Semantic theme values must reject stylesheet injection characters");
|
||||
|
||||
wm.startWebPortal();
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.setPortalConfig(config),
|
||||
"Portal presentation must remain immutable while async routes are active");
|
||||
wm.stopWebPortal();
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.setPortalConfig(config),
|
||||
"Presentation can be applied after the portal stops");
|
||||
|
||||
Serial.println("[TEST] Portal presentation configuration test completed successfully");
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ 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 kPortalTheme[] PROGMEM = "";
|
||||
|
||||
const char* dynamicStringGetter(void* userData) {
|
||||
const auto* value = static_cast<const String*>(userData);
|
||||
@@ -55,7 +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);
|
||||
registry.registerProgmemData("%PORTAL_THEME%", kPortalTheme);
|
||||
|
||||
String output = renderTemplate(WM_ROOT_SHELL_TEMPLATE, registry);
|
||||
|
||||
@@ -70,35 +70,32 @@ 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...");
|
||||
void test_shell_template_renders_dynamic_theme_with_percent_values() {
|
||||
Serial.println("[TEST] Testing portal shell dynamic theme placeholder with percent values...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
String styles = F("<style>body{width:100%;max-width:92%}</style>");
|
||||
String theme = F("<style>:root{--wm-bg:rgb(20,50,30);--wm-width:100%}</style>");
|
||||
String title = F("Config ESP");
|
||||
String bootstrap = F("{\"title\":\"Test\"}");
|
||||
String appendJs = F("");
|
||||
DynamicDataDescriptor stylesDescriptor{};
|
||||
DynamicDataDescriptor themeDescriptor{};
|
||||
DynamicDataDescriptor titleDescriptor{};
|
||||
DynamicDataDescriptor bootstrapDescriptor{};
|
||||
DynamicDataDescriptor appendJsDescriptor{};
|
||||
|
||||
configureDescriptor(stylesDescriptor, styles);
|
||||
configureDescriptor(themeDescriptor, theme);
|
||||
configureDescriptor(titleDescriptor, title);
|
||||
configureDescriptor(bootstrapDescriptor, bootstrap);
|
||||
configureDescriptor(appendJsDescriptor, appendJs);
|
||||
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%STYLES%", &stylesDescriptor));
|
||||
TEST_ASSERT_TRUE(registry.registerProgmemData("%STYLES%", kEmpty));
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%PORTAL_THEME%", &themeDescriptor));
|
||||
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("<style>:root{--wm-bg:rgb(20,50,30);--wm-width:100%}</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");
|
||||
Serial.println("[TEST] Dynamic theme placeholder percent rendering test completed successfully");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user