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

108 lines
3.7 KiB
TypeScript

import { describe, test, expect } from "bun:test";
import { EffectCategory } from "../src/categories";
import type { EffectContext, EffectDefinition } from "../src/types";
import { skinSmoothEffectDefinition } from "../src/effects/skin-smooth";
import { faceBrightenEffectDefinition } from "../src/effects/face-brighten";
import { eyeEnhanceEffectDefinition } from "../src/effects/eye-enhance";
import { teethWhitenEffectDefinition } from "../src/effects/teeth-whiten";
import { blushEffectDefinition } from "../src/effects/blush";
import { slimFaceEffectDefinition } from "../src/effects/slim-face";
const defaultArgs = { width: 1920, height: 1080, time: 0 };
const mockContext: EffectContext = {
faceDetected: true,
cheekLeft: [0.3, 0.5],
cheekRight: [0.7, 0.5],
cheekRadius: 0.06,
jawPoints: [0.5, 0.65],
};
function validateBeautyEffect(def: EffectDefinition, type: string) {
test(`${type}: correct type and BEAUTY category`, () => {
expect(def.type).toBe(type);
expect(def.category).toBe(EffectCategory.BEAUTY);
});
test(`${type}: uniforms work without context (graceful fallback)`, () => {
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");
}
});
test(`${type}: uniforms work with face context`, () => {
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, context: mockContext });
expect(typeof result).toBe("object");
}
});
}
describe("Skin Smooth", () => {
validateBeautyEffect(skinSmoothEffectDefinition, "skin-smooth");
test("has 2 passes (bilateral filter + mask)", () => {
expect(skinSmoothEffectDefinition.renderer.passes).toHaveLength(2);
});
});
describe("Face Brighten", () => {
validateBeautyEffect(faceBrightenEffectDefinition, "face-brighten");
test("single pass", () => {
expect(faceBrightenEffectDefinition.renderer.passes).toHaveLength(1);
});
});
describe("Eye Enhance", () => {
validateBeautyEffect(eyeEnhanceEffectDefinition, "eye-enhance");
test("has brightness and contrast params", () => {
expect(eyeEnhanceEffectDefinition.params.map((p) => p.key)).toEqual(["brightness", "contrast"]);
});
});
describe("Teeth Whiten", () => {
validateBeautyEffect(teethWhitenEffectDefinition, "teeth-whiten");
});
describe("Blush", () => {
validateBeautyEffect(blushEffectDefinition, "blush");
test("has color param", () => {
const colorParam = blushEffectDefinition.params.find((p) => p.key === "color");
expect(colorParam?.type).toBe("color");
});
test("uses context cheek positions when available", () => {
const pass = blushEffectDefinition.renderer.passes[0];
const result = pass.uniforms({
effectParams: { intensity: 50, color: "#ff9999" },
...defaultArgs,
context: mockContext,
});
expect(result.u_cheekLeft).toEqual([0.3, 0.5]);
});
test("falls back to defaults without context", () => {
const pass = blushEffectDefinition.renderer.passes[0];
const result = pass.uniforms({
effectParams: { intensity: 50, color: "#ff9999" },
...defaultArgs,
});
expect(result.u_cheekLeft).toEqual([0.35, 0.55]);
});
});
describe("Slim Face", () => {
validateBeautyEffect(slimFaceEffectDefinition, "slim-face");
test("uses jawPoints from context", () => {
const pass = slimFaceEffectDefinition.renderer.passes[0];
const result = pass.uniforms({
effectParams: { intensity: 50 },
...defaultArgs,
context: mockContext,
});
expect(result.u_jawCenter).toEqual([0.5, 0.65]);
});
});