import { Injectable, Injector } from '@angular/core'; import { GCMockModule } from '@core/mocks/gc-module.mock'; import { ArrayHelpersService, FileService, validateMultiple } from '@yourcause/common'; import { BeforeEach, Spec, TestCase } from '@yourcause/test-decorators'; import { DescribeAngularService } from '@yourcause/test-decorators/angular'; import { expect } from 'chai'; import { CustomDataTablesService, DependentPicklistOptionImport } from '../custom-data-table.service'; import { CustomDataTablesResources } from '../custom-data-tables.resources'; import { CustomDataTable, CustomDataTableExternalContext, CustomDataTableOption, KeyValue, PicklistDataType } from '../custom-data-tables.typing'; @Injectable({ providedIn: 'root' }) @DescribeAngularService(CustomDataTablesService, { imports: [ GCMockModule ], providers: [ { provide: ArrayHelpersService, useValue: new ArrayHelpersService() } ] }) export class DependentPicklistSpec implements Spec { // for setting test values // keyValuesByGuidReturn: KeyValue[]; customDataTableOptionsReturn: CustomDataTableOption[]; customDataTablesListReturn: CustomDataTable[]; externalContextReturn: CustomDataTableExternalContext; templateColumnsForChild = 'key,value,sortOrder,parentKeys,inactive'; templateColumnsForNonChild = 'key,value,sortOrder,inactive'; // mock data // mockId = 1; nullTestPicklist: CustomDataTable = { dataType: PicklistDataType.Text, id: 121, name: 'Null Test Data Table', guid: '5236fd96-d448-4e1e-b8d9-db5df84c2aa1', createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), createdBy: 'System', updatedBy: 'System', isSystem: true, hasOptions: true, defaultLanguageId: 'en-US', parentPicklistId: null }; parentOnlyPicklist: CustomDataTable = { dataType: PicklistDataType.Text, id: 232, name: 'Parent Only Data Table', guid: '6346fd96-d448-4e1e-b8d9-db5df84c2bb2', createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), createdBy: 'System', updatedBy: 'System', isSystem: true, hasOptions: true, defaultLanguageId: 'en-US', parentPicklistId: null }; childOnlyPicklist: CustomDataTable = { dataType: PicklistDataType.Text, id: 343, name: 'Child Only Data Table', guid: '7456fd96-d448-4e1e-b8d9-db5df84c2cc3', createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), createdBy: 'System', updatedBy: 'System', isSystem: true, hasOptions: true, defaultLanguageId: 'en-US', parentPicklistId: 232 }; bothPicklist: CustomDataTable = { dataType: PicklistDataType.Text, id: 454, name: 'Parent and Childd Data Table', guid: '8566fd96-d448-4e1e-b8d9-db5df84c2dd4', createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), createdBy: 'System', updatedBy: 'System', isSystem: true, hasOptions: true, defaultLanguageId: 'en-US', parentPicklistId: 232 }; dependentPicklistTables: CustomDataTable[] = [ this.nullTestPicklist, this.parentOnlyPicklist, this.childOnlyPicklist, this.bothPicklist ]; // parent only means it does not depend on another picklist // child only means no picklist depends on it // both means that it is a parent to one picklist and a child to another // parent only parentOnlyOptionForChildID = 10; parentOnlyOptionForChildKey = 'parentOnlyOptionForChildKey'; parentOnlyOptionForBothID = 11; parentOnlyOptionForBothKey = 'parentOnlyOptionForBothKey'; // child only childOnlyOptionForParentID = 20; childOnlyOptionForParentKey = 'childOnlyOptionForParentKey'; childOnlyOptionForBothID = 21; childOnlyOptionForBothKey = 'childOnlyOptionForBothKey'; // both parent and child bothOptionForParentID = 30; bothOptionForParentKey = 'bothOptionForParentKey'; bothOptionForChildID = 31; bothOptionForChildKey = 'bothOptionForChildKey'; // dependency between parent only and child only picklists parentOnlyAndChildOnly = { parentPicklistId: this.parentOnlyPicklist.id, parentPicklistOptionId: this.parentOnlyOptionForChildID, parentPicklistOptionKey: this.parentOnlyOptionForChildKey, dependentPicklistId: this.childOnlyPicklist.id, dependentPicklistOptionId: this.childOnlyOptionForParentID, dependentPicklistOptionKey: this.childOnlyOptionForParentKey }; // dependency between both and child only picklists childOnlyAndBoth = { parentPicklistId: this.bothPicklist.id, parentPicklistOptionId: this.bothOptionForChildID, parentPicklistOptionKey: this.bothOptionForChildKey, dependentPicklistId: this.childOnlyPicklist.id, dependentPicklistOptionId: this.childOnlyOptionForBothID, dependentPicklistOptionKey: this.childOnlyOptionForBothKey }; // dependency between parent only and both picklists parentOnlyAndBoth = { parentPicklistId: this.parentOnlyPicklist.id, parentPicklistOptionId: this.parentOnlyOptionForBothID, parentPicklistOptionKey: this.parentOnlyOptionForBothKey, dependentPicklistId: this.bothPicklist.id, dependentPicklistOptionId: this.bothOptionForParentID, dependentPicklistOptionKey: this.bothOptionForParentKey }; parentOnlyOptions: CustomDataTableOption[] = [{ id: this.parentOnlyOptionForChildID, key: this.parentOnlyOptionForChildKey, values: [ { languageId: 'en-US', text: 'a' } ], sortOrder: 1, inUse: true, createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), picklistOptionDependentPicklists: [ this.parentOnlyAndChildOnly ] }, { id: this.parentOnlyOptionForBothID, key: this.parentOnlyOptionForBothKey, sortOrder: 2, values: [ { languageId: 'en-US', text: 'b' } ], inUse: true, createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), picklistOptionDependentPicklists: [ this.parentOnlyAndBoth ] }]; childOnlyOptions: CustomDataTableOption[] = [{ id: this.childOnlyOptionForParentID, key: this.childOnlyOptionForParentKey, sortOrder: 3, values: [ { languageId: 'en-US', text: 'c' } ], inUse: true, createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), picklistOptionDependentPicklists: [ this.parentOnlyAndChildOnly ] }, { id: this.childOnlyOptionForBothID, key: this.childOnlyOptionForBothKey, sortOrder: 4, values: [ { languageId: 'en-US', text: 'd' } ], inUse: true, createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), picklistOptionDependentPicklists: [ this.childOnlyAndBoth ] }]; bothOptions: CustomDataTableOption[] = [{ id: this.bothOptionForParentID, key: this.bothOptionForParentKey, sortOrder: 1, values: [ { languageId: 'en-US', text: 'e' } ], inUse: true, createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), picklistOptionDependentPicklists: [ this.parentOnlyAndBoth ] }, { id: this.childOnlyOptionForBothID, key: this.childOnlyOptionForBothKey, sortOrder: 2, values: [ { languageId: 'en-US', text: 'f' } ], inUse: true, createdDate: new Date().toISOString(), updatedDate: new Date().toISOString(), picklistOptionDependentPicklists: [ this.childOnlyAndBoth ]}]; externalContextForNoParentPicklist: CustomDataTableExternalContext = { dynamicType: null, requiresParentListValidation: false, parentListKeys: null, picklistId: null }; externalContextForUsingParentPicklist: CustomDataTableExternalContext = { dynamicType: null, requiresParentListValidation: true, parentListKeys: null, picklistId: null }; // end mock data // constructor ( private injector: Injector, private customDataTablesResources: CustomDataTablesResources, private fileService: FileService ) { } @BeforeEach() mock (service: CustomDataTablesService) { service['customDataTableResources'] = this.customDataTablesResources; service['fileService'] = this.fileService; this.customDataTablesResources.getKeyValuesByGuid = async () => { return this.keyValuesByGuidReturn; }; this.customDataTablesResources.getCustomDataTableOptions = async () => { return this.customDataTableOptionsReturn; }; this.customDataTablesResources.getCustomTableDataList = async () => { return this.customDataTablesListReturn; }; this.fileService.downloadString = async (input: string) => { return input; }; } @TestCase('should return external context for CDT Validator') async testReturnExternalContextForCDTValidator (service: CustomDataTablesService) { const parentListKeys = this.parentOnlyOptions.map((option, index) => { return { key: option.key, value: option.values[0].text, parentKeys: [], inUse: true, sortOrder: index + 1, parentKeysString: '', picklistGuid: this.parentOnlyPicklist.guid }; }); // set up so parent cdt options exist on map // these are used to validate dependent picklist options this.customDataTablesListReturn = this.dependentPicklistTables; this.customDataTableOptionsReturn = this.parentOnlyOptions; this.keyValuesByGuidReturn = parentListKeys; service.setCustomDataTableOptionsMap( this.parentOnlyPicklist.guid, parentListKeys ); this.externalContextReturn = await service.returnExternalContextForCDTValidator( this.parentOnlyPicklist.guid, this.parentOnlyPicklist.defaultLanguageId, true ); expect(this.externalContextReturn.requiresParentListValidation).to.equal(true); const parentKeyValues = parentListKeys.map((plk) => plk.key); expect(this.externalContextReturn.parentListKeys).to.have.members([...parentKeyValues]); } @TestCase('should be able to tell if a parent option exists') async testDoesParentOptionExist (service: CustomDataTablesService) { expect(service).to.exist; // test confirming existing parent option this.customDataTablesListReturn = this.dependentPicklistTables; this.customDataTableOptionsReturn = this.childOnlyOptions; await service.setCustomDataTableOptions(this.mockId); const childOnlyOptions = service.customDataTableMap[this.mockId]; const parentOptionExistsOnEvery = childOnlyOptions .every((option) => option.picklistOptionDependentPicklists .some((dependency) => dependency.dependentPicklistOptionId === option.id)); expect(parentOptionExistsOnEvery).to.be.true; } @TestCase('should correctly include parentKey column in csv template export') async testIncludingParentKeyColumnInCSVTempkateExport (service: CustomDataTablesService) { let includeParentKeysColumn = true; const childColumnsList = await service.getTemplateForDownload( null, includeParentKeysColumn ); expect(childColumnsList).to.equal(this.templateColumnsForChild); includeParentKeysColumn = false; const nonChildColumnsList = await service.getTemplateForDownload( null, includeParentKeysColumn ); expect(nonChildColumnsList).to.equal(this.templateColumnsForNonChild); } @TestCase('should correctly validate picklist imports') async testShouldValidate () { const invalidEntries: DependentPicklistOptionImport[] = [{ key: 'a', parentKeys: '1', sortOrder: 1, value: 'a', inactive: false }]; const validEntries: DependentPicklistOptionImport[] = [{ key: 'valid', parentKeys: '2', sortOrder: 2, value: 'valid', inactive: false }]; // defaulting to invalid scenario, parentKeys do not exist in parentListKeys let externalContext: CustomDataTableExternalContext = { dynamicType: null, requiresParentListValidation: true, parentListKeys: ['2'], picklistId: null }; const errorsForInvalid = await validateMultiple(DependentPicklistOptionImport, invalidEntries, this.injector, externalContext); expect(errorsForInvalid.recordLevelErrors[0].parentKeys.length).to.be.greaterThan(0); // test valid scenario, parentKeys exist in parentListKeys externalContext = { dynamicType: null, requiresParentListValidation: true, parentListKeys: ['2'], picklistId: null }; const errorsForValid = await validateMultiple(DependentPicklistOptionImport, validEntries, this.injector, externalContext); expect(errorsForValid.recordLevelErrors[0].parentKeys.length).to.be.equal(0); } @TestCase('should get parent picklist from parent picklist id') async testGetParentPicklistFromParentPicklistId (service: CustomDataTablesService) { this.customDataTablesListReturn = [ this.childOnlyPicklist, this.parentOnlyPicklist, this.bothPicklist ]; await service.setCustomDataTables(); this.customDataTableOptionsReturn = this.childOnlyOptions; const parentPicklistForChildOnly = await service.prepParentCDTData(this.childOnlyPicklist.id, this.childOnlyPicklist.parentPicklistId); expect(parentPicklistForChildOnly).to.equal(this.parentOnlyPicklist); this.customDataTableOptionsReturn = this.parentOnlyOptions; const parentPicklistForParentOnly = await service.prepParentCDTData(this.parentOnlyPicklist.id, this.parentOnlyPicklist.parentPicklistId); expect(parentPicklistForParentOnly).to.be.undefined; this.customDataTableOptionsReturn = this.bothOptions; const parentPicklistForBoth = await service.prepParentCDTData(this.bothPicklist.id, this.bothPicklist.parentPicklistId); expect(parentPicklistForBoth).to.equal(this.parentOnlyPicklist); } }