/* eslint-env jest */ import type { ISeoPerfPoint, ISeoPerformance, SeoPerfJobState, } from '../../../service/seo/seo.interface'; import { MEASURE_POLL_MS, dayOf, eventLabel, eventsByDay, formatBytes, formatMs, trendOf, trendTone, waitForMeasurement, } from '../performance'; const point = (at: string, serverMs: number | null): ISeoPerfPoint => ({ at, server_ms: serverMs, cached_ms: null, php_ms: null, queries: null, html_bytes: null, db_bytes: null, autoload_bytes: null, }); describe('times and sizes in words', () => { it('says milliseconds, then seconds', () => { expect(formatMs(412.4)).toBe('412 ms'); expect(formatMs(1640)).toBe('1.6 s'); expect(formatMs(12400)).toBe('12 s'); expect(formatMs(null)).toBe('—'); }); it('counts bytes in 1024s, as WordPress does', () => { expect(formatBytes(900)).toBe('900 B'); expect(formatBytes(361472)).toBe('353 KB'); expect(formatBytes(1.5 * 1024 * 1024)).toBe('1.5 MB'); expect(formatBytes(undefined)).toBe('—'); }); }); describe('how a number moved', () => { const now = new Date('2026-10-05T12:00:00Z').getTime(); const history = [ point('2026-09-01T03:00:00Z', 1000), point('2026-09-28T03:00:00Z', 800), point('2026-10-01T03:00:00Z', null), point('2026-10-05T03:00:00Z', 400), ]; it('compares the latest value with the first one of the window', () => { expect(trendOf(history, 'server_ms', 7, now)).toEqual({ from: 800, to: 400, change: -50, }); expect(trendOf(history, 'server_ms', 90, now)?.change).toBe(-60); }); it('says nothing without two values in the window', () => { expect(trendOf(history, 'server_ms', 1, now)).toBeNull(); expect(trendOf(history, 'db_bytes', 30, now)).toBeNull(); }); it('colours a tenth or more, less is better', () => { expect(trendTone(-50)).toBe('better'); expect(trendTone(4)).toBe('same'); expect(trendTone(25)).toBe('worse'); }); }); describe('the changes on the chart', () => { it('lands each change on the first measured day that carries it', () => { const days = ['2026-10-01', '2026-10-03', '2026-10-05']; const placed = eventsByDay( [ { at: '2026-10-02T09:00:00Z', type: 'plugin_updated', name: 'WooCommerce', version: '11.1.2', }, { at: '2026-10-03T01:00:00Z', type: 'plugin_activated', name: 'Elementor', version: '3.30', }, { at: '2026-10-06T01:00:00Z', type: 'core_updated', name: 'WordPress', version: '7.1', }, ], days ); expect(Object.keys(placed)).toEqual(['2026-10-03']); expect(placed['2026-10-03']).toHaveLength(2); expect(dayOf('2026-10-03T23:59:00Z')).toBe('2026-10-03'); }); it('words each kind of change', () => { expect( eventLabel({ at: '', type: 'plugin_updated', name: 'WooCommerce', version: '11.1.2', }) ).toBe('WooCommerce updated to 11.1.2'); expect( eventLabel({ at: '', type: 'theme_switched', name: 'Astra', version: '4.8', }) ).toBe('Theme switched to Astra'); expect( eventLabel({ at: '', type: 'core_updated', name: 'WordPress', version: '7.1', }) ).toBe('WordPress updated to 7.1'); }); }); describe('waiting for a measurement', () => { const performance = { latest: null } as unknown as ISeoPerformance; it('asks every few seconds until it is done', async () => { const states: SeoPerfJobState[] = [ { status: 'running', seconds: 4 }, { status: 'done', performance }, ]; const slept: number[] = []; const answer = await waitForMeasurement('abc', { ask: async () => states.shift() as SeoPerfJobState, sleep: async ms => { slept.push(ms); }, }); expect(answer).toBe(performance); expect(slept).toEqual([MEASURE_POLL_MS, MEASURE_POLL_MS]); }); it('throws a failed job’s error with its status', async () => { await expect( waitForMeasurement('abc', { ask: async () => ({ status: 'failed', error: { code: 'recomaze_seo_job_stopped', message: 'Stopped.', data: { status: 504 }, }, }), sleep: async () => undefined, }) ).rejects.toMatchObject({ message: 'Stopped.', data: { status: 504 } }); }); it('stops once the page is left', async () => { const ask = jest.fn(); await expect( waitForMeasurement('abc', { ask, cancelled: () => true, sleep: async () => undefined, }) ).rejects.toMatchObject({ code: 'cancelled' }); expect(ask).not.toHaveBeenCalled(); }); });