libraryFolders.test.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /**
  2. * Which folders may a folder be moved into (#3305)?
  3. *
  4. * The picker and the drop targets both lean on these: the folder itself and
  5. * its subtree are out (the server would 400 them anyway), external folders are
  6. * out (they mirror a path on disk), and the parent it already has is a no-op.
  7. */
  8. import { describe, it, expect } from 'vitest';
  9. import type { LibraryFolderTree } from '../../api/client';
  10. import { isValidFolderMove, isWritableFolder, moveDisallowedFolderIds } from '../../utils/libraryFolders';
  11. function folder(id: number, overrides: Partial<LibraryFolderTree> = {}): LibraryFolderTree {
  12. return {
  13. id,
  14. name: `Folder ${id}`,
  15. parent_id: null,
  16. project_id: null,
  17. archive_id: null,
  18. project_name: null,
  19. archive_name: null,
  20. is_external: false,
  21. external_path: null,
  22. external_readonly: false,
  23. file_count: 0,
  24. latest_activity_at: null,
  25. created_by_id: null,
  26. shared: false,
  27. can_write: true,
  28. can_rename: true,
  29. can_delete: true,
  30. children: [],
  31. ...overrides,
  32. };
  33. }
  34. // 1
  35. // ├── 2
  36. // │ └── 3
  37. // └── 4
  38. // 5
  39. // 9 (external)
  40. // └── 10
  41. const tree: LibraryFolderTree[] = [
  42. folder(1, {
  43. children: [
  44. folder(2, { parent_id: 1, children: [folder(3, { parent_id: 2 })] }),
  45. folder(4, { parent_id: 1 }),
  46. ],
  47. }),
  48. folder(5),
  49. folder(9, {
  50. is_external: true,
  51. external_path: '/mnt/nas',
  52. children: [folder(10, { parent_id: 9, is_external: true, external_path: '/mnt/nas/sub' })],
  53. }),
  54. ];
  55. describe('moveDisallowedFolderIds', () => {
  56. it('excludes the folder itself, its whole subtree and every external folder', () => {
  57. expect([...moveDisallowedFolderIds(tree, 1)].sort()).toEqual([1, 10, 2, 3, 4, 9].sort());
  58. });
  59. it('leaves ancestors and siblings available', () => {
  60. const disallowed = moveDisallowedFolderIds(tree, 3);
  61. expect(disallowed.has(3)).toBe(true);
  62. expect(disallowed.has(2)).toBe(false);
  63. expect(disallowed.has(1)).toBe(false);
  64. expect(disallowed.has(4)).toBe(false);
  65. expect(disallowed.has(5)).toBe(false);
  66. });
  67. it('still rules out external folders for an id that is not in the tree', () => {
  68. expect([...moveDisallowedFolderIds(tree, 999)].sort()).toEqual([10, 9].sort());
  69. });
  70. });
  71. describe('isValidFolderMove', () => {
  72. const two = tree[0].children[0];
  73. const five = tree[1];
  74. it('allows a nested folder to go to the root', () => {
  75. expect(isValidFolderMove(tree, two, null)).toBe(true);
  76. });
  77. it('treats the current parent as a no-op', () => {
  78. expect(isValidFolderMove(tree, two, 1)).toBe(false);
  79. expect(isValidFolderMove(tree, five, null)).toBe(false);
  80. });
  81. it('refuses itself, a descendant and an external destination', () => {
  82. expect(isValidFolderMove(tree, two, 2)).toBe(false);
  83. expect(isValidFolderMove(tree, two, 3)).toBe(false);
  84. expect(isValidFolderMove(tree, two, 9)).toBe(false);
  85. expect(isValidFolderMove(tree, two, 10)).toBe(false);
  86. });
  87. it('allows a sibling and an unrelated folder', () => {
  88. expect(isValidFolderMove(tree, two, 4)).toBe(true);
  89. expect(isValidFolderMove(tree, two, 5)).toBe(true);
  90. });
  91. it('never moves an external folder', () => {
  92. expect(isValidFolderMove(tree, tree[2], 5)).toBe(false);
  93. expect(isValidFolderMove(tree, tree[2].children[0], null)).toBe(false);
  94. });
  95. it('refuses a destination the user may not write to, and one that is not in the tree', () => {
  96. const readOnly = [...tree, folder(6, { can_write: false })];
  97. expect(isValidFolderMove(readOnly, two, 6)).toBe(false);
  98. expect(isValidFolderMove(tree, two, 999)).toBe(false);
  99. });
  100. });
  101. describe('isWritableFolder', () => {
  102. it('follows the can_write flag the server sends', () => {
  103. expect(isWritableFolder({ can_write: true })).toBe(true);
  104. expect(isWritableFolder({ can_write: false })).toBe(false);
  105. });
  106. });