Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/app/core/services/edit/edit.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,52 @@ describe('EditService', () => {
expect(apiService.record.update).toHaveBeenCalled();
expect(recordMock.update).not.toHaveBeenCalled();
});

it('should call refreshAccountDebounced and refreshCurrentFolder after successful deletion', async () => {
const record = new RecordVO({ recordId: 1 });
(apiService as any).record.delete = jasmine
.createSpy('delete')
.and.returnValue(Promise.resolve());
accountService.refreshAccountDebounced = Object.assign(
jasmine.createSpy('refreshAccountDebounced'),
{
cancel: jasmine.createSpy('cancel'),
flush: jasmine.createSpy('flush'),
},
) as any;
(mockDataService as any).refreshCurrentFolder = jasmine.createSpy(
'refreshCurrentFolder',
);

await service.deleteItems([record]);

expect(accountService.refreshAccountDebounced).toHaveBeenCalled();
expect(mockDataService.refreshCurrentFolder).toHaveBeenCalled();
});

it('should call refreshAccountDebounced and refreshCurrentFolder even when deletion fails', async () => {
const record = new RecordVO({ recordId: 1 });
(apiService as any).record.delete = jasmine
.createSpy('delete')
.and.returnValue(Promise.reject(new Error('API error')));
accountService.refreshAccountDebounced = Object.assign(
jasmine.createSpy('refreshAccountDebounced'),
{
cancel: jasmine.createSpy('cancel'),
flush: jasmine.createSpy('flush'),
},
) as any;
(mockDataService as any).refreshCurrentFolder = jasmine.createSpy(
'refreshCurrentFolder',
);

try {
await service.deleteItems([record]);
} catch (e) {
// the try will fail, because we are testing the finally block
}

expect(accountService.refreshAccountDebounced).toHaveBeenCalled();
expect(mockDataService.refreshCurrentFolder).toHaveBeenCalled();
});
});
1 change: 1 addition & 0 deletions src/app/core/services/edit/edit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ export class EditService {
throw err;
} finally {
this.accountService.refreshAccountDebounced();
this.dataService.refreshCurrentFolder();
}
}

Expand Down
263 changes: 262 additions & 1 deletion src/app/file-browser/components/edit-tags/edit-tags.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { of, Subject } from 'rxjs';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { DialogRef, DIALOG_DATA } from '@angular/cdk/dialog';

import { ItemVO, TagVOData, RecordVO } from '@models';
import { ApiService } from '@shared/services/api/api.service';
Expand Down Expand Up @@ -291,4 +292,264 @@ describe('EditTagsComponent', () => {

expect(dialogCdkServiceSpy.open).toHaveBeenCalled();
});

describe('checkItemTags allTags filtering', () => {
it('should exclude item tags that do not exist in allTags', () => {
const item = new RecordVO({
TagVOs: [
{ tagId: 1, name: 'tagOne', type: 'type.generic.placeholder' },
{ tagId: 99, name: 'deletedTag', type: 'type.generic.placeholder' },
],
});
setupComponent(item, 'keyword');

expect(component.itemTags.find((t) => t.name === 'tagOne')).toBeTruthy();

expect(
component.itemTags.find((t) => t.name === 'deletedTag'),
).toBeFalsy();
});

it('should include item tags that exist in allTags', () => {
const item = new RecordVO({
TagVOs: [
{ tagId: 1, name: 'tagOne', type: 'type.generic.placeholder' },
{ tagId: 2, name: 'tagTwo', type: 'type.generic.placeholder' },
],
});
setupComponent(item, 'keyword');

expect(component.itemTags.length).toBe(2);
expect(component.itemTags.find((t) => t.name === 'tagOne')).toBeTruthy();

expect(component.itemTags.find((t) => t.name === 'tagTwo')).toBeTruthy();
});

it('should return no item tags when allTags is empty', () => {
TestBed.resetTestingModule();
TestBed.configureTestingModule({
declarations: [EditTagsComponent],
imports: [NoopAnimationsModule, FormsModule],
providers: [
{
provide: SearchService,
useValue: { getTagResults: () => [] },
},
{
provide: TagsService,
useValue: {
getTags: () => [],
getTags$: () => of([]),
setItemTags: () => {},
getItemTags$: () => of([]),
},
},
{
provide: MessageService,
useValue: { showError: () => {} },
},
{
provide: ApiService,
useValue: {
tag: {
deleteTagLink: async () =>
await Promise.resolve(new TagResponse()),
create: async () => await Promise.resolve(new TagResponse()),
},
},
},
{
provide: DataService,
useValue: {
currentFolderChange: of(null),
fetchFullItems: async () => await Promise.resolve([]),
},
},
{
provide: DialogCdkService,
useValue: dialogCdkServiceSpy,
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();

fixture = TestBed.createComponent(EditTagsComponent);
component = fixture.componentInstance;

const item = new RecordVO({
TagVOs: [
{ tagId: 1, name: 'tagOne', type: 'type.generic.placeholder' },
],
});
component.item = item;
component.tagType = 'keyword';
fixture.detectChanges();

expect(component.itemTags.length).toBe(0);
});

it('should exclude deleted custom metadata tags not in allTags', () => {
const item = new RecordVO({
TagVOs: [
{
tagId: 3,
name: 'customField:customValueOne',
type: 'type.tag.metadata.customField',
},
{
tagId: 99,
name: 'deletedField:deletedValue',
type: 'type.tag.metadata.deletedField',
},
],
});
setupComponent(item, 'customMetadata');

expect(
component.itemTags.find((t) => t.name === 'customField:customValueOne'),
).toBeTruthy();

expect(
component.itemTags.find((t) => t.name === 'deletedField:deletedValue'),
).toBeFalsy();
});

it('should only add filtered item tags to itemTagsById', () => {
const item = new RecordVO({
TagVOs: [
{ tagId: 1, name: 'tagOne', type: 'type.generic.placeholder' },
{ tagId: 99, name: 'deletedTag', type: 'type.generic.placeholder' },
],
});
setupComponent(item, 'keyword');

expect(component.itemTagsById.has(1)).toBeTrue();
expect(component.itemTagsById.has(99)).toBeFalse();
});
});

describe('dialog keyword filtering against allTags', () => {
let tagsSubject: Subject<TagVOData[]>;
let itemTagsSubject: Subject<TagVOData[]>;

function setupDialogComponent() {
tagsSubject = new Subject<TagVOData[]>();
itemTagsSubject = new Subject<TagVOData[]>();

TestBed.resetTestingModule();
TestBed.configureTestingModule({
declarations: [EditTagsComponent],
imports: [NoopAnimationsModule, FormsModule],
providers: [
{
provide: SearchService,
useValue: { getTagResults: () => defaultTagList },
},
{
provide: TagsService,
useValue: {
getTags: () => defaultTagList,
getTags$: () => tagsSubject.asObservable(),
setItemTags: () => {},
getItemTags$: () => itemTagsSubject.asObservable(),
},
},
{
provide: MessageService,
useValue: { showError: () => {} },
},
{
provide: ApiService,
useValue: {
tag: {
deleteTagLink: async () =>
await Promise.resolve(new TagResponse()),
create: async () => await Promise.resolve(new TagResponse()),
},
},
},
{
provide: DataService,
useValue: {
currentFolderChange: of(null),
fetchFullItems: async () => await Promise.resolve([]),
},
},
{
provide: DialogCdkService,
useValue: dialogCdkServiceSpy,
},
{
provide: DIALOG_DATA,
useValue: { item: defaultItem, type: 'keyword' },
},
{
provide: DialogRef,
useValue: { close: () => {} },
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();

fixture = TestBed.createComponent(EditTagsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}

it('should only include keywords that exist in allTags in dialogTags', () => {
setupDialogComponent();

itemTagsSubject.next([
{ tagId: 1, name: 'tagOne', type: 'type.generic.placeholder' },
{ tagId: 99, name: 'orphanTag', type: 'type.generic.placeholder' },
]);

expect(component.dialogTags.length).toBe(1);
expect(component.dialogTags[0].name).toBe('tagOne');
});

it('should exclude metadata tags from keyword dialogTags', () => {
setupDialogComponent();

itemTagsSubject.next([
{ tagId: 1, name: 'tagOne', type: 'type.generic.placeholder' },
{
tagId: 3,
name: 'customField:customValueOne',
type: 'type.tag.metadata.customField',
},
]);

expect(component.dialogTags.length).toBe(1);
expect(component.dialogTags[0].name).toBe('tagOne');
});

it('should show no keyword dialogTags when allTags is empty', () => {
setupDialogComponent();

component.allTags = [];

itemTagsSubject.next([
{ tagId: 1, name: 'tagOne', type: 'type.generic.placeholder' },
]);

expect(component.dialogTags.length).toBe(0);
});

it('should update dialogTags when allTags changes via getTags$', () => {
setupDialogComponent();

tagsSubject.next([
{ tagId: 10, name: 'newTag', type: 'type.generic.placeholder' },
]);

itemTagsSubject.next([
{ tagId: 10, name: 'newTag', type: 'type.generic.placeholder' },
{ tagId: 1, name: 'tagOne', type: 'type.generic.placeholder' },
]);

expect(component.dialogTags.length).toBe(1);
expect(component.dialogTags[0].name).toBe('newTag');
});
});
});
11 changes: 8 additions & 3 deletions src/app/file-browser/components/edit-tags/edit-tags.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@ export class EditTagsComponent
.subscribe((tags) => {
if (this.tagType === 'keyword') {
this.dialogTags = tags?.filter(
(tag) => !tag.type.includes('type.tag.metadata'),
(tag) =>
!tag.type.includes('type.tag.metadata') &&
this.allTags.find(
(generalTag) => generalTag?.name === tag?.name,
),
);
} else {
this.dialogTags = tags?.filter((tag) =>
Expand Down Expand Up @@ -233,7 +237,8 @@ export class EditTagsComponent
this.itemTags = this.filterTagsByType(
(this.item?.TagVOs || []).filter(
// Filter out tags that are now null from deletion
(tag) => tag?.name,
(tag) =>
!!this.allTags?.find((genericTag) => genericTag?.name === tag?.name),
),
);

Expand All @@ -244,7 +249,7 @@ export class EditTagsComponent
for (const tag of this.itemTags) {
this.itemTagsById.add(tag.tagId);
}
this.tagsService.setItemTags(this.item.TagVOs);
this.tagsService.setItemTags(this.item?.TagVOs);
}

onManageTagsClick() {
Expand Down
Loading