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,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,
});
});
});
});
});