Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,45 @@ import { cloneDeep, some } from 'lodash';

import { DataService } from '@shared/services/data/data.service';
import { ApiService } from '@shared/services/api/api.service';
import { MessageService } from '@shared/services/message/message.service';
import { FolderResponse } from '@shared/services/api/index.repo';
import { SharedModule } from '@shared/shared.module';
import { FolderVO } from '@root/app/models';
import { HttpTestingController } from '@angular/common/http/testing';
import { FolderPickerService } from '@core/services/folder-picker/folder-picker.service';
import { DataStatus } from '@models/data-status.enum';
import { of } from 'rxjs';
import { FolderPickerComponent } from './folder-picker.component';

const buildRootFolderResponse = () =>
new FolderResponse({
isSuccessful: true,
Results: [
{
data: [
{
FolderVO: {
folderId: 1,
folder_linkId: 10,
type: 'type.folder.root.root',
displayName: 'Root',
ChildItemVOs: [
{ folder_linkId: 11, type: 'type.folder.root.private' },
{ folder_linkId: 12, type: 'type.folder.root.app' },
{ folder_linkId: 13, type: 'type.folder.root.vault' },
],
},
},
],
},
],
});

const buildErrorFolderResponse = (errorMessage: string) => {
const errorResponse = new FolderResponse();
errorResponse.Results = [{ message: [errorMessage] }];
return errorResponse;
};

describe('FolderPickerComponent', () => {
let component: FolderPickerComponent;
let fixture: ComponentFixture<FolderPickerComponent>;
Expand Down Expand Up @@ -49,13 +79,16 @@ describe('FolderPickerComponent', () => {
const navigateMinExpected = require('@root/test/responses/folder.navigateMin.myFiles.success.json');
const myFiles = new FolderResponse(navigateMinExpected).getFolderVO();

spyOn(api.folder, 'navigate').and.returnValue(
of(new FolderResponse(navigateMinExpected)),
);
// myFiles is type.folder.root.private (not root.root), so setFolder
// takes the getWithChildren branch.
const getWithChildrenSpy = spyOn(
api.folder,
'getWithChildren',
).and.resolveTo(new FolderResponse(navigateMinExpected));

await component.setFolder(myFiles);

expect(api.folder.navigate).toHaveBeenCalledTimes(1);
expect(getWithChildrenSpy).toHaveBeenCalledTimes(1);
expect(component.currentFolder).toBeTruthy();
expect(component.currentFolder.folder_linkId).toEqual(
myFiles.folder_linkId,
Expand All @@ -64,9 +97,7 @@ describe('FolderPickerComponent', () => {
expect(some(component.currentFolder.ChildItemVOs, 'isRecord')).toBeFalsy();

const getLeanItemsExpected = require('@root/test/responses/folder.getLeanItems.folderPicker.myFiles.success.json');
spyOn(api.folder, 'getWithChildren').and.returnValue(
Promise.resolve(new FolderResponse(getLeanItemsExpected)),
);
getWithChildrenSpy.and.resolveTo(new FolderResponse(getLeanItemsExpected));

await component.loadCurrentFolderChildData();

Expand All @@ -80,4 +111,50 @@ describe('FolderPickerComponent', () => {
),
).toBeFalsy();
});

it('should load the root folder via getRoot and strip app and vault folders', async () => {
const api = TestBed.inject(ApiService) as ApiService;
const getRootSpy = spyOn(api.folder, 'getRoot').and.resolveTo(
buildRootFolderResponse(),
);
const getWithChildrenSpy = spyOn(api.folder, 'getWithChildren');
const rootFolder = buildRootFolderResponse().getFolderVO();

await component.setFolder(rootFolder);

expect(getRootSpy).toHaveBeenCalledTimes(1);
expect(getWithChildrenSpy).not.toHaveBeenCalled();
expect(component.isRootFolder).toBeTrue();
expect(
some(component.currentFolder.ChildItemVOs, (item) =>
item.type.includes('type.folder.root.app'),
),
).toBeFalse();

expect(
some(component.currentFolder.ChildItemVOs, (item) =>
item.type.includes('type.folder.root.vault'),
),
).toBeFalse();
});

it('should show an error and leave the current folder unchanged when the response is unsuccessful', async () => {
const api = TestBed.inject(ApiService) as ApiService;
const message = TestBed.inject(MessageService) as MessageService;
spyOn(api.folder, 'getWithChildren').and.resolveTo(
buildErrorFolderResponse('Folder not found'),
);
const showErrorSpy = spyOn(message, 'showError');

await component.setFolder(
new FolderVO({ folderId: 42, type: 'type.folder.private' }),
);

expect(showErrorSpy).toHaveBeenCalledWith({
message: 'Folder not found',
translate: true,
});

expect(component.currentFolder).toBeFalsy();
});
});
38 changes: 29 additions & 9 deletions src/app/core/components/folder-picker/folder-picker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FolderResponse } from '@shared/services/api/index.repo';
import { FolderPickerService } from '@core/services/folder-picker/folder-picker.service';
import { MessageService } from '@shared/services/message/message.service';
import { PromptService } from '@shared/services/prompt/prompt.service';
import { AccountService } from '@shared/services/account/account.service';

export enum FolderPickerOperations {
Move = 1,
Expand Down Expand Up @@ -46,6 +47,7 @@ export class FolderPickerComponent implements OnDestroy {
private message: MessageService,
private folderPickerService: FolderPickerService,
private prompt: PromptService,
private accountService: AccountService,
) {
this.folderPickerService.registerComponent(this);
}
Expand Down Expand Up @@ -116,15 +118,33 @@ export class FolderPickerComponent implements OnDestroy {
async setFolder(folder: FolderVO) {
this.waiting = true;
try {
const folderResponse = await this.api.folder
.navigate(
new FolderVO({
folder_linkId: folder.folder_linkId,
folderId: folder.folderId,
archiveNbr: folder.archiveNbr,
}),
)
.toPromise();
const rootFolder = this.accountService.getRootFolder();
// goToParentFolder reconstructs the root folder with only its ids
// (no type), so detect root by type or by identity against the cache.
const isRoot =
folder.type?.includes('type.folder.root.root') ||
(rootFolder &&
(folder.folderId === rootFolder.folderId ||
folder.folder_linkId === rootFolder.folder_linkId));

// The root.root folder is a virtual folder that Stela's
// getWithChildren does not serve, so keep loading it through getRoot.
const folderResponse: FolderResponse = isRoot
? await this.api.folder.getRoot()
: await this.api.folder.getWithChildren([
new FolderVO({
folder_linkId: folder.folder_linkId,
folderId: folder.folderId,
archiveNbr: folder.archiveNbr,
}),
]);

// getWithChildren resolves an error-shaped response instead of
// rejecting, so surface failures explicitly for the catch below.
if (!folderResponse.isSuccessful) {
throw folderResponse;
}

this.currentFolder = folderResponse.getFolderVO(true);
this.isRootFolder = this.currentFolder.type.includes(
'type.folder.root.root',
Expand Down
21 changes: 1 addition & 20 deletions src/app/shared/services/api/folder.repo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FolderVO, FolderVOData, ItemVO } from '@root/app/models';
import { BaseResponse, BaseRepo } from '@shared/services/api/base';
import { firstValueFrom, Observable } from 'rxjs';
import { firstValueFrom } from 'rxjs';
import { DataStatus } from '@models/data-status.enum';
import { ShareLink } from '@root/app/share-links/models/share-link';
import {
Expand Down Expand Up @@ -388,25 +388,6 @@ export class FolderRepo extends BaseRepo {
}
}

public navigate(folderVO: FolderVO): Observable<FolderResponse> {
const response = {
...folderVO,
};
if (folderVO.type === 'type.folder.root.private') {
response.displayName = 'Private';
}

const data = [
{
FolderVO: new FolderVO(response),
},
];

return this.http.sendRequest<FolderResponse>('/folder/navigateMin', data, {
ResponseClass: FolderResponse,
});
}

public async post(folderVOs: FolderVO[]): Promise<FolderResponse> {
const data = folderVOs.map((folderVO) => ({
FolderVO: new FolderVO(folderVO),
Expand Down