@webiny/app-aco 6.6.0-alpha.8 → 6.6.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webiny/app-aco",
3
- "version": "6.6.0-alpha.8",
3
+ "version": "6.6.0-alpha.9",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -15,19 +15,19 @@
15
15
  "author": "Webiny Ltd.",
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
- "@webiny/admin-ui": "6.6.0-alpha.8",
19
- "@webiny/app": "6.6.0-alpha.8",
20
- "@webiny/app-admin": "6.6.0-alpha.8",
21
- "@webiny/app-headless-cms-common": "6.6.0-alpha.8",
22
- "@webiny/app-utils": "6.6.0-alpha.8",
23
- "@webiny/feature": "6.6.0-alpha.8",
24
- "@webiny/feature-flags": "6.6.0-alpha.8",
25
- "@webiny/form": "6.6.0-alpha.8",
26
- "@webiny/icons": "6.6.0-alpha.8",
27
- "@webiny/react-properties": "6.6.0-alpha.8",
28
- "@webiny/shared-aco": "6.6.0-alpha.8",
29
- "@webiny/utils": "6.6.0-alpha.8",
30
- "@webiny/validation": "6.6.0-alpha.8",
18
+ "@webiny/admin-ui": "6.6.0-alpha.9",
19
+ "@webiny/app": "6.6.0-alpha.9",
20
+ "@webiny/app-admin": "6.6.0-alpha.9",
21
+ "@webiny/app-headless-cms-common": "6.6.0-alpha.9",
22
+ "@webiny/app-utils": "6.6.0-alpha.9",
23
+ "@webiny/feature": "6.6.0-alpha.9",
24
+ "@webiny/feature-flags": "6.6.0-alpha.9",
25
+ "@webiny/form": "6.6.0-alpha.9",
26
+ "@webiny/icons": "6.6.0-alpha.9",
27
+ "@webiny/react-properties": "6.6.0-alpha.9",
28
+ "@webiny/shared-aco": "6.6.0-alpha.9",
29
+ "@webiny/utils": "6.6.0-alpha.9",
30
+ "@webiny/validation": "6.6.0-alpha.9",
31
31
  "lodash": "4.18.1",
32
32
  "mobx": "7.0.3",
33
33
  "mobx-react-lite": "5.0.3",
@@ -37,9 +37,9 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/react": "18.3.31",
40
- "@webiny/build-tools": "6.6.0-alpha.8",
40
+ "@webiny/build-tools": "6.6.0-alpha.9",
41
41
  "@webiny/di": "1.1.0",
42
- "@webiny/wcp": "6.6.0-alpha.8",
42
+ "@webiny/wcp": "6.6.0-alpha.9",
43
43
  "rimraf": "6.1.3",
44
44
  "typescript": "7.0.2",
45
45
  "vitest": "4.1.11"
@@ -28,6 +28,7 @@ declare class FolderTreePresenterImpl implements Abstraction.Interface {
28
28
  private operation;
29
29
  private callbacks;
30
30
  private disposed;
31
+ private previousFolders;
31
32
  constructor(foldersContext: FoldersContext.Interface, foldersCache: FoldersCache.Interface, listFoldersUseCase: ListFoldersUseCase.Interface, listFoldersByParentIdsUseCase: ListFoldersByParentIdsUseCase.Interface, createFolderUseCase: CreateFolderUseCase.Interface, updateFolderUseCase: UpdateFolderUseCase.Interface, deleteFolderUseCase: DeleteFolderUseCase.Interface, getFolderAncestorsUseCase: GetFolderAncestorsUseCase.Interface, getFolderLevelPermissionUseCase: GetFolderLevelPermissionUseCase.Interface, formModelFactory: FormModelFactory.Interface, stringFormatter: StringFormatter.Interface);
32
33
  get vm(): IFolderTreeViewModel;
33
34
  selectFolder(folderId: string | null): void;
@@ -42,6 +43,7 @@ declare class FolderTreePresenterImpl implements Abstraction.Interface {
42
43
  deleteFolder(folderId: string): Promise<void>;
43
44
  cancelOperation(): void;
44
45
  private loadFolders;
46
+ private resolveValidFolderId;
45
47
  private buildTree;
46
48
  private buildTreeFromFolders;
47
49
  private findFolder;
@@ -34,14 +34,32 @@ class FolderTreePresenterImpl {
34
34
  };
35
35
  this.callbacks = new Set();
36
36
  this.disposed = false;
37
+ this.previousFolders = [];
37
38
  makeAutoObservable(this, {
38
- callbacks: false
39
+ callbacks: false,
40
+ previousFolders: false
39
41
  }, {
40
42
  autoBind: true
41
43
  });
42
44
  reaction(()=>this.currentFolderId, (folderId)=>{
43
45
  if (!this.disposed) this.callbacks.forEach((cb)=>cb(folderId));
44
46
  });
47
+ reaction(()=>({
48
+ folders: this.foldersCache.getItems(),
49
+ currentFolderId: this.currentFolderId
50
+ }), ({ folders, currentFolderId })=>{
51
+ if (null !== currentFolderId) {
52
+ const stillExists = folders.some((f)=>f.id === currentFolderId);
53
+ const isTransientReloadClear = 0 === folders.length && this.loading;
54
+ if (!stillExists && !isTransientReloadClear) {
55
+ const resolvedId = this.resolveValidFolderId(currentFolderId, this.previousFolders);
56
+ this.selectFolder(resolvedId);
57
+ }
58
+ }
59
+ this.previousFolders = folders;
60
+ }, {
61
+ fireImmediately: true
62
+ });
45
63
  this.loadFolders();
46
64
  }
47
65
  get vm() {
@@ -200,22 +218,18 @@ class FolderTreePresenterImpl {
200
218
  return true;
201
219
  }
202
220
  async deleteFolder(folderId) {
203
- const folders = this.foldersCache.getItems();
204
- const folder = folders.find((f)=>f.id === folderId);
205
- const parentId = folder?.parentId ?? null;
206
221
  this.operation = {
207
222
  active: true,
208
223
  mode: "delete",
209
224
  folderId
210
225
  };
211
226
  await this.deleteFolderUseCase.execute(folderId);
212
- await this.loadFolders();
213
227
  runInAction(()=>{
228
+ this.foldersCache.removeItems((f)=>f.id === folderId);
214
229
  this.operation = {
215
230
  active: false,
216
231
  mode: null
217
232
  };
218
- this.currentFolderId = parentId;
219
233
  });
220
234
  }
221
235
  cancelOperation() {
@@ -236,6 +250,18 @@ class FolderTreePresenterImpl {
236
250
  });
237
251
  }
238
252
  }
253
+ resolveValidFolderId(folderId, previousFolders) {
254
+ let candidateId = folderId;
255
+ const visited = new Set();
256
+ while(null !== candidateId){
257
+ if (visited.has(candidateId)) break;
258
+ visited.add(candidateId);
259
+ if (this.foldersCache.getItem((f)=>f.id === candidateId)) return candidateId;
260
+ const prev = previousFolders.find((f)=>f.id === candidateId);
261
+ candidateId = prev?.parentId ?? null;
262
+ }
263
+ return null;
264
+ }
239
265
  buildTree() {
240
266
  const folders = this.foldersCache.getItems();
241
267
  return this.buildTreeFromFolders(folders);
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/folderTree/FolderTreePresenter.js","sources":["../../../src/presentation/folderTree/FolderTreePresenter.ts"],"sourcesContent":["import { makeAutoObservable, reaction, runInAction } from \"mobx\";\nimport { StringFormatter } from \"@webiny/app-admin/features/stringFormatter/abstractions.js\";\nimport {\n FolderTreePresenter as Abstraction,\n type IFolderTreeNode,\n type IFolderTreeViewModel,\n type IFolderOperationState\n} from \"./abstractions.js\";\nimport { ROOT_FOLDER } from \"~/constants.js\";\nimport { FoldersCache, FoldersContext } from \"~/features/folders/abstractions.js\";\nimport { ListFoldersUseCase } from \"~/features/folders/listFolders/abstractions.js\";\nimport { ListFoldersByParentIdsUseCase } from \"~/features/folders/listFoldersByParentIds/abstractions.js\";\nimport { CreateFolderUseCase } from \"~/features/folders/createFolder/abstractions.js\";\nimport { UpdateFolderUseCase } from \"~/features/folders/updateFolder/abstractions.js\";\nimport { DeleteFolderUseCase } from \"~/features/folders/deleteFolder/abstractions.js\";\nimport { GetFolderAncestorsUseCase } from \"~/features/folders/getFolderAncestors/abstractions.js\";\nimport { GetFolderLevelPermissionUseCase } from \"~/features/folders/getFolderLevelPermission/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { Folder } from \"~/domain/folder/Folder.js\";\nimport type { FolderDto } from \"~/domain/folder/FolderDto.js\";\nimport { FolderDtoMapper } from \"~/domain/folder/FolderDtoMapper.js\";\n\ntype FolderChangeCallback = (folderId: string | null) => void;\n\nclass FolderTreePresenterImpl implements Abstraction.Interface {\n private currentFolderId: string | null = null;\n private loading = false;\n private _loadingNodeIds: string[] = [];\n private operation: IFolderOperationState = { active: false, mode: null };\n private callbacks: Set<FolderChangeCallback> = new Set();\n private disposed = false;\n\n constructor(\n private foldersContext: FoldersContext.Interface,\n private foldersCache: FoldersCache.Interface,\n private listFoldersUseCase: ListFoldersUseCase.Interface,\n private listFoldersByParentIdsUseCase: ListFoldersByParentIdsUseCase.Interface,\n private createFolderUseCase: CreateFolderUseCase.Interface,\n private updateFolderUseCase: UpdateFolderUseCase.Interface,\n private deleteFolderUseCase: DeleteFolderUseCase.Interface,\n private getFolderAncestorsUseCase: GetFolderAncestorsUseCase.Interface,\n private getFolderLevelPermissionUseCase: GetFolderLevelPermissionUseCase.Interface,\n private formModelFactory: FormModelFactory.Interface,\n private stringFormatter: StringFormatter.Interface\n ) {\n makeAutoObservable<FolderTreePresenterImpl, \"callbacks\">(\n this,\n { callbacks: false },\n { autoBind: true }\n );\n\n reaction(\n () => this.currentFolderId,\n folderId => {\n if (!this.disposed) {\n this.callbacks.forEach(cb => cb(folderId));\n }\n }\n );\n\n void this.loadFolders();\n }\n\n get vm(): IFolderTreeViewModel {\n const tree = this.buildTree();\n const allFolders = this.foldersCache.getItems();\n const currentNode = this.findNode(this.currentFolderId, tree);\n const currentFolder = this.findFolder(this.currentFolderId);\n const childNodes = currentNode\n ? currentNode.children\n : tree.filter(n => n.id !== ROOT_FOLDER);\n return {\n folders: allFolders.map(f => FolderDtoMapper.toDTO(f)),\n tree,\n currentFolderId: this.currentFolderId,\n currentFolder: currentFolder ? FolderDtoMapper.toDTO(currentFolder) : null,\n isRootFolder: this.currentFolderId === null,\n currentFolderTitle: currentNode?.name ?? \"All Files\",\n childFolders: childNodes\n .map(n => {\n const folder = allFolders.find(f => f.id === n.id);\n return folder ? FolderDtoMapper.toDTO(folder) : null;\n })\n .filter((f): f is FolderDto => f !== null),\n loading: this.loading,\n loadingNodeIds: this._loadingNodeIds,\n operation: this.operation\n };\n }\n\n selectFolder(folderId: string | null): void {\n const normalized = folderId === ROOT_FOLDER ? null : folderId;\n this.currentFolderId = normalized;\n if (normalized) {\n void this.loadChildFolders([normalized]);\n }\n }\n\n onFolderChange(callback: FolderChangeCallback): () => void {\n this.callbacks.add(callback);\n return () => {\n this.callbacks.delete(callback);\n };\n }\n\n async loadChildFolders(parentIds: string[]): Promise<void> {\n const fetchableIds = parentIds.filter(id => id !== ROOT_FOLDER && id !== \"0\");\n if (fetchableIds.length === 0) {\n return;\n }\n\n runInAction(() => {\n this._loadingNodeIds = fetchableIds;\n });\n\n try {\n await this.listFoldersByParentIdsUseCase.execute(fetchableIds);\n } finally {\n runInAction(() => {\n this._loadingNodeIds = [];\n });\n }\n }\n\n async moveFolder(folderId: string, targetParentId: string | null): Promise<void> {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n if (!folder) {\n return;\n }\n\n await this.updateFolderUseCase.execute({\n id: folderId,\n title: folder.title,\n slug: folder.slug,\n type: this.foldersContext.type,\n parentId: targetParentId,\n permissions: folder.permissions\n });\n\n await this.loadFolders();\n }\n\n canManageStructure(folderId: string): boolean {\n return this.getFolderLevelPermissionUseCase.execute(folderId, \"canManageStructure\");\n }\n\n getAncestorIds(folderId: string): string[] {\n return this.getFolderAncestorsUseCase.execute(folderId).map(f => f.id);\n }\n\n createFolder(parentFolderId?: string): void {\n const form = this.formModelFactory.create({\n fields: fields => ({\n title: fields.text().label(\"Title\").required(\"Title is required\"),\n slug: fields\n .text()\n .label(\"Slug\")\n .required(\"Slug is required\")\n .computedUntilDirty(({ form }) => {\n const title = form.field(\"title\").getValue();\n return this.stringFormatter.slugify(String(title ?? \"\"));\n }),\n parentId: fields\n .text()\n .label(\"Parent folder\")\n .renderer(\"folderTree\" as any)\n }),\n layout: layout => [layout.row(\"title\"), layout.row(\"slug\"), layout.row(\"parentId\")]\n });\n\n form.setData({ parentId: parentFolderId ?? null });\n\n this.operation = {\n active: true,\n mode: \"create\",\n parentFolderId,\n form\n };\n }\n\n editFolder(folderId: string): void {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n if (!folder) {\n return;\n }\n\n const form = this.formModelFactory.create({\n fields: fields => ({\n title: fields.text().label(\"Name\").required(\"Name is required\"),\n slug: fields\n .text()\n .label(\"Slug\")\n .required(\"Slug is required\")\n .computedUntilDirty(({ form }) => {\n const title = form.field(\"title\").getValue();\n return this.stringFormatter.slugify(String(title ?? \"\"));\n })\n }),\n layout: layout => [layout.row(\"title\"), layout.row(\"slug\")]\n });\n\n form.setData({ title: folder.title, slug: folder.slug });\n\n this.operation = {\n active: true,\n mode: \"edit\",\n folderId,\n form\n };\n }\n\n async submitOperation(): Promise<boolean> {\n const { mode, form, parentFolderId, folderId } = this.operation;\n if (!form || !mode) {\n return false;\n }\n\n const data = await form.submit<{ title: string; slug: string; parentId?: string | null }>();\n if (!data) {\n return false;\n }\n\n if (mode === \"create\") {\n await this.createFolderUseCase.execute({\n title: data.title,\n slug: data.slug,\n type: this.foldersContext.type,\n parentId: data.parentId ?? parentFolderId ?? null,\n permissions: []\n });\n } else if (mode === \"edit\" && folderId) {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n\n await this.updateFolderUseCase.execute({\n id: folderId,\n title: data.title,\n slug: data.slug,\n type: this.foldersContext.type,\n parentId: folder?.parentId ?? null,\n permissions: folder?.permissions ?? []\n });\n }\n\n runInAction(() => {\n this.operation = { active: false, mode: null };\n });\n\n return true;\n }\n\n async deleteFolder(folderId: string): Promise<void> {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n const parentId = folder?.parentId ?? null;\n\n this.operation = {\n active: true,\n mode: \"delete\",\n folderId\n };\n\n await this.deleteFolderUseCase.execute(folderId);\n await this.loadFolders();\n\n runInAction(() => {\n this.operation = { active: false, mode: null };\n this.currentFolderId = parentId;\n });\n }\n\n cancelOperation(): void {\n this.operation = { active: false, mode: null };\n }\n\n private async loadFolders(): Promise<void> {\n runInAction(() => {\n this.loading = true;\n });\n\n try {\n await this.listFoldersUseCase.execute();\n } finally {\n runInAction(() => {\n this.loading = false;\n });\n }\n }\n\n private buildTree(): IFolderTreeNode[] {\n const folders = this.foldersCache.getItems();\n return this.buildTreeFromFolders(folders);\n }\n\n private buildTreeFromFolders(folders: Folder[]): IFolderTreeNode[] {\n const nodeMap = new Map<string, IFolderTreeNode>();\n\n for (const folder of folders) {\n nodeMap.set(folder.id, {\n id: folder.id,\n name: folder.title,\n slug: folder.slug,\n parentId: folder.parentId,\n children: [],\n hasNonInheritedPermissions: folder.hasNonInheritedPermissions ?? false,\n canManagePermissions: folder.canManagePermissions ?? false,\n canManageStructure: folder.canManageStructure ?? true\n });\n }\n\n const roots: IFolderTreeNode[] = [];\n for (const folder of folders) {\n const node = nodeMap.get(folder.id)!;\n if (folder.parentId && nodeMap.has(folder.parentId)) {\n nodeMap.get(folder.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n }\n\n private findFolder(folderId: string | null): Folder | null {\n if (!folderId) {\n return null;\n }\n\n return this.foldersCache.getItem(f => f.id === folderId) ?? null;\n }\n\n private findNode(folderId: string | null, nodes: IFolderTreeNode[]): IFolderTreeNode | null {\n if (folderId === null) {\n return null;\n }\n\n for (const node of nodes) {\n if (node.id === folderId) {\n return node;\n }\n const found = this.findNode(folderId, node.children);\n if (found) {\n return found;\n }\n }\n\n return null;\n }\n}\n\nexport const FolderTreePresenter = Abstraction.createImplementation({\n implementation: FolderTreePresenterImpl,\n dependencies: [\n FoldersContext,\n FoldersCache,\n ListFoldersUseCase,\n ListFoldersByParentIdsUseCase,\n CreateFolderUseCase,\n UpdateFolderUseCase,\n DeleteFolderUseCase,\n GetFolderAncestorsUseCase,\n GetFolderLevelPermissionUseCase,\n FormModelFactory,\n StringFormatter\n ]\n});\n"],"names":["FolderTreePresenterImpl","foldersContext","foldersCache","listFoldersUseCase","listFoldersByParentIdsUseCase","createFolderUseCase","updateFolderUseCase","deleteFolderUseCase","getFolderAncestorsUseCase","getFolderLevelPermissionUseCase","formModelFactory","stringFormatter","Set","makeAutoObservable","reaction","folderId","cb","tree","allFolders","currentNode","currentFolder","childNodes","n","ROOT_FOLDER","f","FolderDtoMapper","folder","normalized","callback","parentIds","fetchableIds","id","runInAction","targetParentId","folders","parentFolderId","form","fields","title","String","layout","mode","data","parentId","nodeMap","Map","roots","node","nodes","found","FolderTreePresenter","Abstraction","FoldersContext","FoldersCache","ListFoldersUseCase","ListFoldersByParentIdsUseCase","CreateFolderUseCase","UpdateFolderUseCase","DeleteFolderUseCase","GetFolderAncestorsUseCase","GetFolderLevelPermissionUseCase","FormModelFactory","StringFormatter"],"mappings":"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"}
1
+ {"version":3,"file":"presentation/folderTree/FolderTreePresenter.js","sources":["../../../src/presentation/folderTree/FolderTreePresenter.ts"],"sourcesContent":["import { makeAutoObservable, reaction, runInAction } from \"mobx\";\nimport { StringFormatter } from \"@webiny/app-admin/features/stringFormatter/abstractions.js\";\nimport {\n FolderTreePresenter as Abstraction,\n type IFolderTreeNode,\n type IFolderTreeViewModel,\n type IFolderOperationState\n} from \"./abstractions.js\";\nimport { ROOT_FOLDER } from \"~/constants.js\";\nimport { FoldersCache, FoldersContext } from \"~/features/folders/abstractions.js\";\nimport { ListFoldersUseCase } from \"~/features/folders/listFolders/abstractions.js\";\nimport { ListFoldersByParentIdsUseCase } from \"~/features/folders/listFoldersByParentIds/abstractions.js\";\nimport { CreateFolderUseCase } from \"~/features/folders/createFolder/abstractions.js\";\nimport { UpdateFolderUseCase } from \"~/features/folders/updateFolder/abstractions.js\";\nimport { DeleteFolderUseCase } from \"~/features/folders/deleteFolder/abstractions.js\";\nimport { GetFolderAncestorsUseCase } from \"~/features/folders/getFolderAncestors/abstractions.js\";\nimport { GetFolderLevelPermissionUseCase } from \"~/features/folders/getFolderLevelPermission/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { Folder } from \"~/domain/folder/Folder.js\";\nimport type { FolderDto } from \"~/domain/folder/FolderDto.js\";\nimport { FolderDtoMapper } from \"~/domain/folder/FolderDtoMapper.js\";\n\ntype FolderChangeCallback = (folderId: string | null) => void;\n\nclass FolderTreePresenterImpl implements Abstraction.Interface {\n private currentFolderId: string | null = null;\n private loading = false;\n private _loadingNodeIds: string[] = [];\n private operation: IFolderOperationState = { active: false, mode: null };\n private callbacks: Set<FolderChangeCallback> = new Set();\n private disposed = false;\n\n private previousFolders: Folder[] = [];\n\n constructor(\n private foldersContext: FoldersContext.Interface,\n private foldersCache: FoldersCache.Interface,\n private listFoldersUseCase: ListFoldersUseCase.Interface,\n private listFoldersByParentIdsUseCase: ListFoldersByParentIdsUseCase.Interface,\n private createFolderUseCase: CreateFolderUseCase.Interface,\n private updateFolderUseCase: UpdateFolderUseCase.Interface,\n private deleteFolderUseCase: DeleteFolderUseCase.Interface,\n private getFolderAncestorsUseCase: GetFolderAncestorsUseCase.Interface,\n private getFolderLevelPermissionUseCase: GetFolderLevelPermissionUseCase.Interface,\n private formModelFactory: FormModelFactory.Interface,\n private stringFormatter: StringFormatter.Interface\n ) {\n makeAutoObservable<FolderTreePresenterImpl, \"callbacks\" | \"previousFolders\">(\n this,\n { callbacks: false, previousFolders: false },\n { autoBind: true }\n );\n\n reaction(\n () => this.currentFolderId,\n folderId => {\n if (!this.disposed) {\n this.callbacks.forEach(cb => cb(folderId));\n }\n }\n );\n\n reaction(\n () => ({\n folders: this.foldersCache.getItems(),\n currentFolderId: this.currentFolderId\n }),\n ({ folders, currentFolderId }) => {\n if (currentFolderId !== null) {\n const stillExists = folders.some(f => f.id === currentFolderId);\n // Skip when folders is empty *and* a reload is in flight — the cache is\n // cleared before being repopulated, so this is a transient state, not a\n // real \"all folders were removed\" event.\n const isTransientReloadClear = folders.length === 0 && this.loading;\n if (!stillExists && !isTransientReloadClear) {\n const resolvedId = this.resolveValidFolderId(\n currentFolderId,\n this.previousFolders\n );\n this.selectFolder(resolvedId);\n }\n }\n\n this.previousFolders = folders;\n },\n { fireImmediately: true }\n );\n\n void this.loadFolders();\n }\n\n get vm(): IFolderTreeViewModel {\n const tree = this.buildTree();\n const allFolders = this.foldersCache.getItems();\n const currentNode = this.findNode(this.currentFolderId, tree);\n const currentFolder = this.findFolder(this.currentFolderId);\n const childNodes = currentNode\n ? currentNode.children\n : tree.filter(n => n.id !== ROOT_FOLDER);\n return {\n folders: allFolders.map(f => FolderDtoMapper.toDTO(f)),\n tree,\n currentFolderId: this.currentFolderId,\n currentFolder: currentFolder ? FolderDtoMapper.toDTO(currentFolder) : null,\n isRootFolder: this.currentFolderId === null,\n currentFolderTitle: currentNode?.name ?? \"All Files\",\n childFolders: childNodes\n .map(n => {\n const folder = allFolders.find(f => f.id === n.id);\n return folder ? FolderDtoMapper.toDTO(folder) : null;\n })\n .filter((f): f is FolderDto => f !== null),\n loading: this.loading,\n loadingNodeIds: this._loadingNodeIds,\n operation: this.operation\n };\n }\n\n selectFolder(folderId: string | null): void {\n const normalized = folderId === ROOT_FOLDER ? null : folderId;\n this.currentFolderId = normalized;\n if (normalized) {\n void this.loadChildFolders([normalized]);\n }\n }\n\n onFolderChange(callback: FolderChangeCallback): () => void {\n this.callbacks.add(callback);\n return () => {\n this.callbacks.delete(callback);\n };\n }\n\n async loadChildFolders(parentIds: string[]): Promise<void> {\n const fetchableIds = parentIds.filter(id => id !== ROOT_FOLDER && id !== \"0\");\n if (fetchableIds.length === 0) {\n return;\n }\n\n runInAction(() => {\n this._loadingNodeIds = fetchableIds;\n });\n\n try {\n await this.listFoldersByParentIdsUseCase.execute(fetchableIds);\n } finally {\n runInAction(() => {\n this._loadingNodeIds = [];\n });\n }\n }\n\n async moveFolder(folderId: string, targetParentId: string | null): Promise<void> {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n if (!folder) {\n return;\n }\n\n await this.updateFolderUseCase.execute({\n id: folderId,\n title: folder.title,\n slug: folder.slug,\n type: this.foldersContext.type,\n parentId: targetParentId,\n permissions: folder.permissions\n });\n\n await this.loadFolders();\n }\n\n canManageStructure(folderId: string): boolean {\n return this.getFolderLevelPermissionUseCase.execute(folderId, \"canManageStructure\");\n }\n\n getAncestorIds(folderId: string): string[] {\n return this.getFolderAncestorsUseCase.execute(folderId).map(f => f.id);\n }\n\n createFolder(parentFolderId?: string): void {\n const form = this.formModelFactory.create({\n fields: fields => ({\n title: fields.text().label(\"Title\").required(\"Title is required\"),\n slug: fields\n .text()\n .label(\"Slug\")\n .required(\"Slug is required\")\n .computedUntilDirty(({ form }) => {\n const title = form.field(\"title\").getValue();\n return this.stringFormatter.slugify(String(title ?? \"\"));\n }),\n parentId: fields\n .text()\n .label(\"Parent folder\")\n .renderer(\"folderTree\" as any)\n }),\n layout: layout => [layout.row(\"title\"), layout.row(\"slug\"), layout.row(\"parentId\")]\n });\n\n form.setData({ parentId: parentFolderId ?? null });\n\n this.operation = {\n active: true,\n mode: \"create\",\n parentFolderId,\n form\n };\n }\n\n editFolder(folderId: string): void {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n if (!folder) {\n return;\n }\n\n const form = this.formModelFactory.create({\n fields: fields => ({\n title: fields.text().label(\"Name\").required(\"Name is required\"),\n slug: fields\n .text()\n .label(\"Slug\")\n .required(\"Slug is required\")\n .computedUntilDirty(({ form }) => {\n const title = form.field(\"title\").getValue();\n return this.stringFormatter.slugify(String(title ?? \"\"));\n })\n }),\n layout: layout => [layout.row(\"title\"), layout.row(\"slug\")]\n });\n\n form.setData({ title: folder.title, slug: folder.slug });\n\n this.operation = {\n active: true,\n mode: \"edit\",\n folderId,\n form\n };\n }\n\n async submitOperation(): Promise<boolean> {\n const { mode, form, parentFolderId, folderId } = this.operation;\n if (!form || !mode) {\n return false;\n }\n\n const data = await form.submit<{ title: string; slug: string; parentId?: string | null }>();\n if (!data) {\n return false;\n }\n\n if (mode === \"create\") {\n await this.createFolderUseCase.execute({\n title: data.title,\n slug: data.slug,\n type: this.foldersContext.type,\n parentId: data.parentId ?? parentFolderId ?? null,\n permissions: []\n });\n } else if (mode === \"edit\" && folderId) {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n\n await this.updateFolderUseCase.execute({\n id: folderId,\n title: data.title,\n slug: data.slug,\n type: this.foldersContext.type,\n parentId: folder?.parentId ?? null,\n permissions: folder?.permissions ?? []\n });\n }\n\n runInAction(() => {\n this.operation = { active: false, mode: null };\n });\n\n return true;\n }\n\n async deleteFolder(folderId: string): Promise<void> {\n this.operation = {\n active: true,\n mode: \"delete\",\n folderId\n };\n\n await this.deleteFolderUseCase.execute(folderId);\n\n runInAction(() => {\n this.foldersCache.removeItems(f => f.id === folderId);\n this.operation = { active: false, mode: null };\n });\n }\n\n cancelOperation(): void {\n this.operation = { active: false, mode: null };\n }\n\n private async loadFolders(): Promise<void> {\n runInAction(() => {\n this.loading = true;\n });\n\n try {\n await this.listFoldersUseCase.execute();\n } finally {\n runInAction(() => {\n this.loading = false;\n });\n }\n }\n\n private resolveValidFolderId(\n folderId: string | null,\n previousFolders: Folder[]\n ): string | null {\n let candidateId = folderId;\n const visited = new Set<string>();\n\n while (candidateId !== null) {\n if (visited.has(candidateId)) {\n return null;\n }\n visited.add(candidateId);\n\n if (this.foldersCache.getItem(f => f.id === candidateId)) {\n return candidateId;\n }\n\n const prev = previousFolders.find(f => f.id === candidateId);\n candidateId = prev?.parentId ?? null;\n }\n\n return null;\n }\n\n private buildTree(): IFolderTreeNode[] {\n const folders = this.foldersCache.getItems();\n return this.buildTreeFromFolders(folders);\n }\n\n private buildTreeFromFolders(folders: Folder[]): IFolderTreeNode[] {\n const nodeMap = new Map<string, IFolderTreeNode>();\n\n for (const folder of folders) {\n nodeMap.set(folder.id, {\n id: folder.id,\n name: folder.title,\n slug: folder.slug,\n parentId: folder.parentId,\n children: [],\n hasNonInheritedPermissions: folder.hasNonInheritedPermissions ?? false,\n canManagePermissions: folder.canManagePermissions ?? false,\n canManageStructure: folder.canManageStructure ?? true\n });\n }\n\n const roots: IFolderTreeNode[] = [];\n for (const folder of folders) {\n const node = nodeMap.get(folder.id)!;\n if (folder.parentId && nodeMap.has(folder.parentId)) {\n nodeMap.get(folder.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n }\n\n private findFolder(folderId: string | null): Folder | null {\n if (!folderId) {\n return null;\n }\n\n return this.foldersCache.getItem(f => f.id === folderId) ?? null;\n }\n\n private findNode(folderId: string | null, nodes: IFolderTreeNode[]): IFolderTreeNode | null {\n if (folderId === null) {\n return null;\n }\n\n for (const node of nodes) {\n if (node.id === folderId) {\n return node;\n }\n const found = this.findNode(folderId, node.children);\n if (found) {\n return found;\n }\n }\n\n return null;\n }\n}\n\nexport const FolderTreePresenter = Abstraction.createImplementation({\n implementation: FolderTreePresenterImpl,\n dependencies: [\n FoldersContext,\n FoldersCache,\n ListFoldersUseCase,\n ListFoldersByParentIdsUseCase,\n CreateFolderUseCase,\n UpdateFolderUseCase,\n DeleteFolderUseCase,\n GetFolderAncestorsUseCase,\n GetFolderLevelPermissionUseCase,\n FormModelFactory,\n StringFormatter\n ]\n});\n"],"names":["FolderTreePresenterImpl","foldersContext","foldersCache","listFoldersUseCase","listFoldersByParentIdsUseCase","createFolderUseCase","updateFolderUseCase","deleteFolderUseCase","getFolderAncestorsUseCase","getFolderLevelPermissionUseCase","formModelFactory","stringFormatter","Set","makeAutoObservable","reaction","folderId","cb","folders","currentFolderId","stillExists","f","isTransientReloadClear","resolvedId","tree","allFolders","currentNode","currentFolder","childNodes","n","ROOT_FOLDER","FolderDtoMapper","folder","normalized","callback","parentIds","fetchableIds","id","runInAction","targetParentId","parentFolderId","form","fields","title","String","layout","mode","data","previousFolders","candidateId","visited","prev","nodeMap","Map","roots","node","nodes","found","FolderTreePresenter","Abstraction","FoldersContext","FoldersCache","ListFoldersUseCase","ListFoldersByParentIdsUseCase","CreateFolderUseCase","UpdateFolderUseCase","DeleteFolderUseCase","GetFolderAncestorsUseCase","GetFolderLevelPermissionUseCase","FormModelFactory","StringFormatter"],"mappings":"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"}
@@ -473,40 +473,137 @@ describe("FolderTreePresenter", ()=>{
473
473
  expect(presenter.vm.operation.active).toBe(false);
474
474
  expect(presenter.vm.operation.mode).toBeNull();
475
475
  });
476
- it("should navigate to parent folder after deletion", async ()=>{
476
+ it("should navigate to parent folder after deleting the currently open folder", async ()=>{
477
477
  const { presenter } = createTestPresenter(createFlatFolders());
478
478
  await vi.waitFor(()=>{
479
479
  expect(presenter.vm.loading).toBe(false);
480
480
  });
481
+ presenter.selectFolder("child-1");
481
482
  await presenter.deleteFolder("child-1");
482
483
  expect(presenter.vm.currentFolderId).toBe("root-1");
483
484
  });
484
- it("should navigate to root (null) when deleting a root folder", async ()=>{
485
+ it("should navigate to root (null) when deleting the currently open root folder", async ()=>{
485
486
  const { presenter } = createTestPresenter(createFlatFolders());
486
487
  await vi.waitFor(()=>{
487
488
  expect(presenter.vm.loading).toBe(false);
488
489
  });
490
+ presenter.selectFolder("root-1");
489
491
  await presenter.deleteFolder("root-1");
490
492
  expect(presenter.vm.currentFolderId).toBeNull();
491
493
  });
492
- it("should refresh the folder list after deletion", async ()=>{
494
+ it("should not change currentFolderId when deleting a folder that isn't open", async ()=>{
495
+ const { presenter } = createTestPresenter(createFlatFolders());
496
+ await vi.waitFor(()=>{
497
+ expect(presenter.vm.loading).toBe(false);
498
+ });
499
+ presenter.selectFolder("root-2");
500
+ await presenter.deleteFolder("child-1");
501
+ expect(presenter.vm.currentFolderId).toBe("root-2");
502
+ });
503
+ it("should remove the deleted folder from the cache without refetching from the API", async ()=>{
493
504
  const { presenter, listFolders } = createTestPresenter(createFlatFolders());
494
505
  await vi.waitFor(()=>{
495
506
  expect(presenter.vm.loading).toBe(false);
496
507
  });
497
508
  await presenter.deleteFolder("child-1");
498
- expect(listFolders.execute).toHaveBeenCalledTimes(2);
509
+ expect(listFolders.execute).toHaveBeenCalledTimes(1);
510
+ expect(presenter.vm.folders.find((f)=>"child-1" === f.id)).toBeUndefined();
499
511
  });
500
- it("should fire onFolderChange callback when navigating to parent after delete", async ()=>{
512
+ it("should fire onFolderChange callback when navigating to parent after deleting the open folder", async ()=>{
501
513
  const { presenter } = createTestPresenter(createFlatFolders());
502
514
  await vi.waitFor(()=>{
503
515
  expect(presenter.vm.loading).toBe(false);
504
516
  });
517
+ presenter.selectFolder("child-1");
505
518
  const callback = vi.fn();
506
519
  presenter.onFolderChange(callback);
507
520
  await presenter.deleteFolder("child-1");
508
521
  expect(callback).toHaveBeenCalledWith("root-1");
509
522
  });
523
+ it("should resolve the label for the parent folder after deleting the currently open folder", async ()=>{
524
+ const { presenter } = createTestPresenter(createFlatFolders());
525
+ await vi.waitFor(()=>{
526
+ expect(presenter.vm.loading).toBe(false);
527
+ });
528
+ presenter.selectFolder("grandchild-1");
529
+ await presenter.deleteFolder("grandchild-1");
530
+ expect(presenter.vm.currentFolderId).toBe("child-1");
531
+ expect(presenter.vm.currentFolderTitle).toBe("Invoices");
532
+ });
533
+ });
534
+ describe("stale currentFolderId validation", ()=>{
535
+ it("should fall back to root when the initial currentFolderId no longer exists", async ()=>{
536
+ const { presenter } = createTestPresenter(createFlatFolders());
537
+ presenter.selectFolder("does-not-exist");
538
+ await vi.waitFor(()=>{
539
+ expect(presenter.vm.loading).toBe(false);
540
+ });
541
+ expect(presenter.vm.currentFolderId).toBeNull();
542
+ });
543
+ it("should fall back to the nearest existing ancestor when a deeply nested folder was deleted in another session", async ()=>{
544
+ const { presenter, foldersCache, listFolders, updateFolderUseCase } = createTestPresenter(createFlatFolders());
545
+ await vi.waitFor(()=>{
546
+ expect(presenter.vm.loading).toBe(false);
547
+ });
548
+ presenter.selectFolder("grandchild-1");
549
+ expect(presenter.vm.currentFolderId).toBe("grandchild-1");
550
+ listFolders.execute.mockImplementationOnce(async ()=>{
551
+ foldersCache.removeItems((f)=>"grandchild-1" === f.id || "child-1" === f.id);
552
+ });
553
+ await presenter.moveFolder("root-2", "root-1");
554
+ expect(updateFolderUseCase.execute).toHaveBeenCalledTimes(1);
555
+ expect(presenter.vm.currentFolderId).toBe("root-1");
556
+ });
557
+ });
558
+ describe("external cache deletion (e.g. useDeleteFolder bypassing presenter)", ()=>{
559
+ it("should redirect to parent when the current folder is removed from cache externally", async ()=>{
560
+ const { presenter, foldersCache } = createTestPresenter(createFlatFolders());
561
+ await vi.waitFor(()=>{
562
+ expect(presenter.vm.loading).toBe(false);
563
+ });
564
+ presenter.selectFolder("child-1");
565
+ expect(presenter.vm.currentFolderId).toBe("child-1");
566
+ foldersCache.removeItems((f)=>"child-1" === f.id);
567
+ expect(presenter.vm.currentFolderId).toBe("root-1");
568
+ });
569
+ it("should fire onFolderChange callback on external cache deletion", async ()=>{
570
+ const { presenter, foldersCache } = createTestPresenter(createFlatFolders());
571
+ await vi.waitFor(()=>{
572
+ expect(presenter.vm.loading).toBe(false);
573
+ });
574
+ presenter.selectFolder("child-1");
575
+ const callback = vi.fn();
576
+ presenter.onFolderChange(callback);
577
+ foldersCache.removeItems((f)=>"child-1" === f.id);
578
+ expect(callback).toHaveBeenCalledWith("root-1");
579
+ });
580
+ it("should redirect to root when a root-level folder is removed externally", async ()=>{
581
+ const { presenter, foldersCache } = createTestPresenter(createFlatFolders());
582
+ await vi.waitFor(()=>{
583
+ expect(presenter.vm.loading).toBe(false);
584
+ });
585
+ presenter.selectFolder("root-1");
586
+ foldersCache.removeItems((f)=>"root-1" === f.id);
587
+ expect(presenter.vm.currentFolderId).toBeNull();
588
+ });
589
+ it("should redirect to root when the last remaining folder is removed externally", async ()=>{
590
+ const singleFolder = [
591
+ createFolder({
592
+ id: "root-1",
593
+ title: "Documents",
594
+ slug: "documents",
595
+ parentId: null
596
+ })
597
+ ];
598
+ const { presenter, foldersCache } = createTestPresenter(singleFolder);
599
+ await vi.waitFor(()=>{
600
+ expect(presenter.vm.loading).toBe(false);
601
+ });
602
+ presenter.selectFolder("root-1");
603
+ expect(presenter.vm.currentFolderId).toBe("root-1");
604
+ foldersCache.removeItems((f)=>"root-1" === f.id);
605
+ expect(presenter.vm.currentFolderId).toBeNull();
606
+ });
510
607
  });
511
608
  describe("operation state", ()=>{
512
609
  it("should start with inactive operation state", async ()=>{
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/folderTree/FolderTreePresenter.test.js","sources":["../../../src/presentation/folderTree/FolderTreePresenter.test.ts"],"sourcesContent":["import { describe, it, expect, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { FolderTreePresenter as Abstraction } from \"./abstractions.js\";\nimport { FolderTreePresenterFeature } from \"./feature.js\";\nimport { FoldersCache, FoldersContext } from \"~/features/folders/abstractions.js\";\nimport { ListFoldersUseCase } from \"~/features/folders/listFolders/abstractions.js\";\nimport { CreateFolderUseCase } from \"~/features/folders/createFolder/abstractions.js\";\nimport { UpdateFolderUseCase } from \"~/features/folders/updateFolder/abstractions.js\";\nimport { DeleteFolderUseCase } from \"~/features/folders/deleteFolder/abstractions.js\";\nimport { ListFoldersByParentIdsUseCase } from \"~/features/folders/listFoldersByParentIds/abstractions.js\";\nimport { GetFolderAncestorsUseCase } from \"~/features/folders/getFolderAncestors/abstractions.js\";\nimport { GetFolderLevelPermissionUseCase } from \"~/features/folders/getFolderLevelPermission/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { StringFormatter } from \"@webiny/app-admin/features/stringFormatter/abstractions.js\";\nimport { ListCache } from \"~/features/folders/cache/index.js\";\nimport { Folder } from \"~/domain/folder/Folder.js\";\nimport type {\n IFormModel,\n IFormModelConfig,\n IField\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst TYPE = \"FmFile\";\n\nfunction createFolder(data: {\n id: string;\n title: string;\n slug: string;\n parentId: string | null;\n}): Folder {\n return Folder.create({\n ...data,\n type: TYPE,\n permissions: []\n });\n}\n\nfunction createFlatFolders(): Folder[] {\n return [\n createFolder({ id: \"root-1\", title: \"Documents\", slug: \"documents\", parentId: null }),\n createFolder({ id: \"root-2\", title: \"Images\", slug: \"images\", parentId: null }),\n createFolder({ id: \"child-1\", title: \"Invoices\", slug: \"invoices\", parentId: \"root-1\" }),\n createFolder({\n id: \"child-2\",\n title: \"Contracts\",\n slug: \"contracts\",\n parentId: \"root-1\"\n }),\n createFolder({\n id: \"grandchild-1\",\n title: \"2024\",\n slug: \"2024\",\n parentId: \"child-1\"\n })\n ];\n}\n\n// ---------------------------------------------------------------------------\n// Mock FormModel\n// ---------------------------------------------------------------------------\n\nfunction createMockField(): IField {\n let value: unknown = undefined;\n return {\n getValue: vi.fn(() => value),\n setValue: vi.fn((v: unknown) => {\n value = v;\n }),\n setDisabled: vi.fn(),\n setVisible: vi.fn(),\n remove: vi.fn(),\n addBeforeChange: vi.fn(),\n addAfterChange: vi.fn(),\n addAfterSetValue: vi.fn(),\n addOnBlur: vi.fn(),\n blur: vi.fn(),\n as: vi.fn()\n } as unknown as IField;\n}\n\nfunction createMockFormModel(): IFormModel {\n const fields = new Map<string, IField>();\n let data: Record<string, unknown> = {};\n\n const form: IFormModel = {\n field(name: string) {\n if (!fields.has(name)) {\n fields.set(name, createMockField());\n }\n return fields.get(name)!;\n },\n fields: vi.fn(),\n layout: vi.fn() as IFormModel[\"layout\"],\n setLayout: vi.fn(),\n addRule: vi.fn(),\n getData: vi.fn(() => data),\n setData: vi.fn((d: Record<string, unknown>) => {\n data = d;\n }),\n reset: vi.fn(),\n validate: vi.fn(async () => true),\n submit: vi.fn(async () => data) as IFormModel[\"submit\"],\n evaluateRules: vi.fn(() => ({ visible: true, disabled: false })),\n focusField: vi.fn(),\n isDirty: false,\n isValid: null,\n submitted: false,\n submitCount: 0,\n errors: [],\n vm: {\n layout: [],\n isDirty: false,\n isValid: null,\n errors: [],\n hasErrors: false,\n submitCount: 0,\n focusField: vi.fn(),\n getData: vi.fn(() => ({})),\n setData: vi.fn()\n },\n getFieldBuilders: vi.fn(() => []),\n resolveChildLayout: vi.fn(() => []),\n registry: {} as IFormModel[\"registry\"],\n traverse: vi.fn()\n };\n\n return form;\n}\n\n// ---------------------------------------------------------------------------\n// Mock use cases\n// ---------------------------------------------------------------------------\n\nclass MockListFoldersUseCase implements ListFoldersUseCase.Interface {\n execute = vi.fn(async () => {});\n}\n\nclass MockCreateFolderUseCase implements CreateFolderUseCase.Interface {\n execute = vi.fn(async () => {});\n}\n\nclass MockUpdateFolderUseCase implements UpdateFolderUseCase.Interface {\n execute = vi.fn(async () => {});\n}\n\nclass MockDeleteFolderUseCase implements DeleteFolderUseCase.Interface {\n execute = vi.fn(async () => {});\n}\n\nclass MockFormModelFactory {\n create = vi.fn((_config: IFormModelConfig) => createMockFormModel());\n}\n\n// ---------------------------------------------------------------------------\n// Setup\n// ---------------------------------------------------------------------------\n\nfunction createTestPresenter(folders: Folder[] = []) {\n const container = new Container();\n const foldersCache = new ListCache<Folder>();\n const listFolders = new MockListFoldersUseCase();\n const createFolderUseCase = new MockCreateFolderUseCase();\n const updateFolderUseCase = new MockUpdateFolderUseCase();\n const deleteFolderUseCase = new MockDeleteFolderUseCase();\n const formModelFactory = new MockFormModelFactory();\n\n // Pre-populate cache.\n if (folders.length > 0) {\n foldersCache.addItems(folders);\n }\n\n container.registerInstance(FoldersContext, { type: TYPE });\n container.registerInstance(FoldersCache, foldersCache);\n container.registerInstance(ListFoldersUseCase, listFolders);\n container.registerInstance(CreateFolderUseCase, createFolderUseCase);\n container.registerInstance(UpdateFolderUseCase, updateFolderUseCase);\n container.registerInstance(DeleteFolderUseCase, deleteFolderUseCase);\n container.registerInstance(ListFoldersByParentIdsUseCase, {\n execute: vi.fn().mockResolvedValue(undefined)\n });\n container.registerInstance(GetFolderAncestorsUseCase, {\n execute: vi.fn().mockReturnValue([])\n });\n container.registerInstance(GetFolderLevelPermissionUseCase, {\n execute: vi.fn().mockReturnValue(true)\n });\n container.registerInstance(\n FormModelFactory,\n formModelFactory as unknown as FormModelFactory.Interface\n );\n container.registerInstance(StringFormatter, {\n slugify: (value: string) => value\n });\n\n FolderTreePresenterFeature.register(container);\n\n const presenter = container.resolve(Abstraction);\n\n return {\n presenter,\n foldersCache,\n listFolders,\n createFolderUseCase,\n updateFolderUseCase,\n deleteFolderUseCase,\n formModelFactory\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tests\n// ---------------------------------------------------------------------------\n\ndescribe(\"FolderTreePresenter\", () => {\n // -------------------------------------------------------------------\n // Scoped initialization via FoldersContext\n // -------------------------------------------------------------------\n\n describe(\"scoped initialization\", () => {\n it(\"should load folders on construction via ListFoldersUseCase\", () => {\n const { listFolders } = createTestPresenter();\n expect(listFolders.execute).toHaveBeenCalledTimes(1);\n });\n\n it(\"should not require an init(type) method — type comes from FoldersContext\", () => {\n const { presenter } = createTestPresenter();\n // The presenter has no init method — it reads type from injected FoldersContext.\n expect((presenter as any).init).toBeUndefined();\n });\n\n it(\"should start with loading=true then resolve to false\", async () => {\n const { presenter } = createTestPresenter();\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n });\n });\n\n // -------------------------------------------------------------------\n // Tree computation from flat folder list\n // -------------------------------------------------------------------\n\n describe(\"tree computation\", () => {\n it(\"should build a nested tree from a flat folder list\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const { tree } = presenter.vm;\n\n // Two root nodes.\n expect(tree).toHaveLength(2);\n expect(tree[0].name).toBe(\"Documents\");\n expect(tree[1].name).toBe(\"Images\");\n\n // Documents has 2 children.\n expect(tree[0].children).toHaveLength(2);\n expect(tree[0].children[0].name).toBe(\"Invoices\");\n expect(tree[0].children[1].name).toBe(\"Contracts\");\n\n // Invoices has 1 grandchild.\n expect(tree[0].children[0].children).toHaveLength(1);\n expect(tree[0].children[0].children[0].name).toBe(\"2024\");\n\n // Images has no children.\n expect(tree[1].children).toHaveLength(0);\n });\n\n it(\"should return an empty tree when cache is empty\", async () => {\n const { presenter } = createTestPresenter([]);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.tree).toEqual([]);\n });\n\n it(\"should treat folders with unknown parentId as root nodes\", async () => {\n const folders = [\n createFolder({\n id: \"orphan\",\n title: \"Orphan\",\n slug: \"orphan\",\n parentId: \"non-existent\"\n })\n ];\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.tree).toHaveLength(1);\n expect(presenter.vm.tree[0].name).toBe(\"Orphan\");\n });\n\n it(\"should map folder properties correctly to tree nodes\", async () => {\n const folders = [\n createFolder({\n id: \"f1\",\n title: \"My Folder\",\n slug: \"my-folder\",\n parentId: null\n })\n ];\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const node = presenter.vm.tree[0];\n expect(node.id).toBe(\"f1\");\n expect(node.name).toBe(\"My Folder\");\n expect(node.slug).toBe(\"my-folder\");\n expect(node.parentId).toBeNull();\n expect(node.children).toEqual([]);\n });\n });\n\n // -------------------------------------------------------------------\n // selectFolder and onFolderChange\n // -------------------------------------------------------------------\n\n describe(\"selectFolder\", () => {\n it(\"should update currentFolderId\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.currentFolderId).toBeNull();\n\n presenter.selectFolder(\"root-1\");\n expect(presenter.vm.currentFolderId).toBe(\"root-1\");\n });\n\n it(\"should update currentFolder to the matching folder\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"child-1\");\n expect(presenter.vm.currentFolder).not.toBeNull();\n expect(presenter.vm.currentFolder!.id).toBe(\"child-1\");\n expect(presenter.vm.currentFolder!.title).toBe(\"Invoices\");\n });\n\n it(\"should set currentFolder to null when selecting root (null)\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-1\");\n expect(presenter.vm.currentFolder).not.toBeNull();\n\n presenter.selectFolder(null);\n expect(presenter.vm.currentFolderId).toBeNull();\n expect(presenter.vm.currentFolder).toBeNull();\n });\n\n it(\"should find deeply nested folders as currentFolder\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"grandchild-1\");\n expect(presenter.vm.currentFolder).not.toBeNull();\n expect(presenter.vm.currentFolder!.title).toBe(\"2024\");\n });\n });\n\n describe(\"onFolderChange\", () => {\n it(\"should fire callbacks when currentFolderId changes\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n presenter.selectFolder(\"root-1\");\n\n // MobX reaction fires synchronously for simple observable changes.\n expect(callback).toHaveBeenCalledTimes(1);\n expect(callback).toHaveBeenCalledWith(\"root-1\");\n });\n\n it(\"should fire callback with null when navigating to root\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-1\");\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n presenter.selectFolder(null);\n expect(callback).toHaveBeenCalledWith(null);\n });\n\n it(\"should support multiple callbacks\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const cb1 = vi.fn();\n const cb2 = vi.fn();\n presenter.onFolderChange(cb1);\n presenter.onFolderChange(cb2);\n\n presenter.selectFolder(\"root-2\");\n\n expect(cb1).toHaveBeenCalledWith(\"root-2\");\n expect(cb2).toHaveBeenCalledWith(\"root-2\");\n });\n\n it(\"should return an unsubscribe function that stops callbacks\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const callback = vi.fn();\n const unsubscribe = presenter.onFolderChange(callback);\n\n presenter.selectFolder(\"root-1\");\n expect(callback).toHaveBeenCalledTimes(1);\n\n unsubscribe();\n\n presenter.selectFolder(\"root-2\");\n // Should not have been called again after unsubscribe.\n expect(callback).toHaveBeenCalledTimes(1);\n });\n\n it(\"should not fire callback when selecting the same folder\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-1\");\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n // Select the same folder again.\n presenter.selectFolder(\"root-1\");\n expect(callback).not.toHaveBeenCalled();\n });\n });\n\n // -------------------------------------------------------------------\n // createFolder / editFolder — operation state with FormModel\n // -------------------------------------------------------------------\n\n describe(\"createFolder\", () => {\n it(\"should set operation state with mode 'create' and a FormModel\", async () => {\n const { presenter, formModelFactory } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.createFolder(\"root-1\");\n\n expect(presenter.vm.operation.active).toBe(true);\n expect(presenter.vm.operation.mode).toBe(\"create\");\n expect(presenter.vm.operation.parentFolderId).toBe(\"root-1\");\n expect(presenter.vm.operation.form).toBeDefined();\n expect(formModelFactory.create).toHaveBeenCalledTimes(1);\n });\n\n it(\"should set parentFolderId to undefined when creating at root\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.createFolder();\n\n expect(presenter.vm.operation.active).toBe(true);\n expect(presenter.vm.operation.mode).toBe(\"create\");\n expect(presenter.vm.operation.parentFolderId).toBeUndefined();\n });\n\n it(\"should wire submitOperation to CreateFolderUseCase\", async () => {\n const { presenter, createFolderUseCase, listFolders } =\n createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.createFolder(\"root-1\");\n\n await presenter.submitOperation();\n\n expect(createFolderUseCase.execute).toHaveBeenCalledTimes(1);\n expect(listFolders.execute).toHaveBeenCalledTimes(1);\n });\n });\n\n describe(\"editFolder\", () => {\n it(\"should set operation state with mode 'edit' and a FormModel\", async () => {\n const { presenter, formModelFactory } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.editFolder(\"root-1\");\n\n expect(presenter.vm.operation.active).toBe(true);\n expect(presenter.vm.operation.mode).toBe(\"edit\");\n expect(presenter.vm.operation.folderId).toBe(\"root-1\");\n expect(presenter.vm.operation.form).toBeDefined();\n expect(formModelFactory.create).toHaveBeenCalledTimes(1);\n });\n\n it(\"should pre-populate form with existing folder data\", async () => {\n const mockForm = createMockFormModel();\n const { presenter, formModelFactory } = createTestPresenter(createFlatFolders());\n formModelFactory.create.mockReturnValue(mockForm);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.editFolder(\"root-1\");\n\n expect(mockForm.setData).toHaveBeenCalledWith({\n title: \"Documents\",\n slug: \"documents\"\n });\n });\n\n it(\"should do nothing when editing a non-existent folder\", async () => {\n const { presenter, formModelFactory } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.editFolder(\"non-existent\");\n\n expect(presenter.vm.operation.active).toBe(false);\n expect(presenter.vm.operation.mode).toBeNull();\n expect(formModelFactory.create).not.toHaveBeenCalled();\n });\n });\n\n // -------------------------------------------------------------------\n // deleteFolder — navigates to parent and updates tree\n // -------------------------------------------------------------------\n\n describe(\"deleteFolder\", () => {\n it(\"should call DeleteFolderUseCase with the folder id\", async () => {\n const { presenter, deleteFolderUseCase } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n await presenter.deleteFolder(\"child-1\");\n\n expect(deleteFolderUseCase.execute).toHaveBeenCalledWith(\"child-1\");\n });\n\n it(\"should set operation state with mode 'delete' during deletion\", async () => {\n const { presenter, deleteFolderUseCase } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n // Make delete hang so we can inspect intermediate state.\n let resolveDelete!: () => void;\n deleteFolderUseCase.execute.mockReturnValueOnce(\n new Promise<void>(resolve => {\n resolveDelete = resolve;\n })\n );\n\n const deletePromise = presenter.deleteFolder(\"child-1\");\n\n expect(presenter.vm.operation.active).toBe(true);\n expect(presenter.vm.operation.mode).toBe(\"delete\");\n expect(presenter.vm.operation.folderId).toBe(\"child-1\");\n\n resolveDelete();\n await deletePromise;\n\n // After completion, operation should be reset.\n expect(presenter.vm.operation.active).toBe(false);\n expect(presenter.vm.operation.mode).toBeNull();\n });\n\n it(\"should navigate to parent folder after deletion\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n // child-1 has parentId \"root-1\".\n await presenter.deleteFolder(\"child-1\");\n\n expect(presenter.vm.currentFolderId).toBe(\"root-1\");\n });\n\n it(\"should navigate to root (null) when deleting a root folder\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n // root-1 has parentId null.\n await presenter.deleteFolder(\"root-1\");\n\n expect(presenter.vm.currentFolderId).toBeNull();\n });\n\n it(\"should refresh the folder list after deletion\", async () => {\n const { presenter, listFolders } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n await presenter.deleteFolder(\"child-1\");\n\n // Initial load + refresh after delete.\n expect(listFolders.execute).toHaveBeenCalledTimes(2);\n });\n\n it(\"should fire onFolderChange callback when navigating to parent after delete\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n // child-1 has parentId \"root-1\".\n await presenter.deleteFolder(\"child-1\");\n\n expect(callback).toHaveBeenCalledWith(\"root-1\");\n });\n });\n\n // -------------------------------------------------------------------\n // Operation state reset\n // -------------------------------------------------------------------\n\n describe(\"operation state\", () => {\n it(\"should start with inactive operation state\", async () => {\n const { presenter } = createTestPresenter();\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.operation.active).toBe(false);\n expect(presenter.vm.operation.mode).toBeNull();\n });\n });\n});\n"],"names":["TYPE","createFolder","data","Folder","createFlatFolders","createMockField","value","vi","v","createMockFormModel","fields","Map","form","name","d","MockListFoldersUseCase","MockCreateFolderUseCase","MockUpdateFolderUseCase","MockDeleteFolderUseCase","MockFormModelFactory","_config","createTestPresenter","folders","container","Container","foldersCache","ListCache","listFolders","createFolderUseCase","updateFolderUseCase","deleteFolderUseCase","formModelFactory","FoldersContext","FoldersCache","ListFoldersUseCase","CreateFolderUseCase","UpdateFolderUseCase","DeleteFolderUseCase","ListFoldersByParentIdsUseCase","undefined","GetFolderAncestorsUseCase","GetFolderLevelPermissionUseCase","FormModelFactory","StringFormatter","FolderTreePresenterFeature","presenter","Abstraction","describe","it","expect","tree","node","callback","cb1","cb2","unsubscribe","mockForm","resolveDelete","Promise","resolve","deletePromise"],"mappings":"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"}
1
+ {"version":3,"file":"presentation/folderTree/FolderTreePresenter.test.js","sources":["../../../src/presentation/folderTree/FolderTreePresenter.test.ts"],"sourcesContent":["import { describe, it, expect, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { FolderTreePresenter as Abstraction } from \"./abstractions.js\";\nimport { FolderTreePresenterFeature } from \"./feature.js\";\nimport { FoldersCache, FoldersContext } from \"~/features/folders/abstractions.js\";\nimport { ListFoldersUseCase } from \"~/features/folders/listFolders/abstractions.js\";\nimport { CreateFolderUseCase } from \"~/features/folders/createFolder/abstractions.js\";\nimport { UpdateFolderUseCase } from \"~/features/folders/updateFolder/abstractions.js\";\nimport { DeleteFolderUseCase } from \"~/features/folders/deleteFolder/abstractions.js\";\nimport { ListFoldersByParentIdsUseCase } from \"~/features/folders/listFoldersByParentIds/abstractions.js\";\nimport { GetFolderAncestorsUseCase } from \"~/features/folders/getFolderAncestors/abstractions.js\";\nimport { GetFolderLevelPermissionUseCase } from \"~/features/folders/getFolderLevelPermission/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { StringFormatter } from \"@webiny/app-admin/features/stringFormatter/abstractions.js\";\nimport { ListCache } from \"~/features/folders/cache/index.js\";\nimport { Folder } from \"~/domain/folder/Folder.js\";\nimport type {\n IFormModel,\n IFormModelConfig,\n IField\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst TYPE = \"FmFile\";\n\nfunction createFolder(data: {\n id: string;\n title: string;\n slug: string;\n parentId: string | null;\n}): Folder {\n return Folder.create({\n ...data,\n type: TYPE,\n permissions: []\n });\n}\n\nfunction createFlatFolders(): Folder[] {\n return [\n createFolder({ id: \"root-1\", title: \"Documents\", slug: \"documents\", parentId: null }),\n createFolder({ id: \"root-2\", title: \"Images\", slug: \"images\", parentId: null }),\n createFolder({ id: \"child-1\", title: \"Invoices\", slug: \"invoices\", parentId: \"root-1\" }),\n createFolder({\n id: \"child-2\",\n title: \"Contracts\",\n slug: \"contracts\",\n parentId: \"root-1\"\n }),\n createFolder({\n id: \"grandchild-1\",\n title: \"2024\",\n slug: \"2024\",\n parentId: \"child-1\"\n })\n ];\n}\n\n// ---------------------------------------------------------------------------\n// Mock FormModel\n// ---------------------------------------------------------------------------\n\nfunction createMockField(): IField {\n let value: unknown = undefined;\n return {\n getValue: vi.fn(() => value),\n setValue: vi.fn((v: unknown) => {\n value = v;\n }),\n setDisabled: vi.fn(),\n setVisible: vi.fn(),\n remove: vi.fn(),\n addBeforeChange: vi.fn(),\n addAfterChange: vi.fn(),\n addAfterSetValue: vi.fn(),\n addOnBlur: vi.fn(),\n blur: vi.fn(),\n as: vi.fn()\n } as unknown as IField;\n}\n\nfunction createMockFormModel(): IFormModel {\n const fields = new Map<string, IField>();\n let data: Record<string, unknown> = {};\n\n const form: IFormModel = {\n field(name: string) {\n if (!fields.has(name)) {\n fields.set(name, createMockField());\n }\n return fields.get(name)!;\n },\n fields: vi.fn(),\n layout: vi.fn() as IFormModel[\"layout\"],\n setLayout: vi.fn(),\n addRule: vi.fn(),\n getData: vi.fn(() => data),\n setData: vi.fn((d: Record<string, unknown>) => {\n data = d;\n }),\n reset: vi.fn(),\n validate: vi.fn(async () => true),\n submit: vi.fn(async () => data) as IFormModel[\"submit\"],\n evaluateRules: vi.fn(() => ({ visible: true, disabled: false })),\n focusField: vi.fn(),\n isDirty: false,\n isValid: null,\n submitted: false,\n submitCount: 0,\n errors: [],\n vm: {\n layout: [],\n isDirty: false,\n isValid: null,\n errors: [],\n hasErrors: false,\n submitCount: 0,\n focusField: vi.fn(),\n getData: vi.fn(() => ({})),\n setData: vi.fn()\n },\n getFieldBuilders: vi.fn(() => []),\n resolveChildLayout: vi.fn(() => []),\n registry: {} as IFormModel[\"registry\"],\n traverse: vi.fn()\n };\n\n return form;\n}\n\n// ---------------------------------------------------------------------------\n// Mock use cases\n// ---------------------------------------------------------------------------\n\nclass MockListFoldersUseCase implements ListFoldersUseCase.Interface {\n execute = vi.fn(async () => {});\n}\n\nclass MockCreateFolderUseCase implements CreateFolderUseCase.Interface {\n execute = vi.fn(async () => {});\n}\n\nclass MockUpdateFolderUseCase implements UpdateFolderUseCase.Interface {\n execute = vi.fn(async () => {});\n}\n\nclass MockDeleteFolderUseCase implements DeleteFolderUseCase.Interface {\n execute = vi.fn(async () => {});\n}\n\nclass MockFormModelFactory {\n create = vi.fn((_config: IFormModelConfig) => createMockFormModel());\n}\n\n// ---------------------------------------------------------------------------\n// Setup\n// ---------------------------------------------------------------------------\n\nfunction createTestPresenter(folders: Folder[] = []) {\n const container = new Container();\n const foldersCache = new ListCache<Folder>();\n const listFolders = new MockListFoldersUseCase();\n const createFolderUseCase = new MockCreateFolderUseCase();\n const updateFolderUseCase = new MockUpdateFolderUseCase();\n const deleteFolderUseCase = new MockDeleteFolderUseCase();\n const formModelFactory = new MockFormModelFactory();\n\n // Pre-populate cache.\n if (folders.length > 0) {\n foldersCache.addItems(folders);\n }\n\n container.registerInstance(FoldersContext, { type: TYPE });\n container.registerInstance(FoldersCache, foldersCache);\n container.registerInstance(ListFoldersUseCase, listFolders);\n container.registerInstance(CreateFolderUseCase, createFolderUseCase);\n container.registerInstance(UpdateFolderUseCase, updateFolderUseCase);\n container.registerInstance(DeleteFolderUseCase, deleteFolderUseCase);\n container.registerInstance(ListFoldersByParentIdsUseCase, {\n execute: vi.fn().mockResolvedValue(undefined)\n });\n container.registerInstance(GetFolderAncestorsUseCase, {\n execute: vi.fn().mockReturnValue([])\n });\n container.registerInstance(GetFolderLevelPermissionUseCase, {\n execute: vi.fn().mockReturnValue(true)\n });\n container.registerInstance(\n FormModelFactory,\n formModelFactory as unknown as FormModelFactory.Interface\n );\n container.registerInstance(StringFormatter, {\n slugify: (value: string) => value\n });\n\n FolderTreePresenterFeature.register(container);\n\n const presenter = container.resolve(Abstraction);\n\n return {\n presenter,\n foldersCache,\n listFolders,\n createFolderUseCase,\n updateFolderUseCase,\n deleteFolderUseCase,\n formModelFactory\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tests\n// ---------------------------------------------------------------------------\n\ndescribe(\"FolderTreePresenter\", () => {\n // -------------------------------------------------------------------\n // Scoped initialization via FoldersContext\n // -------------------------------------------------------------------\n\n describe(\"scoped initialization\", () => {\n it(\"should load folders on construction via ListFoldersUseCase\", () => {\n const { listFolders } = createTestPresenter();\n expect(listFolders.execute).toHaveBeenCalledTimes(1);\n });\n\n it(\"should not require an init(type) method — type comes from FoldersContext\", () => {\n const { presenter } = createTestPresenter();\n // The presenter has no init method — it reads type from injected FoldersContext.\n expect((presenter as any).init).toBeUndefined();\n });\n\n it(\"should start with loading=true then resolve to false\", async () => {\n const { presenter } = createTestPresenter();\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n });\n });\n\n // -------------------------------------------------------------------\n // Tree computation from flat folder list\n // -------------------------------------------------------------------\n\n describe(\"tree computation\", () => {\n it(\"should build a nested tree from a flat folder list\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const { tree } = presenter.vm;\n\n // Two root nodes.\n expect(tree).toHaveLength(2);\n expect(tree[0].name).toBe(\"Documents\");\n expect(tree[1].name).toBe(\"Images\");\n\n // Documents has 2 children.\n expect(tree[0].children).toHaveLength(2);\n expect(tree[0].children[0].name).toBe(\"Invoices\");\n expect(tree[0].children[1].name).toBe(\"Contracts\");\n\n // Invoices has 1 grandchild.\n expect(tree[0].children[0].children).toHaveLength(1);\n expect(tree[0].children[0].children[0].name).toBe(\"2024\");\n\n // Images has no children.\n expect(tree[1].children).toHaveLength(0);\n });\n\n it(\"should return an empty tree when cache is empty\", async () => {\n const { presenter } = createTestPresenter([]);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.tree).toEqual([]);\n });\n\n it(\"should treat folders with unknown parentId as root nodes\", async () => {\n const folders = [\n createFolder({\n id: \"orphan\",\n title: \"Orphan\",\n slug: \"orphan\",\n parentId: \"non-existent\"\n })\n ];\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.tree).toHaveLength(1);\n expect(presenter.vm.tree[0].name).toBe(\"Orphan\");\n });\n\n it(\"should map folder properties correctly to tree nodes\", async () => {\n const folders = [\n createFolder({\n id: \"f1\",\n title: \"My Folder\",\n slug: \"my-folder\",\n parentId: null\n })\n ];\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const node = presenter.vm.tree[0];\n expect(node.id).toBe(\"f1\");\n expect(node.name).toBe(\"My Folder\");\n expect(node.slug).toBe(\"my-folder\");\n expect(node.parentId).toBeNull();\n expect(node.children).toEqual([]);\n });\n });\n\n // -------------------------------------------------------------------\n // selectFolder and onFolderChange\n // -------------------------------------------------------------------\n\n describe(\"selectFolder\", () => {\n it(\"should update currentFolderId\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.currentFolderId).toBeNull();\n\n presenter.selectFolder(\"root-1\");\n expect(presenter.vm.currentFolderId).toBe(\"root-1\");\n });\n\n it(\"should update currentFolder to the matching folder\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"child-1\");\n expect(presenter.vm.currentFolder).not.toBeNull();\n expect(presenter.vm.currentFolder!.id).toBe(\"child-1\");\n expect(presenter.vm.currentFolder!.title).toBe(\"Invoices\");\n });\n\n it(\"should set currentFolder to null when selecting root (null)\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-1\");\n expect(presenter.vm.currentFolder).not.toBeNull();\n\n presenter.selectFolder(null);\n expect(presenter.vm.currentFolderId).toBeNull();\n expect(presenter.vm.currentFolder).toBeNull();\n });\n\n it(\"should find deeply nested folders as currentFolder\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"grandchild-1\");\n expect(presenter.vm.currentFolder).not.toBeNull();\n expect(presenter.vm.currentFolder!.title).toBe(\"2024\");\n });\n });\n\n describe(\"onFolderChange\", () => {\n it(\"should fire callbacks when currentFolderId changes\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n presenter.selectFolder(\"root-1\");\n\n // MobX reaction fires synchronously for simple observable changes.\n expect(callback).toHaveBeenCalledTimes(1);\n expect(callback).toHaveBeenCalledWith(\"root-1\");\n });\n\n it(\"should fire callback with null when navigating to root\", async () => {\n const folders = createFlatFolders();\n const { presenter } = createTestPresenter(folders);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-1\");\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n presenter.selectFolder(null);\n expect(callback).toHaveBeenCalledWith(null);\n });\n\n it(\"should support multiple callbacks\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const cb1 = vi.fn();\n const cb2 = vi.fn();\n presenter.onFolderChange(cb1);\n presenter.onFolderChange(cb2);\n\n presenter.selectFolder(\"root-2\");\n\n expect(cb1).toHaveBeenCalledWith(\"root-2\");\n expect(cb2).toHaveBeenCalledWith(\"root-2\");\n });\n\n it(\"should return an unsubscribe function that stops callbacks\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n const callback = vi.fn();\n const unsubscribe = presenter.onFolderChange(callback);\n\n presenter.selectFolder(\"root-1\");\n expect(callback).toHaveBeenCalledTimes(1);\n\n unsubscribe();\n\n presenter.selectFolder(\"root-2\");\n // Should not have been called again after unsubscribe.\n expect(callback).toHaveBeenCalledTimes(1);\n });\n\n it(\"should not fire callback when selecting the same folder\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-1\");\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n // Select the same folder again.\n presenter.selectFolder(\"root-1\");\n expect(callback).not.toHaveBeenCalled();\n });\n });\n\n // -------------------------------------------------------------------\n // createFolder / editFolder — operation state with FormModel\n // -------------------------------------------------------------------\n\n describe(\"createFolder\", () => {\n it(\"should set operation state with mode 'create' and a FormModel\", async () => {\n const { presenter, formModelFactory } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.createFolder(\"root-1\");\n\n expect(presenter.vm.operation.active).toBe(true);\n expect(presenter.vm.operation.mode).toBe(\"create\");\n expect(presenter.vm.operation.parentFolderId).toBe(\"root-1\");\n expect(presenter.vm.operation.form).toBeDefined();\n expect(formModelFactory.create).toHaveBeenCalledTimes(1);\n });\n\n it(\"should set parentFolderId to undefined when creating at root\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.createFolder();\n\n expect(presenter.vm.operation.active).toBe(true);\n expect(presenter.vm.operation.mode).toBe(\"create\");\n expect(presenter.vm.operation.parentFolderId).toBeUndefined();\n });\n\n it(\"should wire submitOperation to CreateFolderUseCase\", async () => {\n const { presenter, createFolderUseCase, listFolders } =\n createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.createFolder(\"root-1\");\n\n await presenter.submitOperation();\n\n expect(createFolderUseCase.execute).toHaveBeenCalledTimes(1);\n expect(listFolders.execute).toHaveBeenCalledTimes(1);\n });\n });\n\n describe(\"editFolder\", () => {\n it(\"should set operation state with mode 'edit' and a FormModel\", async () => {\n const { presenter, formModelFactory } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.editFolder(\"root-1\");\n\n expect(presenter.vm.operation.active).toBe(true);\n expect(presenter.vm.operation.mode).toBe(\"edit\");\n expect(presenter.vm.operation.folderId).toBe(\"root-1\");\n expect(presenter.vm.operation.form).toBeDefined();\n expect(formModelFactory.create).toHaveBeenCalledTimes(1);\n });\n\n it(\"should pre-populate form with existing folder data\", async () => {\n const mockForm = createMockFormModel();\n const { presenter, formModelFactory } = createTestPresenter(createFlatFolders());\n formModelFactory.create.mockReturnValue(mockForm);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.editFolder(\"root-1\");\n\n expect(mockForm.setData).toHaveBeenCalledWith({\n title: \"Documents\",\n slug: \"documents\"\n });\n });\n\n it(\"should do nothing when editing a non-existent folder\", async () => {\n const { presenter, formModelFactory } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.editFolder(\"non-existent\");\n\n expect(presenter.vm.operation.active).toBe(false);\n expect(presenter.vm.operation.mode).toBeNull();\n expect(formModelFactory.create).not.toHaveBeenCalled();\n });\n });\n\n // -------------------------------------------------------------------\n // deleteFolder — navigates to parent and updates tree\n // -------------------------------------------------------------------\n\n describe(\"deleteFolder\", () => {\n it(\"should call DeleteFolderUseCase with the folder id\", async () => {\n const { presenter, deleteFolderUseCase } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n await presenter.deleteFolder(\"child-1\");\n\n expect(deleteFolderUseCase.execute).toHaveBeenCalledWith(\"child-1\");\n });\n\n it(\"should set operation state with mode 'delete' during deletion\", async () => {\n const { presenter, deleteFolderUseCase } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n // Make delete hang so we can inspect intermediate state.\n let resolveDelete!: () => void;\n deleteFolderUseCase.execute.mockReturnValueOnce(\n new Promise<void>(resolve => {\n resolveDelete = resolve;\n })\n );\n\n const deletePromise = presenter.deleteFolder(\"child-1\");\n\n expect(presenter.vm.operation.active).toBe(true);\n expect(presenter.vm.operation.mode).toBe(\"delete\");\n expect(presenter.vm.operation.folderId).toBe(\"child-1\");\n\n resolveDelete();\n await deletePromise;\n\n // After completion, operation should be reset.\n expect(presenter.vm.operation.active).toBe(false);\n expect(presenter.vm.operation.mode).toBeNull();\n });\n\n it(\"should navigate to parent folder after deleting the currently open folder\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n // child-1 has parentId \"root-1\".\n presenter.selectFolder(\"child-1\");\n await presenter.deleteFolder(\"child-1\");\n\n expect(presenter.vm.currentFolderId).toBe(\"root-1\");\n });\n\n it(\"should navigate to root (null) when deleting the currently open root folder\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n // root-1 has parentId null.\n presenter.selectFolder(\"root-1\");\n await presenter.deleteFolder(\"root-1\");\n\n expect(presenter.vm.currentFolderId).toBeNull();\n });\n\n it(\"should not change currentFolderId when deleting a folder that isn't open\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-2\");\n await presenter.deleteFolder(\"child-1\");\n\n expect(presenter.vm.currentFolderId).toBe(\"root-2\");\n });\n\n it(\"should remove the deleted folder from the cache without refetching from the API\", async () => {\n const { presenter, listFolders } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n await presenter.deleteFolder(\"child-1\");\n\n // Only the initial load — deletion updates the cache directly, since\n // refetching would be unreliable while storage is eventually consistent.\n expect(listFolders.execute).toHaveBeenCalledTimes(1);\n expect(presenter.vm.folders.find(f => f.id === \"child-1\")).toBeUndefined();\n });\n\n it(\"should fire onFolderChange callback when navigating to parent after deleting the open folder\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"child-1\");\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n // child-1 has parentId \"root-1\".\n await presenter.deleteFolder(\"child-1\");\n\n expect(callback).toHaveBeenCalledWith(\"root-1\");\n });\n\n it(\"should resolve the label for the parent folder after deleting the currently open folder\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n // grandchild-1's parent is child-1, which must resolve (not fall back to \"All Files\").\n presenter.selectFolder(\"grandchild-1\");\n await presenter.deleteFolder(\"grandchild-1\");\n\n expect(presenter.vm.currentFolderId).toBe(\"child-1\");\n expect(presenter.vm.currentFolderTitle).toBe(\"Invoices\");\n });\n });\n\n describe(\"stale currentFolderId validation\", () => {\n it(\"should fall back to root when the initial currentFolderId no longer exists\", async () => {\n const { presenter } = createTestPresenter(createFlatFolders());\n\n // Simulate a consumer selecting a stale (already-deleted) folder before the\n // initial load resolves, e.g. from a URL param or localStorage value.\n presenter.selectFolder(\"does-not-exist\");\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.currentFolderId).toBeNull();\n });\n\n it(\"should fall back to the nearest existing ancestor when a deeply nested folder was deleted in another session\", async () => {\n const { presenter, foldersCache, listFolders, updateFolderUseCase } =\n createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n // Simulate having navigated into grandchild-1 in a previous session.\n presenter.selectFolder(\"grandchild-1\");\n expect(presenter.vm.currentFolderId).toBe(\"grandchild-1\");\n\n // Simulate grandchild-1 and child-1 both having been deleted server-side\n // (e.g. from another tab), and a reload only bringing back the surviving folders.\n listFolders.execute.mockImplementationOnce(async () => {\n foldersCache.removeItems(f => f.id === \"grandchild-1\" || f.id === \"child-1\");\n });\n\n // Trigger a reload via an unrelated action (moveFolder), simulating any\n // action that causes the presenter to refresh its folder list.\n await presenter.moveFolder(\"root-2\", \"root-1\");\n expect(updateFolderUseCase.execute).toHaveBeenCalledTimes(1);\n\n // Falls back past the also-deleted immediate parent (child-1) to root-1.\n expect(presenter.vm.currentFolderId).toBe(\"root-1\");\n });\n });\n\n describe(\"external cache deletion (e.g. useDeleteFolder bypassing presenter)\", () => {\n it(\"should redirect to parent when the current folder is removed from cache externally\", async () => {\n const { presenter, foldersCache } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"child-1\");\n expect(presenter.vm.currentFolderId).toBe(\"child-1\");\n\n foldersCache.removeItems(f => f.id === \"child-1\");\n\n expect(presenter.vm.currentFolderId).toBe(\"root-1\");\n });\n\n it(\"should fire onFolderChange callback on external cache deletion\", async () => {\n const { presenter, foldersCache } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"child-1\");\n\n const callback = vi.fn();\n presenter.onFolderChange(callback);\n\n foldersCache.removeItems(f => f.id === \"child-1\");\n\n expect(callback).toHaveBeenCalledWith(\"root-1\");\n });\n\n it(\"should redirect to root when a root-level folder is removed externally\", async () => {\n const { presenter, foldersCache } = createTestPresenter(createFlatFolders());\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-1\");\n\n foldersCache.removeItems(f => f.id === \"root-1\");\n\n expect(presenter.vm.currentFolderId).toBeNull();\n });\n\n it(\"should redirect to root when the last remaining folder is removed externally\", async () => {\n const singleFolder = [\n createFolder({\n id: \"root-1\",\n title: \"Documents\",\n slug: \"documents\",\n parentId: null\n })\n ];\n const { presenter, foldersCache } = createTestPresenter(singleFolder);\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n presenter.selectFolder(\"root-1\");\n expect(presenter.vm.currentFolderId).toBe(\"root-1\");\n\n // Cache goes from 1 item to 0 — must still redirect, not get stuck on a\n // stale currentFolderId just because the cache is now empty.\n foldersCache.removeItems(f => f.id === \"root-1\");\n\n expect(presenter.vm.currentFolderId).toBeNull();\n });\n });\n\n // -------------------------------------------------------------------\n // Operation state reset\n // -------------------------------------------------------------------\n\n describe(\"operation state\", () => {\n it(\"should start with inactive operation state\", async () => {\n const { presenter } = createTestPresenter();\n\n await vi.waitFor(() => {\n expect(presenter.vm.loading).toBe(false);\n });\n\n expect(presenter.vm.operation.active).toBe(false);\n expect(presenter.vm.operation.mode).toBeNull();\n });\n });\n});\n"],"names":["TYPE","createFolder","data","Folder","createFlatFolders","createMockField","value","vi","v","createMockFormModel","fields","Map","form","name","d","MockListFoldersUseCase","MockCreateFolderUseCase","MockUpdateFolderUseCase","MockDeleteFolderUseCase","MockFormModelFactory","_config","createTestPresenter","folders","container","Container","foldersCache","ListCache","listFolders","createFolderUseCase","updateFolderUseCase","deleteFolderUseCase","formModelFactory","FoldersContext","FoldersCache","ListFoldersUseCase","CreateFolderUseCase","UpdateFolderUseCase","DeleteFolderUseCase","ListFoldersByParentIdsUseCase","undefined","GetFolderAncestorsUseCase","GetFolderLevelPermissionUseCase","FormModelFactory","StringFormatter","FolderTreePresenterFeature","presenter","Abstraction","describe","it","expect","tree","node","callback","cb1","cb2","unsubscribe","mockForm","resolveDelete","Promise","resolve","deletePromise","f","singleFolder"],"mappings":"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"}