sciagent code + Gitea Actions CI/CD
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Thinh Lam
2026-06-30 09:38:30 +07:00
commit 688fac73e9
1167 changed files with 158244 additions and 0 deletions
@@ -0,0 +1,96 @@
import { describe, it, expect } from 'vitest';
import {
clampFrame,
naturalSortFiles,
naturalSortBy,
IMAGE_QUAD_LABELS,
frameLabel,
} from './imageSequenceModel';
// A minimal File stand-in — we only ever read `.name`. Avoids relying on the DOM
// File constructor under the `node` vitest env.
function mkFile(name: string): File {
return { name } as File;
}
describe('clampFrame', () => {
it('returns an in-range index unchanged', () => {
expect(clampFrame(0, 10)).toBe(0);
expect(clampFrame(4, 10)).toBe(4);
expect(clampFrame(9, 10)).toBe(9);
});
it('clamps an over-range index down to count - 1', () => {
expect(clampFrame(15, 10)).toBe(9);
expect(clampFrame(10, 10)).toBe(9);
});
it('clamps a negative index up to 0', () => {
expect(clampFrame(-3, 10)).toBe(0);
});
it('returns 0 for a NaN / non-finite index', () => {
expect(clampFrame(NaN, 10)).toBe(0);
// Infinity is non-finite → 0 per the contract (not the max index).
expect(clampFrame(Infinity, 10)).toBe(0);
});
it('returns 0 for an empty sequence (count 0)', () => {
expect(clampFrame(0, 0)).toBe(0);
expect(clampFrame(3, 0)).toBe(0);
});
it('floors a fractional index', () => {
expect(clampFrame(2.9, 10)).toBe(2);
});
});
describe('naturalSortFiles', () => {
it('orders frame names numerically (frame2 < frame10)', () => {
const sorted = naturalSortFiles([
mkFile('f10.png'),
mkFile('f2.png'),
mkFile('f1.png'),
]);
expect(sorted.map((f) => f.name)).toEqual(['f1.png', 'f2.png', 'f10.png']);
});
it('returns a new array and does not mutate the input', () => {
const input = [mkFile('b.png'), mkFile('a.png')];
const sorted = naturalSortFiles(input);
expect(sorted).not.toBe(input);
expect(input.map((f) => f.name)).toEqual(['b.png', 'a.png']);
});
});
describe('naturalSortBy', () => {
it('sorts by a string key with numeric awareness', () => {
expect(naturalSortBy(['x10', 'x2', 'x1'], (s) => s)).toEqual(['x1', 'x2', 'x10']);
});
});
describe('IMAGE_QUAD_LABELS', () => {
it('carries the Vietnamese pane labels with diacritics', () => {
expect(IMAGE_QUAD_LABELS[0]).toBe('Ảnh gốc');
expect(IMAGE_QUAD_LABELS[1]).toBe('Bản đồ độ sâu');
expect(IMAGE_QUAD_LABELS[2]).toBe('Phủ phân vùng');
expect(IMAGE_QUAD_LABELS[3]).toBe('Tái tạo 3D');
});
});
describe('frameLabel', () => {
it('returns "khung 0/0" for an empty sequence', () => {
expect(frameLabel(0, 0)).toBe('khung 0/0');
});
it('formats a 1-based counter', () => {
expect(frameLabel(4, 10)).toBe('khung 5/10');
expect(frameLabel(0, 10)).toBe('khung 1/10');
});
it('clamps the index inside the counter', () => {
expect(frameLabel(99, 10)).toBe('khung 10/10');
expect(frameLabel(-5, 10)).toBe('khung 1/10');
});
});