FileManagerExternalFolder.test.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /**
  2. * Tests for External Folder functionality in FileManagerPage.
  3. */
  4. import { describe, it, expect, beforeEach } from 'vitest';
  5. import { screen, waitFor } from '@testing-library/react';
  6. import userEvent from '@testing-library/user-event';
  7. import { render } from '../utils';
  8. import { FileManagerPage } from '../../pages/FileManagerPage';
  9. import { http, HttpResponse } from 'msw';
  10. import { server } from '../mocks/server';
  11. // Mock data with external folder
  12. const mockFoldersWithExternal = [
  13. {
  14. id: 1,
  15. name: 'Regular Folder',
  16. parent_id: null,
  17. file_count: 3,
  18. project_id: null,
  19. archive_id: null,
  20. project_name: null,
  21. archive_name: null,
  22. is_external: false,
  23. external_path: null,
  24. external_readonly: false,
  25. children: [],
  26. },
  27. {
  28. id: 2,
  29. name: 'NAS Prints',
  30. parent_id: null,
  31. file_count: 5,
  32. project_id: null,
  33. archive_id: null,
  34. project_name: null,
  35. archive_name: null,
  36. is_external: true,
  37. external_path: '/mnt/nas/prints',
  38. external_readonly: true,
  39. children: [],
  40. },
  41. {
  42. id: 3,
  43. name: 'USB Drive',
  44. parent_id: null,
  45. file_count: 2,
  46. project_id: null,
  47. archive_id: null,
  48. project_name: null,
  49. archive_name: null,
  50. is_external: true,
  51. external_path: '/mnt/usb',
  52. external_readonly: false,
  53. children: [],
  54. },
  55. ];
  56. const mockFiles = [
  57. {
  58. id: 1,
  59. filename: 'benchy.3mf',
  60. file_path: '/mnt/nas/prints/benchy.3mf',
  61. file_size: 1048576,
  62. file_type: '3mf',
  63. folder_id: 2,
  64. is_external: true,
  65. thumbnail_path: null,
  66. print_name: 'Benchy',
  67. print_time_seconds: 3600,
  68. print_count: 0,
  69. duplicate_count: 0,
  70. created_at: '2024-01-01T00:00:00Z',
  71. },
  72. ];
  73. const mockStats = {
  74. total_files: 10,
  75. total_folders: 3,
  76. total_size_bytes: 104857600,
  77. disk_free_bytes: 10737418240,
  78. disk_total_bytes: 107374182400,
  79. };
  80. describe('FileManagerPage - External Folders', () => {
  81. beforeEach(() => {
  82. localStorage.clear();
  83. server.use(
  84. http.get('/api/v1/library/folders', () => {
  85. return HttpResponse.json(mockFoldersWithExternal);
  86. }),
  87. http.get('/api/v1/library/files', () => {
  88. return HttpResponse.json(mockFiles);
  89. }),
  90. http.get('/api/v1/library/stats', () => {
  91. return HttpResponse.json(mockStats);
  92. }),
  93. http.get('/api/v1/settings/', () => {
  94. return HttpResponse.json({
  95. check_updates: false,
  96. check_printer_firmware: false,
  97. library_disk_warning_gb: 5,
  98. });
  99. }),
  100. http.post('/api/v1/library/folders/external', async ({ request }) => {
  101. const body = await request.json() as { name: string; external_path: string };
  102. return HttpResponse.json({
  103. id: 10,
  104. name: body.name,
  105. parent_id: null,
  106. is_external: true,
  107. external_path: body.external_path,
  108. external_readonly: true,
  109. external_show_hidden: false,
  110. file_count: 0,
  111. created_at: '2024-01-01T00:00:00Z',
  112. updated_at: '2024-01-01T00:00:00Z',
  113. });
  114. }),
  115. http.post('/api/v1/library/folders/:id/scan', () => {
  116. return HttpResponse.json({ status: 'success', added: 3, removed: 0 });
  117. }),
  118. http.get('/api/v1/projects/', () => {
  119. return HttpResponse.json([]);
  120. }),
  121. http.get('/api/v1/archives/', () => {
  122. return HttpResponse.json([]);
  123. }),
  124. http.delete('/api/v1/library/folders/:id', () => {
  125. return HttpResponse.json({ success: true });
  126. }),
  127. http.delete('/api/v1/library/files/:id', () => {
  128. return HttpResponse.json({ success: true });
  129. }),
  130. http.post('/api/v1/library/files/move', () => {
  131. return HttpResponse.json({ success: true });
  132. }),
  133. );
  134. });
  135. describe('rendering', () => {
  136. it('shows Link External button', async () => {
  137. render(<FileManagerPage />);
  138. await waitFor(() => {
  139. expect(screen.getByText('Link External')).toBeInTheDocument();
  140. });
  141. });
  142. it('shows external folder in sidebar', async () => {
  143. render(<FileManagerPage />);
  144. await waitFor(() => {
  145. expect(screen.getByText('NAS Prints')).toBeInTheDocument();
  146. expect(screen.getByText('USB Drive')).toBeInTheDocument();
  147. });
  148. });
  149. it('shows regular folder alongside external', async () => {
  150. render(<FileManagerPage />);
  151. await waitFor(() => {
  152. expect(screen.getByText('Regular Folder')).toBeInTheDocument();
  153. expect(screen.getByText('NAS Prints')).toBeInTheDocument();
  154. });
  155. });
  156. it('shows read-only indicator for readonly external folders', async () => {
  157. render(<FileManagerPage />);
  158. await waitFor(() => {
  159. // NAS Prints is readonly, should have a lock icon title
  160. const lockIcons = document.querySelectorAll('[title="Read Only"]');
  161. expect(lockIcons.length).toBeGreaterThan(0);
  162. });
  163. });
  164. });
  165. describe('external folder modal', () => {
  166. it('opens modal when Link External clicked', async () => {
  167. const user = userEvent.setup();
  168. render(<FileManagerPage />);
  169. await waitFor(() => {
  170. expect(screen.getByText('Link External')).toBeInTheDocument();
  171. });
  172. await user.click(screen.getByText('Link External'));
  173. await waitFor(() => {
  174. expect(screen.getByText('Link External Folder')).toBeInTheDocument();
  175. });
  176. });
  177. it('modal has name and path fields', async () => {
  178. const user = userEvent.setup();
  179. render(<FileManagerPage />);
  180. await waitFor(() => {
  181. expect(screen.getByText('Link External')).toBeInTheDocument();
  182. });
  183. await user.click(screen.getByText('Link External'));
  184. await waitFor(() => {
  185. expect(screen.getByPlaceholderText('e.g., NAS Prints')).toBeInTheDocument();
  186. expect(screen.getByPlaceholderText('/mnt/nas/3d-prints')).toBeInTheDocument();
  187. });
  188. });
  189. it('modal has readonly checkbox checked by default', async () => {
  190. const user = userEvent.setup();
  191. render(<FileManagerPage />);
  192. await waitFor(() => {
  193. expect(screen.getByText('Link External')).toBeInTheDocument();
  194. });
  195. await user.click(screen.getByText('Link External'));
  196. await waitFor(() => {
  197. const readonlyCheckbox = screen.getByText('Read Only').previousElementSibling as HTMLInputElement;
  198. expect(readonlyCheckbox).toBeChecked();
  199. });
  200. });
  201. it('modal can be closed', async () => {
  202. const user = userEvent.setup();
  203. render(<FileManagerPage />);
  204. await waitFor(() => {
  205. expect(screen.getByText('Link External')).toBeInTheDocument();
  206. });
  207. await user.click(screen.getByText('Link External'));
  208. await waitFor(() => {
  209. expect(screen.getByText('Link External Folder')).toBeInTheDocument();
  210. });
  211. await user.click(screen.getByText('Cancel'));
  212. await waitFor(() => {
  213. expect(screen.queryByText('Link External Folder')).not.toBeInTheDocument();
  214. });
  215. });
  216. });
  217. describe('external folder info bar', () => {
  218. it('shows info bar when external folder selected', async () => {
  219. const user = userEvent.setup();
  220. render(<FileManagerPage />);
  221. await waitFor(() => {
  222. expect(screen.getByText('NAS Prints')).toBeInTheDocument();
  223. });
  224. // Click on NAS Prints folder - there are multiple elements, get the one in the sidebar
  225. const folderElements = screen.getAllByText('NAS Prints');
  226. await user.click(folderElements[0]);
  227. await waitFor(() => {
  228. expect(screen.getByText('External Folder')).toBeInTheDocument();
  229. expect(screen.getByText('/mnt/nas/prints')).toBeInTheDocument();
  230. });
  231. });
  232. it('shows scan button for external folders', async () => {
  233. const user = userEvent.setup();
  234. render(<FileManagerPage />);
  235. await waitFor(() => {
  236. expect(screen.getByText('NAS Prints')).toBeInTheDocument();
  237. });
  238. const folderElements = screen.getAllByText('NAS Prints');
  239. await user.click(folderElements[0]);
  240. await waitFor(() => {
  241. expect(screen.getByText('Scan')).toBeInTheDocument();
  242. });
  243. });
  244. it('does not show info bar for regular folders', async () => {
  245. const user = userEvent.setup();
  246. render(<FileManagerPage />);
  247. await waitFor(() => {
  248. expect(screen.getByText('Regular Folder')).toBeInTheDocument();
  249. });
  250. const folderElements = screen.getAllByText('Regular Folder');
  251. await user.click(folderElements[0]);
  252. // External Folder label should NOT appear
  253. await waitFor(() => {
  254. expect(screen.queryByText('External Folder')).not.toBeInTheDocument();
  255. });
  256. });
  257. });
  258. });