Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | // Copyright 2019 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import {assert} from 'chai'; |
| 6 | import sinon from 'sinon'; |
| 7 | import {prpcClient} from 'prpc-client-instance.js'; |
| 8 | import * as projectV0 from './projectV0.js'; |
| 9 | import {store} from './base.js'; |
| 10 | import * as example from 'shared/test/constants-projectV0.js'; |
| 11 | import {restrictionLabelsForPermissions} from 'shared/convertersV0.js'; |
| 12 | import {fieldTypes, SITEWIDE_DEFAULT_COLUMNS} from 'shared/issue-fields.js'; |
| 13 | |
| 14 | describe('project reducers', () => { |
| 15 | it('root reducer initial state', () => { |
| 16 | const actual = projectV0.reducer(undefined, {type: null}); |
| 17 | const expected = { |
| 18 | name: null, |
| 19 | configs: {}, |
| 20 | presentationConfigs: {}, |
| 21 | customPermissions: {}, |
| 22 | visibleMembers: {}, |
| 23 | templates: {}, |
| 24 | requests: { |
| 25 | fetchConfig: { |
| 26 | error: null, |
| 27 | requesting: false, |
| 28 | }, |
| 29 | fetchCustomPermissions: { |
| 30 | error: null, |
| 31 | requesting: false, |
| 32 | }, |
| 33 | fetchMembers: { |
| 34 | error: null, |
| 35 | requesting: false, |
| 36 | }, |
| 37 | fetchPresentationConfig: { |
| 38 | error: null, |
| 39 | requesting: false, |
| 40 | }, |
| 41 | fetchTemplates: { |
| 42 | error: null, |
| 43 | requesting: false, |
| 44 | }, |
| 45 | }, |
| 46 | }; |
| 47 | assert.deepEqual(actual, expected); |
| 48 | }); |
| 49 | |
| 50 | it('name', () => { |
| 51 | const action = {type: projectV0.SELECT, projectName: example.PROJECT_NAME}; |
| 52 | assert.deepEqual(projectV0.nameReducer(null, action), example.PROJECT_NAME); |
| 53 | }); |
| 54 | |
| 55 | it('configs updates when fetching Config', () => { |
| 56 | const action = { |
| 57 | type: projectV0.FETCH_CONFIG_SUCCESS, |
| 58 | projectName: example.PROJECT_NAME, |
| 59 | config: example.CONFIG, |
| 60 | }; |
| 61 | const expected = {[example.PROJECT_NAME]: example.CONFIG}; |
| 62 | assert.deepEqual(projectV0.configsReducer({}, action), expected); |
| 63 | }); |
| 64 | |
| 65 | it('customPermissions', () => { |
| 66 | const action = { |
| 67 | type: projectV0.FETCH_CUSTOM_PERMISSIONS_SUCCESS, |
| 68 | projectName: example.PROJECT_NAME, |
| 69 | permissions: example.CUSTOM_PERMISSIONS, |
| 70 | }; |
| 71 | const expected = {[example.PROJECT_NAME]: example.CUSTOM_PERMISSIONS}; |
| 72 | assert.deepEqual(projectV0.customPermissionsReducer({}, action), expected); |
| 73 | }); |
| 74 | |
| 75 | it('presentationConfigs', () => { |
| 76 | const action = { |
| 77 | type: projectV0.FETCH_PRESENTATION_CONFIG_SUCCESS, |
| 78 | projectName: example.PROJECT_NAME, |
| 79 | presentationConfig: example.PRESENTATION_CONFIG, |
| 80 | }; |
| 81 | const expected = {[example.PROJECT_NAME]: example.PRESENTATION_CONFIG}; |
| 82 | assert.deepEqual(projectV0.presentationConfigsReducer({}, action), |
| 83 | expected); |
| 84 | }); |
| 85 | |
| 86 | it('visibleMembers', () => { |
| 87 | const action = { |
| 88 | type: projectV0.FETCH_VISIBLE_MEMBERS_SUCCESS, |
| 89 | projectName: example.PROJECT_NAME, |
| 90 | visibleMembers: example.VISIBLE_MEMBERS, |
| 91 | }; |
| 92 | const expected = {[example.PROJECT_NAME]: example.VISIBLE_MEMBERS}; |
| 93 | assert.deepEqual(projectV0.visibleMembersReducer({}, action), expected); |
| 94 | }); |
| 95 | |
| 96 | it('templates', () => { |
| 97 | const action = { |
| 98 | type: projectV0.FETCH_TEMPLATES_SUCCESS, |
| 99 | projectName: example.PROJECT_NAME, |
| 100 | templates: [example.TEMPLATE_DEF], |
| 101 | }; |
| 102 | const expected = {[example.PROJECT_NAME]: [example.TEMPLATE_DEF]}; |
| 103 | assert.deepEqual(projectV0.templatesReducer({}, action), expected); |
| 104 | }); |
| 105 | }); |
| 106 | |
| 107 | describe('project selectors', () => { |
| 108 | it('viewedProjectName', () => { |
| 109 | const actual = projectV0.viewedProjectName(example.STATE); |
| 110 | assert.deepEqual(actual, example.PROJECT_NAME); |
| 111 | }); |
| 112 | |
| 113 | it('viewedVisibleMembers', () => { |
| 114 | assert.deepEqual(projectV0.viewedVisibleMembers({}), {}); |
| 115 | assert.deepEqual(projectV0.viewedVisibleMembers({projectV0: {}}), {}); |
| 116 | assert.deepEqual(projectV0.viewedVisibleMembers( |
| 117 | {projectV0: {visibleMembers: {}}}), {}); |
| 118 | const actual = projectV0.viewedVisibleMembers(example.STATE); |
| 119 | assert.deepEqual(actual, example.VISIBLE_MEMBERS); |
| 120 | }); |
| 121 | |
| 122 | it('viewedCustomPermissions', () => { |
| 123 | assert.deepEqual(projectV0.viewedCustomPermissions({}), []); |
| 124 | assert.deepEqual(projectV0.viewedCustomPermissions({projectV0: {}}), []); |
| 125 | assert.deepEqual(projectV0.viewedCustomPermissions( |
| 126 | {projectV0: {customPermissions: {}}}), []); |
| 127 | const actual = projectV0.viewedCustomPermissions(example.STATE); |
| 128 | assert.deepEqual(actual, example.CUSTOM_PERMISSIONS); |
| 129 | }); |
| 130 | |
| 131 | it('viewedPresentationConfig', () => { |
| 132 | assert.deepEqual(projectV0.viewedPresentationConfig({}), {}); |
| 133 | assert.deepEqual(projectV0.viewedPresentationConfig({projectV0: {}}), {}); |
| 134 | const actual = projectV0.viewedPresentationConfig(example.STATE); |
| 135 | assert.deepEqual(actual, example.PRESENTATION_CONFIG); |
| 136 | }); |
| 137 | |
| 138 | it('defaultColumns', () => { |
| 139 | assert.deepEqual(projectV0.defaultColumns({}), SITEWIDE_DEFAULT_COLUMNS); |
| 140 | assert.deepEqual( |
| 141 | projectV0.defaultColumns({projectV0: {}}), SITEWIDE_DEFAULT_COLUMNS); |
| 142 | assert.deepEqual( |
| 143 | projectV0.defaultColumns({projectV0: {presentationConfig: {}}}), |
| 144 | SITEWIDE_DEFAULT_COLUMNS); |
| 145 | const expected = ['ID', 'Summary', 'AllLabels']; |
| 146 | assert.deepEqual(projectV0.defaultColumns(example.STATE), expected); |
| 147 | }); |
| 148 | |
| 149 | it('defaultQuery', () => { |
| 150 | assert.deepEqual(projectV0.defaultQuery({}), ''); |
| 151 | assert.deepEqual(projectV0.defaultQuery({projectV0: {}}), ''); |
| 152 | const actual = projectV0.defaultQuery(example.STATE); |
| 153 | assert.deepEqual(actual, example.DEFAULT_QUERY); |
| 154 | }); |
| 155 | |
| 156 | it('fieldDefs', () => { |
| 157 | assert.deepEqual(projectV0.fieldDefs({projectV0: {}}), []); |
| 158 | assert.deepEqual(projectV0.fieldDefs({projectV0: {config: {}}}), []); |
| 159 | const actual = projectV0.fieldDefs(example.STATE); |
| 160 | assert.deepEqual(actual, example.FIELD_DEFS); |
| 161 | }); |
| 162 | |
| 163 | it('labelDefMap', () => { |
| 164 | const labelDefs = (permissions) => |
| 165 | restrictionLabelsForPermissions(permissions).map((labelDef) => |
| 166 | [labelDef.label.toLowerCase(), labelDef]); |
| 167 | |
| 168 | assert.deepEqual( |
| 169 | projectV0.labelDefMap({projectV0: {}}), new Map(labelDefs([]))); |
| 170 | assert.deepEqual( |
| 171 | projectV0.labelDefMap({projectV0: {config: {}}}), new Map(labelDefs([]))); |
| 172 | const expected = new Map([ |
| 173 | ['one', {label: 'One'}], |
| 174 | ['enum', {label: 'EnUm'}], |
| 175 | ['enum-options', {label: 'eNuM-Options'}], |
| 176 | ['hello-world', {label: 'hello-world', docstring: 'hmmm'}], |
| 177 | ['hello-me', {label: 'hello-me', docstring: 'hmmm'}], |
| 178 | ...labelDefs(example.CUSTOM_PERMISSIONS), |
| 179 | ]); |
| 180 | assert.deepEqual(projectV0.labelDefMap(example.STATE), expected); |
| 181 | }); |
| 182 | |
| 183 | it('labelPrefixValueMap', () => { |
| 184 | const builtInLabelPrefixes = [ |
| 185 | ['Restrict', new Set(['View-EditIssue', 'AddIssueComment-EditIssue'])], |
| 186 | ['Restrict-View', new Set(['EditIssue'])], |
| 187 | ['Restrict-AddIssueComment', new Set(['EditIssue'])], |
| 188 | ]; |
| 189 | assert.deepEqual(projectV0.labelPrefixValueMap({projectV0: {}}), |
| 190 | new Map(builtInLabelPrefixes)); |
| 191 | |
| 192 | assert.deepEqual(projectV0.labelPrefixValueMap( |
| 193 | {projectV0: {config: {}}}), new Map(builtInLabelPrefixes)); |
| 194 | |
| 195 | const expected = new Map([ |
| 196 | ['Restrict', new Set(['View-Google', 'View-Security', 'EditIssue-Google', |
| 197 | 'EditIssue-Security', 'AddIssueComment-Google', |
| 198 | 'AddIssueComment-Security', 'DeleteIssue-Google', |
| 199 | 'DeleteIssue-Security', 'FlagSpam-Google', 'FlagSpam-Security', |
| 200 | 'View-EditIssue', 'AddIssueComment-EditIssue'])], |
| 201 | ['Restrict-View', new Set(['Google', 'Security', 'EditIssue'])], |
| 202 | ['Restrict-EditIssue', new Set(['Google', 'Security'])], |
| 203 | ['Restrict-AddIssueComment', new Set(['Google', 'Security', 'EditIssue'])], |
| 204 | ['Restrict-DeleteIssue', new Set(['Google', 'Security'])], |
| 205 | ['Restrict-FlagSpam', new Set(['Google', 'Security'])], |
| 206 | ['eNuM', new Set(['Options'])], |
| 207 | ['hello', new Set(['world', 'me'])], |
| 208 | ]); |
| 209 | assert.deepEqual(projectV0.labelPrefixValueMap(example.STATE), expected); |
| 210 | }); |
| 211 | |
| 212 | it('labelPrefixFields', () => { |
| 213 | const fields1 = projectV0.labelPrefixFields({projectV0: {}}); |
| 214 | assert.deepEqual(fields1, ['Restrict']); |
| 215 | const fields2 = projectV0.labelPrefixFields({projectV0: {config: {}}}); |
| 216 | assert.deepEqual(fields2, ['Restrict']); |
| 217 | const expected = [ |
| 218 | 'hello', 'Restrict', 'Restrict-View', 'Restrict-EditIssue', |
| 219 | 'Restrict-AddIssueComment', 'Restrict-DeleteIssue', 'Restrict-FlagSpam' |
| 220 | ]; |
| 221 | assert.deepEqual(projectV0.labelPrefixFields(example.STATE), expected); |
| 222 | }); |
| 223 | |
| 224 | it('enumFieldDefs', () => { |
| 225 | assert.deepEqual(projectV0.enumFieldDefs({projectV0: {}}), []); |
| 226 | assert.deepEqual(projectV0.enumFieldDefs({projectV0: {config: {}}}), []); |
| 227 | const expected = [example.FIELD_DEF_ENUM]; |
| 228 | assert.deepEqual(projectV0.enumFieldDefs(example.STATE), expected); |
| 229 | }); |
| 230 | |
| 231 | it('optionsPerEnumField', () => { |
| 232 | assert.deepEqual(projectV0.optionsPerEnumField({projectV0: {}}), new Map()); |
| 233 | const expected = new Map([ |
| 234 | ['enum', [ |
| 235 | {label: 'eNuM-Options', optionName: 'Options'}, |
| 236 | ]], |
| 237 | ]); |
| 238 | assert.deepEqual(projectV0.optionsPerEnumField(example.STATE), expected); |
| 239 | }); |
| 240 | |
| 241 | it('viewedPresentationConfigLoaded', () => { |
| 242 | const loadConfigAction = { |
| 243 | type: projectV0.FETCH_PRESENTATION_CONFIG_SUCCESS, |
| 244 | projectName: example.PROJECT_NAME, |
| 245 | presentationConfig: example.PRESENTATION_CONFIG, |
| 246 | }; |
| 247 | const selectProjectAction = { |
| 248 | type: projectV0.SELECT, |
| 249 | projectName: example.PROJECT_NAME, |
| 250 | }; |
| 251 | let projectState = {}; |
| 252 | |
| 253 | assert.equal(false, projectV0.viewedPresentationConfigLoaded( |
| 254 | {projectV0: projectState})); |
| 255 | |
| 256 | projectState = projectV0.reducer(projectState, selectProjectAction); |
| 257 | projectState = projectV0.reducer(projectState, loadConfigAction); |
| 258 | |
| 259 | assert.equal(true, projectV0.viewedPresentationConfigLoaded( |
| 260 | {projectV0: projectState})); |
| 261 | }); |
| 262 | |
| 263 | it('fetchingPresentationConfig', () => { |
| 264 | const projectState = projectV0.reducer(undefined, {type: null}); |
| 265 | assert.equal(false, |
| 266 | projectState.requests.fetchPresentationConfig.requesting); |
| 267 | }); |
| 268 | |
| 269 | describe('extractTypeForFieldName', () => { |
| 270 | let typeExtractor; |
| 271 | |
| 272 | describe('built-in fields', () => { |
| 273 | beforeEach(() => { |
| 274 | typeExtractor = projectV0.extractTypeForFieldName({}); |
| 275 | }); |
| 276 | |
| 277 | it('not case sensitive', () => { |
| 278 | assert.deepEqual(typeExtractor('id'), fieldTypes.ISSUE_TYPE); |
| 279 | assert.deepEqual(typeExtractor('iD'), fieldTypes.ISSUE_TYPE); |
| 280 | assert.deepEqual(typeExtractor('Id'), fieldTypes.ISSUE_TYPE); |
| 281 | }); |
| 282 | |
| 283 | it('gets type for ID', () => { |
| 284 | assert.deepEqual(typeExtractor('ID'), fieldTypes.ISSUE_TYPE); |
| 285 | }); |
| 286 | |
| 287 | it('gets type for Project', () => { |
| 288 | assert.deepEqual(typeExtractor('Project'), fieldTypes.PROJECT_TYPE); |
| 289 | }); |
| 290 | |
| 291 | it('gets type for Attachments', () => { |
| 292 | assert.deepEqual(typeExtractor('Attachments'), fieldTypes.INT_TYPE); |
| 293 | }); |
| 294 | |
| 295 | it('gets type for AllLabels', () => { |
| 296 | assert.deepEqual(typeExtractor('AllLabels'), fieldTypes.LABEL_TYPE); |
| 297 | }); |
| 298 | |
| 299 | it('gets type for AllLabels', () => { |
| 300 | assert.deepEqual(typeExtractor('AllLabels'), fieldTypes.LABEL_TYPE); |
| 301 | }); |
| 302 | |
| 303 | it('gets type for Blocked', () => { |
| 304 | assert.deepEqual(typeExtractor('Blocked'), fieldTypes.STR_TYPE); |
| 305 | }); |
| 306 | |
| 307 | it('gets type for BlockedOn', () => { |
| 308 | assert.deepEqual(typeExtractor('BlockedOn'), fieldTypes.ISSUE_TYPE); |
| 309 | }); |
| 310 | |
| 311 | it('gets type for Blocking', () => { |
| 312 | assert.deepEqual(typeExtractor('Blocking'), fieldTypes.ISSUE_TYPE); |
| 313 | }); |
| 314 | |
| 315 | it('gets type for CC', () => { |
| 316 | assert.deepEqual(typeExtractor('CC'), fieldTypes.USER_TYPE); |
| 317 | }); |
| 318 | |
| 319 | it('gets type for Closed', () => { |
| 320 | assert.deepEqual(typeExtractor('Closed'), fieldTypes.TIME_TYPE); |
| 321 | }); |
| 322 | |
| 323 | it('gets type for Component', () => { |
| 324 | assert.deepEqual(typeExtractor('Component'), fieldTypes.COMPONENT_TYPE); |
| 325 | }); |
| 326 | |
| 327 | it('gets type for ComponentModified', () => { |
| 328 | assert.deepEqual(typeExtractor('ComponentModified'), |
| 329 | fieldTypes.TIME_TYPE); |
| 330 | }); |
| 331 | |
| 332 | it('gets type for MergedInto', () => { |
| 333 | assert.deepEqual(typeExtractor('MergedInto'), fieldTypes.ISSUE_TYPE); |
| 334 | }); |
| 335 | |
| 336 | it('gets type for Modified', () => { |
| 337 | assert.deepEqual(typeExtractor('Modified'), fieldTypes.TIME_TYPE); |
| 338 | }); |
| 339 | |
| 340 | it('gets type for Reporter', () => { |
| 341 | assert.deepEqual(typeExtractor('Reporter'), fieldTypes.USER_TYPE); |
| 342 | }); |
| 343 | |
| 344 | it('gets type for Stars', () => { |
| 345 | assert.deepEqual(typeExtractor('Stars'), fieldTypes.INT_TYPE); |
| 346 | }); |
| 347 | |
| 348 | it('gets type for Status', () => { |
| 349 | assert.deepEqual(typeExtractor('Status'), fieldTypes.STATUS_TYPE); |
| 350 | }); |
| 351 | |
| 352 | it('gets type for StatusModified', () => { |
| 353 | assert.deepEqual(typeExtractor('StatusModified'), fieldTypes.TIME_TYPE); |
| 354 | }); |
| 355 | |
| 356 | it('gets type for Summary', () => { |
| 357 | assert.deepEqual(typeExtractor('Summary'), fieldTypes.STR_TYPE); |
| 358 | }); |
| 359 | |
| 360 | it('gets type for Type', () => { |
| 361 | assert.deepEqual(typeExtractor('Type'), fieldTypes.ENUM_TYPE); |
| 362 | }); |
| 363 | |
| 364 | it('gets type for Owner', () => { |
| 365 | assert.deepEqual(typeExtractor('Owner'), fieldTypes.USER_TYPE); |
| 366 | }); |
| 367 | |
| 368 | it('gets type for OwnerModified', () => { |
| 369 | assert.deepEqual(typeExtractor('OwnerModified'), fieldTypes.TIME_TYPE); |
| 370 | }); |
| 371 | |
| 372 | it('gets type for Opened', () => { |
| 373 | assert.deepEqual(typeExtractor('Opened'), fieldTypes.TIME_TYPE); |
| 374 | }); |
| 375 | }); |
| 376 | |
| 377 | it('gets types for custom fields', () => { |
| 378 | typeExtractor = projectV0.extractTypeForFieldName({projectV0: { |
| 379 | name: example.PROJECT_NAME, |
| 380 | configs: {[example.PROJECT_NAME]: {fieldDefs: [ |
| 381 | {fieldRef: {fieldName: 'CustomIntField', type: 'INT_TYPE'}}, |
| 382 | {fieldRef: {fieldName: 'CustomStrField', type: 'STR_TYPE'}}, |
| 383 | {fieldRef: {fieldName: 'CustomUserField', type: 'USER_TYPE'}}, |
| 384 | {fieldRef: {fieldName: 'CustomEnumField', type: 'ENUM_TYPE'}}, |
| 385 | {fieldRef: {fieldName: 'CustomApprovalField', |
| 386 | type: 'APPROVAL_TYPE'}}, |
| 387 | ]}}, |
| 388 | }}); |
| 389 | |
| 390 | assert.deepEqual(typeExtractor('CustomIntField'), fieldTypes.INT_TYPE); |
| 391 | assert.deepEqual(typeExtractor('CustomStrField'), fieldTypes.STR_TYPE); |
| 392 | assert.deepEqual(typeExtractor('CustomUserField'), fieldTypes.USER_TYPE); |
| 393 | assert.deepEqual(typeExtractor('CustomEnumField'), fieldTypes.ENUM_TYPE); |
| 394 | assert.deepEqual(typeExtractor('CustomApprovalField'), |
| 395 | fieldTypes.APPROVAL_TYPE); |
| 396 | }); |
| 397 | |
| 398 | it('defaults to string type for other fields', () => { |
| 399 | typeExtractor = projectV0.extractTypeForFieldName({projectV0: { |
| 400 | name: example.PROJECT_NAME, |
| 401 | configs: {[example.PROJECT_NAME]: {fieldDefs: [ |
| 402 | {fieldRef: {fieldName: 'CustomIntField', type: 'INT_TYPE'}}, |
| 403 | {fieldRef: {fieldName: 'CustomUserField', type: 'USER_TYPE'}}, |
| 404 | ]}}, |
| 405 | }}); |
| 406 | |
| 407 | assert.deepEqual(typeExtractor('FakeUserField'), fieldTypes.STR_TYPE); |
| 408 | assert.deepEqual(typeExtractor('NotOwner'), fieldTypes.STR_TYPE); |
| 409 | }); |
| 410 | }); |
| 411 | |
| 412 | describe('extractFieldValuesFromIssue', () => { |
| 413 | let clock; |
| 414 | let issue; |
| 415 | let fieldExtractor; |
| 416 | |
| 417 | describe('built-in fields', () => { |
| 418 | beforeEach(() => { |
| 419 | // Built-in fields will always act the same, regardless of |
| 420 | // project config. |
| 421 | fieldExtractor = projectV0.extractFieldValuesFromIssue({}); |
| 422 | |
| 423 | // Set clock to some specified date for relative time. |
| 424 | const initialTime = 365 * 24 * 60 * 60; |
| 425 | |
| 426 | issue = { |
| 427 | localId: 33, |
| 428 | projectName: 'chromium', |
| 429 | summary: 'Test summary', |
| 430 | attachmentCount: 22, |
| 431 | starCount: 2, |
| 432 | componentRefs: [{path: 'Infra'}, {path: 'Monorail>UI'}], |
| 433 | blockedOnIssueRefs: [{localId: 30, projectName: 'chromium'}], |
| 434 | blockingIssueRefs: [{localId: 60, projectName: 'chromium'}], |
| 435 | labelRefs: [{label: 'Restrict-View-Google'}, {label: 'Type-Defect'}], |
| 436 | reporterRef: {displayName: 'test@example.com'}, |
| 437 | ccRefs: [{displayName: 'test@example.com'}], |
| 438 | ownerRef: {displayName: 'owner@example.com'}, |
| 439 | closedTimestamp: initialTime - 120, // 2 minutes ago |
| 440 | modifiedTimestamp: initialTime - 60, // a minute ago |
| 441 | openedTimestamp: initialTime - 24 * 60 * 60, // a day ago |
| 442 | componentModifiedTimestamp: initialTime - 60, // a minute ago |
| 443 | statusModifiedTimestamp: initialTime - 60, // a minute ago |
| 444 | ownerModifiedTimestamp: initialTime - 60, // a minute ago |
| 445 | statusRef: {status: 'Duplicate'}, |
| 446 | mergedIntoIssueRef: {localId: 31, projectName: 'chromium'}, |
| 447 | }; |
| 448 | |
| 449 | clock = sinon.useFakeTimers({ |
| 450 | now: new Date(initialTime * 1000), |
| 451 | shouldAdvanceTime: false, |
| 452 | }); |
| 453 | }); |
| 454 | |
| 455 | afterEach(() => { |
| 456 | clock.restore(); |
| 457 | }); |
| 458 | |
| 459 | it('computes strings for ID', () => { |
| 460 | const fieldName = 'ID'; |
| 461 | |
| 462 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 463 | ['chromium:33']); |
| 464 | }); |
| 465 | |
| 466 | it('computes strings for Project', () => { |
| 467 | const fieldName = 'Project'; |
| 468 | |
| 469 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 470 | ['chromium']); |
| 471 | }); |
| 472 | |
| 473 | it('computes strings for Attachments', () => { |
| 474 | const fieldName = 'Attachments'; |
| 475 | |
| 476 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 477 | ['22']); |
| 478 | }); |
| 479 | |
| 480 | it('computes strings for AllLabels', () => { |
| 481 | const fieldName = 'AllLabels'; |
| 482 | |
| 483 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 484 | ['Restrict-View-Google', 'Type-Defect']); |
| 485 | }); |
| 486 | |
| 487 | it('computes strings for Blocked when issue is blocked', () => { |
| 488 | const fieldName = 'Blocked'; |
| 489 | |
| 490 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 491 | ['Yes']); |
| 492 | }); |
| 493 | |
| 494 | it('computes strings for Blocked when issue is not blocked', () => { |
| 495 | const fieldName = 'Blocked'; |
| 496 | issue.blockedOnIssueRefs = []; |
| 497 | |
| 498 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 499 | ['No']); |
| 500 | }); |
| 501 | |
| 502 | it('computes strings for BlockedOn', () => { |
| 503 | const fieldName = 'BlockedOn'; |
| 504 | |
| 505 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 506 | ['chromium:30']); |
| 507 | }); |
| 508 | |
| 509 | it('computes strings for Blocking', () => { |
| 510 | const fieldName = 'Blocking'; |
| 511 | |
| 512 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 513 | ['chromium:60']); |
| 514 | }); |
| 515 | |
| 516 | it('computes strings for CC', () => { |
| 517 | const fieldName = 'CC'; |
| 518 | |
| 519 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 520 | ['test@example.com']); |
| 521 | }); |
| 522 | |
| 523 | it('computes strings for Closed', () => { |
| 524 | const fieldName = 'Closed'; |
| 525 | |
| 526 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 527 | ['2 minutes ago']); |
| 528 | }); |
| 529 | |
| 530 | it('computes strings for Component', () => { |
| 531 | const fieldName = 'Component'; |
| 532 | |
| 533 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 534 | ['Infra', 'Monorail>UI']); |
| 535 | }); |
| 536 | |
| 537 | it('computes strings for ComponentModified', () => { |
| 538 | const fieldName = 'ComponentModified'; |
| 539 | |
| 540 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 541 | ['a minute ago']); |
| 542 | }); |
| 543 | |
| 544 | it('computes strings for MergedInto', () => { |
| 545 | const fieldName = 'MergedInto'; |
| 546 | |
| 547 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 548 | ['chromium:31']); |
| 549 | }); |
| 550 | |
| 551 | it('computes strings for Modified', () => { |
| 552 | const fieldName = 'Modified'; |
| 553 | |
| 554 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 555 | ['a minute ago']); |
| 556 | }); |
| 557 | |
| 558 | it('computes strings for Reporter', () => { |
| 559 | const fieldName = 'Reporter'; |
| 560 | |
| 561 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 562 | ['test@example.com']); |
| 563 | }); |
| 564 | |
| 565 | it('computes strings for Stars', () => { |
| 566 | const fieldName = 'Stars'; |
| 567 | |
| 568 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 569 | ['2']); |
| 570 | }); |
| 571 | |
| 572 | it('computes strings for Status', () => { |
| 573 | const fieldName = 'Status'; |
| 574 | |
| 575 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 576 | ['Duplicate']); |
| 577 | }); |
| 578 | |
| 579 | it('computes strings for StatusModified', () => { |
| 580 | const fieldName = 'StatusModified'; |
| 581 | |
| 582 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 583 | ['a minute ago']); |
| 584 | }); |
| 585 | |
| 586 | it('computes strings for Summary', () => { |
| 587 | const fieldName = 'Summary'; |
| 588 | |
| 589 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 590 | ['Test summary']); |
| 591 | }); |
| 592 | |
| 593 | it('computes strings for Type', () => { |
| 594 | const fieldName = 'Type'; |
| 595 | |
| 596 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 597 | ['Defect']); |
| 598 | }); |
| 599 | |
| 600 | it('computes strings for Owner', () => { |
| 601 | const fieldName = 'Owner'; |
| 602 | |
| 603 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 604 | ['owner@example.com']); |
| 605 | }); |
| 606 | |
| 607 | it('computes strings for OwnerModified', () => { |
| 608 | const fieldName = 'OwnerModified'; |
| 609 | |
| 610 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 611 | ['a minute ago']); |
| 612 | }); |
| 613 | |
| 614 | it('computes strings for Opened', () => { |
| 615 | const fieldName = 'Opened'; |
| 616 | |
| 617 | assert.deepEqual(fieldExtractor(issue, fieldName), |
| 618 | ['a day ago']); |
| 619 | }); |
| 620 | }); |
| 621 | |
| 622 | describe('custom approval fields', () => { |
| 623 | beforeEach(() => { |
| 624 | const fieldDefs = [ |
| 625 | {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Goose-Approval'}}, |
| 626 | {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Chicken-Approval'}}, |
| 627 | {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Dodo-Approval'}}, |
| 628 | ]; |
| 629 | fieldExtractor = projectV0.extractFieldValuesFromIssue({ |
| 630 | projectV0: { |
| 631 | name: example.PROJECT_NAME, |
| 632 | configs: { |
| 633 | [example.PROJECT_NAME]: { |
| 634 | projectName: 'chromium', |
| 635 | fieldDefs, |
| 636 | }, |
| 637 | }, |
| 638 | }, |
| 639 | }); |
| 640 | |
| 641 | issue = { |
| 642 | localId: 33, |
| 643 | projectName: 'bird', |
| 644 | approvalValues: [ |
| 645 | {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Goose-Approval'}, |
| 646 | approverRefs: []}, |
| 647 | {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Chicken-Approval'}, |
| 648 | status: 'APPROVED'}, |
| 649 | {fieldRef: {type: 'APPROVAL_TYPE', fieldName: 'Dodo-Approval'}, |
| 650 | status: 'NEED_INFO', approverRefs: [ |
| 651 | {displayName: 'kiwi@bird.test'}, |
| 652 | {displayName: 'mini-dino@bird.test'}, |
| 653 | ], |
| 654 | }, |
| 655 | ], |
| 656 | }; |
| 657 | }); |
| 658 | |
| 659 | it('handles approval approver columns', () => { |
| 660 | assert.deepEqual(fieldExtractor(issue, 'goose-approval-approver'), []); |
| 661 | assert.deepEqual(fieldExtractor(issue, 'chicken-approval-approver'), |
| 662 | []); |
| 663 | assert.deepEqual(fieldExtractor(issue, 'dodo-approval-approver'), |
| 664 | ['kiwi@bird.test', 'mini-dino@bird.test']); |
| 665 | }); |
| 666 | |
| 667 | it('handles approval value columns', () => { |
| 668 | assert.deepEqual(fieldExtractor(issue, 'goose-approval'), ['NotSet']); |
| 669 | assert.deepEqual(fieldExtractor(issue, 'chicken-approval'), |
| 670 | ['Approved']); |
| 671 | assert.deepEqual(fieldExtractor(issue, 'dodo-approval'), |
| 672 | ['NeedInfo']); |
| 673 | }); |
| 674 | }); |
| 675 | |
| 676 | describe('custom fields', () => { |
| 677 | beforeEach(() => { |
| 678 | const fieldDefs = [ |
| 679 | {fieldRef: {type: 'STR_TYPE', fieldName: 'aString'}}, |
| 680 | {fieldRef: {type: 'ENUM_TYPE', fieldName: 'ENUM'}}, |
| 681 | {fieldRef: {type: 'INT_TYPE', fieldName: 'Cow-Number'}, |
| 682 | bool_is_phase_field: true, is_multivalued: true}, |
| 683 | ]; |
| 684 | // As a label prefix, aString conflicts with the custom field named |
| 685 | // "aString". In this case, Monorail gives precedence to the |
| 686 | // custom field. |
| 687 | const labelDefs = [ |
| 688 | {label: 'aString-ignore'}, |
| 689 | {label: 'aString-two'}, |
| 690 | ]; |
| 691 | fieldExtractor = projectV0.extractFieldValuesFromIssue({ |
| 692 | projectV0: { |
| 693 | name: example.PROJECT_NAME, |
| 694 | configs: { |
| 695 | [example.PROJECT_NAME]: { |
| 696 | projectName: 'chromium', |
| 697 | fieldDefs, |
| 698 | labelDefs, |
| 699 | }, |
| 700 | }, |
| 701 | }, |
| 702 | }); |
| 703 | |
| 704 | const fieldValues = [ |
| 705 | {fieldRef: {type: 'STR_TYPE', fieldName: 'aString'}, |
| 706 | value: 'test'}, |
| 707 | {fieldRef: {type: 'STR_TYPE', fieldName: 'aString'}, |
| 708 | value: 'test2'}, |
| 709 | {fieldRef: {type: 'ENUM_TYPE', fieldName: 'ENUM'}, |
| 710 | value: 'a-value'}, |
| 711 | {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'}, |
| 712 | phaseRef: {phaseName: 'Cow-Phase'}, value: '55'}, |
| 713 | {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'}, |
| 714 | phaseRef: {phaseName: 'Cow-Phase'}, value: '54'}, |
| 715 | {fieldRef: {type: 'INT_TYPE', fieldId: '6', fieldName: 'Cow-Number'}, |
| 716 | phaseRef: {phaseName: 'MilkCow-Phase'}, value: '56'}, |
| 717 | ]; |
| 718 | |
| 719 | issue = { |
| 720 | localId: 33, |
| 721 | projectName: 'chromium', |
| 722 | fieldValues, |
| 723 | }; |
| 724 | }); |
| 725 | |
| 726 | it('gets values for custom fields', () => { |
| 727 | assert.deepEqual(fieldExtractor(issue, 'aString'), ['test', 'test2']); |
| 728 | assert.deepEqual(fieldExtractor(issue, 'enum'), ['a-value']); |
| 729 | assert.deepEqual(fieldExtractor(issue, 'cow-phase.cow-number'), |
| 730 | ['55', '54']); |
| 731 | assert.deepEqual(fieldExtractor(issue, 'milkcow-phase.cow-number'), |
| 732 | ['56']); |
| 733 | }); |
| 734 | |
| 735 | it('custom fields get precedence over label fields', () => { |
| 736 | issue.labelRefs = [{label: 'aString-ignore'}]; |
| 737 | assert.deepEqual(fieldExtractor(issue, 'aString'), |
| 738 | ['test', 'test2']); |
| 739 | }); |
| 740 | }); |
| 741 | |
| 742 | describe('label prefix fields', () => { |
| 743 | beforeEach(() => { |
| 744 | issue = { |
| 745 | localId: 33, |
| 746 | projectName: 'chromium', |
| 747 | labelRefs: [ |
| 748 | {label: 'test-label'}, |
| 749 | {label: 'test-label-2'}, |
| 750 | {label: 'ignore-me'}, |
| 751 | {label: 'Milestone-UI'}, |
| 752 | {label: 'Milestone-Goodies'}, |
| 753 | ], |
| 754 | }; |
| 755 | |
| 756 | fieldExtractor = projectV0.extractFieldValuesFromIssue({ |
| 757 | projectV0: { |
| 758 | name: example.PROJECT_NAME, |
| 759 | configs: { |
| 760 | [example.PROJECT_NAME]: { |
| 761 | projectName: 'chromium', |
| 762 | labelDefs: [ |
| 763 | {label: 'test-1'}, |
| 764 | {label: 'test-2'}, |
| 765 | {label: 'milestone-1'}, |
| 766 | {label: 'milestone-2'}, |
| 767 | ], |
| 768 | }, |
| 769 | }, |
| 770 | }, |
| 771 | }); |
| 772 | }); |
| 773 | |
| 774 | it('gets values for label prefixes', () => { |
| 775 | assert.deepEqual(fieldExtractor(issue, 'test'), ['label', 'label-2']); |
| 776 | assert.deepEqual(fieldExtractor(issue, 'Milestone'), ['UI', 'Goodies']); |
| 777 | }); |
| 778 | }); |
| 779 | }); |
| 780 | |
| 781 | it('fieldDefsByApprovalName', () => { |
| 782 | assert.deepEqual(projectV0.fieldDefsByApprovalName({projectV0: {}}), |
| 783 | new Map()); |
| 784 | |
| 785 | assert.deepEqual(projectV0.fieldDefsByApprovalName({projectV0: { |
| 786 | name: example.PROJECT_NAME, |
| 787 | configs: {[example.PROJECT_NAME]: { |
| 788 | fieldDefs: [ |
| 789 | {fieldRef: {fieldName: 'test', type: fieldTypes.INT_TYPE}}, |
| 790 | {fieldRef: {fieldName: 'ignoreMe', type: fieldTypes.APPROVAL_TYPE}}, |
| 791 | {fieldRef: {fieldName: 'yay', approvalName: 'ThisIsAnApproval'}}, |
| 792 | {fieldRef: {fieldName: 'ImAField', approvalName: 'ThisIsAnApproval'}}, |
| 793 | {fieldRef: {fieldName: 'TalkToALawyer', approvalName: 'Legal'}}, |
| 794 | ], |
| 795 | }}, |
| 796 | }}), new Map([ |
| 797 | ['ThisIsAnApproval', [ |
| 798 | {fieldRef: {fieldName: 'yay', approvalName: 'ThisIsAnApproval'}}, |
| 799 | {fieldRef: {fieldName: 'ImAField', approvalName: 'ThisIsAnApproval'}}, |
| 800 | ]], |
| 801 | ['Legal', [ |
| 802 | {fieldRef: {fieldName: 'TalkToALawyer', approvalName: 'Legal'}}, |
| 803 | ]], |
| 804 | ])); |
| 805 | }); |
| 806 | }); |
| 807 | |
| 808 | let dispatch; |
| 809 | |
| 810 | describe('project action creators', () => { |
| 811 | beforeEach(() => { |
| 812 | sinon.stub(prpcClient, 'call'); |
| 813 | |
| 814 | dispatch = sinon.stub(); |
| 815 | }); |
| 816 | |
| 817 | afterEach(() => { |
| 818 | prpcClient.call.restore(); |
| 819 | }); |
| 820 | |
| 821 | it('select', () => { |
| 822 | projectV0.select('project-name')(dispatch); |
| 823 | const action = {type: projectV0.SELECT, projectName: 'project-name'}; |
| 824 | sinon.assert.calledWith(dispatch, action); |
| 825 | }); |
| 826 | |
| 827 | it('fetchCustomPermissions', async () => { |
| 828 | const action = projectV0.fetchCustomPermissions('chromium'); |
| 829 | |
| 830 | prpcClient.call.returns(Promise.resolve({permissions: ['google']})); |
| 831 | |
| 832 | await action(dispatch); |
| 833 | |
| 834 | sinon.assert.calledWith(dispatch, |
| 835 | {type: projectV0.FETCH_CUSTOM_PERMISSIONS_START}); |
| 836 | |
| 837 | sinon.assert.calledWith( |
| 838 | prpcClient.call, |
| 839 | 'monorail.Projects', |
| 840 | 'GetCustomPermissions', |
| 841 | {projectName: 'chromium'}); |
| 842 | |
| 843 | sinon.assert.calledWith(dispatch, { |
| 844 | type: projectV0.FETCH_CUSTOM_PERMISSIONS_SUCCESS, |
| 845 | projectName: 'chromium', |
| 846 | permissions: ['google'], |
| 847 | }); |
| 848 | }); |
| 849 | |
| 850 | it('fetchPresentationConfig', async () => { |
| 851 | const action = projectV0.fetchPresentationConfig('chromium'); |
| 852 | |
| 853 | prpcClient.call.returns(Promise.resolve({projectThumbnailUrl: 'test'})); |
| 854 | |
| 855 | await action(dispatch); |
| 856 | |
| 857 | sinon.assert.calledWith(dispatch, |
| 858 | {type: projectV0.FETCH_PRESENTATION_CONFIG_START}); |
| 859 | |
| 860 | sinon.assert.calledWith( |
| 861 | prpcClient.call, |
| 862 | 'monorail.Projects', |
| 863 | 'GetPresentationConfig', |
| 864 | {projectName: 'chromium'}); |
| 865 | |
| 866 | sinon.assert.calledWith(dispatch, { |
| 867 | type: projectV0.FETCH_PRESENTATION_CONFIG_SUCCESS, |
| 868 | projectName: 'chromium', |
| 869 | presentationConfig: {projectThumbnailUrl: 'test'}, |
| 870 | }); |
| 871 | }); |
| 872 | |
| 873 | it('fetchVisibleMembers', async () => { |
| 874 | const action = projectV0.fetchVisibleMembers('chromium'); |
| 875 | |
| 876 | prpcClient.call.returns(Promise.resolve({userRefs: [{userId: '123'}]})); |
| 877 | |
| 878 | await action(dispatch); |
| 879 | |
| 880 | sinon.assert.calledWith(dispatch, |
| 881 | {type: projectV0.FETCH_VISIBLE_MEMBERS_START}); |
| 882 | |
| 883 | sinon.assert.calledWith( |
| 884 | prpcClient.call, |
| 885 | 'monorail.Projects', |
| 886 | 'GetVisibleMembers', |
| 887 | {projectName: 'chromium'}); |
| 888 | |
| 889 | sinon.assert.calledWith(dispatch, { |
| 890 | type: projectV0.FETCH_VISIBLE_MEMBERS_SUCCESS, |
| 891 | projectName: 'chromium', |
| 892 | visibleMembers: {userRefs: [{userId: '123'}]}, |
| 893 | }); |
| 894 | }); |
| 895 | }); |
| 896 | |
| 897 | describe('helpers', () => { |
| 898 | beforeEach(() => { |
| 899 | sinon.stub(prpcClient, 'call'); |
| 900 | }); |
| 901 | |
| 902 | afterEach(() => { |
| 903 | prpcClient.call.restore(); |
| 904 | }); |
| 905 | |
| 906 | describe('fetchFieldPerms', () => { |
| 907 | it('fetch field permissions', async () => { |
| 908 | const projectName = 'proj'; |
| 909 | const fieldDefs = [ |
| 910 | { |
| 911 | fieldRef: { |
| 912 | fieldName: 'testField', |
| 913 | fieldId: 1, |
| 914 | type: 'ENUM_TYPE', |
| 915 | }, |
| 916 | }, |
| 917 | ]; |
| 918 | const response = {}; |
| 919 | prpcClient.call.returns(Promise.resolve(response)); |
| 920 | |
| 921 | await store.dispatch(projectV0.fetchFieldPerms(projectName, fieldDefs)); |
| 922 | |
| 923 | const args = {names: ['projects/proj/fieldDefs/1']}; |
| 924 | sinon.assert.calledWith( |
| 925 | prpcClient.call, 'monorail.v3.Permissions', |
| 926 | 'BatchGetPermissionSets', args); |
| 927 | }); |
| 928 | |
| 929 | it('fetch with no fieldDefs', async () => { |
| 930 | const config = {projectName: 'proj'}; |
| 931 | const response = {}; |
| 932 | prpcClient.call.returns(Promise.resolve(response)); |
| 933 | |
| 934 | // fieldDefs will be undefined. |
| 935 | await store.dispatch(projectV0.fetchFieldPerms( |
| 936 | config.projectName, config.fieldDefs)); |
| 937 | |
| 938 | const args = {names: []}; |
| 939 | sinon.assert.calledWith( |
| 940 | prpcClient.call, 'monorail.v3.Permissions', |
| 941 | 'BatchGetPermissionSets', args); |
| 942 | }); |
| 943 | }); |
| 944 | }); |