Release 1.3.0

This commit is contained in:
2021-08-26 12:33:15 +02:00
commit 9bb72f0207
1148 changed files with 92133 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
require('regenerator-runtime');
const replaceAll = require('string.prototype.replaceall');
replaceAll.shim();
window.crypto = {
getRandomValues: require('polyfill-crypto.getrandomvalues'),
};
require('tests/__setup__/enzyme.setup.js');
let testsContext = require.context('.', true, /\.spec\.js$/);
testsContext.keys().forEach(testsContext);

View File

@@ -0,0 +1,49 @@
import {FakeWidget, FakeService} from 'tests/__fixtures__/services';
export const DashboardsFactory = () => {
return [
{
id: 'testing',
name: 'Testing',
services: [
new FakeService({
instance: 'fake_instance',
widgetComponent: FakeWidget,
characteristics: {
appearance: {
coolor: 'red',
},
spam: true,
},
layout: {
x: 0,
y: 0,
w: 1,
h: 1,
},
}),
new FakeService({
instance: 'other_fake_instance',
widgetComponent: null,
characteristics: {
appearance: {
coolor: 'red',
},
spam: true,
},
layout: {
x: 0,
y: 1,
w: 1,
h: 1,
},
}),
],
},
{
id: 'testing2',
name: 'Testing2',
services: [],
},
];
};

View File

@@ -0,0 +1,23 @@
import {DEFAULT_DASHBOARDS_CONTEXT} from '@bthlabs/homehub-core';
import {DashboardsFactory} from 'tests/__fixtures__/dashboards';
export const DashboardsContextFactory = () => {
return {
...DEFAULT_DASHBOARDS_CONTEXT,
currentDashboardId: 'testing',
dashboards: DashboardsFactory(),
nukeService: jasmine.createSpy(),
saveServiceCharacteristics: jasmine.createSpy(),
saveServiceLayout: jasmine.createSpy(),
addService: jasmine.createSpy(),
isLoading: false,
lastSaveTimestamp: null,
lastSaveError: null,
isSaving: false,
isWebSocketConnected: false,
setCurrentDashboardId: jasmine.createSpy(),
dashboardsHash: null,
addDashboard: jasmine.createSpy(),
};
};

View File

@@ -0,0 +1,39 @@
import {BaseService} from '@bthlabs/homehub-core';
import React from 'react';
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.layoutConstraints = {minW: 1, minH: 1};
FakeWidget.settingsView = FakeWidgetSettingsView;
export class FakeService extends BaseService {
static kind = 'FakeService';
static widget = 'FakeWidget';
async start () {
}
async stop () {
}
}
export const FakeWidgetWithoutSettings = (props) => { // eslint-disable-line no-unused-vars
return <span>FakeWidgetWithoutSettings</span>;
};
FakeWidgetWithoutSettings.defaultLayout = {w: 1, h: 1};
FakeWidgetWithoutSettings.layoutConstraints = {minW: 1, minH: 1};
export class FakeServiceWithoutSettings extends BaseService {
static kind = 'FakeServiceWithoutSettings';
static widget = 'FakeWidgetWithoutSettings';
async start () {
}
async stop () {
}
}

View File

@@ -0,0 +1,29 @@
import * as ServicesFixtures from 'tests/__fixtures__/services';
export const SettingsFactory = () => {
return {
DEBUG: false,
HOMEHUB: {
version: '1.0',
build: 123,
},
INSTALLED_APPS: [],
REDUX_MIDDLEWARES: [],
LOCALES: {
'en-us': 'English (United States)',
'en-gb': 'English (United Kingdom)',
},
SERVICES: {
FakeService: ServicesFixtures.FakeService,
FakeServiceWithoutSettings: ServicesFixtures.FakeServiceWithoutSettings,
},
WIDGETS: {
FakeWidget: ServicesFixtures.FakeWidget,
FakeWidgetWithoutSettings: ServicesFixtures.FakeWidgetWithoutSettings,
},
WEBSOCKET: {
url: 'ws://127.0.0.1:3010/backend/websocket',
},
OFFLINE_MODE: false,
};
};

View File

@@ -0,0 +1,44 @@
export const WeatherFactory = () => {
return {
'coord': {
'lon': 145.77,
'lat': -16.92,
},
'weather': [
{
'id': 802,
'main': 'Clouds',
'description': 'scattered clouds',
'icon': '03n',
},
],
'base': 'stations',
'main': {
'temp': 300.15,
'pressure': 1007,
'humidity': 74,
'temp_min': 300.15,
'temp_max': 300.15,
},
'visibility': 10000,
'wind': {
'speed': 3.6,
'deg': 160,
},
'clouds': {
'all': 40,
},
'dt': 1485790200,
'sys': {
'type': 1,
'id': 8166,
'message': 0.2064,
'country': 'AU',
'sunrise': 1485720272,
'sunset': 1485766550,
},
'id': 2172797,
'name': 'Cairns',
'cod': 200,
};
};

View File

@@ -0,0 +1,7 @@
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({
adapter: new Adapter(),
disableLifecycleMethods: true,
});

View File

@@ -0,0 +1,86 @@
import * as luxon from 'luxon';
import * as ClockLib from 'src/lib/clock';
describe('src/lib/clock', () => {
describe('Clock', () => {
let fakeDateTime = null;
beforeEach(() => {
fakeDateTime = luxon.DateTime.local(1987, 10, 3, 8, 0, 1, 200);
spyOn(window, 'setTimeout').and.returnValue(123);
spyOn(window, 'clearTimeout');
spyOn(luxon.DateTime, 'local').and.returnValue(fakeDateTime);
});
describe('constructor', () => {
it('initializes an instance', () => {
const clock = new ClockLib.Clock();
expect(clock.now).toEqual(fakeDateTime);
expect(clock.timeout).toBe(null);
});
});
describe('clearTimeout', () => {
it('is a noop is timeout is null', () => {
const clock = new ClockLib.Clock();
clock.clearTimeout();
expect(window.clearTimeout).not.toHaveBeenCalled();
});
it('clears the timeout', () => {
const clock = new ClockLib.Clock();
clock.timeout = 123;
clock.clearTimeout();
expect(window.clearTimeout).toHaveBeenCalledWith(123);
expect(clock.timeout).toBe(null);
});
});
describe('onTimeout', () => {
it('handles the clock timeout', () => {
const newFakeDateTime = fakeDateTime.plus({minutes: 1});
luxon.DateTime.local.and.returnValue(newFakeDateTime);
const clock = new ClockLib.Clock();
spyOn(clock, 'clearTimeout');
spyOn(clock, 'fireEvent');
spyOn(clock, 'start');
clock.onTimeout();
expect(clock.now).toEqual(newFakeDateTime);
expect(clock.clearTimeout).toHaveBeenCalled();
expect(clock.fireEvent).toHaveBeenCalledWith('tick');
expect(clock.start).toHaveBeenCalled();
});
});
describe('start', () => {
it('starts the clock', () => {
const clock = new ClockLib.Clock();
clock.start();
expect(window.setTimeout).toHaveBeenCalledWith(clock.onTimeout, 58810);
});
});
describe('stop', () => {
it('stops the clock', () => {
const clock = new ClockLib.Clock();
spyOn(clock, 'clearTimeout');
clock.stop();
expect(clock.clearTimeout).toHaveBeenCalled();
});
});
});
});

View File

@@ -0,0 +1,35 @@
import {shallow} from 'enzyme';
import React from 'react';
import {Button, Modal} from 'src/components';
import * as AboutModal from 'src/main/components/AboutModal';
describe('src/main/components/AboutModal', () => {
describe('AboutModal', () => {
it('configures and renders the about modal', () => {
// Given
const mockOnClose = jasmine.createSpy();
const component = shallow(
<AboutModal.AboutModal
build={123}
show={true}
version='1.0'
onClose={mockOnClose}
/>
);
// Then
const modal = component.find(Modal);
expect(modal.exists()).toBe(true);
expect(modal.prop('show')).toBe(true);
expect(modal.prop('onClose')).toEqual(mockOnClose);
const versionLine = component.find('p.text-muted');
expect(versionLine.text()).toMatch('v1.0-123');
const button = component.find(Button).at(0);
expect(button.exists()).toBe(true);
expect(button.prop('onClick')).toEqual(mockOnClose);
});
});
});

View File

@@ -0,0 +1,123 @@
import {shallow} from 'enzyme';
import React from 'react';
import {Button, Form, Modal} from 'src/components';
import * as AddDashboardModal from 'src/main/components/AddDashboardModal';
describe('src/main/components/AddDashboardModal', () => {
describe('AddDashboardModal', () => {
let mockOnClose = null;
let mockOnSave = null;
beforeEach(() => {
mockOnClose = jasmine.createSpy();
mockOnSave = jasmine.createSpy();
});
it('closes the modal when it hides', async () => {
// Given
const component = shallow(
<AddDashboardModal.AddDashboardModal
show={true}
onClose={mockOnClose}
onSave={mockOnSave}
/>
);
// When
component.find(Form.Control).at(0).simulate(
'change', {target: {value: 'spam'}}
);
const modal = component.find(Modal).at(0);
await modal.invoke('onHide')();
// Then
expect(component.find(Form.Control).at(0).prop('value')).toEqual('');
expect(mockOnClose).toHaveBeenCalled();
});
it('configures and renders the name input', () => {
// Given
const component = shallow(
<AddDashboardModal.AddDashboardModal
show={true}
onClose={mockOnClose}
onSave={mockOnSave}
/>
);
// When
const nameInput = component.find(Form.Control).at(0);
// Then
expect(nameInput.exists()).toBe(true);
expect(nameInput.prop('value')).toEqual('');
});
it('updates the state when name input changes', () => {
// Given
const component = shallow(
<AddDashboardModal.AddDashboardModal
show={true}
onClose={mockOnClose}
onSave={mockOnSave}
/>
);
// When
component.find(Form.Control).at(0).simulate(
'change', {target: {value: 'spam'}}
);
// Then
const nameInput = component.find(Form.Control).at(0);
expect(nameInput.prop('value')).toEqual('spam');
});
it('closes the modal when cancel button is clicked', () => {
// Given
const component = shallow(
<AddDashboardModal.AddDashboardModal
show={true}
onClose={mockOnClose}
onSave={mockOnSave}
/>
);
// When
component.find(Form.Control).at(0).simulate(
'change', {target: {value: 'spam'}}
);
component.find(Button).at(0).simulate('click');
// Then
const nameInput = component.find(Form.Control).at(0);
expect(nameInput.prop('value')).toEqual('');
expect(mockOnClose).toHaveBeenCalled();
});
it('saves and closes the modal when the save button is clicked', () => {
// Given
const component = shallow(
<AddDashboardModal.AddDashboardModal
show={true}
onClose={mockOnClose}
onSave={mockOnSave}
/>
);
// When
component.find(Form.Control).at(0).simulate(
'change', {target: {value: 'spam'}}
);
component.find(Button).at(1).simulate('click');
// Then
const nameInput = component.find(Form.Control).at(0);
expect(nameInput.prop('value')).toEqual('');
expect(mockOnSave).toHaveBeenCalledWith('spam');
expect(mockOnClose).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,18 @@
import {shallow} from 'enzyme';
import React from 'react';
import * as AppLoader from 'src/main/components/AppLoader';
describe('src/main/components/AppLoader', () => {
describe('AppLoader', () => {
it('allows passing an arbitrary class name', () => {
// Given
const component = shallow(
<AppLoader.AppLoader className="test" />
);
// Then
expect(component.hasClass('test')).toBe(true);
});
});
});

View File

@@ -0,0 +1,55 @@
import {shallow} from 'enzyme';
import React from 'react';
import {DynamicGridLayout} from 'src/components';
import * as Dashboard from 'src/main/components/Dashboard';
describe('src/main/components/Dashboard', () => {
describe('Dashboard', () => {
let layout = null;
let mockOnGridLayoutChange = null;
beforeEach(() => {
layout = [{i: 'test', x: 0, y: 0, w: 1, h: 1}];
mockOnGridLayoutChange = jasmine.createSpy();
});
it('configures and renders the DynamicGridLayout', () => {
// Given
const component = shallow(
<Dashboard.Dashboard
layout={layout}
onGridLayoutChange={mockOnGridLayoutChange}
>
<span key="test">It works!</span>
</Dashboard.Dashboard>
);
// Then
const dynamicGridLayout = component.find(DynamicGridLayout).at(0);
expect(dynamicGridLayout.exists()).toBe(true);
expect(dynamicGridLayout.prop('cols')).toEqual(12);
expect(dynamicGridLayout.prop('layout')).toEqual(layout);
expect(dynamicGridLayout.prop('rowHeight')).toEqual(30);
expect(dynamicGridLayout.prop('onLayoutChange')).toEqual(
mockOnGridLayoutChange
);
});
it('renders the children', () => {
// Given
const children = <span key="test">It works!</span>;
const component = shallow(
<Dashboard.Dashboard
layout={layout}
onGridLayoutChange={mockOnGridLayoutChange}
>
{children}
</Dashboard.Dashboard>
);
// Then
expect(component.contains(children)).toBe(true);
});
});
});

View File

@@ -0,0 +1,531 @@
import Popup from '@bthlabs/react-custom-popup';
import {shallow} from 'enzyme';
import React from 'react';
import {AppearancePopup, WidgetSettingsModal} from 'src/main/components';
import * as DashboardItem from 'src/main/components/DashboardItem';
describe('src/main/components/DashboardItem', () => {
describe('DashboardItem', () => {
let fakeServiceState = null;
let mockOnAppearancePopupColorChange = null;
let mockOnServiceRestartButtonClick = null;
let mockOnSettingsButtonClick = null;
let mockOnSettingsModalClose = null;
let mockOnSettingsModalNukeButtonClick = null;
let mockOnSettingsModalSaveButtonClick = null;
beforeEach(() => {
fakeServiceState = jasmine.createSpyObj('ServiceState', ['isLoading']);
fakeServiceState.isLoading.and.returnValue(false);
mockOnAppearancePopupColorChange = jasmine.createSpy();
mockOnServiceRestartButtonClick = jasmine.createSpy();
mockOnSettingsButtonClick = jasmine.createSpy();
mockOnSettingsModalClose = jasmine.createSpy();
mockOnSettingsModalNukeButtonClick = jasmine.createSpy();
mockOnSettingsModalSaveButtonClick = jasmine.createSpy();
});
it('renders the children', () => {
// Given
const children = <span>It works!</span>;
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
{children}
</DashboardItem.DashboardItem>
);
// Then
expect(component.contains(children)).toBe(true);
});
it('renders the draggable handle', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// Then
expect(component.find('.draggable-handle').exists()).toBe(true);
});
it('does not render the restart toolbar item if serviceState is null', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={null}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// Then
expect(component.find('WidgetToolbarItem[_hint="Restart"]').exists()).toBe(
false
);
});
it('does not render the restart toolbar item if service state is loading', () => {
// Given
fakeServiceState.isLoading.and.returnValue(true);
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// Then
expect(component.find('WidgetToolbarItem[_hint="Restart"]').exists()).toBe(
false
);
});
it('configures and renders the restart toolbar item', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
const restartItem = component.find('WidgetToolbarItem[_hint="Restart"]');
// Then
expect(restartItem.exists()).toBe(true);
expect(restartItem.prop('onClick')).toEqual(
mockOnServiceRestartButtonClick
);
});
it('configures and renders the appearance toolbar item', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
const appearanceItem = component.find(
'WidgetToolbarItem[_hint="Appearance"]'
);
// Then
expect(appearanceItem.exists()).toBe(true);
});
it('displays the appearance popup when appearance toolbar item is clicked', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
component.find('WidgetToolbarItem[_hint="Appearance"]').simulate(
'click'
);
// Then
const popup = component.find(Popup).at(0);
expect(popup.prop('visible')).toBe(true);
});
it('does not render the settings toolbar item if hasSettingsView is false', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// Then
expect(component.find('WidgetToolbarItem[_hint="Settings"]').exists()).toBe(
false
);
});
it('configures and renders the settings toolbar item', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={true}
serviceState={fakeServiceState}
settingsView={<span>Settings</span>}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
const settingsItem = component.find(
'WidgetToolbarItem[_hint="Settings"]'
);
// Then
expect(settingsItem.exists()).toBe(true);
expect(settingsItem.prop('onClick')).toEqual(mockOnSettingsButtonClick);
});
it('does not render the delete toolbar item if hasSettingsView is true', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={true}
serviceState={fakeServiceState}
settingsView={<span>Settings</span>}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// Then
expect(component.find('WidgetToolbarItem[_hint="Delete"]').exists()).toBe(
false
);
});
it('configures and renders the delete toolbar item', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
const deleteItem = component.find(
'WidgetToolbarItem[_hint="Delete"]'
);
// Then
expect(deleteItem.exists()).toBe(true);
expect(deleteItem.prop('onClick')).toEqual(
mockOnSettingsModalNukeButtonClick
);
});
it('does not render the WidgetSettingsModal if hasSettingsView is false', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={false}
serviceState={fakeServiceState}
settingsView={null}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
const widgetSettingsModal = component.find(WidgetSettingsModal).at(0);
// Then
expect(widgetSettingsModal.exists()).toBe(false);
});
it('configures and renders the WidgetSettingsModal', () => {
// Given
const settingsView = <span>Settings</span>;
const settingsViewProps = {spam: true};
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={true}
serviceState={fakeServiceState}
settingsView={settingsView}
settingsViewProps={settingsViewProps}
showSettingsModal={true}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
const widgetSettingsModal = component.find(WidgetSettingsModal).at(0);
// Then
expect(widgetSettingsModal.exists()).toBe(true);
expect(widgetSettingsModal.prop('buttons').length).toEqual(1);
expect(widgetSettingsModal.prop('settingsView')).toEqual(settingsView);
expect(widgetSettingsModal.prop('settingsViewProps')).toEqual(
settingsViewProps
);
expect(widgetSettingsModal.prop('show')).toBe(true);
expect(widgetSettingsModal.prop('onClose')).toEqual(
mockOnSettingsModalClose
);
expect(widgetSettingsModal.prop('onCancelButtonClick')).toEqual(
mockOnSettingsModalClose
);
expect(widgetSettingsModal.prop('onSaveButtonClick')).toEqual(
mockOnSettingsModalSaveButtonClick
);
const nukeButtonComponent = shallow(
widgetSettingsModal.prop('buttons')[0]
);
expect(nukeButtonComponent.isEmptyRender()).toBe(false);
expect(nukeButtonComponent.prop('onClick')).toEqual(
mockOnSettingsModalNukeButtonClick
);
});
it('configures and renders the appearance popup wrapper', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={true}
serviceState={fakeServiceState}
settingsView={<span>Settings</span>}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
const popup = component.find(Popup).at(0);
// Then
expect(popup.exists()).toBe(true);
expect(popup.prop('visible')).toBe(false);
});
it('hides the appearance popup wrapper when the overlay is clicked', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={true}
serviceState={fakeServiceState}
settingsView={<span>Settings</span>}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
component.find('WidgetToolbarItem[_hint="Appearance"]').simulate(
'click'
);
component.find(Popup).at(0).invoke('onOverlayClick')();
// Then
const popup = component.find(Popup).at(0);
expect(popup.prop('visible')).toBe(false);
});
it('configures and renders the appearance popup view', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={true}
serviceState={fakeServiceState}
settingsView={<span>Settings</span>}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
const popupView = component.find(AppearancePopup).at(0);
// Then
expect(popupView.exists()).toBe(true);
});
it('handles the AppearancePopup color change', () => {
// Given
const component = shallow(
<DashboardItem.DashboardItem
hasSettingsView={true}
serviceState={fakeServiceState}
settingsView={<span>Settings</span>}
settingsViewProps={{}}
showSettingsModal={false}
onAppearancePopupColorChange={mockOnAppearancePopupColorChange}
onServiceRestartButtonClick={mockOnServiceRestartButtonClick}
onSettingsButtonClick={mockOnSettingsButtonClick}
onSettingsModalClose={mockOnSettingsModalClose}
onSettingsModalNukeButtonClick={mockOnSettingsModalNukeButtonClick}
onSettingsModalSaveButtonClick={mockOnSettingsModalSaveButtonClick}
>
<span>It works!</span>
</DashboardItem.DashboardItem>
);
// When
component.find('WidgetToolbarItem[_hint="Appearance"]').simulate(
'click'
);
component.find(AppearancePopup).at(0).invoke('onColorChange')('red');
// Then
expect(component.find(Popup).at(0).prop('visible')).toBe(false);
expect(mockOnAppearancePopupColorChange).toHaveBeenCalledWith('red');
});
});
});

View File

@@ -0,0 +1,23 @@
import {shallow} from 'enzyme';
import React from 'react';
import * as DummyWidget from 'src/main/components/DummyWidget';
import {UptimeService} from 'src/services/uptime';
describe('src/main/components/DummyWidget', () => {
describe('DummyWidget', () => {
it('renders the service information', () => {
// Given
const service = new UptimeService({instance: 'testing'});
const component = shallow(
<DummyWidget.DummyWidget service={service} />
);
// When
const code = component.find('code').at(0);
// Then
expect(code.text()).toEqual(`${service.widget} (${service.instance})`);
});
});
});

View File

@@ -0,0 +1,544 @@
import {
kServiceOfflineWeather, kServiceTime, kServiceUptime, kServiceWeather,
kWidgetUptime, kWidgetWeather,
} from '@bthlabs/homehub-core';
import {Icon, IconBasicGear} from '@bthlabs/homehub-icons';
import Popup from '@bthlabs/react-custom-popup';
import {shallow} from 'enzyme';
import React from 'react';
import {AboutModal} from 'src/main/components/AboutModal';
import * as StartMenu from 'src/main/components/StartMenu';
import {SERVICES} from 'src/services';
import {WIDGETS} from 'src/widgets';
import {SettingsFactory} from 'tests/__fixtures__/settings';
describe('src/main/components/StartMenu', () => {
describe('ControlledStartMenu', () => {
let mockOnAddDashboard = null;
let mockOnAddService = null;
let fakeSettings = null;
beforeEach(() => {
mockOnAddService = jasmine.createSpy();
mockOnAddDashboard = jasmine.createSpy();
fakeSettings = SettingsFactory();
});
it('renders the start button', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const startButton = component.find('a[_hint="StartButton"]').at(0);
// Then
expect(startButton.exists()).toBe(true);
});
it('displays the start menu popup when the start button is clicked', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
component.find('a[_hint="StartButton"]').at(0).simulate('click');
// Then
const popup = component.find(Popup).at(0);
expect(popup.prop('visible')).toBe(true);
});
it('configures and renders the start menu popup', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const popup = component.find(Popup).at(0);
// Then
expect(popup.exists()).toBe(true);
expect(popup.prop('visible')).toBe(false);
});
it('hides the start menu popup when the overlay is clicked', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
component.find('a[_hint="StartButton"]').at(0).simulate('click');
component.find(Popup).at(0).invoke('onOverlayClick')();
// Then
const popup = component.find(Popup).at(0);
expect(popup.prop('visible')).toBe(false);
});
it('renders the start menu view', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const startMenu = component.find('div[_hint="StartMenu"]').at(0);
// Then
expect(startMenu.exists()).toBe(true);
});
it('hides the start menu popup when the start menu is clicked', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
component.find('a[_hint="StartButton"]').at(0).simulate('click');
component.find('div[_hint="StartMenu"]').at(0).simulate('click');
// Then
const popup = component.find(Popup).at(0);
expect(popup.prop('visible')).toBe(false);
});
it('renders the about toolbar item', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const aboutItem = component.find('div[_hint="About"]').at(0);
// Then
expect(aboutItem.exists()).toBe(true);
});
it('displays the about modal when the about toolbar item is clicked', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
component.find('div[_hint="About"]').at(0).simulate('click');
// Then
const aboutModal = component.find(AboutModal).at(0);
expect(aboutModal.prop('show')).toBe(true);
});
it('configures and renders the add dashboard toolbar item', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const addDashboardItem = component.find('div[_hint="AddDashboard"]').at(0);
// Then
expect(addDashboardItem.exists()).toBe(true);
expect(addDashboardItem.prop('onClick')).toEqual(mockOnAddDashboard);
});
it('renders the reload toolbar item', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const addDashboardItem = component.find('div[_hint="Reload"]').at(0);
// Then
expect(addDashboardItem.exists()).toBe(true);
});
xit('reloads the page when the reload button is clicked', () => {
// Given
spyOn(window.location, 'reload');
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
component.find('div[_hint="Reload"]').at(0).simulate('click');
// Then
expect(window.location.reload).toHaveBeenCalled();
});
describe('Widgets', () => {
let fakeServices = null;
let fakeWidgets = null;
beforeEach(() => {
fakeServices = {
[kServiceOfflineWeather]: SERVICES.kServiceOfflineWeather,
[kServiceTime]: SERVICES.kServiceTime,
[kServiceUptime]: SERVICES.kServiceUptime,
[kServiceWeather]: SERVICES.kServiceWeather,
};
fakeWidgets = {
[kWidgetUptime]: WIDGETS.kWidgetUptime,
[kWidgetWeather]: WIDGETS.kWidgetWeather,
};
});
it('renders the list of widgets available in online mode', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={fakeServices}
settings={fakeSettings}
widgets={fakeWidgets}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const widgets = component.find('div[_hint="Widgets"]').at(0);
// Then
const serviceKeys = widgets.find('div[_hint="Widget"]').map((wrapper) => {
return wrapper.prop('data-service');
});
expect(serviceKeys.length).toEqual(3);
expect(serviceKeys).not.toContain(kServiceOfflineWeather);
expect(serviceKeys).toContain(kServiceTime);
expect(serviceKeys).toContain(kServiceUptime);
expect(serviceKeys).toContain(kServiceWeather);
});
it('renders the list of widgets available in offline mode', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={true}
services={fakeServices}
settings={fakeSettings}
widgets={fakeWidgets}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const widgets = component.find('div[_hint="Widgets"]').at(0);
// Then
const serviceKeys = widgets.find('div[_hint="Widget"]').map((wrapper) => {
return wrapper.prop('data-service');
});
expect(serviceKeys.length).toEqual(2);
expect(serviceKeys).toContain(kServiceOfflineWeather);
expect(serviceKeys).toContain(kServiceTime);
expect(serviceKeys).not.toContain(kServiceUptime);
expect(serviceKeys).not.toContain(kServiceWeather);
});
it('configures and renders a widget item for a known widget', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={fakeServices}
settings={fakeSettings}
widgets={fakeWidgets}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const widgetItem = component.find(
`div[data-service="${kServiceWeather}"]`
).at(
0
);
const icon = widgetItem.find(Icon).at(0);
const title = widgetItem.find('p').at(0);
// Then
expect(widgetItem.hasClass('disabled')).toBe(false);
expect(icon.prop('icon')).toEqual(fakeWidgets[kWidgetWeather].icon);
expect(title.text()).toEqual(fakeWidgets[kWidgetWeather].title);
});
it('configures and renders a widget item for an unknown widget', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={fakeServices}
settings={fakeSettings}
widgets={fakeWidgets}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const widgetItem = component.find(
`div[data-service="${kServiceTime}"]`
).at(
0
);
const icon = widgetItem.find(Icon).at(0);
const title = widgetItem.find('p').at(0);
// Then
expect(icon.prop('icon')).toEqual(IconBasicGear);
expect(title.text()).toEqual('Unnamed Widget');
});
it('renders widgets as disabled if it has no dashboards', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={false}
offlineMode={false}
services={fakeServices}
settings={fakeSettings}
widgets={fakeWidgets}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const widgetItem = component.find(
`div[data-service="${kServiceWeather}"]`
).at(
0
);
// Then
expect(widgetItem.hasClass('disabled')).toBe(true);
});
it('handles a widget item click', () => {
// Given
const mockEventPreventDefault = jasmine.createSpy();
const mockEventStopPropagation = jasmine.createSpy();
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={fakeServices}
settings={fakeSettings}
widgets={fakeWidgets}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const widgetItem = component.find(
`div[data-service="${kServiceTime}"]`
).at(
0
);
widgetItem.simulate('click', {
currentTarget: {
dataset: {service: kServiceTime},
},
preventDefault: mockEventPreventDefault,
stopPropagation: mockEventStopPropagation,
});
// Then
expect(mockOnAddService).toHaveBeenCalledWith(kServiceTime);
expect(mockEventPreventDefault).not.toHaveBeenCalled();
expect(mockEventStopPropagation).not.toHaveBeenCalled();
});
it('cancels the event when it does not have dashboards', () => {
// Given
const mockEventPreventDefault = jasmine.createSpy();
const mockEventStopPropagation = jasmine.createSpy();
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={false}
offlineMode={false}
services={fakeServices}
settings={fakeSettings}
widgets={fakeWidgets}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const widgetItem = component.find(
`div[data-service="${kServiceTime}"]`
).at(
0
);
widgetItem.simulate('click', {
currentTarget: {
dataset: {service: kServiceTime},
},
preventDefault: mockEventPreventDefault,
stopPropagation: mockEventStopPropagation,
});
// Then
expect(mockOnAddService).not.toHaveBeenCalled();
expect(mockEventPreventDefault).toHaveBeenCalled();
expect(mockEventStopPropagation).toHaveBeenCalled();
});
});
it('configures and renders the about modal', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const aboutModal = component.find(AboutModal).at(0);
// Then
expect(aboutModal.exists()).toBe(true);
expect(aboutModal.prop('build')).toEqual(123);
expect(aboutModal.prop('show')).toBe(false);
expect(aboutModal.prop('version')).toEqual('1.0');
});
it('displays the about modal when it closes', () => {
// Given
const component = shallow(
<StartMenu.ControlledStartMenu
hasDashboards={true}
offlineMode={false}
services={SERVICES}
settings={fakeSettings}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
component.find('div[_hint="About"]').at(0).simulate('click');
component.find(AboutModal).at(0).invoke('onClose')();
// Then
const aboutModal = component.find(AboutModal).at(0);
expect(aboutModal.prop('show')).toBe(false);
});
});
});

View File

@@ -0,0 +1,273 @@
import {shallow} from 'enzyme';
import React from 'react';
import {StartMenu} from 'src/main/components/StartMenu';
import * as Taskbar from 'src/main/components/Taskbar';
import {SERVICES} from 'src/services';
import {WIDGETS} from 'src/widgets';
import {DashboardsFactory} from 'tests/__fixtures__/dashboards';
describe('src/main/components/Taskbar', () => {
describe('Taskbar', () => {
let mockOnAddDashboard = null;
let mockOnAddService = null;
let mockOnSelectDashboard = null;
let fakeDashboards = null;
let fakeSavingState = null;
let fakeWebSocketState = null;
beforeEach(() => {
mockOnAddDashboard = jasmine.createSpy();
mockOnAddService = jasmine.createSpy();
mockOnSelectDashboard = jasmine.createSpy();
fakeDashboards = DashboardsFactory();
fakeSavingState = {
isSaving: false,
lastSaveError: null,
lastSaveTimestamp: new Date(1987, 9, 3, 8, 0, 0),
};
fakeWebSocketState = {
isWebSocketConnected: true,
};
});
it('configures and renders the start menu', () => {
// Given
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={false}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const startMenu = component.find(StartMenu).at(0);
// Then
expect(startMenu.exists()).toBe(true);
expect(startMenu.prop('offlineMode')).toBe(false);
expect(startMenu.prop('services')).toEqual(SERVICES);
expect(startMenu.prop('widgets')).toEqual(WIDGETS);
expect(startMenu.prop('onAddDashboard')).toEqual(mockOnAddDashboard);
expect(startMenu.prop('onAddService')).toEqual(mockOnAddService);
});
it('configures renders the dashboard buttons', () => {
// Given
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={false}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
/>
);
// When
const dashboardButtons = component.find('a[_hint="Dashboard"]');
// Then
expect(dashboardButtons.length).toEqual(2);
const dashboardButton = dashboardButtons.at(0);
expect(dashboardButton.hasClass('active')).toBe(true);
expect(dashboardButton.prop('data-dashboard')).toEqual(
fakeDashboards[0].id
);
expect(dashboardButton.text()).toEqual(fakeDashboards[0].name);
expect(dashboardButtons.at(1).hasClass('active')).toBe(false);
});
it('selects a dashboard when its button is clicked', () => {
// Given
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={false}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
onSelectDashboard={mockOnSelectDashboard}
/>
);
// When
component.find('a[_hint="Dashboard"]').at(0).simulate('click', {
currentTarget: {
dataset: {dashboard: fakeDashboards[0].id},
},
});
// Then
expect(mockOnSelectDashboard).toHaveBeenCalledWith(fakeDashboards[0].id);
});
it('does not render websocket status in offline mode', () => {
// Given
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={true}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
onSelectDashboard={mockOnSelectDashboard}
/>
);
// Then
expect(component.find('Icon[_hint="WebSocketConnected"]').exists()).toBe(
false
);
expect(component.find('Icon[_hint="WebSocketNotConnected"]').exists()).toBe(
false
);
});
it('renders the websocket connection status when websocket is connected', () => {
// Given
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={false}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
onSelectDashboard={mockOnSelectDashboard}
/>
);
// Then
expect(component.find('Icon[_hint="WebSocketConnected"]').exists()).toBe(
true
);
expect(component.find('Icon[_hint="WebSocketNotConnected"]').exists()).toBe(
false
);
});
it('renders the websocket connection status when websocket is not connected', () => {
// Given
fakeWebSocketState.isWebSocketConnected = false;
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={false}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
onSelectDashboard={mockOnSelectDashboard}
/>
);
// Then
expect(component.find('Icon[_hint="WebSocketConnected"]').exists()).toBe(
false
);
expect(component.find('Icon[_hint="WebSocketNotConnected"]').exists()).toBe(
true
);
});
it('renders the saving status when it is saving', () => {
// Given
fakeSavingState.isSaving = true;
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={false}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
onSelectDashboard={mockOnSelectDashboard}
/>
);
// Then
expect(component.find('Icon[_hint="IsSaving"]').exists()).toBe(true);
expect(component.find('Icon[_hint="SaveError"]').exists()).toBe(false);
expect(component.find('Icon[_hint="SaveOK"]').exists()).toBe(false);
});
it('renders the saving status when last save was an error', () => {
// Given
fakeSavingState.lastSaveError = 'FIAL';
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={false}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
onSelectDashboard={mockOnSelectDashboard}
/>
);
// Then
expect(component.find('Icon[_hint="IsSaving"]').exists()).toBe(false);
expect(component.find('Icon[_hint="SaveError"]').exists()).toBe(true);
expect(component.find('Icon[_hint="SaveOK"]').exists()).toBe(false);
});
it('renders the saving status when last save was OK', () => {
// Given
const component = shallow(
<Taskbar.Taskbar
currentDashboardId="testing"
dashboards={fakeDashboards}
offlineMode={false}
savingState={fakeSavingState}
services={SERVICES}
webSocketState={fakeWebSocketState}
widgets={WIDGETS}
onAddDashboard={mockOnAddDashboard}
onAddService={mockOnAddService}
onSelectDashboard={mockOnSelectDashboard}
/>
);
// Then
expect(component.find('Icon[_hint="IsSaving"]').exists()).toBe(false);
expect(component.find('Icon[_hint="SaveError"]').exists()).toBe(false);
expect(component.find('Icon[_hint="SaveOK"]').exists()).toBe(true);
});
});
});

View File

@@ -0,0 +1,133 @@
import {shallow} from 'enzyme';
import React from 'react';
import {Button, Modal} from 'src/components';
import * as WidgetSettingsModal from 'src/main/components/WidgetSettingsModal';
describe('src/main/components/WidgetSettingsModal', () => {
describe('WidgetSettingsModal', () => {
let mockOnCancelButtonClick = null;
let mockOnClose = null;
let mockOnSaveButtonClick = null;
let fakeSettingsView = null;
let fakeSettingsViewProps = null;
beforeEach(() => {
mockOnCancelButtonClick = jasmine.createSpy();
mockOnClose = jasmine.createSpy();
mockOnSaveButtonClick = jasmine.createSpy();
fakeSettingsView = jasmine.createSpy().and.returnValue(
<span>Settings</span>
);
fakeSettingsView.displayName = 'FakeSettingsView';
fakeSettingsViewProps = {spam: true};
});
it('configures and renders the modal', () => {
// Given
const component = shallow(
<WidgetSettingsModal.WidgetSettingsModal
buttons={[]}
settingsView={fakeSettingsView}
settingsViewProps={fakeSettingsViewProps}
show={false}
onCancelButtonClick={mockOnCancelButtonClick}
onClose={mockOnClose}
onSaveButtonClick={mockOnSaveButtonClick}
/>
);
// When
const modal = component.find(Modal).at(0);
// Then
expect(modal.exists()).toBe(true);
expect(modal.prop('show')).toBe(false);
expect(modal.prop('onHide')).toEqual(mockOnClose);
});
it('configures and renders the settings view', () => {
// Given
const component = shallow(
<WidgetSettingsModal.WidgetSettingsModal
buttons={[]}
settingsView={fakeSettingsView}
settingsViewProps={fakeSettingsViewProps}
show={false}
onCancelButtonClick={mockOnCancelButtonClick}
onClose={mockOnClose}
onSaveButtonClick={mockOnSaveButtonClick}
/>
);
// When
const settingsView = component.find(fakeSettingsView).at(0);
// Then
expect(settingsView.props()).toEqual(fakeSettingsViewProps);
});
it('renders the additional buttons', () => {
// Given
const button = <Button key="testing">Test</Button>;
const component = shallow(
<WidgetSettingsModal.WidgetSettingsModal
buttons={[button]}
settingsView={fakeSettingsView}
settingsViewProps={fakeSettingsViewProps}
show={false}
onCancelButtonClick={mockOnCancelButtonClick}
onClose={mockOnClose}
onSaveButtonClick={mockOnSaveButtonClick}
/>
);
// Then
expect(component.contains(button)).toBe(true);
});
it('configures and renders the cancel button', () => {
// Given
const component = shallow(
<WidgetSettingsModal.WidgetSettingsModal
buttons={[]}
settingsView={fakeSettingsView}
settingsViewProps={fakeSettingsViewProps}
show={false}
onCancelButtonClick={mockOnCancelButtonClick}
onClose={mockOnClose}
onSaveButtonClick={mockOnSaveButtonClick}
/>
);
// When
const button = component.find('Button[_hint="Cancel"]').at(0);
// Then
expect(button.exists()).toBe(true);
expect(button.prop('onClick')).toEqual(mockOnCancelButtonClick);
});
it('configures and renders the save button', () => {
// Given
const component = shallow(
<WidgetSettingsModal.WidgetSettingsModal
buttons={[]}
settingsView={fakeSettingsView}
settingsViewProps={fakeSettingsViewProps}
show={false}
onCancelButtonClick={mockOnCancelButtonClick}
onClose={mockOnClose}
onSaveButtonClick={mockOnSaveButtonClick}
/>
);
// When
const button = component.find('Button[_hint="Save"]').at(0);
// Then
expect(button.exists()).toBe(true);
expect(button.prop('onClick')).toEqual(mockOnSaveButtonClick);
});
});
});

View File

@@ -0,0 +1,98 @@
import {shallow} from 'enzyme';
import React from 'react';
import * as WidgetToolbar from 'src/main/components/WidgetToolbar';
describe('src/main/components/WidgetToolbar', () => {
describe('WidgetToolbar', () => {
it('allows passing an arbitrary class name', () => {
// Given
const component = shallow(
<WidgetToolbar.WidgetToolbar className="test">
<span>It works!</span>
</WidgetToolbar.WidgetToolbar>
);
// Then
expect(component.hasClass('test')).toBe(true);
});
it('renders a right-side toolbar', () => {
// Given
const component = shallow(
<WidgetToolbar.WidgetToolbar right>
<span>It works!</span>
</WidgetToolbar.WidgetToolbar>
);
// Then
expect(component.hasClass('left')).toBe(false);
expect(component.hasClass('right')).toBe(true);
});
it('renders a left-side toolbar', () => {
// Given
const component = shallow(
<WidgetToolbar.WidgetToolbar>
<span>It works!</span>
</WidgetToolbar.WidgetToolbar>
);
// Then
expect(component.hasClass('left')).toBe(true);
expect(component.hasClass('right')).toBe(false);
});
it('renders the children', () => {
// Given
const children = <span>It works!</span>;
const component = shallow(
<WidgetToolbar.WidgetToolbar right>
{children}
</WidgetToolbar.WidgetToolbar>
);
// Then
expect(component.contains(children)).toBe(true);
});
});
describe('WidgetToolbar.Item', () => {
it('allows passing an arbitrary class name', () => {
// Given
const component = shallow(
<WidgetToolbar.WidgetToolbar.Item className="test">
<span>It works!</span>
</WidgetToolbar.WidgetToolbar.Item>
);
// Then
expect(component.hasClass('test')).toBe(true);
});
it('renders a danger item', () => {
// Given
const component = shallow(
<WidgetToolbar.WidgetToolbar.Item danger>
<span>It works!</span>
</WidgetToolbar.WidgetToolbar.Item>
);
// Then
expect(component.hasClass('danger')).toBe(true);
});
it('renders the children', () => {
// Given
const children = <span>It works!</span>;
const component = shallow(
<WidgetToolbar.WidgetToolbar.Item>
{children}
</WidgetToolbar.WidgetToolbar.Item>
);
// Then
expect(component.contains(children)).toBe(true);
});
});
});

View File

@@ -0,0 +1,170 @@
import {
DashboardsContext, ServiceContainer, ServiceState,
} from '@bthlabs/homehub-core';
import {mount} from 'enzyme';
import React from 'react';
import {Dashboard, DashboardItem, DummyWidget} from 'src/main/components';
import * as DashboardContainer from 'src/main/containers/DashboardContainer';
import {DashboardsContextFactory} from 'tests/__fixtures__/dashboardsContext';
import {FakeWidget} from 'tests/__fixtures__/services';
import {SettingsFactory} from 'tests/__fixtures__/settings';
describe('src/main/containers/DashboardContainer', () => {
describe('ControlledDashboardContainer', () => {
let fakeDashboardsContext = null;
let fakeSettings = null;
beforeEach(() => {
fakeDashboardsContext = DashboardsContextFactory();
fakeSettings = SettingsFactory();
});
it('handles grid layout change', () => {
// Given
const component = mount(
<DashboardsContext.Provider value={fakeDashboardsContext}>
<DashboardContainer.ControlledDashboardContainer
settings={fakeSettings}
/>
</DashboardsContext.Provider>
);
// When
const dashboard = component.find(Dashboard).at(0);
dashboard.invoke('onGridLayoutChange')([
{i: 'fake_instance', x: 0, y: 0, w: 1, h: 1},
{i: 'other_fake_instance', x: 1, y: 1, w: 1, h: 1},
]);
// Then
expect(fakeDashboardsContext.saveServiceLayout).toHaveBeenCalledWith(
'fake_instance', {x: 0, y: 0, w: 1, h: 1}
);
expect(fakeDashboardsContext.saveServiceLayout).toHaveBeenCalledWith(
'other_fake_instance', {x: 1, y: 1, w: 1, h: 1}
);
});
it('configures and renders the dashboard', () => {
// Given
const component = mount(
<DashboardsContext.Provider value={fakeDashboardsContext}>
<DashboardContainer.ControlledDashboardContainer
settings={fakeSettings}
/>
</DashboardsContext.Provider>
);
const expectedLayout = [
{i: 'fake_instance', x: 0, y: 0, w: 1, h: 1, minW: 1, minH: 1},
{i: 'other_fake_instance', x: 0, y: 1, w: 1, h: 1},
];
// When
const dashboard = component.find(Dashboard).at(0);
// Then
expect(dashboard.exists()).toBe(true);
expect(dashboard.prop('layout')).toEqual(expectedLayout);
});
it('configures and render a ServiceContainer for services', () => {
// Given
const component = mount(
<DashboardsContext.Provider value={fakeDashboardsContext}>
<DashboardContainer.ControlledDashboardContainer
settings={fakeSettings}
/>
</DashboardsContext.Provider>
);
// When
const serviceContainers = component.find(ServiceContainer);
// Then
expect(serviceContainers.length).toEqual(2);
const serviceContainer = serviceContainers.at(0);
expect(serviceContainer.prop('instance')).toEqual(
fakeDashboardsContext.dashboards[0].services[0].instance
);
expect(serviceContainer.prop('kind')).toEqual(
fakeDashboardsContext.dashboards[0].services[0].kind
);
});
it('configures and renders a DashboardItem for services', () => {
// Given
const component = mount(
<DashboardsContext.Provider value={fakeDashboardsContext}>
<DashboardContainer.ControlledDashboardContainer
settings={fakeSettings}
/>
</DashboardsContext.Provider>
);
// When
const dashboardItems = component.find(DashboardItem);
// Then
expect(dashboardItems.length).toEqual(2);
const dashboardItem = dashboardItems.at(0);
expect(dashboardItem.prop('settingsView')).toEqual(
FakeWidget.settingsView
);
expect(dashboardItem.prop('settingsViewProps').settings).toEqual(
fakeSettings
);
expect(dashboardItems.at(1).prop('settingsView')).not.toBeDefined();
});
it('configures and renders a widget for services', () => {
// Given
const component = mount(
<DashboardsContext.Provider value={fakeDashboardsContext}>
<DashboardContainer.ControlledDashboardContainer
settings={fakeSettings}
/>
</DashboardsContext.Provider>
);
// When
const widget = component.find(FakeWidget).at(0);
// Then
expect(widget.exists()).toBe(true);
expect(widget.prop('appearance')).toEqual(
fakeDashboardsContext.dashboards[0].services[0].characteristics.appearance
);
expect(widget.prop('service')).toEqual(
fakeDashboardsContext.dashboards[0].services[0]
);
expect(widget.prop('serviceState')).toBeInstanceOf(ServiceState);
expect(widget.prop('setServiceState')).toBeDefined();
});
it('renders a DummyWidget for service with unknown widget', () => {
// Given
const component = mount(
<DashboardsContext.Provider value={fakeDashboardsContext}>
<DashboardContainer.ControlledDashboardContainer
settings={fakeSettings}
/>
</DashboardsContext.Provider>
);
// When
const widget = component.find(DummyWidget).at(0);
// Then
expect(widget.exists()).toBe(true);
expect(widget.prop('service')).toEqual(
fakeDashboardsContext.dashboards[0].services[1]
);
});
});
});

View File

@@ -0,0 +1,270 @@
import {DashboardsContext} from '@bthlabs/homehub-core';
import {RangoAppContext} from '@bthlabs/rango';
import {mount} from 'enzyme';
import React from 'react';
import {
AddDashboardModal, Taskbar, WidgetSettingsModal,
} from 'src/main/components';
import * as TaskbarContainer from 'src/main/containers/TaskbarContainer';
import {DashboardsContextFactory} from 'tests/__fixtures__/dashboardsContext';
import {
FakeService, FakeServiceWithoutSettings, FakeWidget,
} from 'tests/__fixtures__/services';
import {SettingsFactory} from 'tests/__fixtures__/settings';
describe('src/main/containers/TaskbarContainer', () => {
describe('TaskbarContainer', () => {
let fakeDashboardsContext = null;
let fakeSettings = null;
let fakeRangoAppContext = null;
beforeEach(() => {
fakeDashboardsContext = DashboardsContextFactory();
fakeSettings = SettingsFactory();
fakeRangoAppContext = {settings: fakeSettings};
});
it('configures the WidgetSettingsModal to show settings view for added service', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
component.find(Taskbar).at(0).invoke('onAddService')(FakeService.kind);
component.setProps({});
// Then
const widgetSettingsModal = component.find(WidgetSettingsModal).at(0);
expect(widgetSettingsModal.prop('settingsView')).toEqual(
FakeWidget.settingsView
);
expect(widgetSettingsModal.prop('settingsViewProps')).toEqual({
nextCharacteristics: FakeService.emptyCharacteristics(),
settings: fakeSettings,
setNextCharacteristics: jasmine.any(Function),
});
expect(fakeDashboardsContext.addService).not.toHaveBeenCalled();
});
it('immediately adds a service that does not use settings view', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
component.find(Taskbar).at(0).invoke('onAddService')(
FakeServiceWithoutSettings.kind
);
component.setProps({});
// Then
expect(fakeDashboardsContext.addService).toHaveBeenCalledWith(
FakeServiceWithoutSettings.kind,
FakeServiceWithoutSettings.emptyCharacteristics()
);
});
it('handles the WidgetSettingsModal being closed', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
component.find(Taskbar).at(0).invoke('onAddService')(FakeService.kind);
component.setProps({});
component.find(WidgetSettingsModal).at(0).invoke('onClose')();
component.setProps({});
// Then
const widgetSettingsModal = component.find(WidgetSettingsModal).at(0);
expect(widgetSettingsModal.prop('settingsView')).toBe(null);
expect(widgetSettingsModal.prop('settingsViewProps')).toEqual({});
});
it('handles the WidgetSettingsModal save button click', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
component.find(Taskbar).at(0).invoke('onAddService')(FakeService.kind);
component.setProps({});
component.find(WidgetSettingsModal).at(0).invoke('onSaveButtonClick')();
component.setProps({});
// Then
expect(fakeDashboardsContext.addService).toHaveBeenCalledWith(
FakeService.kind,
FakeService.emptyCharacteristics()
);
});
it('displays the AddDashboardModal when add dashboard button is clicked', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
component.find(Taskbar).at(0).invoke('onAddDashboard')();
// Then
const addDashboardModal = component.find(AddDashboardModal).at(0);
expect(addDashboardModal.prop('show')).toBe(true);
});
it('hides the AddDashboardModal when it is closed', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
component.find(Taskbar).at(0).invoke('onAddDashboard')();
component.find(AddDashboardModal).at(0).invoke('onClose')();
// Then
const addDashboardModal = component.find(AddDashboardModal).at(0);
expect(addDashboardModal.prop('show')).toBe(false);
});
it('handles the AddDashboardModal save button click', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
component.find(AddDashboardModal).at(0).invoke('onSave')('Testing2');
// Then
expect(fakeDashboardsContext.addDashboard).toHaveBeenCalledWith(
'Testing2'
);
});
it('sets a selected dashboard as current', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
component.find(Taskbar).at(0).invoke('onSelectDashboard')('testing2');
// Then
expect(fakeDashboardsContext.setCurrentDashboardId).toHaveBeenCalledWith(
'testing2'
);
});
it('configures and renders the Taskbar', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
const taskbar = component.find(Taskbar).at(0);
// Then
expect(taskbar.exists()).toBe(true);
expect(taskbar.prop('currentDashboardId')).toEqual(
fakeDashboardsContext.currentDashboardId
);
expect(taskbar.prop('dashboards')).toEqual(
fakeDashboardsContext.dashboards
);
expect(taskbar.prop('offlineMode')).toEqual(fakeSettings.OFFLINE_MODE);
expect(taskbar.prop('savingState')).toEqual({
isSaving: false,
lastSaveError: null,
lastSaveTimestamp: null,
});
expect(taskbar.prop('services')).toEqual(fakeSettings.SERVICES);
expect(taskbar.prop('webSocketState')).toEqual({
isWebSocketConnected: false,
});
expect(taskbar.prop('widgets')).toEqual(fakeSettings.WIDGETS);
});
it('configures and renders the WidgetSettingsModal', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
const widgetSettingsModal = component.find(WidgetSettingsModal).at(0);
// Then
expect(widgetSettingsModal.exists()).toBe(true);
expect(widgetSettingsModal.prop('show')).toBe(true);
});
it('configures and renders the AddDashboardModal', () => {
// Given
const component = mount(
<RangoAppContext.Provider value={fakeRangoAppContext}>
<DashboardsContext.Provider value={fakeDashboardsContext}>
<TaskbarContainer.TaskbarContainer />
</DashboardsContext.Provider>
</RangoAppContext.Provider>
);
// When
const addDashboardModal = component.find(AddDashboardModal).at(0);
// Then
expect(addDashboardModal.exists()).toBe(true);
});
});
});

View File

@@ -0,0 +1,147 @@
import * as HomeHubCore from '@bthlabs/homehub-core';
import * as StateDataSource from 'src/main/dataSources/StateDataSource';
describe('src/lib/main/dataSources/StateDataSource', () => {
beforeEach(() => {
spyOn(HomeHubCore.API.State, 'get');
spyOn(HomeHubCore.API.State, 'save');
spyOn(HomeHubCore.LocalStorage, 'getItem');
spyOn(HomeHubCore.LocalStorage, 'setItem');
});
afterEach(() => {
StateDataSource.setOfflineMode(false);
});
describe('loadState', () => {
it('throws an error if API call in online mode returns an error', async () => {
// Given
StateDataSource.setOfflineMode(false);
HomeHubCore.API.State.get.and.resolveTo({
error: 'FIAL',
});
// When
let result = null;
try {
await StateDataSource.loadState();
} catch (error) {
result = error;
}
// Then
expect(result).toEqual('FIAL');
});
it('returns state loaded from the API in online mode', async () => {
// Given
StateDataSource.setOfflineMode(false);
const data = {dashboards: []};
HomeHubCore.API.State.get.and.resolveTo({
data: data,
});
// When
const result = await StateDataSource.loadState();
// Then
expect(result).toEqual(data);
expect(HomeHubCore.API.State.get).toHaveBeenCalled();
expect(HomeHubCore.LocalStorage.getItem).not.toHaveBeenCalled();
});
it('returns state loaded from local storage in offline mode', async () => {
// Given
StateDataSource.setOfflineMode(true);
const data = {dashboards: []};
HomeHubCore.LocalStorage.getItem.and.returnValue(JSON.stringify(data));
// When
const result = await StateDataSource.loadState();
// Then
expect(result).toEqual(data);
expect(HomeHubCore.API.State.get).not.toHaveBeenCalled();
expect(HomeHubCore.LocalStorage.getItem).toHaveBeenCalledWith('state');
});
it('returns initial state if it is missing in local storage in offline mode', async () => {
// Given
StateDataSource.setOfflineMode(true);
HomeHubCore.LocalStorage.getItem.and.returnValue(null);
// When
const result = await StateDataSource.loadState();
// Then
expect(result.dashboards.length).toEqual(1);
});
});
describe('doSaveState', () => {
let fakeState = null;
beforeEach(() => {
fakeState = {
dashboards: [
{
id: 'testing',
name: 'Testing',
services: [],
},
],
};
});
it('throws an error if API call in online mode returns an error', async () => {
// Given
StateDataSource.setOfflineMode(false);
HomeHubCore.API.State.save.and.resolveTo({
error: 'FIAL',
});
// When
let result = null;
try {
result = await StateDataSource.doSaveState(fakeState);
} catch (error) {
result = error;
}
// Then
expect(result).toEqual('FIAL');
});
it('returns the result of the API call in online mode', async () => {
// Given
StateDataSource.setOfflineMode(false);
HomeHubCore.API.State.save.and.resolveTo({
data: true,
});
// When
const result = await StateDataSource.doSaveState(fakeState);
// Then
expect(result).toBe(true);
expect(HomeHubCore.API.State.save).toHaveBeenCalledWith(fakeState);
expect(HomeHubCore.LocalStorage.setItem).not.toHaveBeenCalled();
});
it('returns the result of local storage save operation in offline mode', async () => {
// Given
StateDataSource.setOfflineMode(true);
// When
const result = await StateDataSource.doSaveState(fakeState);
// Then
expect(result).toBe(true);
expect(HomeHubCore.API.State.save).not.toHaveBeenCalled();
expect(HomeHubCore.LocalStorage.setItem).toHaveBeenCalledWith(
'state', JSON.stringify(fakeState)
);
});
});
});

View File

@@ -0,0 +1,101 @@
import {DashboardsProvider} from '@bthlabs/homehub-core';
import {shallow} from 'enzyme';
import React from 'react';
import {DashboardContainer, TaskbarContainer} from 'src/main/containers';
import {StateDataSource} from 'src/main/dataSources';
import * as AppView from 'src/main/views/AppView';
import {SettingsFactory} from 'tests/__fixtures__/settings';
describe('src/main/views/AppView', () => {
describe('AppView', () => {
let fakeActions = null;
let fakeSetings = null;
beforeEach(() => {
fakeActions = {
setDocumentTitle: jasmine.createSpy(),
};
fakeSetings = SettingsFactory();
});
describe('componentDidMount', () => {
it('calls the setDocumentTitle action', () => {
// Given
const component = shallow(
<AppView.AppView
actions={fakeActions}
settings={fakeSetings}
/>
);
// When
component.instance().componentDidMount();
// Then
expect(fakeActions.setDocumentTitle).toHaveBeenCalledWith(
jasmine.any(String)
);
});
});
describe('render', () => {
it('configures and renders the DashboardsProvider', () => {
// Given
const component = shallow(
<AppView.AppView
actions={fakeActions}
settings={fakeSetings}
/>
);
// When
const dashboardsProvider = component.find(DashboardsProvider).at(0);
// Then
expect(dashboardsProvider.exists()).toBe(true);
expect(dashboardsProvider.prop('loader')).toBeDefined();
expect(dashboardsProvider.prop('loadDashboards')).toEqual(
StateDataSource.loadState
);
expect(dashboardsProvider.prop('saveDashboards')).toEqual(
StateDataSource.saveState
);
expect(dashboardsProvider.prop('settings')).toEqual(fakeSetings);
});
it('configures and renders the DashboardContainer', () => {
// Given
const component = shallow(
<AppView.AppView
actions={fakeActions}
settings={fakeSetings}
/>
);
// When
const dashboardContainer = component.find(DashboardContainer).at(0);
// Then
expect(dashboardContainer.exists()).toBe(true);
});
it('configures and renders the TaskbarContainer', () => {
// Given
const component = shallow(
<AppView.AppView
actions={fakeActions}
settings={fakeSetings}
/>
);
// When
const taskbarContainer = component.find(TaskbarContainer).at(0);
// Then
expect(taskbarContainer.exists()).toBe(true);
});
});
});
});

View File

@@ -0,0 +1,86 @@
import {shallow} from 'enzyme';
import React from 'react';
import * as ErrorBoundaryView from 'src/main/views/ErrorBoundaryView';
describe('src/main/views/ErrorBoundaryView', () => {
describe('ErrorBoundaryView', () => {
let fakeError = null;
beforeEach(() => {
fakeError = new Error('FIAL');
});
describe('constructor', () => {
it('initializes the state', () => {
// Given
const component = shallow(
<ErrorBoundaryView.ErrorBoundaryView>
<span>It works!</span>
</ErrorBoundaryView.ErrorBoundaryView>
);
// Then
expect(component.state('error')).toBe(null);
});
});
describe('componentDidCatch', () => {
it('updates the state with error', () => {
// Given
const component = shallow(
<ErrorBoundaryView.ErrorBoundaryView>
<span>It works!</span>
</ErrorBoundaryView.ErrorBoundaryView>
);
// When
component.instance().componentDidCatch(fakeError);
// Then
expect(component.state('error')).toEqual(fakeError);
});
});
describe('render', () => {
let fakeChildren = null;
beforeEach(() => {
fakeChildren = <span>It works!</span>;
});
it('renders the children when error is null', () => {
// Given
const component = shallow(
<ErrorBoundaryView.ErrorBoundaryView>
{fakeChildren}
</ErrorBoundaryView.ErrorBoundaryView>
);
// When
component.setState({error: null});
// Then
expect(component.contains(fakeChildren)).toBe(true);
expect(component.contains('.ErrorBoundaryView')).toBe(false);
});
it('renders the error view when error is not null', () => {
// Given
const component = shallow(
<ErrorBoundaryView.ErrorBoundaryView>
{fakeChildren}
</ErrorBoundaryView.ErrorBoundaryView>
);
// When
component.setState({error: fakeError});
// Then
expect(component.contains(fakeChildren)).toBe(false);
expect(component.hasClass('ErrorBoundaryView')).toBe(true);
expect(component.find('p').at(1).text()).toMatch(fakeError.message);
});
});
});
});

View File

@@ -0,0 +1,273 @@
import {DateTime} from 'luxon';
import {Clock} from 'src/lib/clock';
import * as TimeService from 'src/services/time';
describe('src/services/time', () => {
describe('TimeService', () => {
let fakeClock = null;
beforeEach(() => {
fakeClock = jasmine.createSpyObj('FakeClock', [
'addEventListener', 'removeEventListener',
]);
fakeClock.now = DateTime.local();
spyOn(Clock, 'instance').and.returnValue(fakeClock);
});
describe('emptyCharacteristics', () => {
it('returns empty characteristics', () => {
// Given
const result = TimeService.TimeService.emptyCharacteristics();
// Then
expect(result).toEqual({
locale: '',
timeFormat: '',
dateFormat: 'DATE_HUGE',
});
});
});
describe('payloadFromDateTime', () => {
let now = null;
beforeEach(() => {
now = DateTime.local(1985, 10, 3, 8, 0, 0);
spyOn(now, 'setLocale').and.callFake(() => {
return now;
});
spyOn(now, 'toFormat').and.returnValue('Formatted Time String');
spyOn(now, 'toLocaleString');
now.toLocaleString.withArgs(DateTime.TIME_SIMPLE).and.returnValue(
'Simple Time String'
);
now.toLocaleString.withArgs(DateTime.DATE_HUGE).and.returnValue(
'Huge Date String'
);
now.toLocaleString.withArgs(DateTime.DATE_FULL).and.returnValue(
'Full Date String'
);
});
it('sets locale if it is specified', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
characteristics.locale = 'en-us';
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
// When
service.payloadFromDateTime(now);
// Then
expect(now.setLocale).toHaveBeenCalledWith('en-us');
});
it('does not set locale if it is not specified', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
// When
service.payloadFromDateTime(now);
// Then
expect(now.setLocale).not.toHaveBeenCalled();
});
it('uses timeFormat to format the time string if it is specified', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
characteristics.timeFormat = 'hh:mm a';
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
// When
const result = service.payloadFromDateTime(now);
// Then
expect(result.data.time).toEqual('Formatted Time String');
expect(now.toFormat).toHaveBeenCalledWith('hh:mm a');
expect(now.toLocaleString).not.toHaveBeenCalledWith(
DateTime.TIME_SIMPLE
);
});
it('uses default time format to format the time string if timeFormat is not specified', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
// When
const result = service.payloadFromDateTime(now);
// Then
expect(result.data.time).toEqual('Simple Time String');
expect(now.toFormat).not.toHaveBeenCalled();
expect(now.toLocaleString).toHaveBeenCalledWith(DateTime.TIME_SIMPLE);
});
it('uses default date format to format the date string if dateFormat is not specified', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
characteristics.dateFormat = '';
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
// When
const result = service.payloadFromDateTime(now);
// Then
expect(result.data.date).toEqual('Huge Date String');
expect(now.toLocaleString).toHaveBeenCalledWith(DateTime.DATE_HUGE);
});
it('uses dateFormat to format the date string if it is specified', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
characteristics.dateFormat = 'DATE_FULL';
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
// When
const result = service.payloadFromDateTime(now);
// Then
expect(result.data.date).toEqual('Full Date String');
expect(now.toLocaleString).toHaveBeenCalledWith(DateTime.DATE_FULL);
});
});
describe('onClockTick', () => {
it('notfies subscribers with payload', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
const fakePayload = {
data: {
date: '1987-10-03',
time: '08:00 AM',
},
};
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
spyOn(service, 'payloadFromDateTime').and.returnValue(fakePayload);
spyOn(service, 'notify');
// When
service.onClockTick(fakeClock);
// Then
expect(service.notify).toHaveBeenCalledWith(fakePayload);
expect(service.payloadFromDateTime).toHaveBeenCalledWith(
fakeClock.now
);
});
});
describe('initialState', () => {
it('returns payload from the current timestamp', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
const fakePayload = {
data: {
date: '1987-10-03',
time: '08:00 AM',
},
};
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
spyOn(service, 'payloadFromDateTime').and.returnValue(fakePayload);
// When
const result = service.initialState();
// Then
expect(result).toEqual(fakePayload);
expect(service.payloadFromDateTime).toHaveBeenCalledWith(
fakeClock.now
);
});
});
describe('setCharacteristics', () => {
it('triggers the onClockTick to update', () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
const newCharacteristics = {
...characteristics,
locale: 'en-us',
};
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
spyOn(service, 'onClockTick');
// When
service.setCharacteristics(newCharacteristics);
// Then
expect(service.characteristics).toEqual(newCharacteristics);
expect(service.onClockTick).toHaveBeenCalledWith(fakeClock);
});
});
describe('start', () => {
it('adds event listener for the clock tick event', async () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
// When
await service.start();
// Then
expect(fakeClock.addEventListener).toHaveBeenCalledWith(
'tick', service.onClockTick
);
});
});
describe('start', () => {
it('removes event listener for the clock tick event', async () => {
// Given
const characteristics = TimeService.TimeService.emptyCharacteristics();
const service = new TimeService.TimeService({
instance: 'testing',
characteristics: characteristics,
});
// When
await service.stop();
// Then
expect(fakeClock.removeEventListener).toHaveBeenCalledWith(
'tick', service.onClockTick
);
});
});
});
});

View File

@@ -0,0 +1,56 @@
import * as HomeHubCore from '@bthlabs/homehub-core';
import * as UptimeService from 'src/services/uptime';
describe('src/services/uptime', () => {
describe('UptimeService', () => {
let fakeResult = null;
beforeEach(() => {
fakeResult = {
data: 123,
};
spyOn(HomeHubCore.API.Services, 'start').and.returnValue(fakeResult);
spyOn(HomeHubCore.API.Services, 'stop').and.returnValue('ok');
});
describe('start', () => {
it('notifies subscribers with the result of start service API call', async () => {
// Given
const service = new UptimeService.UptimeService({
instance: 'testing',
characteristics: {},
});
spyOn(service, 'notify');
// When
await service.start();
// Then
expect(HomeHubCore.API.Services.start).toHaveBeenCalledWith(
service.kind, 'testing', {}
);
expect(service.notify).toHaveBeenCalledWith(fakeResult);
});
});
describe('stop', () => {
it('calls the stop service API method', async () => {
// Given
const service = new UptimeService.UptimeService({
instance: 'testing',
characteristics: {},
});
// When
await service.stop();
// Then
expect(HomeHubCore.API.Services.stop).toHaveBeenCalledWith(
service.kind, 'testing'
);
});
});
});
});

View File

@@ -0,0 +1,578 @@
import * as HomeHubCore from '@bthlabs/homehub-core';
import noop from 'lodash/noop';
import {DateTime} from 'luxon';
import {Clock} from 'src/lib/clock';
import * as WeatherService from 'src/services/weather';
import {WeatherFactory} from 'tests/__fixtures__/weather';
describe('src/services/weather', () => {
describe('WeatherService', () => {
let fakeCharacteristics = null;
let fakeResult = null;
beforeEach(() => {
fakeCharacteristics = {
city: 'Wroclaw,PL',
units: 'metric',
};
fakeResult = {
data: WeatherFactory(),
};
spyOn(HomeHubCore.API.Services, 'start').and.returnValue(fakeResult);
spyOn(HomeHubCore.API.Services, 'stop').and.returnValue('ok');
});
describe('emptyCharacteristics', () => {
it('returns empty characteristics', () => {
// Given
const result = WeatherService.WeatherService.emptyCharacteristics();
// Then
expect(result).toEqual({
city: '',
units: 'metric',
});
});
});
describe('start', () => {
it('notifies subscribers with the result of start service API call', async () => {
// Given
const service = new WeatherService.WeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'notify');
// When
await service.start();
// Then
expect(HomeHubCore.API.Services.start).toHaveBeenCalledWith(
service.kind, 'testing', fakeCharacteristics
);
expect(service.notify).toHaveBeenCalledWith(fakeResult);
});
});
describe('stop', () => {
it('calls the stop service API method', async () => {
// Given
const service = new WeatherService.WeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
// When
await service.stop();
// Then
expect(HomeHubCore.API.Services.stop).toHaveBeenCalledWith(
service.kind, 'testing'
);
});
});
describe('setCharacteristics', () => {
it('updates the service characteristics', () => {
// Given
const newCharacteristics = {
city: 'Poznan,PL',
units: 'imperial',
};
const service = new WeatherService.WeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'restart');
// When
service.setCharacteristics(newCharacteristics);
// Then
expect(service.characteristics).toEqual(newCharacteristics);
});
it('restarts the service if city characteristic changed', () => {
// Given
const newCharacteristics = {
...fakeCharacteristics,
city: 'Poznan,PL',
};
const service = new WeatherService.WeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'restart');
// When
service.setCharacteristics(newCharacteristics);
// Then
expect(service.restart).toHaveBeenCalled();
});
it('restarts the service if units characteristic changed', () => {
// Given
const newCharacteristics = {
...fakeCharacteristics,
units: 'imperial',
};
const service = new WeatherService.WeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'restart');
// When
service.setCharacteristics(newCharacteristics);
// Then
expect(service.restart).toHaveBeenCalled();
});
});
});
describe('OfflineWeatherService', () => {
let fakeCharacteristics = null;
let fakeClock = null;
let fakeDateTime = null;
let fakeResult = null;
beforeEach(() => {
fakeCharacteristics = {
city: 'Wroclaw,PL',
units: 'metric',
apiKey: 'API_KEY',
};
fakeClock = jasmine.createSpyObj(
'FakeClock', ['addEventListener', 'removeEventListener']
);
fakeClock.now = DateTime.local(1987, 10, 3, 8, 0, 0);
spyOn(Clock, 'instance').and.returnValue(fakeClock);
fakeDateTime = DateTime.local(1987, 10, 3, 8, 0, 0);
spyOn(DateTime, 'fromSeconds').and.returnValue(fakeDateTime);
fakeResult = {
data: WeatherFactory(),
};
spyOn(document, 'createElement');
spyOn(document.body, 'appendChild');
spyOn(document.body, 'removeChild');
spyOn(HomeHubCore.LocalStorage, 'getItem').and.returnValue(null);
spyOn(HomeHubCore.LocalStorage, 'setItem');
});
afterEach(() => {
const kind = WeatherService.OfflineWeatherService.kind;
const callbackKey = `${kind}_testing_callback`;
window[callbackKey] = undefined;
});
it('defines availability properties', () => {
// Then
expect(WeatherService.OfflineWeatherService.availableOnline).toBe(false);
expect(WeatherService.OfflineWeatherService.availableOffline).toBe(true);
});
describe('emptyCharacteristics', () => {
it('returns empty characteristics', () => {
// Given
const result = WeatherService.OfflineWeatherService.emptyCharacteristics();
// Then
expect(result).toEqual({
city: '',
units: 'metric',
apiKey: '',
});
});
});
describe('constructor', () => {
it('initializes an instance', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'this-should-be-an-uuid4',
characteristics: fakeCharacteristics,
});
// Then
expect(service.instanceKey).toEqual(
`${service.kind}_this_should_be_an_uuid4`
);
expect(service.callbackKey).toEqual(
`${service.kind}_this_should_be_an_uuid4_callback`
);
expect(service.lastScriptElement).toBe(null);
expect(service.lastData).toBe(null);
expect(service.lastRefreshDt).toBe(null);
});
});
describe('start', () => {
it('installs the JSONP callback', async () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'refresh');
// When
await service.start();
// Then
expect(window[service.callbackKey]).toEqual(service.onWeatherCallback);
});
it('does not initialize the last data if it is missing from local storage', async () => {
// Given
HomeHubCore.LocalStorage.getItem.and.returnValue(null);
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'refresh');
// When
await service.start();
// Then
expect(service.lastData).toBe(null);
expect(service.lastRefreshDt).toBe(null);
expect(HomeHubCore.LocalStorage.getItem).toHaveBeenCalledWith(
`${service.instanceKey}_lastData`
);
});
it('initializes last data if it is present in local storage', async () => {
// Given
HomeHubCore.LocalStorage.getItem.and.returnValue(fakeResult);
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'refresh');
// When
await service.start();
// Then
expect(service.lastData).toEqual(fakeResult);
expect(service.lastRefreshDt).toEqual(fakeDateTime);
expect(DateTime.fromSeconds).toHaveBeenCalledWith(fakeResult.dt);
});
it('triggers a refresh', async () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'refresh');
// When
await service.start();
// Then
expect(service.refresh).toHaveBeenCalled();
});
it('adds event listener for the clock tick event', async () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'refresh');
// When
await service.start();
// Then
expect(Clock.instance().addEventListener).toHaveBeenCalledWith(
'tick', service.onClockTick
);
});
});
describe('stop', () => {
it('replaces the JSONP callback with a noop', async () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
// When
await service.stop();
// Then
expect(window[service.callbackKey]).toEqual(noop);
});
it('removes event listener for the clock tick event', async () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
// When
await service.stop();
// Then
expect(Clock.instance().removeEventListener).toHaveBeenCalledWith(
'tick', service.onClockTick
);
});
});
describe('initialState', () => {
it('returns the initial state', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
// When
const result = service.initialState();
// Then
expect(result).toEqual({
error: null,
data: null,
});
});
it('returns state with last data if it is present', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
service.lastData = fakeResult;
// When
const result = service.initialState();
// Then
expect(result).toEqual({
error: null,
data: fakeResult,
});
});
});
describe('onClockTick', () => {
it('does not request a refresh when it should not refresh', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'shouldRefresh').and.returnValue(false);
spyOn(service, 'refresh');
// When
service.onClockTick();
// Then
expect(service.refresh).not.toHaveBeenCalled();
expect(service.shouldRefresh).toHaveBeenCalled();
});
it('requests a refresh when it should refresh', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'shouldRefresh').and.returnValue(true);
spyOn(service, 'refresh');
// When
service.onClockTick();
// Then
expect(service.refresh).toHaveBeenCalled();
});
});
describe('shouldRefresh', () => {
it('returns true if last data is null', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
// When
const result = service.shouldRefresh();
// Then
expect(result).toBe(true);
});
it('returns true if last refresh was more than 600 secs ago', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
service.lastData = fakeResult;
service.lastRefreshDt = DateTime.local(1987, 10, 3, 7, 50, 0);
// When
const result = service.shouldRefresh();
// Then
expect(result).toBe(true);
});
it('returns false if last refresh was less than 600 secs ago', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
service.lastData = fakeResult;
service.lastRefreshDt = DateTime.local(1987, 10, 3, 7, 50, 1);
// When
const result = service.shouldRefresh();
// Then
expect(result).toBe(false);
});
});
describe('onWeatherCallback', () => {
it('removes the last script element', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
service.lastScriptElement = jasmine.createSpy();
spyOn(service, 'notify');
// When
service.onWeatherCallback(fakeResult);
// Then
expect(document.body.removeChild).toHaveBeenCalledWith(
service.lastScriptElement
);
});
it('stores the data in local storage and instance', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
service.lastScriptElement = jasmine.createSpy();
spyOn(service, 'notify');
// When
service.onWeatherCallback(fakeResult);
// Then
expect(service.lastData).toEqual(fakeResult);
expect(service.lastRefreshDt).toEqual(fakeClock.now);
expect(HomeHubCore.LocalStorage.setItem).toHaveBeenCalledWith(
`${service.instanceKey}_lastData`, fakeResult
);
});
it('notifies subscribers with the loaded data', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
service.lastScriptElement = jasmine.createSpy();
spyOn(service, 'notify');
// When
service.onWeatherCallback(fakeResult);
// Then
expect(service.notify).toHaveBeenCalledWith({
error: null,
data: fakeResult,
});
});
});
describe('refresh', () => {
it('is a noop when it should not refresh', () => {
// Given
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'shouldRefresh').and.returnValue(false);
// When
service.refresh();
// Then
expect(document.createElement).not.toHaveBeenCalled();
expect(document.body.appendChild).not.toHaveBeenCalled();
});
it('calls the API via JSONP when it should refresh', () => {
// Given
const fakeScriptElement = jasmine.createSpy();
document.createElement.and.returnValue(fakeScriptElement);
const service = new WeatherService.OfflineWeatherService({
instance: 'testing',
characteristics: fakeCharacteristics,
});
spyOn(service, 'shouldRefresh').and.returnValue(true);
// When
service.refresh();
// Then
expect(document.createElement).toHaveBeenCalledWith('script');
expect(fakeScriptElement.async).toBe(true);
expect(fakeScriptElement.src).toBeDefined();
expect(fakeScriptElement.type).toEqual('text/javascript');
expect(service.lastScriptElement).toEqual(fakeScriptElement);
expect(document.body.appendChild).toHaveBeenCalledWith(
fakeScriptElement
);
expect(fakeScriptElement.src.startsWith(
WeatherService.OfflineWeatherService.apiURL
)).toBe(
true
);
const scriptSrcURL = new URL(fakeScriptElement.src);
const scriptSrcURLParams = {};
for (let pair of scriptSrcURL.searchParams.entries()) {
scriptSrcURLParams[pair[0]] = pair[1];
}
expect(scriptSrcURLParams).toEqual({
APPID: fakeCharacteristics.apiKey,
callback: service.callbackKey,
q: fakeCharacteristics.city,
units: fakeCharacteristics.units,
});
});
});
});
});

View File

@@ -0,0 +1,285 @@
import {Widget} from '@bthlabs/homehub-components';
import {ServiceState} from '@bthlabs/homehub-core';
import {shallow} from 'enzyme';
import React from 'react';
import {Form} from 'src/components';
import * as TimeWidgetView from 'src/widgets/TimeWidgetView';
import {FakeService} from 'tests/__fixtures__/services';
import {SettingsFactory} from 'tests/__fixtures__/settings';
describe('src/widgets/TimeWidgetView', () => {
describe('TimeWidgetSettingsView', () => {
let fakeNextCharacteristics = null;
let fakeSettings = null;
let mockSetNextCharacteristics = null;
beforeEach(() => {
fakeNextCharacteristics = {
locale: '',
timeFormat: '',
dateFormat: 'DATE_HUGE',
};
fakeSettings = SettingsFactory();
mockSetNextCharacteristics = jasmine.createSpy();
});
it('calls the setNextCharacteristics callback when locale input changes', () => {
// Given
const component = shallow(
<TimeWidgetView.TimeWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
component.find(Form.Control).at(0).simulate('change', {
target: {value: 'en-gb'},
});
// Then
expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
...fakeNextCharacteristics,
locale: 'en-gb',
});
});
it('calls the setNextCharacteristics callback when time format input changes', () => {
// Given
const component = shallow(
<TimeWidgetView.TimeWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
component.find(Form.Control).at(1).simulate('change', {
target: {value: 'hh:mm a'},
});
// Then
expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
...fakeNextCharacteristics,
timeFormat: 'hh:mm a',
});
});
it('calls the setNextCharacteristics callback when date format input changes', () => {
// Given
const component = shallow(
<TimeWidgetView.TimeWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
component.find(Form.Control).at(2).simulate('change', {
target: {value: 'DATE_FULL'},
});
// Then
expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
...fakeNextCharacteristics,
dateFormat: 'DATE_FULL',
});
});
it('configures and renders the locale input', () => {
// Given
fakeNextCharacteristics = {
...fakeNextCharacteristics,
locale: 'en-gb',
timeFormat: 'hh:mm a',
dateFormat: 'DATE_FULL',
};
const component = shallow(
<TimeWidgetView.TimeWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
const input = component.find(Form.Control).at(0);
// Then
expect(input.exists()).toBe(true);
expect(input.prop('value')).toEqual(fakeNextCharacteristics.locale);
const options = input.find('option');
expect(options.length).toEqual(3);
expect(options.at(1).prop('value')).toEqual('en-us');
});
it('configures and renders the time format input', () => {
// Given
fakeNextCharacteristics = {
...fakeNextCharacteristics,
locale: 'en-gb',
timeFormat: 'hh:mm a',
dateFormat: 'DATE_FULL',
};
const component = shallow(
<TimeWidgetView.TimeWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
const input = component.find(Form.Control).at(1);
// Then
expect(input.exists()).toBe(true);
expect(input.prop('value')).toEqual(fakeNextCharacteristics.timeFormat);
const options = input.find('option');
expect(options.length).toEqual(3);
expect(options.at(1).prop('value')).toEqual('hh:mm a');
});
it('configures and renders the date format input', () => {
// Given
fakeNextCharacteristics = {
...fakeNextCharacteristics,
locale: 'en-gb',
timeFormat: 'hh:mm a',
dateFormat: 'DATE_FULL',
};
const component = shallow(
<TimeWidgetView.TimeWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
const input = component.find(Form.Control).at(2);
// Then
expect(input.exists()).toBe(true);
expect(input.prop('value')).toEqual(fakeNextCharacteristics.dateFormat);
const options = input.find('option');
expect(options.length).toEqual(3);
expect(options.at(0).prop('value')).toEqual('DATE_HUGE');
});
});
describe('TimeWidgetView', () => {
let fakeAppearance = null;
let fakeService = null;
let fakeServiceState = null;
beforeEach(() => {
fakeAppearance = {color: 'red'};
fakeService = new FakeService({
instance: 'testing',
layout: {x: 0, y: 0, h: 1, w: 1},
});
fakeServiceState = new ServiceState({
data: {
time: '08:00 AM',
date: '3 October 1987',
},
});
});
it('defines the widget attributes', () => {
// Then
expect(TimeWidgetView.TimeWidgetView.defaultLayout).toEqual({
h: jasmine.any(Number),
w: jasmine.any(Number),
});
expect(TimeWidgetView.TimeWidgetView.icon).toBeDefined();
expect(TimeWidgetView.TimeWidgetView.layoutConstraints).toEqual({
minH: jasmine.any(Number),
minW: jasmine.any(Number),
});
expect(TimeWidgetView.TimeWidgetView.settingsView).toEqual(
TimeWidgetView.TimeWidgetSettingsView
);
expect(TimeWidgetView.TimeWidgetView.title).toEqual('Time');
});
it('renders empty when service state is null', () => {
// Given
fakeServiceState = null;
const component = shallow(
<TimeWidgetView.TimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// Then
expect(component.isEmptyRender()).toBe(true);
});
it('configures and renders the Widget', () => {
// Given
const component = shallow(
<TimeWidgetView.TimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const widget = component.find(Widget).at(0);
// Then
expect(widget.exists()).toBe(true);
expect(widget.prop('appearance')).toEqual(fakeAppearance);
expect(widget.prop('instance')).toEqual(fakeService.instance);
expect(widget.prop('kind')).toEqual(fakeService.kind);
expect(widget.prop('layout')).toEqual(fakeService.layout);
});
it('renders the time string', () => {
// Given
const component = shallow(
<TimeWidgetView.TimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual(fakeServiceState.data().time);
});
it('renders the date string', () => {
// Given
const component = shallow(
<TimeWidgetView.TimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h6 = component.find('h6').at(0);
// Then
expect(h6.text()).toEqual(fakeServiceState.data().date);
});
});
});

View File

@@ -0,0 +1,226 @@
import {Widget} from '@bthlabs/homehub-components';
import {ServiceState} from '@bthlabs/homehub-core';
import {shallow} from 'enzyme';
import React from 'react';
import * as UptimeWidgetView from 'src/widgets/UptimeWidgetView';
import {FakeService} from 'tests/__fixtures__/services';
describe('src/widgets/UptimeWidgetView', () => {
describe('UptimeWidgetView', () => {
let fakeAppearance = null;
let fakeService = null;
let fakeServiceState = null;
beforeEach(() => {
fakeAppearance = {color: 'red'};
fakeService = new FakeService({
instance: 'testing',
layout: {x: 0, y: 0, h: 1, w: 1},
});
fakeServiceState = new ServiceState({
data: 123,
});
});
it('defines the widget attributes', () => {
// Then
expect(UptimeWidgetView.UptimeWidgetView.defaultLayout).toEqual({
h: jasmine.any(Number),
w: jasmine.any(Number),
});
expect(UptimeWidgetView.UptimeWidgetView.icon).toBe(null);
expect(UptimeWidgetView.UptimeWidgetView.layoutConstraints).toEqual({
minH: jasmine.any(Number),
minW: jasmine.any(Number),
});
expect(UptimeWidgetView.UptimeWidgetView.settingsView).toEqual(
UptimeWidgetView.TimeWidgetSettingsView
);
expect(UptimeWidgetView.UptimeWidgetView.title).toEqual('Uptime');
});
it('renders empty when service state is null', () => {
// Given
fakeServiceState = null;
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// Then
expect(component.isEmptyRender()).toBe(true);
});
it('configures and renders the Widget', () => {
// Given
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const widget = component.find(Widget).at(0);
// Then
expect(widget.exists()).toBe(true);
expect(widget.prop('appearance')).toEqual(fakeAppearance);
expect(widget.prop('instance')).toEqual(fakeService.instance);
expect(widget.prop('kind')).toEqual(fakeService.kind);
expect(widget.prop('layout')).toEqual(fakeService.layout);
});
it('rendes the formatted uptime interval for 1 day', () => {
// Given
fakeServiceState = fakeServiceState.update({data: 86401});
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual('1 day');
});
it('rendes the formatted uptime interval for 2 days', () => {
// Given
fakeServiceState = fakeServiceState.update({data: 86401 * 2});
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual('2 days');
});
it('rendes the formatted uptime interval for 1 hour', () => {
// Given
fakeServiceState = fakeServiceState.update({data: 3601});
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual('1 hour');
});
it('rendes the formatted uptime interval for 2 hours', () => {
// Given
fakeServiceState = fakeServiceState.update({data: 3601 * 2});
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual('2 hours');
});
it('rendes the formatted uptime interval for 1 minute', () => {
// Given
fakeServiceState = fakeServiceState.update({data: 61});
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual('1 minute');
});
it('rendes the formatted uptime interval for 2 minutes', () => {
// Given
fakeServiceState = fakeServiceState.update({data: 61 * 2});
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual('2 minutes');
});
it('rendes the formatted uptime interval for 1 second', () => {
// Given
fakeServiceState = fakeServiceState.update({data: 1});
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual('1 second');
});
it('rendes the formatted uptime interval for 2 seconds', () => {
// Given
fakeServiceState = fakeServiceState.update({data: 2});
const component = shallow(
<UptimeWidgetView.UptimeWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const h4 = component.find('h4').at(0);
// Then
expect(h4.text()).toEqual('2 seconds');
});
});
});

View File

@@ -0,0 +1,429 @@
import {FatalError, Widget} from '@bthlabs/homehub-components';
import {ServiceState} from '@bthlabs/homehub-core';
import {DateTime} from 'luxon';
import {shallow} from 'enzyme';
import React from 'react';
import {Form} from 'src/components';
import * as WeatherWidgetView from 'src/widgets/WeatherWidgetView';
import {FakeService} from 'tests/__fixtures__/services';
import {SettingsFactory} from 'tests/__fixtures__/settings';
import {WeatherFactory} from 'tests/__fixtures__/weather';
describe('src/widgets/WeatherWidgetView', () => {
describe('WeatherWidgetSettingsView', () => {
let fakeNextCharacteristics = null;
let fakeSettings = null;
let mockSetNextCharacteristics = null;
beforeEach(() => {
fakeNextCharacteristics = {
city: '',
units: '',
apiKey: '',
};
fakeSettings = SettingsFactory();
mockSetNextCharacteristics = jasmine.createSpy();
});
it('calls the setNextCharacteristics callback when city input changes', () => {
// Given
const component = shallow(
<WeatherWidgetView.WeatherWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
component.find(Form.Control).at(0).simulate('change', {
target: {value: 'Wroclaw,PL'},
});
// Then
expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
...fakeNextCharacteristics,
city: 'Wroclaw,PL',
});
});
it('calls the setNextCharacteristics callback when units input changes', () => {
// Given
const component = shallow(
<WeatherWidgetView.WeatherWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
component.find(Form.Control).at(1).simulate('change', {
target: {value: 'imperial'},
});
// Then
expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
...fakeNextCharacteristics,
units: 'imperial',
});
});
it('calls the setNextCharacteristics callback when API key input changes', () => {
// Given
const component = shallow(
<WeatherWidgetView.WeatherWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
component.find(Form.Control).at(2).simulate('change', {
target: {value: 'thisisntright'},
});
// Then
expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
...fakeNextCharacteristics,
apiKey: 'thisisntright',
});
});
it('renders empty when nextCharacteristics is empty', () => {
// Given
fakeNextCharacteristics = null;
const component = shallow(
<WeatherWidgetView.WeatherWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// Then
expect(component.isEmptyRender()).toBe(true);
});
it('configures and renders the city input', () => {
// Given
fakeNextCharacteristics = {
...fakeNextCharacteristics,
city: 'Wroclaw,PL',
units: 'metric',
apiKey: 'thisisntright',
};
const component = shallow(
<WeatherWidgetView.WeatherWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
const input = component.find(Form.Control).at(0);
// Then
expect(input.exists()).toBe(true);
expect(input.prop('value')).toEqual(fakeNextCharacteristics.city);
});
it('configures and renders the units input', () => {
// Given
fakeNextCharacteristics = {
...fakeNextCharacteristics,
city: 'Wroclaw,PL',
units: 'metric',
apiKey: 'thisisntright',
};
const component = shallow(
<WeatherWidgetView.WeatherWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
const input = component.find(Form.Control).at(1);
// Then
expect(input.exists()).toBe(true);
expect(input.prop('value')).toEqual(fakeNextCharacteristics.units);
});
it('configures and renders the API key input', () => {
// Given
fakeNextCharacteristics = {
...fakeNextCharacteristics,
city: 'Wroclaw,PL',
units: 'metric',
apiKey: 'thisisntright',
};
const component = shallow(
<WeatherWidgetView.WeatherWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
const input = component.find(Form.Control).at(2);
// Then
expect(input.exists()).toBe(true);
expect(input.prop('value')).toEqual(fakeNextCharacteristics.apiKey);
});
it('does not render the API key input if it is missing in characteristics', () => {
// Given
fakeNextCharacteristics = {
city: 'Wroclaw,PL',
units: 'metric',
};
const component = shallow(
<WeatherWidgetView.WeatherWidgetSettingsView
nextCharacteristics={fakeNextCharacteristics}
settings={fakeSettings}
setNextCharacteristics={mockSetNextCharacteristics}
/>
);
// When
const input = component.find(Form.Control).at(2);
// Then
expect(input.exists()).toBe(false);
});
});
describe('WeatherWidgetView', () => {
let fakeAppearance = null;
let fakeDateTime = null;
let fakeService = null;
let fakeServiceState = null;
let fakeWeather = null;
beforeEach(() => {
fakeAppearance = {color: 'red'};
fakeDateTime = DateTime.local(1987, 10, 3, 8, 0, 0);
fakeService = new FakeService({
instance: 'testing',
layout: {x: 0, y: 0, h: 1, w: 1},
});
fakeWeather = WeatherFactory();
fakeServiceState = new ServiceState({
data: fakeWeather,
});
spyOn(DateTime, 'fromSeconds').and.returnValue(fakeDateTime);
spyOn(fakeDateTime, 'toLocaleString').and.returnValue(
'Formatted DateTime'
);
});
it('defines the widget attributes', () => {
// Then
expect(WeatherWidgetView.WeatherWidgetView.defaultLayout).toEqual({
h: jasmine.any(Number),
w: jasmine.any(Number),
});
expect(WeatherWidgetView.WeatherWidgetView.icon).toBeDefined();
expect(WeatherWidgetView.WeatherWidgetView.layoutConstraints).toEqual({
minH: jasmine.any(Number),
minW: jasmine.any(Number),
});
expect(WeatherWidgetView.WeatherWidgetView.settingsView).toEqual(
WeatherWidgetView.WeatherWidgetSettingsView
);
expect(WeatherWidgetView.WeatherWidgetView.title).toEqual('Weather');
});
it('renders empty when service state is null', () => {
// Given
fakeServiceState = null;
const component = shallow(
<WeatherWidgetView.WeatherWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// Then
expect(component.isEmptyRender()).toBe(true);
});
it('configures and renders the Widget', () => {
// Given
const component = shallow(
<WeatherWidgetView.WeatherWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const widget = component.find(Widget).at(0);
// Then
expect(widget.exists()).toBe(true);
expect(widget.prop('appearance')).toEqual(fakeAppearance);
expect(widget.prop('instance')).toEqual(fakeService.instance);
expect(widget.prop('kind')).toEqual(fakeService.kind);
expect(widget.prop('layout')).toEqual(fakeService.layout);
});
it('renders the loader when state is loading', () => {
// Given
spyOn(fakeServiceState, 'isLoading').and.returnValue(true);
const component = shallow(
<WeatherWidgetView.WeatherWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const loader = component.find('Icon[_hint="Loader"]').at(0);
// Then
expect(loader.exists()).toBe(true);
expect(loader.prop('icon')).toBeDefined();
});
it('does not render the loader when state is not loading', () => {
// Given
const component = shallow(
<WeatherWidgetView.WeatherWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const loader = component.find('Icon[_hint="Loader"]').at(0);
// Then
expect(loader.exists()).toBe(false);
});
it('does not compute and render weather info when service state has no data', () => {
// Given
spyOn(fakeServiceState, 'hasData').and.returnValue(false);
const component = shallow(
<WeatherWidgetView.WeatherWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const weatherInfo = component.find('div[_hint="WeatherInfo"]').at(0);
// Then
expect(weatherInfo.exists()).toBe(false);
});
it('computes and renders weather info when service state has data', () => {
// Given
const component = shallow(
<WeatherWidgetView.WeatherWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const weatherInfo = component.find('div[_hint="WeatherInfo"]').at(0);
// Then
expect(weatherInfo.exists()).toBe(true);
const weatherIcon = component.find('Icon[_hint="WeatherIcon"]').at(0);
expect(weatherIcon.exists()).toBe(true);
expect(weatherIcon.prop('icon')).toBeDefined();
const weatherName = component.find('h5[_hint="WeatherName"]').at(0);
expect(weatherName.exists()).toBe(true);
expect(weatherName.text()).toEqual(fakeWeather.name);
const weatherDescription = component.find('h6[_hint="WeatherDescription"]').at(0);
expect(weatherDescription.exists()).toBe(true);
expect(weatherDescription.text()).toEqual(
fakeWeather.weather[0].description
);
const weatherTemp = component.find('h5[_hint="WeatherTemp"]').at(0);
expect(weatherTemp.exists()).toBe(true);
expect(weatherTemp.text()).toEqual(
`${Math.round(fakeWeather.main.temp)}°`
);
const weatherTempRange = component.find('h6[_hint="WeatherTempRange"]').at(0);
expect(weatherTempRange.exists()).toBe(true);
expect(weatherTempRange.text()).toContain(
`${Math.round(fakeWeather.main.temp_min)}°`
);
expect(weatherTempRange.text()).toContain(
`${Math.round(fakeWeather.main.temp_max)}°`
);
const lastUpdate = component.find('small[_hint="LastUpdate"]').at(0);
expect(lastUpdate.exists()).toBe(true);
expect(lastUpdate.text()).toContain('Formatted DateTime');
expect(DateTime.fromSeconds).toHaveBeenCalledWith(fakeWeather.dt);
expect(fakeDateTime.toLocaleString).toHaveBeenCalledWith(
DateTime.TIME_SIMPLE
);
});
it('does not render FatalError if service state has no fatal error', () => {
// Given
const component = shallow(
<WeatherWidgetView.WeatherWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const fatalError = component.find(FatalError).at(0);
// Then
expect(fatalError.exists()).toBe(false);
});
it('configures and renders FatalError if service state has fatal error', () => {
// Given
spyOn(fakeServiceState, 'hasFatalError').and.returnValue(true);
const component = shallow(
<WeatherWidgetView.WeatherWidgetView
appearance={fakeAppearance}
service={fakeService}
serviceState={fakeServiceState}
/>
);
// When
const fatalError = component.find(FatalError).at(0);
// Then
expect(fatalError.exists()).toBe(true);
expect(fatalError.prop('service')).toEqual(fakeService);
expect(fatalError.prop('serviceState')).toEqual(fakeServiceState);
});
});
});