#include "unity/unity.h"
#include
#include
#include
#include
#include
#include
/* Wrapper provided in the source module for the static function */
extern htmlNodePtr test_htmlFindFirstChild(htmlNodePtr parent, const char *name);
void setUp(void) {
/* Optional: initialize libxml2 if needed */
/* xmlInitParser(); */
}
void tearDown(void) {
/* Optional: cleanup between tests if needed */
/* Do not call xmlCleanupParser() here; call once at program end if used */
}
/* Helper to create a fresh document with a root "parent" node */
static void create_doc_with_parent(xmlDocPtr *outDoc, htmlNodePtr *outParent) {
xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
TEST_ASSERT_NOT_NULL(doc);
htmlNodePtr parent = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "parent");
TEST_ASSERT_NOT_NULL(parent);
xmlDocSetRootElement(doc, (xmlNodePtr)parent);
*outDoc = doc;
*outParent = parent;
}
/* Test: No children -> should return NULL */
void test_htmlFindFirstChild_no_children_returns_NULL(void) {
xmlDocPtr doc = NULL;
htmlNodePtr parent = NULL;
create_doc_with_parent(&doc, &parent);
htmlNodePtr res = test_htmlFindFirstChild(parent, "div");
TEST_ASSERT_NULL(res);
xmlFreeDoc(doc);
}
/* Test: Case-insensitive match returns the first matching child */
void test_htmlFindFirstChild_case_insensitive_first_match(void) {
xmlDocPtr doc = NULL;
htmlNodePtr parent = NULL;
create_doc_with_parent(&doc, &parent);
/* Create two element children named "div" */
htmlNodePtr firstDiv = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "div");
TEST_ASSERT_NOT_NULL(firstDiv);
xmlAddChild((xmlNodePtr)parent, (xmlNodePtr)firstDiv);
htmlNodePtr secondDiv = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "DiV");
TEST_ASSERT_NOT_NULL(secondDiv);
xmlAddChild((xmlNodePtr)parent, (xmlNodePtr)secondDiv);
/* Mixed-case search string */
htmlNodePtr res = test_htmlFindFirstChild(parent, "dIv");
TEST_ASSERT_NOT_NULL(res);
TEST_ASSERT_EQUAL_PTR(firstDiv, res);
xmlFreeDoc(doc);
}
/* Test: Skips non-element nodes (text and comments) before the first matching element */
void test_htmlFindFirstChild_skips_non_element_nodes(void) {
xmlDocPtr doc = NULL;
htmlNodePtr parent = NULL;
create_doc_with_parent(&doc, &parent);
xmlNodePtr text = xmlNewText(BAD_CAST "text node");
TEST_ASSERT_NOT_NULL(text);
xmlAddChild((xmlNodePtr)parent, text);
xmlNodePtr comment = xmlNewComment(BAD_CAST "a comment");
TEST_ASSERT_NOT_NULL(comment);
xmlAddChild((xmlNodePtr)parent, comment);
htmlNodePtr target = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "span");
TEST_ASSERT_NOT_NULL(target);
xmlAddChild((xmlNodePtr)parent, (xmlNodePtr)target);
htmlNodePtr res = test_htmlFindFirstChild(parent, "SPAN"); /* uppercase query */
TEST_ASSERT_NOT_NULL(res);
TEST_ASSERT_EQUAL_PTR(target, res);
xmlFreeDoc(doc);
}
/* Test: No matching child -> should return NULL */
void test_htmlFindFirstChild_no_matching_child_returns_NULL(void) {
xmlDocPtr doc = NULL;
htmlNodePtr parent = NULL;
create_doc_with_parent(&doc, &parent);
htmlNodePtr a = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "div");
TEST_ASSERT_NOT_NULL(a);
xmlAddChild((xmlNodePtr)parent, (xmlNodePtr)a);
htmlNodePtr b = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "p");
TEST_ASSERT_NOT_NULL(b);
xmlAddChild((xmlNodePtr)parent, (xmlNodePtr)b);
htmlNodePtr res = test_htmlFindFirstChild(parent, "span");
TEST_ASSERT_NULL(res);
xmlFreeDoc(doc);
}
/* Test: Exact name match required; should not match prefixes (e.g., "div" vs "div2") */
void test_htmlFindFirstChild_exact_name_not_prefix(void) {
xmlDocPtr doc = NULL;
htmlNodePtr parent = NULL;
create_doc_with_parent(&doc, &parent);
htmlNodePtr notMatch = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "div2");
TEST_ASSERT_NOT_NULL(notMatch);
xmlAddChild((xmlNodePtr)parent, (xmlNodePtr)notMatch);
htmlNodePtr match = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "div");
TEST_ASSERT_NOT_NULL(match);
xmlAddChild((xmlNodePtr)parent, (xmlNodePtr)match);
htmlNodePtr res = test_htmlFindFirstChild(parent, "DIV");
TEST_ASSERT_NOT_NULL(res);
TEST_ASSERT_EQUAL_PTR(match, res);
xmlFreeDoc(doc);
}
/* Test: Child name uppercase vs. lowercase query (case-insensitive) */
void test_htmlFindFirstChild_child_uppercase_name(void) {
xmlDocPtr doc = NULL;
htmlNodePtr parent = NULL;
create_doc_with_parent(&doc, &parent);
htmlNodePtr child = (htmlNodePtr)xmlNewNode(NULL, BAD_CAST "SPAN");
TEST_ASSERT_NOT_NULL(child);
xmlAddChild((xmlNodePtr)parent, (xmlNodePtr)child);
htmlNodePtr res = test_htmlFindFirstChild(parent, "span");
TEST_ASSERT_NOT_NULL(res);
TEST_ASSERT_EQUAL_PTR(child, res);
xmlFreeDoc(doc);
}
int main(void) {
/* Initialize libxml2 globally if desired; safe to omit for basic tree ops */
/* xmlInitParser(); */
UNITY_BEGIN();
RUN_TEST(test_htmlFindFirstChild_no_children_returns_NULL);
RUN_TEST(test_htmlFindFirstChild_case_insensitive_first_match);
RUN_TEST(test_htmlFindFirstChild_skips_non_element_nodes);
RUN_TEST(test_htmlFindFirstChild_no_matching_child_returns_NULL);
RUN_TEST(test_htmlFindFirstChild_exact_name_not_prefix);
RUN_TEST(test_htmlFindFirstChild_child_uppercase_name);
int ret = UNITY_END();
/* Cleanup libxml2 if initialized above */
/* xmlCleanupParser(); */
return ret;
}