mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Some more frontend coverage
This commit is contained in:
		| @@ -11,7 +11,9 @@ import { | ||||
| import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgSelectModule } from '@ng-select/ng-select' | ||||
| import { of } from 'rxjs' | ||||
| import { CustomFieldQueriesModel } from 'src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component' | ||||
| import { CustomFieldDataType } from 'src/app/data/custom-field' | ||||
| import { CustomFieldQueryLogicalOperator } from 'src/app/data/custom-field-query' | ||||
| import { | ||||
|   MATCHING_ALGORITHMS, | ||||
|   MATCH_AUTO, | ||||
| @@ -35,6 +37,7 @@ import { DocumentTypeService } from 'src/app/services/rest/document-type.service | ||||
| import { MailRuleService } from 'src/app/services/rest/mail-rule.service' | ||||
| import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { CustomFieldQueryExpression } from 'src/app/utils/custom-field-query-element' | ||||
| import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component' | ||||
| import { NumberComponent } from '../../input/number/number.component' | ||||
| import { PermissionsGroupComponent } from '../../input/permissions/permissions-group/permissions-group.component' | ||||
| @@ -466,6 +469,49 @@ describe('WorkflowEditDialogComponent', () => { | ||||
|     expect(formValues.triggers[0].conditions).toBeUndefined() | ||||
|   }) | ||||
|  | ||||
|   it('should ignore empty and null condition values when mapping filters', () => { | ||||
|     component.object = undefined | ||||
|     component.addTrigger() | ||||
|     const triggerGroup = component.triggerFields.at(0) as FormGroup | ||||
|  | ||||
|     const tagsCondition = component.addCondition(triggerGroup) | ||||
|     tagsCondition.get('type').setValue(TriggerConditionType.TagsAny) | ||||
|     tagsCondition.get('values').setValue([]) | ||||
|  | ||||
|     const correspondentCondition = component.addCondition(triggerGroup) | ||||
|     correspondentCondition | ||||
|       .get('type') | ||||
|       .setValue(TriggerConditionType.CorrespondentIs) | ||||
|     correspondentCondition.get('values').setValue(null) | ||||
|  | ||||
|     const formValues = component['getFormValues']() | ||||
|  | ||||
|     expect(formValues.triggers[0].filter_has_tags).toEqual([]) | ||||
|     expect(formValues.triggers[0].filter_has_correspondent).toBeNull() | ||||
|   }) | ||||
|  | ||||
|   it('should derive single select filters from array values', () => { | ||||
|     component.object = undefined | ||||
|     component.addTrigger() | ||||
|     const triggerGroup = component.triggerFields.at(0) as FormGroup | ||||
|  | ||||
|     const addConditionOfType = (type: TriggerConditionType, value: any) => { | ||||
|       const condition = component.addCondition(triggerGroup) | ||||
|       condition.get('type').setValue(type) | ||||
|       condition.get('values').setValue(value) | ||||
|     } | ||||
|  | ||||
|     addConditionOfType(TriggerConditionType.CorrespondentIs, [5]) | ||||
|     addConditionOfType(TriggerConditionType.DocumentTypeIs, [6]) | ||||
|     addConditionOfType(TriggerConditionType.StoragePathIs, [7]) | ||||
|  | ||||
|     const formValues = component['getFormValues']() | ||||
|  | ||||
|     expect(formValues.triggers[0].filter_has_correspondent).toEqual(5) | ||||
|     expect(formValues.triggers[0].filter_has_document_type).toEqual(6) | ||||
|     expect(formValues.triggers[0].filter_has_storage_path).toEqual(7) | ||||
|   }) | ||||
|  | ||||
|   it('should reuse cached condition type options and update disabled state', () => { | ||||
|     component.object = undefined | ||||
|     component.addTrigger() | ||||
| @@ -500,6 +546,111 @@ describe('WorkflowEditDialogComponent', () => { | ||||
|     expect(correspondentOptionAfter.disabled).toBe(false) | ||||
|   }) | ||||
|  | ||||
|   it('should keep multi-entry condition options enabled and allow duplicates', () => { | ||||
|     component.object = undefined | ||||
|     component.addTrigger() | ||||
|     const triggerGroup = component.triggerFields.at(0) as FormGroup | ||||
|  | ||||
|     component.conditionDefinitions = [ | ||||
|       { | ||||
|         id: TriggerConditionType.TagsAny, | ||||
|         name: 'Any tags', | ||||
|         inputType: 'tags', | ||||
|         allowMultipleEntries: true, | ||||
|         allowMultipleValues: true, | ||||
|       } as any, | ||||
|       { | ||||
|         id: TriggerConditionType.CorrespondentIs, | ||||
|         name: 'Correspondent is', | ||||
|         inputType: 'select', | ||||
|         allowMultipleEntries: false, | ||||
|         allowMultipleValues: false, | ||||
|         selectItems: 'correspondents', | ||||
|       } as any, | ||||
|     ] | ||||
|  | ||||
|     const firstCondition = component.addCondition(triggerGroup) | ||||
|     firstCondition.get('type').setValue(TriggerConditionType.TagsAny) | ||||
|  | ||||
|     const secondCondition = component.addCondition(triggerGroup) | ||||
|     expect(secondCondition).not.toBeNull() | ||||
|  | ||||
|     const options = component.getConditionTypeOptions(triggerGroup, 1) | ||||
|     const multiEntryOption = options.find( | ||||
|       (option) => option.id === TriggerConditionType.TagsAny | ||||
|     ) | ||||
|  | ||||
|     expect(multiEntryOption.disabled).toBe(false) | ||||
|     expect(component.canAddCondition(triggerGroup)).toBe(true) | ||||
|   }) | ||||
|  | ||||
|   it('should return null when no condition definitions remain available', () => { | ||||
|     component.object = undefined | ||||
|     component.addTrigger() | ||||
|     const triggerGroup = component.triggerFields.at(0) as FormGroup | ||||
|  | ||||
|     component.conditionDefinitions = [ | ||||
|       { | ||||
|         id: TriggerConditionType.TagsAny, | ||||
|         name: 'Any tags', | ||||
|         inputType: 'tags', | ||||
|         allowMultipleEntries: false, | ||||
|         allowMultipleValues: true, | ||||
|       } as any, | ||||
|       { | ||||
|         id: TriggerConditionType.CorrespondentIs, | ||||
|         name: 'Correspondent is', | ||||
|         inputType: 'select', | ||||
|         allowMultipleEntries: false, | ||||
|         allowMultipleValues: false, | ||||
|         selectItems: 'correspondents', | ||||
|       } as any, | ||||
|     ] | ||||
|  | ||||
|     const firstCondition = component.addCondition(triggerGroup) | ||||
|     firstCondition.get('type').setValue(TriggerConditionType.TagsAny) | ||||
|     const secondCondition = component.addCondition(triggerGroup) | ||||
|     secondCondition.get('type').setValue(TriggerConditionType.CorrespondentIs) | ||||
|  | ||||
|     expect(component.canAddCondition(triggerGroup)).toBe(false) | ||||
|     expect(component.addCondition(triggerGroup)).toBeNull() | ||||
|   }) | ||||
|  | ||||
|   it('should return null when adding condition for unknown trigger form group', () => { | ||||
|     expect(component.addCondition(new FormGroup({}) as any)).toBeNull() | ||||
|   }) | ||||
|  | ||||
|   it('should ignore remove condition calls for unknown trigger form group', () => { | ||||
|     expect(() => | ||||
|       component.removeCondition(new FormGroup({}) as any, 0) | ||||
|     ).not.toThrow() | ||||
|   }) | ||||
|  | ||||
|   it('should teardown custom field query model when removing a custom field condition', () => { | ||||
|     component.object = undefined | ||||
|     component.addTrigger() | ||||
|     const triggerGroup = component.triggerFields.at(0) as FormGroup | ||||
|  | ||||
|     component.addCondition(triggerGroup) | ||||
|     const conditions = component.getConditionsFormArray(triggerGroup) | ||||
|     const conditionGroup = conditions.at(0) as FormGroup | ||||
|     conditionGroup.get('type').setValue(TriggerConditionType.CustomFieldQuery) | ||||
|  | ||||
|     const model = component.getCustomFieldQueryModel(conditionGroup) | ||||
|     expect(model).toBeDefined() | ||||
|     expect(component['customFieldQueryModels'].has(conditionGroup)).toBe(true) | ||||
|  | ||||
|     component.removeCondition(triggerGroup, 0) | ||||
|     expect(component['customFieldQueryModels'].has(conditionGroup)).toBe(false) | ||||
|   }) | ||||
|  | ||||
|   it('should return readable condition names', () => { | ||||
|     expect(component.getConditionName(TriggerConditionType.TagsAny)).toBe( | ||||
|       'Has any of these tags' | ||||
|     ) | ||||
|     expect(component.getConditionName(999 as any)).toBe('') | ||||
|   }) | ||||
|  | ||||
|   it('should build condition form array from existing trigger filters', () => { | ||||
|     const trigger = workflow.triggers[0] | ||||
|     trigger.filter_has_tags = [1] | ||||
| @@ -561,6 +712,100 @@ describe('WorkflowEditDialogComponent', () => { | ||||
|     ).toBe(true) | ||||
|   }) | ||||
|  | ||||
|   it('should handle custom field query selection change and validation states', () => { | ||||
|     const formGroup = new FormGroup({ | ||||
|       values: new FormControl(null), | ||||
|     }) | ||||
|     const model = new CustomFieldQueriesModel() | ||||
|  | ||||
|     const changeSpy = jest.spyOn( | ||||
|       component as any, | ||||
|       'onCustomFieldQueryModelChanged' | ||||
|     ) | ||||
|  | ||||
|     component.onCustomFieldQuerySelectionChange(formGroup, model) | ||||
|     expect(changeSpy).toHaveBeenCalledWith(formGroup, model) | ||||
|  | ||||
|     const map = component['customFieldQueryModels'] | ||||
|  | ||||
|     expect(component.isCustomFieldQueryValid(formGroup)).toBe(true) | ||||
|     map.set(formGroup, model) | ||||
|  | ||||
|     const validSpy = jest.spyOn(model, 'isValid').mockReturnValue(false) | ||||
|     const emptySpy = jest.spyOn(model, 'isEmpty').mockReturnValue(false) | ||||
|     expect(component.isCustomFieldQueryValid(formGroup)).toBe(false) | ||||
|     expect(validSpy).toHaveBeenCalled() | ||||
|  | ||||
|     validSpy.mockReturnValue(true) | ||||
|     emptySpy.mockReturnValue(true) | ||||
|     expect(component.isCustomFieldQueryValid(formGroup)).toBe(true) | ||||
|  | ||||
|     emptySpy.mockReturnValue(false) | ||||
|     expect(component.isCustomFieldQueryValid(formGroup)).toBe(true) | ||||
|  | ||||
|     map.delete(formGroup) | ||||
|   }) | ||||
|  | ||||
|   it('should recover from invalid custom field query json and update control on changes', () => { | ||||
|     const conditionGroup = new FormGroup({ | ||||
|       values: new FormControl('not-json'), | ||||
|     }) | ||||
|  | ||||
|     component['ensureCustomFieldQueryModel'](conditionGroup, 'not-json') | ||||
|  | ||||
|     const model = component['customFieldQueryModels'].get(conditionGroup) | ||||
|     expect(model).toBeDefined() | ||||
|     expect(model.queries.length).toBeGreaterThan(0) | ||||
|  | ||||
|     const valuesControl = conditionGroup.get('values') | ||||
|     expect(valuesControl.value).toBeNull() | ||||
|  | ||||
|     const expression = new CustomFieldQueryExpression([ | ||||
|       CustomFieldQueryLogicalOperator.And, | ||||
|       [[1, 'exact', 'value']], | ||||
|     ]) | ||||
|     model.queries = [expression] | ||||
|  | ||||
|     jest.spyOn(model, 'isValid').mockReturnValue(true) | ||||
|     jest.spyOn(model, 'isEmpty').mockReturnValue(false) | ||||
|  | ||||
|     model.changed.next(model) | ||||
|  | ||||
|     expect(valuesControl.value).toEqual(JSON.stringify(expression.serialize())) | ||||
|  | ||||
|     component['teardownCustomFieldQueryModel'](conditionGroup) | ||||
|   }) | ||||
|  | ||||
|   it('should handle custom field query model change edge cases', () => { | ||||
|     const groupWithoutControl = new FormGroup({}) | ||||
|     const dummyModel = { | ||||
|       isValid: jest.fn().mockReturnValue(true), | ||||
|       isEmpty: jest.fn().mockReturnValue(false), | ||||
|     } | ||||
|  | ||||
|     expect(() => | ||||
|       component['onCustomFieldQueryModelChanged']( | ||||
|         groupWithoutControl as any, | ||||
|         dummyModel as any | ||||
|       ) | ||||
|     ).not.toThrow() | ||||
|  | ||||
|     const groupWithControl = new FormGroup({ | ||||
|       values: new FormControl('initial'), | ||||
|     }) | ||||
|     const emptyModel = { | ||||
|       isValid: jest.fn().mockReturnValue(true), | ||||
|       isEmpty: jest.fn().mockReturnValue(true), | ||||
|     } | ||||
|  | ||||
|     component['onCustomFieldQueryModelChanged']( | ||||
|       groupWithControl as any, | ||||
|       emptyModel as any | ||||
|     ) | ||||
|  | ||||
|     expect(groupWithControl.get('values').value).toBeNull() | ||||
|   }) | ||||
|  | ||||
|   it('should normalize condition values for single and multi selects', () => { | ||||
|     expect( | ||||
|       component['normalizeConditionValue'](TriggerConditionType.TagsAny) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon