view mrjunejune/test/template_renderer_test.c @ 273:e02e2036ef84 default tip

add Layer 2 JRPG component system Add reusable content and window modals, an isolated component sandbox, shared cyberpunk scroll areas, production-safe cache freshness, and server-rendered JRPG panel state. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Sat, 08 Aug 2026 02:08:08 -0700
parents 04fee26ecce0
children
line wrap: on
line source

/*
 * template_renderer_test.c — unit tests for the Mjj template renderer.
 *
 * Tests cover:
 *   - Passthrough (no includes)
 *   - Include expansion with real parts files
 *   - Missing include (silently skipped, output is still valid)
 *   - Missing file (Mjj_Template_Render_File returns FALSE)
 *   - Capacity-overflow protection (returns FALSE)
 *   - Page bodies contain no literal {{ / }} / <{{ markers
 *   - Rendered pages contain expected design-system asset references
 */

#include "mrjunejune/template_renderer.h"

#include "dowa/dowa.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* ------------------------------------------------------------------ */
/* Utilities                                                            */
/* ------------------------------------------------------------------ */

#define ASSERT(cond) \
  do { \
    if (!(cond)) { \
      fprintf(stderr, "FAIL [%s:%d]: %s\n", __FILE__, __LINE__, #cond); \
      abort(); \
    } \
  } while (0)

#define TEST(name) \
  do { fprintf(stdout, "  %-60s", name); fflush(stdout); } while (0)

#define PASS() \
  do { fprintf(stdout, "PASS\n"); } while (0)

/* Capacity used for page-level renders */
#define PAGE_CAP (256 * 1024)

/* ------------------------------------------------------------------ */
/* Tests                                                                */
/* ------------------------------------------------------------------ */

static void test_passthrough(void)
{
  printf("\n[passthrough — no includes]\n");

  Dowa_Arena *arena = Dowa_Arena_Create(4096);
  ASSERT(arena);

  TEST("plain text passes through unchanged");
  {
    char out[64];
    boolean ok = Mjj_Template_Render(out, sizeof(out), "Hello, world!", arena);
    ASSERT(ok);
    ASSERT(strcmp(out, "Hello, world!") == 0);
  }
  PASS();

  TEST("empty template produces empty string");
  {
    char out[8];
    out[0] = 'x';
    boolean ok = Mjj_Template_Render(out, sizeof(out), "", arena);
    ASSERT(ok);
    ASSERT(out[0] == '\0');
  }
  PASS();

  TEST("NULL template returns FALSE");
  {
    char out[8];
    boolean ok = Mjj_Template_Render(out, sizeof(out), NULL, arena);
    ASSERT(!ok);
  }
  PASS();

  Dowa_Arena_Free(arena);
}

static void test_capacity_overflow(void)
{
  printf("\n[capacity-overflow protection]\n");

  Dowa_Arena *arena = Dowa_Arena_Create(4096);
  ASSERT(arena);

  TEST("render into 1-byte buffer returns FALSE");
  {
    char out[1];
    boolean ok = Mjj_Template_Render(out, 1, "Hello", arena);
    ASSERT(!ok);
    /* NUL-termination must still hold */
    ASSERT(out[0] == '\0');
  }
  PASS();

  TEST("render into exactly-fitting buffer (size = len+1) returns TRUE");
  {
    const char *src = "Hi";
    char out[3]; /* 2 chars + NUL */
    boolean ok = Mjj_Template_Render(out, sizeof(out), src, arena);
    ASSERT(ok);
    ASSERT(strcmp(out, "Hi") == 0);
  }
  PASS();

  TEST("render into too-small buffer returns FALSE");
  {
    const char *src = "Hello world";
    char out[5];
    boolean ok = Mjj_Template_Render(out, sizeof(out), src, arena);
    ASSERT(!ok);
    /* Must still be NUL-terminated */
    ASSERT(out[sizeof(out) - 1] == '\0');
  }
  PASS();

  TEST("Render_File into tiny buffer returns FALSE");
  {
    char out[4];
    boolean ok = Mjj_Template_Render_File(out, sizeof(out), "/login/index.html", arena);
    ASSERT(!ok);
    ASSERT(out[sizeof(out) - 1] == '\0' || out[0] == '\0');
  }
  PASS();

  Dowa_Arena_Free(arena);
}

static void test_missing_file(void)
{
  printf("\n[missing file / missing include]\n");

  Dowa_Arena *arena = Dowa_Arena_Create(4096);
  ASSERT(arena);

  TEST("Render_File on non-existent path returns FALSE");
  {
    char out[256];
    boolean ok = Mjj_Template_Render_File(out, sizeof(out), "/does/not/exist.html", arena);
    ASSERT(!ok);
  }
  PASS();

  TEST("missing include in template is skipped silently");
  {
    char out[256];
    boolean ok = Mjj_Template_Render(
        out, sizeof(out),
        "before{{/parts/this_does_not_exist.html}}after",
        arena);
    ASSERT(ok);
    ASSERT(strcmp(out, "beforeafter") == 0);
    /* No literal {{ or }} should remain */
    ASSERT(strstr(out, "{{") == NULL);
    ASSERT(strstr(out, "}}") == NULL);
  }
  PASS();

  Dowa_Arena_Free(arena);
}

static void test_include_expansion(void)
{
  printf("\n[include expansion — real parts files]\n");

  Dowa_Arena *arena = Dowa_Arena_Create(8192);
  ASSERT(arena);

  TEST("{{/parts/base_head.html}} expands to non-empty content");
  {
    char *out = (char *)malloc(PAGE_CAP);
    ASSERT(out);
    boolean ok = Mjj_Template_Render(
        out, PAGE_CAP,
        "A{{/parts/base_head.html}}B",
        arena);
    ASSERT(ok);
    /* Expansion must have happened: output is longer than "AB" */
    ASSERT(strlen(out) > 2);
    /* No literal markers must remain */
    ASSERT(strstr(out, "{{") == NULL);
    ASSERT(strstr(out, "}}") == NULL);
    /* base_head includes charset and stylesheet references */
    ASSERT(strstr(out, "charset") != NULL);
    ASSERT(strstr(out, "design-system") != NULL);
    free(out);
  }
  PASS();

  TEST("{{/parts/header.html}} expands and contains MrJuneJune link");
  {
    char *out = (char *)malloc(PAGE_CAP);
    ASSERT(out);
    boolean ok = Mjj_Template_Render(
        out, PAGE_CAP,
        "{{/parts/header.html}}",
        arena);
    ASSERT(ok);
    ASSERT(strlen(out) > 0);
    ASSERT(strstr(out, "{{") == NULL);
    ASSERT(strstr(out, "MrJuneJune") != NULL);
    free(out);
  }
  PASS();

  Dowa_Arena_Free(arena);
}

/* Check that a fully rendered page contains no literal template markers. */
static void assert_page_clean(const char *path, char *buf)
{
  Dowa_Arena *arena = Dowa_Arena_Create(8192);
  ASSERT(arena);

  boolean ok = Mjj_Template_Render_File(buf, PAGE_CAP, path, arena);
  if (!ok)
  {
    fprintf(stderr, "  FAIL: Mjj_Template_Render_File returned FALSE for '%s'\n", path);
    abort();
  }

  if (strstr(buf, "{{"))
  {
    fprintf(stderr, "  FAIL: rendered '%s' contains '{{'\n", path);
    abort();
  }
  if (strstr(buf, "}}"))
  {
    fprintf(stderr, "  FAIL: rendered '%s' contains '}}'\n", path);
    abort();
  }
  if (strstr(buf, "<{{"))
  {
    fprintf(stderr, "  FAIL: rendered '%s' contains '<{{'\n", path);
    abort();
  }

  Dowa_Arena_Free(arena);
}

static void test_page_bodies(void)
{
  printf("\n[page bodies — no literal markers, design-system assets present]\n");

  char *buf = (char *)malloc(PAGE_CAP);
  ASSERT(buf);

  const char *pages[] = {
    "/login/index.html",
    "/account/password.html",
    "/admin/users/index.html",
    "/index.html",
    "/resume/index.html",
    "/tools/index.html",
    "/blog/index.html",
    "/talk/index.html",
    "/notes/index.html",
    NULL
  };

  for (int i = 0; pages[i]; i++)
  {
    char label[128];
    snprintf(label, sizeof(label), "no {{ in rendered '%s'", pages[i]);
    TEST(label);
    assert_page_clean(pages[i], buf);
    PASS();
  }

  /* Verify design-system stylesheet and component script are referenced
     in the login page (canonical example that contains base_head). */
  TEST("login page references design-system/styles/tokens.css");
  {
    Dowa_Arena *arena = Dowa_Arena_Create(4096);
    ASSERT(arena);
    boolean ok = Mjj_Template_Render_File(buf, PAGE_CAP, "/login/index.html", arena);
    ASSERT(ok);
    ASSERT(strstr(buf, "design-system") != NULL);
    ASSERT(strstr(buf, "tokens.css") != NULL);
    Dowa_Arena_Free(arena);
  }
  PASS();

  TEST("admin page references design-system and header");
  {
    Dowa_Arena *arena = Dowa_Arena_Create(4096);
    ASSERT(arena);
    boolean ok = Mjj_Template_Render_File(buf, PAGE_CAP, "/admin/users/index.html", arena);
    ASSERT(ok);
    ASSERT(strstr(buf, "design-system") != NULL);
    ASSERT(strstr(buf, "MrJuneJune") != NULL);
    Dowa_Arena_Free(arena);
  }
  PASS();

  TEST("password page references design-system and header");
  {
    Dowa_Arena *arena = Dowa_Arena_Create(4096);
    ASSERT(arena);
    boolean ok = Mjj_Template_Render_File(buf, PAGE_CAP, "/account/password.html", arena);
    ASSERT(ok);
    ASSERT(strstr(buf, "design-system") != NULL);
    ASSERT(strstr(buf, "MrJuneJune") != NULL);
    Dowa_Arena_Free(arena);
  }
  PASS();

  free(buf);
}

/* ------------------------------------------------------------------ */
/* main                                                                 */
/* ------------------------------------------------------------------ */

int main(void)
{
  printf("=== template_renderer_test ===\n");

  /* The renderer defaults to "mrjunejune/src"; in Bazel test, CWD is the
     runfiles root where mrjunejune/src/... is directly accessible. */

  test_passthrough();
  test_capacity_overflow();
  test_missing_file();
  test_include_expansion();
  test_page_bodies();

  printf("\nAll template_renderer tests passed.\n");
  return 0;
}