Procedurally generates a radio weather report
at v0.0.4 6.2 kB view raw
1import { describe, expect, it, vi } from 'vitest'; 2import { type Options } from 'openweather-api-node'; 3import { Sequencer } from '../src/sequencer.js'; 4 5const dummyWeather: Options = { key: 'dummy' }; 6 7vi.mock('openweather-api-node', () => { 8 return { 9 OpenWeatherAPI: vi.fn().mockImplementation((_) => { 10 return { 11 getToday: vi.fn(() => { 12 return { 13 "lat": 43.0748, 14 "lon": -89.3838, 15 "dt": "2025-08-29T06:31:05.000Z", 16 "dtRaw": 1756449065, 17 "timezoneOffset": -18000, 18 "astronomical": { 19 "sunrise": "2025-08-29T11:19:05.000Z", 20 "sunriseRaw": 1756466345, 21 "sunset": "2025-08-30T00:38:08.000Z", 22 "sunsetRaw": 1756514288 23 }, 24 "weather": { 25 "temp": { 26 "cur": 55.85, 27 "min": 52.99, 28 "max": 58.01 29 }, 30 "feelsLike": { 31 "cur": 55.31 32 }, 33 "pressure": 1022, 34 "humidity": 89, 35 "clouds": 0, 36 "visibility": 10000, 37 "wind": { 38 "deg": 140, 39 "speed": 5.75 40 }, 41 "rain": 0, 42 "snow": 0, 43 "conditionId": 800, 44 "main": "Clear", 45 "description": "clear sky", 46 "icon": { 47 "url": "http://openweathermap.org/img/wn/01n@2x.png", 48 "raw": "01n" 49 } 50 } 51 } 52 }) 53 } 54 }) 55 } 56}); 57 58describe('sequencer', () => { 59 it('can generate a list from a static config', async () => { 60 expect(await Sequencer({ 61 programs: [['sequence 1', 'segment 1', 'segment 2']], 62 segments: { 63 'segment 1': ['sequence 1'], 64 'segment 2': ['sequence 2'] 65 }, 66 sequences: { 67 'sequence 1': { 68 'tracks': [ 69 'seq1.flac' 70 ] 71 }, 72 'sequence 2': { 73 'tracks': [ 74 'seq2.flac' 75 ] 76 } 77 }, 78 voices: {}, 79 weather: dummyWeather 80 })).to.include.ordered.members(['seq1.flac', 'seq1.flac', 'seq2.flac']); 81 }); 82 83 it('can include tracks conditionally', async () => { 84 expect(await Sequencer({ 85 programs: [['segment 1', 'sequence 1', 'segment 2', 'sequence 2']], 86 segments: { 87 'segment 1': ['sequence 1'], 88 'segment 2': ['sequence 2'] 89 }, 90 sequences: { 91 'sequence 1': { 92 'conditions': ['1 = 1', '1.1 > 1', '2 >= 1', '1 < 2', '1 <= 1', '-1 != 0', undefined], 93 'tracks': [ 94 'seq1.flac' 95 ] 96 }, 97 'sequence 2': { 98 'conditions': ['weather.lat = -500'], 99 'tracks': [ 100 'seq2.flac' 101 ] 102 } 103 }, 104 voices: {}, 105 weather: dummyWeather 106 })).to.be.ordered.members(['seq1.flac', 'seq1.flac']); 107 }); 108 109 it('throws an error on invalid conditions', async () => { 110 await expect(Sequencer({ 111 programs: [['sequence 1']], 112 segments: { 113 }, 114 sequences: { 115 'sequence 1': { 116 'conditions': ['100'], 117 'tracks': [ 118 'seq1.flac' 119 ] 120 } 121 }, 122 voices: {}, 123 weather: dummyWeather 124 })).rejects.toThrow(/not in the correct format/); 125 126 await expect(Sequencer({ 127 programs: [['sequence 1']], 128 segments: { 129 }, 130 sequences: { 131 'sequence 1': { 132 'conditions': ['1 ~ 2'], 133 'tracks': [ 134 'seq1.flac' 135 ] 136 } 137 }, 138 voices: {}, 139 weather: dummyWeather 140 })).rejects.toThrow(/Unsupported relational operator/); 141 }); 142 143 it('can stringify conditions', async () => { 144 expect(await Sequencer({ 145 programs: [['sequence 1']], 146 segments: { 147 }, 148 sequences: { 149 'sequence 1': { 150 'conditions': ['weather.feelsLike = {"cur":55.31}'], 151 'tracks': [ 152 'seq1.flac' 153 ] 154 } 155 }, 156 voices: {}, 157 weather: dummyWeather 158 })).to.be.ordered.members(['seq1.flac']); 159 }); 160 161 it('can parse voice macros', async () => { 162 expect(await Sequencer({ 163 programs: [['sequence 1']], 164 segments: { 165 }, 166 sequences: { 167 'sequence 1': { 168 'tracks': [ 169 '%alice weather.temp.max' 170 ] 171 } 172 }, 173 voices: { 174 "alice": { 175 "directory": "alice/", 176 "extension": "flac" 177 } 178 }, 179 weather: dummyWeather 180 })).to.be.ordered.members([ 181 'alice/fifty.flac', 182 'alice/eight.flac', 183 'alice/point.flac', 184 'alice/zero.flac', 185 'alice/one.flac' 186 ]); 187 }); 188}); 189