blob: 4aa42a25868b31be8429a427812dc9c42564fb11 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chai';
import sinon from 'sinon';
import * as hotlists from './hotlists.js';
import * as example from 'shared/test/constants-hotlists.js';
import * as exampleIssues from 'shared/test/constants-issueV0.js';
import * as exampleUsers from 'shared/test/constants-users.js';
import {prpcClient} from 'prpc-client-instance.js';
let dispatch;
describe('hotlist reducers', () => {
it('root reducer initial state', () => {
const actual = hotlists.reducer(undefined, {type: null});
const expected = {
name: null,
byName: {},
hotlistItems: {},
requests: {
deleteHotlist: {error: null, requesting: false},
fetch: {error: null, requesting: false},
fetchItems: {error: null, requesting: false},
removeEditors: {error: null, requesting: false},
removeItems: {error: null, requesting: false},
rerankItems: {error: null, requesting: false},
update: {error: null, requesting: false},
},
};
assert.deepEqual(actual, expected);
});
it('name updates on SELECT', () => {
const action = {type: hotlists.SELECT, name: example.NAME};
const actual = hotlists.nameReducer(null, action);
assert.deepEqual(actual, example.NAME);
});
it('byName updates on RECEIVE_HOTLIST', () => {
const action = {type: hotlists.RECEIVE_HOTLIST, hotlist: example.HOTLIST};
const actual = hotlists.byNameReducer({}, action);
assert.deepEqual(actual, example.BY_NAME);
});
it('byName fills in missing fields on RECEIVE_HOTLIST', () => {
const action = {
type: hotlists.RECEIVE_HOTLIST,
hotlist: {name: example.NAME},
};
const actual = hotlists.byNameReducer({}, action);
const hotlist = {name: example.NAME, defaultColumns: [], editors: []};
assert.deepEqual(actual, {[example.NAME]: hotlist});
});
it('hotlistItems updates on FETCH_ITEMS_SUCCESS', () => {
const action = {
type: hotlists.FETCH_ITEMS_SUCCESS,
name: example.NAME,
items: [example.HOTLIST_ITEM],
};
const actual = hotlists.hotlistItemsReducer({}, action);
assert.deepEqual(actual, example.HOTLIST_ITEMS);
});
});
describe('hotlist selectors', () => {
it('name', () => {
const state = {hotlists: {name: example.NAME}};
assert.deepEqual(hotlists.name(state), example.NAME);
});
it('byName', () => {
const state = {hotlists: {byName: example.BY_NAME}};
assert.deepEqual(hotlists.byName(state), example.BY_NAME);
});
it('hotlistItems', () => {
const state = {hotlists: {hotlistItems: example.HOTLIST_ITEMS}};
assert.deepEqual(hotlists.hotlistItems(state), example.HOTLIST_ITEMS);
});
describe('viewedHotlist', () => {
it('normal case', () => {
const state = {hotlists: {name: example.NAME, byName: example.BY_NAME}};
assert.deepEqual(hotlists.viewedHotlist(state), example.HOTLIST);
});
it('no name', () => {
const state = {hotlists: {name: null, byName: example.BY_NAME}};
assert.deepEqual(hotlists.viewedHotlist(state), null);
});
it('hotlist not found', () => {
const state = {hotlists: {name: example.NAME, byName: {}}};
assert.deepEqual(hotlists.viewedHotlist(state), null);
});
});
describe('viewedHotlistOwner', () => {
it('normal case', () => {
const state = {
hotlists: {name: example.NAME, byName: example.BY_NAME},
users: {byName: exampleUsers.BY_NAME},
};
assert.deepEqual(hotlists.viewedHotlistOwner(state), exampleUsers.USER);
});
it('no hotlist', () => {
const state = {hotlists: {}, users: {}};
assert.deepEqual(hotlists.viewedHotlistOwner(state), null);
});
});
describe('viewedHotlistEditors', () => {
it('normal case', () => {
const state = {
hotlists: {
name: example.NAME,
byName: {[example.NAME]: {
...example.HOTLIST,
editors: [exampleUsers.NAME, exampleUsers.NAME_2],
}},
},
users: {byName: exampleUsers.BY_NAME},
};
const editors = [exampleUsers.USER, exampleUsers.USER_2];
assert.deepEqual(hotlists.viewedHotlistEditors(state), editors);
});
it('no user data', () => {
const editors = [exampleUsers.NAME, exampleUsers.NAME_2];
const state = {
hotlists: {
name: example.NAME,
byName: {[example.NAME]: {...example.HOTLIST, editors}},
},
users: {byName: {}},
};
assert.deepEqual(hotlists.viewedHotlistEditors(state), [null, null]);
});
it('no hotlist', () => {
const state = {hotlists: {}, users: {}};
assert.deepEqual(hotlists.viewedHotlistEditors(state), null);
});
});
describe('viewedHotlistItems', () => {
it('normal case', () => {
const state = {hotlists: {
name: example.NAME,
hotlistItems: example.HOTLIST_ITEMS,
}};
const actual = hotlists.viewedHotlistItems(state);
assert.deepEqual(actual, [example.HOTLIST_ITEM]);
});
it('no name', () => {
const state = {hotlists: {
name: null,
hotlistItems: example.HOTLIST_ITEMS,
}};
assert.deepEqual(hotlists.viewedHotlistItems(state), []);
});
it('hotlist not found', () => {
const state = {hotlists: {name: example.NAME, hotlistItems: {}}};
assert.deepEqual(hotlists.viewedHotlistItems(state), []);
});
});
describe('viewedHotlistIssues', () => {
it('normal case', () => {
const state = {
hotlists: {
name: example.NAME,
hotlistItems: example.HOTLIST_ITEMS,
},
issue: {
issuesByRefString: {
[exampleIssues.ISSUE_REF_STRING]: exampleIssues.ISSUE,
},
},
users: {byName: {[exampleUsers.NAME]: exampleUsers.USER}},
};
const actual = hotlists.viewedHotlistIssues(state);
assert.deepEqual(actual, [example.HOTLIST_ISSUE]);
});
it('no issue', () => {
const state = {
hotlists: {
name: example.NAME,
hotlistItems: example.HOTLIST_ITEMS,
},
issue: {
issuesByRefString: {
[exampleIssues.ISSUE_OTHER_PROJECT_REF_STRING]: exampleIssues.ISSUE,
},
},
users: {byName: {}},
};
assert.deepEqual(hotlists.viewedHotlistIssues(state), []);
});
});
describe('viewedHotlistColumns', () => {
it('sitewide currentColumns overrides hotlist defaultColumns', () => {
const state = {
sitewide: {queryParams: {colspec: 'Summary+ColumnName'}},
hotlists: {},
};
const actual = hotlists.viewedHotlistColumns(state);
assert.deepEqual(actual, ['Summary', 'ColumnName']);
});
it('uses DEFAULT_COLUMNS when no hotlist', () => {
const actual = hotlists.viewedHotlistColumns({hotlists: {}});
assert.deepEqual(actual, hotlists.DEFAULT_COLUMNS);
});
it('uses DEFAULT_COLUMNS when hotlist has empty defaultColumns', () => {
const state = {hotlists: {
name: example.HOTLIST.name,
byName: {
[example.HOTLIST.name]: {...example.HOTLIST, defaultColumns: []},
},
}};
const actual = hotlists.viewedHotlistColumns(state);
assert.deepEqual(actual, hotlists.DEFAULT_COLUMNS);
});
it('uses hotlist defaultColumns', () => {
const state = {hotlists: {
name: example.HOTLIST.name,
byName: {[example.HOTLIST.name]: {
...example.HOTLIST,
defaultColumns: [{column: 'ID'}, {column: 'ColumnName'}],
}},
}};
const actual = hotlists.viewedHotlistColumns(state);
assert.deepEqual(actual, ['ID', 'ColumnName']);
});
});
describe('viewedHotlistPermissions', () => {
it('normal case', () => {
const permissions = [hotlists.ADMINISTER, hotlists.EDIT];
const state = {
hotlists: {name: example.NAME, byName: example.BY_NAME},
permissions: {byName: {[example.NAME]: {permissions}}},
};
assert.deepEqual(hotlists.viewedHotlistPermissions(state), permissions);
});
it('no issue', () => {
const state = {hotlists: {}, permissions: {}};
assert.deepEqual(hotlists.viewedHotlistPermissions(state), []);
});
});
});
describe('hotlist action creators', () => {
beforeEach(() => {
sinon.stub(prpcClient, 'call');
dispatch = sinon.stub();
});
afterEach(() => {
prpcClient.call.restore();
});
it('select', () => {
const actual = hotlists.select(example.NAME);
const expected = {type: hotlists.SELECT, name: example.NAME};
assert.deepEqual(actual, expected);
});
describe('deleteHotlist', () => {
it('success', async () => {
prpcClient.call.returns(Promise.resolve({}));
await hotlists.deleteHotlist(example.NAME)(dispatch);
sinon.assert.calledWith(dispatch, {type: hotlists.DELETE_START});
const args = {name: example.NAME};
sinon.assert.calledWith(
prpcClient.call, 'monorail.v3.Hotlists', 'DeleteHotlist', args);
sinon.assert.calledWith(dispatch, {type: hotlists.DELETE_SUCCESS});
});
it('failure', async () => {
prpcClient.call.throws();
await hotlists.deleteHotlist(example.NAME)(dispatch);
const action = {
type: hotlists.DELETE_FAILURE,
error: sinon.match.any,
};
sinon.assert.calledWith(dispatch, action);
});
});
describe('fetch', () => {
it('success', async () => {
const hotlist = example.HOTLIST;
prpcClient.call.returns(Promise.resolve(hotlist));
await hotlists.fetch(example.NAME)(dispatch);
const args = {name: example.NAME};
sinon.assert.calledWith(
prpcClient.call, 'monorail.v3.Hotlists', 'GetHotlist', args);
sinon.assert.calledWith(dispatch, {type: hotlists.FETCH_START});
sinon.assert.calledWith(dispatch, {type: hotlists.FETCH_SUCCESS});
sinon.assert.calledWith(
dispatch, {type: hotlists.RECEIVE_HOTLIST, hotlist});
});
it('failure', async () => {
prpcClient.call.throws();
await hotlists.fetch(example.NAME)(dispatch);
const action = {type: hotlists.FETCH_FAILURE, error: sinon.match.any};
sinon.assert.calledWith(dispatch, action);
});
});
describe('fetchItems', () => {
it('success', async () => {
const response = {items: [example.HOTLIST_ITEM]};
prpcClient.call.returns(Promise.resolve(response));
const returnValue = await hotlists.fetchItems(example.NAME)(dispatch);
assert.deepEqual(returnValue, [{...example.HOTLIST_ITEM, rank: 0}]);
const args = {parent: example.NAME, orderBy: 'rank'};
sinon.assert.calledWith(
prpcClient.call, 'monorail.v3.Hotlists', 'ListHotlistItems', args);
sinon.assert.calledWith(dispatch, {type: hotlists.FETCH_ITEMS_START});
const action = {
type: hotlists.FETCH_ITEMS_SUCCESS,
name: example.NAME,
items: [{...example.HOTLIST_ITEM, rank: 0}],
};
sinon.assert.calledWith(dispatch, action);
});
it('failure', async () => {
prpcClient.call.throws();
await hotlists.fetchItems(example.NAME)(dispatch);
const action = {
type: hotlists.FETCH_ITEMS_FAILURE,
error: sinon.match.any,
};
sinon.assert.calledWith(dispatch, action);
});
it('success with empty hotlist', async () => {
const response = {items: []};
prpcClient.call.returns(Promise.resolve(response));
const returnValue = await hotlists.fetchItems(example.NAME)(dispatch);
assert.deepEqual(returnValue, []);
sinon.assert.calledWith(dispatch, {type: hotlists.FETCH_ITEMS_START});
const args = {parent: example.NAME, orderBy: 'rank'};
sinon.assert.calledWith(
prpcClient.call, 'monorail.v3.Hotlists', 'ListHotlistItems', args);
const action = {
type: hotlists.FETCH_ITEMS_SUCCESS,
name: example.NAME,
items: [],
};
sinon.assert.calledWith(dispatch, action);
});
});
describe('removeEditors', () => {
it('success', async () => {
prpcClient.call.returns(Promise.resolve({}));
const editors = [exampleUsers.NAME];
await hotlists.removeEditors(example.NAME, editors)(dispatch);
const args = {name: example.NAME, editors};
sinon.assert.calledWith(
prpcClient.call, 'monorail.v3.Hotlists',
'RemoveHotlistEditors', args);
sinon.assert.calledWith(dispatch, {type: hotlists.REMOVE_EDITORS_START});
const action = {type: hotlists.REMOVE_EDITORS_SUCCESS};
sinon.assert.calledWith(dispatch, action);
});
it('failure', async () => {
prpcClient.call.throws();
await hotlists.removeEditors(example.NAME, [])(dispatch);
const action = {
type: hotlists.REMOVE_EDITORS_FAILURE,
error: sinon.match.any,
};
sinon.assert.calledWith(dispatch, action);
});
});
describe('removeItems', () => {
it('success', async () => {
prpcClient.call.returns(Promise.resolve({}));
const issues = [exampleIssues.NAME];
await hotlists.removeItems(example.NAME, issues)(dispatch);
const args = {parent: example.NAME, issues};
sinon.assert.calledWith(
prpcClient.call, 'monorail.v3.Hotlists',
'RemoveHotlistItems', args);
sinon.assert.calledWith(dispatch, {type: hotlists.REMOVE_ITEMS_START});
sinon.assert.calledWith(dispatch, {type: hotlists.REMOVE_ITEMS_SUCCESS});
});
it('failure', async () => {
prpcClient.call.throws();
await hotlists.removeItems(example.NAME, [])(dispatch);
const action = {
type: hotlists.REMOVE_ITEMS_FAILURE,
error: sinon.match.any,
};
sinon.assert.calledWith(dispatch, action);
});
});
describe('rerankItems', () => {
it('success', async () => {
prpcClient.call.returns(Promise.resolve({}));
const items = [example.HOTLIST_ITEM_NAME];
await hotlists.rerankItems(example.NAME, items, 0)(dispatch);
const args = {
name: example.NAME,
hotlistItems: items,
targetPosition: 0,
};
sinon.assert.calledWith(
prpcClient.call, 'monorail.v3.Hotlists',
'RerankHotlistItems', args);
sinon.assert.calledWith(dispatch, {type: hotlists.RERANK_ITEMS_START});
sinon.assert.calledWith(dispatch, {type: hotlists.RERANK_ITEMS_SUCCESS});
});
it('failure', async () => {
prpcClient.call.throws();
await hotlists.rerankItems(example.NAME, [], 0)(dispatch);
const action = {
type: hotlists.RERANK_ITEMS_FAILURE,
error: sinon.match.any,
};
sinon.assert.calledWith(dispatch, action);
});
});
describe('update', () => {
it('success', async () => {
const hotlistOnlyWithUpdates = {
displayName: example.HOTLIST.displayName + 'foo',
summary: example.HOTLIST.summary + 'abc',
};
const hotlist = {...example.HOTLIST, ...hotlistOnlyWithUpdates};
prpcClient.call.returns(Promise.resolve(hotlist));
await hotlists.update(
example.HOTLIST.name, hotlistOnlyWithUpdates)(dispatch);
const hotlistArg = {
...hotlistOnlyWithUpdates,
name: example.HOTLIST.name,
};
const fieldMask = 'displayName,summary';
const args = {hotlist: hotlistArg, updateMask: fieldMask};
sinon.assert.calledWith(
prpcClient.call, 'monorail.v3.Hotlists', 'UpdateHotlist', args);
sinon.assert.calledWith(dispatch, {type: hotlists.UPDATE_START});
sinon.assert.calledWith(dispatch, {type: hotlists.UPDATE_SUCCESS});
sinon.assert.calledWith(
dispatch, {type: hotlists.RECEIVE_HOTLIST, hotlist});
});
it('failure', async () => {
prpcClient.call.throws();
const hotlistOnlyWithUpdates = {
displayName: example.HOTLIST.displayName + 'foo',
summary: example.HOTLIST.summary + 'abc',
};
try {
// TODO(crbug.com/monorail/7883): Use Chai Promises plugin
// to assert promise rejected.
await hotlists.update(
example.HOTLIST.name, hotlistOnlyWithUpdates)(dispatch);
} catch (e) {}
const action = {
type: hotlists.UPDATE_FAILURE,
error: sinon.match.any,
};
sinon.assert.calledWith(dispatch, action);
});
});
});
describe('helpers', () => {
beforeEach(() => {
sinon.stub(prpcClient, 'call');
dispatch = sinon.stub();
});
afterEach(() => {
prpcClient.call.restore();
});
describe('getHotlistName', () => {
it('success', async () => {
const response = {hotlistId: '1234'};
prpcClient.call.returns(Promise.resolve(response));
const name = await hotlists.getHotlistName('foo@bar.com', 'hotlist');
assert.deepEqual(name, 'hotlists/1234');
const args = {hotlistRef: {
owner: {displayName: 'foo@bar.com'},
name: 'hotlist',
}};
sinon.assert.calledWith(
prpcClient.call, 'monorail.Features', 'GetHotlistID', args);
});
it('failure', async () => {
prpcClient.call.throws();
assert.isNull(await hotlists.getHotlistName('foo@bar.com', 'hotlist'));
});
});
});