You've already forked homehub
Release 1.3.0
This commit is contained in:
11
packages/homehub_core/tests/__entry__.js
Normal file
11
packages/homehub_core/tests/__entry__.js
Normal file
@@ -0,0 +1,11 @@
|
||||
require('regenerator-runtime/runtime');
|
||||
require('whatwg-fetch');
|
||||
window.crypto = {
|
||||
getRandomValues: require('polyfill-crypto.getrandomvalues'),
|
||||
};
|
||||
|
||||
require('tests/__setup__/enzyme.setup.js');
|
||||
require('tests/__setup__/jasmine.setup.js');
|
||||
|
||||
let testsContext = require.context('.', true, /\.spec\.js$/);
|
||||
testsContext.keys().forEach(testsContext);
|
||||
51
packages/homehub_core/tests/__fixtures__/dashboards.js
Normal file
51
packages/homehub_core/tests/__fixtures__/dashboards.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import {FakeWidget, FakeService} from 'tests/__fixtures__/services';
|
||||
|
||||
export const DashboardsFactory = () => {
|
||||
return [
|
||||
{
|
||||
id: 'testing',
|
||||
name: 'Testing',
|
||||
services: [
|
||||
new FakeService({
|
||||
instance: 'fake_instance',
|
||||
widgetComponent: FakeWidget,
|
||||
characteristics: {
|
||||
spam: true,
|
||||
},
|
||||
layout: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 1,
|
||||
h: 1,
|
||||
},
|
||||
}),
|
||||
new FakeService({
|
||||
instance: 'other_fake_instance',
|
||||
widgetComponent: FakeWidget,
|
||||
characteristics: {
|
||||
spam: true,
|
||||
},
|
||||
layout: {
|
||||
x: 0,
|
||||
y: 1,
|
||||
w: 1,
|
||||
h: 1,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const DashboardsJSONFactory = (dashboards) => {
|
||||
dashboards = dashboards || DashboardsFactory();
|
||||
|
||||
return dashboards.map((dashboard) => {
|
||||
return {
|
||||
...dashboard,
|
||||
services: dashboard.services.map((service) => {
|
||||
return service.toJSON();
|
||||
}),
|
||||
};
|
||||
});
|
||||
};
|
||||
19
packages/homehub_core/tests/__fixtures__/services.js
Normal file
19
packages/homehub_core/tests/__fixtures__/services.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
import {BaseService} from 'src/lib/services';
|
||||
|
||||
export const FakeWidget = (props) => { // eslint-disable-line no-unused-vars
|
||||
return <span>FakeWidget</span>;
|
||||
};
|
||||
|
||||
export const FakeWidgetSettingsView = (props) => { // eslint-disable-line no-unused-vars
|
||||
return <span>FakeWidgetSettingsView</span>;
|
||||
};
|
||||
|
||||
FakeWidget.defaultLayout = {w: 1, h: 1};
|
||||
FakeWidget.settingsView = FakeWidgetSettingsView;
|
||||
|
||||
export class FakeService extends BaseService {
|
||||
static kind = 'FakeService';
|
||||
static widget = 'FakeWidget';
|
||||
}
|
||||
7
packages/homehub_core/tests/__setup__/enzyme.setup.js
Normal file
7
packages/homehub_core/tests/__setup__/enzyme.setup.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
Enzyme.configure({
|
||||
adapter: new Adapter(),
|
||||
disableLifecycleMethods: true,
|
||||
});
|
||||
22
packages/homehub_core/tests/__setup__/jasmine.setup.js
Normal file
22
packages/homehub_core/tests/__setup__/jasmine.setup.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
jasmine.getEnv().beforeAll(() => {
|
||||
jasmine.addMatchers({
|
||||
toBeUUIDv4: (util, customEqualityTesters) => {
|
||||
return {
|
||||
compare: (actual, expected) => {
|
||||
const reUUIDv4 = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
||||
|
||||
const result = {};
|
||||
result.pass = reUUIDv4.test(actual);
|
||||
|
||||
if (!result.pass) {
|
||||
result.message = `Expected ${actual} to be a UUIDv4`;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
51
packages/homehub_core/tests/api/services.spec.js
Normal file
51
packages/homehub_core/tests/api/services.spec.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as Services from 'src/api/services';
|
||||
import * as RPC from 'src/lib/rpc';
|
||||
|
||||
describe('src/api/services', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(RPC, 'callMethod').and.resolveTo('ok');
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
it('calls and RPC method to start a service', async () => {
|
||||
// When
|
||||
const result = await Services.start('FakeService', 'fake_instance', {
|
||||
'spam': true,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toEqual('ok');
|
||||
expect(RPC.callMethod).toHaveBeenCalledWith('services.start', [
|
||||
'FakeService', 'fake_instance', {'spam': true},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stop', () => {
|
||||
it('calls and RPC method to stop a service', async () => {
|
||||
// When
|
||||
const result = await Services.stop('FakeService', 'fake_instance');
|
||||
|
||||
// Then
|
||||
expect(result).toEqual('ok');
|
||||
expect(RPC.callMethod).toHaveBeenCalledWith('services.stop', [
|
||||
'FakeService', 'fake_instance',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('use', () => {
|
||||
it('calls and RPC method to use a service capability', async () => {
|
||||
// When
|
||||
const result = await Services.use(
|
||||
'FakeService', 'fake_instance', 'testing', ['spam'],
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result).toEqual('ok');
|
||||
expect(RPC.callMethod).toHaveBeenCalledWith('services.use', [
|
||||
'FakeService', 'fake_instance', 'testing', ['spam'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
32
packages/homehub_core/tests/api/state.spec.js
Normal file
32
packages/homehub_core/tests/api/state.spec.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as State from 'src/api/state';
|
||||
import * as RPC from 'src/lib/rpc';
|
||||
|
||||
describe('src/api/state', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(RPC, 'callMethod').and.resolveTo('ok');
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('calls and RPC method to get frontend state', async () => {
|
||||
// When
|
||||
const result = await State.get();
|
||||
|
||||
// Then
|
||||
expect(result).toEqual('ok');
|
||||
expect(RPC.callMethod).toHaveBeenCalledWith('state.get_frontend');
|
||||
});
|
||||
});
|
||||
|
||||
describe('save', () => {
|
||||
it('calls and RPC method to save frontend state', async () => {
|
||||
// When
|
||||
const result = await State.save('spam');
|
||||
|
||||
// Then
|
||||
expect(result).toEqual('ok');
|
||||
expect(RPC.callMethod).toHaveBeenCalledWith(
|
||||
'state.save_frontend', ['spam']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
750
packages/homehub_core/tests/containers/ServiceContainer.spec.js
Normal file
750
packages/homehub_core/tests/containers/ServiceContainer.spec.js
Normal file
@@ -0,0 +1,750 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {withContext} from 'shallow-with-context';
|
||||
|
||||
import {ServiceContainer} from 'src/containers/ServiceContainer';
|
||||
import {DEFAULT_DASHBOARDS_CONTEXT} from 'src/context/DashboardsContext';
|
||||
import {DummyService, ServiceState} from 'src/lib/services';
|
||||
|
||||
import {DashboardsFactory} from 'tests/__fixtures__/dashboards';
|
||||
|
||||
describe('src/containers/ServiceContainer', () => {
|
||||
const ServiceContainerWithContext = withContext(
|
||||
ServiceContainer, DEFAULT_DASHBOARDS_CONTEXT
|
||||
);
|
||||
|
||||
let context = null;
|
||||
|
||||
beforeEach(() => {
|
||||
context = {
|
||||
...DEFAULT_DASHBOARDS_CONTEXT,
|
||||
nukeService: jasmine.createSpy(),
|
||||
saveServiceCharacteristics: jasmine.createSpy(),
|
||||
saveServiceLayout: jasmine.createSpy(),
|
||||
addService: jasmine.createSpy(),
|
||||
setCurrentDashboardId: jasmine.createSpy(),
|
||||
addDashboard: jasmine.createSpy(),
|
||||
dashboards: DashboardsFactory(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('initializes the state', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(component.state('serviceState')).toBe(null);
|
||||
expect(component.state('showSettingsModal')).toBe(false);
|
||||
expect(component.state('nextCharacteristics')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('service', () => {
|
||||
it('looks up the bound service instance', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// When
|
||||
const service = component.instance().service();
|
||||
|
||||
// Then
|
||||
expect(service).toEqual(context.dashboards[0].services[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setServiceState', () => {
|
||||
it('initializes new state object if the current state is `null`', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// When
|
||||
component.instance().setServiceState({data: {spam: true}}, {});
|
||||
|
||||
// Then
|
||||
expect(component.state('serviceState')).toBeInstanceOf(ServiceState);
|
||||
expect(component.state('serviceState').data()).toEqual({spam: true});
|
||||
});
|
||||
|
||||
it('initializes new state object if reset is requested', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
const oldState = new ServiceState({data: {spam: false}});
|
||||
component.setState({serviceState: oldState});
|
||||
|
||||
// When
|
||||
component.instance().setServiceState(
|
||||
{data: {spam: true}}, {reset: true}
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(component.state('serviceState')).not.toBe(oldState);
|
||||
});
|
||||
|
||||
it('updates the existing state object with new payload', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
const oldState = new ServiceState({data: {spam: false}});
|
||||
component.setState({serviceState: oldState});
|
||||
|
||||
// When
|
||||
component.instance().setServiceState({data: {spam: true}}, {});
|
||||
|
||||
// Then
|
||||
expect(component.state('serviceState')).not.toBe(oldState);
|
||||
expect(component.state('serviceState').data()).toEqual({spam: true});
|
||||
});
|
||||
});
|
||||
|
||||
it('allows setting next characteristics', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// When
|
||||
component.instance().setNextCharacteristics({spam: true});
|
||||
|
||||
// Then
|
||||
expect(component.state('nextCharacteristics')).toEqual({spam: true});
|
||||
});
|
||||
|
||||
describe('setNextCharacteristicsFromService', () => {
|
||||
it('resets the next characteristics if the service is null', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
component.setState({nextCharacteristics: {spam: false}});
|
||||
|
||||
// When
|
||||
component.instance().setNextCharacteristicsFromService(null);
|
||||
|
||||
// Then
|
||||
expect(component.state('nextCharacteristics')).toBe(null);
|
||||
});
|
||||
|
||||
it('resets the next characteristics if the service has no widget', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
component.setState({nextCharacteristics: {spam: false}});
|
||||
|
||||
const service = context.dashboards[0].services[0];
|
||||
service.widgetComponent = null;
|
||||
|
||||
// When
|
||||
component.instance().setNextCharacteristicsFromService(service);
|
||||
|
||||
// Then
|
||||
expect(component.state('nextCharacteristics')).toBe(null);
|
||||
});
|
||||
|
||||
it('resets the next characteristics if the service is dummy', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
component.setState({nextCharacteristics: {spam: false}});
|
||||
|
||||
const service = new DummyService();
|
||||
|
||||
// When
|
||||
component.instance().setNextCharacteristicsFromService(service);
|
||||
|
||||
// Then
|
||||
expect(component.state('nextCharacteristics')).toBe(null);
|
||||
});
|
||||
|
||||
it('sets the next characteristics from the service characteristics', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
const service = context.dashboards[0].services[0];
|
||||
|
||||
// When
|
||||
component.instance().setNextCharacteristicsFromService(service);
|
||||
|
||||
// Then
|
||||
expect(component.state('nextCharacteristics')).toEqual(
|
||||
service.characteristics
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showHideSettingsModal', () => {
|
||||
it('sets next characteristics from the service when modal is to be shown', () => {
|
||||
// Givem
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'setNextCharacteristicsFromService');
|
||||
|
||||
// When
|
||||
component.instance().showHideSettingsModal(true);
|
||||
|
||||
// Then
|
||||
expect(component.instance().setNextCharacteristicsFromService).toHaveBeenCalledWith(
|
||||
context.dashboards[0].services[0]
|
||||
);
|
||||
});
|
||||
|
||||
it('resets next characteristics when modal is to be hidden', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'setNextCharacteristicsFromService');
|
||||
|
||||
// When
|
||||
component.instance().showHideSettingsModal(false);
|
||||
|
||||
// Then
|
||||
expect(component.instance().setNextCharacteristicsFromService).toHaveBeenCalledWith(
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
it('updates the state with new visibility flag', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'setNextCharacteristicsFromService');
|
||||
|
||||
// When
|
||||
component.instance().showHideSettingsModal(true);
|
||||
|
||||
// Then
|
||||
expect(component.state('showSettingsModal')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSettingsButtonClick', () => {
|
||||
it('requests the settings modal to be shown', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'showHideSettingsModal');
|
||||
|
||||
// When
|
||||
component.instance().onSettingsButtonClick();
|
||||
|
||||
// Then
|
||||
expect(component.instance().showHideSettingsModal).toHaveBeenCalledWith(
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSettingsModalClose', () => {
|
||||
it('requests the settings modal to be hidden', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'showHideSettingsModal');
|
||||
|
||||
// When
|
||||
component.instance().onSettingsModalClose();
|
||||
|
||||
// Then
|
||||
expect(component.instance().showHideSettingsModal).toHaveBeenCalledWith(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSettingsModalSaveButtonClick', () => {
|
||||
it('requests the next characteristics to be saved', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
component.setState({nextCharacteristics: {spam: true}});
|
||||
|
||||
// When
|
||||
component.instance().onSettingsModalSaveButtonClick();
|
||||
|
||||
// Then
|
||||
expect(context.saveServiceCharacteristics).toHaveBeenCalledWith(
|
||||
'FakeService', 'fake_instance', {spam: true}
|
||||
);
|
||||
});
|
||||
|
||||
it('closes the settings modal', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'onSettingsModalClose');
|
||||
|
||||
// When
|
||||
component.instance().onSettingsModalSaveButtonClick();
|
||||
|
||||
// Then
|
||||
expect(component.instance().onSettingsModalClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSettingsModalNukeButtonClick', () => {
|
||||
it('requests the service to be nuked', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// When
|
||||
component.instance().onSettingsModalNukeButtonClick();
|
||||
|
||||
// Then
|
||||
expect(context.nukeService).toHaveBeenCalledWith(
|
||||
'FakeService', 'fake_instance'
|
||||
);
|
||||
});
|
||||
|
||||
it('closes the settings modal', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'onSettingsModalClose');
|
||||
|
||||
// When
|
||||
component.instance().onSettingsModalNukeButtonClick();
|
||||
|
||||
// Then
|
||||
expect(component.instance().onSettingsModalClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onAppearancePopupColorChange', () => {
|
||||
it('requests the updated characteristics to be saved', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// When
|
||||
component.instance().onAppearancePopupColorChange('red');
|
||||
|
||||
// Then
|
||||
expect(context.saveServiceCharacteristics).toHaveBeenCalledWith(
|
||||
'FakeService', 'fake_instance', {
|
||||
spam: true,
|
||||
appearance: {
|
||||
color: 'red',
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onServiceRestartButtonClick', () => {
|
||||
it('requests the updated characteristics to be saved', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
const service = context.dashboards[0].services[0];
|
||||
spyOn(service, 'restart');
|
||||
|
||||
// When
|
||||
component.instance().onServiceRestartButtonClick();
|
||||
|
||||
// Then
|
||||
expect(service.restart).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsubscribeIfNeeded', () => {
|
||||
it('calls the unsubscribe callback', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
component.instance().serviceUnsubscribe = jasmine.createSpy();
|
||||
|
||||
// When
|
||||
component.instance().unsubscribeIfNeeded();
|
||||
|
||||
// Then
|
||||
expect(component.instance().serviceUnsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentDidMount', () => {
|
||||
it('is a noop if the service is null', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'setServiceState');
|
||||
spyOn(component.instance(), 'unsubscribeIfNeeded');
|
||||
spyOn(component.instance(), 'service').and.returnValue(null);
|
||||
|
||||
// When
|
||||
component.instance().componentDidMount();
|
||||
|
||||
// Then
|
||||
expect(component.instance().setServiceState).not.toHaveBeenCalled();
|
||||
expect(component.instance().unsubscribeIfNeeded).not.toHaveBeenCalled();
|
||||
expect(component.instance().serviceUnsubscribe).toBe(null);
|
||||
});
|
||||
|
||||
it('is a noop if the service has no widget', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'setServiceState');
|
||||
spyOn(component.instance(), 'unsubscribeIfNeeded');
|
||||
|
||||
context.dashboards[0].services[0].widgetComponent = null;
|
||||
|
||||
// When
|
||||
component.instance().componentDidMount();
|
||||
|
||||
// Then
|
||||
expect(component.instance().setServiceState).not.toHaveBeenCalled();
|
||||
expect(component.instance().unsubscribeIfNeeded).not.toHaveBeenCalled();
|
||||
expect(component.instance().serviceUnsubscribe).toBe(null);
|
||||
});
|
||||
|
||||
it('is a noop if the service is dummy', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'setServiceState');
|
||||
spyOn(component.instance(), 'unsubscribeIfNeeded');
|
||||
spyOn(component.instance(), 'service').and.returnValue(
|
||||
new DummyService()
|
||||
);
|
||||
|
||||
// When
|
||||
component.instance().componentDidMount();
|
||||
|
||||
// Then
|
||||
expect(component.instance().setServiceState).not.toHaveBeenCalled();
|
||||
expect(component.instance().unsubscribeIfNeeded).not.toHaveBeenCalled();
|
||||
expect(component.instance().serviceUnsubscribe).toBe(null);
|
||||
});
|
||||
|
||||
it('sets itself up for the service', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
spyOn(component.instance(), 'setServiceState');
|
||||
spyOn(component.instance(), 'unsubscribeIfNeeded');
|
||||
|
||||
const service = context.dashboards[0].services[0];
|
||||
spyOn(service, 'start');
|
||||
|
||||
const initialState = new ServiceState({data: {initial: true}});
|
||||
spyOn(service, 'initialState').and.returnValue(initialState);
|
||||
|
||||
const fakeUnsubscribe = jasmine.createSpy();
|
||||
spyOn(service, 'subscribe').and.returnValue(fakeUnsubscribe);
|
||||
|
||||
// When
|
||||
component.instance().componentDidMount();
|
||||
|
||||
// Then
|
||||
expect(component.instance().setServiceState).toHaveBeenCalledWith(
|
||||
initialState, true
|
||||
);
|
||||
expect(service.start).toHaveBeenCalled();
|
||||
expect(component.instance().unsubscribeIfNeeded).toHaveBeenCalled();
|
||||
expect(service.subscribe).toHaveBeenCalledWith(
|
||||
component.instance().setServiceState
|
||||
);
|
||||
expect(component.instance().serviceUnsubscribe).toEqual(fakeUnsubscribe);
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentWillUnmount', () => {
|
||||
it('unsubscribes from the service', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{(props) => <span>It works!</span>}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
const mockUnsubscribeIfNeeded = jasmine.createSpy();
|
||||
component.instance().unsubscribeIfNeeded = mockUnsubscribeIfNeeded;
|
||||
|
||||
// When
|
||||
component.unmount();
|
||||
|
||||
// Then
|
||||
expect(mockUnsubscribeIfNeeded).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('render', () => {
|
||||
let childrenFunc = null;
|
||||
|
||||
beforeEach(() => {
|
||||
childrenFunc = jasmine.createSpy();
|
||||
childrenFunc.returnValue = <span>It works!</span>;
|
||||
});
|
||||
|
||||
it('calls the children func with props', () => {
|
||||
// Given
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{childrenFunc}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
const serviceState = new ServiceState({data: {spam: true}});
|
||||
component.setState({
|
||||
serviceState: serviceState,
|
||||
showSettingsModal: true,
|
||||
nextCharacteristics: {eggs: true},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(childrenFunc).toHaveBeenCalledWith({
|
||||
hasSettingsView: true,
|
||||
service: context.dashboards[0].services[0],
|
||||
serviceState: serviceState,
|
||||
setServiceState: component.instance().setServiceState,
|
||||
settingsViewProps: {
|
||||
kind: 'FakeService',
|
||||
instance: 'fake_instance',
|
||||
nextCharacteristics: {eggs: true},
|
||||
setNextCharacteristics: component.instance().setNextCharacteristics,
|
||||
},
|
||||
showSettingsModal: true,
|
||||
onAppearancePopupColorChange: component.instance().onAppearancePopupColorChange,
|
||||
onServiceRestartButtonClick: component.instance().onServiceRestartButtonClick,
|
||||
onSettingsButtonClick: component.instance().onSettingsButtonClick,
|
||||
onSettingsModalClose: component.instance().onSettingsModalClose,
|
||||
onSettingsModalNukeButtonClick: component.instance().onSettingsModalNukeButtonClick,
|
||||
onSettingsModalSaveButtonClick: component.instance().onSettingsModalSaveButtonClick,
|
||||
});
|
||||
});
|
||||
|
||||
it('disables settings view if the service is dummy', () => {
|
||||
// Given
|
||||
const service = context.dashboards[0].services[0];
|
||||
spyOn(service, 'isDummy').and.returnValue(true);
|
||||
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{childrenFunc}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(childrenFunc).toHaveBeenCalledWith(jasmine.objectContaining({
|
||||
hasSettingsView: false,
|
||||
}));
|
||||
});
|
||||
|
||||
it('disables settings view if the service has no widget', () => {
|
||||
// Given
|
||||
const service = context.dashboards[0].services[0];
|
||||
service.widgetComponent = null;
|
||||
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{childrenFunc}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(childrenFunc).toHaveBeenCalledWith(jasmine.objectContaining({
|
||||
hasSettingsView: false,
|
||||
}));
|
||||
});
|
||||
|
||||
it('disables settings view if the service widget has no settings view', () => {
|
||||
// Given
|
||||
const service = context.dashboards[0].services[0];
|
||||
const oldSettingsView = service.widgetComponent.settingsView;
|
||||
service.widgetComponent.settingsView = undefined;
|
||||
|
||||
const component = shallow(
|
||||
<ServiceContainerWithContext kind="FakeService" instance="fake_instance">
|
||||
{childrenFunc}
|
||||
</ServiceContainerWithContext>,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(childrenFunc).toHaveBeenCalledWith(jasmine.objectContaining({
|
||||
hasSettingsView: false,
|
||||
}));
|
||||
|
||||
// After
|
||||
service.widgetComponent.settingsView = oldSettingsView;
|
||||
});
|
||||
});
|
||||
});
|
||||
304
packages/homehub_core/tests/hooks.spec.js
Normal file
304
packages/homehub_core/tests/hooks.spec.js
Normal file
@@ -0,0 +1,304 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import {mount} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
DEFAULT_DASHBOARDS_CONTEXT, DashboardsContext,
|
||||
} from 'src/context/DashboardsContext';
|
||||
import * as Hooks from 'src/hooks';
|
||||
|
||||
import {DashboardsFactory} from 'tests/__fixtures__/dashboards';
|
||||
|
||||
describe('src/hooks', () => {
|
||||
const HookWrapper = ({context, hook, hookArgs, output}) => {
|
||||
const Children = (props) => {
|
||||
output.result = hook(...hookArgs);
|
||||
return <span>It works!</span>;
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardsContext.Provider value={context}>
|
||||
<Children />
|
||||
</DashboardsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
let context = null;
|
||||
|
||||
beforeEach(() => {
|
||||
context = {
|
||||
...DEFAULT_DASHBOARDS_CONTEXT,
|
||||
dashboards: DashboardsFactory(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('useServices', () => {
|
||||
it('returns the the current dashboard services', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
context = {
|
||||
...context,
|
||||
currentDashboardId: context.dashboards[0].id,
|
||||
};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useServices}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toEqual(context.dashboards[0].services);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useService', () => {
|
||||
it('returns the the specified service instance', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useService}
|
||||
hookArgs={['FakeService', 'fake_instance']}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toEqual(context.dashboards[0].services[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSaveServiceCharacteristics', () => {
|
||||
it('returns the saveServiceCharacteristics callback', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// WHen
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useSaveServiceCharacteristics}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toBe(context.saveServiceCharacteristics);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSaveServiceLayout', () => {
|
||||
it('returns the saveServiceLayout callback', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useSaveServiceLayout}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toBe(context.saveServiceLayout);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useNukeService', () => {
|
||||
it('returns the nukeService callback', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useNukeService}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toBe(context.nukeService);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useAddService', () => {
|
||||
it('returns the addService callback', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useAddService}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toBe(context.addService);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useServicesSavingState', () => {
|
||||
it('returns the service saving state', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useServicesSavingState}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toEqual({
|
||||
lastSaveTimestamp: context.lastSaveTimestamp,
|
||||
lastSaveError: context.lastSaveError,
|
||||
isSaving: context.isSaving,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useWebSocketState', () => {
|
||||
it('returns the websocket state', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useWebSocketState}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toEqual({
|
||||
isWebSocketConnected: context.isWebSocketConnected,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useDashboards', () => {
|
||||
it('returns the dashboards', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useDashboards}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toEqual(context.dashboards);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCurrentDashboardId', () => {
|
||||
it('returns the dashboards', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useCurrentDashboardId}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toEqual(context.currentDashboardId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSetCurrentDashboardId', () => {
|
||||
it('returns the setCurrentDashboardId callback', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useSetCurrentDashboardId}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toBe(context.setCurrentDashboardId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useDashboardsHash', () => {
|
||||
it('returns the dashboards hash', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useDashboardsHash}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toEqual(context.dashboardsHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useAddDashboard', () => {
|
||||
it('returns the addDashboard callback', () => {
|
||||
// Given
|
||||
const output = {};
|
||||
|
||||
// When
|
||||
const component = mount(
|
||||
<HookWrapper
|
||||
context={context}
|
||||
hook={Hooks.useAddDashboard}
|
||||
hookArgs={[]}
|
||||
output={output}
|
||||
/>
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(output.result).toBe(context.addDashboard);
|
||||
});
|
||||
});
|
||||
});
|
||||
242
packages/homehub_core/tests/lib/base.spec.js
Normal file
242
packages/homehub_core/tests/lib/base.spec.js
Normal file
@@ -0,0 +1,242 @@
|
||||
import * as BaseLib from 'src/lib/base';
|
||||
|
||||
describe('src/lib/base', () => {
|
||||
describe('SubscribableMixin', () => {
|
||||
const TestClass = class extends BaseLib.SubscribableMixin(BaseLib.HomeHubBaseClass) {
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('initializes the instance', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
// Then
|
||||
expect(mixin.subscribers).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('notify', () => {
|
||||
it('notifies the subscribers', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
const mockSubscriber = jasmine.createSpy();
|
||||
mixin.subscribers.push(mockSubscriber);
|
||||
mixin.subscribers.push(null);
|
||||
|
||||
// When
|
||||
mixin.notify('test');
|
||||
|
||||
// Then
|
||||
expect(mockSubscriber).toHaveBeenCalledWith('test', {});
|
||||
});
|
||||
|
||||
it('allows sending a notification with user info', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
const mockSubscriber = jasmine.createSpy();
|
||||
mixin.subscribers.push(mockSubscriber);
|
||||
|
||||
// When
|
||||
mixin.notify('test', {reset: true});
|
||||
|
||||
// Then
|
||||
expect(mockSubscriber).toHaveBeenCalledWith('test', {reset: true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsubscriberFactory', () => {
|
||||
it('returns an unsubscriber function', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
// When
|
||||
const result = mixin.unsubscriberFactory(0);
|
||||
|
||||
// Then
|
||||
expect(result).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('configures the unsubscriber function to remove the bound subscriber', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
const mockSubscriber1 = jasmine.createSpy();
|
||||
mixin.subscribers.push(mockSubscriber1);
|
||||
|
||||
const mockSubscriber2 = jasmine.createSpy();
|
||||
mixin.subscribers.push(mockSubscriber2);
|
||||
|
||||
const mockSubscriber3 = jasmine.createSpy();
|
||||
mixin.subscribers.push(mockSubscriber3);
|
||||
|
||||
const unsubscriber = mixin.unsubscriberFactory(1);
|
||||
|
||||
// When
|
||||
unsubscriber();
|
||||
|
||||
// Then
|
||||
expect(mixin.subscribers.length).toEqual(3);
|
||||
expect(mixin.subscribers[0]).toBe(mockSubscriber1);
|
||||
expect(mixin.subscribers[1]).toBe(null);
|
||||
expect(mixin.subscribers[2]).toBe(mockSubscriber3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
it('adds a subscriber and returns its unsubscriber', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
const mockUnsubscriber = jasmine.createSpy();
|
||||
spyOn(mixin, 'unsubscriberFactory').and.returnValue = mockUnsubscriber;
|
||||
|
||||
const mockSubscriber = jasmine.createSpy();
|
||||
|
||||
// When
|
||||
mixin.subscribe(mockSubscriber);
|
||||
|
||||
// Then
|
||||
expect(mixin.subscribers.length).toEqual(1);
|
||||
expect(mixin.subscribers[0]).toBe(mockSubscriber);
|
||||
expect(mixin.unsubscriberFactory).toHaveBeenCalledWith(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('EventSouceMixin', () => {
|
||||
const Base = BaseLib.EventSourceMixin(BaseLib.HomeHubBaseClass, ['start']);
|
||||
const TestClass = class extends Base {
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('initializes the instance', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
// Then
|
||||
expect(mixin.eventListeners).toEqual({start: []});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addEventListener', () => {
|
||||
let mockListener = null;
|
||||
|
||||
beforeEach(() => {
|
||||
mockListener = jasmine.createSpy();
|
||||
});
|
||||
|
||||
it('ignores an uknown event', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
// When
|
||||
mixin.addEventListener('testing', mockListener);
|
||||
|
||||
// Then
|
||||
expect(mixin.eventListeners.testing).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('adds a listener for an event', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
|
||||
// When
|
||||
mixin.addEventListener('start', mockListener);
|
||||
|
||||
// Then
|
||||
expect(mixin.eventListeners.start.length).toEqual(1);
|
||||
expect(mixin.eventListeners.start[0]).toBe(mockListener);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addEventListener', () => {
|
||||
let mockListener = null;
|
||||
|
||||
beforeEach(() => {
|
||||
mockListener = jasmine.createSpy();
|
||||
});
|
||||
|
||||
it('ignores an uknown event', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
mixin.addEventListener('start', mockListener);
|
||||
|
||||
// When
|
||||
mixin.removeEventListener('testing', mockListener);
|
||||
|
||||
// Then
|
||||
expect(mixin.eventListeners.testing).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('ignores an unknown listener', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
mixin.addEventListener('start', mockListener);
|
||||
|
||||
const mockListener2 = jasmine.createSpy();
|
||||
|
||||
// When
|
||||
mixin.removeEventListener('start', mockListener2);
|
||||
|
||||
// Then
|
||||
expect(mixin.eventListeners.start.length).toEqual(1);
|
||||
expect(mixin.eventListeners.start[0]).toBe(mockListener);
|
||||
});
|
||||
|
||||
it('removes a listener for an event', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
mixin.addEventListener('start', mockListener);
|
||||
|
||||
const mockListener2 = jasmine.createSpy();
|
||||
mixin.addEventListener('start', mockListener2);
|
||||
|
||||
const mockListener3 = jasmine.createSpy();
|
||||
mixin.addEventListener('start', mockListener3);
|
||||
|
||||
// When
|
||||
mixin.removeEventListener('start', mockListener2);
|
||||
|
||||
// Then
|
||||
expect(mixin.eventListeners.start.length).toEqual(3);
|
||||
expect(mixin.eventListeners.start[0]).toBe(mockListener);
|
||||
expect(mixin.eventListeners.start[1]).toBe(null);
|
||||
expect(mixin.eventListeners.start[2]).toBe(mockListener3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fireEvent', () => {
|
||||
let mockListener = null;
|
||||
|
||||
beforeEach(() => {
|
||||
mockListener = jasmine.createSpy();
|
||||
});
|
||||
|
||||
it('ignores an uknown event', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
mixin.addEventListener('start', mockListener);
|
||||
|
||||
// When
|
||||
mixin.fireEvent('testing', mockListener);
|
||||
|
||||
// Then
|
||||
expect(mockListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls event listeners for an event', () => {
|
||||
// Given
|
||||
const mixin = new TestClass();
|
||||
mixin.addEventListener('start', mockListener);
|
||||
|
||||
// When
|
||||
mixin.fireEvent('start', mockListener);
|
||||
|
||||
// Then
|
||||
expect(mockListener).toHaveBeenCalledWith(mixin);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
23
packages/homehub_core/tests/lib/dashboards.spec.js
Normal file
23
packages/homehub_core/tests/lib/dashboards.spec.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as DashboardsLib from 'src/lib/dashboards';
|
||||
|
||||
describe('src/lib/dashboards', () => {
|
||||
describe('DashboardFactory', () => {
|
||||
it('initializes the dashboard with defaults', () => {
|
||||
// Given
|
||||
const result = DashboardsLib.DashboardFactory();
|
||||
|
||||
// Then
|
||||
expect(result.name).toEqual(jasmine.any(String));
|
||||
expect(result.id).toBeUUIDv4();
|
||||
expect(result.services).toEqual([]);
|
||||
});
|
||||
|
||||
it('allows specifying an arbitrary name', () => {
|
||||
// Given
|
||||
const result = DashboardsLib.DashboardFactory('Testing');
|
||||
|
||||
// Then
|
||||
expect(result.name).toEqual('Testing');
|
||||
});
|
||||
});
|
||||
});
|
||||
15
packages/homehub_core/tests/lib/hashlib.spec.js
Normal file
15
packages/homehub_core/tests/lib/hashlib.spec.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as HashLib from 'src/lib/hashlib';
|
||||
|
||||
describe('src/lib/hashlib', () => {
|
||||
describe('sha256', () => {
|
||||
it('generates a hex digest of a message', async () => {
|
||||
// Given
|
||||
const result = await HashLib.sha256('spam');
|
||||
|
||||
// Then
|
||||
expect(result).toEqual(
|
||||
'f10c5a90947d4313c6af600facfb0c259444e4932a217fc341be0b3776f40933'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
48
packages/homehub_core/tests/lib/localStorage.spec.js
Normal file
48
packages/homehub_core/tests/lib/localStorage.spec.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as LocalStorageLib from 'src/lib/localStorage';
|
||||
|
||||
describe('src/lib/localStorage', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.setItem('test', '{"spam":true}');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.localStorage.removeItem('test');
|
||||
});
|
||||
|
||||
describe('getItem', () => {
|
||||
it('returns the parsed item', () => {
|
||||
// Given
|
||||
const result = LocalStorageLib.getItem('test');
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({'spam': true});
|
||||
});
|
||||
|
||||
it('returns default if the item is not present', () => {
|
||||
// Given
|
||||
const result = LocalStorageLib.getItem('test2', 'default');
|
||||
|
||||
// Then
|
||||
expect(result).toEqual('default');
|
||||
});
|
||||
|
||||
it('defaults the default to null', () => {
|
||||
// Given
|
||||
const result = LocalStorageLib.getItem('test3');
|
||||
|
||||
// Then
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setItem', () => {
|
||||
it('stores the serialized item', () => {
|
||||
// Given
|
||||
LocalStorageLib.setItem('test', {spam: false});
|
||||
|
||||
// Then
|
||||
const storedItem = window.localStorage.getItem('test');
|
||||
expect(storedItem).toEqual('{"spam":false}');
|
||||
});
|
||||
});
|
||||
});
|
||||
117
packages/homehub_core/tests/lib/rpc.spec.js
Normal file
117
packages/homehub_core/tests/lib/rpc.spec.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import * as RPCLib from 'src/lib/rpc';
|
||||
|
||||
describe('src/lib/rpc', () => {
|
||||
describe('callMethod', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(window, 'fetch');
|
||||
});
|
||||
|
||||
it('formats and sends a POST request to the backend RPC URL', async () => {
|
||||
// Given
|
||||
window.fetch.and.resolveTo({
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
json: jasmine.createSpy().and.resolveTo({
|
||||
result: 'ok',
|
||||
}),
|
||||
});
|
||||
|
||||
// When
|
||||
await RPCLib.callMethod('testing', ['spam']);
|
||||
|
||||
// Then
|
||||
expect(window.fetch).toHaveBeenCalledWith('/backend/rpc', {
|
||||
method: 'POST',
|
||||
body: jasmine.any(String),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const callArgs = window.fetch.calls.argsFor(0);
|
||||
const body = JSON.parse(callArgs[1].body);
|
||||
expect(body.jsonrpc).toEqual('2.0');
|
||||
expect(body.method).toEqual('testing');
|
||||
expect(body.params).toEqual(['spam']);
|
||||
expect(body.id).toBeUUIDv4();
|
||||
});
|
||||
|
||||
it('formats a notification call', async () => {
|
||||
// Given
|
||||
window.fetch.and.resolveTo({
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
json: jasmine.createSpy().and.resolveTo({
|
||||
result: 'ok',
|
||||
}),
|
||||
});
|
||||
|
||||
// When
|
||||
await RPCLib.callMethod('testing', ['spam'], true);
|
||||
|
||||
// Then
|
||||
const callArgs = window.fetch.calls.argsFor(0);
|
||||
const body = JSON.parse(callArgs[1].body);
|
||||
expect(body.id).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('returns an error is the response was not OK', async () => {
|
||||
// Given
|
||||
window.fetch.and.resolveTo({
|
||||
ok: false,
|
||||
status: 400,
|
||||
statusText: 'Bad Request',
|
||||
});
|
||||
|
||||
// When
|
||||
const result = await RPCLib.callMethod('testing');
|
||||
|
||||
// Then
|
||||
expect(result).not.toContain('data');
|
||||
expect(result.error).toMatch('HTTP 400 Bad Request');
|
||||
});
|
||||
|
||||
it('returns an error is the response was a JSONRPC error', async () => {
|
||||
// Given
|
||||
window.fetch.and.resolveTo({
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
json: jasmine.createSpy().and.resolveTo({
|
||||
error: {
|
||||
message: 'Internal error',
|
||||
data: 'Test',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// When
|
||||
const result = await RPCLib.callMethod('testing');
|
||||
|
||||
// Then
|
||||
expect(result).not.toContain('data');
|
||||
expect(result.error).toMatch('RPC Error: Internal error Test');
|
||||
});
|
||||
|
||||
it('returns an result is the response was successful', async () => {
|
||||
// Given
|
||||
window.fetch.and.resolveTo({
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
json: jasmine.createSpy().and.resolveTo({
|
||||
result: 'ok',
|
||||
}),
|
||||
});
|
||||
|
||||
// When
|
||||
const result = await RPCLib.callMethod('testing');
|
||||
|
||||
// Then
|
||||
expect(result.data).toEqual('ok');
|
||||
expect(result).not.toContain('error');
|
||||
});
|
||||
});
|
||||
});
|
||||
495
packages/homehub_core/tests/lib/services.spec.js
Normal file
495
packages/homehub_core/tests/lib/services.spec.js
Normal file
@@ -0,0 +1,495 @@
|
||||
import * as ServicesLib from 'src/lib/services';
|
||||
|
||||
import {DashboardsFactory} from 'tests/__fixtures__/dashboards';
|
||||
import {FakeService, FakeWidget} from 'tests/__fixtures__/services';
|
||||
|
||||
describe('src/lib/services', () => {
|
||||
describe('ServiceState', () => {
|
||||
describe('constructor', () => {
|
||||
it('initializes the instance with a payload', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
error: {
|
||||
message: 'FIAL',
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(serviceState.payload.data).toEqual({spam: true});
|
||||
expect(serviceState.payload.error).toEqual({message: 'FIAL'});
|
||||
});
|
||||
|
||||
it('initializes the instance with the default payload', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState();
|
||||
|
||||
// Then
|
||||
expect(serviceState.payload.data).toBe(null);
|
||||
expect(serviceState.payload.error).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLoading', () => {
|
||||
it('returns true if both data and error fields are null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState();
|
||||
|
||||
// Then
|
||||
expect(serviceState.isLoading()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if data is null and error is not null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
error: {
|
||||
message: 'FIAL',
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(serviceState.isLoading()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if data is not null and error is null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(serviceState.isLoading()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if both data and error fields are not null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
error: {
|
||||
message: 'FIAL',
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(serviceState.isLoading()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasData', () => {
|
||||
it('returns false if isLoading is true and data is not null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
});
|
||||
spyOn(serviceState, 'isLoading').and.returnValue(true);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasData()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if isLoading is false and data is null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState();
|
||||
spyOn(serviceState, 'isLoading').and.returnValue(true);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasData()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if isLoading is false and data is not null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
});
|
||||
spyOn(serviceState, 'isLoading').and.returnValue(false);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasData()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasError', () => {
|
||||
it('returns false if isLoading is true and error is not null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
error: {
|
||||
message: 'FIAL',
|
||||
},
|
||||
});
|
||||
spyOn(serviceState, 'isLoading').and.returnValue(true);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasError()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if isLoading is false and error is null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState();
|
||||
spyOn(serviceState, 'isLoading').and.returnValue(true);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasError()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if isLoading is false and error is not null', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
error: {
|
||||
message: 'FIAL',
|
||||
},
|
||||
});
|
||||
spyOn(serviceState, 'isLoading').and.returnValue(false);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasError()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasFatalError', () => {
|
||||
it('returns true if hasData is false and hasError is true', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState();
|
||||
spyOn(serviceState, 'hasData').and.returnValue(false);
|
||||
spyOn(serviceState, 'hasError').and.returnValue(true);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasFatalError()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if hasData is true and hasError is true', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState();
|
||||
spyOn(serviceState, 'hasData').and.returnValue(true);
|
||||
spyOn(serviceState, 'hasError').and.returnValue(true);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasFatalError()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if hasData is true and hasError is false', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState();
|
||||
spyOn(serviceState, 'hasData').and.returnValue(true);
|
||||
spyOn(serviceState, 'hasError').and.returnValue(false);
|
||||
|
||||
// Then
|
||||
expect(serviceState.hasFatalError()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('returns a new ServiceState with updated payload', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState();
|
||||
|
||||
// When
|
||||
const result = serviceState.update({
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
error: {
|
||||
message: 'FIAL',
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).not.toBe(serviceState);
|
||||
expect(result.payload.data).toEqual({spam: true});
|
||||
expect(result.payload.error).toEqual({message: 'FIAL'});
|
||||
expect(serviceState.payload).toEqual({
|
||||
data: null,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('data', () => {
|
||||
it('returns the data payload field', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
error: {
|
||||
message: 'FIAL',
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(serviceState.data()).toEqual(serviceState.payload.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error', () => {
|
||||
it('returns the error payload field', () => {
|
||||
// Given
|
||||
const serviceState = new ServicesLib.ServiceState({
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
error: {
|
||||
message: 'FIAL',
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(serviceState.error()).toEqual(serviceState.payload.error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('BaseService', () => {
|
||||
let spec = null;
|
||||
|
||||
beforeEach(() => {
|
||||
spec = {
|
||||
instance: 'fake_instance',
|
||||
characteristics: {
|
||||
spam: true,
|
||||
},
|
||||
widgetComponent: FakeWidget,
|
||||
layout: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 1,
|
||||
h: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('includes the subscribable mixin', () => {
|
||||
// Given
|
||||
const service = new FakeService(spec);
|
||||
|
||||
// Then
|
||||
expect(service.__mixins__).toContain('SubscribableMixin');
|
||||
});
|
||||
|
||||
describe('emptyCharacteristics', () => {
|
||||
it('returns the empty characteristics', () => {
|
||||
// Given
|
||||
const result = FakeService.emptyCharacteristics();
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('initializes the instance', () => {
|
||||
// Given
|
||||
const service = new FakeService(spec);
|
||||
|
||||
// Then
|
||||
expect(service.kind).toEqual(FakeService.kind);
|
||||
expect(service.instance).toEqual(spec.instance);
|
||||
expect(service.characteristics).toEqual(spec.characteristics);
|
||||
expect(service.widget).toEqual(FakeService.widget);
|
||||
expect(service.widgetComponent).toEqual(spec.widgetComponent);
|
||||
expect(service.layout).toEqual(spec.layout);
|
||||
});
|
||||
});
|
||||
|
||||
describe('restart', () => {
|
||||
it('restarts the service', async () => {
|
||||
// Given
|
||||
const service = new FakeService(spec);
|
||||
spyOn(service, 'notify');
|
||||
spyOn(service, 'start').and.resolveTo(null);
|
||||
spyOn(service, 'stop').and.resolveTo(null);
|
||||
|
||||
// When
|
||||
await service.restart();
|
||||
|
||||
// Then
|
||||
expect(service.notify).toHaveBeenCalledWith(null, {reset: true});
|
||||
expect(service.stop).toHaveBeenCalledBefore(service.start);
|
||||
expect(service.start).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDummy', () => {
|
||||
it('returns false', () => {
|
||||
// Given
|
||||
const service = new FakeService(spec);
|
||||
|
||||
// Then
|
||||
expect(service.isDummy()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initialState', () => {
|
||||
it('returns null', () => {
|
||||
// Given
|
||||
const service = new FakeService(spec);
|
||||
|
||||
// Then
|
||||
expect(service.initialState()).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCharacteristics', () => {
|
||||
it('sets the new characteristics', () => {
|
||||
// Given
|
||||
const service = new FakeService(spec);
|
||||
|
||||
const newCharacteristics = {spam: false};
|
||||
|
||||
// When
|
||||
service.setCharacteristics(newCharacteristics);
|
||||
|
||||
// Then
|
||||
expect(service.characteristics).not.toBe(newCharacteristics);
|
||||
expect(service.characteristics).toEqual(newCharacteristics);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setLayout', () => {
|
||||
it('sets the new layout', () => {
|
||||
// Given
|
||||
const service = new FakeService(spec);
|
||||
|
||||
const newLayout = {x: 1, y: 1, w: 2, h: 2};
|
||||
|
||||
// When
|
||||
service.setLayout(newLayout);
|
||||
|
||||
// Then
|
||||
expect(service.layout).not.toBe(newLayout);
|
||||
expect(service.layout).toEqual(newLayout);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toJSON', () => {
|
||||
it('returns a JSON-serializable representation of the service', () => {
|
||||
// Given
|
||||
const service = new FakeService(spec);
|
||||
|
||||
// When
|
||||
const result = service.toJSON();
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({
|
||||
kind: service.kind,
|
||||
instance: service.instance,
|
||||
characteristics: service.characteristics,
|
||||
layout: service.layout,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DummyService', () => {
|
||||
let spec = null;
|
||||
|
||||
beforeEach(() => {
|
||||
spec = {
|
||||
instance: 'fake_instance',
|
||||
characteristics: {
|
||||
spam: true,
|
||||
},
|
||||
widgetComponent: FakeWidget,
|
||||
layout: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 1,
|
||||
h: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('initializes the instance', () => {
|
||||
// Given
|
||||
const service = new ServicesLib.DummyService(spec);
|
||||
|
||||
// Then
|
||||
expect(service.instance).toBe(null);
|
||||
expect(service.widgetComponent).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDummy', () => {
|
||||
it('returns true', () => {
|
||||
// Given
|
||||
const service = new ServicesLib.DummyService(spec);
|
||||
|
||||
// Then
|
||||
expect(service.isDummy()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('lookupService', () => {
|
||||
let dashboards = null;
|
||||
|
||||
beforeEach(() => {
|
||||
dashboards = DashboardsFactory();
|
||||
});
|
||||
|
||||
it('returns DummyService instance if the specified kind does not exist', () => {
|
||||
// Given
|
||||
const result = ServicesLib.lookupService(
|
||||
dashboards, 'Testing', 'fake_instance'
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result).toBeInstanceOf(ServicesLib.DummyService);
|
||||
expect(result.isDummy()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns DummyService instance if the specified instance does not exist', () => {
|
||||
// Given
|
||||
const result = ServicesLib.lookupService(
|
||||
dashboards, 'FakeService', 'testing'
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result).toBeInstanceOf(ServicesLib.DummyService);
|
||||
expect(result.isDummy()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns the service that matches the specified kind and instace', () => {
|
||||
// Given
|
||||
const result = ServicesLib.lookupService(
|
||||
dashboards, 'FakeService', 'fake_instance'
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result).toBe(dashboards[0].services[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lookupServices', () => {
|
||||
let dashboards = null;
|
||||
|
||||
beforeEach(() => {
|
||||
dashboards = DashboardsFactory();
|
||||
dashboards[0].services.push(new ServicesLib.DummyService());
|
||||
dashboards[0].services.push(new FakeService({
|
||||
instance: 'fake_instance2',
|
||||
}));
|
||||
});
|
||||
|
||||
it('returns all instances of services specified by kind', () => {
|
||||
// Givem
|
||||
const result = ServicesLib.lookupServices(dashboards, 'FakeService');
|
||||
|
||||
// Then
|
||||
expect(result.length).toEqual(3);
|
||||
expect(result[0]).toBe(dashboards[0].services[0]);
|
||||
expect(result[1]).toBe(dashboards[0].services[1]);
|
||||
expect(result[2]).toBe(dashboards[0].services[3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
268
packages/homehub_core/tests/lib/websocket.spec.js
Normal file
268
packages/homehub_core/tests/lib/websocket.spec.js
Normal file
@@ -0,0 +1,268 @@
|
||||
import * as WebSocketLib from 'src/lib/websocket';
|
||||
|
||||
describe('src/lib/websocket', () => {
|
||||
describe('HomeHubWebSocket', () => {
|
||||
const settings = {
|
||||
url: '/websocket',
|
||||
};
|
||||
|
||||
it('includes the subscribable mixin', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
|
||||
// Then
|
||||
expect(webSocket.__mixins__).toContain('SubscribableMixin');
|
||||
});
|
||||
|
||||
it('includes the event source mixin', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
|
||||
// Then
|
||||
expect(webSocket.__mixins__).toContain('EventSourceMixin');
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('initializes the instance', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
|
||||
// Then
|
||||
expect(webSocket.debug).toBe(false);
|
||||
expect(webSocket.settings).toEqual(settings);
|
||||
expect(webSocket.socket).toBe(null);
|
||||
expect(webSocket.reconnectTimeout).toBe(null);
|
||||
expect(webSocket.reconnectCounter).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logDebug', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(console, 'log');
|
||||
});
|
||||
|
||||
it('logs a message if debug is true', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(true, settings);
|
||||
|
||||
// When
|
||||
webSocket.logDebug('Testing');
|
||||
|
||||
// Then
|
||||
expect(console.log).toHaveBeenCalledWith('Testing'); // eslint-disable-line no-console
|
||||
});
|
||||
|
||||
it('does not log a message if debug is false', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
|
||||
// When
|
||||
webSocket.logDebug('Testing');
|
||||
|
||||
// Then
|
||||
expect(console.log).not.toHaveBeenCalled(); // eslint-disable-line no-console
|
||||
});
|
||||
});
|
||||
|
||||
describe('stopReconnect', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(window, 'clearTimeout');
|
||||
});
|
||||
|
||||
it('stops the reconnect process', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
webSocket.reconnectTimeout = 123;
|
||||
webSocket.reconnectCounter = 5;
|
||||
|
||||
// When
|
||||
webSocket.stopReconnect();
|
||||
|
||||
// Then
|
||||
expect(webSocket.reconnectTimeout).toBe(null);
|
||||
expect(webSocket.reconnectCounter).toEqual(0);
|
||||
expect(window.clearTimeout).toHaveBeenCalledWith(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe('startReconnect', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(window, 'setTimeout').and.returnValue(123);
|
||||
});
|
||||
|
||||
it('starts the reconnect process', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
webSocket.reconnectCounter = 5;
|
||||
|
||||
// When
|
||||
webSocket.startReconnect();
|
||||
|
||||
// Then
|
||||
expect(webSocket.reconnectCounter).toEqual(6);
|
||||
expect(webSocket.reconnectTimeout).toEqual(123);
|
||||
expect(window.setTimeout).toHaveBeenCalledWith(webSocket.start, 1000);
|
||||
});
|
||||
|
||||
it('breaks the reconnect process when retry count reaches limit', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
webSocket.reconnectCounter = 30;
|
||||
spyOn(webSocket, 'stopReconnect');
|
||||
|
||||
// When
|
||||
let error = null;
|
||||
try {
|
||||
webSocket.startReconnect();
|
||||
} catch (exc) {
|
||||
error = exc;
|
||||
}
|
||||
|
||||
// Then
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(webSocket.stopReconnect).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
let fakeWebSocket = null;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeWebSocket = jasmine.createSpyObj(['addEventListener', 'close']);
|
||||
spyOn(window, 'WebSocket').and.returnValue(fakeWebSocket);
|
||||
});
|
||||
|
||||
it('configures and opens the websocket connection', () => {
|
||||
// Given
|
||||
let fullSettings = {
|
||||
...settings,
|
||||
protocols: ['spam', 'eggs'],
|
||||
};
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(
|
||||
false, fullSettings
|
||||
);
|
||||
|
||||
// When
|
||||
webSocket.start();
|
||||
|
||||
// Then
|
||||
expect(window.WebSocket).toHaveBeenCalledWith(
|
||||
fullSettings.url, fullSettings.protocols
|
||||
);
|
||||
expect(fakeWebSocket.addEventListener).toHaveBeenCalledWith(
|
||||
'open', webSocket.onSocketOpen
|
||||
);
|
||||
expect(fakeWebSocket.addEventListener).toHaveBeenCalledWith(
|
||||
'close', webSocket.onSocketClose
|
||||
);
|
||||
expect(fakeWebSocket.addEventListener).toHaveBeenCalledWith(
|
||||
'message', webSocket.onSocketMessage
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stop', () => {
|
||||
let fakeWebSocket = null;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeWebSocket = jasmine.createSpyObj(['addEventListener', 'close']);
|
||||
});
|
||||
|
||||
it('closes the websocket connection', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
webSocket.socket = fakeWebSocket;
|
||||
|
||||
// When
|
||||
webSocket.stop();
|
||||
|
||||
// Then
|
||||
expect(fakeWebSocket.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSocketOpen', () => {
|
||||
let fakeWebSocket = null;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeWebSocket = jasmine.createSpyObj(['addEventListener', 'close']);
|
||||
fakeWebSocket.readyState = 1;
|
||||
});
|
||||
|
||||
it('handles the open websocket event', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
webSocket.socket = fakeWebSocket;
|
||||
spyOn(webSocket, 'logDebug');
|
||||
spyOn(webSocket, 'stopReconnect');
|
||||
spyOn(webSocket, 'fireEvent');
|
||||
|
||||
// When
|
||||
webSocket.onSocketOpen();
|
||||
|
||||
// Then
|
||||
expect(webSocket.logDebug).toHaveBeenCalled();
|
||||
expect(webSocket.stopReconnect).toHaveBeenCalled();
|
||||
expect(webSocket.fireEvent).toHaveBeenCalledWith('start');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSocketClose', () => {
|
||||
it('handles the open websocket event', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
spyOn(webSocket, 'logDebug');
|
||||
spyOn(webSocket, 'startReconnect');
|
||||
spyOn(webSocket, 'fireEvent');
|
||||
|
||||
// When
|
||||
webSocket.onSocketClose({code: 1000});
|
||||
|
||||
// Then
|
||||
expect(webSocket.logDebug).toHaveBeenCalled();
|
||||
expect(webSocket.startReconnect).toHaveBeenCalled();
|
||||
expect(webSocket.fireEvent).toHaveBeenCalledWith('stop');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSocketMessage', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(console, 'error');
|
||||
});
|
||||
|
||||
it('gracefully handles JSON parse error', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
spyOn(webSocket, 'notify');
|
||||
|
||||
// When
|
||||
webSocket.onSocketMessage({data: 'spam'});
|
||||
|
||||
// Then
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
jasmine.any(String), jasmine.any(Error)
|
||||
);
|
||||
expect(webSocket.notify).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('parses the event data and notifies the subscribers', () => {
|
||||
// Given
|
||||
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
|
||||
spyOn(webSocket, 'notify');
|
||||
|
||||
const message = {
|
||||
type: 'TESTING',
|
||||
data: {
|
||||
spam: true,
|
||||
},
|
||||
};
|
||||
|
||||
// When
|
||||
webSocket.onSocketMessage({data: JSON.stringify(message)});
|
||||
|
||||
// Then
|
||||
expect(webSocket.notify).toHaveBeenCalledWith(message);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
1280
packages/homehub_core/tests/providers/DashboardsProvider.spec.js
Normal file
1280
packages/homehub_core/tests/providers/DashboardsProvider.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user