Enhancement (QoL): support deselecting single items from "select all" (#14117)

This commit is contained in:
shamoon
2026-09-18 01:28:55 +00:00
committed by GitHub
parent d80997a070
commit f4ff87e69b
9 changed files with 350 additions and 33 deletions
@@ -9,7 +9,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { Router } from '@angular/router'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { Correspondent } from 'src/app/data/correspondent'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
@@ -392,6 +392,42 @@ describe('BulkEditorComponent', () => {
expect(component.tagSelectionModel.selectionSize()).toEqual(1)
})
it('should request selection data for tags when documents are excluded from an all-filtered selection', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
jest
.spyOn(documentListViewService, 'allSelected', 'get')
.mockReturnValue(true)
jest
.spyOn(documentListViewService, 'excluded', 'get')
.mockReturnValue(new Set([4]))
jest
.spyOn(documentListViewService, 'filterRules', 'get')
.mockReturnValue([{ rule_type: FILTER_TITLE, value: 'apple' }])
jest
.spyOn(documentListViewService, 'selectedCount', 'get')
.mockReturnValue(2)
const adjustedSelectionData: SelectionData = {
...selectionData,
selected_tags: [{ id: 12, document_count: 2 }],
}
const getSelectionDataSpy = jest
.spyOn(documentService, 'getSelectionData')
.mockReturnValue(of(adjustedSelectionData))
component.openTagsDropdown()
expect(getSelectionDataSpy).toHaveBeenCalledWith({
all: true,
filters: { title_search: 'apple' },
excluded_documents: [4],
})
expect(component.tagDocumentCounts()).toEqual(
adjustedSelectionData.selected_tags
)
expect(component.tagSelectionModel.selectionSize()).toEqual(1)
})
it('should apply list selection data to document types menu when all filtered documents are selected', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
@@ -460,6 +496,47 @@ describe('BulkEditorComponent', () => {
)
})
it('should request selection data for the other metadata menus when documents are excluded', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
jest
.spyOn(documentListViewService, 'allSelected', 'get')
.mockReturnValue(true)
jest
.spyOn(documentListViewService, 'excluded', 'get')
.mockReturnValue(new Set([4]))
jest
.spyOn(documentListViewService, 'filterRules', 'get')
.mockReturnValue([{ rule_type: FILTER_TITLE, value: 'apple' }])
const getSelectionDataSpy = jest
.spyOn(documentService, 'getSelectionData')
.mockReturnValue(of(selectionData))
component.openDocumentTypeDropdown()
component.openCorrespondentDropdown()
component.openStoragePathDropdown()
component.openCustomFieldsDropdown()
expect(getSelectionDataSpy).toHaveBeenCalledTimes(4)
expect(getSelectionDataSpy).toHaveBeenCalledWith({
all: true,
filters: { title_search: 'apple' },
excluded_documents: [4],
})
expect(component.documentTypeDocumentCounts()).toEqual(
selectionData.selected_document_types
)
expect(component.correspondentDocumentCounts()).toEqual(
selectionData.selected_correspondents
)
expect(component.storagePathDocumentCounts()).toEqual(
selectionData.selected_storage_paths
)
expect(component.customFieldDocumentCounts()).toEqual(
selectionData.selected_custom_fields
)
})
it('should execute modify tags bulk operation', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
@@ -501,16 +578,19 @@ describe('BulkEditorComponent', () => {
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
.mockReturnValue(new Set([3]))
jest
.spyOn(documentListViewService, 'allSelected', 'get')
.mockReturnValue(true)
jest
.spyOn(documentListViewService, 'excluded', 'get')
.mockReturnValue(new Set([4]))
jest
.spyOn(documentListViewService, 'filterRules', 'get')
.mockReturnValue([{ rule_type: FILTER_TITLE, value: 'apple' }])
jest
.spyOn(documentListViewService, 'selectedCount', 'get')
.mockReturnValue(25)
.mockReturnValue(24)
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
@@ -529,6 +609,7 @@ describe('BulkEditorComponent', () => {
expect(req.request.body).toEqual({
all: true,
filters: { title_search: 'apple' },
excluded_documents: [4],
method: 'modify_tags',
parameters: { add_tags: [101], remove_tags: [] },
})
@@ -361,6 +361,7 @@ export class BulkEditorComponent
return {
all: true,
filters: queryParamsFromFilterRules(this.list.filterRules),
excluded_documents: Array.from(this.list.excluded),
}
}
@@ -374,7 +375,8 @@ export class BulkEditorComponent
}
openTagsDropdown() {
if (this.list.allSelected) {
// If none excluded, use the selection data already available in the list view, otherwise fetch
if (this.list.allSelected && this.list.excluded.size === 0) {
const selectionData = this.list.selectionData
this.tagDocumentCounts.set(selectionData?.selected_tags ?? [])
this.applySelectionData(this.tagDocumentCounts(), this.tagSelectionModel)
@@ -382,7 +384,7 @@ export class BulkEditorComponent
}
this.documentService
.getSelectionData(Array.from(this.list.selected))
.getSelectionData(this.getSelectionQuery())
.pipe(first())
.subscribe((s) => {
this.tagDocumentCounts.set(s.selected_tags)
@@ -391,7 +393,7 @@ export class BulkEditorComponent
}
openDocumentTypeDropdown() {
if (this.list.allSelected) {
if (this.list.allSelected && this.list.excluded.size === 0) {
const selectionData = this.list.selectionData
this.documentTypeDocumentCounts.set(
selectionData?.selected_document_types ?? []
@@ -404,7 +406,7 @@ export class BulkEditorComponent
}
this.documentService
.getSelectionData(Array.from(this.list.selected))
.getSelectionData(this.getSelectionQuery())
.pipe(first())
.subscribe((s) => {
this.documentTypeDocumentCounts.set(s.selected_document_types)
@@ -416,7 +418,7 @@ export class BulkEditorComponent
}
openCorrespondentDropdown() {
if (this.list.allSelected) {
if (this.list.allSelected && this.list.excluded.size === 0) {
const selectionData = this.list.selectionData
this.correspondentDocumentCounts.set(
selectionData?.selected_correspondents ?? []
@@ -429,7 +431,7 @@ export class BulkEditorComponent
}
this.documentService
.getSelectionData(Array.from(this.list.selected))
.getSelectionData(this.getSelectionQuery())
.pipe(first())
.subscribe((s) => {
this.correspondentDocumentCounts.set(s.selected_correspondents)
@@ -441,7 +443,7 @@ export class BulkEditorComponent
}
openStoragePathDropdown() {
if (this.list.allSelected) {
if (this.list.allSelected && this.list.excluded.size === 0) {
const selectionData = this.list.selectionData
this.storagePathDocumentCounts.set(
selectionData?.selected_storage_paths ?? []
@@ -454,7 +456,7 @@ export class BulkEditorComponent
}
this.documentService
.getSelectionData(Array.from(this.list.selected))
.getSelectionData(this.getSelectionQuery())
.pipe(first())
.subscribe((s) => {
this.storagePathDocumentCounts.set(s.selected_storage_paths)
@@ -466,7 +468,7 @@ export class BulkEditorComponent
}
openCustomFieldsDropdown() {
if (this.list.allSelected) {
if (this.list.allSelected && this.list.excluded.size === 0) {
const selectionData = this.list.selectionData
this.customFieldDocumentCounts.set(
selectionData?.selected_custom_fields ?? []
@@ -479,7 +481,7 @@ export class BulkEditorComponent
}
this.documentService
.getSelectionData(Array.from(this.list.selected))
.getSelectionData(this.getSelectionQuery())
.pipe(first())
.subscribe((s) => {
this.customFieldDocumentCounts.set(s.selected_custom_fields)