Skip to content

Commit

Permalink
feat: add ability to save filter state
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelbely committed Aug 8, 2019
1 parent 86d5c00 commit fa773be
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 11 deletions.
3 changes: 2 additions & 1 deletion lib/static/modules/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getRefImagesInfo, getAllOpenedImagesInfo, getImagesInfoId, filterByBro, rejectRefImagesInfo,
filterStatus, filterImagesInfo, filterByEqualDiffSizes
} from './find-same-diffs-utils';
import * as localStorageWrapper from './local-storage-wrapper';

export const initial = () => {
return async (dispatch) => {
Expand Down Expand Up @@ -113,7 +114,7 @@ export const toggleScaleImages = () => triggerViewChanges({type: actionNames.VIE
export const toggleGroupByError = () => ({type: actionNames.VIEW_TOGGLE_GROUP_BY_ERROR});
export const toggleLazyLoad = () => ({type: actionNames.VIEW_TOGGLE_LAZY_LOAD_IMAGES});
export const updateBaseHost = (host) => {
window.localStorage.setItem('_gemini-replace-host', host);
localStorageWrapper.setItem('replace-host', host);
return {type: actionNames.VIEW_UPDATE_BASE_HOST, host};
};

Expand Down
70 changes: 70 additions & 0 deletions lib/static/modules/local-storage-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

const _prefix = 'html-reporter';
const _deprecatedKeysCollection = [
{deprecatedKey: '_gemini-replace-host', newKey: _getStorageKey('replace-host')}
];

/**
* Format Storage key
* @param key
* @returns {string}
* @private
*/
function _getStorageKey(key) {
return `${_prefix}:${key}`;
}

/**
* Helping to migrate on new storage keys
* @private
*/
export function updateDeprecatedKeys() {
_deprecatedKeysCollection.forEach(({deprecatedKey, newKey}) => {
if (window.localStorage.hasOwnProperty(deprecatedKey)) {
window.localStorage.setItem(newKey, JSON.stringify(window.localStorage.getItem(deprecatedKey)));
window.localStorage.removeItem(deprecatedKey);
}
});
}

/**
* Wrap localStorage#setItem method
* Support value serialization
* @param key
* @param value
*/
export function setItem(key, value) {
window.localStorage.setItem(_getStorageKey(key), JSON.stringify(value));
}

/**
* Wrap localStorage#getItem method
* Parse deserialize storage value
* If key doesn't exist in storage return defaultValue
* @param key
* @param defaultValue
* @returns {object|string}
*/
export function getItem(key, defaultValue) {
if (!hasItem(key)) {
return defaultValue;
}

const item = window.localStorage.getItem(_getStorageKey(key));

try {
return JSON.parse(item);
} catch (e) {
return item;
}
}

/**
* Checks whether the key is contained in storage.
* @param key
* @returns {boolean}
*/
export function hasItem(key) {
return window.localStorage.hasOwnProperty(_getStorageKey(key));
}
47 changes: 38 additions & 9 deletions lib/static/modules/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import defaultState from './default-state';
import {assign, merge, filter, map, clone, cloneDeep, reduce, find, last} from 'lodash';
import {isSuiteFailed, setStatusToAll, findNode, setStatusForBranch, dateToLocaleString} from './utils';
import {groupErrors} from './group-errors';
import * as localStorageWrapper from './local-storage-wrapper';

const compiledData = window.data || defaultState;
const localStorage = window.localStorage;

function getInitialState(data) {
const {skips, suites, config, total, updated, passed,
Expand All @@ -20,6 +20,10 @@ function getInitialState(data) {
const formattedSuites = formatSuitesData(suites);
const groupedErrors = groupErrors({suites: formattedSuites.suites, viewMode, errorPatterns, filteredBrowsers});

localStorageWrapper.updateDeprecatedKeys();
const view = localStorageWrapper.getItem('view', {});
const host = _loadBaseHost(config.baseHost, localStorageWrapper);

return merge(defaultState, {
gui,
skips,
Expand All @@ -31,17 +35,17 @@ function getInitialState(data) {
all: {total, updated, passed, failed, skipped, retries, warned},
perBrowser
},
view: {
view: merge({
viewMode,
scaleImages,
lazyLoadOffset,
..._loadBaseHost(config.baseHost, localStorage),
filteredBrowsers
}
}, host, view)
}, formattedSuites);
}

export default function reducer(state = getInitialState(compiledData), action) {
export default withBrowserStorage(reducer);
function reducer(state = getInitialState(compiledData), action) {
switch (action.type) {
case actionNames.VIEW_INITIAL: {
const {
Expand All @@ -64,6 +68,8 @@ export default function reducer(state = getInitialState(compiledData), action) {
testNameFilter
});

const view = localStorageWrapper.getItem('view', {});

return merge(
{},
state,
Expand All @@ -75,7 +81,8 @@ export default function reducer(state = getInitialState(compiledData), action) {
apiValues,
view: {scaleImages, lazyLoadOffset}
},
formattedSuites
formattedSuites,
{view}
);
}
case actionNames.RUN_ALL_TESTS: {
Expand Down Expand Up @@ -315,8 +322,7 @@ function _mutateViewMode(state, viewMode) {
groupedErrors,
view: {
...state.view,
viewMode,
expand: 'errors'
viewMode
}
};
}
Expand All @@ -326,7 +332,7 @@ function _loadBaseHost(configuredHost, storage) {
return configuredHost;
}

const storageHost = storage.getItem('_gemini-replace-host');
const storageHost = storage.getItem('replace-host');
const baseHost = storageHost || configuredHost;
const parsedHost = _parseHost(baseHost);

Expand Down Expand Up @@ -375,3 +381,26 @@ function forceUpdateSuiteData(suites, test) {
const id = getSuiteId(test);
suites[id] = cloneDeep(suites[id]);
}

export function withBrowserStorage(reducer) {
return (state, action) => {
const newState = reducer(state, action);

if (/^VIEW_/.test(action.type)) {
const {view} = newState;
localStorageWrapper.setItem('view', {
expand: view.expand,
viewMode: view.viewMode,
showSkipped: view.showSkipped,
showOnlyDiff: view.showOnlyDiff,
scaleImages: view.scaleImages,
// TODO: Uncomment when issues with rendering speed will fixed
// lazyLoadOffset: view.lazyLoadOffset,
groupByError: view.groupByError,
testNameFilter: view.testNameFilter
});
}

return newState;
};
}
71 changes: 71 additions & 0 deletions test/unit/lib/static/modules/local-storage-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';
import * as localStorageWrapper from 'lib/static/modules/local-storage-wrapper';
import {mkStorage} from '../../../utils';

describe('lib/static/modules/local-storage-wrapper', () => {
const prefix = 'html-reporter';

beforeEach(() => {
global.window.localStorage = mkStorage();
});

afterEach(() => {
global.window.localStorage = undefined;
});

describe('updateDeprecatedKeys', () => {
it('should update storage keys if deprecated', () => {
global.window.localStorage.setItem('_gemini-replace-host', 'foo1');
global.window.localStorage.setItem('foo2', 'foo2');

localStorageWrapper.updateDeprecatedKeys();

assert.equal(global.window.localStorage.getItem(`${prefix}:replace-host`), '"foo1"');
assert.equal(global.window.localStorage.getItem('foo2'), 'foo2');
});
});

describe('setItem', () => {
it('should convert value to json and set value to localStorage', () => {
localStorageWrapper.setItem('foo', {bar: []});

assert.equal(global.window.localStorage.getItem(`${prefix}:foo`), '{"bar":[]}');
});
});

describe('getItem', () => {
it('should return parsed value from localStorage', () => {
const value = {bar: []};

localStorageWrapper.setItem('foo', value);

assert.deepEqual(localStorageWrapper.getItem('foo'), value);
});

it('should return default value if key doesn\'t exist in localStorage', () => {
assert.equal(localStorageWrapper.getItem('bar', 'baz'), 'baz');
});

it('should return localStorage value if parsing occur an error', () => {
const value = {bar: []};

localStorageWrapper.setItem('foo', value);
global.window.localStorage.setItem(`${prefix}:foo`, '{foo}');

assert.equal(localStorageWrapper.getItem('foo'), '{foo}');
});
});

describe('hasItem', () => {
it('should return true if key exist in localStorage', () => {
localStorageWrapper.setItem('foo', 'bar');

assert.isTrue(localStorageWrapper.hasItem('foo'));
});

it('should return false if key doesn\'t exist in localStorage', () => {
assert.isFalse(localStorageWrapper.hasItem('foo'));
assert.equal(localStorageWrapper.getItem('foo'), undefined);
});
});
});
53 changes: 53 additions & 0 deletions test/unit/lib/static/modules/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';
import actionNames from 'lib/static/modules/action-names';
import defaultState from 'lib/static/modules/default-state';

const proxyquire = require('proxyquire');
const reducer = proxyquire('lib/static/modules/reducer', {
'./local-storage-wrapper': {
setItem: sinon.stub(),
getItem: sinon.stub()
}
}).default;

describe('lib/static/modules/reducer', () => {
describe('reducer', () => {
describe('regression', () => {
it('shouldn\'t change "Expand" filter when "Show all" or "Show only failed" state changing', function() {
let newState = [
{type: actionNames.VIEW_EXPAND_RETRIES},
{type: actionNames.VIEW_SHOW_ALL}
].reduce(reducer, defaultState);

assert.equal(newState.view.expand, 'retries');

newState = [
{type: actionNames.VIEW_EXPAND_RETRIES},
{type: actionNames.VIEW_SHOW_FAILED}
].reduce(reducer, defaultState);

assert.equal(newState.view.expand, 'retries');
});
});

describe('VIEW_SHOW_ALL', () => {
it('should change "viewMode" field on "all" value', () => {
const action = {type: actionNames.VIEW_SHOW_ALL};

const newState = reducer(defaultState, action);

assert.equal(newState.view.viewMode, 'all');
});
});

describe('VIEW_SHOW_FAILED', () => {
it('should change "viewMode" field on "failed" value', () => {
const action = {type: actionNames.VIEW_SHOW_FAILED};

const newState = reducer(defaultState, action);

assert.equal(newState.view.viewMode, 'failed');
});
});
});
});
22 changes: 21 additions & 1 deletion test/unit/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,25 @@ function mkSuiteTree({suite = mkSuite(), state = mkState(), browsers = [mkBrowse
return suite;
}

function mkStorage() {
const storage = {};

return {
setItem: function(key, value) {
storage[key] = value || '';
},
getItem: function(key) {
return key in storage ? storage[key] : null;
},
removeItem: function(key) {
delete storage[key];
},
hasOwnProperty(prop) {
return prop in storage;
}
};
}

module.exports = {
stubConfig,
stubTool,
Expand All @@ -93,5 +112,6 @@ module.exports = {
mkBrowserResult,
mkTestResult,
mkImagesInfo,
mkSuiteTree
mkSuiteTree,
mkStorage
};

0 comments on commit fa773be

Please sign in to comment.