Fix: tag creation sometimes retained search text

This commit is contained in:
shamoon 2023-08-20 13:36:46 -07:00
parent 17b85f6400
commit 0098936347
3 changed files with 71 additions and 46 deletions

View File

@ -2,7 +2,7 @@
<label class="form-label" for="tags" i18n>Tags</label> <label class="form-label" for="tags" i18n>Tags</label>
<div class="input-group flex-nowrap"> <div class="input-group flex-nowrap">
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value" <ng-select #tagSelect name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value"
[disabled]="disabled" [disabled]="disabled"
[multiple]="true" [multiple]="true"
[closeOnSelect]="false" [closeOnSelect]="false"
@ -11,11 +11,7 @@
[addTag]="allowCreate ? createTagRef : false" [addTag]="allowCreate ? createTagRef : false"
addTagText="Add tag" addTagText="Add tag"
i18n-addTagText i18n-addTagText
(change)="onChange(value)" (change)="onChange(value)">
(search)="onSearch($event)"
(focus)="clearLastSearchTerm()"
(clear)="clearLastSearchTerm()"
(blur)="onBlur()">
<ng-template ng-label-tmp let-item="item"> <ng-template ng-label-tmp let-item="item">
<span class="tag-wrap tag-wrap-delete" (mousedown)="removeTag($event, item.id)"> <span class="tag-wrap tag-wrap-delete" (mousedown)="removeTag($event, item.id)">

View File

@ -15,16 +15,28 @@ import {
DEFAULT_MATCHING_ALGORITHM, DEFAULT_MATCHING_ALGORITHM,
MATCH_ALL, MATCH_ALL,
} from 'src/app/data/matching-model' } from 'src/app/data/matching-model'
import { NgSelectModule } from '@ng-select/ng-select' import { NgSelectComponent, NgSelectModule } from '@ng-select/ng-select'
import { RouterTestingModule } from '@angular/router/testing' import { RouterTestingModule } from '@angular/router/testing'
import { HttpClientTestingModule } from '@angular/common/http/testing' import { HttpClientTestingModule } from '@angular/common/http/testing'
import { of } from 'rxjs' import { of } from 'rxjs'
import { TagService } from 'src/app/services/rest/tag.service' import { TagService } from 'src/app/services/rest/tag.service'
import { import {
NgbAccordionModule,
NgbModal, NgbModal,
NgbModalModule, NgbModalModule,
NgbModalRef, NgbModalRef,
NgbPopoverModule,
} from '@ng-bootstrap/ng-bootstrap' } from '@ng-bootstrap/ng-bootstrap'
import { TagEditDialogComponent } from '../../edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { CheckComponent } from '../check/check.component'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { TextComponent } from '../text/text.component'
import { ColorComponent } from '../color/color.component'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsFormComponent } from '../permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../select/select.component'
import { ColorSliderModule } from 'ngx-color/slider'
import { By } from '@angular/platform-browser'
const tags: PaperlessTag[] = [ const tags: PaperlessTag[] = [
{ {
@ -56,12 +68,32 @@ describe('TagsComponent', () => {
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [TagsComponent], declarations: [
TagsComponent,
TagEditDialogComponent,
TextComponent,
ColorComponent,
IfOwnerDirective,
SelectComponent,
TextComponent,
PermissionsFormComponent,
ColorComponent,
CheckComponent,
],
providers: [ providers: [
{ {
provide: TagService, provide: TagService,
useValue: { useValue: {
listAll: () => of(tags), listAll: () =>
of({
results: tags,
}),
create: () =>
of({
name: 'bar',
id: 99,
color: '#fff000',
}),
}, },
}, },
], ],
@ -72,6 +104,8 @@ describe('TagsComponent', () => {
RouterTestingModule, RouterTestingModule,
HttpClientTestingModule, HttpClientTestingModule,
NgbModalModule, NgbModalModule,
NgbAccordionModule,
NgbPopoverModule,
], ],
}).compileComponents() }).compileComponents()
@ -85,7 +119,7 @@ describe('TagsComponent', () => {
}) })
it('should support suggestions', () => { it('should support suggestions', () => {
expect(component.value).toBeUndefined() expect(component.value).toHaveLength(0)
component.value = [] component.value = []
component.tags = tags component.tags = tags
component.suggestions = [1, 2] component.suggestions = [1, 2]
@ -107,19 +141,19 @@ describe('TagsComponent', () => {
it('should support create new using last search term and open a modal', () => { it('should support create new using last search term and open a modal', () => {
let activeInstances: NgbModalRef[] let activeInstances: NgbModalRef[]
modalService.activeInstances.subscribe((v) => (activeInstances = v)) modalService.activeInstances.subscribe((v) => (activeInstances = v))
component.onSearch({ term: 'bar' }) component.select.searchTerm = 'foobar'
component.createTag() component.createTag()
expect(modalService.hasOpenModals()).toBeTruthy() expect(modalService.hasOpenModals()).toBeTruthy()
expect(activeInstances[0].componentInstance.object.name).toEqual('bar') expect(activeInstances[0].componentInstance.object.name).toEqual('foobar')
const editDialog = activeInstances[0]
.componentInstance as TagEditDialogComponent
editDialog.save() // create is mocked
fixture.detectChanges()
fixture.whenStable().then(() => {
expect(fixture.debugElement.nativeElement.textContent).toContain('foobar')
})
}) })
it('should clear search term on blur after delay', fakeAsync(() => {
const clearSpy = jest.spyOn(component, 'clearLastSearchTerm')
component.onBlur()
tick(3000)
expect(clearSpy).toHaveBeenCalled()
}))
it('support remove tags', () => { it('support remove tags', () => {
component.tags = tags component.tags = tags
component.value = [1, 2] component.value = [1, 2]
@ -132,6 +166,7 @@ describe('TagsComponent', () => {
}) })
it('should get tags', () => { it('should get tags', () => {
component.tags = null
expect(component.getTag(2)).toBeNull() expect(component.getTag(2)).toBeNull()
component.tags = tags component.tags = tags
expect(component.getTag(2)).toEqual(tags[1]) expect(component.getTag(2)).toEqual(tags[1])

View File

@ -5,6 +5,7 @@ import {
Input, Input,
OnInit, OnInit,
Output, Output,
ViewChild,
} from '@angular/core' } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
@ -12,6 +13,8 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
import { TagEditDialogComponent } from '../../edit-dialog/tag-edit-dialog/tag-edit-dialog.component' import { TagEditDialogComponent } from '../../edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { TagService } from 'src/app/services/rest/tag.service' import { TagService } from 'src/app/services/rest/tag.service'
import { EditDialogMode } from '../../edit-dialog/edit-dialog.component' import { EditDialogMode } from '../../edit-dialog/edit-dialog.component'
import { first, firstValueFrom, tap } from 'rxjs'
import { NgSelectComponent } from '@ng-select/ng-select'
@Component({ @Component({
providers: [ providers: [
@ -74,14 +77,14 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
@Output() @Output()
filterDocuments = new EventEmitter<PaperlessTag[]>() filterDocuments = new EventEmitter<PaperlessTag[]>()
value: number[] @ViewChild('tagSelect') select: NgSelectComponent
tags: PaperlessTag[] value: number[] = []
tags: PaperlessTag[] = []
public createTagRef: (name) => void public createTagRef: (name) => void
private _lastSearchTerm: string
getTag(id: number) { getTag(id: number) {
if (this.tags) { if (this.tags) {
return this.tags.find((tag) => tag.id == id) return this.tags.find((tag) => tag.id == id)
@ -111,15 +114,20 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
}) })
modal.componentInstance.dialogMode = EditDialogMode.CREATE modal.componentInstance.dialogMode = EditDialogMode.CREATE
if (name) modal.componentInstance.object = { name: name } if (name) modal.componentInstance.object = { name: name }
else if (this._lastSearchTerm) else if (this.select.searchTerm)
modal.componentInstance.object = { name: this._lastSearchTerm } modal.componentInstance.object = { name: this.select.searchTerm }
modal.componentInstance.succeeded.subscribe((newTag) => { this.select.searchTerm = null
this.tagService.listAll().subscribe((tags) => { this.select.detectChanges()
this.tags = tags.results return firstValueFrom(
this.value = [...this.value, newTag.id] (modal.componentInstance as TagEditDialogComponent).succeeded.pipe(
this.onChange(this.value) first(),
}) tap(() => {
}) this.tagService.listAll().subscribe((tags) => {
this.tags = tags.results
})
})
)
)
} }
getSuggestions() { getSuggestions() {
@ -137,20 +145,6 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
this.onChange(this.value) this.onChange(this.value)
} }
clearLastSearchTerm() {
this._lastSearchTerm = null
}
onSearch($event) {
this._lastSearchTerm = $event.term
}
onBlur() {
setTimeout(() => {
this.clearLastSearchTerm()
}, 3000)
}
get hasPrivate(): boolean { get hasPrivate(): boolean {
return this.value.some( return this.value.some(
(t) => this.tags?.find((t2) => t2.id === t) === undefined (t) => this.tags?.find((t2) => t2.id === t) === undefined