mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-19 10:24:57 +00:00
Chorehancement: update to Angular v22, 'zoneless' / 'reactive' (#13114)
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
||||
HttpTestingController,
|
||||
provideHttpClientTesting,
|
||||
} from '@angular/common/http/testing'
|
||||
import { EventEmitter } from '@angular/core'
|
||||
import { EventEmitter, signal } from '@angular/core'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -38,7 +38,6 @@ import { environment } from 'src/environments/environment'
|
||||
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
||||
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||
import { FilterableDropdownComponent } from '../../common/filterable-dropdown/filterable-dropdown.component'
|
||||
@@ -1146,7 +1145,7 @@ describe('BulkEditorComponent', () => {
|
||||
fixture.detectChanges()
|
||||
component.mergeSelected()
|
||||
expect(modal).not.toBeUndefined()
|
||||
modal.componentInstance.metadataDocumentID = 3
|
||||
modal.componentInstance.metadataDocumentID.set(3)
|
||||
modal.componentInstance.confirm()
|
||||
let req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/merge/`
|
||||
@@ -1164,7 +1163,7 @@ describe('BulkEditorComponent', () => {
|
||||
) // listAllFilteredIds
|
||||
|
||||
// Test with Delete Originals enabled
|
||||
modal.componentInstance.deleteOriginals = true
|
||||
modal.componentInstance.deleteOriginals.set(true)
|
||||
modal.componentInstance.confirm()
|
||||
req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/merge/`
|
||||
@@ -1184,8 +1183,8 @@ describe('BulkEditorComponent', () => {
|
||||
expect(documentListViewService.selected.size).toEqual(0)
|
||||
|
||||
// Test with archiveFallback enabled
|
||||
modal.componentInstance.deleteOriginals = false
|
||||
modal.componentInstance.archiveFallback = true
|
||||
modal.componentInstance.deleteOriginals.set(false)
|
||||
modal.componentInstance.archiveFallback.set(true)
|
||||
modal.componentInstance.confirm()
|
||||
req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/merge/`
|
||||
@@ -1339,7 +1338,7 @@ describe('BulkEditorComponent', () => {
|
||||
|
||||
const modalInstance = {
|
||||
componentInstance: {
|
||||
dialogMode: EditDialogMode.CREATE,
|
||||
dialogMode: { set: jest.fn() },
|
||||
object: { name },
|
||||
succeeded: of(newTag),
|
||||
},
|
||||
@@ -1382,7 +1381,7 @@ describe('BulkEditorComponent', () => {
|
||||
|
||||
const modalInstance = {
|
||||
componentInstance: {
|
||||
dialogMode: EditDialogMode.CREATE,
|
||||
dialogMode: { set: jest.fn() },
|
||||
object: { name },
|
||||
succeeded: of(newCorrespondent),
|
||||
},
|
||||
@@ -1431,7 +1430,7 @@ describe('BulkEditorComponent', () => {
|
||||
|
||||
const modalInstance = {
|
||||
componentInstance: {
|
||||
dialogMode: EditDialogMode.CREATE,
|
||||
dialogMode: { set: jest.fn() },
|
||||
object: { name },
|
||||
succeeded: of(newDocumentType),
|
||||
},
|
||||
@@ -1477,7 +1476,7 @@ describe('BulkEditorComponent', () => {
|
||||
|
||||
const modalInstance = {
|
||||
componentInstance: {
|
||||
dialogMode: EditDialogMode.CREATE,
|
||||
dialogMode: { set: jest.fn() },
|
||||
object: { name },
|
||||
succeeded: of(newStoragePath),
|
||||
},
|
||||
@@ -1531,7 +1530,7 @@ describe('BulkEditorComponent', () => {
|
||||
|
||||
const modalInstance = {
|
||||
componentInstance: {
|
||||
dialogMode: EditDialogMode.CREATE,
|
||||
dialogMode: { set: jest.fn() },
|
||||
object: { name },
|
||||
succeeded: of(newCustomField),
|
||||
},
|
||||
@@ -1629,15 +1628,18 @@ describe('BulkEditorComponent', () => {
|
||||
close: jest.fn(),
|
||||
componentInstance: {
|
||||
documents: [],
|
||||
setDocuments(docs) {
|
||||
this.documents = docs
|
||||
},
|
||||
confirmClicked,
|
||||
payload: {
|
||||
document_ids: [5, 7],
|
||||
file_version: 'archive',
|
||||
expiration_days: 7,
|
||||
},
|
||||
loading: false,
|
||||
loading: signal(false),
|
||||
buttonsEnabled: true,
|
||||
copied: false,
|
||||
copied: signal(false),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1667,7 +1669,7 @@ describe('BulkEditorComponent', () => {
|
||||
file_version: 'archive',
|
||||
expiration_days: 7,
|
||||
})
|
||||
expect(dialogInstance.loading).toBe(false)
|
||||
expect(dialogInstance.loading()).toBe(false)
|
||||
expect(dialogInstance.buttonsEnabled).toBe(false)
|
||||
expect(dialogInstance.createdBundle).toEqual({ id: 42 })
|
||||
expect(typeof dialogInstance.onOpenManage).toBe('function')
|
||||
@@ -1698,13 +1700,16 @@ describe('BulkEditorComponent', () => {
|
||||
const modalRef: Partial<NgbModalRef> = {
|
||||
componentInstance: {
|
||||
documents: [],
|
||||
setDocuments(docs) {
|
||||
this.documents = docs
|
||||
},
|
||||
confirmClicked,
|
||||
payload: {
|
||||
document_ids: [9],
|
||||
file_version: 'original',
|
||||
expiration_days: null,
|
||||
},
|
||||
loading: false,
|
||||
loading: signal(false),
|
||||
buttonsEnabled: true,
|
||||
},
|
||||
}
|
||||
@@ -1726,7 +1731,7 @@ describe('BulkEditorComponent', () => {
|
||||
$localize`Share link bundle creation is not available yet.`,
|
||||
expect.any(Error)
|
||||
)
|
||||
expect(dialogInstance.loading).toBe(false)
|
||||
expect(dialogInstance.loading()).toBe(false)
|
||||
expect(dialogInstance.buttonsEnabled).toBe(true)
|
||||
openSpy.mockRestore()
|
||||
})
|
||||
|
||||
@@ -266,7 +266,7 @@ export class BulkEditorComponent
|
||||
overrideSelection?: DocumentSelectionQuery
|
||||
) {
|
||||
if (modal) {
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
this.setModalButtonsEnabled(modal, false)
|
||||
}
|
||||
this.documentService
|
||||
.bulkEdit(overrideSelection ?? this.getSelectionQuery(), method, args)
|
||||
@@ -283,7 +283,7 @@ export class BulkEditorComponent
|
||||
options: { deleteOriginals?: boolean } = {}
|
||||
) {
|
||||
if (modal) {
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
this.setModalButtonsEnabled(modal, false)
|
||||
}
|
||||
request.pipe(first()).subscribe({
|
||||
next: () => {
|
||||
@@ -313,7 +313,7 @@ export class BulkEditorComponent
|
||||
|
||||
private handleOperationError(modal: NgbModalRef, error: any) {
|
||||
if (modal) {
|
||||
modal.componentInstance.buttonsEnabled = true
|
||||
this.setModalButtonsEnabled(modal, true)
|
||||
}
|
||||
this.toastService.showError(
|
||||
$localize`Error executing bulk operation`,
|
||||
@@ -321,6 +321,15 @@ export class BulkEditorComponent
|
||||
)
|
||||
}
|
||||
|
||||
private setModalButtonsEnabled(modal: NgbModalRef, enabled: boolean) {
|
||||
const buttonsEnabled = modal.componentInstance.buttonsEnabled
|
||||
if (typeof buttonsEnabled?.set === 'function') {
|
||||
buttonsEnabled.set(enabled)
|
||||
} else {
|
||||
modal.componentInstance.buttonsEnabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
private applySelectionData(
|
||||
items: SelectionDataItem[],
|
||||
selectionModel: FilterableDropdownSelectionModel
|
||||
@@ -736,7 +745,7 @@ export class BulkEditorComponent
|
||||
let modal = this.modalService.open(TagEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
modal.componentInstance.dialogMode.set(EditDialogMode.CREATE)
|
||||
modal.componentInstance.object = { name }
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(
|
||||
@@ -757,7 +766,7 @@ export class BulkEditorComponent
|
||||
let modal = this.modalService.open(CorrespondentEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
modal.componentInstance.dialogMode.set(EditDialogMode.CREATE)
|
||||
modal.componentInstance.object = { name }
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(
|
||||
@@ -780,7 +789,7 @@ export class BulkEditorComponent
|
||||
let modal = this.modalService.open(DocumentTypeEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
modal.componentInstance.dialogMode.set(EditDialogMode.CREATE)
|
||||
modal.componentInstance.object = { name }
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(
|
||||
@@ -801,7 +810,7 @@ export class BulkEditorComponent
|
||||
let modal = this.modalService.open(StoragePathEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
modal.componentInstance.dialogMode.set(EditDialogMode.CREATE)
|
||||
modal.componentInstance.object = { name }
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(
|
||||
@@ -822,7 +831,7 @@ export class BulkEditorComponent
|
||||
let modal = this.modalService.open(CustomFieldEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
modal.componentInstance.dialogMode.set(EditDialogMode.CREATE)
|
||||
modal.componentInstance.object = { name }
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(
|
||||
@@ -914,7 +923,7 @@ export class BulkEditorComponent
|
||||
})
|
||||
modal.componentInstance.confirmClicked.subscribe(
|
||||
({ permissions, merge }) => {
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
modal.componentInstance.buttonsEnabled.set(false)
|
||||
this.executeBulkEditMethod(modal, 'set_permissions', {
|
||||
...permissions,
|
||||
merge,
|
||||
@@ -933,7 +942,7 @@ export class BulkEditorComponent
|
||||
rotateDialog.messageBold = $localize`This operation will add rotated versions of the ${this.getSelectionSize()} document(s).`
|
||||
rotateDialog.btnClass = 'btn-danger'
|
||||
rotateDialog.btnCaption = $localize`Proceed`
|
||||
rotateDialog.documentID = Array.from(this.list.selected)[0]
|
||||
rotateDialog.documentID.set(Array.from(this.list.selected)[0])
|
||||
rotateDialog.confirmClicked
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
@@ -956,24 +965,24 @@ export class BulkEditorComponent
|
||||
mergeDialog.title = $localize`Merge confirm`
|
||||
mergeDialog.messageBold = $localize`This operation will merge ${this.getSelectionSize()} selected documents into a new document.`
|
||||
mergeDialog.btnCaption = $localize`Proceed`
|
||||
mergeDialog.documentIDs = Array.from(this.list.selected)
|
||||
mergeDialog.documentIDs.set(Array.from(this.list.selected))
|
||||
mergeDialog.confirmClicked
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
const args: MergeDocumentsRequest = {}
|
||||
if (mergeDialog.metadataDocumentID > -1) {
|
||||
args.metadata_document_id = mergeDialog.metadataDocumentID
|
||||
if (mergeDialog.metadataDocumentID() > -1) {
|
||||
args.metadata_document_id = mergeDialog.metadataDocumentID()
|
||||
}
|
||||
if (mergeDialog.deleteOriginals) {
|
||||
if (mergeDialog.deleteOriginals()) {
|
||||
args.delete_originals = true
|
||||
}
|
||||
if (mergeDialog.archiveFallback) {
|
||||
if (mergeDialog.archiveFallback()) {
|
||||
args.archive_fallback = true
|
||||
}
|
||||
mergeDialog.buttonsEnabled = false
|
||||
this.executeDocumentAction(
|
||||
modal,
|
||||
this.documentService.mergeDocuments(mergeDialog.documentIDs, args),
|
||||
this.documentService.mergeDocuments(mergeDialog.documentIDs(), args),
|
||||
{ deleteOriginals: !!args.delete_originals }
|
||||
)
|
||||
this.toastService.showInfo(
|
||||
@@ -1029,21 +1038,21 @@ export class BulkEditorComponent
|
||||
const selectedDocuments = this.list.documents.filter((d) =>
|
||||
this.list.selected.has(d.id)
|
||||
)
|
||||
dialog.documents = selectedDocuments
|
||||
dialog.setDocuments(selectedDocuments)
|
||||
dialog.confirmClicked
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
dialog.loading = true
|
||||
dialog.loading.set(true)
|
||||
dialog.buttonsEnabled = false
|
||||
this.shareLinkBundleService
|
||||
.createBundle(dialog.payload)
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
dialog.loading = false
|
||||
dialog.loading.set(false)
|
||||
dialog.buttonsEnabled = false
|
||||
dialog.createdBundle = result
|
||||
dialog.copied = false
|
||||
dialog.copied.set(false)
|
||||
dialog.payload = null
|
||||
dialog.onOpenManage = () => {
|
||||
modal.close()
|
||||
@@ -1054,7 +1063,7 @@ export class BulkEditorComponent
|
||||
)
|
||||
},
|
||||
error: (error) => {
|
||||
dialog.loading = false
|
||||
dialog.loading.set(false)
|
||||
dialog.buttonsEnabled = true
|
||||
this.toastService.showError(
|
||||
$localize`Share link bundle creation is not available yet.`,
|
||||
@@ -1080,7 +1089,7 @@ export class BulkEditorComponent
|
||||
const modal = this.modalService.open(EmailDocumentDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.documentIds = Array.from(this.list.selected)
|
||||
modal.componentInstance.hasArchiveVersion = allHaveArchiveVersion
|
||||
modal.componentInstance.documentIds.set(Array.from(this.list.selected))
|
||||
modal.componentInstance.hasArchiveVersion.set(allHaveArchiveVersion)
|
||||
}
|
||||
}
|
||||
|
||||
+48
-48
@@ -1,13 +1,13 @@
|
||||
<div class="card document-card-large mb-3 shadow-sm bg-light placeholder-glow fade" [class.show]="show" [class.card-selected]="selected" [class.document-card]="selectable" (mouseleave)="mouseLeaveCard()">
|
||||
<div class="card document-card-large mb-3 shadow-sm bg-light placeholder-glow fade" [class.show]="show()" [class.card-selected]="selected()" [class.document-card]="selectable" (mouseleave)="mouseLeaveCard()">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 doc-img-container rounded-start" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit()">
|
||||
@if (document) {
|
||||
@if (document()) {
|
||||
<img [src]="getThumbUrl()" class="card-img doc-img border-end rounded-start" [class.inverted]="getIsThumbInverted()">
|
||||
|
||||
<div class="border-end border-bottom bg-light document-card-check">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="smallCardCheck{{document.id}}" [checked]="selected" (click)="this.toggleSelected.emit($event)">
|
||||
<label class="form-check-label" for="smallCardCheck{{document.id}}"></label>
|
||||
<input type="checkbox" class="form-check-input" id="smallCardCheck{{document().id}}" [checked]="selected()" (click)="this.toggleSelected.emit($event)">
|
||||
<label class="form-check-label" for="smallCardCheck{{document().id}}"></label>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
@@ -19,20 +19,20 @@
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title w-100">
|
||||
@if (document) {
|
||||
@if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
|
||||
@if (document()) {
|
||||
@if (displayFields().includes(DisplayField.CORRESPONDENT) && document().correspondent) {
|
||||
@if (clickCorrespondent.observers.length ) {
|
||||
<a title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{document.correspondent | correspondentName | async}}</a>
|
||||
<a title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document().correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{document().correspondent | correspondentName | async}}</a>
|
||||
} @else {
|
||||
{{document.correspondent | correspondentName | async}}
|
||||
{{document().correspondent | correspondentName | async}}
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.TITLE)) {:}
|
||||
@if (displayFields().includes(DisplayField.TITLE)) {:}
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.TITLE)) {
|
||||
{{document.title | documentTitle}}
|
||||
@if (displayFields().includes(DisplayField.TITLE)) {
|
||||
{{document().title | documentTitle}}
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.TAGS)) {
|
||||
@for (tagID of document.tags; track tagID) {
|
||||
@if (displayFields().includes(DisplayField.TAGS)) {
|
||||
@for (tagID of document().tags; track tagID) {
|
||||
<pngx-tag [tagID]="tagID" linkTitle="Filter by tag" i18n-linkTitle class="ms-1" (click)="clickTag.emit(tagID);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
|
||||
}
|
||||
}
|
||||
@@ -42,9 +42,9 @@
|
||||
</h5>
|
||||
</div>
|
||||
<p class="card-text">
|
||||
@if (document) {
|
||||
@if (document()) {
|
||||
@if (hasSearchHighlights) {
|
||||
<span [innerHtml]="document.__search_hit__.highlights"></span>
|
||||
<span [innerHtml]="document().__search_hit__.highlights"></span>
|
||||
}
|
||||
@for (highlight of searchNoteHighlights; track highlight) {
|
||||
<span class="d-block">
|
||||
@@ -64,14 +64,14 @@
|
||||
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center">
|
||||
<div class="btn-group">
|
||||
@if (document) {
|
||||
@if (document()) {
|
||||
<a class="btn btn-sm btn-outline-secondary" (click)="clickMoreLike.emit()">
|
||||
<i-bs name="diagram-3" class="me-1"></i-bs><span class="d-none d-md-inline" i18n>More like this</span>
|
||||
</a>
|
||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<a routerLink="/documents/{{document().id}}" class="btn btn-sm btn-outline-secondary" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<i-bs name="file-earmark-richtext" class="me-1"></i-bs><span class="d-none d-md-inline" i18n>Open</span>
|
||||
</a>
|
||||
<pngx-preview-popup [document]="document" #popupPreview>
|
||||
<pngx-preview-popup [document]="document()" #popupPreview>
|
||||
<i-bs name="eye" class="me-1"></i-bs><span class="d-none d-md-inline" i18n>View</span>
|
||||
</pngx-preview-popup>
|
||||
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
|
||||
@@ -86,76 +86,76 @@
|
||||
</div>
|
||||
|
||||
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
|
||||
@if (document) {
|
||||
@if (displayFields.includes(DisplayField.NOTES) && notesEnabled && document.notes.length) {
|
||||
<button routerLink="/documents/{{document.id}}/notes" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="View notes" i18n-title>
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="chat-left-text"></i-bs><small>{{document.notes.length}} Notes</small>
|
||||
@if (document()) {
|
||||
@if (displayFields().includes(DisplayField.NOTES) && notesEnabled && document().notes.length) {
|
||||
<button routerLink="/documents/{{document().id}}/notes" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="View notes" i18n-title>
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="chat-left-text"></i-bs><small>{{document().notes.length}} Notes</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.DOCUMENT_TYPE) && document.document_type) {
|
||||
@if (displayFields().includes(DisplayField.DOCUMENT_TYPE) && document().document_type) {
|
||||
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by document type" i18n-title
|
||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="file-earmark"></i-bs><small>{{document.document_type | documentTypeName | async}}</small>
|
||||
(click)="clickDocumentType.emit(document().document_type);$event.stopPropagation()">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="file-earmark"></i-bs><small>{{document().document_type | documentTypeName | async}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
|
||||
@if (displayFields().includes(DisplayField.STORAGE_PATH) && document().storage_path) {
|
||||
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by storage path" i18n-title
|
||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="archive"></i-bs><small>{{document.storage_path | storagePathName | async}}</small>
|
||||
(click)="clickStoragePath.emit(document().storage_path);$event.stopPropagation()">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="archive"></i-bs><small>{{document().storage_path | storagePathName | async}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.ASN) && document.archive_serial_number | isNumber) {
|
||||
@if (displayFields().includes(DisplayField.ASN) && document().archive_serial_number | isNumber) {
|
||||
<div class="list-group-item me-2 bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="upc-scan"></i-bs><small>#{{document.archive_serial_number}}</small>
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="upc-scan"></i-bs><small>#{{document().archive_serial_number}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.CREATED) || displayFields.includes(DisplayField.ADDED)) {
|
||||
@if (displayFields().includes(DisplayField.CREATED) || displayFields().includes(DisplayField.ADDED)) {
|
||||
<ng-template #dateTooltip>
|
||||
<div class="d-flex flex-column text-light">
|
||||
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||
<span i18n>Added: {{ document.added | customDate }}</span>
|
||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||
<span i18n>Created: {{ document().created | customDate }}</span>
|
||||
<span i18n>Added: {{ document().added | customDate }}</span>
|
||||
<span i18n>Modified: {{ document().modified | customDate }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
@if (displayFields.includes(DisplayField.CREATED)) {
|
||||
@if (displayFields().includes(DisplayField.CREATED)) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created | customDate:'mediumDate'}}</small>
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document().created | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.ADDED)) {
|
||||
@if (displayFields().includes(DisplayField.ADDED)) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.added | customDate:'mediumDate'}}</small>
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document().added | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.PAGE_COUNT) && document.page_count) {
|
||||
@if (displayFields().includes(DisplayField.PAGE_COUNT) && document().page_count) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="files"></i-bs>
|
||||
<small i18n>{document.page_count, plural, =1 {1 page} other {{{document.page_count}} pages}}</small>
|
||||
<small i18n>{document().page_count, plural, =1 {1 page} other {{{document().page_count}} pages}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
@if (displayFields().includes(DisplayField.OWNER) && document().owner && document().owner !== settingsService.currentUser().id) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username | async}}</small>
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document().owner | username | async}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
|
||||
@if (displayFields().includes(DisplayField.SHARED) && document().is_shared_by_requester) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="people-fill"></i-bs><small i18n>Shared</small>
|
||||
</div>
|
||||
}
|
||||
@if (document.__search_hit__?.score) {
|
||||
@if (document().__search_hit__?.score) {
|
||||
<div class="list-group-item bg-light text-dark border-0 d-flex p-0 ps-4 search-score">
|
||||
<small class="me-2 text-muted" i18n>Score:</small>
|
||||
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
|
||||
<ngb-progressbar [type]="searchScoreClass" [value]="document().__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
|
||||
</div>
|
||||
}
|
||||
@for (field of document.custom_fields; track field.field) {
|
||||
@if (displayFields.includes(DisplayField.CUSTOM_FIELD + field.field)) {
|
||||
@for (field of document().custom_fields; track field.field) {
|
||||
@if (displayFields().includes(DisplayField.CUSTOM_FIELD + field.field)) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="ui-radios"></i-bs>
|
||||
<small>
|
||||
<pngx-custom-field-display [document]="document" [fieldId]="field.field" showNameIfEmpty="true"></pngx-custom-field-display>
|
||||
<pngx-custom-field-display [document]="document()" [fieldId]="field.field" showNameIfEmpty="true"></pngx-custom-field-display>
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
|
||||
+20
@@ -2,6 +2,26 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.fade.show {
|
||||
animation: pngx-entry-fade 160ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes pngx-entry-fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fade.show {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-img-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
+40
-22
@@ -67,16 +67,19 @@ describe('DocumentCardLargeComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(DocumentCardLargeComponent)
|
||||
component = fixture.componentInstance
|
||||
component.document = doc
|
||||
fixture.componentRef.setInput('document', {
|
||||
...doc,
|
||||
tags: [...doc.tags],
|
||||
notes: [...doc.notes],
|
||||
})
|
||||
fixture.detectChanges()
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
it('should show the card', () => {
|
||||
expect(component.show).toBeFalsy()
|
||||
expect(component.show()).toBeTruthy()
|
||||
component.ngAfterViewInit()
|
||||
jest.advanceTimersByTime(100)
|
||||
expect(component.show).toBeTruthy()
|
||||
expect(component.show()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should display a document', () => {
|
||||
@@ -91,25 +94,34 @@ describe('DocumentCardLargeComponent', () => {
|
||||
|
||||
it('should display search hits with colored score', () => {
|
||||
// high
|
||||
component.document.__search_hit__ = {
|
||||
score: 0.9,
|
||||
rank: 1,
|
||||
highlights: 'cheesecake',
|
||||
}
|
||||
fixture.componentRef.setInput('document', {
|
||||
...component.document(),
|
||||
__search_hit__: {
|
||||
score: 0.9,
|
||||
rank: 1,
|
||||
highlights: 'cheesecake',
|
||||
},
|
||||
})
|
||||
fixture.detectChanges()
|
||||
let search_hit = fixture.debugElement.query(By.css('.search-score'))
|
||||
expect(search_hit).not.toBeUndefined()
|
||||
expect(component.searchScoreClass).toEqual('success')
|
||||
|
||||
// medium
|
||||
component.document.__search_hit__.score = 0.6
|
||||
fixture.componentRef.setInput('document', {
|
||||
...component.document(),
|
||||
__search_hit__: { ...component.document().__search_hit__, score: 0.6 },
|
||||
})
|
||||
fixture.detectChanges()
|
||||
search_hit = fixture.debugElement.query(By.css('.search-score'))
|
||||
expect(search_hit).not.toBeUndefined()
|
||||
expect(component.searchScoreClass).toEqual('warning')
|
||||
|
||||
// low
|
||||
component.document.__search_hit__.score = 0.1
|
||||
fixture.componentRef.setInput('document', {
|
||||
...component.document(),
|
||||
__search_hit__: { ...component.document().__search_hit__, score: 0.1 },
|
||||
})
|
||||
fixture.detectChanges()
|
||||
search_hit = fixture.debugElement.query(By.css('.search-score'))
|
||||
expect(search_hit).not.toBeUndefined()
|
||||
@@ -117,23 +129,29 @@ describe('DocumentCardLargeComponent', () => {
|
||||
})
|
||||
|
||||
it('should display note highlights', () => {
|
||||
component.document.__search_hit__ = {
|
||||
score: 0.9,
|
||||
rank: 1,
|
||||
note_highlights: '<span>bananas</span>',
|
||||
}
|
||||
fixture.componentRef.setInput('document', {
|
||||
...component.document(),
|
||||
__search_hit__: {
|
||||
score: 0.9,
|
||||
rank: 1,
|
||||
note_highlights: '<span>bananas</span>',
|
||||
},
|
||||
})
|
||||
fixture.detectChanges()
|
||||
expect(fixture.nativeElement.textContent).toContain('bananas')
|
||||
expect(component.searchNoteHighlights).toContain('<span>bananas</span>')
|
||||
})
|
||||
|
||||
it('should fall back to document content when a search hit has no highlights', () => {
|
||||
component.document.__search_hit__ = {
|
||||
score: 0.9,
|
||||
rank: 1,
|
||||
highlights: '',
|
||||
note_highlights: null,
|
||||
}
|
||||
fixture.componentRef.setInput('document', {
|
||||
...component.document(),
|
||||
__search_hit__: {
|
||||
score: 0.9,
|
||||
rank: 1,
|
||||
highlights: '',
|
||||
note_highlights: null,
|
||||
},
|
||||
})
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('Cupcake ipsum')
|
||||
|
||||
+22
-30
@@ -3,10 +3,10 @@ import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import {
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
NgbTooltipModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { delay, of } from 'rxjs'
|
||||
import {
|
||||
DEFAULT_DISPLAY_FIELDS,
|
||||
DisplayField,
|
||||
@@ -65,15 +64,14 @@ export class DocumentCardLargeComponent
|
||||
{
|
||||
private documentService = inject(DocumentService)
|
||||
settingsService = inject(SettingsService)
|
||||
readonly selected = input(false)
|
||||
readonly displayFields = input<string[]>(
|
||||
DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
)
|
||||
readonly document = input<Document>(undefined)
|
||||
|
||||
DisplayField = DisplayField
|
||||
|
||||
@Input()
|
||||
selected = false
|
||||
|
||||
@Input()
|
||||
displayFields: string[] = DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
|
||||
@Output()
|
||||
toggleSelected = new EventEmitter()
|
||||
|
||||
@@ -81,9 +79,6 @@ export class DocumentCardLargeComponent
|
||||
return this.toggleSelected.observers.length > 0
|
||||
}
|
||||
|
||||
@Input()
|
||||
document: Document
|
||||
|
||||
@Output()
|
||||
dblClickDocument = new EventEmitter()
|
||||
|
||||
@@ -108,18 +103,15 @@ export class DocumentCardLargeComponent
|
||||
popoverHidden = true
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
of(true)
|
||||
.pipe(delay(50))
|
||||
.subscribe(() => {
|
||||
this.show = true
|
||||
})
|
||||
this.show.set(true)
|
||||
}
|
||||
|
||||
get searchScoreClass() {
|
||||
if (this.document.__search_hit__) {
|
||||
if (this.document.__search_hit__.score > 0.7) {
|
||||
const document = this.document()
|
||||
if (document.__search_hit__) {
|
||||
if (document.__search_hit__.score > 0.7) {
|
||||
return 'success'
|
||||
} else if (this.document.__search_hit__.score > 0.3) {
|
||||
} else if (document.__search_hit__.score > 0.3) {
|
||||
return 'warning'
|
||||
} else {
|
||||
return 'danger'
|
||||
@@ -129,12 +121,10 @@ export class DocumentCardLargeComponent
|
||||
|
||||
get searchNoteHighlights() {
|
||||
let highlights = []
|
||||
if (
|
||||
this.document['__search_hit__'] &&
|
||||
this.document['__search_hit__'].note_highlights
|
||||
) {
|
||||
const document = this.document()
|
||||
if (document?.['__search_hit__']?.note_highlights) {
|
||||
// only show notes with a match
|
||||
highlights = (this.document['__search_hit__'].note_highlights as string)
|
||||
highlights = (document['__search_hit__'].note_highlights as string)
|
||||
.split(',')
|
||||
.filter((highlight) => highlight.includes('<span'))
|
||||
}
|
||||
@@ -146,11 +136,11 @@ export class DocumentCardLargeComponent
|
||||
}
|
||||
|
||||
getThumbUrl() {
|
||||
return this.documentService.getThumbUrl(this.document.id)
|
||||
return this.documentService.getThumbUrl(this.document().id)
|
||||
}
|
||||
|
||||
getDownloadUrl() {
|
||||
return this.documentService.getDownloadUrl(this.document.id)
|
||||
return this.documentService.getDownloadUrl(this.document().id)
|
||||
}
|
||||
|
||||
mouseLeaveCard() {
|
||||
@@ -158,19 +148,21 @@ export class DocumentCardLargeComponent
|
||||
}
|
||||
|
||||
get contentTrimmed() {
|
||||
const document = this.document()
|
||||
return (
|
||||
this.document.content.substring(0, 500) +
|
||||
(this.document.content.length > 500 ? '...' : '')
|
||||
document.content.substring(0, 500) +
|
||||
(document.content.length > 500 ? '...' : '')
|
||||
)
|
||||
}
|
||||
|
||||
get hasSearchHighlights() {
|
||||
return Boolean(this.document?.__search_hit__?.highlights?.trim()?.length)
|
||||
return Boolean(this.document()?.__search_hit__?.highlights?.trim()?.length)
|
||||
}
|
||||
|
||||
get shouldShowContentFallback() {
|
||||
const document = this.document()
|
||||
return (
|
||||
this.document?.__search_hit__?.score == null ||
|
||||
document?.__search_hit__?.score == null ||
|
||||
(!this.hasSearchHighlights && this.searchNoteHighlights.length === 0)
|
||||
)
|
||||
}
|
||||
|
||||
+46
-46
@@ -1,21 +1,21 @@
|
||||
<div class="col p-2 h-100 fade" [class.show]="show">
|
||||
<div class="card h-100 shadow-sm document-card" [class.placeholder-glow]="!document" [class.card-selected]="selected" (mouseleave)="mouseLeaveCard()">
|
||||
<div class="col p-2 h-100 fade" [class.show]="show()">
|
||||
<div class="card h-100 shadow-sm document-card" [class.placeholder-glow]="!document()" [class.card-selected]="selected()" (mouseleave)="mouseLeaveCard()">
|
||||
<div class="border-bottom doc-img-container rounded-top" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit(this)">
|
||||
@if (document) {
|
||||
@if (document()) {
|
||||
<img class="card-img doc-img" [class.inverted]="getIsThumbInverted()" [src]="getThumbUrl()">
|
||||
|
||||
<div class="border-end border-bottom bg-light py-1 px-2 document-card-check">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="smallCardCheck{{document.id}}" [checked]="selected" (click)="this.toggleSelected.emit($event)">
|
||||
<label class="form-check-label" for="smallCardCheck{{document.id}}"></label>
|
||||
<input type="checkbox" class="form-check-input" id="smallCardCheck{{document().id}}" [checked]="selected()" (click)="this.toggleSelected.emit($event)">
|
||||
<label class="form-check-label" for="smallCardCheck{{document().id}}"></label>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="placeholder bg-secondary w-100 card-img doc-img"></div>
|
||||
}
|
||||
|
||||
@if (document && displayFields?.includes(DisplayField.TAGS)) {
|
||||
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6" [class.tags-no-wrap]="document.tags.length > 3">
|
||||
@if (document() && displayFields().includes(DisplayField.TAGS)) {
|
||||
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6" [class.tags-no-wrap]="document().tags.length > 3">
|
||||
@for (tagID of tagIDs; track tagID) {
|
||||
<pngx-tag [tagID]="tagID" (click)="clickTag.emit(tagID);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
|
||||
}
|
||||
@@ -28,23 +28,23 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (document && displayFields.includes(DisplayField.NOTES) && notesEnabled && document.notes.length) {
|
||||
<a routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
|
||||
@if (document() && displayFields().includes(DisplayField.NOTES) && notesEnabled && document().notes.length) {
|
||||
<a routerLink="/documents/{{document().id}}/notes" class="document-card-notes py-2 px-1">
|
||||
<span class="badge rounded-pill bg-light border text-primary">
|
||||
<i-bs width="1.2em" height="1.2em" class="ms-1 me-1" name="chat-left-text"></i-bs>
|
||||
{{document.notes.length}}</span>
|
||||
{{document().notes.length}}</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
<div class="card-body bg-light p-2">
|
||||
<p class="card-text">
|
||||
@if (document) {
|
||||
@if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
|
||||
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{document.correspondent | correspondentName | async}}</a>
|
||||
@if (displayFields.includes(DisplayField.TITLE)) {:}
|
||||
@if (document()) {
|
||||
@if (displayFields().includes(DisplayField.CORRESPONDENT) && document().correspondent) {
|
||||
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document().correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{document().correspondent | correspondentName | async}}</a>
|
||||
@if (displayFields().includes(DisplayField.TITLE)) {:}
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.TITLE)) {
|
||||
{{document.title | documentTitle}}
|
||||
@if (displayFields().includes(DisplayField.TITLE)) {
|
||||
{{document().title | documentTitle}}
|
||||
}
|
||||
} @else {
|
||||
<div class="placeholder bg-secondary w-100"></div>
|
||||
@@ -54,82 +54,82 @@
|
||||
</div>
|
||||
<div class="card-footer pt-0 pb-2 px-2">
|
||||
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
|
||||
@if (document) {
|
||||
@if (displayFields.includes(DisplayField.DOCUMENT_TYPE) && document.document_type) {
|
||||
@if (document()) {
|
||||
@if (displayFields().includes(DisplayField.DOCUMENT_TYPE) && document().document_type) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" i18n-title
|
||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||
(click)="clickDocumentType.emit(document().document_type);$event.stopPropagation()">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
|
||||
<small>{{document.document_type | documentTypeName | async}}</small>
|
||||
<small>{{document().document_type | documentTypeName | async}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
|
||||
@if (displayFields().includes(DisplayField.STORAGE_PATH) && document().storage_path) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" i18n-title
|
||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||
(click)="clickStoragePath.emit(document().storage_path);$event.stopPropagation()">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
|
||||
<small>{{document.storage_path | storagePathName | async}}</small>
|
||||
<small>{{document().storage_path | storagePathName | async}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.CREATED)) {
|
||||
@if (displayFields().includes(DisplayField.CREATED)) {
|
||||
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
|
||||
<ng-template #dateCreatedTooltip>
|
||||
<div class="d-flex flex-column text-light">
|
||||
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||
<span i18n>Added: {{ document.added | customDate }}</span>
|
||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||
<span i18n>Created: {{ document().created | customDate }}</span>
|
||||
<span i18n>Added: {{ document().added | customDate }}</span>
|
||||
<span i18n>Modified: {{ document().modified | customDate }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateCreatedTooltip">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
|
||||
<small>{{document.created | customDate:'mediumDate'}}</small>
|
||||
<small>{{document().created | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.ADDED)) {
|
||||
@if (displayFields().includes(DisplayField.ADDED)) {
|
||||
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
|
||||
<ng-template #dateAddedTooltip>
|
||||
<div class="d-flex flex-column text-light">
|
||||
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||
<span i18n>Added: {{ document.added | customDate }}</span>
|
||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||
<span i18n>Created: {{ document().created | customDate }}</span>
|
||||
<span i18n>Added: {{ document().added | customDate }}</span>
|
||||
<span i18n>Modified: {{ document().modified | customDate }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateAddedTooltip">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
|
||||
<small>{{document.added | customDate:'mediumDate'}}</small>
|
||||
<small>{{document().added | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.PAGE_COUNT) && document.page_count) {
|
||||
@if (displayFields().includes(DisplayField.PAGE_COUNT) && document().page_count) {
|
||||
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
|
||||
<div class="ps-0 p-1" placement="top">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="files"></i-bs>
|
||||
<small i18n>{document.page_count, plural, =1 {1 page} other {{{document.page_count}} pages}}</small>
|
||||
<small i18n>{document().page_count, plural, =1 {1 page} other {{{document().page_count}} pages}}</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.ASN) && document.archive_serial_number | isNumber) {
|
||||
@if (displayFields().includes(DisplayField.ASN) && document().archive_serial_number | isNumber) {
|
||||
<div class="ps-0 p-1">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="upc-scan"></i-bs>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
<small>#{{document().archive_serial_number}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
@if (displayFields().includes(DisplayField.OWNER) && document().owner && document().owner !== settingsService.currentUser().id) {
|
||||
<div class="ps-0 p-1">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
|
||||
<small>{{document.owner | username | async}}</small>
|
||||
<small>{{document().owner | username | async}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
|
||||
@if (displayFields().includes(DisplayField.SHARED) && document().is_shared_by_requester) {
|
||||
<div class="ps-0 p-1">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="people-fill"></i-bs>
|
||||
<small i18n>Shared</small>
|
||||
</div>
|
||||
}
|
||||
@for (field of document.custom_fields; track field.field) {
|
||||
@if (displayFields.includes(DisplayField.CUSTOM_FIELD + field.field)) {
|
||||
@for (field of document().custom_fields; track field.field) {
|
||||
@if (displayFields().includes(DisplayField.CUSTOM_FIELD + field.field)) {
|
||||
<div class="ps-0 p-1 d-flex align-items-center overflow-hidden">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="ui-radios"></i-bs>
|
||||
<small><pngx-custom-field-display [document]="document" [fieldId]="field.field" showNameIfEmpty="true"></pngx-custom-field-display></small>
|
||||
<small><pngx-custom-field-display [document]="document()" [fieldId]="field.field" showNameIfEmpty="true"></pngx-custom-field-display></small>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -139,11 +139,11 @@
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="btn-group w-100">
|
||||
@if (document) {
|
||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" title="Open" i18n-title *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n-title>
|
||||
@if (document()) {
|
||||
<a routerLink="/documents/{{document().id}}" class="btn btn-sm btn-outline-secondary" title="Open" i18n-title *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n-title>
|
||||
<i-bs name="file-earmark-richtext"></i-bs>
|
||||
</a>
|
||||
<pngx-preview-popup [document]="document" #popupPreview>
|
||||
<pngx-preview-popup [document]="document()" #popupPreview>
|
||||
<i-bs name="eye"></i-bs>
|
||||
</pngx-preview-popup>
|
||||
<a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||
|
||||
+20
@@ -2,6 +2,26 @@
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.fade.show {
|
||||
animation: pngx-entry-fade 160ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes pngx-entry-fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fade.show {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-img {
|
||||
object-fit: cover;
|
||||
object-position: top left;
|
||||
|
||||
+15
-7
@@ -46,16 +46,15 @@ describe('DocumentCardSmallComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(DocumentCardSmallComponent)
|
||||
component = fixture.componentInstance
|
||||
component.document = Object.assign({}, doc)
|
||||
fixture.componentRef.setInput('document', Object.assign({}, doc))
|
||||
fixture.detectChanges()
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
it('should show the card', () => {
|
||||
expect(component.show).toBeFalsy()
|
||||
expect(component.show()).toBeTruthy()
|
||||
component.ngAfterViewInit()
|
||||
jest.advanceTimersByTime(100)
|
||||
expect(component.show).toBeTruthy()
|
||||
expect(component.show()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should display page count', () => {
|
||||
@@ -67,7 +66,10 @@ describe('DocumentCardSmallComponent', () => {
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||
).toHaveLength(5)
|
||||
component.document.tags = [1, 2]
|
||||
fixture.componentRef.setInput('document', {
|
||||
...component.document(),
|
||||
tags: [1, 2],
|
||||
})
|
||||
fixture.detectChanges()
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||
@@ -75,7 +77,10 @@ describe('DocumentCardSmallComponent', () => {
|
||||
})
|
||||
|
||||
it('should increase limit tags to 6 if no notes', () => {
|
||||
component.document.notes = []
|
||||
fixture.componentRef.setInput('document', {
|
||||
...component.document(),
|
||||
notes: [],
|
||||
})
|
||||
fixture.detectChanges()
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||
@@ -85,7 +90,10 @@ describe('DocumentCardSmallComponent', () => {
|
||||
it('should clear hidden tag counter when tag count falls below the limit', () => {
|
||||
expect(component.moreTags).toEqual(3)
|
||||
|
||||
component.document.tags = [1, 2, 3, 4, 5, 6]
|
||||
fixture.componentRef.setInput('document', {
|
||||
...component.document(),
|
||||
tags: [1, 2, 3, 4, 5, 6],
|
||||
})
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(component.moreTags).toBeNull()
|
||||
|
||||
+21
-26
@@ -3,10 +3,10 @@ import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import {
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
NgbTooltipModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { of } from 'rxjs'
|
||||
import { delay } from 'rxjs/operators'
|
||||
import {
|
||||
DEFAULT_DISPLAY_FIELDS,
|
||||
DisplayField,
|
||||
@@ -66,21 +64,17 @@ export class DocumentCardSmallComponent
|
||||
{
|
||||
private documentService = inject(DocumentService)
|
||||
settingsService = inject(SettingsService)
|
||||
readonly selected = input(false)
|
||||
readonly document = input<Document>(undefined)
|
||||
readonly displayFields = input<string[]>(
|
||||
DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
)
|
||||
|
||||
DisplayField = DisplayField
|
||||
|
||||
@Input()
|
||||
selected = false
|
||||
|
||||
@Output()
|
||||
toggleSelected = new EventEmitter()
|
||||
|
||||
@Input()
|
||||
document: Document
|
||||
|
||||
@Input()
|
||||
displayFields: string[] = DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
|
||||
@Output()
|
||||
dblClickDocument = new EventEmitter()
|
||||
|
||||
@@ -96,16 +90,18 @@ export class DocumentCardSmallComponent
|
||||
@Output()
|
||||
clickStoragePath = new EventEmitter<number>()
|
||||
|
||||
moreTags: number = null
|
||||
get moreTags(): number {
|
||||
const document = this.document()
|
||||
const limit = document?.notes.length > 0 ? 6 : 7
|
||||
return document?.tags.length > limit
|
||||
? document.tags.length - (limit - 1)
|
||||
: null
|
||||
}
|
||||
|
||||
@ViewChild('popupPreview') popupPreview: PreviewPopupComponent
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
of(true)
|
||||
.pipe(delay(50))
|
||||
.subscribe(() => {
|
||||
this.show = true
|
||||
})
|
||||
this.show.set(true)
|
||||
}
|
||||
|
||||
getIsThumbInverted() {
|
||||
@@ -113,21 +109,20 @@ export class DocumentCardSmallComponent
|
||||
}
|
||||
|
||||
getThumbUrl() {
|
||||
return this.documentService.getThumbUrl(this.document.id)
|
||||
return this.documentService.getThumbUrl(this.document().id)
|
||||
}
|
||||
|
||||
getDownloadUrl() {
|
||||
return this.documentService.getDownloadUrl(this.document.id)
|
||||
return this.documentService.getDownloadUrl(this.document().id)
|
||||
}
|
||||
|
||||
get tagIDs() {
|
||||
const limit = this.document.notes.length > 0 ? 6 : 7
|
||||
if (this.document.tags.length > limit) {
|
||||
this.moreTags = this.document.tags.length - (limit - 1)
|
||||
return this.document.tags.slice(0, limit - 1)
|
||||
const document = this.document()
|
||||
const limit = document.notes.length > 0 ? 6 : 7
|
||||
if (document.tags.length > limit) {
|
||||
return document.tags.slice(0, limit - 1)
|
||||
} else {
|
||||
this.moreTags = null
|
||||
return this.document.tags
|
||||
return document.tags
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownDisplayFields" class="shadow">
|
||||
<div class="px-3">
|
||||
@for (field of settingsService.allDisplayFields; track field.id) {
|
||||
@for (field of settingsService.allDisplayFields(); track field.id) {
|
||||
<div class="form-check my-1">
|
||||
<input class="form-check-input mt-1" type="checkbox" id="displayField{{field.id}}" [checked]="activeDisplayFields.includes(field.id)" (change)="toggleDisplayField(field.id)">
|
||||
<label class="form-check-label" for="displayField{{field.id}}">{{field.name}}</label>
|
||||
|
||||
@@ -730,7 +730,9 @@ describe('DocumentListComponent', () => {
|
||||
showInSideBar: true,
|
||||
})
|
||||
expect(updateVisibilitySpy).not.toHaveBeenCalled()
|
||||
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
|
||||
expect(openModal.componentInstance.error()).toEqual({
|
||||
filter_rules: ['11'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should detect saved view changes', () => {
|
||||
@@ -777,7 +779,7 @@ describe('DocumentListComponent', () => {
|
||||
})
|
||||
|
||||
it('should hide columns if no perms or notes disabled', () => {
|
||||
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(true)
|
||||
permissionService.initialize([], { is_superuser: true } as any)
|
||||
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
||||
expect(documentListService.sortField).toEqual('created')
|
||||
|
||||
@@ -798,7 +800,7 @@ describe('DocumentListComponent', () => {
|
||||
).toHaveLength(9)
|
||||
|
||||
// insufficient perms
|
||||
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
|
||||
permissionService.initialize([], { is_superuser: false } as any)
|
||||
fixture.detectChanges()
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||
@@ -837,11 +839,10 @@ describe('DocumentListComponent', () => {
|
||||
|
||||
it('should get custom field title', () => {
|
||||
fixture.detectChanges()
|
||||
jest
|
||||
.spyOn(settingsService, 'allDisplayFields', 'get')
|
||||
.mockReturnValue([
|
||||
{ id: 'custom_field_1' as any, name: 'Custom Field 1' },
|
||||
])
|
||||
const mockDisplayFields = [
|
||||
{ id: 'custom_field_1' as any, name: 'Custom Field 1' },
|
||||
]
|
||||
settingsService.allDisplayFields = jest.fn(() => mockDisplayFields) as any
|
||||
expect(component.getDisplayCustomFieldTitle('custom_field_1')).toEqual(
|
||||
'Custom Field 1'
|
||||
)
|
||||
|
||||
@@ -255,7 +255,7 @@ export class DocumentListComponent
|
||||
}
|
||||
|
||||
public getDisplayCustomFieldTitle(field: string) {
|
||||
return this.settingsService.allDisplayFields.find((f) => f.id === field)
|
||||
return this.settingsService.allDisplayFields().find((f) => f.id === field)
|
||||
?.name
|
||||
}
|
||||
|
||||
@@ -449,9 +449,11 @@ export class DocumentListComponent
|
||||
let modal = this.modalService.open(SaveViewConfigDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.defaultName = this.filterEditor.generateFilterName()
|
||||
modal.componentInstance.setDefaultName(
|
||||
this.filterEditor.generateFilterName()
|
||||
)
|
||||
modal.componentInstance.saveClicked.pipe(first()).subscribe((formValue) => {
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
modal.componentInstance.buttonsEnabled.set(false)
|
||||
let savedView: SavedView = {
|
||||
name: formValue.name,
|
||||
filter_rules: this.list.filterRules,
|
||||
@@ -502,8 +504,8 @@ export class DocumentListComponent
|
||||
if (error.filter_rules) {
|
||||
error.filter_rules = error.filter_rules.map((r) => r.value)
|
||||
}
|
||||
modal.componentInstance.error = error
|
||||
modal.componentInstance.buttonsEnabled = true
|
||||
modal.componentInstance.error.set(error)
|
||||
modal.componentInstance.buttonsEnabled.set(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -570,6 +572,7 @@ export class DocumentListComponent
|
||||
}
|
||||
|
||||
get notesEnabled(): boolean {
|
||||
this.settingsService.trackChanges()
|
||||
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="row flex-wrap row-gap-3" tourAnchor="tour.documents-filter-editor">
|
||||
<div class="col">
|
||||
<div class="form-inline d-flex align-items-center fade" [class.show]="show">
|
||||
<div class="form-inline d-flex align-items-center fade" [class.show]="show()">
|
||||
<div class="input-group input-group-sm flex-fill w-auto flex-nowrap">
|
||||
<div ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary" ngbDropdownToggle>{{textFilterTargetName}}</button>
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag) && tagSelectionModel.items.length > 0) {
|
||||
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Tags" icon="tag-fill" i18n-title
|
||||
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show()" title="Tags" icon="tag-fill" i18n-title
|
||||
filterPlaceholder="Filter tags" i18n-filterPlaceholder
|
||||
[(selectionModel)]="tagSelectionModel"
|
||||
(selectionModelChange)="updateRules()"
|
||||
@@ -47,7 +47,7 @@
|
||||
shortcutKey="t"></pngx-filterable-dropdown>
|
||||
}
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent) && correspondentSelectionModel.items.length > 0) {
|
||||
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Correspondent" icon="person-fill" i18n-title
|
||||
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show()" title="Correspondent" icon="person-fill" i18n-title
|
||||
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
||||
[(selectionModel)]="correspondentSelectionModel"
|
||||
(selectionModelChange)="updateRules()"
|
||||
@@ -58,7 +58,7 @@
|
||||
shortcutKey="y"></pngx-filterable-dropdown>
|
||||
}
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.DocumentType) && documentTypeSelectionModel.items.length > 0) {
|
||||
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Document type" icon="file-earmark-fill" i18n-title
|
||||
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show()" title="Document type" icon="file-earmark-fill" i18n-title
|
||||
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
||||
[(selectionModel)]="documentTypeSelectionModel"
|
||||
(selectionModelChange)="updateRules()"
|
||||
@@ -69,7 +69,7 @@
|
||||
shortcutKey="u"></pngx-filterable-dropdown>
|
||||
}
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.StoragePath) && storagePathSelectionModel.items.length > 0) {
|
||||
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Storage path" icon="folder-fill" i18n-title
|
||||
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show()" title="Storage path" icon="folder-fill" i18n-title
|
||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||
[(selectionModel)]="storagePathSelectionModel"
|
||||
(selectionModelChange)="updateRules()"
|
||||
@@ -81,12 +81,12 @@
|
||||
}
|
||||
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.CustomField) && customFields.length > 0) {
|
||||
<pngx-custom-fields-query-dropdown class="flex-fill fade" [class.show]="show" title="Custom fields" icon="ui-radios" i18n-title
|
||||
<pngx-custom-fields-query-dropdown class="flex-fill fade" [class.show]="show()" title="Custom fields" icon="ui-radios" i18n-title
|
||||
[(selectionModel)]="customFieldQueriesModel"
|
||||
(selectionModelChange)="updateRules()"
|
||||
></pngx-custom-fields-query-dropdown>
|
||||
}
|
||||
<pngx-dates-dropdown class="flex-fill fade" [class.show]="show"
|
||||
<pngx-dates-dropdown class="flex-fill fade" [class.show]="show()"
|
||||
title="Dates" i18n-title
|
||||
placement="bottom-end"
|
||||
(datesSet)="updateRules()"
|
||||
@@ -97,7 +97,7 @@
|
||||
[(addedDateFrom)]="dateAddedFrom"
|
||||
[(addedRelativeDate)]="dateAddedRelativeDate">
|
||||
</pngx-dates-dropdown>
|
||||
<pngx-permissions-filter-dropdown class="flex-fill fade" [class.show]="show"
|
||||
<pngx-permissions-filter-dropdown class="flex-fill fade" [class.show]="show()"
|
||||
title="Permissions" i18n-title
|
||||
(ownerFilterSet)="updateRules()"
|
||||
[(selectionModel)]="permissionsSelectionModel"></pngx-permissions-filter-dropdown>
|
||||
|
||||
+182
-192
@@ -4,12 +4,7 @@ import {
|
||||
HttpTestingController,
|
||||
provideHttpClientTesting,
|
||||
} from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { RouterModule } from '@angular/router'
|
||||
@@ -189,8 +184,13 @@ describe('FilterEditorComponent', () => {
|
||||
let httpTestingController: HttpTestingController
|
||||
let searchService: SearchService
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
const tick = (ms: number = 0) => {
|
||||
jest.advanceTimersByTime(ms)
|
||||
fixture.detectChanges()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterModule,
|
||||
NgbDropdownModule,
|
||||
@@ -260,7 +260,7 @@ describe('FilterEditorComponent', () => {
|
||||
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = users[0]
|
||||
settingsService.currentUser.set(users[0])
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
searchService = TestBed.inject(SearchService)
|
||||
jest
|
||||
@@ -275,8 +275,13 @@ describe('FilterEditorComponent', () => {
|
||||
component = fixture.componentInstance
|
||||
component.filterRules = []
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
}))
|
||||
await fixture.whenStable()
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('should not attempt to retrieve objects if user does not have permissions', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReset()
|
||||
@@ -298,7 +303,7 @@ describe('FilterEditorComponent', () => {
|
||||
|
||||
// SET filterRules
|
||||
|
||||
it('should ingest text filter rules for doc title', fakeAsync(() => {
|
||||
it('should ingest text filter rules for doc title', () => {
|
||||
expect(component.textFilter).toEqual(null)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -308,9 +313,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilter).toEqual('foo')
|
||||
expect(component.textFilterTarget).toEqual('title') // TEXT_FILTER_TARGET_TITLE
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for doc title + content', fakeAsync(() => {
|
||||
it('should ingest text filter rules for doc title + content', () => {
|
||||
expect(component.textFilter).toEqual(null)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -320,9 +325,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilter).toEqual('foo')
|
||||
expect(component.textFilterTarget).toEqual('title-content') // TEXT_FILTER_TARGET_TITLE_CONTENT
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest legacy text filter rules for doc title + content', fakeAsync(() => {
|
||||
it('should ingest legacy text filter rules for doc title + content', () => {
|
||||
expect(component.textFilter).toEqual(null)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -332,9 +337,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilter).toEqual('legacy foo')
|
||||
expect(component.textFilterTarget).toEqual('title-content') // TEXT_FILTER_TARGET_TITLE_CONTENT
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for doc asn', fakeAsync(() => {
|
||||
it('should ingest text filter rules for doc asn', () => {
|
||||
expect(component.textFilter).toEqual(null)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -344,9 +349,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilter).toEqual('foo')
|
||||
expect(component.textFilterTarget).toEqual('asn') // TEXT_FILTER_TARGET_ASN
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for custom fields', fakeAsync(() => {
|
||||
it('should ingest text filter rules for custom fields', () => {
|
||||
expect(component.textFilter).toEqual(null)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -356,9 +361,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilter).toEqual('foo')
|
||||
expect(component.textFilterTarget).toEqual('custom-fields') // TEXT_FILTER_TARGET_CUSTOM_FIELDS
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for doc asn is null', fakeAsync(() => {
|
||||
it('should ingest text filter rules for doc asn is null', () => {
|
||||
expect(component.textFilterTarget).toEqual('title-content')
|
||||
expect(component.textFilterModifier).toEqual('equals') // TEXT_FILTER_MODIFIER_EQUALS
|
||||
component.filterRules = [
|
||||
@@ -369,9 +374,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilterTarget).toEqual('asn') // TEXT_FILTER_TARGET_ASN
|
||||
expect(component.textFilterModifier).toEqual('is null') // TEXT_FILTER_MODIFIER_NULL
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for doc asn is not null', fakeAsync(() => {
|
||||
it('should ingest text filter rules for doc asn is not null', () => {
|
||||
expect(component.textFilterTarget).toEqual('title-content')
|
||||
expect(component.textFilterModifier).toEqual('equals') // TEXT_FILTER_MODIFIER_EQUALS
|
||||
component.filterRules = [
|
||||
@@ -382,9 +387,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilterTarget).toEqual('asn') // TEXT_FILTER_TARGET_ASN
|
||||
expect(component.textFilterModifier).toEqual('not null') // TEXT_FILTER_MODIFIER_NOTNULL
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for doc asn greater than', fakeAsync(() => {
|
||||
it('should ingest text filter rules for doc asn greater than', () => {
|
||||
expect(component.textFilterTarget).toEqual('title-content')
|
||||
expect(component.textFilterModifier).toEqual('equals') // TEXT_FILTER_MODIFIER_EQUALS
|
||||
component.filterRules = [
|
||||
@@ -395,9 +400,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilterTarget).toEqual('asn') // TEXT_FILTER_TARGET_ASN
|
||||
expect(component.textFilterModifier).toEqual('greater') // TEXT_FILTER_MODIFIER_GT
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for doc asn less than', fakeAsync(() => {
|
||||
it('should ingest text filter rules for doc asn less than', () => {
|
||||
expect(component.textFilterTarget).toEqual('title-content')
|
||||
expect(component.textFilterModifier).toEqual('equals') // TEXT_FILTER_MODIFIER_EQUALS
|
||||
component.filterRules = [
|
||||
@@ -408,9 +413,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilterTarget).toEqual('asn') // TEXT_FILTER_TARGET_ASN
|
||||
expect(component.textFilterModifier).toEqual('less') // TEXT_FILTER_MODIFIER_LT
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for mime type', fakeAsync(() => {
|
||||
it('should ingest text filter rules for mime type', () => {
|
||||
expect(component.textFilter).toEqual(null)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -420,9 +425,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilter).toEqual('pdf')
|
||||
expect(component.textFilterTarget).toEqual('mime-type') // TEXT_FILTER_TARGET_MIME_TYPE
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for fulltext query', fakeAsync(() => {
|
||||
it('should ingest text filter rules for fulltext query', () => {
|
||||
expect(component.textFilter).toEqual(null)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -432,9 +437,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.textFilter).toEqual('foo,bar')
|
||||
expect(component.textFilterTarget).toEqual('fulltext-query') // TEXT_FILTER_TARGET_FULLTEXT_QUERY
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for fulltext query that include date created', fakeAsync(() => {
|
||||
it('should ingest text filter rules for fulltext query that include date created', () => {
|
||||
expect(component.dateCreatedRelativeDate).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -444,9 +449,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.dateCreatedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||
expect(component.textFilter).toBeNull()
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for fulltext query that include date added', fakeAsync(() => {
|
||||
it('should ingest text filter rules for fulltext query that include date added', () => {
|
||||
expect(component.dateAddedRelativeDate).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -456,9 +461,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.dateAddedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||
expect(component.textFilter).toBeNull()
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter content with relative dates that are not in quick list', fakeAsync(() => {
|
||||
it('should ingest text filter content with relative dates that are not in quick list', () => {
|
||||
expect(component.dateAddedRelativeDate).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -478,9 +483,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.dateCreatedRelativeDate).toBeNull()
|
||||
expect(component.textFilter).toEqual('created:[-2 week to now]')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for more like', fakeAsync(() => {
|
||||
it('should ingest text filter rules for more like', () => {
|
||||
const moreLikeSpy = jest.spyOn(documentService, 'get')
|
||||
moreLikeSpy.mockReturnValue(of({ id: 1, title: 'Foo Bar' }))
|
||||
expect(component.textFilter).toEqual(null)
|
||||
@@ -500,9 +505,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '1',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for date created after and adjust date by 1 day', fakeAsync(() => {
|
||||
it('should ingest filter rules for date created after and adjust date by 1 day', () => {
|
||||
expect(component.dateCreatedFrom).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -511,9 +516,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.dateCreatedFrom).toEqual('2023-05-15')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for date created from', fakeAsync(() => {
|
||||
it('should ingest filter rules for date created from', () => {
|
||||
expect(component.dateCreatedFrom).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -522,9 +527,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.dateCreatedFrom).toEqual('2023-05-14')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for date created before and adjust date by 1 day', fakeAsync(() => {
|
||||
it('should ingest filter rules for date created before and adjust date by 1 day', () => {
|
||||
expect(component.dateCreatedTo).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -533,9 +538,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.dateCreatedTo).toEqual('2023-05-13')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for date created to', fakeAsync(() => {
|
||||
it('should ingest filter rules for date created to', () => {
|
||||
expect(component.dateCreatedTo).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -544,9 +549,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.dateCreatedTo).toEqual('2023-05-14')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for date added after and adjust date by 1 day', fakeAsync(() => {
|
||||
it('should ingest filter rules for date added after and adjust date by 1 day', () => {
|
||||
expect(component.dateAddedFrom).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -555,9 +560,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.dateAddedFrom).toEqual('2023-05-15')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for date added from', fakeAsync(() => {
|
||||
it('should ingest filter rules for date added from', () => {
|
||||
expect(component.dateAddedFrom).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -566,9 +571,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.dateAddedFrom).toEqual('2023-05-14')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for date added before and adjust date by 1 day', fakeAsync(() => {
|
||||
it('should ingest filter rules for date added before and adjust date by 1 day', () => {
|
||||
expect(component.dateAddedTo).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -577,9 +582,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.dateAddedTo).toEqual('2023-05-13')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for date added to', fakeAsync(() => {
|
||||
it('should ingest filter rules for date added to', () => {
|
||||
expect(component.dateAddedTo).toBeNull()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -588,9 +593,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.dateAddedTo).toEqual('2023-05-14')
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has all tags', fakeAsync(() => {
|
||||
it('should ingest filter rules for has all tags', () => {
|
||||
expect(component.tagSelectionModel.getSelectedItems()).toHaveLength(0)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -614,9 +619,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
component.toggleTag(2) // coverage
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has any tags', fakeAsync(() => {
|
||||
it('should ingest filter rules for has any tags', () => {
|
||||
expect(component.tagSelectionModel.getSelectedItems()).toHaveLength(0)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -639,9 +644,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has any tag', fakeAsync(() => {
|
||||
it('should ingest filter rules for has any tag', () => {
|
||||
expect(component.tagSelectionModel.getSelectedItems()).toHaveLength(0)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -651,9 +656,9 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
expect(component.tagSelectionModel.getSelectedItems()).toHaveLength(1)
|
||||
expect(component.tagSelectionModel.get(null)).toBeTruthy()
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for exclude tag(s)', fakeAsync(() => {
|
||||
it('should ingest filter rules for exclude tag(s)', () => {
|
||||
expect(component.tagSelectionModel.getExcludedItems()).toHaveLength(0)
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -676,9 +681,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has correspondent', fakeAsync(() => {
|
||||
it('should ingest filter rules for has correspondent', () => {
|
||||
expect(
|
||||
component.correspondentSelectionModel.getSelectedItems()
|
||||
).toHaveLength(0)
|
||||
@@ -708,9 +713,9 @@ describe('FilterEditorComponent', () => {
|
||||
expect(component.correspondentSelectionModel.getExcludedItems()).toEqual([
|
||||
{ id: NEGATIVE_NULL_FILTER_VALUE, name: 'Not assigned' },
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has any of correspondents', fakeAsync(() => {
|
||||
it('should ingest filter rules for has any of correspondents', () => {
|
||||
expect(
|
||||
component.correspondentSelectionModel.getSelectedItems()
|
||||
).toHaveLength(0)
|
||||
@@ -740,9 +745,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for does not have any of correspondents', fakeAsync(() => {
|
||||
it('should ingest filter rules for does not have any of correspondents', () => {
|
||||
expect(
|
||||
component.correspondentSelectionModel.getExcludedItems()
|
||||
).toHaveLength(0)
|
||||
@@ -769,9 +774,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has document type', fakeAsync(() => {
|
||||
it('should ingest filter rules for has document type', () => {
|
||||
expect(
|
||||
component.documentTypeSelectionModel.getSelectedItems()
|
||||
).toHaveLength(0)
|
||||
@@ -801,9 +806,9 @@ describe('FilterEditorComponent', () => {
|
||||
expect(component.documentTypeSelectionModel.getExcludedItems()).toEqual([
|
||||
{ id: NEGATIVE_NULL_FILTER_VALUE, name: 'Not assigned' },
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has any of document types', fakeAsync(() => {
|
||||
it('should ingest filter rules for has any of document types', () => {
|
||||
expect(
|
||||
component.documentTypeSelectionModel.getSelectedItems()
|
||||
).toHaveLength(0)
|
||||
@@ -830,9 +835,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for does not have any of document types', fakeAsync(() => {
|
||||
it('should ingest filter rules for does not have any of document types', () => {
|
||||
expect(
|
||||
component.documentTypeSelectionModel.getExcludedItems()
|
||||
).toHaveLength(0)
|
||||
@@ -859,9 +864,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has storage path', fakeAsync(() => {
|
||||
it('should ingest filter rules for has storage path', () => {
|
||||
expect(component.storagePathSelectionModel.getSelectedItems()).toHaveLength(
|
||||
0
|
||||
)
|
||||
@@ -891,9 +896,9 @@ describe('FilterEditorComponent', () => {
|
||||
expect(component.storagePathSelectionModel.getExcludedItems()).toEqual([
|
||||
{ id: NEGATIVE_NULL_FILTER_VALUE, name: 'Not assigned' },
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has any of storage paths', fakeAsync(() => {
|
||||
it('should ingest filter rules for has any of storage paths', () => {
|
||||
expect(component.storagePathSelectionModel.getSelectedItems()).toHaveLength(
|
||||
0
|
||||
)
|
||||
@@ -923,9 +928,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for does not have any of storage paths', fakeAsync(() => {
|
||||
it('should ingest filter rules for does not have any of storage paths', () => {
|
||||
expect(component.storagePathSelectionModel.getExcludedItems()).toHaveLength(
|
||||
0
|
||||
)
|
||||
@@ -952,9 +957,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for custom fields all', fakeAsync(() => {
|
||||
it('should ingest filter rules for custom fields all', () => {
|
||||
expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -972,9 +977,9 @@ describe('FilterEditorComponent', () => {
|
||||
.value[0] as CustomFieldQueryAtom
|
||||
).serialize()
|
||||
).toEqual(['42', CustomFieldQueryOperator.Exists, 'true'])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for has any custom fields', fakeAsync(() => {
|
||||
it('should ingest filter rules for has any custom fields', () => {
|
||||
expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -992,9 +997,9 @@ describe('FilterEditorComponent', () => {
|
||||
.value[0] as CustomFieldQueryAtom
|
||||
).serialize()
|
||||
).toEqual(['42', CustomFieldQueryOperator.Exists, 'true'])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for custom field queries', fakeAsync(() => {
|
||||
it('should ingest filter rules for custom field queries', () => {
|
||||
expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy()
|
||||
component.filterRules = [
|
||||
{
|
||||
@@ -1027,9 +1032,9 @@ describe('FilterEditorComponent', () => {
|
||||
.value[0] as CustomFieldQueryAtom
|
||||
).serialize()
|
||||
).toEqual([42, CustomFieldQueryOperator.Exists, 'true'])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for owner', fakeAsync(() => {
|
||||
it('should ingest filter rules for owner', () => {
|
||||
expect(component.permissionsSelectionModel.ownerFilter).toEqual(
|
||||
OwnerFilterType.NONE
|
||||
)
|
||||
@@ -1044,9 +1049,9 @@ describe('FilterEditorComponent', () => {
|
||||
)
|
||||
expect(component.permissionsSelectionModel.hideUnowned).toBeFalsy()
|
||||
expect(component.permissionsSelectionModel.userID).toEqual(100)
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for owner is others', fakeAsync(() => {
|
||||
it('should ingest filter rules for owner is others', () => {
|
||||
expect(component.permissionsSelectionModel.ownerFilter).toEqual(
|
||||
OwnerFilterType.NONE
|
||||
)
|
||||
@@ -1060,9 +1065,9 @@ describe('FilterEditorComponent', () => {
|
||||
OwnerFilterType.OTHERS
|
||||
)
|
||||
expect(component.permissionsSelectionModel.includeUsers).toContain(50)
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for owner does not include others', fakeAsync(() => {
|
||||
it('should ingest filter rules for owner does not include others', () => {
|
||||
expect(component.permissionsSelectionModel.ownerFilter).toEqual(
|
||||
OwnerFilterType.NONE
|
||||
)
|
||||
@@ -1076,9 +1081,9 @@ describe('FilterEditorComponent', () => {
|
||||
OwnerFilterType.NOT_SELF
|
||||
)
|
||||
expect(component.permissionsSelectionModel.excludeUsers).toContain(50)
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for owner is null', fakeAsync(() => {
|
||||
it('should ingest filter rules for owner is null', () => {
|
||||
expect(component.permissionsSelectionModel.ownerFilter).toEqual(
|
||||
OwnerFilterType.NONE
|
||||
)
|
||||
@@ -1092,9 +1097,9 @@ describe('FilterEditorComponent', () => {
|
||||
OwnerFilterType.UNOWNED
|
||||
)
|
||||
expect(component.permissionsSelectionModel.hideUnowned).toBeFalsy()
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for owner is not null', fakeAsync(() => {
|
||||
it('should ingest filter rules for owner is not null', () => {
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_OWNER_ISNULL,
|
||||
@@ -1109,9 +1114,9 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.permissionsSelectionModel.hideUnowned).toBeTruthy()
|
||||
}))
|
||||
})
|
||||
|
||||
it('should ingest filter rules for shared by me', fakeAsync(() => {
|
||||
it('should ingest filter rules for shared by me', () => {
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_SHARED_BY_USER,
|
||||
@@ -1119,11 +1124,11 @@ describe('FilterEditorComponent', () => {
|
||||
},
|
||||
]
|
||||
expect(component.permissionsSelectionModel.userID).toEqual(2)
|
||||
}))
|
||||
})
|
||||
|
||||
// GET filterRules
|
||||
|
||||
it('should convert user input to correct filter rules on text field search title + content', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on text field search title + content', () => {
|
||||
component.textFilterInput.nativeElement.value = 'foo'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
fixture.detectChanges()
|
||||
@@ -1135,9 +1140,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'foo',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on text field search title only', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on text field search title only', () => {
|
||||
component.textFilterInput.nativeElement.value = 'foo'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
const textFieldTargetDropdown = fixture.debugElement.query(
|
||||
@@ -1154,9 +1159,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'foo',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on text field search equals asn', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on text field search equals asn', () => {
|
||||
component.textFilterInput.nativeElement.value = '1234'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
const textFieldTargetDropdown = fixture.debugElement.queryAll(
|
||||
@@ -1174,9 +1179,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '1234',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on text field search greater than asn', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on text field search greater than asn', () => {
|
||||
component.textFilterInput.nativeElement.value = '123'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
const textFieldTargetDropdown = fixture.debugElement.queryAll(
|
||||
@@ -1198,9 +1203,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '123',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on text field search less than asn', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on text field search less than asn', () => {
|
||||
component.textFilterInput.nativeElement.value = '999'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
const textFieldTargetDropdown = fixture.debugElement.queryAll(
|
||||
@@ -1222,9 +1227,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '999',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on asn is null', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on asn is null', () => {
|
||||
const textFieldTargetDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(NgbDropdownItem)
|
||||
)[2]
|
||||
@@ -1242,9 +1247,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'true',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on asn is not null', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on asn is not null', () => {
|
||||
const textFieldTargetDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(NgbDropdownItem)
|
||||
)[2]
|
||||
@@ -1262,9 +1267,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'false',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on mime type', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on mime type', () => {
|
||||
component.textFilterInput.nativeElement.value = 'pdf'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
const textFieldTargetDropdown = fixture.debugElement.queryAll(
|
||||
@@ -1280,9 +1285,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'pdf',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on full text query', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on full text query', () => {
|
||||
component.textFilterInput.nativeElement.value = 'foo'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
const textFieldTargetDropdown = fixture.debugElement.queryAll(
|
||||
@@ -1298,9 +1303,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'foo',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on tag select not assigned', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on tag select not assigned', () => {
|
||||
const tagsFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[0]
|
||||
@@ -1317,9 +1322,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'false',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on tag selections', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on tag selections', () => {
|
||||
const tagsFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[0] // Tags dropdown
|
||||
@@ -1369,9 +1374,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: tags[1].id.toString(),
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on correspondent selections', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on correspondent selections', () => {
|
||||
const correspondentsFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[1] // Corresp dropdown
|
||||
@@ -1409,9 +1414,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: correspondents[1].id.toString(),
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on correspondent select not assigned', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on correspondent select not assigned', () => {
|
||||
const correspondentsFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[1]
|
||||
@@ -1441,9 +1446,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on document type selections', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on document type selections', () => {
|
||||
const documentTypesFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[2] // DocType dropdown
|
||||
@@ -1481,9 +1486,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: document_types[1].id.toString(),
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on doc type select not assigned', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on doc type select not assigned', () => {
|
||||
const docTypesFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[2]
|
||||
@@ -1513,9 +1518,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on storage path selections', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on storage path selections', () => {
|
||||
const storagePathFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[3] // StoragePath dropdown
|
||||
@@ -1553,9 +1558,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: storage_paths[1].id.toString(),
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on storage path select not assigned', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on storage path select not assigned', () => {
|
||||
const storagePathsFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[3]
|
||||
@@ -1585,9 +1590,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on custom field selections', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on custom field selections', () => {
|
||||
const customFieldsQueryDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(CustomFieldsQueryDropdownComponent)
|
||||
)[0]
|
||||
@@ -1617,9 +1622,9 @@ describe('FilterEditorComponent', () => {
|
||||
]),
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on date created from', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on date created from', () => {
|
||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)[0]
|
||||
@@ -1637,9 +1642,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '2023-05-14',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on date created to', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on date created to', () => {
|
||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)[0]
|
||||
@@ -1657,9 +1662,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '2023-05-14',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on date created with relative date', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on date created with relative date', () => {
|
||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)[0]
|
||||
@@ -1673,9 +1678,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'created:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should carry over text filtering on date created with relative date', fakeAsync(() => {
|
||||
it('should carry over text filtering on date created with relative date', () => {
|
||||
component.textFilter = 'foo'
|
||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(DatesDropdownComponent)
|
||||
@@ -1690,9 +1695,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'foo,created:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert legacy title filters into full text query when adding a created relative date', fakeAsync(() => {
|
||||
it('should convert legacy title filters into full text query when adding a created relative date', () => {
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_TITLE,
|
||||
@@ -1712,9 +1717,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'foo,created:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert simple title filters into full text query when adding a created relative date', fakeAsync(() => {
|
||||
it('should convert simple title filters into full text query when adding a created relative date', () => {
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_SIMPLE_TITLE,
|
||||
@@ -1734,9 +1739,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'foo,created:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should leave relative dates not in quick list intact', fakeAsync(() => {
|
||||
it('should leave relative dates not in quick list intact', () => {
|
||||
component.textFilterInput.nativeElement.value = 'created:[-2 week to now]'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
const textFieldTargetDropdown = fixture.debugElement.queryAll(
|
||||
@@ -1762,9 +1767,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'added:[-2 month to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on date added after', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on date added after', () => {
|
||||
const datesDropdown = fixture.debugElement.query(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)
|
||||
@@ -1782,9 +1787,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '2023-05-14',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on date added before', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on date added before', () => {
|
||||
const datesDropdown = fixture.debugElement.query(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)
|
||||
@@ -1802,9 +1807,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '2023-05-14',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter rules on date added with relative date', fakeAsync(() => {
|
||||
it('should convert user input to correct filter rules on date added with relative date', () => {
|
||||
const datesDropdown = fixture.debugElement.query(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)
|
||||
@@ -1818,9 +1823,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'added:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should carry over text filtering on date added with relative date', fakeAsync(() => {
|
||||
it('should carry over text filtering on date added with relative date', () => {
|
||||
component.textFilter = 'foo'
|
||||
const datesDropdown = fixture.debugElement.query(
|
||||
By.directive(DatesDropdownComponent)
|
||||
@@ -1835,9 +1840,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'foo,added:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter on permissions select my docs', fakeAsync(() => {
|
||||
it('should convert user input to correct filter on permissions select my docs', () => {
|
||||
const permissionsDropdown = fixture.debugElement.query(
|
||||
By.directive(PermissionsFilterDropdownComponent)
|
||||
)
|
||||
@@ -1851,24 +1856,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '1',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter on permissions select shared with me', fakeAsync(() => {
|
||||
const permissionsDropdown = fixture.debugElement.query(
|
||||
By.directive(PermissionsFilterDropdownComponent)
|
||||
)
|
||||
const sharedWithMe = permissionsDropdown.queryAll(By.css('button'))[3]
|
||||
sharedWithMe.triggerEventHandler('click')
|
||||
fixture.detectChanges()
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_OWNER_DOES_NOT_INCLUDE,
|
||||
value: '1',
|
||||
},
|
||||
])
|
||||
}))
|
||||
|
||||
it('should convert user input to correct filter on permissions select shared with me', fakeAsync(() => {
|
||||
it('should convert user input to correct filter on permissions select shared with me', () => {
|
||||
const permissionsDropdown = fixture.debugElement.query(
|
||||
By.directive(PermissionsFilterDropdownComponent)
|
||||
)
|
||||
@@ -1889,9 +1879,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '1,2',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter on permissions select shared by me', fakeAsync(() => {
|
||||
it('should convert user input to correct filter on permissions select shared by me', () => {
|
||||
const permissionsDropdown = fixture.debugElement.query(
|
||||
By.directive(PermissionsFilterDropdownComponent)
|
||||
)
|
||||
@@ -1904,9 +1894,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '1',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter on permissions select unowned', fakeAsync(() => {
|
||||
it('should convert user input to correct filter on permissions select unowned', () => {
|
||||
const permissionsDropdown = fixture.debugElement.query(
|
||||
By.directive(PermissionsFilterDropdownComponent)
|
||||
)
|
||||
@@ -1919,9 +1909,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'true',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter on permissions select others', fakeAsync(() => {
|
||||
it('should convert user input to correct filter on permissions select others', () => {
|
||||
const permissionsDropdown = fixture.debugElement.query(
|
||||
By.directive(PermissionsFilterDropdownComponent)
|
||||
)
|
||||
@@ -1940,9 +1930,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: '3',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should convert user input to correct filter on permissions hide unowned', fakeAsync(() => {
|
||||
it('should convert user input to correct filter on permissions hide unowned', () => {
|
||||
const permissionsDropdown = fixture.debugElement.query(
|
||||
By.directive(PermissionsFilterDropdownComponent)
|
||||
)
|
||||
@@ -1960,7 +1950,7 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'false',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
// The rest
|
||||
|
||||
@@ -2221,7 +2211,7 @@ describe('FilterEditorComponent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should keep deprecated custom fields target available for legacy filters', fakeAsync(() => {
|
||||
it('should keep deprecated custom fields target available for legacy filters', () => {
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_CUSTOM_FIELDS_TEXT,
|
||||
@@ -2242,9 +2232,9 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'foo',
|
||||
},
|
||||
])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should call autocomplete endpoint on input', fakeAsync(() => {
|
||||
it('should call autocomplete endpoint on input', () => {
|
||||
component.textFilterTarget = 'fulltext-query' // TEXT_FILTER_TARGET_FULLTEXT_QUERY
|
||||
const autocompleteSpy = jest.spyOn(searchService, 'autocomplete')
|
||||
component.searchAutoComplete(of('hello')).subscribe()
|
||||
@@ -2254,9 +2244,9 @@ describe('FilterEditorComponent', () => {
|
||||
component.searchAutoComplete(of('hello world 1')).subscribe()
|
||||
tick(250)
|
||||
expect(autocompleteSpy).toHaveBeenCalled()
|
||||
}))
|
||||
})
|
||||
|
||||
it('should handle autocomplete backend failure gracefully', fakeAsync(() => {
|
||||
it('should handle autocomplete backend failure gracefully', () => {
|
||||
component.textFilterTarget = 'fulltext-query' // TEXT_FILTER_TARGET_FULLTEXT_QUERY
|
||||
const serviceAutocompleteSpy = jest.spyOn(searchService, 'autocomplete')
|
||||
serviceAutocompleteSpy.mockReturnValue(
|
||||
@@ -2270,7 +2260,7 @@ describe('FilterEditorComponent', () => {
|
||||
tick(250)
|
||||
expect(serviceAutocompleteSpy).toHaveBeenCalled()
|
||||
expect(result).toEqual([])
|
||||
}))
|
||||
})
|
||||
|
||||
it('should support choosing a autocomplete item', () => {
|
||||
expect(component.textFilter).toBeNull()
|
||||
|
||||
@@ -1166,13 +1166,13 @@ export class FilterEditorComponent
|
||||
private maybeCompleteLoading() {
|
||||
this.loadingCount++
|
||||
if (this.loadingCount == this.loadingCountTotal) {
|
||||
this.loading = false
|
||||
this.show = true
|
||||
this.loading.set(false)
|
||||
this.show.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
if (
|
||||
this.permissionsService.currentUserCan(
|
||||
PermissionAction.View,
|
||||
|
||||
+6
-6
@@ -1,24 +1,24 @@
|
||||
<form [formGroup]="saveViewConfigForm" (ngSubmit)="save()">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title" i18n>Save current view</h4>
|
||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
<button type="button" [disabled]="!closeEnabled()" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error()?.name" autocomplete="off"></pngx-input-text>
|
||||
<pngx-input-check i18n-title title="Show in sidebar" formControlName="showInSideBar"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Show on dashboard" formControlName="showOnDashboard"></pngx-input-check>
|
||||
<pngx-permissions-form accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||
@if (error?.filter_rules) {
|
||||
@if (error()?.filter_rules) {
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h6 class="alert-heading" i18n>Filter rules error occurred while saving this view</h6>
|
||||
<ng-container i18n>The error returned was</ng-container>:<br/>
|
||||
{{ error.filter_rules }}
|
||||
{{ error().filter_rules }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="!buttonsEnabled">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="!buttonsEnabled">Save</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="!buttonsEnabled()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="!buttonsEnabled()">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+7
-12
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { NgbActiveModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -22,8 +17,8 @@ describe('SaveViewConfigDialogComponent', () => {
|
||||
let fixture: ComponentFixture<SaveViewConfigDialogComponent>
|
||||
let modal: NgbActiveModal
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
@@ -56,16 +51,16 @@ describe('SaveViewConfigDialogComponent', () => {
|
||||
fixture = TestBed.createComponent(SaveViewConfigDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
}))
|
||||
await fixture.whenStable()
|
||||
})
|
||||
|
||||
it('should support default name', () => {
|
||||
const name = 'Tag: Inbox'
|
||||
let result
|
||||
component.saveClicked.subscribe((saveResult) => (result = saveResult))
|
||||
component.defaultName = name
|
||||
component.setDefaultName(name)
|
||||
component.save()
|
||||
expect(component.defaultName).toEqual(name)
|
||||
expect(component.defaultName()).toEqual(name)
|
||||
expect(result).toEqual({
|
||||
name,
|
||||
showInSideBar: false,
|
||||
|
||||
+8
-19
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import {
|
||||
FormControl,
|
||||
@@ -32,29 +32,18 @@ import { TextComponent } from '../../common/input/text/text.component'
|
||||
})
|
||||
export class SaveViewConfigDialogComponent implements OnInit {
|
||||
private modal = inject(NgbActiveModal)
|
||||
readonly error = signal(undefined)
|
||||
readonly buttonsEnabled = signal(true)
|
||||
readonly defaultName = signal('')
|
||||
readonly closeEnabled = signal(false)
|
||||
|
||||
@Output()
|
||||
public saveClicked = new EventEmitter()
|
||||
|
||||
@Input()
|
||||
error
|
||||
|
||||
@Input()
|
||||
buttonsEnabled = true
|
||||
|
||||
closeEnabled = false
|
||||
|
||||
users: User[]
|
||||
|
||||
_defaultName = ''
|
||||
|
||||
get defaultName() {
|
||||
return this._defaultName
|
||||
}
|
||||
|
||||
@Input()
|
||||
set defaultName(value: string) {
|
||||
this._defaultName = value
|
||||
setDefaultName(value: string) {
|
||||
this.defaultName.set(value)
|
||||
this.saveViewConfigForm.patchValue({ name: value })
|
||||
}
|
||||
|
||||
@@ -68,7 +57,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
// wait to enable close button so it doesn't steal focus from input since its the first clickable element in the DOM
|
||||
setTimeout(() => {
|
||||
this.closeEnabled = true
|
||||
this.closeEnabled.set(true)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user