Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/reducers/projectV0.test.js b/static_src/reducers/projectV0.test.js
new file mode 100644
index 0000000..fb1f051
--- /dev/null
+++ b/static_src/reducers/projectV0.test.js
@@ -0,0 +1,944 @@
+// 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 {prpcClient} from 'prpc-client-instance.js';
+import * as projectV0 from './projectV0.js';
+import {store} from './base.js';
+import * as example from 'shared/test/constants-projectV0.js';
+import {restrictionLabelsForPermissions} from 'shared/convertersV0.js';
+import {fieldTypes, SITEWIDE_DEFAULT_COLUMNS} from 'shared/issue-fields.js';
+
+describe('project reducers', () => {
+ it('root reducer initial state', () => {
+ const actual = projectV0.reducer(undefined, {type: null});
+ const expected = {
+ name: null,
+ configs: {},
+ presentationConfigs: {},
+ customPermissions: {},
+ visibleMembers: {},
+ templates: {},
+ requests: {
+ fetchConfig: {
+ error: null,
+ requesting: false,
+ },
+ fetchCustomPermissions: {
+ error: null,
+ requesting: false,
+ },
+ fetchMembers: {
+ error: null,
+ requesting: false,
+ },
+ fetchPresentationConfig: {
+ error: null,
+ requesting: false,
+ },
+ fetchTemplates: {
+ error: null,
+ requesting: false,
+ },
+ },
+ };
+ assert.deepEqual(actual, expected);
+ });
+
+ it('name', () => {
+ const action = {type: projectV0.SELECT, projectName: example.PROJECT_NAME};
+ assert.deepEqual(projectV0.nameReducer(null, action), example.PROJECT_NAME);
+ });
+
+ it('configs updates when fetching Config', () => {
+ const action = {
+ type: projectV0.FETCH_CONFIG_SUCCESS,
+ projectName: example.PROJECT_NAME,
+ config: example.CONFIG,
+ };
+ const expected = {[example.PROJECT_NAME]: example.CONFIG};
+ assert.deepEqual(projectV0.configsReducer({}, action), expected);
+ });
+
+ it('customPermissions', () => {
+ const action = {
+ type: projectV0.FETCH_CUSTOM_PERMISSIONS_SUCCESS,
+ projectName: example.PROJECT_NAME,
+ permissions: example.CUSTOM_PERMISSIONS,
+ };
+ const expected = {[example.PROJECT_NAME]: example.CUSTOM_PERMISSIONS};
+ assert.deepEqual(projectV0.customPermissionsReducer({}, action), expected);
+ });
+
+ it('presentationConfigs', () => {
+ const action = {
+ type: projectV0.FETCH_PRESENTATION_CONFIG_SUCCESS,
+ projectName: example.PROJECT_NAME,
+ presentationConfig: example.PRESENTATION_CONFIG,
+ };
+ const expected = {[example.PROJECT_NAME]: example.PRESENTATION_CONFIG};
+ assert.deepEqual(projectV0.presentationConfigsReducer({}, action),
+ expected);
+ });
+
+ it('visibleMembers', () => {
+ const action = {
+ type: projectV0.FETCH_VISIBLE_MEMBERS_SUCCESS,
+ projectName: example.PROJECT_NAME,
+ visibleMembers: example.VISIBLE_MEMBERS,
+ };
+ const expected = {[example.PROJECT_NAME]: example.VISIBLE_MEMBERS};
+ assert.deepEqual(projectV0.visibleMembersReducer({}, action), expected);
+ });
+
+ it('templates', () => {
+ const action = {
+ type: projectV0.FETCH_TEMPLATES_SUCCESS,
+ projectName: example.PROJECT_NAME,
+ templates: [example.TEMPLATE_DEF],
+ };
+ const expected = {[example.PROJECT_NAME]: [example.TEMPLATE_DEF]};
+ assert.deepEqual(projectV0.templatesReducer({}, action), expected);
+ });
+});
+
+describe('project selectors', () => {
+ it('viewedProjectName', () => {
+ const actual = projectV0.viewedProjectName(example.STATE);
+ assert.deepEqual(actual, example.PROJECT_NAME);
+ });
+
+ it('viewedVisibleMembers', () => {
+ assert.deepEqual(projectV0.viewedVisibleMembers({}), {});
+ assert.deepEqual(projectV0.viewedVisibleMembers({projectV0: {}}), {});
+ assert.deepEqual(projectV0.viewedVisibleMembers(
+ {projectV0: {visibleMembers: {}}}), {});
+ const actual = projectV0.viewedVisibleMembers(example.STATE);
+ assert.deepEqual(actual, example.VISIBLE_MEMBERS);
+ });
+
+ it('viewedCustomPermissions', () => {
+ assert.deepEqual(projectV0.viewedCustomPermissions({}), []);
+ assert.deepEqual(projectV0.viewedCustomPermissions({projectV0: {}}), []);
+ assert.deepEqual(projectV0.viewedCustomPermissions(
+ {projectV0: {customPermissions: {}}}), []);
+ const actual = projectV0.viewedCustomPermissions(example.STATE);
+ assert.deepEqual(actual, example.CUSTOM_PERMISSIONS);
+ });
+
+ it('viewedPresentationConfig', () => {
+ assert.deepEqual(projectV0.viewedPresentationConfig({}), {});
+ assert.deepEqual(projectV0.viewedPresentationConfig({projectV0: {}}), {});
+ const actual = projectV0.viewedPresentationConfig(example.STATE);
+ assert.deepEqual(actual, example.PRESENTATION_CONFIG);
+ });
+
+ it('defaultColumns', () => {
+ assert.deepEqual(projectV0.defaultColumns({}), SITEWIDE_DEFAULT_COLUMNS);
+ assert.deepEqual(
+ projectV0.defaultColumns({projectV0: {}}), SITEWIDE_DEFAULT_COLUMNS);
+ assert.deepEqual(
+ projectV0.defaultColumns({projectV0: {presentationConfig: {}}}),
+ SITEWIDE_DEFAULT_COLUMNS);
+ const expected = ['ID', 'Summary', 'AllLabels'];
+ assert.deepEqual(projectV0.defaultColumns(example.STATE), expected);
+ });
+
+ it('defaultQuery', () => {
+ assert.deepEqual(projectV0.defaultQuery({}), '');
+ assert.deepEqual(projectV0.defaultQuery({projectV0: {}}), '');
+ const actual = projectV0.defaultQuery(example.STATE);
+ assert.deepEqual(actual, example.DEFAULT_QUERY);
+ });
+
+ it('fieldDefs', () => {
+ assert.deepEqual(projectV0.fieldDefs({projectV0: {}}), []);
+ assert.deepEqual(projectV0.fieldDefs({projectV0: {config: {}}}), []);
+ const actual = projectV0.fieldDefs(example.STATE);
+ assert.deepEqual(actual, example.FIELD_DEFS);
+ });
+
+ it('labelDefMap', () => {
+ const labelDefs = (permissions) =>
+ restrictionLabelsForPermissions(permissions).map((labelDef) =>
+ [labelDef.label.toLowerCase(), labelDef]);
+
+ assert.deepEqual(
+ projectV0.labelDefMap({projectV0: {}}), new Map(labelDefs([])));
+ assert.deepEqual(
+ projectV0.labelDefMap({projectV0: {config: {}}}), new Map(labelDefs([])));
+ const expected = new Map([
+ ['one', {label: 'One'}],
+ ['enum', {label: 'EnUm'}],
+ ['enum-options', {label: 'eNuM-Options'}],
+ ['hello-world', {label: 'hello-world', docstring: 'hmmm'}],
+ ['hello-me', {label: 'hello-me', docstring: 'hmmm'}],
+ ...labelDefs(example.CUSTOM_PERMISSIONS),
+ ]);
+ assert.deepEqual(projectV0.labelDefMap(example.STATE), expected);
+ });
+
+ it('labelPrefixValueMap', () => {
+ const builtInLabelPrefixes = [
+ ['Restrict', new Set(['View-EditIssue', 'AddIssueComment-EditIssue'])],
+ ['Restrict-View', new Set(['EditIssue'])],
+ ['Restrict-AddIssueComment', new Set(['EditIssue'])],
+ ];
+ assert.deepEqual(projectV0.labelPrefixValueMap({projectV0: {}}),
+ new Map(builtInLabelPrefixes));
+
+ assert.deepEqual(projectV0.labelPrefixValueMap(
+ {projectV0: {config: {}}}), new Map(builtInLabelPrefixes));
+
+ const expected = new Map([
+ ['Restrict', new Set(['View-Google', 'View-Security', 'EditIssue-Google',
+ 'EditIssue-Security', 'AddIssueComment-Google',
+ 'AddIssueComment-Security', 'DeleteIssue-Google',
+ 'DeleteIssue-Security', 'FlagSpam-Google', 'FlagSpam-Security',
+ 'View-EditIssue', 'AddIssueComment-EditIssue'])],
+ ['Restrict-View', new Set(['Google', 'Security', 'EditIssue'])],
+ ['Restrict-EditIssue', new Set(['Google', 'Security'])],
+ ['Restrict-AddIssueComment', new Set(['Google', 'Security', 'EditIssue'])],
+ ['Restrict-DeleteIssue', new Set(['Google', 'Security'])],
+ ['Restrict-FlagSpam', new Set(['Google', 'Security'])],
+ ['eNuM', new Set(['Options'])],
+ ['hello', new Set(['world', 'me'])],
+ ]);
+ assert.deepEqual(projectV0.labelPrefixValueMap(example.STATE), expected);
+ });
+
+ it('labelPrefixFields', () => {
+ const fields1 = projectV0.labelPrefixFields({projectV0: {}});
+ assert.deepEqual(fields1, ['Restrict']);
+ const fields2 = projectV0.labelPrefixFields({projectV0: {config: {}}});
+ assert.deepEqual(fields2, ['Restrict']);
+ const expected = [
+ 'hello', 'Restrict', 'Restrict-View', 'Restrict-EditIssue',
+ 'Restrict-AddIssueComment', 'Restrict-DeleteIssue', 'Restrict-FlagSpam'
+ ];
+ assert.deepEqual(projectV0.labelPrefixFields(example.STATE), expected);
+ });
+
+ it('enumFieldDefs', () => {
+ assert.deepEqual(projectV0.enumFieldDefs({projectV0: {}}), []);
+ assert.deepEqual(projectV0.enumFieldDefs({projectV0: {config: {}}}), []);
+ const expected = [example.FIELD_DEF_ENUM];
+ assert.deepEqual(projectV0.enumFieldDefs(example.STATE), expected);
+ });
+
+ it('optionsPerEnumField', () => {
+ assert.deepEqual(projectV0.optionsPerEnumField({projectV0: {}}), new Map());
+ const expected = new Map([
+ ['enum', [
+ {label: 'eNuM-Options', optionName: 'Options'},
+ ]],
+ ]);
+ assert.deepEqual(projectV0.optionsPerEnumField(example.STATE), expected);
+ });
+
+ it('viewedPresentationConfigLoaded', () => {
+ const loadConfigAction = {
+ type: projectV0.FETCH_PRESENTATION_CONFIG_SUCCESS,
+ projectName: example.PROJECT_NAME,
+ presentationConfig: example.PRESENTATION_CONFIG,
+ };
+ const selectProjectAction = {
+ type: projectV0.SELECT,
+ projectName: example.PROJECT_NAME,
+ };
+ let projectState = {};
+
+ assert.equal(false, projectV0.viewedPresentationConfigLoaded(
+ {projectV0: projectState}));
+
+ projectState = projectV0.reducer(projectState, selectProjectAction);
+ projectState = projectV0.reducer(projectState, loadConfigAction);
+
+ assert.equal(true, projectV0.viewedPresentationConfigLoaded(
+ {projectV0: projectState}));
+ });
+
+ it('fetchingPresentationConfig', () => {
+ const projectState = projectV0.reducer(undefined, {type: null});
+ assert.equal(false,
+ projectState.requests.fetchPresentationConfig.requesting);
+ });
+
+ describe('extractTypeForFieldName', () => {
+ let typeExtractor;
+
+ describe('built-in fields', () => {
+ beforeEach(() => {
+ typeExtractor = projectV0.extractTypeForFieldName({});
+ });
+
+ it('not case sensitive', () => {
+ assert.deepEqual(typeExtractor('id'), fieldTypes.ISSUE_TYPE);
+ assert.deepEqual(typeExtractor('iD'), fieldTypes.ISSUE_TYPE);
+ assert.deepEqual(typeExtractor('Id'), fieldTypes.ISSUE_TYPE);
+ });
+
+ it('gets type for ID', () => {
+ assert.deepEqual(typeExtractor('ID'), fieldTypes.ISSUE_TYPE);
+ });
+
+ it('gets type for Project', () => {
+ assert.deepEqual(typeExtractor('Project'), fieldTypes.PROJECT_TYPE);
+ });
+
+ it('gets type for Attachments', () => {
+ assert.deepEqual(typeExtractor('Attachments'), fieldTypes.INT_TYPE);
+ });
+
+ it('gets type for AllLabels', () => {
+ assert.deepEqual(typeExtractor('AllLabels'), fieldTypes.LABEL_TYPE);
+ });
+
+ it('gets type for AllLabels', () => {
+ assert.deepEqual(typeExtractor('AllLabels'), fieldTypes.LABEL_TYPE);
+ });
+
+ it('gets type for Blocked', () => {
+ assert.deepEqual(typeExtractor('Blocked'), fieldTypes.STR_TYPE);
+ });
+
+ it('gets type for BlockedOn', () => {
+ assert.deepEqual(typeExtractor('BlockedOn'), fieldTypes.ISSUE_TYPE);
+ });
+
+ it('gets type for Blocking', () => {
+ assert.deepEqual(typeExtractor('Blocking'), fieldTypes.ISSUE_TYPE);
+ });
+
+ it('gets type for CC', () => {
+ assert.deepEqual(typeExtractor('CC'), fieldTypes.USER_TYPE);
+ });
+
+ it('gets type for Closed', () => {
+ assert.deepEqual(typeExtractor('Closed'), fieldTypes.TIME_TYPE);
+ });
+
+ it('gets type for Component', () => {
+ assert.deepEqual(typeExtractor('Component'), fieldTypes.COMPONENT_TYPE);
+ });
+
+ it('gets type for ComponentModified', () => {
+ assert.deepEqual(typeExtractor('ComponentModified'),
+ fieldTypes.TIME_TYPE);
+ });
+
+ it('gets type for MergedInto', () => {
+ assert.deepEqual(typeExtractor('MergedInto'), fieldTypes.ISSUE_TYPE);
+ });
+
+ it('gets type for Modified', () => {
+ assert.deepEqual(typeExtractor('Modified'), fieldTypes.TIME_TYPE);
+ });
+
+ it('gets type for Reporter', () => {
+ assert.deepEqual(typeExtractor('Reporter'), fieldTypes.USER_TYPE);
+ });
+
+ it('gets type for Stars', () => {
+ assert.deepEqual(typeExtractor('Stars'), fieldTypes.INT_TYPE);
+ });
+
+ it('gets type for Status', () => {
+ assert.deepEqual(typeExtractor('Status'), fieldTypes.STATUS_TYPE);
+ });
+
+ it('gets type for StatusModified', () => {
+ assert.deepEqual(typeExtractor('StatusModified'), fieldTypes.TIME_TYPE);
+ });
+
+ it('gets type for Summary', () => {
+ assert.deepEqual(typeExtractor('Summary'), fieldTypes.STR_TYPE);
+ });
+
+ it('gets type for Type', () => {
+ assert.deepEqual(typeExtractor('Type'), fieldTypes.ENUM_TYPE);
+ });
+
+ it('gets type for Owner', () => {
+ assert.deepEqual(typeExtractor('Owner'), fieldTypes.USER_TYPE);
+ });
+
+ it('gets type for OwnerModified', () => {
+ assert.deepEqual(typeExtractor('OwnerModified'), fieldTypes.TIME_TYPE);
+ });
+
+ it('gets type for Opened', () => {
+ assert.deepEqual(typeExtractor('Opened'), fieldTypes.TIME_TYPE);
+ });
+ });
+
+ it('gets types for custom fields', () => {
+ typeExtractor = projectV0.extractTypeForFieldName({projectV0: {
+ name: example.PROJECT_NAME,
+ configs: {[example.PROJECT_NAME]: {fieldDefs: [
+ {fieldRef: {fieldName: 'CustomIntField', type: 'INT_TYPE'}},
+ {fieldRef: {fieldName: 'CustomStrField', type: 'STR_TYPE'}},
+ {fieldRef: {fieldName: 'CustomUserField', type: 'USER_TYPE'}},
+ {fieldRef: {fieldName: 'CustomEnumField', type: 'ENUM_TYPE'}},
+ {fieldRef: {fieldName: 'CustomApprovalField',
+ type: 'APPROVAL_TYPE'}},
+ ]}},
+ }});
+
+ assert.deepEqual(typeExtractor('CustomIntField'), fieldTypes.INT_TYPE);
+ assert.deepEqual(typeExtractor('CustomStrField'), fieldTypes.STR_TYPE);
+ assert.deepEqual(typeExtractor('CustomUserField'), fieldTypes.USER_TYPE);
+ assert.deepEqual(typeExtractor('CustomEnumField'), fieldTypes.ENUM_TYPE);
+ assert.deepEqual(typeExtractor('CustomApprovalField'),
+ fieldTypes.APPROVAL_TYPE);
+ });
+
+ it('defaults to string type for other fields', () => {
+ typeExtractor = projectV0.extractTypeForFieldName({projectV0: {
+ name: example.PROJECT_NAME,
+ configs: {[example.PROJECT_NAME]: {fieldDefs: [
+ {fieldRef: {fieldName: 'CustomIntField', type: 'INT_TYPE'}},
+ {fieldRef: {fieldName: 'CustomUserField', type: 'USER_TYPE'}},
+ ]}},
+ }});
+
+ assert.deepEqual(typeExtractor('FakeUserField'), fieldTypes.STR_TYPE);
+ assert.deepEqual(typeExtractor('NotOwner'), fieldTypes.STR_TYPE);
+ });
+ });
+
+ describe('extractFieldValuesFromIssue', () => {
+ let clock;
+ let issue;
+ let fieldExtractor;
+
+ describe('built-in fields', () => {
+ beforeEach(() => {
+ // Built-in fields will always act the same, regardless of
+ // project config.
+ fieldExtractor = projectV0.extractFieldValuesFromIssue({});
+
+ // Set clock to some specified date for relative time.
+ const initialTime = 365 * 24 * 60 * 60;
+
+ issue = {
+ localId: 33,
+ projectName: 'chromium',
+ summary: 'Test summary',
+ attachmentCount: 22,
+ starCount: 2,
+ componentRefs: [{path: 'Infra'}, {path: 'Monorail>UI'}],
+ blockedOnIssueRefs: [{localId: 30, projectName: 'chromium'}],
+ blockingIssueRefs: [{localId: 60, projectName: 'chromium'}],
+ labelRefs: [{label: 'Restrict-View-Google'}, {label: 'Type-Defect'}],
+ reporterRef: {displayName: 'test@example.com'},
+ ccRefs: [{displayName: 'test@example.com'}],
+ ownerRef: {displayName: 'owner@example.com'},
+ closedTimestamp: initialTime - 120, // 2 minutes ago
+ modifiedTimestamp: initialTime - 60, // a minute ago
+ openedTimestamp: initialTime - 24 * 60 * 60, // a day ago
+ componentModifiedTimestamp: initialTime - 60, // a minute ago
+ statusModifiedTimestamp: initialTime - 60, // a minute ago
+ ownerModifiedTimestamp: initialTime - 60, // a minute ago
+ statusRef: {status: 'Duplicate'},
+ mergedIntoIssueRef: {localId: 31, projectName: 'chromium'},
+ };
+
+ clock = sinon.useFakeTimers({
+ now: new Date(initialTime * 1000),
+ shouldAdvanceTime: false,
+ });
+ });
+
+ afterEach(() => {
+ clock.restore();
+ });
+
+ it('computes strings for ID', () => {
+ const fieldName = 'ID';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['chromium:33']);
+ });
+
+ it('computes strings for Project', () => {
+ const fieldName = 'Project';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['chromium']);
+ });
+
+ it('computes strings for Attachments', () => {
+ const fieldName = 'Attachments';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['22']);
+ });
+
+ it('computes strings for AllLabels', () => {
+ const fieldName = 'AllLabels';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['Restrict-View-Google', 'Type-Defect']);
+ });
+
+ it('computes strings for Blocked when issue is blocked', () => {
+ const fieldName = 'Blocked';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['Yes']);
+ });
+
+ it('computes strings for Blocked when issue is not blocked', () => {
+ const fieldName = 'Blocked';
+ issue.blockedOnIssueRefs = [];
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['No']);
+ });
+
+ it('computes strings for BlockedOn', () => {
+ const fieldName = 'BlockedOn';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['chromium:30']);
+ });
+
+ it('computes strings for Blocking', () => {
+ const fieldName = 'Blocking';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['chromium:60']);
+ });
+
+ it('computes strings for CC', () => {
+ const fieldName = 'CC';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['test@example.com']);
+ });
+
+ it('computes strings for Closed', () => {
+ const fieldName = 'Closed';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['2 minutes ago']);
+ });
+
+ it('computes strings for Component', () => {
+ const fieldName = 'Component';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['Infra', 'Monorail>UI']);
+ });
+
+ it('computes strings for ComponentModified', () => {
+ const fieldName = 'ComponentModified';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['a minute ago']);
+ });
+
+ it('computes strings for MergedInto', () => {
+ const fieldName = 'MergedInto';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['chromium:31']);
+ });
+
+ it('computes strings for Modified', () => {
+ const fieldName = 'Modified';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['a minute ago']);
+ });
+
+ it('computes strings for Reporter', () => {
+ const fieldName = 'Reporter';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['test@example.com']);
+ });
+
+ it('computes strings for Stars', () => {
+ const fieldName = 'Stars';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['2']);
+ });
+
+ it('computes strings for Status', () => {
+ const fieldName = 'Status';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['Duplicate']);
+ });
+
+ it('computes strings for StatusModified', () => {
+ const fieldName = 'StatusModified';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['a minute ago']);
+ });
+
+ it('computes strings for Summary', () => {
+ const fieldName = 'Summary';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['Test summary']);
+ });
+
+ it('computes strings for Type', () => {
+ const fieldName = 'Type';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['Defect']);
+ });
+
+ it('computes strings for Owner', () => {
+ const fieldName = 'Owner';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['owner@example.com']);
+ });
+
+ it('computes strings for OwnerModified', () => {
+ const fieldName = 'OwnerModified';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['a minute ago']);
+ });
+
+ it('computes strings for Opened', () => {
+ const fieldName = 'Opened';
+
+ assert.deepEqual(fieldExtractor(issue, fieldName),
+ ['a day ago']);
+ });
+ });
+
+ describe('custom approval fields', () => {
+ beforeEach(() => {
+ const fieldDefs = [
+ {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Goose-Approval'}},
+ {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Chicken-Approval'}},
+ {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Dodo-Approval'}},
+ ];
+ fieldExtractor = projectV0.extractFieldValuesFromIssue({
+ projectV0: {
+ name: example.PROJECT_NAME,
+ configs: {
+ [example.PROJECT_NAME]: {
+ projectName: 'chromium',
+ fieldDefs,
+ },
+ },
+ },
+ });
+
+ issue = {
+ localId: 33,
+ projectName: 'bird',
+ approvalValues: [
+ {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Goose-Approval'},
+ approverRefs: []},
+ {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Chicken-Approval'},
+ status: 'APPROVED'},
+ {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Dodo-Approval'},
+ status: 'NEED_INFO', approverRefs: [
+ {displayName: 'kiwi@bird.test'},
+ {displayName: 'mini-dino@bird.test'},
+ ],
+ },
+ ],
+ };
+ });
+
+ it('handles approval approver columns', () => {
+ assert.deepEqual(fieldExtractor(issue, 'goose-approval-approver'), []);
+ assert.deepEqual(fieldExtractor(issue, 'chicken-approval-approver'),
+ []);
+ assert.deepEqual(fieldExtractor(issue, 'dodo-approval-approver'),
+ ['kiwi@bird.test', 'mini-dino@bird.test']);
+ });
+
+ it('handles approval value columns', () => {
+ assert.deepEqual(fieldExtractor(issue, 'goose-approval'), ['NotSet']);
+ assert.deepEqual(fieldExtractor(issue, 'chicken-approval'),
+ ['Approved']);
+ assert.deepEqual(fieldExtractor(issue, 'dodo-approval'),
+ ['NeedInfo']);
+ });
+ });
+
+ describe('custom fields', () => {
+ beforeEach(() => {
+ const fieldDefs = [
+ {fieldRef: {type: 'STR_TYPE', fieldName: 'aString'}},
+ {fieldRef: {type: 'ENUM_TYPE', fieldName: 'ENUM'}},
+ {fieldRef: {type: 'INT_TYPE', fieldName: 'Cow-Number'},
+ bool_is_phase_field: true, is_multivalued: true},
+ ];
+ // As a label prefix, aString conflicts with the custom field named
+ // "aString". In this case, Monorail gives precedence to the
+ // custom field.
+ const labelDefs = [
+ {label: 'aString-ignore'},
+ {label: 'aString-two'},
+ ];
+ fieldExtractor = projectV0.extractFieldValuesFromIssue({
+ projectV0: {
+ name: example.PROJECT_NAME,
+ configs: {
+ [example.PROJECT_NAME]: {
+ projectName: 'chromium',
+ fieldDefs,
+ labelDefs,
+ },
+ },
+ },
+ });
+
+ const fieldValues = [
+ {fieldRef: {type: 'STR_TYPE', fieldName: 'aString'},
+ value: 'test'},
+ {fieldRef: {type: 'STR_TYPE', fieldName: 'aString'},
+ value: 'test2'},
+ {fieldRef: {type: 'ENUM_TYPE', fieldName: 'ENUM'},
+ value: 'a-value'},
+ {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'},
+ phaseRef: {phaseName: 'Cow-Phase'}, value: '55'},
+ {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'},
+ phaseRef: {phaseName: 'Cow-Phase'}, value: '54'},
+ {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'},
+ phaseRef: {phaseName: 'MilkCow-Phase'}, value: '56'},
+ ];
+
+ issue = {
+ localId: 33,
+ projectName: 'chromium',
+ fieldValues,
+ };
+ });
+
+ it('gets values for custom fields', () => {
+ assert.deepEqual(fieldExtractor(issue, 'aString'), ['test', 'test2']);
+ assert.deepEqual(fieldExtractor(issue, 'enum'), ['a-value']);
+ assert.deepEqual(fieldExtractor(issue, 'cow-phase.cow-number'),
+ ['55', '54']);
+ assert.deepEqual(fieldExtractor(issue, 'milkcow-phase.cow-number'),
+ ['56']);
+ });
+
+ it('custom fields get precedence over label fields', () => {
+ issue.labelRefs = [{label: 'aString-ignore'}];
+ assert.deepEqual(fieldExtractor(issue, 'aString'),
+ ['test', 'test2']);
+ });
+ });
+
+ describe('label prefix fields', () => {
+ beforeEach(() => {
+ issue = {
+ localId: 33,
+ projectName: 'chromium',
+ labelRefs: [
+ {label: 'test-label'},
+ {label: 'test-label-2'},
+ {label: 'ignore-me'},
+ {label: 'Milestone-UI'},
+ {label: 'Milestone-Goodies'},
+ ],
+ };
+
+ fieldExtractor = projectV0.extractFieldValuesFromIssue({
+ projectV0: {
+ name: example.PROJECT_NAME,
+ configs: {
+ [example.PROJECT_NAME]: {
+ projectName: 'chromium',
+ labelDefs: [
+ {label: 'test-1'},
+ {label: 'test-2'},
+ {label: 'milestone-1'},
+ {label: 'milestone-2'},
+ ],
+ },
+ },
+ },
+ });
+ });
+
+ it('gets values for label prefixes', () => {
+ assert.deepEqual(fieldExtractor(issue, 'test'), ['label', 'label-2']);
+ assert.deepEqual(fieldExtractor(issue, 'Milestone'), ['UI', 'Goodies']);
+ });
+ });
+ });
+
+ it('fieldDefsByApprovalName', () => {
+ assert.deepEqual(projectV0.fieldDefsByApprovalName({projectV0: {}}),
+ new Map());
+
+ assert.deepEqual(projectV0.fieldDefsByApprovalName({projectV0: {
+ name: example.PROJECT_NAME,
+ configs: {[example.PROJECT_NAME]: {
+ fieldDefs: [
+ {fieldRef: {fieldName: 'test', type: fieldTypes.INT_TYPE}},
+ {fieldRef: {fieldName: 'ignoreMe', type: fieldTypes.APPROVAL_TYPE}},
+ {fieldRef: {fieldName: 'yay', approvalName: 'ThisIsAnApproval'}},
+ {fieldRef: {fieldName: 'ImAField', approvalName: 'ThisIsAnApproval'}},
+ {fieldRef: {fieldName: 'TalkToALawyer', approvalName: 'Legal'}},
+ ],
+ }},
+ }}), new Map([
+ ['ThisIsAnApproval', [
+ {fieldRef: {fieldName: 'yay', approvalName: 'ThisIsAnApproval'}},
+ {fieldRef: {fieldName: 'ImAField', approvalName: 'ThisIsAnApproval'}},
+ ]],
+ ['Legal', [
+ {fieldRef: {fieldName: 'TalkToALawyer', approvalName: 'Legal'}},
+ ]],
+ ]));
+ });
+});
+
+let dispatch;
+
+describe('project action creators', () => {
+ beforeEach(() => {
+ sinon.stub(prpcClient, 'call');
+
+ dispatch = sinon.stub();
+ });
+
+ afterEach(() => {
+ prpcClient.call.restore();
+ });
+
+ it('select', () => {
+ projectV0.select('project-name')(dispatch);
+ const action = {type: projectV0.SELECT, projectName: 'project-name'};
+ sinon.assert.calledWith(dispatch, action);
+ });
+
+ it('fetchCustomPermissions', async () => {
+ const action = projectV0.fetchCustomPermissions('chromium');
+
+ prpcClient.call.returns(Promise.resolve({permissions: ['google']}));
+
+ await action(dispatch);
+
+ sinon.assert.calledWith(dispatch,
+ {type: projectV0.FETCH_CUSTOM_PERMISSIONS_START});
+
+ sinon.assert.calledWith(
+ prpcClient.call,
+ 'monorail.Projects',
+ 'GetCustomPermissions',
+ {projectName: 'chromium'});
+
+ sinon.assert.calledWith(dispatch, {
+ type: projectV0.FETCH_CUSTOM_PERMISSIONS_SUCCESS,
+ projectName: 'chromium',
+ permissions: ['google'],
+ });
+ });
+
+ it('fetchPresentationConfig', async () => {
+ const action = projectV0.fetchPresentationConfig('chromium');
+
+ prpcClient.call.returns(Promise.resolve({projectThumbnailUrl: 'test'}));
+
+ await action(dispatch);
+
+ sinon.assert.calledWith(dispatch,
+ {type: projectV0.FETCH_PRESENTATION_CONFIG_START});
+
+ sinon.assert.calledWith(
+ prpcClient.call,
+ 'monorail.Projects',
+ 'GetPresentationConfig',
+ {projectName: 'chromium'});
+
+ sinon.assert.calledWith(dispatch, {
+ type: projectV0.FETCH_PRESENTATION_CONFIG_SUCCESS,
+ projectName: 'chromium',
+ presentationConfig: {projectThumbnailUrl: 'test'},
+ });
+ });
+
+ it('fetchVisibleMembers', async () => {
+ const action = projectV0.fetchVisibleMembers('chromium');
+
+ prpcClient.call.returns(Promise.resolve({userRefs: [{userId: '123'}]}));
+
+ await action(dispatch);
+
+ sinon.assert.calledWith(dispatch,
+ {type: projectV0.FETCH_VISIBLE_MEMBERS_START});
+
+ sinon.assert.calledWith(
+ prpcClient.call,
+ 'monorail.Projects',
+ 'GetVisibleMembers',
+ {projectName: 'chromium'});
+
+ sinon.assert.calledWith(dispatch, {
+ type: projectV0.FETCH_VISIBLE_MEMBERS_SUCCESS,
+ projectName: 'chromium',
+ visibleMembers: {userRefs: [{userId: '123'}]},
+ });
+ });
+});
+
+describe('helpers', () => {
+ beforeEach(() => {
+ sinon.stub(prpcClient, 'call');
+ });
+
+ afterEach(() => {
+ prpcClient.call.restore();
+ });
+
+ describe('fetchFieldPerms', () => {
+ it('fetch field permissions', async () => {
+ const projectName = 'proj';
+ const fieldDefs = [
+ {
+ fieldRef: {
+ fieldName: 'testField',
+ fieldId: 1,
+ type: 'ENUM_TYPE',
+ },
+ },
+ ];
+ const response = {};
+ prpcClient.call.returns(Promise.resolve(response));
+
+ await store.dispatch(projectV0.fetchFieldPerms(projectName, fieldDefs));
+
+ const args = {names: ['projects/proj/fieldDefs/1']};
+ sinon.assert.calledWith(
+ prpcClient.call, 'monorail.v3.Permissions',
+ 'BatchGetPermissionSets', args);
+ });
+
+ it('fetch with no fieldDefs', async () => {
+ const config = {projectName: 'proj'};
+ const response = {};
+ prpcClient.call.returns(Promise.resolve(response));
+
+ // fieldDefs will be undefined.
+ await store.dispatch(projectV0.fetchFieldPerms(
+ config.projectName, config.fieldDefs));
+
+ const args = {names: []};
+ sinon.assert.calledWith(
+ prpcClient.call, 'monorail.v3.Permissions',
+ 'BatchGetPermissionSets', args);
+ });
+ });
+});