chore: tests

This commit is contained in:
bwees
2026-01-24 17:07:26 -06:00
parent 20ccbcec47
commit f6205ec3c4
2 changed files with 349 additions and 18 deletions

View File

@@ -0,0 +1,326 @@
import type { EditActions } from '$lib/managers/edit/edit-manager.svelte';
import { buildAffineFromEdits, normalizeTransformEdits } from '$lib/utils/editor';
import { AssetEditAction, MirrorAxis } from '@immich/sdk';
type NormalizedParameters = {
rotation: number;
mirrorHorizontal: boolean;
mirrorVertical: boolean;
};
function normalizedToEdits(params: NormalizedParameters): EditActions {
const edits: EditActions = [];
if (params.mirrorHorizontal) {
edits.push({
action: AssetEditAction.Mirror,
parameters: { axis: MirrorAxis.Horizontal },
});
}
if (params.mirrorVertical) {
edits.push({
action: AssetEditAction.Mirror,
parameters: { axis: MirrorAxis.Vertical },
});
}
if (params.rotation !== 0) {
edits.push({
action: AssetEditAction.Rotate,
parameters: { angle: params.rotation },
});
}
return edits;
}
function compareEditAffines(editsA: EditActions, editsB: EditActions): boolean {
const normA = buildAffineFromEdits(editsA);
const normB = buildAffineFromEdits(editsB);
return (
Math.abs(normA.a - normB.a) < 0.0001 &&
Math.abs(normA.b - normB.b) < 0.0001 &&
Math.abs(normA.c - normB.c) < 0.0001 &&
Math.abs(normA.d - normB.d) < 0.0001
);
}
describe('edit normalization', () => {
it('should handle no edits', () => {
const edits: EditActions = [];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle a single 90° rotation', () => {
const edits: EditActions = [{ action: AssetEditAction.Rotate, parameters: { angle: 90 } }];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle a single 180° rotation', () => {
const edits: EditActions = [{ action: AssetEditAction.Rotate, parameters: { angle: 180 } }];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle a single 270° rotation', () => {
const edits: EditActions = [{ action: AssetEditAction.Rotate, parameters: { angle: 270 } }];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle a single horizontal mirror', () => {
const edits: EditActions = [{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle a single vertical mirror', () => {
const edits: EditActions = [{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } }];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 90° rotation + horizontal mirror', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 90° rotation + vertical mirror', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 90° rotation + both mirrors', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 180° rotation + horizontal mirror', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 180 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 180° rotation + vertical mirror', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 180 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 180° rotation + both mirrors', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 180 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 270° rotation + horizontal mirror', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 270 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 270° rotation + vertical mirror', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 270 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle 270° rotation + both mirrors', () => {
const edits: EditActions = [
{ action: AssetEditAction.Rotate, parameters: { angle: 270 } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle horizontal mirror + 90° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle horizontal mirror + 180° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Rotate, parameters: { angle: 180 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle horizontal mirror + 270° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Rotate, parameters: { angle: 270 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle vertical mirror + 90° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle vertical mirror + 180° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
{ action: AssetEditAction.Rotate, parameters: { angle: 180 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle vertical mirror + 270° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
{ action: AssetEditAction.Rotate, parameters: { angle: 270 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle both mirrors + 90° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle both mirrors + 180° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
{ action: AssetEditAction.Rotate, parameters: { angle: 180 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
it('should handle both mirrors + 270° rotation', () => {
const edits: EditActions = [
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } },
{ action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } },
{ action: AssetEditAction.Rotate, parameters: { angle: 270 } },
];
const result = normalizeTransformEdits(edits);
const normalizedEdits = normalizedToEdits(result);
expect(compareEditAffines(normalizedEdits, edits)).toBe(true);
});
});

View File

@@ -9,24 +9,7 @@ export function normalizeTransformEdits(edits: EditActions): {
} {
// construct an affine matrix from the edits
// this is the same approach used in the backend to combine multiple transforms
const matrix = compose(
...edits.map((edit) => {
switch (edit.action) {
case 'rotate': {
const parameters = edit.parameters as RotateParameters;
const angleInRadians = (-parameters.angle * Math.PI) / 180;
return rotate(angleInRadians);
}
case 'mirror': {
const parameters = edit.parameters as MirrorParameters;
return parameters.axis === 'horizontal' ? flipY() : flipX();
}
default: {
return identity();
}
}
}),
);
const matrix = buildAffineFromEdits(edits);
let rotation = 0;
let mirrorH = false;
@@ -57,3 +40,25 @@ export function normalizeTransformEdits(edits: EditActions): {
mirrorVertical: mirrorV,
};
}
export function buildAffineFromEdits(edits: EditActions) {
return compose(
identity(),
...edits.map((edit) => {
switch (edit.action) {
case 'rotate': {
const parameters = edit.parameters as RotateParameters;
const angleInRadians = (-parameters.angle * Math.PI) / 180;
return rotate(angleInRadians);
}
case 'mirror': {
const parameters = edit.parameters as MirrorParameters;
return parameters.axis === 'horizontal' ? flipY() : flipX();
}
default: {
return identity();
}
}
}),
);
}