1import { describe, expect, it } from "vitest";
2
3import { cosineSimilarity, StubEmbeddingProvider, stubEmbed, STUB_EMBEDDING_DIMENSION } from "./embeddings.js";
4
5describe("stubEmbed", () => {
6 it("is deterministic: same text → byte-identical vector", () => {
7 expect(stubEmbed("hello world")).toEqual(stubEmbed("hello world"));
8 });
9
10 it("is dimension-exact and L2-normalized", () => {
11 const v = stubEmbed("the quick brown fox jumps over the lazy dog");
12 expect(v).toHaveLength(STUB_EMBEDDING_DIMENSION);
13 const norm = Math.sqrt(v.reduce((acc, x) => acc + x * x, 0));
14 expect(norm).toBeCloseTo(1, 10);
15 });
16
17 it("respects a custom dimension", () => {
18 expect(stubEmbed("abc", 16)).toHaveLength(16);
19 });
20
21 it("returns a zero vector for empty text", () => {
22 const v = stubEmbed(" ");
23 expect(v.every((x) => x === 0)).toBe(true);
24 });
25
26 it("case- and whitespace-insensitive at the surface", () => {
27 expect(stubEmbed("Hello World")).toEqual(stubEmbed("hello world"));
28 });
29
30 it("similar texts score higher than dissimilar ones", () => {
31 const base = stubEmbed("quarterly revenue forecast for the sales team");
32 const near = stubEmbed("quarterly revenue forecast targets for sales");
33 const far = stubEmbed("recipe for chocolate chip cookies with butter");
34 expect(cosineSimilarity(base, near)).toBeGreaterThan(cosineSimilarity(base, far));
35 });
36});
37
38describe("StubEmbeddingProvider", () => {
39 it("exposes the contract (model, dimension, embed)", async () => {
40 const provider = new StubEmbeddingProvider();
41 expect(provider.model).toBe("stub-v1");
42 expect(provider.dimension).toBe(STUB_EMBEDDING_DIMENSION);
43 const vectors = await provider.embed(["alpha", "beta", "alpha"]);
44 expect(vectors).toHaveLength(3);
45 expect(vectors[0]).toEqual(vectors[2]);
46 expect(vectors[0]).not.toEqual(vectors[1]);
47 });
48});
49
50describe("cosineSimilarity", () => {
51 it("is 1 for identical unit vectors and 0 for orthogonal", () => {
52 expect(cosineSimilarity([1, 0], [1, 0])).toBeCloseTo(1, 12);
53 expect(cosineSimilarity([1, 0], [0, 1])).toBeCloseTo(0, 12);
54 });
55
56 it("returns 0 for zero vectors (no NaN)", () => {
57 expect(cosineSimilarity([0, 0], [1, 1])).toBe(0);
58 });
59
60 it("throws on dimension mismatch (the §7.3 structural guard)", () => {
61 expect(() => cosineSimilarity([1], [1, 2])).toThrow("dimension mismatch");
62 });
63});
64