import { describe, expect, it, vi } from 'vitest'; import { type CurrentWeather, type Options } from 'openweather-api-node'; import { Sequencer } from '../src/sequencer.js'; import type { Config } from '../src/index.js'; const dummyWeather: Options = { key: 'dummy' }; vi.mock('openweather-api-node', () => { return { OpenWeatherAPI: vi.fn().mockImplementation((_) => { return { getToday: vi.fn(() => { return { "lat": 43.0748, "lon": -89.3838, "dt": "2025-08-29T06:31:05.000Z", "dtRaw": 1756449065, "timezoneOffset": -18000, "astronomical": { "sunrise": "2025-08-29T11:19:05.000Z", "sunriseRaw": 1756466345, "sunset": "2025-08-30T00:38:08.000Z", "sunsetRaw": 1756514288 }, "weather": { "temp": { "cur": 55.85, "min": 52.99, "max": 58.01 }, "feelsLike": { "cur": 55.31 }, "pressure": 1022, "humidity": 89, "clouds": 0, "visibility": 10000, "wind": { "deg": 140, "speed": 5.75 }, "rain": 0, "snow": 0, "conditionId": 800, "main": "Clear", "description": "clear sky", "icon": { "url": "http://openweathermap.org/img/wn/01n@2x.png", "raw": "01n" } } } }) } }) } }); describe('sequencer', () => { it('can generate a list from a static config', async () => { expect(await Sequencer({ programs: [['sequence 1', 'segment 1', 'segment 2']], segments: { 'segment 1': ['sequence 1'], 'segment 2': ['sequence 2'] }, sequences: { 'sequence 1': { 'tracks': [ 'seq1.flac' ] }, 'sequence 2': { 'tracks': [ 'seq2.flac' ] } }, voices: {}, weather: dummyWeather })).to.include.ordered.members(['seq1.flac', 'seq1.flac', 'seq2.flac']); }); it('can include tracks conditionally', async () => { expect(await Sequencer({ programs: [['segment 1', 'sequence 1', 'segment 2', 'sequence 2']], segments: { 'segment 1': ['sequence 1'], 'segment 2': ['sequence 2'] }, sequences: { 'sequence 1': { 'conditions': ['1 = 1', '1.1 > 1', '2 >= 1', '1 < 2', '1 <= 1', '-1 != 0', undefined], 'tracks': [ 'seq1.flac' ] }, 'sequence 2': { 'conditions': ['weather.lat = -500'], 'tracks': [ 'seq2.flac' ] } }, voices: {}, weather: dummyWeather })).to.be.ordered.members(['seq1.flac', 'seq1.flac']); }); it('throws an error on invalid conditions', async () => { await expect(Sequencer({ programs: [['sequence 1']], segments: { }, sequences: { 'sequence 1': { 'conditions': ['100'], 'tracks': [ 'seq1.flac' ] } }, voices: {}, weather: dummyWeather })).rejects.toThrow(/not in the correct format/); await expect(Sequencer({ programs: [['sequence 1']], segments: { }, sequences: { 'sequence 1': { 'conditions': ['1 ~ 2'], 'tracks': [ 'seq1.flac' ] } }, voices: {}, weather: dummyWeather })).rejects.toThrow(/Unsupported relational operator/); }); it('can stringify conditions', async () => { expect(await Sequencer({ programs: [['sequence 1']], segments: { }, sequences: { 'sequence 1': { 'conditions': ['weather.feelsLike = {"cur":55.31}'], 'tracks': [ 'seq1.flac' ] } }, voices: {}, weather: dummyWeather })).to.be.ordered.members(['seq1.flac']); }); it('can parse voice macros', async () => { expect(await Sequencer({ programs: [['sequence 1']], segments: { }, sequences: { 'sequence 1': { 'tracks': [ '%alice weather.temp.max' ] } }, voices: { "alice": { "directory": "alice/", "extension": "flac" } }, weather: dummyWeather })).to.be.ordered.members([ 'alice/fifty.flac', 'alice/eight.flac', 'alice/point.flac', 'alice/zero.flac', 'alice/one.flac' ]); }); });