Skip to content

Commit 7e40be9

Browse files
committed
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.
1 parent 81adac6 commit 7e40be9

File tree

5 files changed

+521
-1
lines changed

5 files changed

+521
-1
lines changed

apps/web/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { chromaticAberrationEffectDefinition } from "./index";
3+
import { EffectCategory } from "../../categories";
4+
5+
describe("Chromatic Aberration Effect Definition", () => {
6+
test("has correct type and name", () => {
7+
expect(chromaticAberrationEffectDefinition.type).toBe("chromatic-aberration");
8+
expect(chromaticAberrationEffectDefinition.name).toBe("Chromatic Aberration");
9+
});
10+
11+
test("is categorized as artistic", () => {
12+
expect(chromaticAberrationEffectDefinition.category).toBe(EffectCategory.ARTISTIC);
13+
});
14+
15+
test("has searchable keywords", () => {
16+
expect(chromaticAberrationEffectDefinition.keywords).toContain("chromatic");
17+
expect(chromaticAberrationEffectDefinition.keywords).toContain("aberration");
18+
expect(chromaticAberrationEffectDefinition.keywords).toContain("rgb split");
19+
expect(chromaticAberrationEffectDefinition.keywords).toContain("prism");
20+
});
21+
22+
test("has 2 params: intensity and angle", () => {
23+
expect(chromaticAberrationEffectDefinition.params.map((p) => p.key)).toEqual([
24+
"intensity",
25+
"angle",
26+
]);
27+
});
28+
29+
test("intensity param has correct defaults", () => {
30+
const param = chromaticAberrationEffectDefinition.params[0];
31+
expect(param.key).toBe("intensity");
32+
expect(param.type).toBe("number");
33+
if (param.type === "number") {
34+
expect(param.default).toBe(20);
35+
expect(param.min).toBe(0);
36+
expect(param.max).toBe(100);
37+
expect(param.step).toBe(1);
38+
}
39+
});
40+
41+
test("angle param has correct defaults", () => {
42+
const param = chromaticAberrationEffectDefinition.params[1];
43+
expect(param.key).toBe("angle");
44+
expect(param.type).toBe("number");
45+
if (param.type === "number") {
46+
expect(param.default).toBe(0);
47+
expect(param.min).toBe(0);
48+
expect(param.max).toBe(360);
49+
expect(param.step).toBe(1);
50+
}
51+
});
52+
53+
test("renderer has single pass", () => {
54+
expect(chromaticAberrationEffectDefinition.renderer.type).toBe("webgl");
55+
expect(chromaticAberrationEffectDefinition.renderer.passes).toHaveLength(1);
56+
});
57+
58+
test("uniforms scale intensity by factor of 10", () => {
59+
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
60+
const at0 = pass.uniforms({
61+
effectParams: { intensity: 0, angle: 0 },
62+
width: 1920,
63+
height: 1080,
64+
});
65+
const at100 = pass.uniforms({
66+
effectParams: { intensity: 100, angle: 0 },
67+
width: 1920,
68+
height: 1080,
69+
});
70+
expect(at0.u_intensity).toBe(0);
71+
expect(at100.u_intensity).toBe(10); // 100 / 10 = 10
72+
});
73+
74+
test("uniforms convert angle from degrees to radians", () => {
75+
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
76+
const at0 = pass.uniforms({
77+
effectParams: { intensity: 50, angle: 0 },
78+
width: 1920,
79+
height: 1080,
80+
});
81+
const at90 = pass.uniforms({
82+
effectParams: { intensity: 50, angle: 90 },
83+
width: 1920,
84+
height: 1080,
85+
});
86+
const at180 = pass.uniforms({
87+
effectParams: { intensity: 50, angle: 180 },
88+
width: 1920,
89+
height: 1080,
90+
});
91+
const at360 = pass.uniforms({
92+
effectParams: { intensity: 50, angle: 360 },
93+
width: 1920,
94+
height: 1080,
95+
});
96+
expect(at0.u_angle).toBe(0);
97+
expect(at90.u_angle).toBeCloseTo(Math.PI / 2, 5);
98+
expect(at180.u_angle).toBeCloseTo(Math.PI, 5);
99+
expect(at360.u_angle).toBeCloseTo(2 * Math.PI, 5);
100+
});
101+
102+
test("angle 45 degrees converts correctly", () => {
103+
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
104+
const result = pass.uniforms({
105+
effectParams: { intensity: 50, angle: 45 },
106+
width: 1920,
107+
height: 1080,
108+
});
109+
expect(result.u_angle).toBeCloseTo(Math.PI / 4, 5);
110+
});
111+
112+
test("uniforms with default values", () => {
113+
const pass = chromaticAberrationEffectDefinition.renderer.passes[0];
114+
const result = pass.uniforms({
115+
effectParams: { intensity: 20, angle: 0 },
116+
width: 1920,
117+
height: 1080,
118+
});
119+
expect(result.u_intensity).toBe(2); // 20 / 10 = 2
120+
expect(result.u_angle).toBe(0);
121+
});
122+
});
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { glitchEffectDefinition } from "./index";
3+
import { EffectCategory } from "../../categories";
4+
5+
describe("Glitch Effect Definition", () => {
6+
test("has correct type and name", () => {
7+
expect(glitchEffectDefinition.type).toBe("glitch");
8+
expect(glitchEffectDefinition.name).toBe("Glitch");
9+
});
10+
11+
test("is categorized as artistic", () => {
12+
expect(glitchEffectDefinition.category).toBe(EffectCategory.ARTISTIC);
13+
});
14+
15+
test("has searchable keywords", () => {
16+
expect(glitchEffectDefinition.keywords).toContain("glitch");
17+
expect(glitchEffectDefinition.keywords).toContain("digital");
18+
expect(glitchEffectDefinition.keywords).toContain("vhs");
19+
expect(glitchEffectDefinition.keywords).toContain("corrupt");
20+
});
21+
22+
test("has 3 params: intensity, speed, colorSplit", () => {
23+
expect(glitchEffectDefinition.params.map((p) => p.key)).toEqual([
24+
"intensity",
25+
"speed",
26+
"colorSplit",
27+
]);
28+
});
29+
30+
test("intensity param has correct defaults", () => {
31+
const param = glitchEffectDefinition.params[0];
32+
expect(param.key).toBe("intensity");
33+
expect(param.type).toBe("number");
34+
if (param.type === "number") {
35+
expect(param.default).toBe(50);
36+
expect(param.min).toBe(0);
37+
expect(param.max).toBe(100);
38+
expect(param.step).toBe(1);
39+
}
40+
});
41+
42+
test("speed param has correct defaults", () => {
43+
const param = glitchEffectDefinition.params[1];
44+
expect(param.key).toBe("speed");
45+
expect(param.type).toBe("number");
46+
if (param.type === "number") {
47+
expect(param.default).toBe(50);
48+
expect(param.min).toBe(0);
49+
expect(param.max).toBe(100);
50+
expect(param.step).toBe(1);
51+
}
52+
});
53+
54+
test("colorSplit param has correct defaults", () => {
55+
const param = glitchEffectDefinition.params[2];
56+
expect(param.key).toBe("colorSplit");
57+
expect(param.type).toBe("number");
58+
if (param.type === "number") {
59+
expect(param.default).toBe(30);
60+
expect(param.min).toBe(0);
61+
expect(param.max).toBe(100);
62+
expect(param.step).toBe(1);
63+
}
64+
});
65+
66+
test("renderer has 2 passes (block displacement + scanlines)", () => {
67+
expect(glitchEffectDefinition.renderer.type).toBe("webgl");
68+
expect(glitchEffectDefinition.renderer.passes).toHaveLength(2);
69+
});
70+
71+
test("pass 1 uniforms scale intensity correctly", () => {
72+
const pass1 = glitchEffectDefinition.renderer.passes[0];
73+
const result = pass1.uniforms({
74+
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
75+
width: 1920,
76+
height: 1080,
77+
time: 1,
78+
});
79+
expect(result.u_intensity).toBe(0.05); // 50 / 1000 = 0.05
80+
});
81+
82+
test("pass 1 colorSplit scales with width", () => {
83+
const pass1 = glitchEffectDefinition.renderer.passes[0];
84+
const at1920 = pass1.uniforms({
85+
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
86+
width: 1920,
87+
height: 1080,
88+
time: 1,
89+
});
90+
const at3840 = pass1.uniforms({
91+
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
92+
width: 3840,
93+
height: 2160,
94+
time: 1,
95+
});
96+
// colorSplit: 30 / (width * 10)
97+
expect(at1920.u_colorSplit).toBeCloseTo(30 / 19200, 6);
98+
expect(at3840.u_colorSplit).toBeCloseTo(30 / 38400, 6);
99+
// Higher resolution = smaller colorSplit value
100+
expect(at3840.u_colorSplit).toBeLessThan(at1920.u_colorSplit as number);
101+
});
102+
103+
test("pass 1 time scales with speed", () => {
104+
const pass1 = glitchEffectDefinition.renderer.passes[0];
105+
const atSpeed0 = pass1.uniforms({
106+
effectParams: { intensity: 50, speed: 0, colorSplit: 30 },
107+
width: 1920,
108+
height: 1080,
109+
time: 2,
110+
});
111+
const atSpeed50 = pass1.uniforms({
112+
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
113+
width: 1920,
114+
height: 1080,
115+
time: 2,
116+
});
117+
const atSpeed100 = pass1.uniforms({
118+
effectParams: { intensity: 50, speed: 100, colorSplit: 30 },
119+
width: 1920,
120+
height: 1080,
121+
time: 2,
122+
});
123+
// time * (speed / 50)
124+
expect(atSpeed0.u_time).toBe(0);
125+
expect(atSpeed50.u_time).toBe(2); // 2 * (50 / 50) = 2
126+
expect(atSpeed100.u_time).toBe(4); // 2 * (100 / 50) = 4
127+
});
128+
129+
test("pass 2 uniforms scale intensity correctly", () => {
130+
const pass2 = glitchEffectDefinition.renderer.passes[1];
131+
const result = pass2.uniforms({
132+
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
133+
width: 1920,
134+
height: 1080,
135+
time: 1,
136+
});
137+
expect(result.u_intensity).toBe(0.5); // 50 / 100 = 0.5
138+
});
139+
140+
test("pass 2 time scales with speed", () => {
141+
const pass2 = glitchEffectDefinition.renderer.passes[1];
142+
const atSpeed25 = pass2.uniforms({
143+
effectParams: { intensity: 50, speed: 25, colorSplit: 30 },
144+
width: 1920,
145+
height: 1080,
146+
time: 4,
147+
});
148+
expect(atSpeed25.u_time).toBe(2); // 4 * (25 / 50) = 2
149+
});
150+
151+
test("both passes use time uniformly", () => {
152+
const pass1 = glitchEffectDefinition.renderer.passes[0];
153+
const pass2 = glitchEffectDefinition.renderer.passes[1];
154+
const params = { intensity: 50, speed: 50, colorSplit: 30 };
155+
const args = { effectParams: params, width: 1920, height: 1080, time: 3 };
156+
const result1 = pass1.uniforms(args);
157+
const result2 = pass2.uniforms(args);
158+
// Both passes should have the same time value when speed is 50
159+
expect(result1.u_time).toBe(3);
160+
expect(result2.u_time).toBe(3);
161+
});
162+
163+
test("handles undefined time gracefully", () => {
164+
const pass1 = glitchEffectDefinition.renderer.passes[0];
165+
const result = pass1.uniforms({
166+
effectParams: { intensity: 50, speed: 50, colorSplit: 30 },
167+
width: 1920,
168+
height: 1080,
169+
});
170+
expect(result.u_time).toBe(0);
171+
});
172+
173+
test("zero speed results in zero time", () => {
174+
const pass1 = glitchEffectDefinition.renderer.passes[0];
175+
const result = pass1.uniforms({
176+
effectParams: { intensity: 50, speed: 0, colorSplit: 30 },
177+
width: 1920,
178+
height: 1080,
179+
time: 100,
180+
});
181+
expect(result.u_time).toBe(0);
182+
});
183+
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { sharpenEffectDefinition } from "./index";
3+
import { EffectCategory } from "../../categories";
4+
5+
describe("Sharpen Effect Definition", () => {
6+
test("has correct type and name", () => {
7+
expect(sharpenEffectDefinition.type).toBe("sharpen");
8+
expect(sharpenEffectDefinition.name).toBe("Sharpen");
9+
});
10+
11+
test("is categorized as artistic", () => {
12+
expect(sharpenEffectDefinition.category).toBe(EffectCategory.ARTISTIC);
13+
});
14+
15+
test("has searchable keywords", () => {
16+
expect(sharpenEffectDefinition.keywords).toContain("sharpen");
17+
expect(sharpenEffectDefinition.keywords).toContain("crisp");
18+
expect(sharpenEffectDefinition.keywords).toContain("detail");
19+
expect(sharpenEffectDefinition.keywords).toContain("unsharp mask");
20+
});
21+
22+
test("has single intensity param", () => {
23+
expect(sharpenEffectDefinition.params).toHaveLength(1);
24+
expect(sharpenEffectDefinition.params[0].key).toBe("intensity");
25+
});
26+
27+
test("intensity param has correct defaults", () => {
28+
const param = sharpenEffectDefinition.params[0];
29+
expect(param.type).toBe("number");
30+
if (param.type === "number") {
31+
expect(param.default).toBe(50);
32+
expect(param.min).toBe(0);
33+
expect(param.max).toBe(100);
34+
expect(param.step).toBe(1);
35+
}
36+
});
37+
38+
test("renderer has single pass", () => {
39+
expect(sharpenEffectDefinition.renderer.type).toBe("webgl");
40+
expect(sharpenEffectDefinition.renderer.passes).toHaveLength(1);
41+
});
42+
43+
test("uniforms normalize intensity to 0..1 range", () => {
44+
const pass = sharpenEffectDefinition.renderer.passes[0];
45+
const at0 = pass.uniforms({
46+
effectParams: { intensity: 0 },
47+
width: 1920,
48+
height: 1080,
49+
});
50+
const at100 = pass.uniforms({
51+
effectParams: { intensity: 100 },
52+
width: 1920,
53+
height: 1080,
54+
});
55+
expect(at0.u_intensity).toBe(0);
56+
expect(at100.u_intensity).toBe(1);
57+
});
58+
59+
test("uniforms at 50% intensity", () => {
60+
const pass = sharpenEffectDefinition.renderer.passes[0];
61+
const result = pass.uniforms({
62+
effectParams: { intensity: 50 },
63+
width: 1920,
64+
height: 1080,
65+
});
66+
expect(result.u_intensity).toBe(0.5);
67+
});
68+
69+
test("uniforms at various intensity levels", () => {
70+
const pass = sharpenEffectDefinition.renderer.passes[0];
71+
const at25 = pass.uniforms({
72+
effectParams: { intensity: 25 },
73+
width: 1920,
74+
height: 1080,
75+
});
76+
const at75 = pass.uniforms({
77+
effectParams: { intensity: 75 },
78+
width: 1920,
79+
height: 1080,
80+
});
81+
expect(at25.u_intensity).toBe(0.25);
82+
expect(at75.u_intensity).toBe(0.75);
83+
});
84+
});

0 commit comments

Comments
 (0)