430 lines
13 KiB
JavaScript
430 lines
13 KiB
JavaScript
|
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);
|
||
|
});
|
||
|
});
|
||
|
});
|