diff --git a/tests/3_signal_pipeline/test.js b/tests/3_signal_pipeline/test.js new file mode 100644 index 0000000..cea0627 --- /dev/null +++ b/tests/3_signal_pipeline/test.js @@ -0,0 +1,86 @@ +export default { + functionName: 'analyzeSignal', + prompt: `// Write an async JavaScript function 'analyzeSignal' that builds a signal processing pipeline chaining four libraries. +// - The function accepts a YAML string describing signal parameters and returns analysis results. +// - You MUST use dynamic import() to load ALL FOUR of these libraries from CDNs: +// 1. 'js-yaml' — to parse the YAML config string into a JS object. +// 2. 'mathjs' — to generate a composite sine wave signal using math.sin, math.pi, etc. +// 3. 'ndarray' and 'ndarray-fft' — to perform an in-place Fast Fourier Transform on the signal. +// 4. 'DOMPurify' — to sanitize the final HTML output string. +// +// YAML format: +// sampleRate: 64 +// duration: 1 +// components: +// - frequency: 5 +// amplitude: 1.0 +// - frequency: 12 +// amplitude: 0.5 +// +// Pipeline steps: +// 1. Parse the YAML string with js-yaml to extract config. +// 2. Generate a time-domain signal array of length (sampleRate * duration). +// For each sample index i, compute t = i / sampleRate, then sum over all components: +// signal[i] = sum( amplitude * math.sin(2 * math.pi * frequency * t) ) +// Use mathjs functions (math.sin, math.pi) for the computation. +// 3. Create two ndarray instances of length N (same as signal length): +// - 'real' initialized with the signal values +// - 'imag' initialized with all zeros +// Run ndarray-fft(1, real, imag) to compute the forward FFT in-place. +// 4. Compute the magnitude spectrum: for each bin k from 0 to N/2 (inclusive), +// magnitude[k] = math.sqrt(real.get(k)**2 + imag.get(k)**2) / (N/2) +// (Normalize by dividing by N/2 so that a pure sine of amplitude A gives a peak of ~A.) +// 5. Find the dominant frequencies: collect all bins where magnitude > 0.1, +// as objects { frequencyHz: k * sampleRate / N, magnitude: magnitude[k] }. +// Sort them by magnitude descending. Round frequencyHz to the nearest integer. +// Round magnitude to 2 decimal places. +// 6. Build an HTML table string: +// +// For each dominant frequency: +//
Frequency (Hz)Magnitude
{frequencyHz}{magnitude}
+// 7. Sanitize the HTML string using DOMPurify.sanitize(). +// 8. Return an object: { peaks: dominantFrequenciesArray, html: sanitizedHTMLString, signalLength: N } +// where dominantFrequenciesArray is the sorted array from step 5.`, + runTest: async (analyzeSignal) => { + const assert = { + strictEqual: (a, e, m) => { if (a !== e) throw new Error(m || `FAIL: ${a} !== ${e}`) }, + ok: (v, m) => { if (!v) throw new Error(m) }, + }; + + const yaml = `sampleRate: 64 +duration: 1 +components: + - frequency: 5 + amplitude: 1.0 + - frequency: 12 + amplitude: 0.5`; + + const result = await analyzeSignal(yaml); + + assert.ok(result && typeof result === 'object', 'Should return an object'); + assert.strictEqual(result.signalLength, 64, `signalLength should be 64, got ${result.signalLength}`); + + assert.ok(Array.isArray(result.peaks), 'peaks should be an array'); + assert.ok(result.peaks.length >= 2, `Should detect at least 2 peaks, got ${result.peaks.length}`); + + const freqs = result.peaks.map(p => p.frequencyHz); + assert.ok(freqs.includes(5), `Should detect 5 Hz peak, found: ${freqs}`); + assert.ok(freqs.includes(12), `Should detect 12 Hz peak, found: ${freqs}`); + + const peak5 = result.peaks.find(p => p.frequencyHz === 5); + const peak12 = result.peaks.find(p => p.frequencyHz === 12); + assert.ok(peak5.magnitude > 0.8 && peak5.magnitude < 1.2, `5 Hz magnitude should be ~1.0, got ${peak5.magnitude}`); + assert.ok(peak12.magnitude > 0.3 && peak12.magnitude < 0.7, `12 Hz magnitude should be ~0.5, got ${peak12.magnitude}`); + + assert.ok(result.peaks[0].magnitude >= result.peaks[1].magnitude, 'Peaks should be sorted by magnitude descending'); + + assert.ok(typeof result.html === 'string', 'html should be a string'); + assert.ok(result.html.includes(''), 'HTML should contain a table'); + assert.ok(result.html.includes('Frequency (Hz)'), 'HTML should contain header'); + assert.ok(result.html.includes('Magnitude'), 'HTML should contain Magnitude header'); + assert.ok(result.html.includes(''), 'HTML table should contain 5 Hz row'); + assert.ok(result.html.includes(''), 'HTML table should contain 12 Hz row'); + + assert.ok(!result.html.includes('
512