mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-05 18:58:34 -05:00
Feature: Implement custom fields for documents (#4502)
Adds custom fields of certain data types, attachable to documents and searchable Co-Authored-By: Trenton H <797416+stumpylog@users.noreply.github.com>
This commit is contained in:
@@ -67,6 +67,9 @@ import { PageHeaderComponent } from '../common/page-header/page-header.component
|
||||
import { DocumentNotesComponent } from '../document-notes/document-notes.component'
|
||||
import { DocumentDetailComponent } from './document-detail.component'
|
||||
import { ShareLinksDropdownComponent } from '../common/share-links-dropdown/share-links-dropdown.component'
|
||||
import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
|
||||
import { PaperlessCustomFieldDataType } from 'src/app/data/paperless-custom-field'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
|
||||
const doc: PaperlessDocument = {
|
||||
id: 3,
|
||||
@@ -94,8 +97,31 @@ const doc: PaperlessDocument = {
|
||||
user: 2,
|
||||
},
|
||||
],
|
||||
custom_fields: [
|
||||
{
|
||||
field: 0,
|
||||
document: 3,
|
||||
created: new Date(),
|
||||
value: 'custom foo bar',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const customFields = [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Field 1',
|
||||
data_type: PaperlessCustomFieldDataType.String,
|
||||
created: new Date(),
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Custom Field 2',
|
||||
data_type: PaperlessCustomFieldDataType.Integer,
|
||||
created: new Date(),
|
||||
},
|
||||
]
|
||||
|
||||
describe('DocumentDetailComponent', () => {
|
||||
let component: DocumentDetailComponent
|
||||
let fixture: ComponentFixture<DocumentDetailComponent>
|
||||
@@ -107,6 +133,7 @@ describe('DocumentDetailComponent', () => {
|
||||
let toastService: ToastService
|
||||
let documentListViewService: DocumentListViewService
|
||||
let settingsService: SettingsService
|
||||
let customFieldsService: CustomFieldsService
|
||||
|
||||
let currentUserCan = true
|
||||
let currentUserHasObjectPermissions = true
|
||||
@@ -136,6 +163,7 @@ describe('DocumentDetailComponent', () => {
|
||||
PdfViewerComponent,
|
||||
SafeUrlPipe,
|
||||
ShareLinksDropdownComponent,
|
||||
CustomFieldsDropdownComponent,
|
||||
],
|
||||
providers: [
|
||||
DocumentTitlePipe,
|
||||
@@ -199,6 +227,7 @@ describe('DocumentDetailComponent', () => {
|
||||
}),
|
||||
},
|
||||
},
|
||||
CustomFieldsService,
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
@@ -234,6 +263,7 @@ describe('DocumentDetailComponent', () => {
|
||||
toastService = TestBed.inject(ToastService)
|
||||
documentListViewService = TestBed.inject(DocumentListViewService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
customFieldsService = TestBed.inject(CustomFieldsService)
|
||||
fixture = TestBed.createComponent(DocumentDetailComponent)
|
||||
component = fixture.componentInstance
|
||||
})
|
||||
@@ -290,6 +320,13 @@ describe('DocumentDetailComponent', () => {
|
||||
it('should load already-opened document via param', () => {
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(doc)
|
||||
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
count: customFields.length,
|
||||
all: customFields.map((f) => f.id),
|
||||
results: customFields,
|
||||
})
|
||||
)
|
||||
fixture.detectChanges() // calls ngOnInit
|
||||
expect(component.document).toEqual(doc)
|
||||
})
|
||||
@@ -797,12 +834,92 @@ describe('DocumentDetailComponent', () => {
|
||||
expect(toastSpy).toHaveBeenCalledWith('Error retrieving metadata', error)
|
||||
})
|
||||
|
||||
it('should display custom fields', () => {
|
||||
initNormally()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
customFields[0].name
|
||||
)
|
||||
})
|
||||
|
||||
it('should support add custom field, correctly send via post', () => {
|
||||
initNormally()
|
||||
const initialLength = doc.custom_fields.length
|
||||
expect(component.customFieldFormFields).toHaveLength(initialLength)
|
||||
component.addField(customFields[1])
|
||||
fixture.detectChanges()
|
||||
expect(component.document.custom_fields).toHaveLength(initialLength + 1)
|
||||
expect(component.customFieldFormFields).toHaveLength(initialLength + 1)
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
customFields[1].name
|
||||
)
|
||||
const updateSpy = jest.spyOn(documentService, 'update')
|
||||
component.save(true)
|
||||
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(2)
|
||||
expect(updateSpy.mock.lastCall[0].custom_fields[1]).toEqual({
|
||||
field: customFields[1].id,
|
||||
value: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('should support remove custom field, correctly send via post', () => {
|
||||
initNormally()
|
||||
const initialLength = doc.custom_fields.length
|
||||
expect(component.customFieldFormFields).toHaveLength(initialLength)
|
||||
component.removeField(doc.custom_fields[0])
|
||||
fixture.detectChanges()
|
||||
expect(component.document.custom_fields).toHaveLength(initialLength - 1)
|
||||
expect(component.customFieldFormFields).toHaveLength(initialLength - 1)
|
||||
expect(fixture.debugElement.nativeElement.textContent).not.toContain(
|
||||
'Field 1'
|
||||
)
|
||||
const updateSpy = jest.spyOn(documentService, 'update')
|
||||
component.save(true)
|
||||
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(
|
||||
initialLength - 1
|
||||
)
|
||||
})
|
||||
|
||||
it('should show custom field errors', () => {
|
||||
initNormally()
|
||||
component.error = {
|
||||
custom_fields: [
|
||||
{},
|
||||
{},
|
||||
{ value: ['This field may not be null.'] },
|
||||
{},
|
||||
{ non_field_errors: ['Enter a valid URL.'] },
|
||||
],
|
||||
}
|
||||
expect(component.getCustomFieldError(2)).toEqual([
|
||||
'This field may not be null.',
|
||||
])
|
||||
expect(component.getCustomFieldError(4)).toEqual(['Enter a valid URL.'])
|
||||
})
|
||||
|
||||
it('should refresh custom fields when created', () => {
|
||||
initNormally()
|
||||
const refreshSpy = jest.spyOn(component, 'refreshCustomFields')
|
||||
fixture.debugElement
|
||||
.query(By.directive(CustomFieldsDropdownComponent))
|
||||
.triggerEventHandler('created')
|
||||
expect(refreshSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
function initNormally() {
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||
jest
|
||||
.spyOn(documentService, 'get')
|
||||
.mockReturnValueOnce(of(Object.assign({}, doc)))
|
||||
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(null)
|
||||
jest
|
||||
.spyOn(openDocumentsService, 'openDocument')
|
||||
.mockReturnValueOnce(of(true))
|
||||
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
count: customFields.length,
|
||||
all: customFields.map((f) => f.id),
|
||||
results: customFields,
|
||||
})
|
||||
)
|
||||
fixture.detectChanges()
|
||||
}
|
||||
})
|
||||
|
Reference in New Issue
Block a user