Skip to content

Commit a2f2c28

Browse files
fix(jest): add mock for @ruvector/nervous-system-wasm ESM module
- Create mock at __mocks__/@ruvector/nervous-system-wasm.ts - Add moduleNameMapper entry in jest.config.js - Update .gitignore to allow @ruvector mock directory - Fixes "Unexpected token 'export'" error in CI tests The mock provides stub implementations of all WASM classes (Hypervector, HdcMemory, BTSPLayer, GlobalWorkspace, etc.) for Jest tests while the real WASM module is used in production. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 7fa1a65 commit a2f2c28

File tree

3 files changed

+374
-2
lines changed

3 files changed

+374
-2
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,11 @@ data/
130130
test-projects/
131131

132132

133-
# Mock files - allow agentdb mock for CI
133+
# Mock files - allow ESM module mocks for CI
134134
__mocks__/*
135135
!__mocks__/agentdb.ts
136+
!__mocks__/@ruvector/
137+
!__mocks__/@ruvector/**
136138

137139
# Test artifacts that should not be committed
138140
test-learning-persistence
Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
/**
2+
* Mock for @ruvector/nervous-system-wasm ESM module
3+
* Prevents "Unexpected token 'export'" errors in Jest
4+
*
5+
* This mock provides stub implementations of all WASM classes and functions
6+
* used by the nervous system integration.
7+
*/
8+
9+
// Mock Hypervector class
10+
export class Hypervector {
11+
private data: Float32Array;
12+
private dim: number;
13+
14+
constructor(dimension: number = 10000) {
15+
this.dim = dimension;
16+
this.data = new Float32Array(dimension);
17+
}
18+
19+
static random(dimension: number = 10000): Hypervector {
20+
const hv = new Hypervector(dimension);
21+
for (let i = 0; i < dimension; i++) {
22+
hv.data[i] = Math.random() > 0.5 ? 1 : -1;
23+
}
24+
return hv;
25+
}
26+
27+
static zeros(dimension: number = 10000): Hypervector {
28+
return new Hypervector(dimension);
29+
}
30+
31+
bind(other: Hypervector): Hypervector {
32+
const result = new Hypervector(this.dim);
33+
for (let i = 0; i < this.dim; i++) {
34+
result.data[i] = this.data[i] * other.data[i];
35+
}
36+
return result;
37+
}
38+
39+
bundle(others: Hypervector[]): Hypervector {
40+
const result = new Hypervector(this.dim);
41+
result.data.set(this.data);
42+
for (const other of others) {
43+
for (let i = 0; i < this.dim; i++) {
44+
result.data[i] += other.data[i];
45+
}
46+
}
47+
return result;
48+
}
49+
50+
permute(shifts: number = 1): Hypervector {
51+
const result = new Hypervector(this.dim);
52+
for (let i = 0; i < this.dim; i++) {
53+
result.data[(i + shifts) % this.dim] = this.data[i];
54+
}
55+
return result;
56+
}
57+
58+
similarity(other: Hypervector): number {
59+
let dot = 0;
60+
let normA = 0;
61+
let normB = 0;
62+
for (let i = 0; i < this.dim; i++) {
63+
dot += this.data[i] * other.data[i];
64+
normA += this.data[i] * this.data[i];
65+
normB += other.data[i] * other.data[i];
66+
}
67+
return dot / (Math.sqrt(normA) * Math.sqrt(normB) || 1);
68+
}
69+
70+
toArray(): Float32Array {
71+
return this.data;
72+
}
73+
74+
dimension(): number {
75+
return this.dim;
76+
}
77+
}
78+
79+
// Mock HdcMemory class
80+
export class HdcMemory {
81+
private memories: Map<string, Hypervector> = new Map();
82+
private dimension: number;
83+
84+
constructor(dimension: number = 10000) {
85+
this.dimension = dimension;
86+
}
87+
88+
store(key: string, value: Hypervector): void {
89+
this.memories.set(key, value);
90+
}
91+
92+
retrieve(key: string): Hypervector | null {
93+
return this.memories.get(key) || null;
94+
}
95+
96+
query(queryVector: Hypervector, topK: number = 5): Array<{ key: string; similarity: number }> {
97+
const results: Array<{ key: string; similarity: number }> = [];
98+
for (const [key, stored] of this.memories) {
99+
results.push({ key, similarity: queryVector.similarity(stored) });
100+
}
101+
return results.sort((a, b) => b.similarity - a.similarity).slice(0, topK);
102+
}
103+
104+
size(): number {
105+
return this.memories.size;
106+
}
107+
108+
clear(): void {
109+
this.memories.clear();
110+
}
111+
}
112+
113+
// Mock BTSPSynapse class
114+
export class BTSPSynapse {
115+
private weight: number = 0;
116+
private eligibilityTrace: number = 0;
117+
118+
constructor() {}
119+
120+
update(preActivity: number, postActivity: number, reward: number): void {
121+
this.eligibilityTrace = preActivity * postActivity;
122+
this.weight += 0.01 * this.eligibilityTrace * reward;
123+
}
124+
125+
getWeight(): number {
126+
return this.weight;
127+
}
128+
129+
setWeight(w: number): void {
130+
this.weight = w;
131+
}
132+
}
133+
134+
// Mock BTSPLayer class
135+
export class BTSPLayer {
136+
private synapses: BTSPSynapse[][] = [];
137+
private inputSize: number;
138+
private outputSize: number;
139+
140+
constructor(inputSize: number, outputSize: number) {
141+
this.inputSize = inputSize;
142+
this.outputSize = outputSize;
143+
for (let i = 0; i < outputSize; i++) {
144+
this.synapses[i] = [];
145+
for (let j = 0; j < inputSize; j++) {
146+
this.synapses[i][j] = new BTSPSynapse();
147+
}
148+
}
149+
}
150+
151+
forward(input: Float32Array): Float32Array {
152+
const output = new Float32Array(this.outputSize);
153+
for (let i = 0; i < this.outputSize; i++) {
154+
let sum = 0;
155+
for (let j = 0; j < this.inputSize; j++) {
156+
sum += input[j] * this.synapses[i][j].getWeight();
157+
}
158+
output[i] = Math.tanh(sum);
159+
}
160+
return output;
161+
}
162+
163+
learn(input: Float32Array, target: Float32Array, reward: number): void {
164+
const output = this.forward(input);
165+
for (let i = 0; i < this.outputSize; i++) {
166+
for (let j = 0; j < this.inputSize; j++) {
167+
this.synapses[i][j].update(input[j], target[i], reward);
168+
}
169+
}
170+
}
171+
}
172+
173+
// Mock BTSPAssociativeMemory class
174+
export class BTSPAssociativeMemory {
175+
private patterns: Map<string, Float32Array> = new Map();
176+
private layer: BTSPLayer;
177+
178+
constructor(inputDim: number = 384, outputDim: number = 128) {
179+
this.layer = new BTSPLayer(inputDim, outputDim);
180+
}
181+
182+
store(id: string, pattern: Float32Array): void {
183+
this.patterns.set(id, pattern);
184+
}
185+
186+
recall(query: Float32Array, topK: number = 5): Array<{ id: string; similarity: number }> {
187+
const results: Array<{ id: string; similarity: number }> = [];
188+
for (const [id, pattern] of this.patterns) {
189+
let similarity = 0;
190+
let normA = 0;
191+
let normB = 0;
192+
for (let i = 0; i < pattern.length; i++) {
193+
similarity += pattern[i] * query[i];
194+
normA += pattern[i] * pattern[i];
195+
normB += query[i] * query[i];
196+
}
197+
similarity = similarity / (Math.sqrt(normA) * Math.sqrt(normB) || 1);
198+
results.push({ id, similarity });
199+
}
200+
return results.sort((a, b) => b.similarity - a.similarity).slice(0, topK);
201+
}
202+
203+
learn(pattern: Float32Array, reward: number): void {
204+
const target = this.layer.forward(pattern);
205+
this.layer.learn(pattern, target, reward);
206+
}
207+
208+
size(): number {
209+
return this.patterns.size;
210+
}
211+
}
212+
213+
// Mock WTALayer (Winner-Take-All)
214+
export class WTALayer {
215+
private size: number;
216+
217+
constructor(size: number) {
218+
this.size = size;
219+
}
220+
221+
compute(input: Float32Array): Float32Array {
222+
const output = new Float32Array(this.size);
223+
let maxIdx = 0;
224+
let maxVal = input[0];
225+
for (let i = 1; i < input.length; i++) {
226+
if (input[i] > maxVal) {
227+
maxVal = input[i];
228+
maxIdx = i;
229+
}
230+
}
231+
output[maxIdx] = 1;
232+
return output;
233+
}
234+
}
235+
236+
// Mock KWTALayer (K-Winners-Take-All)
237+
export class KWTALayer {
238+
private size: number;
239+
private k: number;
240+
241+
constructor(size: number, k: number) {
242+
this.size = size;
243+
this.k = k;
244+
}
245+
246+
compute(input: Float32Array): Float32Array {
247+
const output = new Float32Array(this.size);
248+
const indexed = Array.from(input).map((val, idx) => ({ val, idx }));
249+
indexed.sort((a, b) => b.val - a.val);
250+
for (let i = 0; i < this.k && i < indexed.length; i++) {
251+
output[indexed[i].idx] = 1;
252+
}
253+
return output;
254+
}
255+
}
256+
257+
// Mock WorkspaceItem
258+
export class WorkspaceItem {
259+
public id: string;
260+
public content: any;
261+
public activation: number;
262+
public timestamp: number;
263+
264+
constructor(id: string, content: any, activation: number = 1.0) {
265+
this.id = id;
266+
this.content = content;
267+
this.activation = activation;
268+
this.timestamp = Date.now();
269+
}
270+
}
271+
272+
// Mock GlobalWorkspace (Global Workspace Theory)
273+
export class GlobalWorkspace {
274+
private items: Map<string, WorkspaceItem> = new Map();
275+
private capacity: number;
276+
private threshold: number;
277+
278+
constructor(capacity: number = 7, threshold: number = 0.5) {
279+
this.capacity = capacity;
280+
this.threshold = threshold;
281+
}
282+
283+
broadcast(item: WorkspaceItem): void {
284+
this.items.set(item.id, item);
285+
this.enforceCapacity();
286+
}
287+
288+
getActive(): WorkspaceItem[] {
289+
return Array.from(this.items.values())
290+
.filter(item => item.activation >= this.threshold)
291+
.sort((a, b) => b.activation - a.activation);
292+
}
293+
294+
compete(): WorkspaceItem | null {
295+
const active = this.getActive();
296+
return active.length > 0 ? active[0] : null;
297+
}
298+
299+
decay(rate: number = 0.1): void {
300+
for (const item of this.items.values()) {
301+
item.activation *= (1 - rate);
302+
}
303+
// Remove items below threshold
304+
for (const [id, item] of this.items) {
305+
if (item.activation < 0.01) {
306+
this.items.delete(id);
307+
}
308+
}
309+
}
310+
311+
private enforceCapacity(): void {
312+
if (this.items.size > this.capacity) {
313+
const sorted = Array.from(this.items.entries())
314+
.sort((a, b) => b[1].activation - a[1].activation);
315+
const toRemove = sorted.slice(this.capacity);
316+
for (const [id] of toRemove) {
317+
this.items.delete(id);
318+
}
319+
}
320+
}
321+
322+
size(): number {
323+
return this.items.size;
324+
}
325+
326+
clear(): void {
327+
this.items.clear();
328+
}
329+
}
330+
331+
// Mock utility functions
332+
export function version(): string {
333+
return '0.1.0-mock';
334+
}
335+
336+
export function available_mechanisms(): Array<[string, string]> {
337+
return [
338+
['btsp', 'Behavioral Time-Scale Plasticity'],
339+
['hdc', 'Hyperdimensional Computing'],
340+
['gwt', 'Global Workspace Theory'],
341+
['wta', 'Winner-Take-All'],
342+
['kwta', 'K-Winners-Take-All']
343+
];
344+
}
345+
346+
export function performance_targets(): Array<[string, string]> {
347+
return [
348+
['vector_ops', '1M ops/sec'],
349+
['memory_recall', '<1ms'],
350+
['pattern_match', '<5ms']
351+
];
352+
}
353+
354+
export function biological_references(): Array<[string, string]> {
355+
return [
356+
['btsp', 'Bittner et al. 2017 - Behavioral time scale synaptic plasticity'],
357+
['hdc', 'Kanerva 2009 - Hyperdimensional computing'],
358+
['gwt', 'Baars 1988 - Global Workspace Theory']
359+
];
360+
}
361+
362+
// Mock init function (default export)
363+
export default async function init(wasmBytes?: Uint8Array | ArrayBuffer): Promise<void> {
364+
// Mock initialization - does nothing but resolves
365+
return Promise.resolve();
366+
}
367+
368+
// Named export for init as well
369+
export { init };

jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ module.exports = {
6161
'^@streaming/(.*)$': '<rootDir>/src/streaming/$1',
6262
'^@routing/(.*)$': '<rootDir>/src/core/routing/$1',
6363
'^@memory/(.*)$': '<rootDir>/src/memory/$1',
64-
// Mock agentdb ESM module to avoid "Unexpected token 'export'" errors
64+
// Mock ESM modules to avoid "Unexpected token 'export'" errors
6565
'^agentdb$': '<rootDir>/__mocks__/agentdb.ts',
66+
'^@ruvector/nervous-system-wasm$': '<rootDir>/__mocks__/@ruvector/nervous-system-wasm.ts',
6667
// Map .js imports to .ts source files for Jest
6768
'^(\\.{1,2}/.*)\\.js$': '$1'
6869
},

0 commit comments

Comments
 (0)