mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-09 09:58:20 -05:00
Feature: separate save / save & close buttons (#3575)
* Add setting to decide whether the edit dialog should automatically close on save * Add the actual button to the ui * Revert "Add the actual button to the ui" This reverts commit e1f5a8bde0e8a2f6bafa2896d6a7e57e70d0c670. * Revert "Add setting to decide whether the edit dialog should automatically close on save" This reverts commit feef3c909b055b4bc4d2884324a4678348e1e536. * Add button for save without exit * Correct save button ordering, ensure perms, update translation strings * fix e2e tests * Add unit testing for save / save & close button --------- Update messages.xlf Update document-detail.component.spec.ts Co-Authored-By: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
parent
4c4b571a88
commit
4693632c7d
@ -12,9 +12,13 @@ test('should activate / deactivate save button when changes are saved', async ({
|
|||||||
await expect(page.getByTitle('Storage path', { exact: true })).toHaveText(
|
await expect(page.getByTitle('Storage path', { exact: true })).toHaveText(
|
||||||
/\w+/
|
/\w+/
|
||||||
)
|
)
|
||||||
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Save', exact: true })
|
||||||
|
).toBeDisabled()
|
||||||
await page.getByTitle('Storage path').getByTitle('Clear all').click()
|
await page.getByTitle('Storage path').getByTitle('Clear all').click()
|
||||||
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled()
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Save', exact: true })
|
||||||
|
).toBeEnabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should warn on unsaved changes', async ({ page }) => {
|
test('should warn on unsaved changes', async ({ page }) => {
|
||||||
@ -23,13 +27,17 @@ test('should warn on unsaved changes', async ({ page }) => {
|
|||||||
await expect(page.getByTitle('Correspondent', { exact: true })).toHaveText(
|
await expect(page.getByTitle('Correspondent', { exact: true })).toHaveText(
|
||||||
/\w+/
|
/\w+/
|
||||||
)
|
)
|
||||||
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Save', exact: true })
|
||||||
|
).toBeDisabled()
|
||||||
await page
|
await page
|
||||||
.getByTitle('Storage path', { exact: true })
|
.getByTitle('Storage path', { exact: true })
|
||||||
.getByTitle('Clear all')
|
.getByTitle('Clear all')
|
||||||
.click()
|
.click()
|
||||||
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled()
|
await expect(
|
||||||
await page.getByRole('button', { name: 'Close' }).click()
|
page.getByRole('button', { name: 'Save', exact: true })
|
||||||
|
).toBeEnabled()
|
||||||
|
await page.getByRole('button', { name: 'Close', exact: true }).click()
|
||||||
await expect(page.getByRole('dialog')).toHaveText(/unsaved changes/)
|
await expect(page.getByRole('dialog')).toHaveText(/unsaved changes/)
|
||||||
await page.getByRole('button', { name: 'Cancel' }).click()
|
await page.getByRole('button', { name: 'Cancel' }).click()
|
||||||
await page.getByRole('link', { name: 'Close all' }).click()
|
await page.getByRole('link', { name: 'Close all' }).click()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -191,9 +191,12 @@
|
|||||||
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||||
|
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
<button type="button" class="btn btn-outline-secondary me-2" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
||||||
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & next</button>
|
<ng-container *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||||
<button type="submit" class="btn btn-primary" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
|
<button *ngIf="hasNext()" type="button" class="btn btn-outline-primary me-2" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & next</button>
|
||||||
|
<button *ngIf="!hasNext()" type="button" class="btn btn-outline-primary me-2" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & close</button>
|
||||||
|
<button type="submit" class="btn btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -372,12 +372,25 @@ describe('DocumentDetailComponent', () => {
|
|||||||
const updateSpy = jest.spyOn(documentService, 'update')
|
const updateSpy = jest.spyOn(documentService, 'update')
|
||||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
updateSpy.mockImplementation((o) => of(doc))
|
updateSpy.mockImplementation((o) => of(doc))
|
||||||
component.save()
|
component.save(true)
|
||||||
expect(updateSpy).toHaveBeenCalled()
|
expect(updateSpy).toHaveBeenCalled()
|
||||||
expect(closeSpy).toHaveBeenCalled()
|
expect(closeSpy).toHaveBeenCalled()
|
||||||
expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.')
|
expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support save without close and show success toast', () => {
|
||||||
|
initNormally()
|
||||||
|
component.title = 'Foo Bar'
|
||||||
|
const closeSpy = jest.spyOn(component, 'close')
|
||||||
|
const updateSpy = jest.spyOn(documentService, 'update')
|
||||||
|
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
|
updateSpy.mockImplementation((o) => of(doc))
|
||||||
|
component.save()
|
||||||
|
expect(updateSpy).toHaveBeenCalled()
|
||||||
|
expect(closeSpy).not.toHaveBeenCalled()
|
||||||
|
expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.')
|
||||||
|
})
|
||||||
|
|
||||||
it('should show toast error on save if error occurs', () => {
|
it('should show toast error on save if error occurs', () => {
|
||||||
currentUserHasObjectPermissions = true
|
currentUserHasObjectPermissions = true
|
||||||
initNormally()
|
initNormally()
|
||||||
@ -406,7 +419,7 @@ describe('DocumentDetailComponent', () => {
|
|||||||
updateSpy.mockImplementation(() =>
|
updateSpy.mockImplementation(() =>
|
||||||
throwError(() => new Error('failed to save'))
|
throwError(() => new Error('failed to save'))
|
||||||
)
|
)
|
||||||
component.save()
|
component.save(true)
|
||||||
expect(updateSpy).toHaveBeenCalled()
|
expect(updateSpy).toHaveBeenCalled()
|
||||||
expect(closeSpy).toHaveBeenCalled()
|
expect(closeSpy).toHaveBeenCalled()
|
||||||
expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.')
|
expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.')
|
||||||
@ -430,7 +443,7 @@ describe('DocumentDetailComponent', () => {
|
|||||||
expect
|
expect
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show toast error on saveAll if error occurs', () => {
|
it('should show toast error on save & next if error occurs', () => {
|
||||||
currentUserHasObjectPermissions = true
|
currentUserHasObjectPermissions = true
|
||||||
initNormally()
|
initNormally()
|
||||||
component.title = 'Foo Bar'
|
component.title = 'Foo Bar'
|
||||||
@ -448,6 +461,39 @@ describe('DocumentDetailComponent', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should show save button and save & close or save & next', () => {
|
||||||
|
const nextSpy = jest.spyOn(component, 'hasNext')
|
||||||
|
nextSpy.mockReturnValueOnce(false)
|
||||||
|
fixture.detectChanges()
|
||||||
|
expect(
|
||||||
|
fixture.debugElement
|
||||||
|
.queryAll(By.css('button'))
|
||||||
|
.find((b) => b.nativeElement.textContent === 'Save')
|
||||||
|
).not.toBeUndefined()
|
||||||
|
expect(
|
||||||
|
fixture.debugElement
|
||||||
|
.queryAll(By.css('button'))
|
||||||
|
.find((b) => b.nativeElement.textContent === 'Save & close')
|
||||||
|
).not.toBeUndefined()
|
||||||
|
expect(
|
||||||
|
fixture.debugElement
|
||||||
|
.queryAll(By.css('button'))
|
||||||
|
.find((b) => b.nativeElement.textContent === 'Save & next')
|
||||||
|
).toBeUndefined()
|
||||||
|
nextSpy.mockReturnValue(true)
|
||||||
|
fixture.detectChanges()
|
||||||
|
expect(
|
||||||
|
fixture.debugElement
|
||||||
|
.queryAll(By.css('button'))
|
||||||
|
.find((b) => b.nativeElement.textContent === 'Save & close')
|
||||||
|
).toBeUndefined()
|
||||||
|
expect(
|
||||||
|
fixture.debugElement
|
||||||
|
.queryAll(By.css('button'))
|
||||||
|
.find((b) => b.nativeElement.textContent === 'Save & next')
|
||||||
|
).not.toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('should allow close and navigate to documents by default', () => {
|
it('should allow close and navigate to documents by default', () => {
|
||||||
initNormally()
|
initNormally()
|
||||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||||
|
@ -518,7 +518,7 @@ export class DocumentDetailComponent
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save(close: boolean = false) {
|
||||||
this.networkActive = true
|
this.networkActive = true
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.update(this.document)
|
.update(this.document)
|
||||||
@ -527,7 +527,7 @@ export class DocumentDetailComponent
|
|||||||
next: () => {
|
next: () => {
|
||||||
this.store.next(this.documentForm.value)
|
this.store.next(this.documentForm.value)
|
||||||
this.toastService.showInfo($localize`Document saved successfully.`)
|
this.toastService.showInfo($localize`Document saved successfully.`)
|
||||||
this.close()
|
close && this.close()
|
||||||
this.networkActive = false
|
this.networkActive = false
|
||||||
this.error = null
|
this.error = null
|
||||||
},
|
},
|
||||||
@ -535,7 +535,7 @@ export class DocumentDetailComponent
|
|||||||
this.networkActive = false
|
this.networkActive = false
|
||||||
if (!this.userCanEdit) {
|
if (!this.userCanEdit) {
|
||||||
this.toastService.showInfo($localize`Document saved successfully.`)
|
this.toastService.showInfo($localize`Document saved successfully.`)
|
||||||
this.close()
|
close && this.close()
|
||||||
} else {
|
} else {
|
||||||
this.error = error.error
|
this.error = error.error
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user