TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { describe, expect, it } from 'vitest';2import { PRESETS, csvCell, isPlausibleChange, parseScreenerParams, toCsv } from './screener';34describe('parseScreenerParams', () => {5 it('reads percent inputs as fractions and applies sort defaults', () => {6 const f = parseScreenerParams({ c30min: '10', spmax: '-15', ddmax: '-30', conf: '75', min: '1,000', size: '500' });7 expect(f.change30dMin).toBeCloseTo(0.1);8 expect(f.spreadMax).toBeCloseTo(-0.15);9 expect(f.drawdownMax).toBeCloseTo(-0.3);10 expect(f.confidenceMin).toBe(0.75); // "75" → 0.7511 expect(f.rivMin).toBe(1000);12 expect(f.pageSize).toBe(100); // capped13 expect(f.sort).toBe('riv');14 expect(f.dir).toBe('desc');15 });16 it('spread and drawdown sort ascending by default (most below first)', () => {17 expect(parseScreenerParams({ sort: 'spread' }).dir).toBe('asc');18 expect(parseScreenerParams({ sort: 'drawdown' }).dir).toBe('asc');19 expect(parseScreenerParams({ sort: 'spread', dir: 'desc' }).dir).toBe('desc');20 expect(parseScreenerParams({ sort: 'nonsense' }).sort).toBe('riv');21 });22 it('applies preset filters without overriding explicit values', () => {23 const f = parseScreenerParams({ preset: 'underpriced' });24 expect(f.sort).toBe('opportunity');25 expect(f.dir).toBe('asc');26 expect(f.spreadMax).toBe(-0.1);27 expect(f.confidenceMin).toBe(0.5);28 const g = parseScreenerParams({ preset: 'underpriced', conf: '0.75', sort: 'liquidity' });29 expect(g.confidenceMin).toBe(0.75);30 expect(g.sort).toBe('liquidity');31 expect(g.dir).toBe('desc');32 });33 it('every preset has a valid sort and a description', () => {34 for (const p of PRESETS) {35 expect(p.description.length).toBeGreaterThan(10);36 expect(parseScreenerParams({ preset: p.id }).sort).toBe(p.filters.sort);37 }38 expect(PRESETS.map((p) => p.id)).not.toContain('supply'); // not derivable honestly yet, see PRESETS comment39 });40});4142describe('plausibility', () => {43 it('holds back artefact-sized moves', () => {44 expect(isPlausibleChange(0.42)).toBe(true);45 expect(isPlausibleChange(-0.99)).toBe(true);46 expect(isPlausibleChange(85.17)).toBe(false); // the −8,516 % class of value47 expect(isPlausibleChange(null)).toBe(false);48 });49});5051describe('csv', () => {52 it('escapes quotes, commas and newlines', () => {53 expect(csvCell('Charizard, 1st Ed "Holo"')).toBe('"Charizard, 1st Ed ""Holo"""');54 expect(csvCell(null)).toBe('');55 expect(csvCell(12.5)).toBe('12.5');56 expect(csvCell(new Date('2026-09-11T00:00:00Z'))).toBe('2026-09-11T00:00:00.000Z');57 });58 it('writes comment lines, a header and CRLF rows', () => {59 const out = toCsv(['a', 'b'], [[1, 'x,y'], [null, 'z']], ['as_of=now']);60 expect(out).toBe('# as_of=now\r\na,b\r\n1,"x,y"\r\n,z\r\n');61 });62});63