mirror of
https://github.com/alexhopeoconnor/DFTE.git
synced 2026-10-22 02:32:13 +10:00
Add DFTE sources and tests
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "../templates/simple_templates.h"
|
||||
#include "../templates/placeholder_templates.h"
|
||||
#include "../templates/edge_case_templates.h"
|
||||
#include "../templates/nested_templates.h"
|
||||
#include "../utils/test_utils.h"
|
||||
|
||||
// Test RAM data getter that returns null
|
||||
static const char* getNullRamData() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Test RAM data getter that returns empty string
|
||||
static const char* getEmptyRamData() {
|
||||
static String empty = "";
|
||||
return empty.c_str();
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
void test_edge_cases_error_handling() {
|
||||
Serial.println("[TEST] Testing error handling...");
|
||||
|
||||
PlaceholderRegistry registry(10);
|
||||
TemplateContext ctx;
|
||||
ctx.setRegistry(®istry);
|
||||
|
||||
// Test render with null template pointer (should handle gracefully)
|
||||
// Note: initializeContext expects valid PROGMEM pointer, so we'll test with empty template
|
||||
TemplateRenderer::initializeContext(ctx, empty_template);
|
||||
String result1 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("", result1.c_str(),
|
||||
"Should handle empty template");
|
||||
|
||||
// Test render with null registry
|
||||
ctx.reset();
|
||||
ctx.setRegistry(nullptr);
|
||||
TemplateRenderer::initializeContext(ctx, single_placeholder_template);
|
||||
String result2 = captureRenderedOutput(ctx);
|
||||
// Should skip placeholder and render text
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("Hello, !", result2.c_str(),
|
||||
"Should handle null registry");
|
||||
|
||||
// Test RAM getter returning null
|
||||
registry.registerRamData("%NULL%", getNullRamData);
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, single_placeholder_template);
|
||||
ctx.setRegistry(®istry);
|
||||
String result3 = captureRenderedOutput(ctx);
|
||||
// Should handle null return gracefully
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete even with null RAM getter");
|
||||
|
||||
// Test RAM getter returning empty string
|
||||
registry.registerRamData("%EMPTY%", getEmptyRamData);
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, single_placeholder_template);
|
||||
ctx.setRegistry(®istry);
|
||||
String result4 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with empty RAM data");
|
||||
|
||||
// Test incomplete placeholder (no closing %)
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, incomplete_placeholder_template);
|
||||
ctx.setRegistry(®istry);
|
||||
String result5 = captureRenderedOutput(ctx);
|
||||
// Should handle incomplete placeholder gracefully
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete even with incomplete placeholder");
|
||||
|
||||
Serial.println("[TEST] Error handling tests completed successfully");
|
||||
}
|
||||
|
||||
// Test boundary conditions
|
||||
void test_edge_cases_boundary_conditions() {
|
||||
Serial.println("[TEST] Testing boundary conditions...");
|
||||
|
||||
PlaceholderRegistry registry(10);
|
||||
registry.registerProgmemData("%PLACEHOLDER%", test_css_data);
|
||||
|
||||
TemplateContext ctx;
|
||||
ctx.setRegistry(®istry);
|
||||
|
||||
// Test placeholder at start
|
||||
// PLACEHOLDER is already registered above, so we can use it
|
||||
Serial.print("[DEBUG] Edge case - %PLACEHOLDER% registered: ");
|
||||
Serial.println(registry.getPlaceholder("%PLACEHOLDER%") != nullptr ? "YES" : "NO");
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, placeholder_start_template);
|
||||
Serial.print("[DEBUG] Edge case template: [");
|
||||
Serial.print(placeholder_start_template);
|
||||
Serial.println("]");
|
||||
String result1 = captureRenderedOutput(ctx);
|
||||
Serial.print("[DEBUG] Edge case result length: ");
|
||||
Serial.println(result1.length());
|
||||
Serial.print("[DEBUG] Edge case result: [");
|
||||
Serial.print(result1);
|
||||
Serial.println("]");
|
||||
Serial.print("[DEBUG] Edge case - Contains ' text': ");
|
||||
Serial.println(result1.indexOf(" text") >= 0 ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] Edge case - Contains 'body': ");
|
||||
Serial.println(result1.indexOf("body") >= 0 ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] Edge case - Final state: ");
|
||||
Serial.println(ctx.getStateString());
|
||||
RenderingContext* currentCtx = ctx.getCurrentContext();
|
||||
if (currentCtx && currentCtx->type == RenderingContextType::TEMPLATE) {
|
||||
Serial.print("[DEBUG] Edge case - Template length: ");
|
||||
Serial.println(currentCtx->context.templateCtx.templateLen);
|
||||
}
|
||||
// Should render placeholder and text
|
||||
TEST_ASSERT_TRUE_MESSAGE(result1.length() > 0, "Should handle placeholder at start");
|
||||
// Check for either the placeholder content or the text part
|
||||
TEST_ASSERT_TRUE_MESSAGE(result1.indexOf(" text") >= 0 || result1.indexOf("body") >= 0,
|
||||
"Should contain placeholder or text");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with placeholder at start");
|
||||
|
||||
// Test placeholder at end
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, placeholder_end_template);
|
||||
String result2 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_TRUE_MESSAGE(result2.length() > 0, "Should handle placeholder at end");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with placeholder at end");
|
||||
|
||||
// Test only placeholders (no text)
|
||||
registry.registerProgmemData("%A%", test_css_data);
|
||||
registry.registerProgmemData("%B%", test_js_data);
|
||||
registry.registerProgmemData("%C%", test_favicon_data);
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, only_placeholders_template);
|
||||
ctx.setRegistry(®istry);
|
||||
String result3 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_TRUE_MESSAGE(result3.length() > 0, "Should handle only placeholders");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with only placeholders");
|
||||
|
||||
// Test only text (no placeholders)
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, only_text_template);
|
||||
ctx.setRegistry(®istry);
|
||||
String result4 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_TRUE_MESSAGE(result4.length() > 0, "Should handle only text");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with only text");
|
||||
|
||||
// Test special characters
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, special_chars_template);
|
||||
ctx.setRegistry(®istry);
|
||||
String result5 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_TRUE_MESSAGE(result5.length() > 0, "Should handle special characters");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with special characters");
|
||||
|
||||
// Test newlines and placeholders
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, newlines_template);
|
||||
ctx.setRegistry(®istry);
|
||||
String result6 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_TRUE_MESSAGE(result6.length() > 0, "Should handle newlines");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with newlines");
|
||||
|
||||
// Test empty placeholder (%%)
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, empty_placeholder_template);
|
||||
ctx.setRegistry(®istry);
|
||||
String result7 = captureRenderedOutput(ctx);
|
||||
// Should handle empty placeholder gracefully
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with empty placeholder");
|
||||
|
||||
Serial.println("[TEST] Boundary condition tests completed successfully");
|
||||
}
|
||||
|
||||
// Test stress conditions
|
||||
void test_edge_cases_stress() {
|
||||
Serial.println("[TEST] Testing stress conditions...");
|
||||
|
||||
PlaceholderRegistry registry(50);
|
||||
|
||||
// Register many placeholders
|
||||
for (int i = 0; i < 20; i++) {
|
||||
char name[10];
|
||||
sprintf(name, "%%P%d%%", i);
|
||||
registry.registerProgmemData(name, test_css_data);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL_MESSAGE(20, registry.getCount(),
|
||||
"Should register many placeholders");
|
||||
|
||||
// Test template with many placeholders
|
||||
// Note: PROGMEM templates must be defined at file scope
|
||||
const char many_placeholders_template[] =
|
||||
"%P0%%P1%%P2%%P3%%P4%%P5%%P6%%P7%%P8%%P9%"
|
||||
"%P10%%P11%%P12%%P13%%P14%%P15%%P16%%P17%%P18%%P19%";
|
||||
|
||||
// All placeholders are already registered above
|
||||
Serial.print("[DEBUG] Stress test - Registry count: ");
|
||||
Serial.println(registry.getCount());
|
||||
Serial.print("[DEBUG] Stress test - %P0% registered: ");
|
||||
Serial.println(registry.getPlaceholder("%P0%") != nullptr ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] Stress test - %P19% registered: ");
|
||||
Serial.println(registry.getPlaceholder("%P19%") != nullptr ? "YES" : "NO");
|
||||
TemplateContext ctx;
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, many_placeholders_template);
|
||||
Serial.print("[DEBUG] Stress test template length: ");
|
||||
RenderingContext* currentCtx = ctx.getCurrentContext();
|
||||
if (currentCtx && currentCtx->type == RenderingContextType::TEMPLATE) {
|
||||
Serial.println(currentCtx->context.templateCtx.templateLen);
|
||||
} else {
|
||||
Serial.println(0);
|
||||
}
|
||||
String result = captureRenderedOutput(ctx);
|
||||
Serial.print("[DEBUG] Stress test result length: ");
|
||||
Serial.println(result.length());
|
||||
Serial.print("[DEBUG] Stress test result (first 200 chars): [");
|
||||
if (result.length() > 200) {
|
||||
Serial.print(result.substring(0, 200));
|
||||
Serial.println("...]");
|
||||
} else {
|
||||
Serial.print(result);
|
||||
Serial.println("]");
|
||||
}
|
||||
Serial.print("[DEBUG] Stress test - Contains 'body': ");
|
||||
Serial.println(result.indexOf("body") >= 0 ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] Stress test - Final state: ");
|
||||
Serial.println(ctx.getStateString());
|
||||
Serial.print("[DEBUG] Stress test - Is complete: ");
|
||||
Serial.println(TemplateRenderer::isComplete(ctx) ? "YES" : "NO");
|
||||
// All placeholders are registered with test_css_data, so result should contain "body" multiple times
|
||||
TEST_ASSERT_TRUE_MESSAGE(result.length() > 0, "Should render template with many placeholders");
|
||||
// Verify we got some content (all placeholders render the same CSS data)
|
||||
TEST_ASSERT_TRUE_MESSAGE(result.indexOf("body") >= 0 || result.length() > 20,
|
||||
"Should contain rendered placeholder data");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete with many placeholders");
|
||||
|
||||
// Test very long template
|
||||
// Register placeholder for long_text_template (if not already registered)
|
||||
if (registry.getPlaceholder("%PLACEHOLDER%") == nullptr) {
|
||||
registry.registerProgmemData("%PLACEHOLDER%", test_css_data);
|
||||
}
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, long_text_template);
|
||||
String result2 = captureRenderedOutput(ctx);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(result2.length() > 0, "Should render very long template");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete very long template");
|
||||
|
||||
// Test rendering with very small buffer repeatedly
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, long_text_template);
|
||||
|
||||
uint8_t tinyBuffer[1];
|
||||
int iterations = 0;
|
||||
while (!TemplateRenderer::isComplete(ctx) && iterations < 10000) {
|
||||
TemplateRenderer::renderNextChunk(ctx, tinyBuffer, sizeof(tinyBuffer));
|
||||
iterations++;
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete even with tiny buffer");
|
||||
TEST_ASSERT_LESS_THAN_MESSAGE(10000, iterations,
|
||||
"Should complete in reasonable iterations");
|
||||
|
||||
// Test stack depth near limit
|
||||
registry.clear();
|
||||
registry.registerProgmemTemplate("%L2%", deep_nest_level2);
|
||||
registry.registerProgmemTemplate("%L3%", deep_nest_level3);
|
||||
registry.registerProgmemTemplate("%L4%", deep_nest_level4);
|
||||
registry.registerProgmemTemplate("%L5%", deep_nest_level5);
|
||||
registry.registerProgmemTemplate("%L6%", deep_nest_level6);
|
||||
registry.registerProgmemTemplate("%L7%", deep_nest_level7);
|
||||
registry.registerProgmemTemplate("%L8%", deep_nest_level8);
|
||||
registry.registerProgmemTemplate("%L9%", deep_nest_level9);
|
||||
registry.registerProgmemTemplate("%L10%", deep_nest_level10);
|
||||
registry.registerProgmemTemplate("%L11%", deep_nest_level11);
|
||||
registry.registerProgmemTemplate("%L12%", deep_nest_level12);
|
||||
registry.registerProgmemTemplate("%L13%", deep_nest_level13);
|
||||
registry.registerProgmemTemplate("%L14%", deep_nest_level14);
|
||||
registry.registerProgmemTemplate("%L15%", deep_nest_level15);
|
||||
registry.registerProgmemTemplate("%L16%", deep_nest_level16);
|
||||
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, deep_nest_level1);
|
||||
String result3 = captureRenderedOutput(ctx);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(result3.length() > 0, "Should render deep nested template");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should complete deep nested template");
|
||||
TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(TemplateContext::MAX_RENDERING_DEPTH, ctx.renderingDepth,
|
||||
"Rendering depth should not exceed MAX_RENDERING_DEPTH");
|
||||
|
||||
Serial.println("[TEST] Stress condition tests completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "../templates/simple_templates.h"
|
||||
#include "../templates/placeholder_templates.h"
|
||||
#include "../templates/nested_templates.h"
|
||||
#include "../utils/test_utils.h"
|
||||
|
||||
// Test RAM data getters
|
||||
static String testTitle = "Test Title";
|
||||
static String testSubtitle = "Test Subtitle";
|
||||
static String testDesc = "Test Description";
|
||||
|
||||
static const char* getTestTitle() { return testTitle.c_str(); }
|
||||
static const char* getTestSubtitle() { return testSubtitle.c_str(); }
|
||||
static const char* getTestDesc() { return testDesc.c_str(); }
|
||||
|
||||
// Test full rendering scenarios
|
||||
void test_integration_full_rendering() {
|
||||
Serial.println("[TEST] Testing full rendering scenarios...");
|
||||
|
||||
PlaceholderRegistry registry(20);
|
||||
|
||||
// Register all placeholder types
|
||||
// Note: mixed_placeholders_template uses %STYLES%, %PAGE_TITLE%, %HEADER%, %FOOTER%
|
||||
registry.registerProgmemData("%STYLES%", test_css_data);
|
||||
registry.registerProgmemData("%JS%", test_js_data);
|
||||
registry.registerProgmemData("%FAVICON%", test_favicon_data);
|
||||
registry.registerProgmemTemplate("%HEADER%", test_header_template);
|
||||
registry.registerProgmemTemplate("%FOOTER%", test_footer_template);
|
||||
registry.registerRamData("%PAGE_TITLE%", getTestTitle);
|
||||
registry.registerRamData("%SUBTITLE%", getTestSubtitle);
|
||||
registry.registerRamData("%DESC%", getTestDesc);
|
||||
|
||||
TemplateContext ctx;
|
||||
ctx.setRegistry(®istry);
|
||||
Serial.print("[DEBUG] Registry count: ");
|
||||
Serial.println(registry.getCount());
|
||||
Serial.print("[DEBUG] %STYLES% registered: ");
|
||||
Serial.println(registry.getPlaceholder("%STYLES%") != nullptr ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] %PAGE_TITLE% registered: ");
|
||||
Serial.println(registry.getPlaceholder("%PAGE_TITLE%") != nullptr ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] %HEADER% registered: ");
|
||||
Serial.println(registry.getPlaceholder("%HEADER%") != nullptr ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] %FOOTER% registered: ");
|
||||
Serial.println(registry.getPlaceholder("%FOOTER%") != nullptr ? "YES" : "NO");
|
||||
TemplateRenderer::initializeContext(ctx, mixed_placeholders_template);
|
||||
String result = captureRenderedOutput(ctx);
|
||||
Serial.print("[DEBUG] Mixed result length: ");
|
||||
Serial.println(result.length());
|
||||
Serial.print("[DEBUG] Mixed result: [");
|
||||
Serial.print(result);
|
||||
Serial.println("]");
|
||||
Serial.print("[DEBUG] Contains 'Header:': ");
|
||||
Serial.println(result.indexOf("Header:") >= 0 ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] Contains 'header': ");
|
||||
Serial.println(result.indexOf("header") >= 0 ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] Contains 'body': ");
|
||||
Serial.println(result.indexOf("body") >= 0 ? "YES" : "NO");
|
||||
Serial.print("[DEBUG] Contains 'Title:': ");
|
||||
Serial.println(result.indexOf("Title:") >= 0 ? "YES" : "NO");
|
||||
TEST_ASSERT_TRUE_MESSAGE(result.length() > 0, "Should render template with all placeholder types");
|
||||
// Check for rendered content - PROGMEM data is "body { color: red; }"
|
||||
TEST_ASSERT_TRUE_MESSAGE(result.indexOf("Header:") >= 0 || result.indexOf("header") >= 0 || result.indexOf("body") >= 0 || result.indexOf("Title:") >= 0,
|
||||
"Should contain rendered content");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should be complete after rendering");
|
||||
|
||||
// Test real-world HTML-like template structure
|
||||
// Note: PROGMEM templates must be defined at file scope, not as local variables
|
||||
// Using a simple template string instead
|
||||
// Register placeholders for HTML template
|
||||
registry.registerRamData("%TITLE%", getTestTitle);
|
||||
registry.registerProgmemData("%CSS%", test_css_data);
|
||||
|
||||
const char html_template[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
" <title>%TITLE%</title>\n"
|
||||
" <style>%CSS%</style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
" %HEADER%\n"
|
||||
" <main>%DESC%</main>\n"
|
||||
" %FOOTER%\n"
|
||||
" <script>%JS%</script>\n"
|
||||
"</body>\n"
|
||||
"</html>";
|
||||
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, html_template);
|
||||
String htmlResult = captureRenderedOutput(ctx);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(htmlResult.length() > 0, "Should render HTML-like template");
|
||||
TEST_ASSERT_TRUE_MESSAGE(htmlResult.indexOf("<!DOCTYPE html>") >= 0,
|
||||
"Should contain HTML structure");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Should be complete after rendering HTML template");
|
||||
|
||||
Serial.println("[TEST] Full rendering scenario tests completed successfully");
|
||||
}
|
||||
|
||||
// Test memory efficiency
|
||||
void test_integration_memory_efficiency() {
|
||||
Serial.println("[TEST] Testing memory efficiency...");
|
||||
|
||||
PlaceholderRegistry registry(10);
|
||||
// Register placeholder for long_text_template
|
||||
registry.registerProgmemData("%PLACEHOLDER%", test_css_data);
|
||||
|
||||
TemplateContext ctx;
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, long_text_template);
|
||||
|
||||
size_t totalBytes = 0;
|
||||
uint8_t buffer[64];
|
||||
|
||||
while (!TemplateRenderer::isComplete(ctx)) {
|
||||
size_t written = TemplateRenderer::renderNextChunk(ctx, buffer, sizeof(buffer));
|
||||
totalBytes += written;
|
||||
|
||||
// Verify buffer size stays reasonable
|
||||
TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(sizeof(buffer), written,
|
||||
"Chunk size should not exceed buffer size");
|
||||
}
|
||||
|
||||
// Verify we processed all data
|
||||
TEST_ASSERT_GREATER_THAN_MESSAGE(0, totalBytes, "Should process some bytes");
|
||||
|
||||
// Test that stack depth stays within limits
|
||||
ctx.reset();
|
||||
TemplateRenderer::initializeContext(ctx, nested_one_level_outer);
|
||||
ctx.setRegistry(®istry);
|
||||
registry.registerProgmemTemplate("%INNER%", nested_one_level_inner);
|
||||
|
||||
while (!TemplateRenderer::isComplete(ctx)) {
|
||||
TemplateRenderer::renderNextChunk(ctx, buffer, sizeof(buffer));
|
||||
TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(TemplateContext::MAX_RENDERING_DEPTH, ctx.renderingDepth,
|
||||
"Rendering depth should not exceed MAX_RENDERING_DEPTH");
|
||||
}
|
||||
|
||||
Serial.println("[TEST] Memory efficiency tests completed successfully");
|
||||
}
|
||||
|
||||
// Test multiple templates sequentially
|
||||
void test_integration_multiple_templates() {
|
||||
Serial.println("[TEST] Testing multiple templates sequentially...");
|
||||
|
||||
PlaceholderRegistry registry(10);
|
||||
registry.registerProgmemData("%CSS%", test_css_data);
|
||||
registry.registerRamData("%TITLE%", getTestTitle);
|
||||
|
||||
TemplateContext ctx;
|
||||
ctx.setRegistry(®istry);
|
||||
|
||||
// Register placeholder for progmem_data_template
|
||||
registry.registerProgmemData("%STYLES%", test_css_data);
|
||||
// Render first template
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, progmem_data_template);
|
||||
String result1 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_TRUE_MESSAGE(result1.length() > 0, "Should render first template");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"First template should be complete");
|
||||
|
||||
// Register placeholder for ram_data_template
|
||||
registry.registerRamData("%PAGE_TITLE%", getTestTitle);
|
||||
// Reset and render second template
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, ram_data_template);
|
||||
String result2 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_TRUE_MESSAGE(result2.length() > 0, "Should render second template");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Second template should be complete");
|
||||
|
||||
// Reset and render third template
|
||||
ctx.reset();
|
||||
ctx.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(ctx, plain_text_template);
|
||||
String result3 = captureRenderedOutput(ctx);
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("Hello, World!", result3.c_str(),
|
||||
"Should render third template");
|
||||
TEST_ASSERT_TRUE_MESSAGE(TemplateRenderer::isComplete(ctx),
|
||||
"Third template should be complete");
|
||||
|
||||
// Verify results are different
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(result1.c_str(), result2.c_str(),
|
||||
"Results should be different for different templates");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(result2.c_str(), result3.c_str(),
|
||||
"Results should be different for different templates");
|
||||
|
||||
Serial.println("[TEST] Multiple templates sequential tests completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "../templates/placeholder_templates.h"
|
||||
#include "../utils/test_utils.h"
|
||||
#include <pgmspace.h>
|
||||
|
||||
// Note: PROGMEM data is defined in placeholder_templates.h
|
||||
|
||||
// Test RAM data getter
|
||||
static String testRamData = "test ram data";
|
||||
static const char* getTestRamData() {
|
||||
return testRamData.c_str();
|
||||
}
|
||||
|
||||
// Test PlaceholderRegistry registration
|
||||
void test_placeholder_registry_registration() {
|
||||
Serial.println("[TEST] Testing PlaceholderRegistry registration...");
|
||||
|
||||
PlaceholderRegistry registry(10);
|
||||
|
||||
// Test initial state
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, registry.getCount(), "Registry should start empty");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(10, registry.getMaxPlaceholders(), "Registry should have correct max");
|
||||
|
||||
// Test register PROGMEM_DATA
|
||||
TEST_ASSERT_TRUE_MESSAGE(registry.registerProgmemData("%TEST%", test_css_data),
|
||||
"Should register PROGMEM_DATA placeholder");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(1, registry.getCount(), "Registry count should be 1");
|
||||
|
||||
// Test register PROGMEM_TEMPLATE
|
||||
TEST_ASSERT_TRUE_MESSAGE(registry.registerProgmemTemplate("%HEADER%", test_header_template),
|
||||
"Should register PROGMEM_TEMPLATE placeholder");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(2, registry.getCount(), "Registry count should be 2");
|
||||
|
||||
// Test register RAM_DATA
|
||||
TEST_ASSERT_TRUE_MESSAGE(registry.registerRamData("%TITLE%", getTestRamData),
|
||||
"Should register RAM_DATA placeholder");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(3, registry.getCount(), "Registry count should be 3");
|
||||
|
||||
// Test register multiple placeholders
|
||||
TEST_ASSERT_TRUE_MESSAGE(registry.registerProgmemData("%CSS%", test_css_data),
|
||||
"Should register multiple PROGMEM_DATA placeholders");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registry.registerProgmemData("%JS%", test_js_data),
|
||||
"Should register multiple PROGMEM_DATA placeholders");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(5, registry.getCount(), "Registry count should be 5");
|
||||
|
||||
// Test register duplicate (should warn but succeed)
|
||||
TEST_ASSERT_TRUE_MESSAGE(registry.registerProgmemData("%TEST%", test_js_data),
|
||||
"Should allow duplicate registration (last wins)");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(6, registry.getCount(), "Registry count should be 6");
|
||||
|
||||
// Test register with invalid name (null)
|
||||
TEST_ASSERT_FALSE_MESSAGE(registry.registerProgmemData(nullptr, test_css_data),
|
||||
"Should reject null placeholder name");
|
||||
|
||||
// Test register with invalid name (empty)
|
||||
// Note: Empty string "" might be accepted by validation (length 0 is valid)
|
||||
// The validation checks for null and length >= MAX_PLACEHOLDER_NAME_SIZE
|
||||
// Empty string has length 0, which is < MAX_PLACEHOLDER_NAME_SIZE, so it might pass
|
||||
// This test verifies the behavior - if empty strings are accepted, that's fine
|
||||
const char* emptyStr = "";
|
||||
bool emptyResult = registry.registerProgmemData(emptyStr, test_css_data);
|
||||
// Empty string might be accepted (length 0 < MAX_PLACEHOLDER_NAME_SIZE)
|
||||
// We just verify the function doesn't crash
|
||||
(void)emptyResult; // Suppress unused variable warning
|
||||
|
||||
// Test clear
|
||||
registry.clear();
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, registry.getCount(), "Registry should be empty after clear");
|
||||
|
||||
// Test register when full
|
||||
PlaceholderRegistry smallRegistry(2);
|
||||
TEST_ASSERT_TRUE_MESSAGE(smallRegistry.registerProgmemData("%A%", test_css_data),
|
||||
"Should register first placeholder");
|
||||
TEST_ASSERT_TRUE_MESSAGE(smallRegistry.registerProgmemData("%B%", test_js_data),
|
||||
"Should register second placeholder");
|
||||
TEST_ASSERT_FALSE_MESSAGE(smallRegistry.registerProgmemData("%C%", test_css_data),
|
||||
"Should reject registration when registry is full");
|
||||
|
||||
Serial.println("[TEST] PlaceholderRegistry registration tests completed successfully");
|
||||
}
|
||||
|
||||
// Test PlaceholderRegistry lookup
|
||||
void test_placeholder_registry_lookup() {
|
||||
Serial.println("[TEST] Testing PlaceholderRegistry lookup...");
|
||||
|
||||
PlaceholderRegistry registry(10);
|
||||
|
||||
// Register some placeholders
|
||||
registry.registerProgmemData("%CSS%", test_css_data);
|
||||
registry.registerProgmemTemplate("%HEADER%", test_header_template);
|
||||
registry.registerRamData("%TITLE%", getTestRamData);
|
||||
|
||||
// Test get existing placeholder
|
||||
const PlaceholderEntry* entry1 = registry.getPlaceholder("%CSS%");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(entry1, "Should find existing PROGMEM_DATA placeholder");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(PlaceholderType::PROGMEM_DATA, entry1->type,
|
||||
"Placeholder type should be PROGMEM_DATA");
|
||||
|
||||
const PlaceholderEntry* entry2 = registry.getPlaceholder("%HEADER%");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(entry2, "Should find existing PROGMEM_TEMPLATE placeholder");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(PlaceholderType::PROGMEM_TEMPLATE, entry2->type,
|
||||
"Placeholder type should be PROGMEM_TEMPLATE");
|
||||
|
||||
const PlaceholderEntry* entry3 = registry.getPlaceholder("%TITLE%");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(entry3, "Should find existing RAM_DATA placeholder");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(PlaceholderType::RAM_DATA, entry3->type,
|
||||
"Placeholder type should be RAM_DATA");
|
||||
|
||||
// Test get non-existent placeholder
|
||||
const PlaceholderEntry* entry4 = registry.getPlaceholder("%NOTFOUND%");
|
||||
TEST_ASSERT_NULL_MESSAGE(entry4, "Should return nullptr for non-existent placeholder");
|
||||
|
||||
// Test case-sensitive matching
|
||||
const PlaceholderEntry* entry5 = registry.getPlaceholder("%css%");
|
||||
TEST_ASSERT_NULL_MESSAGE(entry5, "Should be case-sensitive (lowercase not found)");
|
||||
|
||||
// Test get with null name
|
||||
const PlaceholderEntry* entry6 = registry.getPlaceholder(nullptr);
|
||||
TEST_ASSERT_NULL_MESSAGE(entry6, "Should return nullptr for null name");
|
||||
|
||||
Serial.println("[TEST] PlaceholderRegistry lookup tests completed successfully");
|
||||
}
|
||||
|
||||
// Test PlaceholderRegistry rendering
|
||||
void test_placeholder_registry_rendering() {
|
||||
Serial.println("[TEST] Testing PlaceholderRegistry rendering...");
|
||||
|
||||
PlaceholderRegistry registry(10);
|
||||
|
||||
// Register PROGMEM_DATA placeholder
|
||||
registry.registerProgmemData("%CSS%", test_css_data);
|
||||
const PlaceholderEntry* entry1 = registry.getPlaceholder("%CSS%");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(entry1, "Should find PROGMEM_DATA placeholder");
|
||||
|
||||
// Test render PROGMEM_DATA (full)
|
||||
uint8_t buffer1[256];
|
||||
size_t len1 = registry.renderPlaceholder(entry1, 0, buffer1, sizeof(buffer1));
|
||||
char* temp1 = new char[len1 + 1];
|
||||
memcpy(temp1, buffer1, len1);
|
||||
temp1[len1] = '\0';
|
||||
String result1 = String(temp1);
|
||||
delete[] temp1;
|
||||
|
||||
// Compare with PROGMEM data using strlen_P
|
||||
size_t expectedLen = strlen_P(test_css_data);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(expectedLen, len1,
|
||||
"Should render full PROGMEM_DATA length");
|
||||
// Compare first few characters to verify content
|
||||
char firstChar = pgm_read_byte(test_css_data);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(firstChar, result1.charAt(0),
|
||||
"Should render correct PROGMEM_DATA content");
|
||||
|
||||
// Test render PROGMEM_DATA (chunked)
|
||||
uint8_t buffer2[10];
|
||||
size_t len2 = registry.renderPlaceholder(entry1, 0, buffer2, sizeof(buffer2));
|
||||
TEST_ASSERT_EQUAL_MESSAGE(10, len2, "Should render chunk of PROGMEM_DATA");
|
||||
|
||||
// Test render PROGMEM_DATA with offset
|
||||
size_t len3 = registry.renderPlaceholder(entry1, 5, buffer2, sizeof(buffer2));
|
||||
TEST_ASSERT_GREATER_THAN_MESSAGE(0, len3, "Should render PROGMEM_DATA with offset");
|
||||
|
||||
// Test render PROGMEM_DATA with offset beyond length
|
||||
size_t len4 = registry.renderPlaceholder(entry1, 1000, buffer2, sizeof(buffer2));
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, len4, "Should return 0 when offset beyond length");
|
||||
|
||||
// Register RAM_DATA placeholder
|
||||
registry.registerRamData("%TITLE%", getTestRamData);
|
||||
const PlaceholderEntry* entry2 = registry.getPlaceholder("%TITLE%");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(entry2, "Should find RAM_DATA placeholder");
|
||||
|
||||
// Test render RAM_DATA (full)
|
||||
uint8_t buffer3[256];
|
||||
size_t len5 = registry.renderPlaceholder(entry2, 0, buffer3, sizeof(buffer3));
|
||||
char* temp2 = new char[len5 + 1];
|
||||
memcpy(temp2, buffer3, len5);
|
||||
temp2[len5] = '\0';
|
||||
String result2 = String(temp2);
|
||||
delete[] temp2;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(testRamData.c_str(), result2.c_str(),
|
||||
"Should render full RAM_DATA");
|
||||
|
||||
// Test render RAM_DATA (chunked)
|
||||
uint8_t buffer4[5];
|
||||
size_t len6 = registry.renderPlaceholder(entry2, 0, buffer4, sizeof(buffer4));
|
||||
TEST_ASSERT_EQUAL_MESSAGE(5, len6, "Should render chunk of RAM_DATA");
|
||||
|
||||
// Test render with null entry
|
||||
size_t len7 = registry.renderPlaceholder(nullptr, 0, buffer1, sizeof(buffer1));
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, len7, "Should return 0 for null entry");
|
||||
|
||||
// Test render with maxLen = 0
|
||||
size_t len8 = registry.renderPlaceholder(entry1, 0, buffer1, 0);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, len8, "Should return 0 when maxLen is 0");
|
||||
|
||||
Serial.println("[TEST] PlaceholderRegistry rendering tests completed successfully");
|
||||
}
|
||||
|
||||
// Test PlaceholderRegistry edge cases
|
||||
void test_placeholder_registry_edge_cases() {
|
||||
Serial.println("[TEST] Testing PlaceholderRegistry edge cases...");
|
||||
|
||||
// Test registry with maxPlaceholders = 1
|
||||
PlaceholderRegistry smallRegistry(1);
|
||||
TEST_ASSERT_TRUE_MESSAGE(smallRegistry.registerProgmemData("%A%", test_css_data),
|
||||
"Should register placeholder in small registry");
|
||||
TEST_ASSERT_FALSE_MESSAGE(smallRegistry.registerProgmemData("%B%", test_js_data),
|
||||
"Should reject second placeholder in small registry");
|
||||
|
||||
// Test placeholder name at maximum length
|
||||
char maxName[25];
|
||||
strncpy(maxName, "%", sizeof(maxName) - 1);
|
||||
maxName[0] = '%';
|
||||
for (int i = 1; i < 22; i++) {
|
||||
maxName[i] = 'A';
|
||||
}
|
||||
maxName[22] = '%';
|
||||
maxName[23] = '\0';
|
||||
|
||||
PlaceholderRegistry registry(10);
|
||||
TEST_ASSERT_TRUE_MESSAGE(registry.registerProgmemData(maxName, test_css_data),
|
||||
"Should register placeholder with max length name");
|
||||
|
||||
// Test placeholder name over maximum length
|
||||
char overName[30];
|
||||
overName[0] = '%';
|
||||
for (int i = 1; i < 27; i++) {
|
||||
overName[i] = 'A';
|
||||
}
|
||||
overName[27] = '%';
|
||||
overName[28] = '\0';
|
||||
|
||||
TEST_ASSERT_FALSE_MESSAGE(registry.registerProgmemData(overName, test_css_data),
|
||||
"Should reject placeholder with over max length name");
|
||||
|
||||
// Test length calculation
|
||||
size_t progmemLen = PlaceholderRegistry::getProgmemLength(test_css_data);
|
||||
TEST_ASSERT_GREATER_THAN_MESSAGE(0, progmemLen, "Should calculate PROGMEM length");
|
||||
|
||||
// Test length calculation with null
|
||||
size_t nullLen = PlaceholderRegistry::getProgmemLength(nullptr);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, nullLen, "Should return 0 for null PROGMEM data");
|
||||
|
||||
// Test RAM length calculation
|
||||
size_t ramLen = PlaceholderRegistry::getRamLength((const void*)getTestRamData);
|
||||
TEST_ASSERT_GREATER_THAN_MESSAGE(0, ramLen, "Should calculate RAM length");
|
||||
|
||||
// Test RAM length calculation with null
|
||||
size_t nullRamLen = PlaceholderRegistry::getRamLength(nullptr);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, nullRamLen, "Should return 0 for null RAM getter");
|
||||
|
||||
Serial.println("[TEST] PlaceholderRegistry edge case tests completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "../templates/simple_templates.h"
|
||||
#include "../utils/test_utils.h"
|
||||
|
||||
// Test TemplateContext initialization
|
||||
void test_template_context_initialization() {
|
||||
Serial.println("[TEST] Testing TemplateContext initialization...");
|
||||
|
||||
TemplateContext ctx;
|
||||
|
||||
// Test default constructor
|
||||
TEST_ASSERT_EQUAL_MESSAGE(TemplateRenderState::TEXT, ctx.state,
|
||||
"Initial state should be TEXT");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.renderingDepth, "Initial renderingDepth should be 0");
|
||||
TEST_ASSERT_NULL_MESSAGE(ctx.getCurrentContext(), "Initial context should be null");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.placeholderPos, "Initial placeholderPos should be 0");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.bufferPos, "Initial bufferPos should be 0");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.bufferLen, "Initial bufferLen should be 0");
|
||||
TEST_ASSERT_NULL_MESSAGE(ctx.registry, "Initial registry should be null");
|
||||
|
||||
// Test reset
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "TEST");
|
||||
ctx.renderingDepth = 5;
|
||||
ctx.reset();
|
||||
|
||||
TEST_ASSERT_EQUAL_MESSAGE(TemplateRenderState::TEXT, ctx.state,
|
||||
"Reset state should be TEXT");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.renderingDepth, "Reset renderingDepth should be 0");
|
||||
TEST_ASSERT_NULL_MESSAGE(ctx.getCurrentContext(), "Reset context should be null");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.placeholderPos, "Reset placeholderPos should be 0");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.bufferPos, "Reset bufferPos should be 0");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.bufferLen, "Reset bufferLen should be 0");
|
||||
|
||||
// Test isComplete
|
||||
TEST_ASSERT_FALSE_MESSAGE(ctx.isComplete(), "Should not be complete initially");
|
||||
ctx.state = TemplateRenderState::COMPLETE;
|
||||
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "Should be complete when state is COMPLETE");
|
||||
ctx.state = TemplateRenderState::ERROR;
|
||||
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "Should be complete when state is ERROR");
|
||||
|
||||
// Test hasError
|
||||
ctx.state = TemplateRenderState::TEXT;
|
||||
TEST_ASSERT_FALSE_MESSAGE(ctx.hasError(), "Should not have error initially");
|
||||
ctx.state = TemplateRenderState::ERROR;
|
||||
TEST_ASSERT_TRUE_MESSAGE(ctx.hasError(), "Should have error when state is ERROR");
|
||||
|
||||
// Test getStateString
|
||||
ctx.state = TemplateRenderState::TEXT;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("TEXT", ctx.getStateString().c_str(),
|
||||
"Should return correct state string for TEXT");
|
||||
ctx.state = TemplateRenderState::RENDERING_CONTEXT;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("RENDERING_CONTEXT", ctx.getStateString().c_str(),
|
||||
"Should return correct state string for RENDERING_CONTEXT");
|
||||
ctx.state = TemplateRenderState::COMPLETE;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("COMPLETE", ctx.getStateString().c_str(),
|
||||
"Should return correct state string for COMPLETE");
|
||||
ctx.state = TemplateRenderState::ERROR;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("ERROR", ctx.getStateString().c_str(),
|
||||
"Should return correct state string for ERROR");
|
||||
|
||||
// Test setRegistry
|
||||
PlaceholderRegistry registry(10);
|
||||
ctx.setRegistry(®istry);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(®istry, ctx.registry, "Should set registry correctly");
|
||||
|
||||
Serial.println("[TEST] TemplateContext initialization tests completed successfully");
|
||||
}
|
||||
|
||||
// Test TemplateContext stack
|
||||
void test_template_context_stack() {
|
||||
Serial.println("[TEST] Testing TemplateContext stack...");
|
||||
|
||||
TemplateContext ctx;
|
||||
const char* template1 = plain_text_template;
|
||||
const char* template2 = single_placeholder_template;
|
||||
const char* template3 = multiple_placeholders_template;
|
||||
|
||||
size_t len1 = strlen_P(template1);
|
||||
size_t len2 = strlen_P(template2);
|
||||
size_t len3 = strlen_P(template3);
|
||||
|
||||
// Test push context
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "%TEMPLATE1%");
|
||||
RenderingContext* ctx1 = ctx.getCurrentContext();
|
||||
ctx1->context.templateCtx.templateData = template1;
|
||||
ctx1->context.templateCtx.templateLen = len1;
|
||||
ctx1->context.templateCtx.isProgmem = true;
|
||||
ctx1->context.templateCtx.position = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL_MESSAGE(1, ctx.renderingDepth, "Stack depth should be 1 after push");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(ctx.getCurrentContext(), "Current context should not be null");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(template1, ctx.getCurrentContext()->context.templateCtx.templateData,
|
||||
"Current template should be pushed template");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(len1, ctx.getCurrentContext()->context.templateCtx.templateLen, "Template length should be set");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.getCurrentContext()->context.templateCtx.position, "Template position should be reset");
|
||||
|
||||
// Test push multiple templates
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "%TEMPLATE2%");
|
||||
RenderingContext* ctx2 = ctx.getCurrentContext();
|
||||
ctx2->context.templateCtx.templateData = template2;
|
||||
ctx2->context.templateCtx.templateLen = len2;
|
||||
ctx2->context.templateCtx.isProgmem = true;
|
||||
ctx2->context.templateCtx.position = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL_MESSAGE(2, ctx.renderingDepth, "Stack depth should be 2 after second push");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(template2, ctx.getCurrentContext()->context.templateCtx.templateData,
|
||||
"Current template should be second pushed template");
|
||||
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "%TEMPLATE3%");
|
||||
RenderingContext* ctx3 = ctx.getCurrentContext();
|
||||
ctx3->context.templateCtx.templateData = template3;
|
||||
ctx3->context.templateCtx.templateLen = len3;
|
||||
ctx3->context.templateCtx.isProgmem = true;
|
||||
ctx3->context.templateCtx.position = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL_MESSAGE(3, ctx.renderingDepth, "Stack depth should be 3 after third push");
|
||||
|
||||
// Test pop context
|
||||
ctx.popContext();
|
||||
TEST_ASSERT_EQUAL_MESSAGE(2, ctx.renderingDepth, "Stack depth should be 2 after pop");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(template2, ctx.getCurrentContext()->context.templateCtx.templateData,
|
||||
"Current template should be previous template");
|
||||
|
||||
ctx.popContext();
|
||||
TEST_ASSERT_EQUAL_MESSAGE(1, ctx.renderingDepth, "Stack depth should be 1 after second pop");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(template1, ctx.getCurrentContext()->context.templateCtx.templateData,
|
||||
"Current template should be first template");
|
||||
|
||||
ctx.popContext();
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.renderingDepth, "Stack depth should be 0 after third pop");
|
||||
|
||||
// Test stack overflow
|
||||
// Push up to MAX_RENDERING_DEPTH
|
||||
for (int i = 0; i < TemplateContext::MAX_RENDERING_DEPTH; i++) {
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "%TEMPLATE%");
|
||||
RenderingContext* c = ctx.getCurrentContext();
|
||||
c->context.templateCtx.templateData = template1;
|
||||
c->context.templateCtx.templateLen = len1;
|
||||
c->context.templateCtx.isProgmem = true;
|
||||
c->context.templateCtx.position = 0;
|
||||
}
|
||||
TEST_ASSERT_EQUAL_MESSAGE(TemplateContext::MAX_RENDERING_DEPTH, ctx.renderingDepth,
|
||||
"Stack depth should be at MAX_RENDERING_DEPTH");
|
||||
|
||||
// Try to push one more (should error)
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "%OVERFLOW%");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(TemplateRenderState::ERROR, ctx.state,
|
||||
"State should be ERROR after stack overflow");
|
||||
|
||||
// Test stack underflow
|
||||
ctx.reset();
|
||||
ctx.popContext();
|
||||
TEST_ASSERT_EQUAL_MESSAGE(TemplateRenderState::ERROR, ctx.state,
|
||||
"State should be ERROR after stack underflow");
|
||||
|
||||
// Test getStackTrace
|
||||
ctx.reset();
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "%TEMPLATE1%");
|
||||
ctx.getCurrentContext()->context.templateCtx.templateData = template1;
|
||||
ctx.getCurrentContext()->context.templateCtx.templateLen = len1;
|
||||
ctx.getCurrentContext()->context.templateCtx.isProgmem = true;
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "%TEMPLATE2%");
|
||||
ctx.getCurrentContext()->context.templateCtx.templateData = template2;
|
||||
ctx.getCurrentContext()->context.templateCtx.templateLen = len2;
|
||||
ctx.getCurrentContext()->context.templateCtx.isProgmem = true;
|
||||
String trace = ctx.getStackTrace();
|
||||
TEST_ASSERT_TRUE_MESSAGE(trace.length() > 0, "Stack trace should not be empty");
|
||||
TEST_ASSERT_TRUE_MESSAGE(trace.indexOf("TEMPLATE1") >= 0,
|
||||
"Stack trace should contain template1");
|
||||
TEST_ASSERT_TRUE_MESSAGE(trace.indexOf("TEMPLATE2") >= 0,
|
||||
"Stack trace should contain template2");
|
||||
|
||||
Serial.println("[TEST] TemplateContext stack tests completed successfully");
|
||||
}
|
||||
|
||||
// Test TemplateContext buffer management
|
||||
void test_template_context_buffer() {
|
||||
Serial.println("[TEST] Testing TemplateContext buffer management...");
|
||||
|
||||
TemplateContext ctx;
|
||||
const char* templateData = plain_text_template;
|
||||
size_t templateLen = strlen_P(templateData);
|
||||
|
||||
// Push a template context first
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "TEST");
|
||||
RenderingContext* currentCtx = ctx.getCurrentContext();
|
||||
currentCtx->context.templateCtx.templateData = templateData;
|
||||
currentCtx->context.templateCtx.templateLen = templateLen;
|
||||
currentCtx->context.templateCtx.isProgmem = true;
|
||||
currentCtx->context.templateCtx.position = 0;
|
||||
|
||||
// Test refillBuffer
|
||||
bool refilled = ctx.refillBuffer();
|
||||
TEST_ASSERT_TRUE_MESSAGE(refilled, "Should refill buffer successfully");
|
||||
TEST_ASSERT_GREATER_THAN_MESSAGE(0, ctx.bufferLen, "Buffer length should be greater than 0");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.bufferPos, "Buffer position should be 0 after refill");
|
||||
|
||||
// Test getNextChar
|
||||
char c1 = ctx.getNextChar();
|
||||
TEST_ASSERT_EQUAL_MESSAGE('H', c1, "Should get first character");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(1, ctx.bufferPos, "Buffer position should be 1");
|
||||
|
||||
// Test getNextChar multiple times
|
||||
char c2 = ctx.getNextChar();
|
||||
char c3 = ctx.getNextChar();
|
||||
TEST_ASSERT_EQUAL_MESSAGE('e', c2, "Should get second character");
|
||||
TEST_ASSERT_EQUAL_MESSAGE('l', c3, "Should get third character");
|
||||
|
||||
// Test getAvailableBytes
|
||||
size_t available = ctx.getAvailableBytes();
|
||||
TEST_ASSERT_GREATER_THAN_MESSAGE(0, available, "Should have available bytes");
|
||||
|
||||
// Test hasMoreData
|
||||
TEST_ASSERT_TRUE_MESSAGE(ctx.hasMoreData(), "Should have more data initially");
|
||||
|
||||
// Test getNextChar until end
|
||||
ctx.reset();
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "TEST");
|
||||
currentCtx = ctx.getCurrentContext();
|
||||
currentCtx->context.templateCtx.templateData = templateData;
|
||||
currentCtx->context.templateCtx.templateLen = templateLen;
|
||||
currentCtx->context.templateCtx.isProgmem = true;
|
||||
currentCtx->context.templateCtx.position = 0;
|
||||
|
||||
int charCount = 0;
|
||||
while (ctx.hasMoreData()) {
|
||||
char c = ctx.getNextChar();
|
||||
if (c == '\0') {
|
||||
break;
|
||||
}
|
||||
charCount++;
|
||||
}
|
||||
TEST_ASSERT_GREATER_THAN_MESSAGE(0, charCount, "Should read all characters");
|
||||
|
||||
// Test refillBuffer when at end
|
||||
ctx.reset();
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "TEST");
|
||||
currentCtx = ctx.getCurrentContext();
|
||||
currentCtx->context.templateCtx.templateData = templateData;
|
||||
currentCtx->context.templateCtx.templateLen = templateLen;
|
||||
currentCtx->context.templateCtx.isProgmem = true;
|
||||
currentCtx->context.templateCtx.position = templateLen;
|
||||
|
||||
bool refilled2 = ctx.refillBuffer();
|
||||
TEST_ASSERT_FALSE_MESSAGE(refilled2, "Should not refill when at end");
|
||||
|
||||
// Test getNextChar when at end
|
||||
ctx.reset();
|
||||
ctx.pushContext(RenderingContextType::TEMPLATE, "TEST");
|
||||
currentCtx = ctx.getCurrentContext();
|
||||
currentCtx->context.templateCtx.templateData = templateData;
|
||||
currentCtx->context.templateCtx.templateLen = templateLen;
|
||||
currentCtx->context.templateCtx.position = templateLen;
|
||||
|
||||
char c4 = ctx.getNextChar();
|
||||
TEST_ASSERT_EQUAL_MESSAGE('\0', c4, "Should return null character at end");
|
||||
|
||||
// Test resetPlaceholder
|
||||
ctx.placeholderPos = 10;
|
||||
strcpy(ctx.placeholderName, "%TEST%");
|
||||
ctx.resetPlaceholder();
|
||||
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, ctx.placeholderPos, "Placeholder position should be reset");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, strlen(ctx.placeholderName),
|
||||
"Placeholder name should be cleared");
|
||||
|
||||
Serial.println("[TEST] TemplateContext buffer tests completed successfully");
|
||||
}
|
||||
|
||||
// Test TemplateContext state management
|
||||
void test_template_context_state() {
|
||||
Serial.println("[TEST] Testing TemplateContext state management...");
|
||||
|
||||
TemplateContext ctx;
|
||||
|
||||
// Test all state strings
|
||||
ctx.state = TemplateRenderState::TEXT;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("TEXT", ctx.getStateString().c_str(),
|
||||
"Should return TEXT for TEXT state");
|
||||
|
||||
ctx.state = TemplateRenderState::BUILDING_PLACEHOLDER;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("BUILDING_PLACEHOLDER", ctx.getStateString().c_str(),
|
||||
"Should return BUILDING_PLACEHOLDER for BUILDING_PLACEHOLDER state");
|
||||
|
||||
ctx.state = TemplateRenderState::RENDERING_CONTEXT;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("RENDERING_CONTEXT", ctx.getStateString().c_str(),
|
||||
"Should return RENDERING_CONTEXT for RENDERING_CONTEXT state");
|
||||
|
||||
ctx.state = TemplateRenderState::COMPLETE;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("COMPLETE", ctx.getStateString().c_str(),
|
||||
"Should return COMPLETE for COMPLETE state");
|
||||
|
||||
ctx.state = TemplateRenderState::ERROR;
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE("ERROR", ctx.getStateString().c_str(),
|
||||
"Should return ERROR for ERROR state");
|
||||
|
||||
// Test isComplete for all states
|
||||
ctx.state = TemplateRenderState::TEXT;
|
||||
TEST_ASSERT_FALSE_MESSAGE(ctx.isComplete(), "TEXT should not be complete");
|
||||
|
||||
ctx.state = TemplateRenderState::BUILDING_PLACEHOLDER;
|
||||
TEST_ASSERT_FALSE_MESSAGE(ctx.isComplete(), "BUILDING_PLACEHOLDER should not be complete");
|
||||
|
||||
ctx.state = TemplateRenderState::RENDERING_CONTEXT;
|
||||
TEST_ASSERT_FALSE_MESSAGE(ctx.isComplete(), "RENDERING_CONTEXT should not be complete");
|
||||
|
||||
ctx.state = TemplateRenderState::COMPLETE;
|
||||
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "COMPLETE should be complete");
|
||||
|
||||
ctx.state = TemplateRenderState::ERROR;
|
||||
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "ERROR should be complete");
|
||||
|
||||
// Test hasError for all states
|
||||
ctx.state = TemplateRenderState::TEXT;
|
||||
TEST_ASSERT_FALSE_MESSAGE(ctx.hasError(), "TEXT should not have error");
|
||||
|
||||
ctx.state = TemplateRenderState::ERROR;
|
||||
TEST_ASSERT_TRUE_MESSAGE(ctx.hasError(), "ERROR should have error");
|
||||
|
||||
Serial.println("[TEST] TemplateContext state tests completed successfully");
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user