blob: 8c079fda0d17c744c279393df847baeed37ad0fc [file] [log] [blame]
import {assert} from 'chai';
import sinon from 'sinon';
import MrChart, {
subscribedQuery,
} from 'elements/issue-list/mr-chart/mr-chart.js';
import {prpcClient} from 'prpc-client-instance.js';
let element;
let dataLoadedPromise;
const beforeEachElement = () => {
if (element && document.body.contains(element)) {
// Avoid setting up multiple versions of the same element.
document.body.removeChild(element);
element = null;
}
const el = document.createElement('mr-chart');
el.setAttribute('projectName', 'rutabaga');
dataLoadedPromise = new Promise((resolve) => {
el.addEventListener('allDataLoaded', resolve);
});
document.body.appendChild(el);
return el;
};
describe('mr-chart', () => {
beforeEach(() => {
window.CS_env = {
token: 'rutabaga-token',
tokenExpiresSec: 0,
app_version: 'rutabaga-version',
};
sinon.stub(prpcClient, 'call').callsFake(async () => {
return {
snapshotCount: [{count: 8}],
unsupportedField: [],
searchLimitReached: false,
};
});
element = beforeEachElement();
});
afterEach(async () => {
// _fetchData is always called when the element is connected, so we have to
// wait until all data has been loaded.
// Otherwise prpcClient.call will be restored and we will make actual XHR
// calls.
await dataLoadedPromise;
document.body.removeChild(element);
prpcClient.call.restore();
});
describe('initializes', () => {
it('renders', () => {
assert.instanceOf(element, MrChart);
});
it('sets this.projectname', () => {
assert.equal(element.projectName, 'rutabaga');
});
});
describe('data loading', () => {
beforeEach(() => {
// Stub MrChart.makeTimestamps to return 6, not 30 data points.
const originalMakeTimestamps = MrChart.makeTimestamps;
sinon.stub(MrChart, 'makeTimestamps').callsFake((endDate) => {
return originalMakeTimestamps(endDate, 1, 6);
});
sinon.stub(MrChart, 'getEndDate').callsFake(() => {
return new Date(Date.UTC(2018, 10, 3, 23, 59, 59));
});
// Re-instantiate element after stubs.
element = beforeEachElement();
});
afterEach(() => {
MrChart.makeTimestamps.restore();
MrChart.getEndDate.restore();
});
it('makes a series of XHR calls', async () => {
await dataLoadedPromise;
for (let i = 0; i < 6; i++) {
assert.deepEqual(element.values[i], new Map());
}
});
it('sets indices and correctly re-orders values', async () => {
await dataLoadedPromise;
const timestampMap = new Map([
[1540857599, 0], [1540943999, 1], [1541030399, 2], [1541116799, 3],
[1541203199, 4], [1541289599, 5],
]);
sinon.stub(MrChart.prototype, '_fetchDataAtTimestamp').callsFake(
async (ts) => ({issues: {'Issue Count': timestampMap.get(ts)}}));
element.endDate = new Date(Date.UTC(2018, 10, 3, 23, 59, 59));
await element._fetchData();
assert.deepEqual(element.indices, [
'10/29/2018', '10/30/2018', '10/31/2018',
'11/1/2018', '11/2/2018', '11/3/2018',
]);
for (let i = 0; i < 6; i++) {
assert.deepEqual(element.values[i], {'Issue Count': i});
}
MrChart.prototype._fetchDataAtTimestamp.restore();
});
it('if issue count is null, defaults to 0', async () => {
prpcClient.call.restore();
sinon.stub(prpcClient, 'call').callsFake(async () => {
return {snapshotCount: [{}]};
});
MrChart.makeTimestamps.restore();
sinon.stub(MrChart, 'makeTimestamps').callsFake((endDate) => {
return [1234567, 2345678, 3456789];
});
await element._fetchData(new Date());
assert.deepEqual(element.values[0], new Map());
});
it('Retrieve data under groupby feature', async () => {
const data = new Map([['Type-1', 0], ['Type-2', 1]]);
sinon.stub(MrChart.prototype, '_fetchDataAtTimestamp').callsFake(
() => ({issues: data}));
element = beforeEachElement();
await element._fetchData(new Date());
for (let i = 0; i < 3; i++) {
assert.deepEqual(element.values[i], data);
}
MrChart.prototype._fetchDataAtTimestamp.restore();
});
it('_fetchDataAtTimestamp has no default query or can', async () => {
await element._fetchData();
sinon.assert.calledWith(
prpcClient.call,
'monorail.Issues',
'IssueSnapshot',
{
cannedQuery: undefined,
groupBy: undefined,
hotlistId: undefined,
query: undefined,
projectName: 'rutabaga',
timestamp: 1540857599,
});
});
});
describe('start date change detection', () => {
it('illegal query: start-date is greater than end-date', async () => {
await element.updateComplete;
element.startDate = new Date('2199-11-06');
element._fetchData();
assert.equal(element.dateRange, 90);
assert.equal(element.frequency, 7);
assert.equal(element.dateRangeNotLegal, true);
});
it('illegal query: end_date - start_date requires more than 90 queries',
async () => {
await element.updateComplete;
element.startDate = new Date('2016-10-03');
element._fetchData();
assert.equal(element.dateRange, 90 * 7);
assert.equal(element.frequency, 7);
assert.equal(element.maxQuerySizeReached, true);
});
});
describe('date change behavior', () => {
it('pushes to history API via pageJS', async () => {
sinon.stub(element, '_page');
sinon.spy(element, '_setDateRange');
sinon.spy(element, '_onDateChanged');
sinon.spy(element, '_changeUrlParams');
await element.updateComplete;
const thirtyButton = element.shadowRoot
.querySelector('#two-toggle').children[2];
thirtyButton.click();
sinon.assert.calledOnce(element._setDateRange);
sinon.assert.calledOnce(element._onDateChanged);
sinon.assert.calledOnce(element._changeUrlParams);
sinon.assert.calledOnce(element._page);
element._page.restore();
element._setDateRange.restore();
element._onDateChanged.restore();
element._changeUrlParams.restore();
});
});
describe('progress bar', () => {
it('visible based on loading progress', async () => {
// Check for visible progress bar and hidden input after initial render
await element.updateComplete;
const progressBar = element.shadowRoot.querySelector('progress');
const endDateInput = element.shadowRoot.querySelector('#end-date');
assert.isFalse(progressBar.hasAttribute('hidden'));
assert.isTrue(endDateInput.disabled);
// Check for hidden progress bar and enabled input after fetch and render
await dataLoadedPromise;
await element.updateComplete;
assert.isTrue(progressBar.hasAttribute('hidden'));
assert.isFalse(endDateInput.disabled);
// Trigger another data fetch and render, but prior to fetch complete
// Check progress bar is visible again
element.queryParams['start-date'] = '2012-01-01';
await element.requestUpdate('queryParams');
await element.updateComplete;
assert.isFalse(progressBar.hasAttribute('hidden'));
await dataLoadedPromise;
await element.updateComplete;
assert.isTrue(progressBar.hasAttribute('hidden'));
});
});
describe('static methods', () => {
describe('sortInBisectOrder', () => {
it('orders first, last, median recursively', () => {
assert.deepEqual(MrChart.sortInBisectOrder([]), []);
assert.deepEqual(MrChart.sortInBisectOrder([9]), [9]);
assert.deepEqual(MrChart.sortInBisectOrder([8, 9]), [8, 9]);
assert.deepEqual(MrChart.sortInBisectOrder([7, 8, 9]), [7, 9, 8]);
assert.deepEqual(
MrChart.sortInBisectOrder([1, 2, 3, 4, 5]), [1, 5, 3, 2, 4]);
});
});
describe('makeTimestamps', () => {
it('throws an error if endDate not passed', () => {
assert.throws(() => {
MrChart.makeTimestamps();
}, 'endDate required');
});
it('returns an array of in seconds', () => {
const endDate = new Date(Date.UTC(2018, 10, 3, 23, 59, 59));
const secondsInDay = 24 * 60 * 60;
assert.deepEqual(MrChart.makeTimestamps(endDate, 1, 6), [
1541289599 - (secondsInDay * 5), 1541289599 - (secondsInDay * 4),
1541289599 - (secondsInDay * 3), 1541289599 - (secondsInDay * 2),
1541289599 - (secondsInDay * 1), 1541289599 - (secondsInDay * 0),
]);
});
it('tests frequency greater than 1', () => {
const endDate = new Date(Date.UTC(2018, 10, 3, 23, 59, 59));
const secondsInDay = 24 * 60 * 60;
assert.deepEqual(MrChart.makeTimestamps(endDate, 2, 6), [
1541289599 - (secondsInDay * 4),
1541289599 - (secondsInDay * 2),
1541289599 - (secondsInDay * 0),
]);
});
it('tests frequency greater than 1', () => {
const endDate = new Date(Date.UTC(2018, 10, 3, 23, 59, 59));
const secondsInDay = 24 * 60 * 60;
assert.deepEqual(MrChart.makeTimestamps(endDate, 2, 7), [
1541289599 - (secondsInDay * 6),
1541289599 - (secondsInDay * 4),
1541289599 - (secondsInDay * 2),
1541289599 - (secondsInDay * 0),
]);
});
});
describe('dateStringToDate', () => {
it('returns null if no input', () => {
assert.isNull(MrChart.dateStringToDate());
});
it('returns a new Date at EOD UTC', () => {
const actualDate = MrChart.dateStringToDate('2018-11-03');
const expectedDate = new Date(Date.UTC(2018, 10, 3, 23, 59, 59));
assert.equal(expectedDate.getTime(), 1541289599000, 'Sanity check.');
assert.equal(actualDate.getTime(), expectedDate.getTime());
});
});
describe('getEndDate', () => {
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers(10000);
});
afterEach(() => {
clock.restore();
});
it('returns parsed input date', () => {
const input = '2018-11-03';
const expectedDate = new Date(Date.UTC(2018, 10, 3, 23, 59, 59));
// Time sanity check.
assert.equal(Math.round(expectedDate.getTime() / 1e3), 1541289599);
const actual = MrChart.getEndDate(input);
assert.equal(actual.getTime(), expectedDate.getTime());
});
it('returns EOD of current date by default', () => {
const expectedDate = new Date();
expectedDate.setHours(23);
expectedDate.setMinutes(59);
expectedDate.setSeconds(59);
assert.equal(MrChart.getEndDate().getTime(),
expectedDate.getTime());
});
});
describe('getStartDate', () => {
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers(10000);
});
afterEach(() => {
clock.restore();
});
it('returns parsed input date', () => {
const input = '2018-07-03';
const expectedDate = new Date(Date.UTC(2018, 6, 3, 23, 59, 59));
// Time sanity check.
assert.equal(Math.round(expectedDate.getTime() / 1e3), 1530662399);
const actual = MrChart.getStartDate(input);
assert.equal(actual.getTime(), expectedDate.getTime());
});
it('returns EOD of current date by default', () => {
const today = new Date();
today.setHours(23);
today.setMinutes(59);
today.setSeconds(59);
const secondsInDay = 24 * 60 * 60;
const expectedDate = new Date(today.getTime() -
1000 * 90 * secondsInDay);
assert.equal(MrChart.getStartDate(undefined, today, 90).getTime(),
expectedDate.getTime());
});
});
describe('makeIndices', () => {
it('returns dates in mm/dd/yyy format', () => {
const timestamps = [
1540857599, 1540943999, 1541030399,
1541116799, 1541203199, 1541289599,
];
assert.deepEqual(MrChart.makeIndices(timestamps), [
'10/29/2018', '10/30/2018', '10/31/2018',
'11/1/2018', '11/2/2018', '11/3/2018',
]);
});
});
describe('getPredictedData', () => {
it('get predicted data shown in daily', () => {
const values = [0, 1, 2, 3, 4, 5, 6];
const result = MrChart.getPredictedData(
values, values.length, 3, 1, new Date('10-02-2017'));
assert.deepEqual(result[0], ['10/4/2017', '10/5/2017', '10/6/2017']);
assert.deepEqual(result[1], [7, 8, 9]);
assert.deepEqual(result[2], [0, 1, 2, 3, 4, 5, 6]);
});
it('get predicted data shown in weekly', () => {
const values = [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84];
const result = MrChart.getPredictedData(
values, 91, 13, 7, new Date('10-02-2017'));
assert.deepEqual(result[1], values.map((x) => x+91));
assert.deepEqual(result[2], values);
});
});
describe('getErrorData', () => {
it('get error data with perfect regression', () => {
const values = [0, 1, 2, 3, 4, 5, 6];
const result = MrChart.getErrorData(values, values, [7, 8, 9]);
assert.deepEqual(result[0], [7, 8, 9]);
assert.deepEqual(result[1], [7, 8, 9]);
});
it('get error data with nonperfect regression', () => {
const values = [0, 1, 3, 4, 6, 6, 7];
const result = MrChart.getPredictedData(
values, values.length, 3, 1, new Date('10-02-2017'));
const error = MrChart.getErrorData(result[2], values, result[1]);
assert.isTrue(error[0][0] > result[1][0]);
assert.isTrue(error[1][0] < result[1][0]);
});
});
describe('getSortedLines', () => {
it('return all lines for less than n lines', () => {
const arrayValues = [
{label: 'line1', data: [0, 0, 1]},
{label: 'line2', data: [0, 1, 2]},
{label: 'line3', data: [0, 1, 0]},
{label: 'line4', data: [4, 0, 3]},
];
const expectedValues = [
{label: 'line1', data: [0, 0, 1]},
{label: 'line2', data: [0, 1, 2]},
{label: 'line3', data: [0, 1, 0]},
{label: 'line4', data: [4, 0, 3]},
];
const actualValues = MrChart.getSortedLines(arrayValues, 4);
for (let i = 0; i < 4; i++) {
assert.deepEqual(expectedValues[i], actualValues[i]);
}
});
it('return top n lines in sorted order for more than n lines',
() => {
const arrayValues = [
{label: 'line1', data: [0, 0, 1]},
{label: 'line2', data: [0, 1, 2]},
{label: 'line3', data: [0, 4, 0]},
{label: 'line4', data: [4, 0, 3]},
{label: 'line5', data: [0, 2, 3]},
];
const expectedValues = [
{label: 'line5', data: [0, 2, 3]},
{label: 'line4', data: [4, 0, 3]},
{label: 'line2', data: [0, 1, 2]},
];
const actualValues = MrChart.getSortedLines(arrayValues, 3);
for (let i = 0; i < 3; i++) {
assert.deepEqual(expectedValues[i], actualValues[i]);
}
});
});
describe('getGroupByFromQuery', () => {
it('get group by label object from URL', () => {
const input = {'groupby': 'label', 'labelprefix': 'Type'};
const expectedGroupBy = {
value: 'label',
labelPrefix: 'Type',
display: 'Type',
};
assert.deepEqual(MrChart.getGroupByFromQuery(input), expectedGroupBy);
});
it('get group by is open object from URL', () => {
const input = {'groupby': 'open'};
const expectedGroupBy = {value: 'open', display: 'Is open'};
assert.deepEqual(MrChart.getGroupByFromQuery(input), expectedGroupBy);
});
it('get group by none object from URL', () => {
const input = {'groupby': ''};
const expectedGroupBy = {value: '', display: 'None'};
assert.deepEqual(MrChart.getGroupByFromQuery(input), expectedGroupBy);
});
it('only returns valid groupBy values', () => {
const invalidKeys = ['pri', 'reporter', 'stars'];
const queryParams = {groupBy: ''};
invalidKeys.forEach((key) => {
queryParams.groupBy = key;
const expected = {value: '', display: 'None'};
const result = MrChart.getGroupByFromQuery(queryParams);
assert.deepEqual(result, expected);
});
});
});
});
describe('subscribedQuery', () => {
it('includes start and end date', () => {
assert.isTrue(subscribedQuery.has('start-date'));
assert.isTrue(subscribedQuery.has('start-date'));
});
it('includes groupby and labelprefix', () => {
assert.isTrue(subscribedQuery.has('groupby'));
assert.isTrue(subscribedQuery.has('labelprefix'));
});
it('includes q and can', () => {
assert.isTrue(subscribedQuery.has('q'));
assert.isTrue(subscribedQuery.has('can'));
});
});
});