exhaustive tests for sequencer function
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
"main": "distribution/index.js",
|
"main": "distribution/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node distribution/index.js",
|
"start": "node distribution/src/index.js",
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { OpenWeatherAPI, type CurrentWeather } from 'openweather-api-node';
|
import { OpenWeatherAPI, type DailyWeather } from 'openweather-api-node';
|
||||||
import { voiceLines } from './voice.js';
|
import { voiceLines } from './voice.js';
|
||||||
import type { Config } from './index.js';
|
import type { Config } from './index.js';
|
||||||
import type { Voice } from './voice.js';
|
import type { Voice } from './voice.js';
|
||||||
@@ -19,7 +19,7 @@ function selectOne<T>(arr: T[]): T {
|
|||||||
return arr[Math.floor(Math.random() * arr.length)];
|
return arr[Math.floor(Math.random() * arr.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSide(side: string, currentWeather: CurrentWeather) {
|
function resolveSide(side: string, currentWeather: DailyWeather) {
|
||||||
if (!side.startsWith('weather')) {
|
if (!side.startsWith('weather')) {
|
||||||
return side.includes('.') ? parseFloat(side) : parseInt(side);
|
return side.includes('.') ? parseFloat(side) : parseInt(side);
|
||||||
}
|
}
|
||||||
@@ -30,13 +30,23 @@ function resolveSide(side: string, currentWeather: CurrentWeather) {
|
|||||||
return typeof w === 'object' ? JSON.stringify(w) : w as (string | number);
|
return typeof w === 'object' ? JSON.stringify(w) : w as (string | number);
|
||||||
}
|
}
|
||||||
|
|
||||||
function conditionIsMet(condition: string | undefined, currentWeather: CurrentWeather): boolean {
|
function notNotANumber(something: number | string, defaultVal: string) {
|
||||||
|
if (typeof something === 'string' || !isNaN(something)) {
|
||||||
|
return something;
|
||||||
|
}
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function conditionIsMet(condition: string | undefined, currentWeather: DailyWeather): boolean {
|
||||||
if (typeof condition !== 'string') {
|
if (typeof condition !== 'string') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const [lhs, relational, rhs] = condition.split(' ');
|
const [lhs, relational, rhs] = condition.split(' ');
|
||||||
const lhsResolved = resolveSide(lhs, currentWeather);
|
if (lhs === undefined || relational === undefined || rhs === undefined) {
|
||||||
const rhsResolved = resolveSide(rhs, currentWeather);
|
throw new Error(`Condition "${condition}" is not in the correct format`);
|
||||||
|
}
|
||||||
|
const lhsResolved = notNotANumber(resolveSide(lhs, currentWeather), lhs);
|
||||||
|
const rhsResolved = notNotANumber(resolveSide(rhs, currentWeather), rhs);
|
||||||
switch (relational) {
|
switch (relational) {
|
||||||
case '=':
|
case '=':
|
||||||
case '==':
|
case '==':
|
||||||
@@ -56,7 +66,7 @@ function conditionIsMet(condition: string | undefined, currentWeather: CurrentWe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveMacro(str: string, currentWeather: CurrentWeather): string[] {
|
function resolveMacro(str: string, currentWeather: DailyWeather): string[] {
|
||||||
if (str.startsWith('%')) {
|
if (str.startsWith('%')) {
|
||||||
const [profile, subject] = str.substring(1).split(' ', 2);
|
const [profile, subject] = str.substring(1).split(' ', 2);
|
||||||
const voiceProfile: Voice = config.voices[profile];
|
const voiceProfile: Voice = config.voices[profile];
|
||||||
@@ -64,18 +74,15 @@ function resolveMacro(str: string, currentWeather: CurrentWeather): string[] {
|
|||||||
subject.split('.').forEach(t => resolvedSubject = resolvedSubject[t]);
|
subject.split('.').forEach(t => resolvedSubject = resolvedSubject[t]);
|
||||||
return voiceLines(voiceProfile, resolvedSubject);
|
return voiceLines(voiceProfile, resolvedSubject);
|
||||||
}
|
}
|
||||||
else if (str.startsWith('$')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return [str];
|
return [str];
|
||||||
}
|
}
|
||||||
|
|
||||||
function processSequence(sequence: Sequence, currentWeather: CurrentWeather): string[] {
|
function processSequence(sequence: Sequence, currentWeather: DailyWeather): string[] {
|
||||||
const tracks = sequence.tracks;
|
const tracks = sequence.tracks;
|
||||||
return tracks.map(t => resolveMacro(t, currentWeather)).flat().filter(t => t !== null);
|
return tracks.map(t => resolveMacro(t, currentWeather)).flat().filter(t => t !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processSegment(segment: SegmentName, currentWeather: CurrentWeather): string[] {
|
function processSegment(segment: SegmentName, currentWeather: DailyWeather): string[] {
|
||||||
if (!(segment in config.segments)) {
|
if (!(segment in config.segments)) {
|
||||||
return (config.sequences[segment].conditions || []).every(c => conditionIsMet(c, currentWeather)) ? processSequence(config.sequences[segment], currentWeather) : [];
|
return (config.sequences[segment].conditions || []).every(c => conditionIsMet(c, currentWeather)) ? processSequence(config.sequences[segment], currentWeather) : [];
|
||||||
}
|
}
|
||||||
@@ -89,8 +96,7 @@ function processSegment(segment: SegmentName, currentWeather: CurrentWeather): s
|
|||||||
async function Sequencer(conf: Config): Promise<string[]> {
|
async function Sequencer(conf: Config): Promise<string[]> {
|
||||||
config = conf;
|
config = conf;
|
||||||
const weather = new OpenWeatherAPI(conf.weather);
|
const weather = new OpenWeatherAPI(conf.weather);
|
||||||
const currentWeather = await weather.getCurrent();
|
const currentWeather = await weather.getToday();
|
||||||
console.log(JSON.stringify(currentWeather));
|
|
||||||
const sequence: string[] = [];
|
const sequence: string[] = [];
|
||||||
const program: SegmentName[] = selectOne(conf.programs);
|
const program: SegmentName[] = selectOne(conf.programs);
|
||||||
for (let i = 0; i < program.length; i++) {
|
for (let i = 0; i < program.length; i++) {
|
||||||
|
@@ -9,7 +9,7 @@ vi.mock('openweather-api-node', () => {
|
|||||||
return {
|
return {
|
||||||
OpenWeatherAPI: vi.fn().mockImplementation((_) => {
|
OpenWeatherAPI: vi.fn().mockImplementation((_) => {
|
||||||
return {
|
return {
|
||||||
getCurrent: vi.fn(() => {
|
getToday: vi.fn(() => {
|
||||||
return {
|
return {
|
||||||
"lat": 43.0748,
|
"lat": 43.0748,
|
||||||
"lon": -89.3838,
|
"lon": -89.3838,
|
||||||
@@ -106,5 +106,85 @@ describe('sequencer', () => {
|
|||||||
weather: dummyWeather
|
weather: dummyWeather
|
||||||
})).to.be.ordered.members(['seq1.flac', 'seq1.flac']);
|
})).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'
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user