Testing & Deployment
Shipping production-grade Coralite applications requires verifying component behavior at unit level, testing full-page hydration during end-to-end (E2E) workflows, deploying static artifacts to host platforms, and hardening application security with strict Content Security Policies (CSP).
1. Unit Testing #
Coralite provides isolated evaluation utilities via coralite/testing to unit test components and pages without launching a full browser instance or static server.
Testing Component Server Logic #
Use loadComponent(path, options) to evaluate a component's server(), getters, and context blocks in Node.js isolated VM contexts. You can pass mock state, route context, and custom context parameters.
import test from 'node:test'
import assert from 'node:assert/strict'
import { loadComponent } from 'coralite/testing'
test('user-card component evaluates server state', async () => {
const result = await loadComponent('src/components/user-card.html', {
context: {
route: { path: '/team', params: {} }
}
})
assert.equal(result.errors.length, 0)
assert.ok(result.definition)
})
Testing Pages with loadPage #
Similarly, loadPage(path, options) isolates page definitions and returns evaluated route patterns, enumerated paths, and server return values. The result also reports hasDefinePage (legacy definePage wrapper) and hasPageExports (flat named exports).
import test from 'node:test'
import assert from 'node:assert/strict'
import { loadPage } from 'coralite/testing'
test('blog page exports enumerated paths', async () => {
const result = await loadPage('src/pages/blog/[slug].html')
assert.equal(typeof result.paths, 'function')
assert.equal(result.hasPageExports, true)
})
Mocking Plugin Context in Unit Tests #
When a component or page consumes a plugin context, you can inject a mock-shaped object directly with options.context on loadComponent or loadPage. This bypasses the plugin system entirely and is the fastest way to test the consumer.
Assuming the page's server() block calls analytics.track('page_view'):
import test from 'node:test'
import assert from 'node:assert/strict'
import { loadPage } from 'coralite/testing'
test('page uses mocked analytics', async () => {
const page = await loadPage('src/pages/index.html', {
context: {
analytics: {
track: event => `mocked:${event}`
}
}
})
const result = await page.server()
assert.equal(result.event, 'mocked:page_view')
})
Assuming the component's server() block calls api.getData():
import test from 'node:test'
import assert from 'node:assert/strict'
import { loadComponent } from 'coralite/testing'
test('component uses mocked API', async () => {
const component = await loadComponent('src/components/user-card.html', {
context: {
api: {
getData: async () => ({ name: 'Mock User' })
}
}
})
const state = await component.server()
assert.equal(state.name, 'Mock User')
})
A per-call context overrides options.context:
await page.server({
analytics: {
track: () => 'per-call-mock'
}
})
Scope: loadComponent and loadPage inject context into server() and client() wrappers. They do not execute the plugin's Phase 1/Phase 2 setup. For getters, style, slots, or top-level plugin context, use a stub plugin registered through createCoralite (see the Plugin Mocking section of the E2E guide).
import { createCoralite, definePlugin } from 'coralite'
const i18nStub = definePlugin({
name: 'i18n',
context: () => () => ({
$t: key => `mocked:${key}`
})
})
const app = await createCoralite({
mode: 'testing',
plugins: [i18nStub]
})
2. End-to-End (E2E) Testing #
For end-to-end verification in real browser environments (such as Playwright or Cypress), Coralite exposes hydration testing helpers when built in testing mode (mode: 'testing').
The window.__coralite__.test API #
When mode is set to 'testing' (or when using coralite test), the browser exposes window.__coralite__.test on the global runtime namespace:
window.__coralite__.test.ready: A Promise resolving whendata-coralite-readyis set on<html>and all initial componentclient()controllers finish execution.window.__coralite__.test.reset(): ClearslocalStorage,sessionStorage, IndexedDB databases, and service worker registrations without triggering a hard page reload.window.__coralite__.test.flush(): Awaits active microtask DOM batch updates across all custom element instances.window.__coralite__.test.getComponent(element): Returns the testing harness for a hydrated custom element instance.window.__coralite__.test.getInstances(tagName?): Returns all hydrated custom element instances in the document (or filtered bytagName).window.__coralite__.test.getEvents(): Returns the telemetry log of dispatched componentCustomEventinstances.window.__coralite__.test.getHydratedCount(): Returns the total count of hydrated component instances on the page.
Playwright Test Example #
import { test, expect } from '@playwright/test'
test('counter button increments state post-hydration', async ({ page }) => {
await page.goto('http://localhost:4200/')
// 1. Wait for framework hydration readiness
await page.evaluate(() => window.__coralite__.test.ready)
// 2. Interact with hydrated component
const button = page.locator('button[ref="counter"]')
await button.click()
// 3. Assert updated state in DOM
await expect(button).toHaveText('Count: 1')
})
Test Fixtures Isolation #
Always place component test fixtures in a dedicated directory (such as tests/fixtures/) configured via fixtures in coralite.config.js. Never author test fixture pages inside src/pages/. In development and test servers (coralite dev and coralite test), fixtures are mounted under /__fixtures__/<name>. When building for production (coralite build), Coralite automatically excludes all test fixtures and test directories (e.g. __test__, *.test.html) to guarantee zero pollution in dist/.
3. Production Deployment #
Building a Coralite project with coralite build compiles all HTML pages, scoped CSS, component scripts, and public static assets into the target directory (default dist/).
Clean URLs Configuration #
The cleanUrls configuration setting in coralite.config.js dictates how page HTML files are structured in the output directory:
import { defineConfig } from 'coralite'
export default defineConfig({
cleanUrls: true // Default: outputs dist/about/index.html (/about)
})
cleanUrls: true(Directory Style, Default): Pagesrc/pages/about.htmlbuilds todist/about/index.htmlwith route path/about.cleanUrls: false(File Style): Pagesrc/pages/about.htmlbuilds todist/about.htmlwith route path/about.html.
Zero Server Runtime Hosting #
Because the output is pure static HTML, CSS, and client JS bundles, Coralite applications require zero Node.js server runtime. You can deploy the dist/ folder directly to any static web host or object storage CDN:
- Netlify: Set publish directory to
distand build command topnpm build. - Vercel: Configure output directory as
distwith static framework preset. - Cloudflare Pages: Point build output directory to
dist. - AWS S3 + CloudFront: Sync
dist/directly to your S3 bucket.
4. Content Security Policy (CSP) #
Coralite provides automated Content Security Policy generation to protect your site against Cross-Site Scripting (XSS) and code injection.
CSP Configuration #
Enable and configure CSP directly in coralite.config.js:
import { defineConfig } from 'coralite'
export default defineConfig({
csp: {
enabled: true,
hashAlgorithm: 'sha256',
injectMeta: true,
directives: {
'default-src': ["'self'"],
'script-src': ["'self'"],
'style-src': ["'self'"]
}
}
})
Hash Mode vs Nonce Mode #
- SSG Hash Mode (Default): During build, Coralite computes cryptographic hashes (SHA-256) for all inline scripts, import maps, and styles, injecting
<meta http-equiv="Content-Security-Policy" content="...">into the document head. - SSR Nonce Mode: When rendering on demand, pass a per-request cryptographic
nonceoption tocoralite.build(url, { nonce })to propagate nonce attributes across framework scripts.
Return to the Guide Overview to explore all framework documentation.