OpenCut/packages/effects/tests/artistic-effects.test.ts

148 lines
5.4 KiB
TypeScript

import { describe, test, expect } from "bun:test";
import { EffectCategory } from "../src/categories";
import type { EffectDefinition } from "../src/types";
import { vignetteEffectDefinition } from "../src/effects/vignette";
import { filmGrainEffectDefinition } from "../src/effects/film-grain";
import { sharpenEffectDefinition } from "../src/effects/sharpen";
import { pixelateEffectDefinition } from "../src/effects/pixelate";
import { chromaticAberrationEffectDefinition } from "../src/effects/chromatic-aberration";
import { glitchEffectDefinition } from "../src/effects/glitch";
import { neonGlowEffectDefinition } from "../src/effects/neon-glow";
import { sketchEffectDefinition } from "../src/effects/sketch";
import { oilPaintEffectDefinition } from "../src/effects/oil-paint";
const defaultArgs = { width: 1920, height: 1080, time: 1.5 };
/** Validate common artistic effect structure */
function validateArtisticEffect(def: EffectDefinition, expectedType: string, expectedPasses: number) {
test(`${expectedType}: has correct type and category`, () => {
expect(def.type).toBe(expectedType);
expect(def.category).toBe(EffectCategory.ARTISTIC);
});
test(`${expectedType}: has ${expectedPasses} pass(es)`, () => {
expect(def.renderer.passes).toHaveLength(expectedPasses);
});
test(`${expectedType}: uniforms factory works with defaults`, () => {
const defaults: Record<string, number | string | boolean> = {};
for (const p of def.params) defaults[p.key] = p.default;
for (const pass of def.renderer.passes) {
const result = pass.uniforms({ effectParams: defaults, ...defaultArgs });
expect(typeof result).toBe("object");
}
});
}
// --- Single-pass effects ---
describe("Vignette", () => {
validateArtisticEffect(vignetteEffectDefinition, "vignette", 1);
test("has 3 params: intensity, radius, softness", () => {
expect(vignetteEffectDefinition.params.map((p) => p.key)).toEqual(["intensity", "radius", "softness"]);
});
});
describe("Film Grain", () => {
validateArtisticEffect(filmGrainEffectDefinition, "film-grain", 1);
test("has animated boolean param", () => {
const animated = filmGrainEffectDefinition.params.find((p) => p.key === "animated");
expect(animated?.type).toBe("boolean");
});
test("uses time when animated", () => {
const pass = filmGrainEffectDefinition.renderer.passes[0];
const withTime = pass.uniforms({ effectParams: { intensity: 30, size: 2, animated: true }, ...defaultArgs });
expect(withTime.u_time).toBe(1.5);
});
test("ignores time when not animated", () => {
const pass = filmGrainEffectDefinition.renderer.passes[0];
const noTime = pass.uniforms({ effectParams: { intensity: 30, size: 2, animated: false }, ...defaultArgs });
expect(noTime.u_time).toBe(0);
});
});
describe("Sharpen", () => {
validateArtisticEffect(sharpenEffectDefinition, "sharpen", 1);
});
describe("Pixelate", () => {
validateArtisticEffect(pixelateEffectDefinition, "pixelate", 1);
test("blockSize default is 10", () => {
expect(pixelateEffectDefinition.params[0].default).toBe(10);
});
test("blockSize passed directly as uniform", () => {
const pass = pixelateEffectDefinition.renderer.passes[0];
const result = pass.uniforms({ effectParams: { blockSize: 20 }, ...defaultArgs });
expect(result.u_blockSize).toBe(20);
});
});
describe("Chromatic Aberration", () => {
validateArtisticEffect(chromaticAberrationEffectDefinition, "chromatic-aberration", 1);
test("angle converted to radians", () => {
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
const result = pass.uniforms({ effectParams: { intensity: 50, angle: 180 }, ...defaultArgs });
expect(result.u_angle).toBeCloseTo(Math.PI, 5);
});
});
// --- Multi-pass effects ---
describe("Glitch", () => {
validateArtisticEffect(glitchEffectDefinition, "glitch", 2);
test("has 3 params: intensity, speed, colorSplit", () => {
expect(glitchEffectDefinition.params.map((p) => p.key)).toEqual(["intensity", "speed", "colorSplit"]);
});
test("both passes use time", () => {
const defaults: Record<string, number> = { intensity: 50, speed: 50, colorSplit: 30 };
for (const pass of glitchEffectDefinition.renderer.passes) {
const result = pass.uniforms({ effectParams: defaults, ...defaultArgs });
expect(result.u_time).toBeGreaterThan(0);
}
});
});
describe("Neon Glow", () => {
validateArtisticEffect(neonGlowEffectDefinition, "neon-glow", 2);
test("has color param", () => {
const colorParam = neonGlowEffectDefinition.params.find((p) => p.key === "color");
expect(colorParam?.type).toBe("color");
});
test("pass 2 converts hex color to RGB array", () => {
const pass2 = neonGlowEffectDefinition.renderer.passes[1];
const result = pass2.uniforms({ effectParams: { intensity: 50, color: "#ff0000", threshold: 50 }, ...defaultArgs });
expect(result.u_glowColor).toEqual([1, 0, 0]);
});
});
describe("Sketch", () => {
validateArtisticEffect(sketchEffectDefinition, "sketch", 2);
test("has threshold and lineWidth params", () => {
expect(sketchEffectDefinition.params.map((p) => p.key)).toEqual(["threshold", "lineWidth"]);
});
});
describe("Oil Paint", () => {
validateArtisticEffect(oilPaintEffectDefinition, "oil-paint", 2);
test("radius max is capped at 5 for performance", () => {
const radiusParam = oilPaintEffectDefinition.params.find((p) => p.key === "radius");
if (radiusParam?.type === "number") {
expect(radiusParam.max).toBeLessThanOrEqual(5);
}
});
});