added testing
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
export function showAlert(msg: string, type: 'success' | 'error', elementId: string = 'alert') {
|
||||
const alertEl = document.getElementById(elementId);
|
||||
if (alertEl) {
|
||||
alertEl.textContent = msg;
|
||||
|
||||
// Define colors based on type using theme variables
|
||||
const colorVar = type === 'success' ? 'var(--green)' : 'var(--red)';
|
||||
|
||||
// Apply inline styles for guaranteed high contrast and glassy look
|
||||
alertEl.style.display = 'block';
|
||||
alertEl.style.backgroundColor = `color-mix(in srgb, ${colorVar} 15%, transparent)`;
|
||||
alertEl.style.color = 'var(--text)';
|
||||
alertEl.style.border = `1px solid color-mix(in srgb, ${colorVar} 40%, transparent)`;
|
||||
alertEl.style.padding = '1rem';
|
||||
alertEl.style.borderRadius = '0.75rem';
|
||||
alertEl.style.marginBottom = '1.5rem';
|
||||
alertEl.style.fontSize = '0.875rem';
|
||||
alertEl.style.fontWeight = '600';
|
||||
alertEl.style.backdropFilter = 'blur(12px)';
|
||||
alertEl.style.textAlign = 'center';
|
||||
alertEl.style.boxShadow = '0 4px 15px -5px rgba(0,0,0,0.3)';
|
||||
|
||||
alertEl.classList.remove('hidden');
|
||||
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
|
||||
setTimeout(() => {
|
||||
alertEl.classList.add('hidden');
|
||||
alertEl.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildCybersigil, GLYPHS } from './cybersigil';
|
||||
|
||||
/** Deterministic LCG so a given seed reproduces a given sigil. */
|
||||
function seeded(seed: number): () => number {
|
||||
let s = seed >>> 0;
|
||||
return () => {
|
||||
s = (s * 1664525 + 1013904223) >>> 0;
|
||||
return s / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
describe('buildCybersigil', () => {
|
||||
it('is deterministic for a fixed RNG', () => {
|
||||
const a = buildCybersigil({ rng: seeded(42) });
|
||||
const b = buildCybersigil({ rng: seeded(42) });
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
|
||||
it('varies with the seed', () => {
|
||||
expect(buildCybersigil({ rng: seeded(1) })).not.toBe(buildCybersigil({ rng: seeded(2) }));
|
||||
});
|
||||
|
||||
it('emits a single well-formed root svg', () => {
|
||||
const svg = buildCybersigil({ rng: seeded(7) });
|
||||
expect(svg.startsWith('<svg')).toBe(true);
|
||||
expect(svg.endsWith('</svg>')).toBe(true);
|
||||
expect(svg.match(/<svg/g)).toHaveLength(1);
|
||||
// no formatting holes leaked into the path data
|
||||
expect(svg).not.toMatch(/NaN|undefined|Infinity/);
|
||||
});
|
||||
|
||||
it('is vertically mirrored (two halves, one flipped about x=0)', () => {
|
||||
const svg = buildCybersigil({ rng: seeded(7) });
|
||||
expect(svg.match(/class="cs-sig-half"/g)).toHaveLength(2);
|
||||
expect(svg).toContain('transform="scale(-1 1)"');
|
||||
});
|
||||
|
||||
it('every stroke is pathLength-normalised for the carve animation', () => {
|
||||
const svg = buildCybersigil({ rng: seeded(99) });
|
||||
const paths = svg.match(/<path\b[^>]*>/g) ?? [];
|
||||
expect(paths.length).toBeGreaterThan(0);
|
||||
for (const p of paths) expect(p).toContain('pathLength="1"');
|
||||
});
|
||||
|
||||
it('exposes a numeric, positive viewBox', () => {
|
||||
const svg = buildCybersigil({ rng: seeded(7) });
|
||||
const vb = svg.match(/viewBox="([^"]+)"/)?.[1] ?? '';
|
||||
const nums = vb.split(/\s+/).map(Number);
|
||||
expect(nums).toHaveLength(4);
|
||||
expect(nums.every(Number.isFinite)).toBe(true);
|
||||
expect(nums[2]).toBeGreaterThan(0); // width
|
||||
expect(nums[3]).toBeGreaterThan(0); // height
|
||||
});
|
||||
|
||||
it('honours the count option and stays bounded', () => {
|
||||
const sparse = buildCybersigil({ count: 1, rng: seeded(5) });
|
||||
const dense = buildCybersigil({ count: 9, rng: seeded(5) });
|
||||
const n = (s: string) => (s.match(/<path/g) ?? []).length;
|
||||
expect(n(dense)).toBeGreaterThan(n(sparse));
|
||||
expect(n(dense)).toBeLessThanOrEqual(260); // MAX_PATHS(110) mirrored + ornaments
|
||||
});
|
||||
|
||||
it('ships a non-empty glyph library with valid path data', () => {
|
||||
expect(GLYPHS.length).toBeGreaterThan(0);
|
||||
for (const g of GLYPHS) {
|
||||
expect(g.w).toBeGreaterThan(0);
|
||||
expect(g.h).toBeGreaterThan(0);
|
||||
expect(g.d).toMatch(/^M/);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,7 @@ export const ALL: APIRoute = async ({ request, params }) => {
|
||||
|
||||
if (request.method !== 'GET' && request.method !== 'HEAD' && request.body) {
|
||||
fetchOptions.body = request.body;
|
||||
// @ts-ignore — required by Node fetch when body is a stream
|
||||
// @ts-expect-error — required by Node fetch when body is a stream
|
||||
fetchOptions.duplex = 'half';
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export const ALL: APIRoute = async ({ request, params }) => {
|
||||
if (FORBIDDEN_RESPONSE_HEADERS.has(k)) return;
|
||||
responseHeaders.set(key, value);
|
||||
});
|
||||
// @ts-ignore — getSetCookie is on Node fetch's Headers
|
||||
// @ts-expect-error — getSetCookie is on Node fetch's Headers
|
||||
const setCookies: string[] = response.headers.getSetCookie?.() ?? [];
|
||||
for (const c of setCookies) {
|
||||
responseHeaders.append('set-cookie', c);
|
||||
|
||||
Reference in New Issue
Block a user