Release 1.4.0

This commit is contained in:
2022-08-13 10:20:06 +02:00
parent 9bb72f0207
commit 5452306c72
162 changed files with 10015 additions and 4419 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@bthlabs/homehub-core",
"version": "1.3.0",
"version": "1.4.0",
"description": "BTHLabs HomeHub - Core",
"main": "lib/index.js",
"author": "BTHLabs <contact@bthlabs.pl> (https://bthlabs.pl/)",
@@ -53,7 +53,6 @@
"karma-webpack": "4.0.2",
"lodash": "4.17.15",
"luxon": "1.24.1",
"node-sass": "4.14.1",
"null-loader": "4.0.0",
"polyfill-crypto.getrandomvalues": "1.0.0",
"prop-types": "15.7.2",
@@ -62,6 +61,7 @@
"react-svg-loader": "3.0.3",
"regenerator-runtime": "0.13.5",
"sass-loader": "8.0.2",
"sass": "^1.3.2",
"shallow-with-context": "0.4.1",
"style-loader": "1.2.1",
"uuid": "8.2.0",

View File

@@ -16,6 +16,7 @@ export const DEFAULT_DASHBOARDS_CONTEXT = {
setCurrentDashboardId: noop,
dashboardsHash: null,
addDashboard: noop,
loadError: null,
};
export const DashboardsContext = React.createContext(

View File

@@ -1,4 +1,5 @@
import * as localStorage from './localStorage';
import * as rpc from './rpc';
export * from './base';
export * from './dashboards';
@@ -10,3 +11,7 @@ export const LocalStorage = {
getItem: localStorage.getItem,
setItem: localStorage.setItem,
};
export const RPC = {
callMethod: rpc.callMethod,
};

View File

@@ -9,16 +9,12 @@ const Base = SubscribableMixin(
EventSourceMixin(HomeHubBaseClass, ['start', 'stop'])
);
export class HomeHubWebSocket extends Base {
export class BaseConnector extends Base {
constructor (debug, settings) {
super(debug, settings);
this.debug = debug;
this.settings = settings;
this.socket = null;
this.reconnectTimeout = null;
this.reconnectCounter = 0;
}
logDebug (...args) {
if (this.debug) {
@@ -26,6 +22,20 @@ export class HomeHubWebSocket extends Base {
console.log(...args);
}
}
start = () => {
}
stop = () => {
}
}
export class HomeHubWebSocket extends BaseConnector {
constructor (debug, settings) {
super(debug, settings);
this.socket = null;
this.reconnectTimeout = null;
this.reconnectCounter = 0;
}
stopReconnect () {
if (this.reconnectTimeout) {
window.clearTimeout(this.reconnectTimeout);
@@ -51,7 +61,7 @@ export class HomeHubWebSocket extends Base {
this.socket.addEventListener('close', this.onSocketClose);
this.socket.addEventListener('message', this.onSocketMessage);
}
stop () {
stop = () => {
this.socket.close();
}
onSocketOpen = () => {

View File

@@ -1,3 +1,4 @@
import isError from 'lodash/isError';
import React from 'react';
import {v4 as uuidv4} from 'uuid';
@@ -23,6 +24,7 @@ export class DashboardsProvider extends React.PureComponent {
lastSaveError: DEFAULT_DASHBOARDS_CONTEXT.lastSaveError,
isSaving: DEFAULT_DASHBOARDS_CONTEXT.isSaving,
isWebSocketConnected: DEFAULT_DASHBOARDS_CONTEXT.isWebSocketConnected,
loadError: DEFAULT_DASHBOARDS_CONTEXT.loadError,
};
}
handleSaveError = (error) => {
@@ -241,6 +243,7 @@ export class DashboardsProvider extends React.PureComponent {
const nextState = {
isLoading: false,
};
try {
const state = await this.props.loadDashboards();
nextState.dashboards = await this.handleLoadedState(state);
@@ -251,7 +254,7 @@ export class DashboardsProvider extends React.PureComponent {
nextState.lastSaveTimestamp = new Date();
} catch (error) {
nextState.lastSaveError = this.handleSaveError(error);
nextState.loadError = this.handleSaveError(error);
}
this.setState(nextState);
@@ -277,12 +280,23 @@ export class DashboardsProvider extends React.PureComponent {
this.saveDashboards(nextDashboards);
this.setState(nextState);
}
throwLoadError = (errorOrString) => {
if (isError(errorOrString)) {
throw errorOrString;
}
throw new Error(errorOrString);
}
componentDidMount () {
this.setState({
isLoading: true,
});
this.webSocket = new WebSocketLib.HomeHubWebSocket(
const ConnectorKlass = (
this.props.settings.CONNECTOR || WebSocketLib.HomeHubWebSocket
);
this.webSocket = new ConnectorKlass(
this.props.settings.DEBUG, this.props.settings.WEBSOCKET
);
this.webSocket.addEventListener('start', this.onWebSocketStart);
@@ -325,8 +339,13 @@ export class DashboardsProvider extends React.PureComponent {
setCurrentDashboardId: this.setCurrentDashboardId,
dashboardsHash: this.dashboardsHash,
addDashboard: this.addDashboard,
loadError: this.state.loadError,
};
if (!this.state.isLoading && this.state.loadError !== null) {
this.throwLoadError(this.state.loadError);
}
return (
<DashboardsContext.Provider value={contextValue}>
{this.state.isLoading && this.props.loader}

View File

@@ -1,14 +1,14 @@
import * as WebSocketLib from 'src/lib/websocket';
describe('src/lib/websocket', () => {
describe('HomeHubWebSocket', () => {
describe('BaseConnector', () => {
const settings = {
url: '/websocket',
spam: true,
};
it('includes the subscribable mixin', () => {
// Given
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
const webSocket = new WebSocketLib.BaseConnector(false, settings);
// Then
expect(webSocket.__mixins__).toContain('SubscribableMixin');
@@ -16,7 +16,7 @@ describe('src/lib/websocket', () => {
it('includes the event source mixin', () => {
// Given
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
const webSocket = new WebSocketLib.BaseConnector(false, settings);
// Then
expect(webSocket.__mixins__).toContain('EventSourceMixin');
@@ -25,14 +25,11 @@ describe('src/lib/websocket', () => {
describe('constructor', () => {
it('initializes the instance', () => {
// Given
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
const webSocket = new WebSocketLib.BaseConnector(false, settings);
// Then
expect(webSocket.debug).toBe(false);
expect(webSocket.settings).toEqual(settings);
expect(webSocket.socket).toBe(null);
expect(webSocket.reconnectTimeout).toBe(null);
expect(webSocket.reconnectCounter).toEqual(0);
});
});
@@ -63,6 +60,24 @@ describe('src/lib/websocket', () => {
expect(console.log).not.toHaveBeenCalled(); // eslint-disable-line no-console
});
});
});
describe('HomeHubWebSocket', () => {
const settings = {
url: '/websocket',
};
describe('constructor', () => {
it('initializes the instance', () => {
// Given
const webSocket = new WebSocketLib.HomeHubWebSocket(false, settings);
// Then
expect(webSocket.socket).toBe(null);
expect(webSocket.reconnectTimeout).toBe(null);
expect(webSocket.reconnectCounter).toEqual(0);
});
});
describe('stopReconnect', () => {
beforeEach(() => {

View File

@@ -94,6 +94,9 @@ describe('src/providers/DashboardsProvider', () => {
expect(component.state('isWebSocketConnected')).toEqual(
DEFAULT_DASHBOARDS_CONTEXT.isWebSocketConnected
);
expect(component.state('loadError')).toBe(
DEFAULT_DASHBOARDS_CONTEXT.loadError
);
});
});
@@ -906,6 +909,7 @@ describe('src/providers/DashboardsProvider', () => {
</DashboardsProvider>
);
spyOn(component.instance(), 'handleLoadedState').and.resolveTo([]);
spyOn(component.instance(), 'throwLoadError');
// When
await component.instance().loadDashboards();
@@ -914,8 +918,11 @@ describe('src/providers/DashboardsProvider', () => {
expect(component.state('dashboards')).toEqual([]);
expect(component.state('currentDashboardId')).toBe(null);
expect(component.state('lastSaveTimestamp')).toBe(null);
expect(component.state('lastSaveError')).toEqual('FIAL');
expect(component.state('loadError')).toEqual('FIAL');
expect(component.instance().handleLoadedState).not.toHaveBeenCalled();
expect(component.instance().throwLoadError).toHaveBeenCalledWith(
component.state('loadError'),
);
});
});
@@ -1018,6 +1025,46 @@ describe('src/providers/DashboardsProvider', () => {
});
});
describe('throwLoadError', () => {
it('throws the error as is if it is an Error', () => {
// Given
const error = new Error('FIAL');
const component = shallow(
<DashboardsProvider
loader={<Loader />}
loadDashboards={loadDashboards}
saveDashboards={saveDashboards}
settings={settings}
>
<Children />
</DashboardsProvider>
);
// When
expect(() => component.instance().throwLoadError(error)).toThrow(error);
});
it('wraps a string in an error and throws it', () => {
// Given
const component = shallow(
<DashboardsProvider
loader={<Loader />}
loadDashboards={loadDashboards}
saveDashboards={saveDashboards}
settings={settings}
>
<Children />
</DashboardsProvider>
);
// When
expect(() => component.instance().throwLoadError('FIAL')).toThrowError(
Error, 'FIAL',
);
});
});
describe('componentDidMount', () => {
let fakeHomeHubWebSocket = null;
let mockWebSocketUnsubscriber = null;
@@ -1094,6 +1141,48 @@ describe('src/providers/DashboardsProvider', () => {
expect(fakeHomeHubWebSocket.start).toHaveBeenCalled();
});
it('configures and starts the websocket connection using custom connector', () => {
// Given
settings.CONNECTOR = jasmine.createSpy().and.returnValue(
fakeHomeHubWebSocket
);
const component = shallow(
<DashboardsProvider
loader={<Loader />}
loadDashboards={loadDashboards}
saveDashboards={saveDashboards}
settings={settings}
>
<Children />
</DashboardsProvider>
);
spyOn(component.instance(), 'loadDashboards');
// When
component.instance().componentDidMount();
// Then
expect(component.instance().webSocket).toEqual(fakeHomeHubWebSocket);
expect(WebSocketLib.HomeHubWebSocket).not.toHaveBeenCalled();
expect(settings.CONNECTOR).toHaveBeenCalledWith(
settings.DEBUG, settings.WEBSOCKET
);
expect(fakeHomeHubWebSocket.addEventListener).toHaveBeenCalledWith(
'start', component.instance().onWebSocketStart
);
expect(fakeHomeHubWebSocket.addEventListener).toHaveBeenCalledWith(
'stop', component.instance().onWebSocketStop
);
expect(component.instance().webSocketUnsubscriber).toEqual(
mockWebSocketUnsubscriber
);
expect(fakeHomeHubWebSocket.subscribe).toHaveBeenCalledWith(
component.instance().onWebSocketMessage
);
expect(fakeHomeHubWebSocket.start).toHaveBeenCalled();
});
it('does not start the websocket in offline mode', () => {
// Given
settings.OFFLINE_MODE = true;
@@ -1235,6 +1324,7 @@ describe('src/providers/DashboardsProvider', () => {
setCurrentDashboardId: component.instance().setCurrentDashboardId,
dashboardsHash: component.instance().dashboardsHash,
addDashboard: component.instance().addDashboard,
loadError: component.state('loadError'),
});
});

File diff suppressed because it is too large Load Diff