SkateSpots/src/lib/translation.test.ts
SkateSpots e85b9aacb6 feat(chat): all-language on-device translation (M2M100 + Basque Opus)
- M2M100 418M (MIT, 617 MB q8) covers 23 app languages; Basque uses converted
  Opus eu-en/en-eu q8 packs; all self-hosted under relay.skate-spots.org/models.
- Worker uses M2M100 directly and pivots Basque through English; UI targets all
  languages; old pilot restriction/note removed; memory-only + guard unchanged.
- Verified en<->es, ca<->gl, hi<->en from the host. Basque packs are converted
  and hosted; the final MarianTokenizer/ONNX runtime check is still pending.
- 138 tests, 24 locales, build and smoke green. SpotChat deliberately skipped.
2026-10-27 13:06:04 +07:00

68 lines
2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import { clearTranslationMemory, isTranslationPairSupported, translateText } from './translation';
class FakeWorker {
onmessage: ((event: MessageEvent) => void) | null = null;
onerror: (() => void) | null = null;
postMessage(request: { id: number; text: string }) {
queueMicrotask(() => {
this.onmessage?.({
data: { type: 'result', id: request.id, text: `translated:${request.text}` },
} as MessageEvent);
});
}
terminate() {}
}
describe('translateText', () => {
afterEach(() => {
vi.unstubAllGlobals();
clearTranslationMemory();
});
it('keeps the message local and never calls fetch', async () => {
const fetchSpy = vi.fn();
const received: string[] = [];
class RecordingWorker extends FakeWorker {
postMessage(request: { id: number; text: string }) {
received.push(request.text);
super.postMessage(request);
}
}
vi.stubGlobal('fetch', fetchSpy);
vi.stubGlobal('Worker', RecordingWorker);
clearTranslationMemory();
const result = await translateText({
id: 'message-1',
text: 'SENTINEL-MESSAGE-TEXT',
source: 'en',
target: 'es',
});
expect(result).toBe('translated:SENTINEL-MESSAGE-TEXT');
expect(received).toEqual(['SENTINEL-MESSAGE-TEXT']);
expect(fetchSpy).not.toHaveBeenCalled();
});
it('returns the input when source and target match', async () => {
const result = await translateText({
id: 'message-2',
text: 'same language',
source: 'en',
target: 'en',
});
expect(result).toBe('same language');
});
it('supports all app language pairs (M2M100 + Basque Opus)', () => {
expect(isTranslationPairSupported('ca', 'en')).toBe(true);
expect(isTranslationPairSupported('eu', 'en')).toBe(true);
expect(isTranslationPairSupported('hi', 'es')).toBe(true);
expect(isTranslationPairSupported('en', 'en')).toBe(false);
});
});