Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AppSelector trait for TV #201

Merged
merged 2 commits into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions functions/commands/appselect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const DefaultCommand = require('./default.js');
const TV = require('../devices/tv.js');

class AppSelect extends DefaultCommand {
static get type() {
return 'action.devices.commands.appSelect';
}

static validateParams(params) {
return (
('newApplication' in params && typeof params.newApplication === 'string') ||
('newApplicationName' in params && typeof params.newApplicationName === 'string')
);
}

static requiresItem() {
return true;
}

static getItemName(item) {
const members = TV.getMembers(item);
if ('tvApplication' in members) {
return members.tvApplication.name;
}
throw { statusCode: 400 };
}

static convertParamsToValue(params, item) {
const applicationMap = TV.getApplicationMap(item);
if (params.newApplication && params.newApplication in applicationMap) {
return params.newApplication;
}
const search = params.newApplicationName;
for (const key in applicationMap) {
if (applicationMap[key].includes(search)) {
return key;
}
}
throw { errorCode: 'noAvailableApp' };
}

static getResponseStates(params, item) {
return {
currentApplication: this.convertParamsToValue(params, item)
};
}
}

module.exports = AppSelect;
8 changes: 7 additions & 1 deletion functions/commands/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,13 @@ class DefaultCommand {
ids: [device.id],
status: 'ERROR',
errorCode:
error.statusCode == 404 ? 'deviceNotFound' : error.statusCode == 400 ? 'notSupported' : 'deviceOffline'
typeof error.errorCode === 'string'
? error.errorCode
: error.statusCode == 404
? 'deviceNotFound'
: error.statusCode == 400
? 'notSupported'
: 'deviceOffline'
});
});
});
Expand Down
5 changes: 3 additions & 2 deletions functions/commands/selectchannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ class SelectChannel extends DefaultCommand {
}

static convertParamsToValue(params, item) {
if (params.channelNumber) {
const channelMap = TV.getChannelMap(item);
if (params.channelNumber && params.channelNumber in channelMap) {
return params.channelNumber;
}
const search = params.channelName || params.channelCode;
const channelMap = TV.getChannelMap(item);
for (const number in channelMap) {
if (channelMap[number].includes(search)) {
return number;
}
}
throw { errorCode: 'noAvailableChannel' };
}

static getResponseStates(params, item) {
Expand Down
32 changes: 31 additions & 1 deletion functions/devices/tv.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class TV extends DefaultDevice {
if ('tvChannel' in members) traits.push('action.devices.traits.Channel');
if ('tvInput' in members) traits.push('action.devices.traits.InputSelector');
if ('tvTransport' in members) traits.push('action.devices.traits.TransportControl');
if ('tvApplication' in members) traits.push('action.devices.traits.AppSelector');
return traits;
}

Expand Down Expand Up @@ -70,6 +71,21 @@ class TV extends DefaultDevice {
});
});
}
if ('tvApplication' in members && 'availableApplications' in config) {
attributes.availableApplications = [];
config.availableApplications.split(',').forEach((application) => {
const [key, synonyms] = application.split('=');
attributes.availableApplications.push({
key: key,
names: [
{
name_synonym: synonyms.split(':'),
lang: config.lang || 'en'
}
]
});
});
}
return attributes;
}

Expand All @@ -96,13 +112,15 @@ class TV extends DefaultDevice {
state.channelName = this.getChannelMap(item)[members[member].state][0];
} catch {}
break;
case 'tvApplication':
state.currentApplication = members[member].state;
}
}
return state;
}

static getMembers(item) {
const supportedMembers = ['tvChannel', 'tvVolume', 'tvInput', 'tvTransport', 'tvPower', 'tvMute'];
const supportedMembers = ['tvApplication', 'tvChannel', 'tvVolume', 'tvInput', 'tvTransport', 'tvPower', 'tvMute'];
const members = Object();
if (item.members && item.members.length) {
item.members.forEach((member) => {
Expand All @@ -128,6 +146,18 @@ class TV extends DefaultDevice {
}
return channelMap;
}

static getApplicationMap(item) {
const config = this.getConfig(item);
const applicationMap = {};
if ('availableApplications' in config) {
config.availableApplications.split(',').forEach((application) => {
const [key, synonyms] = application.split('=');
applicationMap[key] = [...synonyms.split(':'), key];
});
}
return applicationMap;
}
}

module.exports = TV;
70 changes: 70 additions & 0 deletions tests/commands/appselect.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const Command = require('../../functions/commands/appselect.js');

describe('appSelect Command', () => {
const paramsKey = { newApplication: 'netflix' };
const paramsName = { newApplicationName: 'Net Flix' };

test('validateParams', () => {
expect(Command.validateParams({})).toBe(false);
expect(Command.validateParams(paramsKey)).toBe(true);
expect(Command.validateParams(paramsName)).toBe(true);
});

test('requiresItem', () => {
expect(Command.requiresItem()).toBe(true);
});

test('getItemName', () => {
expect(() => {
Command.getItemName({ name: 'Item' });
}).toThrow();
const item = {
members: [
{
name: 'ApplicationItem',
metadata: {
ga: {
value: 'tvApplication'
}
}
}
]
};
expect(Command.getItemName(item)).toBe('ApplicationItem');
});

test('convertParamsToValue', () => {
const item = {
metadata: {
ga: {
config: {
availableApplications: 'youtube=YouTube:Tube,netflix=Net Flix:Flix'
}
}
}
};
expect(Command.convertParamsToValue(paramsKey, item)).toBe('netflix');
expect(Command.convertParamsToValue(paramsName, item)).toBe('netflix');
expect(Command.convertParamsToValue({ newApplicationName: 'Tube' }, item)).toBe('youtube');
expect(() => {
Command.convertParamsToValue({ newApplication: 'wrong' }, item);
}).toThrow();
expect(() => {
Command.convertParamsToValue({ newApplicationName: 'wrong' }, item);
}).toThrow();
});

test('getResponseStates', () => {
const item = {
metadata: {
ga: {
config: {
availableApplications: 'youtube=YouTube,netflix=Net Flix'
}
}
}
};
expect(Command.getResponseStates(paramsKey, item)).toStrictEqual({ currentApplication: 'netflix' });
expect(Command.getResponseStates(paramsName, item)).toStrictEqual({ currentApplication: 'netflix' });
});
});
15 changes: 15 additions & 0 deletions tests/commands/default.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,5 +308,20 @@ describe('Default Command', () => {
}
]);
});

test('execute with errorCode', async () => {
sendCommandMock.mockReturnValue(Promise.reject({ errorCode: 'noAvailableChannel' }));
const devices = [{ id: 'Item1' }];
const result = await TestCommand1.execute(apiHandler, devices, { on: true }, {});
expect(getItemMock).toHaveBeenCalledTimes(0);
expect(sendCommandMock).toHaveBeenCalledTimes(1);
expect(result).toStrictEqual([
{
errorCode: 'noAvailableChannel',
ids: ['Item1'],
status: 'ERROR'
}
]);
});
});
});
6 changes: 6 additions & 0 deletions tests/commands/selectchannel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ describe('selectChannel Command', () => {
expect(Command.convertParamsToValue({ channelCode: 'channel1' }, item)).toBe('1');
expect(Command.convertParamsToValue({ channelName: 'ARD' }, item)).toBe('1');
expect(Command.convertParamsToValue({ channelNumber: '1' }, item)).toBe('1');
expect(() => {
Command.convertParamsToValue({ channelNumber: '0' }, item);
}).toThrow();
expect(() => {
Command.convertParamsToValue({ channelName: 'wrong' }, item);
}).toThrow();
});

test('getResponseStates', () => {
Expand Down
Loading