test(effects): add unit tests for chromatic aberration, glitch, sharpen, and vignette effects
- Implement tests for chromatic aberration effect definition, verifying type, name, category, parameters, and renderer behavior. - Add tests for glitch effect definition, covering type, name, parameters, and rendering logic. - Create tests for sharpen effect definition, ensuring correct parameter defaults and rendering. - Introduce tests for vignette effect definition, validating parameters and rendering behavior. These tests enhance coverage for the new effects introduced in the @opencut/effects package.
This commit is contained in:
parent
81adac6e1e
commit
6cbc8d9d1b
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,122 @@
|
|||
import { describe, test, expect } from "bun:test";
|
||||
import { chromaticAberrationEffectDefinition } from "./index";
|
||||
import { EffectCategory } from "../../categories";
|
||||
|
||||
describe("Chromatic Aberration Effect Definition", () => {
|
||||
test("has correct type and name", () => {
|
||||
expect(chromaticAberrationEffectDefinition.type).toBe("chromatic-aberration");
|
||||
expect(chromaticAberrationEffectDefinition.name).toBe("Chromatic Aberration");
|
||||
});
|
||||
|
||||
test("is categorized as artistic", () => {
|
||||
expect(chromaticAberrationEffectDefinition.category).toBe(EffectCategory.ARTISTIC);
|
||||
});
|
||||
|
||||
test("has searchable keywords", () => {
|
||||
expect(chromaticAberrationEffectDefinition.keywords).toContain("chromatic");
|
||||
expect(chromaticAberrationEffectDefinition.keywords).toContain("aberration");
|
||||
expect(chromaticAberrationEffectDefinition.keywords).toContain("rgb split");
|
||||
expect(chromaticAberrationEffectDefinition.keywords).toContain("prism");
|
||||
});
|
||||
|
||||
test("has 2 params: intensity and angle", () => {
|
||||
expect(chromaticAberrationEffectDefinition.params.map((p) => p.key)).toEqual([
|
||||
"intensity",
|
||||
"angle",
|
||||
]);
|
||||
});
|
||||
|
||||
test("intensity param has correct defaults", () => {
|
||||
const param = chromaticAberrationEffectDefinition.params[0];
|
||||
expect(param.key).toBe("intensity");
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(20);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(100);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("angle param has correct defaults", () => {
|
||||
const param = chromaticAberrationEffectDefinition.params[1];
|
||||
expect(param.key).toBe("angle");
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(0);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(360);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("renderer has single pass", () => {
|
||||
expect(chromaticAberrationEffectDefinition.renderer.type).toBe("webgl");
|
||||
expect(chromaticAberrationEffectDefinition.renderer.passes).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("uniforms scale intensity by factor of 10", () => {
|
||||
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
|
||||
const at0 = pass.uniforms({
|
||||
effectParams: { intensity: 0, angle: 0 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at100 = pass.uniforms({
|
||||
effectParams: { intensity: 100, angle: 0 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(at0.u_intensity).toBe(0);
|
||||
expect(at100.u_intensity).toBe(10); // 100 / 10 = 10
|
||||
});
|
||||
|
||||
test("uniforms convert angle from degrees to radians", () => {
|
||||
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
|
||||
const at0 = pass.uniforms({
|
||||
effectParams: { intensity: 50, angle: 0 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at90 = pass.uniforms({
|
||||
effectParams: { intensity: 50, angle: 90 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at180 = pass.uniforms({
|
||||
effectParams: { intensity: 50, angle: 180 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at360 = pass.uniforms({
|
||||
effectParams: { intensity: 50, angle: 360 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(at0.u_angle).toBe(0);
|
||||
expect(at90.u_angle).toBeCloseTo(Math.PI / 2, 5);
|
||||
expect(at180.u_angle).toBeCloseTo(Math.PI, 5);
|
||||
expect(at360.u_angle).toBeCloseTo(2 * Math.PI, 5);
|
||||
});
|
||||
|
||||
test("angle 45 degrees converts correctly", () => {
|
||||
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
|
||||
const result = pass.uniforms({
|
||||
effectParams: { intensity: 50, angle: 45 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(result.u_angle).toBeCloseTo(Math.PI / 4, 5);
|
||||
});
|
||||
|
||||
test("uniforms with default values", () => {
|
||||
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
|
||||
const result = pass.uniforms({
|
||||
effectParams: { intensity: 20, angle: 0 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(result.u_intensity).toBe(2); // 20 / 10 = 2
|
||||
expect(result.u_angle).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
import { describe, test, expect } from "bun:test";
|
||||
import { glitchEffectDefinition } from "./index";
|
||||
import { EffectCategory } from "../../categories";
|
||||
|
||||
describe("Glitch Effect Definition", () => {
|
||||
test("has correct type and name", () => {
|
||||
expect(glitchEffectDefinition.type).toBe("glitch");
|
||||
expect(glitchEffectDefinition.name).toBe("Glitch");
|
||||
});
|
||||
|
||||
test("is categorized as artistic", () => {
|
||||
expect(glitchEffectDefinition.category).toBe(EffectCategory.ARTISTIC);
|
||||
});
|
||||
|
||||
test("has searchable keywords", () => {
|
||||
expect(glitchEffectDefinition.keywords).toContain("glitch");
|
||||
expect(glitchEffectDefinition.keywords).toContain("digital");
|
||||
expect(glitchEffectDefinition.keywords).toContain("vhs");
|
||||
expect(glitchEffectDefinition.keywords).toContain("corrupt");
|
||||
});
|
||||
|
||||
test("has 3 params: intensity, speed, colorSplit", () => {
|
||||
expect(glitchEffectDefinition.params.map((p) => p.key)).toEqual([
|
||||
"intensity",
|
||||
"speed",
|
||||
"colorSplit",
|
||||
]);
|
||||
});
|
||||
|
||||
test("intensity param has correct defaults", () => {
|
||||
const param = glitchEffectDefinition.params[0];
|
||||
expect(param.key).toBe("intensity");
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(50);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(100);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("speed param has correct defaults", () => {
|
||||
const param = glitchEffectDefinition.params[1];
|
||||
expect(param.key).toBe("speed");
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(50);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(100);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("colorSplit param has correct defaults", () => {
|
||||
const param = glitchEffectDefinition.params[2];
|
||||
expect(param.key).toBe("colorSplit");
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(30);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(100);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("renderer has 2 passes (block displacement + scanlines)", () => {
|
||||
expect(glitchEffectDefinition.renderer.type).toBe("webgl");
|
||||
expect(glitchEffectDefinition.renderer.passes).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("pass 1 uniforms scale intensity correctly", () => {
|
||||
const pass1 = glitchEffectDefinition.renderer.passes[0];
|
||||
const result = pass1.uniforms({
|
||||
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 1,
|
||||
});
|
||||
expect(result.u_intensity).toBe(0.05); // 50 / 1000 = 0.05
|
||||
});
|
||||
|
||||
test("pass 1 colorSplit scales with width", () => {
|
||||
const pass1 = glitchEffectDefinition.renderer.passes[0];
|
||||
const at1920 = pass1.uniforms({
|
||||
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 1,
|
||||
});
|
||||
const at3840 = pass1.uniforms({
|
||||
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
|
||||
width: 3840,
|
||||
height: 2160,
|
||||
time: 1,
|
||||
});
|
||||
// colorSplit: 30 / (width * 10)
|
||||
expect(at1920.u_colorSplit).toBeCloseTo(30 / 19200, 6);
|
||||
expect(at3840.u_colorSplit).toBeCloseTo(30 / 38400, 6);
|
||||
// Higher resolution = smaller colorSplit value
|
||||
expect(at3840.u_colorSplit).toBeLessThan(at1920.u_colorSplit as number);
|
||||
});
|
||||
|
||||
test("pass 1 time scales with speed", () => {
|
||||
const pass1 = glitchEffectDefinition.renderer.passes[0];
|
||||
const atSpeed0 = pass1.uniforms({
|
||||
effectParams: { intensity: 50, speed: 0, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 2,
|
||||
});
|
||||
const atSpeed50 = pass1.uniforms({
|
||||
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 2,
|
||||
});
|
||||
const atSpeed100 = pass1.uniforms({
|
||||
effectParams: { intensity: 50, speed: 100, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 2,
|
||||
});
|
||||
// time * (speed / 50)
|
||||
expect(atSpeed0.u_time).toBe(0);
|
||||
expect(atSpeed50.u_time).toBe(2); // 2 * (50 / 50) = 2
|
||||
expect(atSpeed100.u_time).toBe(4); // 2 * (100 / 50) = 4
|
||||
});
|
||||
|
||||
test("pass 2 uniforms scale intensity correctly", () => {
|
||||
const pass2 = glitchEffectDefinition.renderer.passes[1];
|
||||
const result = pass2.uniforms({
|
||||
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 1,
|
||||
});
|
||||
expect(result.u_intensity).toBe(0.5); // 50 / 100 = 0.5
|
||||
});
|
||||
|
||||
test("pass 2 time scales with speed", () => {
|
||||
const pass2 = glitchEffectDefinition.renderer.passes[1];
|
||||
const atSpeed25 = pass2.uniforms({
|
||||
effectParams: { intensity: 50, speed: 25, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 4,
|
||||
});
|
||||
expect(atSpeed25.u_time).toBe(2); // 4 * (25 / 50) = 2
|
||||
});
|
||||
|
||||
test("both passes use time uniformly", () => {
|
||||
const pass1 = glitchEffectDefinition.renderer.passes[0];
|
||||
const pass2 = glitchEffectDefinition.renderer.passes[1];
|
||||
const params = { intensity: 50, speed: 50, colorSplit: 30 };
|
||||
const args = { effectParams: params, width: 1920, height: 1080, time: 3 };
|
||||
const result1 = pass1.uniforms(args);
|
||||
const result2 = pass2.uniforms(args);
|
||||
// Both passes should have the same time value when speed is 50
|
||||
expect(result1.u_time).toBe(3);
|
||||
expect(result2.u_time).toBe(3);
|
||||
});
|
||||
|
||||
test("handles undefined time gracefully", () => {
|
||||
const pass1 = glitchEffectDefinition.renderer.passes[0];
|
||||
const result = pass1.uniforms({
|
||||
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(result.u_time).toBe(0);
|
||||
});
|
||||
|
||||
test("zero speed results in zero time", () => {
|
||||
const pass1 = glitchEffectDefinition.renderer.passes[0];
|
||||
const result = pass1.uniforms({
|
||||
effectParams: { intensity: 50, speed: 0, colorSplit: 30 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 100,
|
||||
});
|
||||
expect(result.u_time).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
import { describe, test, expect } from "bun:test";
|
||||
import { sharpenEffectDefinition } from "./index";
|
||||
import { EffectCategory } from "../../categories";
|
||||
|
||||
describe("Sharpen Effect Definition", () => {
|
||||
test("has correct type and name", () => {
|
||||
expect(sharpenEffectDefinition.type).toBe("sharpen");
|
||||
expect(sharpenEffectDefinition.name).toBe("Sharpen");
|
||||
});
|
||||
|
||||
test("is categorized as artistic", () => {
|
||||
expect(sharpenEffectDefinition.category).toBe(EffectCategory.ARTISTIC);
|
||||
});
|
||||
|
||||
test("has searchable keywords", () => {
|
||||
expect(sharpenEffectDefinition.keywords).toContain("sharpen");
|
||||
expect(sharpenEffectDefinition.keywords).toContain("crisp");
|
||||
expect(sharpenEffectDefinition.keywords).toContain("detail");
|
||||
expect(sharpenEffectDefinition.keywords).toContain("unsharp mask");
|
||||
});
|
||||
|
||||
test("has single intensity param", () => {
|
||||
expect(sharpenEffectDefinition.params).toHaveLength(1);
|
||||
expect(sharpenEffectDefinition.params[0].key).toBe("intensity");
|
||||
});
|
||||
|
||||
test("intensity param has correct defaults", () => {
|
||||
const param = sharpenEffectDefinition.params[0];
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(50);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(100);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("renderer has single pass", () => {
|
||||
expect(sharpenEffectDefinition.renderer.type).toBe("webgl");
|
||||
expect(sharpenEffectDefinition.renderer.passes).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("uniforms normalize intensity to 0..1 range", () => {
|
||||
const pass = sharpenEffectDefinition.renderer.passes[0];
|
||||
const at0 = pass.uniforms({
|
||||
effectParams: { intensity: 0 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at100 = pass.uniforms({
|
||||
effectParams: { intensity: 100 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(at0.u_intensity).toBe(0);
|
||||
expect(at100.u_intensity).toBe(1);
|
||||
});
|
||||
|
||||
test("uniforms at 50% intensity", () => {
|
||||
const pass = sharpenEffectDefinition.renderer.passes[0];
|
||||
const result = pass.uniforms({
|
||||
effectParams: { intensity: 50 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(result.u_intensity).toBe(0.5);
|
||||
});
|
||||
|
||||
test("uniforms at various intensity levels", () => {
|
||||
const pass = sharpenEffectDefinition.renderer.passes[0];
|
||||
const at25 = pass.uniforms({
|
||||
effectParams: { intensity: 25 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at75 = pass.uniforms({
|
||||
effectParams: { intensity: 75 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(at25.u_intensity).toBe(0.25);
|
||||
expect(at75.u_intensity).toBe(0.75);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import { describe, test, expect } from "bun:test";
|
||||
import { vignetteEffectDefinition } from "./index";
|
||||
import { EffectCategory } from "../../categories";
|
||||
|
||||
describe("Vignette Effect Definition", () => {
|
||||
test("has correct type and name", () => {
|
||||
expect(vignetteEffectDefinition.type).toBe("vignette");
|
||||
expect(vignetteEffectDefinition.name).toBe("Vignette");
|
||||
});
|
||||
|
||||
test("is categorized as artistic", () => {
|
||||
expect(vignetteEffectDefinition.category).toBe(EffectCategory.ARTISTIC);
|
||||
});
|
||||
|
||||
test("has searchable keywords", () => {
|
||||
expect(vignetteEffectDefinition.keywords).toContain("vignette");
|
||||
expect(vignetteEffectDefinition.keywords).toContain("cinematic");
|
||||
expect(vignetteEffectDefinition.keywords).toContain("edges");
|
||||
});
|
||||
|
||||
test("has 3 params: intensity, radius, softness", () => {
|
||||
expect(vignetteEffectDefinition.params.map((p) => p.key)).toEqual([
|
||||
"intensity",
|
||||
"radius",
|
||||
"softness",
|
||||
]);
|
||||
});
|
||||
|
||||
test("intensity param has correct defaults", () => {
|
||||
const param = vignetteEffectDefinition.params[0];
|
||||
expect(param.key).toBe("intensity");
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(50);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(100);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("radius param has correct defaults", () => {
|
||||
const param = vignetteEffectDefinition.params[1];
|
||||
expect(param.key).toBe("radius");
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(80);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(100);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("softness param has correct defaults", () => {
|
||||
const param = vignetteEffectDefinition.params[2];
|
||||
expect(param.key).toBe("softness");
|
||||
expect(param.type).toBe("number");
|
||||
if (param.type === "number") {
|
||||
expect(param.default).toBe(50);
|
||||
expect(param.min).toBe(0);
|
||||
expect(param.max).toBe(100);
|
||||
expect(param.step).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("renderer has single pass", () => {
|
||||
expect(vignetteEffectDefinition.renderer.type).toBe("webgl");
|
||||
expect(vignetteEffectDefinition.renderer.passes).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("uniforms normalize intensity to 0..1 range", () => {
|
||||
const pass = vignetteEffectDefinition.renderer.passes[0];
|
||||
const at0 = pass.uniforms({
|
||||
effectParams: { intensity: 0, radius: 50, softness: 50 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at100 = pass.uniforms({
|
||||
effectParams: { intensity: 100, radius: 50, softness: 50 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(at0.u_intensity).toBe(0);
|
||||
expect(at100.u_intensity).toBe(1);
|
||||
});
|
||||
|
||||
test("uniforms scale radius correctly", () => {
|
||||
const pass = vignetteEffectDefinition.renderer.passes[0];
|
||||
const at0 = pass.uniforms({
|
||||
effectParams: { intensity: 50, radius: 0, softness: 50 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at100 = pass.uniforms({
|
||||
effectParams: { intensity: 50, radius: 100, softness: 50 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
// radius: 100 / 100 * 0.707 = 0.707
|
||||
expect(at0.u_radius).toBe(0);
|
||||
expect(at100.u_radius).toBeCloseTo(0.707, 3);
|
||||
});
|
||||
|
||||
test("uniforms scale softness correctly", () => {
|
||||
const pass = vignetteEffectDefinition.renderer.passes[0];
|
||||
const at0 = pass.uniforms({
|
||||
effectParams: { intensity: 50, radius: 50, softness: 0 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const at100 = pass.uniforms({
|
||||
effectParams: { intensity: 50, radius: 50, softness: 100 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
// softness: 100 / 100 * 0.5 = 0.5
|
||||
expect(at0.u_softness).toBe(0);
|
||||
expect(at100.u_softness).toBe(0.5);
|
||||
});
|
||||
|
||||
test("uniforms with default values", () => {
|
||||
const pass = vignetteEffectDefinition.renderer.passes[0];
|
||||
const result = pass.uniforms({
|
||||
effectParams: { intensity: 50, radius: 80, softness: 50 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
expect(result.u_intensity).toBe(0.5);
|
||||
expect(result.u_radius).toBeCloseTo(0.5656, 3); // 80 / 100 * 0.707
|
||||
expect(result.u_softness).toBe(0.25); // 50 / 100 * 0.5
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue