diff --git a/README.md b/README.md
index 48a2ff21c..a1a3ff8ba 100644
--- a/README.md
+++ b/README.md
@@ -256,7 +256,7 @@ directory.
The routes then can be called from the plugin React components defined in the `plugin.js`. For convenience the plugin name is always passed with options when function- or array-returning form is used to export plugin as the function options property `pluginName`:
```js
- export default ['react', 'axios', function(React, axios, {pluginName, pluginConfig, actions, selectors}) {
+ export default ['react', 'axios', function(React, axios, {pluginName, pluginConfig, actions, actionNames, selectors}) {
class PluginComponent extends React.Component {
// ... somewhere inside the component ...
const result = await axios.get(`/plugin-routes/${pluginName}/plugin-route`);
@@ -271,9 +271,11 @@ directory.
In the example you can also see another convenient properties:
- `pluginName` - plugin name;
+ - `pluginConfig` - plugin configuration;
- `actions` - the html-reporter **Redux** actions;
- - `selectors` - the memoized html-reporter selectors which created using **reselect** library
- - `pluginConfig` - plugin configuration.
+ - `actionNames` - the html-reporter action names, that used in **Redux** actions. To be able to subscribe on html-reporter events;
+ - `selectors` - the memoized html-reporter selectors which created using **reselect** library.
+
Available dependencies:
- `react`
diff --git a/lib/static/components/details.js b/lib/static/components/details.js
index fc0d8dd4e..4f1e8ffb7 100644
--- a/lib/static/components/details.js
+++ b/lib/static/components/details.js
@@ -16,11 +16,15 @@ export default class Details extends Component {
state = {isOpened: false};
handleClick = () => {
- this.setState({isOpened: !this.state.isOpened});
+ this.setState((state, props) => {
+ const newState = {isOpened: !state.isOpened};
- if (this.props.onClick) {
- this.props.onClick();
- }
+ if (props.onClick) {
+ props.onClick(newState);
+ }
+
+ return newState;
+ });
}
render() {
diff --git a/lib/static/modules/action-names.js b/lib/static/modules/action-names.js
index 753b71ed8..78f4b6b16 100644
--- a/lib/static/modules/action-names.js
+++ b/lib/static/modules/action-names.js
@@ -13,7 +13,7 @@ export default {
SUITE_BEGIN: 'SUITE_BEGIN',
TEST_BEGIN: 'TEST_BEGIN',
TEST_RESULT: 'TEST_RESULT',
- TESTS_END: 'TEST_END',
+ TESTS_END: 'TESTS_END',
ACCEPT_SCREENSHOT: 'ACCEPT_SCREENSHOT',
ACCEPT_OPENED_SCREENSHOTS: 'ACCEPT_OPENED_SCREENSHOTS',
CLOSE_SECTIONS: 'CLOSE_SECTIONS',
diff --git a/lib/static/modules/actions.js b/lib/static/modules/actions.js
index 01f64e857..c1df58c1c 100644
--- a/lib/static/modules/actions.js
+++ b/lib/static/modules/actions.js
@@ -5,9 +5,8 @@ import StaticTestsTreeBuilder from '../../tests-tree-builder/static';
import actionNames from './action-names';
import {types as modalTypes} from '../components/modals';
import {QUEUED} from '../../constants/test-statuses';
-import {LOCAL_DATABASE_NAME} from '../../constants/file-names';
import {getHttpErrorMessage} from './utils';
-import {fetchDatabases, mergeDatabases, connectToDatabase} from './sqlite';
+import {fetchDatabases, mergeDatabases, connectToDatabase, getMainDatabaseUrl} from './sqlite';
import {getSuitesTableRows} from './database-utils';
import {setFilteredBrowsers} from './query-params';
import plugins from './plugins';
@@ -23,7 +22,7 @@ export const initGuiReport = () => {
try {
const appState = await axios.get('/init');
- const mainDatabaseUrl = new URL(LOCAL_DATABASE_NAME, window.location.href);
+ const mainDatabaseUrl = getMainDatabaseUrl();
const db = await connectToDatabase(mainDatabaseUrl.href);
await plugins.loadAll(appState.data.config);
@@ -148,6 +147,20 @@ export const stopTests = () => async dispatch => {
}
};
+export const testsEnd = () => async dispatch => {
+ try {
+ const mainDatabaseUrl = getMainDatabaseUrl();
+ const db = await connectToDatabase(mainDatabaseUrl.href);
+
+ dispatch({
+ type: actionNames.TESTS_END,
+ payload: {db}
+ });
+ } catch (e) {
+ dispatch(createNotificationError('testsEnd', e));
+ }
+};
+
export const suiteBegin = (suite) => ({type: actionNames.SUITE_BEGIN, payload: suite});
export const testBegin = (test) => ({type: actionNames.TEST_BEGIN, payload: test});
export const testResult = (result) => ({type: actionNames.TEST_RESULT, payload: result});
@@ -157,7 +170,6 @@ export const toggleLoading = (payload) => ({type: actionNames.TOGGLE_LOADING, pa
export const closeSections = (payload) => ({type: actionNames.CLOSE_SECTIONS, payload});
export const openModal = (payload) => ({type: actionNames.OPEN_MODAL, payload});
export const closeModal = (payload) => ({type: actionNames.CLOSE_MODAL, payload});
-export const testsEnd = () => ({type: actionNames.TESTS_END});
export const runFailed = () => ({type: actionNames.RUN_FAILED_TESTS});
export const expandAll = () => ({type: actionNames.VIEW_EXPAND_ALL});
export const expandErrors = () => ({type: actionNames.VIEW_EXPAND_ERRORS});
diff --git a/lib/static/modules/load-plugin.js b/lib/static/modules/load-plugin.js
index 26a211add..d7500cabd 100644
--- a/lib/static/modules/load-plugin.js
+++ b/lib/static/modules/load-plugin.js
@@ -11,6 +11,7 @@ import immer from 'immer';
import * as reselect from 'reselect';
import axios from 'axios';
import * as selectors from './selectors';
+import actionNames from './action-names';
import Details from '../components/details';
const whitelistedDeps = {
@@ -85,7 +86,7 @@ async function initPlugin(plugin, pluginName, pluginConfig) {
const depArgs = deps.map(dep => whitelistedDeps[dep]);
// cyclic dep, resolve it dynamically
const actions = await import('./actions');
- return plugin(...depArgs, {pluginName, pluginConfig, actions, selectors});
+ return plugin(...depArgs, {pluginName, pluginConfig, actions, actionNames, selectors});
}
return plugin;
diff --git a/lib/static/modules/reducers/db.js b/lib/static/modules/reducers/db.js
index 9abd42129..10fa41411 100644
--- a/lib/static/modules/reducers/db.js
+++ b/lib/static/modules/reducers/db.js
@@ -6,6 +6,14 @@ export default (state, action) => {
case actionNames.INIT_STATIC_REPORT:
case actionNames.INIT_GUI_REPORT: {
const {db} = action.payload;
+
+ return {...state, db};
+ }
+
+ case actionNames.TESTS_END: {
+ closeDatabase(state.db); // close previous connection in order to free memory
+ const {db} = action.payload;
+
return {...state, db};
}
diff --git a/lib/static/modules/sqlite.js b/lib/static/modules/sqlite.js
index da94c6fd0..1d503969b 100644
--- a/lib/static/modules/sqlite.js
+++ b/lib/static/modules/sqlite.js
@@ -3,6 +3,7 @@
import axios from 'axios';
import {flattenDeep} from 'lodash';
import {mergeTables} from '../../common-utils';
+import {LOCAL_DATABASE_NAME} from '../../constants/file-names';
function isRelativeUrl(url) {
try {
@@ -139,8 +140,13 @@ async function connectToDatabase(dbUrl) {
return new SQL.Database(new Uint8Array(data));
}
+function getMainDatabaseUrl() {
+ return new URL(LOCAL_DATABASE_NAME, window.location.href);
+}
+
module.exports = {
fetchDatabases,
mergeDatabases,
- connectToDatabase
+ connectToDatabase,
+ getMainDatabaseUrl
};
diff --git a/test/unit/lib/static/components/details.js b/test/unit/lib/static/components/details.js
index 2d7280b2f..017b16f9f 100644
--- a/test/unit/lib/static/components/details.js
+++ b/test/unit/lib/static/components/details.js
@@ -30,16 +30,34 @@ describe(' ', () => {
assert.equal(text, 'some-title');
});
- it('should call "onClick" handler on click in title', () => {
- const props = {
- title: 'some-title',
- content: 'foo bar',
- onClick: sinon.stub()
- };
+ describe('"onClick" handler', () => {
+ let props;
- const component = mount( );
- component.find('.details__summary').simulate('click');
+ beforeEach(() => {
+ props = {
+ title: 'some-title',
+ content: 'foo bar',
+ onClick: sinon.stub()
+ };
+ });
+
+ it('should call on click in title', () => {
+ const component = mount( );
+
+ component.find('.details__summary').simulate('click');
+
+ assert.calledOnce(props.onClick);
+ });
+
+ it('should call with changed state on each call', () => {
+ const component = mount( );
+
+ component.find('.details__summary')
+ .simulate('click')
+ .simulate('click');
- assert.calledOnceWith(props.onClick);
+ assert.calledWith(props.onClick.firstCall, {isOpened: true});
+ assert.calledWith(props.onClick.secondCall, {isOpened: false});
+ });
});
});
diff --git a/test/unit/lib/static/modules/actions.js b/test/unit/lib/static/modules/actions.js
index 4e06542cc..33f6f119a 100644
--- a/test/unit/lib/static/modules/actions.js
+++ b/test/unit/lib/static/modules/actions.js
@@ -7,13 +7,14 @@ import {LOCAL_DATABASE_NAME} from 'lib/constants/file-names';
describe('lib/static/modules/actions', () => {
const sandbox = sinon.sandbox.create();
- let dispatch, actions, addNotification, getSuitesTableRows, connectToDatabaseStub, pluginsStub;
+ let dispatch, actions, addNotification, getSuitesTableRows, getMainDatabaseUrl, connectToDatabaseStub, pluginsStub;
beforeEach(() => {
dispatch = sandbox.stub();
sandbox.stub(axios, 'post').resolves({data: {}});
addNotification = sandbox.stub();
getSuitesTableRows = sandbox.stub();
+ getMainDatabaseUrl = sandbox.stub().returns({href: 'http://localhost/default/sqlite.db'});
connectToDatabaseStub = sandbox.stub().resolves({});
pluginsStub = {loadAll: sandbox.stub()};
@@ -23,7 +24,7 @@ describe('lib/static/modules/actions', () => {
actions = proxyquire('lib/static/modules/actions', {
'reapop': {addNotification},
'./database-utils': {getSuitesTableRows},
- './sqlite': {connectToDatabase: connectToDatabaseStub},
+ './sqlite': {getMainDatabaseUrl, connectToDatabase: connectToDatabaseStub},
'./plugins': pluginsStub
});
});
@@ -33,16 +34,6 @@ describe('lib/static/modules/actions', () => {
describe('initGuiReport', () => {
beforeEach(() => {
sandbox.stub(axios, 'get').resolves({data: {}});
-
- global.window = {
- location: {
- href: 'http://localhost/random/path.html'
- }
- };
- });
-
- afterEach(() => {
- global.window = undefined;
});
it('should run init action on server', async () => {
@@ -52,7 +43,8 @@ describe('lib/static/modules/actions', () => {
});
it('should fetch database from default html page', async () => {
- global.window.location.href = 'http://127.0.0.1:8080/';
+ const href = 'http://127.0.0.1:8080/sqlite.db';
+ getMainDatabaseUrl.returns({href});
await actions.initGuiReport()(dispatch);
@@ -330,4 +322,43 @@ describe('lib/static/modules/actions', () => {
assert.deepEqual(actions.closeModal(modal), {type: actionNames.CLOSE_MODAL, payload: modal});
});
});
+
+ describe('testsEnd', () => {
+ it('should connect to database', async () => {
+ const href = 'http://127.0.0.1:8080/sqlite.db';
+ getMainDatabaseUrl.returns({href});
+
+ await actions.testsEnd()(dispatch);
+
+ assert.calledOnceWith(connectToDatabaseStub, href);
+ });
+
+ it('should dispatch "TESTS_END" action with db connection', async () => {
+ const db = {};
+ connectToDatabaseStub.resolves(db);
+
+ await actions.testsEnd()(dispatch);
+
+ assert.calledOnceWith(dispatch, {
+ type: actionNames.TESTS_END,
+ payload: {db}
+ });
+ });
+
+ it('should show notification if error appears', async () => {
+ connectToDatabaseStub.rejects(new Error('failed to connect to database'));
+
+ await actions.testsEnd()(dispatch);
+
+ assert.calledOnceWith(
+ addNotification,
+ {
+ dismissAfter: 0,
+ id: 'testsEnd',
+ message: 'failed to connect to database',
+ status: 'error'
+ }
+ );
+ });
+ });
});
diff --git a/test/unit/lib/static/modules/load-plugin.js b/test/unit/lib/static/modules/load-plugin.js
index bd4261286..8b97191fa 100644
--- a/test/unit/lib/static/modules/load-plugin.js
+++ b/test/unit/lib/static/modules/load-plugin.js
@@ -2,6 +2,7 @@
import axios from 'axios';
import loadPlugin from 'lib/static/modules/load-plugin';
+import actionNames from 'lib/static/modules/action-names';
import * as actions from 'lib/static/modules/actions';
import * as selectors from 'lib/static/modules/selectors';
@@ -32,7 +33,7 @@ describe('static/modules/load-plugin', () => {
await loadPlugin('plugin-a');
assert.deepStrictEqual(plugin.args, [
- [{actions, selectors, pluginName: 'plugin-a', pluginConfig: undefined}]
+ [{actions, actionNames, selectors, pluginName: 'plugin-a', pluginConfig: undefined}]
]);
});
@@ -41,7 +42,7 @@ describe('static/modules/load-plugin', () => {
await loadPlugin('plugin-b', config);
assert.deepStrictEqual(plugin.args, [
- [{actions, selectors, pluginName: 'plugin-b', pluginConfig: config}]
+ [{actions, actionNames, selectors, pluginName: 'plugin-b', pluginConfig: config}]
]);
});
@@ -50,7 +51,7 @@ describe('static/modules/load-plugin', () => {
await loadPlugin('plugin-c');
assert.deepStrictEqual(plugin.args, [
- [axios, {actions, selectors, pluginName: 'plugin-c', pluginConfig: undefined}]
+ [axios, {actions, actionNames, selectors, pluginName: 'plugin-c', pluginConfig: undefined}]
]);
});
@@ -59,7 +60,7 @@ describe('static/modules/load-plugin', () => {
await loadPlugin('plugin-d');
assert.deepStrictEqual(plugin.args, [
- [undefined, {actions, selectors, pluginName: 'plugin-d', pluginConfig: undefined}]
+ [undefined, {actions, actionNames, selectors, pluginName: 'plugin-d', pluginConfig: undefined}]
]);
});
});
diff --git a/test/unit/lib/static/modules/sqlite.js b/test/unit/lib/static/modules/sqlite.js
index cba539025..97800c392 100644
--- a/test/unit/lib/static/modules/sqlite.js
+++ b/test/unit/lib/static/modules/sqlite.js
@@ -6,8 +6,10 @@ import proxyquire from 'proxyquire';
import {
fetchDatabases,
mergeDatabases,
- connectToDatabase
+ connectToDatabase,
+ getMainDatabaseUrl
} from 'lib/static/modules/sqlite';
+import {LOCAL_DATABASE_NAME} from 'lib/constants/file-names';
describe('lib/static/modules/sqlite', () => {
const sandbox = sinon.sandbox.create();
@@ -16,7 +18,10 @@ describe('lib/static/modules/sqlite', () => {
sandbox.stub(axios, 'get').resolves();
});
- afterEach(() => sandbox.restore());
+ afterEach(() => {
+ sandbox.restore();
+ global.window = undefined;
+ });
describe('fetchDatabases', () => {
it('should return empty arrays if dbUrls.json not contain useful data', async () => {
@@ -190,10 +195,6 @@ describe('lib/static/modules/sqlite', () => {
};
});
- afterEach(() => {
- global.window = undefined;
- });
-
it('should return null if dataForDbs is empty', async () => {
const mergedDbConnection = await mergeDatabases([]);
@@ -267,10 +268,6 @@ describe('lib/static/modules/sqlite', () => {
};
});
- afterEach(() => {
- global.window = undefined;
- });
-
it('should return connection to database', async () => {
axios.get
.withArgs('http://127.0.0.1:8080/sqlite.db', {responseType: 'arraybuffer'})
@@ -281,4 +278,22 @@ describe('lib/static/modules/sqlite', () => {
assert.instanceOf(db, Database);
});
});
+
+ describe('getMainDatabaseUrl', () => {
+ beforeEach(() => {
+ global.window = {
+ location: {
+ href: 'http://localhost/default/path.html'
+ }
+ };
+ });
+
+ it('should return url to main database', () => {
+ global.window.location.href = 'http://127.0.0.1:8080/';
+
+ const url = getMainDatabaseUrl();
+
+ assert.equal(url.href, `http://127.0.0.1:8080/${LOCAL_DATABASE_NAME}`);
+ });
+ });
});