blob: f77c3ffe72943084941710feb36ee8a6a75342d1 [file] [log] [blame]
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {createChange} from '../../test/test-data-generators';
import {
ChangeInfo,
NumericChangeId,
ChangeStatus,
HttpMethod,
SubmitRequirementStatus,
} from '../../api/rest-api';
import {BulkActionsModel, LoadingState} from './bulk-actions-model';
import {getAppContext} from '../../services/app-context';
import '../../test/common-test-setup-karma';
import {stubRestApi, waitUntilObserved} from '../../test/test-utils';
import {mockPromise} from '../../test/test-utils';
import {SinonStubbedMember} from 'sinon';
import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
suite('bulk actions model test', () => {
let bulkActionsModel: BulkActionsModel;
setup(() => {
bulkActionsModel = new BulkActionsModel(getAppContext().restApiService);
});
test('add changes before sync', () => {
const c1 = createChange();
c1._number = 1 as NumericChangeId;
const c2 = createChange();
c2._number = 2 as NumericChangeId;
assert.isEmpty(bulkActionsModel.getState().selectedChangeNums);
assert.throws(() => bulkActionsModel.addSelectedChangeNum(c1._number));
assert.isEmpty(bulkActionsModel.getState().selectedChangeNums);
bulkActionsModel.sync([c1, c2]);
bulkActionsModel.addSelectedChangeNum(c2._number);
assert.sameMembers(bulkActionsModel.getState().selectedChangeNums, [
c2._number,
]);
bulkActionsModel.removeSelectedChangeNum(c2._number);
assert.isEmpty(bulkActionsModel.getState().selectedChangeNums);
});
test('add and remove selected changes', () => {
const c1 = createChange();
c1._number = 1 as NumericChangeId;
const c2 = createChange();
c2._number = 2 as NumericChangeId;
bulkActionsModel.sync([c1, c2]);
assert.isEmpty(bulkActionsModel.getState().selectedChangeNums);
bulkActionsModel.addSelectedChangeNum(c1._number);
assert.sameMembers(bulkActionsModel.getState().selectedChangeNums, [
c1._number,
]);
bulkActionsModel.addSelectedChangeNum(c2._number);
assert.sameMembers(bulkActionsModel.getState().selectedChangeNums, [
c1._number,
c2._number,
]);
bulkActionsModel.removeSelectedChangeNum(c1._number);
assert.sameMembers(bulkActionsModel.getState().selectedChangeNums, [
c2._number,
]);
bulkActionsModel.removeSelectedChangeNum(c2._number);
assert.isEmpty(bulkActionsModel.getState().selectedChangeNums);
});
test('clears selected change numbers', async () => {
const c1 = createChange();
c1._number = 1 as NumericChangeId;
const c2 = createChange();
c2._number = 2 as NumericChangeId;
bulkActionsModel.sync([c1, c2]);
bulkActionsModel.addSelectedChangeNum(c1._number);
bulkActionsModel.addSelectedChangeNum(c2._number);
let selectedChangeNums = await waitUntilObserved(
bulkActionsModel!.selectedChangeNums$,
s => s.length === 2
);
let totalChangeCount = await waitUntilObserved(
bulkActionsModel.totalChangeCount$,
totalChangeCount => totalChangeCount === 2
);
assert.sameMembers(selectedChangeNums, [c1._number, c2._number]);
assert.equal(totalChangeCount, 2);
bulkActionsModel.clearSelectedChangeNums();
selectedChangeNums = await waitUntilObserved(
bulkActionsModel!.selectedChangeNums$,
s => s.length === 0
);
totalChangeCount = await waitUntilObserved(
bulkActionsModel.totalChangeCount$,
totalChangeCount => totalChangeCount === 2
);
assert.isEmpty(selectedChangeNums);
assert.equal(totalChangeCount, 2);
});
suite('abandon changes', () => {
let detailedActionsStub: SinonStubbedMember<
RestApiService['getDetailedChangesWithActions']
>;
setup(async () => {
detailedActionsStub = stubRestApi('getDetailedChangesWithActions');
const c1 = createChange();
c1._number = 1 as NumericChangeId;
const c2 = createChange();
c2._number = 2 as NumericChangeId;
detailedActionsStub.returns(
Promise.resolve([
{...c1, actions: {abandon: {method: HttpMethod.POST}}},
{...c2, status: ChangeStatus.ABANDONED},
])
);
bulkActionsModel.sync([c1, c2]);
bulkActionsModel.addSelectedChangeNum(c1._number);
bulkActionsModel.addSelectedChangeNum(c2._number);
});
test('already abandoned change does not call executeChangeAction', () => {
const actionStub = stubRestApi('executeChangeAction');
bulkActionsModel.abandonChanges();
assert.equal(actionStub.callCount, 1);
assert.deepEqual(actionStub.lastCall.args.slice(0, 5), [
1 as NumericChangeId,
HttpMethod.POST,
'/abandon',
undefined,
{message: ''},
]);
});
});
test('stale changes are removed from the model', async () => {
const c1 = createChange();
c1._number = 1 as NumericChangeId;
const c2 = createChange();
c2._number = 2 as NumericChangeId;
bulkActionsModel.sync([c1, c2]);
bulkActionsModel.addSelectedChangeNum(c1._number);
bulkActionsModel.addSelectedChangeNum(c2._number);
let selectedChangeNums = await waitUntilObserved(
bulkActionsModel!.selectedChangeNums$,
s => s.length === 2
);
let totalChangeCount = await waitUntilObserved(
bulkActionsModel.totalChangeCount$,
totalChangeCount => totalChangeCount === 2
);
assert.sameMembers(selectedChangeNums, [c1._number, c2._number]);
assert.equal(totalChangeCount, 2);
bulkActionsModel.sync([c1]);
selectedChangeNums = await waitUntilObserved(
bulkActionsModel!.selectedChangeNums$,
s => s.length === 1
);
totalChangeCount = await waitUntilObserved(
bulkActionsModel.totalChangeCount$,
totalChangeCount => totalChangeCount === 1
);
assert.sameMembers(selectedChangeNums, [c1._number]);
assert.equal(totalChangeCount, 1);
});
test('sync fetches new changes', async () => {
const c1 = createChange();
c1._number = 1 as NumericChangeId;
const c2 = createChange();
c2._number = 2 as NumericChangeId;
assert.equal(
bulkActionsModel.getState().loadingState,
LoadingState.NOT_SYNCED
);
bulkActionsModel.sync([c1, c2]);
await waitUntilObserved(
bulkActionsModel.loadingState$,
s => s === LoadingState.LOADING
);
await waitUntilObserved(
bulkActionsModel.loadingState$,
s => s === LoadingState.LOADED
);
const model = bulkActionsModel.getState();
assert.strictEqual(
model.allChanges.get(1 as NumericChangeId)?.subject,
'Subject 1'
);
assert.strictEqual(
model.allChanges.get(2 as NumericChangeId)?.subject,
'Subject 2'
);
});
test('sync retains keys from original change', async () => {
const c1 = createChange();
c1._number = 1 as NumericChangeId;
c1.submit_requirements = [
{
name: 'a',
status: SubmitRequirementStatus.FORCED,
submittability_expression_result: {
expression: 'b',
},
},
];
stubRestApi('getDetailedChangesWithActions').callsFake(() => {
const change = createChange();
change._number = 1 as NumericChangeId;
change.actions = {abandon: {}};
assert.isNotOk(change.submit_requirements);
return Promise.resolve([change]);
});
bulkActionsModel.sync([c1]);
await waitUntilObserved(
bulkActionsModel.loadingState$,
s => s === LoadingState.LOADED
);
const changeAfterSync = bulkActionsModel
.getState()
.allChanges.get(1 as NumericChangeId);
assert.deepEqual(changeAfterSync!.submit_requirements, [
{
name: 'a',
status: SubmitRequirementStatus.FORCED,
submittability_expression_result: {
expression: 'b',
},
},
]);
assert.deepEqual(changeAfterSync!.actions, {abandon: {}});
});
test('sync ignores outdated fetch responses', async () => {
const c1 = createChange();
c1._number = 1 as NumericChangeId;
const c2 = createChange();
c2._number = 2 as NumericChangeId;
const responsePromise1 = mockPromise<ChangeInfo[]>();
let promise = responsePromise1;
const getChangesStub = stubRestApi(
'getDetailedChangesWithActions'
).callsFake(() => promise);
bulkActionsModel.sync([c1, c2]);
assert.strictEqual(getChangesStub.callCount, 1);
await waitUntilObserved(
bulkActionsModel.loadingState$,
s => s === LoadingState.LOADING
);
const responsePromise2 = mockPromise<ChangeInfo[]>();
promise = responsePromise2;
bulkActionsModel.sync([c1, c2]);
assert.strictEqual(getChangesStub.callCount, 2);
responsePromise2.resolve([
{...createChange(), _number: 1, subject: 'Subject 1'},
{...createChange(), _number: 2, subject: 'Subject 2'},
] as ChangeInfo[]);
await waitUntilObserved(
bulkActionsModel.loadingState$,
s => s === LoadingState.LOADED
);
const model = bulkActionsModel.getState();
assert.strictEqual(
model.allChanges.get(1 as NumericChangeId)?.subject,
'Subject 1'
);
assert.strictEqual(
model.allChanges.get(2 as NumericChangeId)?.subject,
'Subject 2'
);
// Resolve the old promise.
responsePromise1.resolve([
{...createChange(), _number: 1, subject: 'Subject 1-old'},
{...createChange(), _number: 2, subject: 'Subject 2-old'},
] as ChangeInfo[]);
await flush();
const model2 = bulkActionsModel.getState();
// No change should happen.
assert.strictEqual(
model2.allChanges.get(1 as NumericChangeId)?.subject,
'Subject 1'
);
assert.strictEqual(
model2.allChanges.get(2 as NumericChangeId)?.subject,
'Subject 2'
);
});
});