@fgv/ts-web-extras 5.1.0-2 → 5.1.0-21

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.
Files changed (263) hide show
  1. package/dist/packlets/crypto-utils/browserCryptoProvider.js +208 -18
  2. package/dist/packlets/crypto-utils/browserCryptoProvider.js.map +1 -1
  3. package/dist/packlets/file-tree/fileApiTreeAccessors.js +1 -1
  4. package/dist/packlets/file-tree/fileApiTreeAccessors.js.map +1 -1
  5. package/dist/packlets/file-tree/httpTreeAccessors.js +72 -42
  6. package/dist/packlets/file-tree/httpTreeAccessors.js.map +1 -1
  7. package/dist/ts-web-extras.d.ts +59 -7
  8. package/dist/tsdoc-metadata.json +1 -1
  9. package/lib/packlets/crypto-utils/browserCryptoProvider.d.ts +52 -5
  10. package/lib/packlets/crypto-utils/browserCryptoProvider.d.ts.map +1 -1
  11. package/lib/packlets/crypto-utils/browserCryptoProvider.js +207 -17
  12. package/lib/packlets/crypto-utils/browserCryptoProvider.js.map +1 -1
  13. package/lib/packlets/file-tree/fileApiTreeAccessors.d.ts +1 -1
  14. package/lib/packlets/file-tree/fileApiTreeAccessors.js +1 -1
  15. package/lib/packlets/file-tree/fileApiTreeAccessors.js.map +1 -1
  16. package/lib/packlets/file-tree/httpTreeAccessors.d.ts +6 -0
  17. package/lib/packlets/file-tree/httpTreeAccessors.d.ts.map +1 -1
  18. package/lib/packlets/file-tree/httpTreeAccessors.js +72 -42
  19. package/lib/packlets/file-tree/httpTreeAccessors.js.map +1 -1
  20. package/package.json +27 -26
  21. package/.rush/temp/chunked-rush-logs/ts-web-extras.build.chunks.jsonl +0 -75
  22. package/.rush/temp/chunked-rush-logs/ts-web-extras.test.chunks.jsonl +0 -75
  23. package/.rush/temp/operation/build/all.log +0 -75
  24. package/.rush/temp/operation/build/error.log +0 -18
  25. package/.rush/temp/operation/build/log-chunks.jsonl +0 -75
  26. package/.rush/temp/operation/build/state.json +0 -3
  27. package/.rush/temp/operation/test/all.log +0 -75
  28. package/.rush/temp/operation/test/error.log +0 -18
  29. package/.rush/temp/operation/test/log-chunks.jsonl +0 -75
  30. package/.rush/temp/operation/test/state.json +0 -3
  31. package/.rush/temp/shrinkwrap-deps.json +0 -635
  32. package/CHANGELOG.md +0 -23
  33. package/config/api-extractor.json +0 -343
  34. package/config/jest.config.json +0 -19
  35. package/config/rig.json +0 -16
  36. package/config/typedoc.json +0 -6
  37. package/dist/test/mocks/idb-keyval.js +0 -6
  38. package/dist/test/mocks/idb-keyval.js.map +0 -1
  39. package/dist/test/setupTests.js +0 -74
  40. package/dist/test/setupTests.js.map +0 -1
  41. package/dist/test/unit/browserHashProvider.test.js +0 -140
  42. package/dist/test/unit/browserHashProvider.test.js.map +0 -1
  43. package/dist/test/unit/directoryHandleStore.test.js +0 -190
  44. package/dist/test/unit/directoryHandleStore.test.js.map +0 -1
  45. package/dist/test/unit/fileApiTreeAccessors.test.js +0 -1188
  46. package/dist/test/unit/fileApiTreeAccessors.test.js.map +0 -1
  47. package/dist/test/unit/fileApiTypes.test.js +0 -472
  48. package/dist/test/unit/fileApiTypes.test.js.map +0 -1
  49. package/dist/test/unit/fileSystemAccessTreeAccessors.test.js +0 -622
  50. package/dist/test/unit/fileSystemAccessTreeAccessors.test.js.map +0 -1
  51. package/dist/test/unit/fileTreeHelpers.test.js +0 -590
  52. package/dist/test/unit/fileTreeHelpers.test.js.map +0 -1
  53. package/dist/test/unit/httpTreeAccessors.test.js +0 -1229
  54. package/dist/test/unit/httpTreeAccessors.test.js.map +0 -1
  55. package/dist/test/unit/localStorageTreeAccessors.test.js +0 -812
  56. package/dist/test/unit/localStorageTreeAccessors.test.js.map +0 -1
  57. package/dist/test/unit/urlParams.test.js +0 -393
  58. package/dist/test/unit/urlParams.test.js.map +0 -1
  59. package/dist/test/utils/fileSystemAccessMocks.js +0 -271
  60. package/dist/test/utils/fileSystemAccessMocks.js.map +0 -1
  61. package/dist/test/utils/testHelpers.js +0 -124
  62. package/dist/test/utils/testHelpers.js.map +0 -1
  63. package/docs/@fgv/namespaces/CryptoUtils/README.md +0 -18
  64. package/docs/@fgv/namespaces/CryptoUtils/classes/BrowserCryptoProvider.md +0 -203
  65. package/docs/@fgv/namespaces/CryptoUtils/classes/BrowserHashProvider.md +0 -63
  66. package/docs/@fgv/namespaces/CryptoUtils/functions/createBrowserCryptoProvider.md +0 -18
  67. package/docs/@fgv/namespaces/FileTreeHelpers/README.md +0 -19
  68. package/docs/@fgv/namespaces/FileTreeHelpers/functions/extractFileListMetadata.md +0 -23
  69. package/docs/@fgv/namespaces/FileTreeHelpers/functions/extractFileMetadata.md +0 -23
  70. package/docs/@fgv/namespaces/FileTreeHelpers/functions/fromDirectoryUpload.md +0 -33
  71. package/docs/@fgv/namespaces/FileTreeHelpers/functions/fromFileList.md +0 -33
  72. package/docs/@fgv/namespaces/FileTreeHelpers/functions/getOriginalFile.md +0 -25
  73. package/docs/@fgv/namespaces/FileTreeHelpers/variables/defaultFileApiTreeInitParams.md +0 -11
  74. package/docs/README.md +0 -78
  75. package/docs/classes/DirectoryHandleStore.md +0 -116
  76. package/docs/classes/FileApiTreeAccessors.md +0 -286
  77. package/docs/classes/FileSystemAccessTreeAccessors.md +0 -557
  78. package/docs/classes/HttpTreeAccessors.md +0 -508
  79. package/docs/classes/LocalStorageTreeAccessors.md +0 -520
  80. package/docs/functions/exportAsJson.md +0 -23
  81. package/docs/functions/exportUsingFileSystemAPI.md +0 -26
  82. package/docs/functions/extractDirectoryPath.md +0 -23
  83. package/docs/functions/isDirectoryHandle.md +0 -23
  84. package/docs/functions/isFileHandle.md +0 -23
  85. package/docs/functions/isFilePath.md +0 -21
  86. package/docs/functions/parseContextFilter.md +0 -22
  87. package/docs/functions/parseQualifierDefaults.md +0 -22
  88. package/docs/functions/parseResourceTypes.md +0 -22
  89. package/docs/functions/parseUrlParameters.md +0 -15
  90. package/docs/functions/safeShowDirectoryPicker.md +0 -24
  91. package/docs/functions/safeShowOpenFilePicker.md +0 -24
  92. package/docs/functions/safeShowSaveFilePicker.md +0 -24
  93. package/docs/functions/supportsFileSystemAccess.md +0 -23
  94. package/docs/interfaces/FilePickerAcceptType.md +0 -16
  95. package/docs/interfaces/FileSystemCreateWritableOptions.md +0 -15
  96. package/docs/interfaces/FileSystemDirectoryHandle.md +0 -187
  97. package/docs/interfaces/FileSystemFileHandle.md +0 -106
  98. package/docs/interfaces/FileSystemGetDirectoryOptions.md +0 -15
  99. package/docs/interfaces/FileSystemGetFileOptions.md +0 -15
  100. package/docs/interfaces/FileSystemHandle.md +0 -69
  101. package/docs/interfaces/FileSystemHandlePermissionDescriptor.md +0 -15
  102. package/docs/interfaces/FileSystemRemoveOptions.md +0 -15
  103. package/docs/interfaces/FileSystemWritableFileStream.md +0 -127
  104. package/docs/interfaces/IDirectoryHandleTreeInitializer.md +0 -17
  105. package/docs/interfaces/IFileHandleTreeInitializer.md +0 -16
  106. package/docs/interfaces/IFileListTreeInitializer.md +0 -15
  107. package/docs/interfaces/IFileMetadata.md +0 -19
  108. package/docs/interfaces/IFileSystemAccessTreeParams.md +0 -30
  109. package/docs/interfaces/IFsAccessApis.md +0 -57
  110. package/docs/interfaces/IHttpTreeParams.md +0 -32
  111. package/docs/interfaces/ILocalStorageTreeParams.md +0 -30
  112. package/docs/interfaces/IUrlConfigOptions.md +0 -27
  113. package/docs/interfaces/ShowDirectoryPickerOptions.md +0 -17
  114. package/docs/interfaces/ShowOpenFilePickerOptions.md +0 -19
  115. package/docs/interfaces/ShowSaveFilePickerOptions.md +0 -19
  116. package/docs/type-aliases/TreeInitializer.md +0 -11
  117. package/docs/type-aliases/WellKnownDirectory.md +0 -11
  118. package/docs/type-aliases/WindowWithFsAccess.md +0 -11
  119. package/docs/variables/DEFAULT_DIRECTORY_HANDLE_DB.md +0 -11
  120. package/docs/variables/DEFAULT_DIRECTORY_HANDLE_STORE.md +0 -11
  121. package/etc/ts-web-extras.api.md +0 -433
  122. package/lib/test/mocks/idb-keyval.d.ts +0 -6
  123. package/lib/test/mocks/idb-keyval.d.ts.map +0 -1
  124. package/lib/test/mocks/idb-keyval.js +0 -9
  125. package/lib/test/mocks/idb-keyval.js.map +0 -1
  126. package/lib/test/setupTests.d.ts +0 -2
  127. package/lib/test/setupTests.d.ts.map +0 -1
  128. package/lib/test/setupTests.js +0 -76
  129. package/lib/test/setupTests.js.map +0 -1
  130. package/lib/test/unit/browserHashProvider.test.d.ts +0 -2
  131. package/lib/test/unit/browserHashProvider.test.d.ts.map +0 -1
  132. package/lib/test/unit/browserHashProvider.test.js +0 -142
  133. package/lib/test/unit/browserHashProvider.test.js.map +0 -1
  134. package/lib/test/unit/directoryHandleStore.test.d.ts +0 -2
  135. package/lib/test/unit/directoryHandleStore.test.d.ts.map +0 -1
  136. package/lib/test/unit/directoryHandleStore.test.js +0 -192
  137. package/lib/test/unit/directoryHandleStore.test.js.map +0 -1
  138. package/lib/test/unit/fileApiTreeAccessors.test.d.ts +0 -2
  139. package/lib/test/unit/fileApiTreeAccessors.test.d.ts.map +0 -1
  140. package/lib/test/unit/fileApiTreeAccessors.test.js +0 -1190
  141. package/lib/test/unit/fileApiTreeAccessors.test.js.map +0 -1
  142. package/lib/test/unit/fileApiTypes.test.d.ts +0 -2
  143. package/lib/test/unit/fileApiTypes.test.d.ts.map +0 -1
  144. package/lib/test/unit/fileApiTypes.test.js +0 -474
  145. package/lib/test/unit/fileApiTypes.test.js.map +0 -1
  146. package/lib/test/unit/fileSystemAccessTreeAccessors.test.d.ts +0 -2
  147. package/lib/test/unit/fileSystemAccessTreeAccessors.test.d.ts.map +0 -1
  148. package/lib/test/unit/fileSystemAccessTreeAccessors.test.js +0 -624
  149. package/lib/test/unit/fileSystemAccessTreeAccessors.test.js.map +0 -1
  150. package/lib/test/unit/fileTreeHelpers.test.d.ts +0 -2
  151. package/lib/test/unit/fileTreeHelpers.test.d.ts.map +0 -1
  152. package/lib/test/unit/fileTreeHelpers.test.js +0 -592
  153. package/lib/test/unit/fileTreeHelpers.test.js.map +0 -1
  154. package/lib/test/unit/httpTreeAccessors.test.d.ts +0 -2
  155. package/lib/test/unit/httpTreeAccessors.test.d.ts.map +0 -1
  156. package/lib/test/unit/httpTreeAccessors.test.js +0 -1231
  157. package/lib/test/unit/httpTreeAccessors.test.js.map +0 -1
  158. package/lib/test/unit/localStorageTreeAccessors.test.d.ts +0 -2
  159. package/lib/test/unit/localStorageTreeAccessors.test.d.ts.map +0 -1
  160. package/lib/test/unit/localStorageTreeAccessors.test.js +0 -814
  161. package/lib/test/unit/localStorageTreeAccessors.test.js.map +0 -1
  162. package/lib/test/unit/urlParams.test.d.ts +0 -2
  163. package/lib/test/unit/urlParams.test.d.ts.map +0 -1
  164. package/lib/test/unit/urlParams.test.js +0 -395
  165. package/lib/test/unit/urlParams.test.js.map +0 -1
  166. package/lib/test/utils/fileSystemAccessMocks.d.ts +0 -53
  167. package/lib/test/utils/fileSystemAccessMocks.d.ts.map +0 -1
  168. package/lib/test/utils/fileSystemAccessMocks.js +0 -277
  169. package/lib/test/utils/fileSystemAccessMocks.js.map +0 -1
  170. package/lib/test/utils/testHelpers.d.ts +0 -51
  171. package/lib/test/utils/testHelpers.d.ts.map +0 -1
  172. package/lib/test/utils/testHelpers.js +0 -133
  173. package/lib/test/utils/testHelpers.js.map +0 -1
  174. package/rush-logs/ts-web-extras.build.cache.log +0 -0
  175. package/rush-logs/ts-web-extras.build.error.log +0 -18
  176. package/rush-logs/ts-web-extras.build.log +0 -75
  177. package/rush-logs/ts-web-extras.test.cache.log +0 -1
  178. package/rush-logs/ts-web-extras.test.error.log +0 -18
  179. package/rush-logs/ts-web-extras.test.log +0 -75
  180. package/src/index.browser.ts +0 -24
  181. package/src/index.ts +0 -47
  182. package/src/packlets/crypto-utils/browserCryptoProvider.ts +0 -311
  183. package/src/packlets/crypto-utils/browserHashProvider.ts +0 -73
  184. package/src/packlets/crypto-utils/index.ts +0 -29
  185. package/src/packlets/file-api-types/index.ts +0 -366
  186. package/src/packlets/file-tree/directoryHandleStore.ts +0 -136
  187. package/src/packlets/file-tree/fileApiTreeAccessors.ts +0 -528
  188. package/src/packlets/file-tree/fileSystemAccessTreeAccessors.ts +0 -519
  189. package/src/packlets/file-tree/httpTreeAccessors.ts +0 -448
  190. package/src/packlets/file-tree/index.ts +0 -32
  191. package/src/packlets/file-tree/localStorageTreeAccessors.ts +0 -430
  192. package/src/packlets/helpers/fileTreeHelpers.ts +0 -107
  193. package/src/packlets/helpers/index.ts +0 -28
  194. package/src/packlets/url-utils/index.ts +0 -28
  195. package/src/packlets/url-utils/urlParams.ts +0 -245
  196. package/src/test/mocks/idb-keyval.ts +0 -5
  197. package/src/test/setupTests.ts +0 -87
  198. package/src/test/unit/browserHashProvider.test.ts +0 -155
  199. package/src/test/unit/browserHashProvider.test.ts.bak +0 -376
  200. package/src/test/unit/directoryHandleStore.test.ts +0 -251
  201. package/src/test/unit/fileApiTreeAccessors.test.ts +0 -1387
  202. package/src/test/unit/fileApiTypes.test.ts +0 -587
  203. package/src/test/unit/fileSystemAccessTreeAccessors.test.ts +0 -885
  204. package/src/test/unit/fileTreeHelpers.test.ts +0 -694
  205. package/src/test/unit/httpTreeAccessors.test.ts +0 -1571
  206. package/src/test/unit/localStorageTreeAccessors.test.ts +0 -1014
  207. package/src/test/unit/urlParams.test.ts +0 -464
  208. package/src/test/utils/fileSystemAccessMocks.ts +0 -353
  209. package/src/test/utils/testHelpers.ts +0 -155
  210. package/temp/build/typescript/ts_8nwakTlr.json +0 -1
  211. package/temp/coverage/base.css +0 -224
  212. package/temp/coverage/block-navigation.js +0 -87
  213. package/temp/coverage/crypto/browserHashProvider.ts.html +0 -304
  214. package/temp/coverage/crypto/index.html +0 -116
  215. package/temp/coverage/crypto-utils/browserCryptoProvider.ts.html +0 -1018
  216. package/temp/coverage/crypto-utils/browserHashProvider.ts.html +0 -304
  217. package/temp/coverage/crypto-utils/index.html +0 -131
  218. package/temp/coverage/favicon.png +0 -0
  219. package/temp/coverage/file-tree/directoryHandleStore.ts.html +0 -493
  220. package/temp/coverage/file-tree/fileApiTreeAccessors.ts.html +0 -1669
  221. package/temp/coverage/file-tree/fileSystemAccessTreeAccessors.ts.html +0 -1642
  222. package/temp/coverage/file-tree/httpTreeAccessors.ts.html +0 -1429
  223. package/temp/coverage/file-tree/index.html +0 -176
  224. package/temp/coverage/file-tree/localStorageTreeAccessors.ts.html +0 -1375
  225. package/temp/coverage/helpers/fileTreeHelpers.ts.html +0 -406
  226. package/temp/coverage/helpers/index.html +0 -116
  227. package/temp/coverage/index.html +0 -161
  228. package/temp/coverage/lcov-report/base.css +0 -224
  229. package/temp/coverage/lcov-report/block-navigation.js +0 -87
  230. package/temp/coverage/lcov-report/crypto/browserHashProvider.ts.html +0 -304
  231. package/temp/coverage/lcov-report/crypto/index.html +0 -116
  232. package/temp/coverage/lcov-report/crypto-utils/browserCryptoProvider.ts.html +0 -1018
  233. package/temp/coverage/lcov-report/crypto-utils/browserHashProvider.ts.html +0 -304
  234. package/temp/coverage/lcov-report/crypto-utils/index.html +0 -131
  235. package/temp/coverage/lcov-report/favicon.png +0 -0
  236. package/temp/coverage/lcov-report/file-tree/directoryHandleStore.ts.html +0 -493
  237. package/temp/coverage/lcov-report/file-tree/fileApiTreeAccessors.ts.html +0 -1669
  238. package/temp/coverage/lcov-report/file-tree/fileSystemAccessTreeAccessors.ts.html +0 -1642
  239. package/temp/coverage/lcov-report/file-tree/httpTreeAccessors.ts.html +0 -1429
  240. package/temp/coverage/lcov-report/file-tree/index.html +0 -176
  241. package/temp/coverage/lcov-report/file-tree/localStorageTreeAccessors.ts.html +0 -1375
  242. package/temp/coverage/lcov-report/helpers/fileTreeHelpers.ts.html +0 -406
  243. package/temp/coverage/lcov-report/helpers/index.html +0 -116
  244. package/temp/coverage/lcov-report/index.html +0 -161
  245. package/temp/coverage/lcov-report/prettify.css +0 -1
  246. package/temp/coverage/lcov-report/prettify.js +0 -2
  247. package/temp/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  248. package/temp/coverage/lcov-report/sorter.js +0 -210
  249. package/temp/coverage/lcov-report/url-utils/index.html +0 -116
  250. package/temp/coverage/lcov-report/url-utils/urlParams.ts.html +0 -820
  251. package/temp/coverage/lcov.info +0 -3597
  252. package/temp/coverage/prettify.css +0 -1
  253. package/temp/coverage/prettify.js +0 -2
  254. package/temp/coverage/sort-arrow-sprite.png +0 -0
  255. package/temp/coverage/sorter.js +0 -210
  256. package/temp/coverage/url-utils/index.html +0 -116
  257. package/temp/coverage/url-utils/urlParams.ts.html +0 -820
  258. package/temp/test/jest/haste-map-b931e4e63102f86c5bd4949f7dced44f-9d713eb41149188b4e5c0ae3d86d0a57-2ad8e16b24e391b8cdbe50b55c137169 +0 -0
  259. package/temp/test/jest/jest-transform-cache-b931e4e63102f86c5bd4949f7dced44f-79ef2876fae7ca75eedb2aa53dc48338/b5/package_b5f57afc9ec2c011239b1608ee5bdfa5 +0 -53
  260. package/temp/test/jest/perf-cache-b931e4e63102f86c5bd4949f7dced44f-da39a3ee5e6b4b0d3255bfef95601890 +0 -1
  261. package/temp/ts-web-extras.api.json +0 -8850
  262. package/temp/ts-web-extras.api.md +0 -433
  263. package/tsconfig.json +0 -7
@@ -1,1387 +0,0 @@
1
- /*
2
- * Copyright (c) 2025 Erik Fortune
3
- *
4
- * Permission is hereby granted, free of charge, to any person obtaining a copy
5
- * of this software and associated documentation files (the "Software"), to deal
6
- * in the Software without restriction, including without limitation the rights
7
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- * copies of the Software, and to permit persons to whom the Software is
9
- * furnished to do so, subject to the following conditions:
10
- *
11
- * The above copyright notice and this permission notice shall be included in all
12
- * copies or substantial portions of the Software.
13
- *
14
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- * SOFTWARE.
21
- */
22
-
23
- import '@fgv/ts-utils-jest';
24
- import { FileApiTreeAccessors } from '../../packlets/file-tree';
25
- import { FileTree } from '@fgv/ts-json-base';
26
- import { FileTreeHelpers } from '../../packlets/helpers';
27
- import {
28
- createMockFile,
29
- createMockFileList,
30
- createMockDirectoryFileList,
31
- verifyFileAPI
32
- } from '../utils/testHelpers';
33
-
34
- // ---- Minimal mock fetch helpers for createFromHttp tests ----
35
-
36
- interface IHttpMockResponse {
37
- ok: boolean;
38
- status?: number;
39
- jsonValue?: unknown;
40
- textValue?: string;
41
- throwOnJson?: boolean;
42
- }
43
-
44
- function makeMockHttpResponse(options: IHttpMockResponse): Response {
45
- const { ok, status = ok ? 200 : 400, jsonValue, textValue, throwOnJson } = options;
46
- return {
47
- ok,
48
- status,
49
- json: throwOnJson
50
- ? () => Promise.reject(new Error('JSON parse error'))
51
- : () => Promise.resolve(jsonValue),
52
- text: () => Promise.resolve(textValue ?? `HTTP ${status}`)
53
- } as unknown as Response;
54
- }
55
-
56
- function makeMockHttpFetch(responses: IHttpMockResponse[]): typeof fetch {
57
- let callIndex = 0;
58
- return (url: string | URL | Request, _init?: RequestInit): Promise<Response> => {
59
- const response = responses[callIndex++];
60
- if (response === undefined) {
61
- return Promise.reject(new Error(`Unexpected fetch call to ${url.toString()}`));
62
- }
63
- return Promise.resolve(makeMockHttpResponse(response));
64
- };
65
- }
66
-
67
- describe('FileApiTreeAccessors', () => {
68
- describe('Static factory methods', () => {
69
- describe('create', () => {
70
- test('creates FileTree from FileList using create method', async () => {
71
- const fileList = createMockFileList([{ name: 'test.txt', content: 'test content' }]);
72
-
73
- const initializers = [{ fileList }];
74
- const result = await FileApiTreeAccessors.create(initializers);
75
- expect(result).toSucceedAndSatisfy((fileTree) => {
76
- // Verify the FileTree was created successfully
77
- expect(fileTree).toBeDefined();
78
-
79
- // Test file access with absolute path (FileTree expects leading slash)
80
- const fileResult = fileTree.getFile('/test.txt');
81
- expect(fileResult).toSucceed();
82
- });
83
- });
84
-
85
- test('creates FileTree with prefix (basic functionality)', async () => {
86
- const fileList = createMockFileList([{ name: 'test.txt', content: 'test content' }]);
87
-
88
- const initializers = [{ fileList }];
89
- const result = await FileApiTreeAccessors.create(initializers, { prefix: '/prefix' });
90
- // Just verify the creation succeeds - the prefix handling is complex
91
- expect(result).toSucceed();
92
- });
93
-
94
- test('handles multiple files', async () => {
95
- const fileList = createMockFileList([
96
- { name: 'file1.txt', content: 'content1' },
97
- { name: 'file2.txt', content: 'content2' }
98
- ]);
99
-
100
- const initializers = [{ fileList }];
101
- const result = await FileApiTreeAccessors.create(initializers);
102
- expect(result).toSucceedAndSatisfy((fileTree) => {
103
- expect(fileTree.getFile('/file1.txt')).toSucceed();
104
- expect(fileTree.getFile('/file2.txt')).toSucceed();
105
- });
106
- });
107
-
108
- test('handles nested directory structure', async () => {
109
- const fileList = createMockDirectoryFileList([
110
- { path: 'src/index.js', content: 'console.log("hello");' },
111
- { path: 'config/config.json', content: '{"name": "test"}' }
112
- ]);
113
-
114
- const initializers = [{ fileList }];
115
- const result = await FileApiTreeAccessors.create(initializers);
116
- expect(result).toSucceedAndSatisfy((fileTree) => {
117
- expect(fileTree.getFile('/src/index.js')).toSucceed();
118
- expect(fileTree.getFile('/config/config.json')).toSucceed();
119
-
120
- // Check directory access
121
- expect(fileTree.getDirectory('/src')).toSucceed();
122
- expect(fileTree.getDirectory('/config')).toSucceed();
123
- });
124
- });
125
-
126
- test('fails when file reading fails', async () => {
127
- // Create a file object without text method to simulate failure
128
- const badFile = {
129
- name: 'bad.txt',
130
- size: 10,
131
- type: 'text/plain',
132
- lastModified: Date.now(),
133
- text: () => Promise.reject(new Error('File read error'))
134
- } as unknown as File;
135
-
136
- // Create FileList with bad file
137
- const dt = new DataTransfer();
138
- dt.items.add(badFile);
139
- const fileList = dt.files;
140
-
141
- const initializers = [{ fileList }];
142
- const result = await FileApiTreeAccessors.create(initializers);
143
- expect(result).toFailWith(/Failed to read file bad\.txt/);
144
- });
145
-
146
- test('handles empty files array', async () => {
147
- const fileList = createMockFileList([]);
148
- const initializers = [{ fileList }];
149
- const result = await FileApiTreeAccessors.create(initializers);
150
- expect(result).toSucceedAndSatisfy((fileTree) => {
151
- expect(fileTree).toBeDefined();
152
- });
153
- });
154
-
155
- test('normalizes paths correctly (critical bug fix verification)', async () => {
156
- const fileList = createMockFileList([{ name: 'test.txt', content: 'content' }]);
157
-
158
- const initializers = [{ fileList }];
159
- const result = await FileApiTreeAccessors.create(initializers);
160
- expect(result).toSucceedAndSatisfy((fileTree) => {
161
- // Path should be normalized to start with / for FileTree compatibility
162
- const fileResult = fileTree.getFile('/test.txt');
163
- expect(fileResult).toSucceed();
164
-
165
- // Content should be accessible
166
- const content = fileTree.hal.getFileContents('/test.txt');
167
- expect(content).toSucceedWith('content');
168
- });
169
- });
170
-
171
- test('normalizes paths with prefix correctly', async () => {
172
- const fileList = createMockDirectoryFileList([{ path: 'subdir/test.txt', content: 'content' }]);
173
-
174
- const initializers = [{ fileList }];
175
- const result = await FileApiTreeAccessors.create(initializers, { prefix: '/prefix' });
176
- // Just verify the creation succeeds with nested paths
177
- expect(result).toSucceed();
178
- });
179
-
180
- test('handles paths with leading slash correctly', async () => {
181
- const fileList = createMockDirectoryFileList([
182
- { path: 'already/absolute.txt', content: 'absolute content' },
183
- { path: 'relative/path.txt', content: 'relative content' }
184
- ]);
185
-
186
- const initializers = [{ fileList }];
187
- const result = await FileApiTreeAccessors.create(initializers);
188
- expect(result).toSucceedAndSatisfy((fileTree) => {
189
- // Both paths should be normalized to start with /
190
- expect(fileTree.getFile('/already/absolute.txt')).toSucceedAndSatisfy((file) => {
191
- expect(file.name).toBe('absolute.txt');
192
- });
193
- expect(fileTree.getFile('/relative/path.txt')).toSucceedAndSatisfy((file) => {
194
- expect(file.name).toBe('path.txt');
195
- });
196
-
197
- // Verify content is accessible
198
- expect(fileTree.hal.getFileContents('/already/absolute.txt')).toSucceedWith('absolute content');
199
- expect(fileTree.hal.getFileContents('/relative/path.txt')).toSucceedWith('relative content');
200
- });
201
- });
202
- });
203
-
204
- describe('fromFileList', () => {
205
- test('creates FileTree from FileList', async () => {
206
- const fileList = createMockFileList([
207
- { name: 'file1.txt', content: 'content1' },
208
- { name: 'file2.txt', content: 'content2' }
209
- ]);
210
-
211
- const result = await FileApiTreeAccessors.fromFileList(fileList);
212
- expect(result).toSucceedAndSatisfy((fileTree) => {
213
- expect(fileTree.getFile('/file1.txt')).toSucceed();
214
- expect(fileTree.getFile('/file2.txt')).toSucceed();
215
- });
216
- });
217
-
218
- test('creates FileTree from FileList with prefix', async () => {
219
- const fileList = createMockFileList([{ name: 'test.txt', content: 'content' }]);
220
-
221
- const result = await FileApiTreeAccessors.fromFileList(fileList, { prefix: '/uploads' });
222
- // Just verify the creation succeeds with prefix
223
- expect(result).toSucceed();
224
- });
225
-
226
- test('handles empty FileList', async () => {
227
- const fileList = createMockFileList([]);
228
- const result = await FileApiTreeAccessors.fromFileList(fileList);
229
- expect(result).toSucceed();
230
- });
231
-
232
- test('verifies File API compatibility', async () => {
233
- const fileList = createMockFileList([{ name: 'test.txt', content: 'test content' }]);
234
-
235
- // Verify our mock files have the correct API
236
- for (let i = 0; i < fileList.length; i++) {
237
- const file = fileList[i];
238
- const isValid = await verifyFileAPI(file);
239
- expect(isValid).toBe(true);
240
- }
241
- });
242
- });
243
-
244
- describe('fromDirectoryUpload', () => {
245
- test('creates FileTree from directory structure', async () => {
246
- const fileList = createMockDirectoryFileList([
247
- { path: 'project/src/index.js', content: 'console.log("main");' },
248
- { path: 'project/src/utils.js', content: 'export const helper = () => {};' },
249
- { path: 'project/package.json', content: '{"name": "test-project"}' }
250
- ]);
251
-
252
- const result = await FileApiTreeAccessors.fromDirectoryUpload(fileList);
253
- expect(result).toSucceedAndSatisfy((fileTree) => {
254
- expect(fileTree.getFile('/project/src/index.js')).toSucceed();
255
- expect(fileTree.getFile('/project/src/utils.js')).toSucceed();
256
- expect(fileTree.getFile('/project/package.json')).toSucceed();
257
-
258
- // Check directory structure
259
- expect(fileTree.getDirectory('/project')).toSucceed();
260
- expect(fileTree.getDirectory('/project/src')).toSucceed();
261
- });
262
- });
263
-
264
- test('creates FileTree from directory with prefix', async () => {
265
- const fileList = createMockDirectoryFileList([
266
- { path: 'app/config.json', content: '{"setting": "value"}' }
267
- ]);
268
-
269
- const result = await FileApiTreeAccessors.fromDirectoryUpload(fileList, { prefix: '/upload' });
270
- // Just verify the creation succeeds with directory prefix
271
- expect(result).toSucceed();
272
- });
273
-
274
- test('handles nested directory structures', async () => {
275
- const fileList = createMockDirectoryFileList([
276
- { path: 'deep/nested/structure/file.txt', content: 'deep content' },
277
- { path: 'deep/other/file.txt', content: 'other content' }
278
- ]);
279
-
280
- const result = await FileApiTreeAccessors.fromDirectoryUpload(fileList);
281
- expect(result).toSucceedAndSatisfy((fileTree) => {
282
- expect(fileTree.getFile('/deep/nested/structure/file.txt')).toSucceed();
283
- expect(fileTree.getFile('/deep/other/file.txt')).toSucceed();
284
-
285
- expect(fileTree.getDirectory('/deep')).toSucceed();
286
- expect(fileTree.getDirectory('/deep/nested')).toSucceed();
287
- expect(fileTree.getDirectory('/deep/nested/structure')).toSucceed();
288
- expect(fileTree.getDirectory('/deep/other')).toSucceed();
289
- });
290
- });
291
- });
292
-
293
- describe('getOriginalFile', () => {
294
- test('finds file by exact path match', () => {
295
- const fileList = createMockFileList([
296
- { name: 'target.txt', content: 'target content' },
297
- { name: 'other.txt', content: 'other content' }
298
- ]);
299
-
300
- const result = FileApiTreeAccessors.getOriginalFile(fileList, 'target.txt');
301
- expect(result).toSucceedAndSatisfy((file) => {
302
- expect(file.name).toBe('target.txt');
303
- });
304
- });
305
-
306
- test('finds file by webkitRelativePath', () => {
307
- const fileList = createMockDirectoryFileList([{ path: 'folder/file.txt', content: 'content' }]);
308
-
309
- const result = FileApiTreeAccessors.getOriginalFile(fileList, 'folder/file.txt');
310
- expect(result).toSucceedAndSatisfy((file) => {
311
- expect((file as any).webkitRelativePath).toBe('folder/file.txt');
312
- });
313
- });
314
-
315
- test('fails for non-existent file', () => {
316
- const fileList = createMockFileList([{ name: 'exists.txt', content: 'content' }]);
317
-
318
- const result = FileApiTreeAccessors.getOriginalFile(fileList, 'missing.txt');
319
- expect(result).toFailWith(/File not found: missing\.txt/);
320
- });
321
-
322
- test('handles empty FileList', () => {
323
- const fileList = createMockFileList([]);
324
- const result = FileApiTreeAccessors.getOriginalFile(fileList, 'any.txt');
325
- expect(result).toFailWith(/File not found/);
326
- });
327
- });
328
-
329
- describe('extractFileMetadata', () => {
330
- test('extracts metadata from regular files', () => {
331
- const testTime = Date.now();
332
- const file = createMockFile({
333
- name: 'test.txt',
334
- content: 'content',
335
- type: 'text/plain',
336
- lastModified: testTime
337
- });
338
-
339
- const metadata = FileApiTreeAccessors.extractFileMetadata(file);
340
- expect(metadata).toEqual({
341
- path: 'test.txt',
342
- name: 'test.txt',
343
- size: expect.any(Number),
344
- type: 'text/plain',
345
- lastModified: testTime
346
- });
347
- });
348
-
349
- test('extracts metadata with webkitRelativePath', () => {
350
- const file = createMockFile({
351
- name: 'file.txt',
352
- content: 'content',
353
- type: 'text/plain',
354
- webkitRelativePath: 'folder/file.txt'
355
- });
356
-
357
- const metadata = FileApiTreeAccessors.extractFileMetadata(file);
358
- expect(metadata).toEqual({
359
- path: 'folder/file.txt',
360
- name: 'file.txt',
361
- size: expect.any(Number),
362
- type: 'text/plain',
363
- lastModified: expect.any(Number)
364
- });
365
- });
366
-
367
- test('handles multiple files with different types', () => {
368
- const files = [
369
- createMockFile({ name: 'text.txt', content: 'text', type: 'text/plain' }),
370
- createMockFile({ name: 'data.json', content: '{}', type: 'application/json' }),
371
- createMockFile({ name: 'script.js', content: 'console.log("hi");', type: 'application/javascript' })
372
- ];
373
-
374
- const metadata = files.map((file) => FileApiTreeAccessors.extractFileMetadata(file));
375
- expect(metadata).toHaveLength(3);
376
-
377
- expect(metadata.find((m) => m.name === 'text.txt')?.type).toBe('text/plain');
378
- expect(metadata.find((m) => m.name === 'data.json')?.type).toBe('application/json');
379
- expect(metadata.find((m) => m.name === 'script.js')?.type).toBe('application/javascript');
380
- });
381
-
382
- test('handles empty file array', () => {
383
- const files: File[] = [];
384
- const metadata = files.map((file) => FileApiTreeAccessors.extractFileMetadata(file));
385
- expect(metadata).toHaveLength(0);
386
- });
387
-
388
- test('includes file sizes', () => {
389
- const shortContent = 'hi';
390
- const longContent = 'a'.repeat(1000);
391
-
392
- const files = [
393
- createMockFile({ name: 'short.txt', content: shortContent }),
394
- createMockFile({ name: 'long.txt', content: longContent })
395
- ];
396
-
397
- const metadata = files.map((file) => FileApiTreeAccessors.extractFileMetadata(file));
398
- const shortMeta = metadata.find((m) => m.name === 'short.txt');
399
- const longMeta = metadata.find((m) => m.name === 'long.txt');
400
-
401
- expect(shortMeta?.size).toBeGreaterThan(0);
402
- expect(longMeta?.size).toBeGreaterThan(shortMeta?.size!);
403
- });
404
- });
405
- });
406
-
407
- describe('Integration with FileTree', () => {
408
- test('created FileTree supports standard operations', async () => {
409
- const fileList = createMockDirectoryFileList([
410
- { path: 'config/data.json', content: '{"key": "value"}' },
411
- { path: 'docs/readme.txt', content: 'This is a readme file' }
412
- ]);
413
-
414
- const initializers = [{ fileList }];
415
- const result = await FileApiTreeAccessors.create(initializers);
416
- expect(result).toSucceedAndSatisfy((fileTree) => {
417
- // Test file access
418
- const jsonFile = fileTree.getFile('/config/data.json');
419
- expect(jsonFile).toSucceedAndSatisfy((file) => {
420
- expect(file.name).toBe('data.json');
421
- expect(file.extension).toBe('.json');
422
- expect(file.baseName).toBe('data');
423
- });
424
-
425
- // Test directory access
426
- const configDir = fileTree.getDirectory('/config');
427
- expect(configDir).toSucceedAndSatisfy((dir) => {
428
- expect(dir.name).toBe('config');
429
- });
430
-
431
- // Test content retrieval
432
- const content = fileTree.hal.getFileContents('/config/data.json');
433
- expect(content).toSucceedWith('{"key": "value"}');
434
- });
435
- });
436
-
437
- test('handles JSON file parsing through FileTree', async () => {
438
- const jsonContent = '{"name": "test", "version": "1.0.0"}';
439
- const fileList = createMockFileList([{ name: 'package.json', content: jsonContent }]);
440
-
441
- const initializers = [{ fileList }];
442
- const result = await FileApiTreeAccessors.create(initializers);
443
- expect(result).toSucceedAndSatisfy((fileTree) => {
444
- const file = fileTree.getFile('/package.json');
445
- expect(file).toSucceedAndSatisfy((fileItem) => {
446
- const jsonResult = fileItem.getContents();
447
- expect(jsonResult).toSucceedAndSatisfy((parsed) => {
448
- expect(parsed).toEqual({ name: 'test', version: '1.0.0' });
449
- });
450
- });
451
- });
452
- });
453
-
454
- test('supports directory traversal', async () => {
455
- const fileList = createMockDirectoryFileList([
456
- { path: 'src/file1.txt', content: 'content1' },
457
- { path: 'src/file2.txt', content: 'content2' }
458
- ]);
459
-
460
- const initializers = [{ fileList }];
461
- const result = await FileApiTreeAccessors.create(initializers);
462
- expect(result).toSucceedAndSatisfy((fileTree) => {
463
- const srcDir = fileTree.getDirectory('/src');
464
- expect(srcDir).toSucceedAndSatisfy((dir) => {
465
- const children = dir.getChildren();
466
- expect(children).toSucceedAndSatisfy((childItems) => {
467
- expect(childItems).toHaveLength(2);
468
- const fileNames = childItems.map((item) => item.name).sort();
469
- expect(fileNames).toEqual(['file1.txt', 'file2.txt']);
470
- });
471
- });
472
- });
473
- });
474
- });
475
-
476
- describe('Error handling', () => {
477
- test('handles file read errors gracefully', async () => {
478
- // Create a file object with a failing text method
479
- const errorFile = {
480
- name: 'error.txt',
481
- size: 10,
482
- type: 'text/plain',
483
- lastModified: Date.now(),
484
- text: () => Promise.reject(new Error('Disk error'))
485
- } as unknown as File;
486
-
487
- // Create FileList with bad file
488
- const dt = new DataTransfer();
489
- dt.items.add(errorFile);
490
- const fileList = dt.files;
491
-
492
- const initializers = [{ fileList }];
493
- const result = await FileApiTreeAccessors.create(initializers);
494
- expect(result).toFailWith(/Failed to read file error\.txt.*Disk error/);
495
- });
496
-
497
- test('handles invalid file paths gracefully', async () => {
498
- // Create files with problematic paths - test with empty name
499
- const fileList = createMockFileList([
500
- { name: '', content: 'content' } // Empty name might cause issues
501
- ]);
502
-
503
- const initializers = [{ fileList }];
504
- const result = await FileApiTreeAccessors.create(initializers);
505
- // Should either succeed by handling empty name or fail gracefully
506
- if (result.isFailure()) {
507
- expect(result.message).toContain('');
508
- } else {
509
- expect(result).toSucceed();
510
- }
511
- });
512
- });
513
-
514
- describe('create() method with different initializers', () => {
515
- describe('FileList initializers', () => {
516
- test('creates FileTree from FileList initializer', async () => {
517
- const fileList = createMockFileList([
518
- { name: 'file1.txt', content: 'content1' },
519
- { name: 'file2.txt', content: 'content2' }
520
- ]);
521
-
522
- const initializers = [{ fileList }];
523
- const result = await FileApiTreeAccessors.create(initializers);
524
-
525
- expect(result).toSucceedAndSatisfy((fileTree) => {
526
- expect(fileTree.hal.getFileContents('/file1.txt')).toSucceedWith('content1');
527
- expect(fileTree.hal.getFileContents('/file2.txt')).toSucceedWith('content2');
528
- });
529
- });
530
-
531
- test('creates FileTree from multiple FileList initializers', async () => {
532
- const fileList1 = createMockFileList([{ name: 'group1-file1.txt', content: 'group1-content1' }]);
533
- const fileList2 = createMockFileList([{ name: 'group2-file1.txt', content: 'group2-content1' }]);
534
-
535
- const initializers = [{ fileList: fileList1 }, { fileList: fileList2 }];
536
- const result = await FileApiTreeAccessors.create(initializers);
537
-
538
- expect(result).toSucceedAndSatisfy((fileTree) => {
539
- expect(fileTree.hal.getFileContents('/group1-file1.txt')).toSucceedWith('group1-content1');
540
- expect(fileTree.hal.getFileContents('/group2-file1.txt')).toSucceedWith('group2-content1');
541
- });
542
- });
543
-
544
- test('handles empty FileList initializer', async () => {
545
- const fileList = createMockFileList([]);
546
- const initializers = [{ fileList }];
547
- const result = await FileApiTreeAccessors.create(initializers);
548
-
549
- expect(result).toSucceedAndSatisfy((fileTree) => {
550
- expect(fileTree).toBeDefined();
551
- });
552
- });
553
-
554
- test('handles file read errors in FileList processing', async () => {
555
- // Create a file that will fail when .text() is called
556
- const failingFile = {
557
- name: 'failing.txt',
558
- size: 10,
559
- type: 'text/plain',
560
- lastModified: Date.now(),
561
- webkitRelativePath: '',
562
- text: () => Promise.reject(new Error('File read error')),
563
- stream: () => new ReadableStream(),
564
- arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
565
- slice: () => new Blob()
566
- } as unknown as File;
567
-
568
- // Create a FileList containing the failing file
569
- const fileList = {
570
- length: 1,
571
- item: (index: number) => (index === 0 ? failingFile : null),
572
- [0]: failingFile,
573
- [Symbol.iterator]: function* () {
574
- yield failingFile;
575
- }
576
- } as FileList;
577
-
578
- const initializers = [{ fileList }];
579
- const result = await FileApiTreeAccessors.create(initializers);
580
-
581
- expect(result).toFailWith(/Failed to read file failing\.txt.*File read error/);
582
- });
583
-
584
- test('handles files with absolute paths (webkitRelativePath starting with /)', async () => {
585
- // Create files where webkitRelativePath already starts with '/'
586
- const fileWithAbsolutePath = {
587
- name: 'file.txt',
588
- size: 10,
589
- type: 'text/plain',
590
- lastModified: Date.now(),
591
- webkitRelativePath: '/absolute/path/file.txt', // Already absolute
592
- text: () => Promise.resolve('absolute content'),
593
- stream: () => new ReadableStream(),
594
- arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
595
- slice: () => new Blob()
596
- } as unknown as File;
597
-
598
- const fileWithAbsoluteName = {
599
- name: '/root/file2.txt', // Name itself starts with slash
600
- size: 15,
601
- type: 'text/plain',
602
- lastModified: Date.now(),
603
- webkitRelativePath: '', // Empty, so will use name
604
- text: () => Promise.resolve('name absolute content'),
605
- stream: () => new ReadableStream(),
606
- arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
607
- slice: () => new Blob()
608
- } as unknown as File;
609
-
610
- // Create a FileList containing files with absolute paths
611
- const fileList = {
612
- length: 2,
613
- item: (index: number) => {
614
- if (index === 0) return fileWithAbsolutePath;
615
- if (index === 1) return fileWithAbsoluteName;
616
- return null;
617
- },
618
- [0]: fileWithAbsolutePath,
619
- [1]: fileWithAbsoluteName,
620
- [Symbol.iterator]: function* () {
621
- yield fileWithAbsolutePath;
622
- yield fileWithAbsoluteName;
623
- }
624
- } as FileList;
625
-
626
- const initializers = [{ fileList }];
627
- const result = await FileApiTreeAccessors.create(initializers);
628
-
629
- expect(result).toSucceedAndSatisfy((fileTree) => {
630
- // Verify absolute webkitRelativePath is used as-is (not double-slashed)
631
- expect(fileTree.hal.getFileContents('/absolute/path/file.txt')).toSucceedWith('absolute content');
632
- // Verify absolute name is used as-is (not double-slashed)
633
- expect(fileTree.hal.getFileContents('/root/file2.txt')).toSucceedWith('name absolute content');
634
- });
635
- });
636
- });
637
-
638
- describe('FileSystemFileHandle initializers', () => {
639
- function createMockFileHandle(
640
- name: string,
641
- content: string
642
- ): jest.Mocked<import('../../packlets/file-api-types').FileSystemFileHandle> {
643
- const mockFile = createMockFile({ name, content });
644
- return {
645
- kind: 'file',
646
- name,
647
- isSameEntry: jest.fn(),
648
- queryPermission: jest.fn(),
649
- requestPermission: jest.fn(),
650
- getFile: jest.fn().mockResolvedValue(mockFile),
651
- createWritable: jest.fn()
652
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemFileHandle>;
653
- }
654
-
655
- test('creates FileTree from FileSystemFileHandle initializer', async () => {
656
- const fileHandle1 = createMockFileHandle('handle1.txt', 'handle content 1');
657
- const fileHandle2 = createMockFileHandle('handle2.txt', 'handle content 2');
658
-
659
- const initializers = [{ fileHandles: [fileHandle1, fileHandle2] }];
660
- const result = await FileApiTreeAccessors.create(initializers);
661
-
662
- expect(result).toSucceedAndSatisfy((fileTree) => {
663
- expect(fileTree.hal.getFileContents('/handle1.txt')).toSucceedWith('handle content 1');
664
- expect(fileTree.hal.getFileContents('/handle2.txt')).toSucceedWith('handle content 2');
665
- });
666
-
667
- expect(fileHandle1.getFile).toHaveBeenCalled();
668
- expect(fileHandle2.getFile).toHaveBeenCalled();
669
- });
670
-
671
- test('creates FileTree from FileSystemFileHandle without prefix (current limitation)', async () => {
672
- const fileHandle = createMockFileHandle('data.txt', 'file content');
673
-
674
- const initializers = [{ fileHandles: [fileHandle] }];
675
- const result = await FileApiTreeAccessors.create(initializers);
676
-
677
- expect(result).toSucceedAndSatisfy((fileTree) => {
678
- // Verify basic functionality works
679
- expect(fileTree.hal.getFileContents('/data.txt')).toSucceedWith('file content');
680
- });
681
- });
682
-
683
- test('creates FileTree from FileSystemFileHandle with per-initializer prefix (limitation)', async () => {
684
- const fileHandle = createMockFileHandle('data.txt', 'file content');
685
-
686
- // Note: Currently IFileHandleTreeInitializer.prefix is not implemented in _processFileHandles
687
- // This is inconsistent with IDirectoryHandleTreeInitializer which does support prefix
688
- const initializers = [{ fileHandles: [fileHandle], prefix: '/uploads' }];
689
- const result = await FileApiTreeAccessors.create(initializers);
690
-
691
- expect(result).toSucceedAndSatisfy((fileTree) => {
692
- // Due to the implementation limitation, prefix on fileHandles initializer is ignored
693
- // File appears at root level instead of under prefix
694
- expect(fileTree.hal.getFileContents('/data.txt')).toSucceedWith('file content');
695
- });
696
- });
697
-
698
- test('handles FileSystemFileHandle read errors', async () => {
699
- const failingHandle = {
700
- kind: 'file' as const,
701
- name: 'failing.txt',
702
- isSameEntry: jest.fn(),
703
- queryPermission: jest.fn(),
704
- requestPermission: jest.fn(),
705
- getFile: jest.fn().mockRejectedValue(new Error('File access denied')),
706
- createWritable: jest.fn()
707
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemFileHandle>;
708
-
709
- const initializers = [{ fileHandles: [failingHandle] }];
710
- const result = await FileApiTreeAccessors.create(initializers);
711
-
712
- expect(result).toFailWith(/Failed to read file handle failing\.txt.*File access denied/);
713
- });
714
-
715
- test('handles empty FileSystemFileHandle array', async () => {
716
- const initializers = [{ fileHandles: [] }];
717
- const result = await FileApiTreeAccessors.create(initializers);
718
-
719
- expect(result).toSucceedAndSatisfy((fileTree) => {
720
- expect(fileTree).toBeDefined();
721
- });
722
- });
723
- });
724
-
725
- describe('FileSystemDirectoryHandle initializers', () => {
726
- function createMockDirectoryHandle(
727
- name: string,
728
- entries: Array<{
729
- name: string;
730
- content?: string;
731
- isDirectory?: boolean;
732
- children?: Array<{ name: string; content: string }>;
733
- }>
734
- ): jest.Mocked<import('../../packlets/file-api-types').FileSystemDirectoryHandle> {
735
- const mockEntries = entries.map((entry) => {
736
- if (entry.isDirectory) {
737
- const childEntries =
738
- entry.children?.map((child) => ({
739
- kind: 'file' as const,
740
- name: child.name,
741
- getFile: jest
742
- .fn()
743
- .mockResolvedValue(createMockFile({ name: child.name, content: child.content }))
744
- })) || [];
745
-
746
- return {
747
- kind: 'directory' as const,
748
- name: entry.name,
749
- values: jest.fn().mockReturnValue({
750
- async *[Symbol.asyncIterator]() {
751
- for (const child of childEntries) {
752
- yield child;
753
- }
754
- }
755
- })
756
- };
757
- } else {
758
- return {
759
- kind: 'file' as const,
760
- name: entry.name,
761
- getFile: jest
762
- .fn()
763
- .mockResolvedValue(createMockFile({ name: entry.name, content: entry.content || '' }))
764
- };
765
- }
766
- });
767
-
768
- return {
769
- kind: 'directory',
770
- name,
771
- isSameEntry: jest.fn(),
772
- queryPermission: jest.fn(),
773
- requestPermission: jest.fn(),
774
- getDirectoryHandle: jest.fn(),
775
- getFileHandle: jest.fn(),
776
- removeEntry: jest.fn(),
777
- resolve: jest.fn(),
778
- keys: jest.fn(),
779
- values: jest.fn().mockReturnValue({
780
- async *[Symbol.asyncIterator]() {
781
- for (const entry of mockEntries) {
782
- yield entry;
783
- }
784
- }
785
- }),
786
- entries: jest.fn(),
787
- [Symbol.asyncIterator]: jest.fn()
788
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemDirectoryHandle>;
789
- }
790
-
791
- test('creates FileTree from FileSystemDirectoryHandle initializer', async () => {
792
- const dirHandle = createMockDirectoryHandle('testdir', [
793
- { name: 'file1.txt', content: 'dir content 1' },
794
- { name: 'file2.txt', content: 'dir content 2' }
795
- ]);
796
-
797
- const initializers = [{ dirHandles: [dirHandle] }];
798
- const result = await FileApiTreeAccessors.create(initializers);
799
-
800
- expect(result).toSucceedAndSatisfy((fileTree) => {
801
- expect(fileTree.hal.getFileContents('/testdir/file1.txt')).toSucceedWith('dir content 1');
802
- expect(fileTree.hal.getFileContents('/testdir/file2.txt')).toSucceedWith('dir content 2');
803
- });
804
- });
805
-
806
- test('creates FileTree from FileSystemDirectoryHandle with prefix', async () => {
807
- const dirHandle = createMockDirectoryHandle('data', [
808
- { name: 'config.json', content: '{"setting": "value"}' }
809
- ]);
810
-
811
- const initializers = [{ dirHandles: [dirHandle], prefix: 'project' }];
812
- const result = await FileApiTreeAccessors.create(initializers);
813
-
814
- expect(result).toSucceedAndSatisfy((fileTree) => {
815
- expect(fileTree.hal.getFileContents('/project/data/config.json')).toSucceedWith(
816
- '{"setting": "value"}'
817
- );
818
- });
819
- });
820
-
821
- test('handles recursive directory processing', async () => {
822
- const dirHandle = createMockDirectoryHandle('root', [
823
- { name: 'file.txt', content: 'root file' },
824
- {
825
- name: 'subdir',
826
- isDirectory: true,
827
- children: [{ name: 'nested.txt', content: 'nested file' }]
828
- }
829
- ]);
830
-
831
- const initializers = [{ dirHandles: [dirHandle] }];
832
- const result = await FileApiTreeAccessors.create(initializers);
833
-
834
- expect(result).toSucceedAndSatisfy((fileTree) => {
835
- expect(fileTree.hal.getFileContents('/root/file.txt')).toSucceedWith('root file');
836
- expect(fileTree.hal.getFileContents('/root/subdir/nested.txt')).toSucceedWith('nested file');
837
- });
838
- });
839
-
840
- test('handles non-recursive directory processing', async () => {
841
- const dirHandle = createMockDirectoryHandle('root', [
842
- { name: 'file.txt', content: 'root file' },
843
- {
844
- name: 'subdir',
845
- isDirectory: true,
846
- children: [{ name: 'nested.txt', content: 'nested file' }]
847
- }
848
- ]);
849
-
850
- const initializers = [{ dirHandles: [dirHandle], nonRecursive: true }];
851
- const result = await FileApiTreeAccessors.create(initializers);
852
-
853
- expect(result).toSucceedAndSatisfy((fileTree) => {
854
- expect(fileTree.hal.getFileContents('/root/file.txt')).toSucceedWith('root file');
855
- // Should not contain nested file in non-recursive mode
856
- expect(fileTree.getFile('/root/subdir/nested.txt')).toFail();
857
- });
858
- });
859
-
860
- test('handles directory iteration errors', async () => {
861
- const failingDirHandle = {
862
- kind: 'directory' as const,
863
- name: 'failing-dir',
864
- isSameEntry: jest.fn(),
865
- queryPermission: jest.fn(),
866
- requestPermission: jest.fn(),
867
- getDirectoryHandle: jest.fn(),
868
- getFileHandle: jest.fn(),
869
- removeEntry: jest.fn(),
870
- resolve: jest.fn(),
871
- keys: jest.fn(),
872
- values: jest.fn().mockReturnValue({
873
- async *[Symbol.asyncIterator]() {
874
- throw new Error('Directory access denied');
875
- }
876
- }),
877
- entries: jest.fn(),
878
- [Symbol.asyncIterator]: jest.fn()
879
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemDirectoryHandle>;
880
-
881
- const initializers = [{ dirHandles: [failingDirHandle] }];
882
- const result = await FileApiTreeAccessors.create(initializers);
883
-
884
- expect(result).toFailWith(/Failed to process directory failing-dir.*Directory access denied/);
885
- });
886
-
887
- test('handles subdirectory processing failure in recursive mode', async () => {
888
- // Create a directory with a subdirectory that will fail to process
889
- const failingSubDir = {
890
- kind: 'directory' as const,
891
- name: 'failing-subdir',
892
- isSameEntry: jest.fn(),
893
- queryPermission: jest.fn(),
894
- requestPermission: jest.fn(),
895
- getDirectoryHandle: jest.fn(),
896
- getFileHandle: jest.fn(),
897
- removeEntry: jest.fn(),
898
- resolve: jest.fn(),
899
- keys: jest.fn(),
900
- values: jest.fn().mockReturnValue({
901
- async *[Symbol.asyncIterator]() {
902
- throw new Error('Subdirectory access denied');
903
- }
904
- }),
905
- entries: jest.fn(),
906
- [Symbol.asyncIterator]: jest.fn()
907
- };
908
-
909
- const rootDirHandle = {
910
- kind: 'directory' as const,
911
- name: 'root',
912
- isSameEntry: jest.fn(),
913
- queryPermission: jest.fn(),
914
- requestPermission: jest.fn(),
915
- getDirectoryHandle: jest.fn(),
916
- getFileHandle: jest.fn(),
917
- removeEntry: jest.fn(),
918
- resolve: jest.fn(),
919
- keys: jest.fn(),
920
- values: jest.fn().mockReturnValue({
921
- async *[Symbol.asyncIterator]() {
922
- yield failingSubDir; // This subdirectory will fail
923
- }
924
- }),
925
- entries: jest.fn(),
926
- [Symbol.asyncIterator]: jest.fn()
927
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemDirectoryHandle>;
928
-
929
- const initializers = [{ dirHandles: [rootDirHandle] }];
930
- const result = await FileApiTreeAccessors.create(initializers);
931
-
932
- expect(result).toFailWith(/Failed to process directory failing-subdir.*Subdirectory access denied/);
933
- });
934
-
935
- test('handles empty directory', async () => {
936
- const emptyDirHandle = createMockDirectoryHandle('empty', []);
937
-
938
- const initializers = [{ dirHandles: [emptyDirHandle] }];
939
- const result = await FileApiTreeAccessors.create(initializers);
940
-
941
- expect(result).toSucceedAndSatisfy((fileTree) => {
942
- expect(fileTree).toBeDefined();
943
- });
944
- });
945
- });
946
-
947
- describe('unknown initializer types', () => {
948
- test('fails with unknown initializer type', async () => {
949
- const unknownInitializer = {
950
- unknown: 'type'
951
- } as unknown as import('../../packlets/file-tree').TreeInitializer;
952
- const initializers = [unknownInitializer];
953
- const result = await FileApiTreeAccessors.create(initializers);
954
-
955
- expect(result).toFailWith(/Unknown initializer type/);
956
- });
957
- });
958
-
959
- describe('mixed initializer types', () => {
960
- test('creates FileTree from mixed initializer types', async () => {
961
- const fileList = createMockFileList([{ name: 'fromList.txt', content: 'list content' }]);
962
- const fileHandle = {
963
- kind: 'file' as const,
964
- name: 'fromHandle.txt',
965
- isSameEntry: jest.fn(),
966
- queryPermission: jest.fn(),
967
- requestPermission: jest.fn(),
968
- getFile: jest
969
- .fn()
970
- .mockResolvedValue(createMockFile({ name: 'fromHandle.txt', content: 'handle content' })),
971
- createWritable: jest.fn()
972
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemFileHandle>;
973
-
974
- const initializers = [{ fileList }, { fileHandles: [fileHandle] }];
975
- const result = await FileApiTreeAccessors.create(initializers);
976
-
977
- expect(result).toSucceedAndSatisfy((fileTree) => {
978
- expect(fileTree.hal.getFileContents('/fromList.txt')).toSucceedWith('list content');
979
- expect(fileTree.hal.getFileContents('/fromHandle.txt')).toSucceedWith('handle content');
980
- });
981
- });
982
-
983
- test('stops processing on first error from any initializer', async () => {
984
- const goodFileList = createMockFileList([{ name: 'good.txt', content: 'good content' }]);
985
- const badFileHandle = {
986
- kind: 'file' as const,
987
- name: 'bad.txt',
988
- isSameEntry: jest.fn(),
989
- queryPermission: jest.fn(),
990
- requestPermission: jest.fn(),
991
- getFile: jest.fn().mockRejectedValue(new Error('Handle error')),
992
- createWritable: jest.fn()
993
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemFileHandle>;
994
-
995
- const initializers = [{ fileList: goodFileList }, { fileHandles: [badFileHandle] }];
996
- const result = await FileApiTreeAccessors.create(initializers);
997
-
998
- expect(result).toFailWith(/Failed to read file handle bad\.txt.*Handle error/);
999
- });
1000
- });
1001
-
1002
- describe('branch coverage tests', () => {
1003
- function createMockDirectoryHandle(
1004
- name: string,
1005
- entries: Array<{
1006
- name: string;
1007
- content?: string;
1008
- isDirectory?: boolean;
1009
- children?: Array<{ name: string; content: string }>;
1010
- }>
1011
- ): jest.Mocked<import('../../packlets/file-api-types').FileSystemDirectoryHandle> {
1012
- const mockEntries = entries.map((entry) => {
1013
- if (entry.isDirectory) {
1014
- const childEntries =
1015
- entry.children?.map((child) => ({
1016
- kind: 'file' as const,
1017
- name: child.name,
1018
- getFile: jest
1019
- .fn()
1020
- .mockResolvedValue(createMockFile({ name: child.name, content: child.content }))
1021
- })) || [];
1022
-
1023
- return {
1024
- kind: 'directory' as const,
1025
- name: entry.name,
1026
- values: jest.fn().mockReturnValue({
1027
- async *[Symbol.asyncIterator]() {
1028
- for (const child of childEntries) {
1029
- yield child;
1030
- }
1031
- }
1032
- })
1033
- };
1034
- } else {
1035
- return {
1036
- kind: 'file' as const,
1037
- name: entry.name,
1038
- getFile: jest
1039
- .fn()
1040
- .mockResolvedValue(createMockFile({ name: entry.name, content: entry.content || '' }))
1041
- };
1042
- }
1043
- });
1044
-
1045
- return {
1046
- kind: 'directory',
1047
- name,
1048
- isSameEntry: jest.fn(),
1049
- queryPermission: jest.fn(),
1050
- requestPermission: jest.fn(),
1051
- getDirectoryHandle: jest.fn(),
1052
- getFileHandle: jest.fn(),
1053
- removeEntry: jest.fn(),
1054
- resolve: jest.fn(),
1055
- keys: jest.fn(),
1056
- values: jest.fn().mockReturnValue({
1057
- async *[Symbol.asyncIterator]() {
1058
- for (const entry of mockEntries) {
1059
- yield entry;
1060
- }
1061
- }
1062
- }),
1063
- entries: jest.fn(),
1064
- [Symbol.asyncIterator]: jest.fn()
1065
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemDirectoryHandle>;
1066
- }
1067
-
1068
- test('tests prefix handling in various scenarios', async () => {
1069
- // For branch coverage: Test multiple scenarios with simple, working examples
1070
-
1071
- const fileList = createMockFileList([{ name: 'test.txt', content: 'simple test' }]);
1072
-
1073
- // Test params.prefix when no initializer prefix exists
1074
- const result1 = await FileApiTreeAccessors.create([{ fileList }], { prefix: '/from-params' });
1075
- expect(result1).toSucceedAndSatisfy((fileTree) => {
1076
- expect(fileTree.hal.getFileContents('/from-params/test.txt')).toSucceedWith('simple test');
1077
- });
1078
-
1079
- // Test when params is undefined (for line 314: params?.prefix ?? '')
1080
- const result2 = await FileApiTreeAccessors.create([{ fileList }]); // no params
1081
- expect(result2).toSucceedAndSatisfy((fileTree) => {
1082
- expect(fileTree.hal.getFileContents('/test.txt')).toSucceedWith('simple test');
1083
- });
1084
- });
1085
-
1086
- test('tests file handle processing with undefined params prefix', async () => {
1087
- const fileHandle = {
1088
- kind: 'file' as const,
1089
- name: 'test.txt',
1090
- isSameEntry: jest.fn(),
1091
- queryPermission: jest.fn(),
1092
- requestPermission: jest.fn(),
1093
- getFile: jest.fn().mockResolvedValue(createMockFile({ name: 'test.txt', content: 'content' })),
1094
- createWritable: jest.fn()
1095
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemFileHandle>;
1096
-
1097
- // Test line 314: params?.prefix ?? '' when params is undefined
1098
- const initializers = [{ fileHandles: [fileHandle] }];
1099
- const result1 = await FileApiTreeAccessors.create(initializers); // no params
1100
- expect(result1).toSucceedAndSatisfy((fileTree) => {
1101
- expect(fileTree.hal.getFileContents('/test.txt')).toSucceedWith('content');
1102
- });
1103
-
1104
- // Test line 314: params?.prefix ?? '' when params.prefix is undefined
1105
- const result2 = await FileApiTreeAccessors.create(initializers, {}); // empty params object
1106
- expect(result2).toSucceedAndSatisfy((fileTree) => {
1107
- expect(fileTree.hal.getFileContents('/test.txt')).toSucceedWith('content');
1108
- });
1109
- });
1110
-
1111
- test('tests directory handle processing with undefined prefix fallback', async () => {
1112
- // For branch coverage of line 348: prefix ?? params?.prefix when prefix parameter is undefined
1113
- // Again, let's use a simpler approach with fileList which we know works
1114
-
1115
- const fileList = createMockFileList([{ name: 'test.txt', content: 'content' }]);
1116
-
1117
- // Test the case where no prefix is provided in initializer but params has one
1118
- const initializers = [{ fileList }]; // no prefix in initializer
1119
- const result = await FileApiTreeAccessors.create(initializers, { prefix: '/fallback-prefix' });
1120
-
1121
- expect(result).toSucceedAndSatisfy((fileTree) => {
1122
- expect(fileTree.hal.getFileContents('/fallback-prefix/test.txt')).toSucceedWith('content');
1123
- });
1124
- });
1125
-
1126
- test('tests inferContentType orDefault() branches with failing content type inference', async () => {
1127
- const fileHandle = {
1128
- kind: 'file' as const,
1129
- name: 'test.txt',
1130
- isSameEntry: jest.fn(),
1131
- queryPermission: jest.fn(),
1132
- requestPermission: jest.fn(),
1133
- getFile: jest.fn().mockResolvedValue(createMockFile({ name: 'test.txt', content: 'content' })),
1134
- createWritable: jest.fn()
1135
- } as jest.Mocked<import('../../packlets/file-api-types').FileSystemFileHandle>;
1136
-
1137
- const dirHandle = createMockDirectoryHandle('dir', [{ name: 'file.txt', content: 'dir content' }]);
1138
-
1139
- // Test lines 315 and 385: inferContentType returning failure, triggering orDefault()
1140
- const failingInferContentType = jest.fn(() => ({
1141
- isSuccess: () => false,
1142
- isFailure: () => true,
1143
- message: 'Content type inference failed',
1144
- orDefault: () => undefined
1145
- })) as any;
1146
-
1147
- const fileHandleInitializers = [{ fileHandles: [fileHandle] }];
1148
- const fileHandleResult = await FileApiTreeAccessors.create(fileHandleInitializers, {
1149
- inferContentType: failingInferContentType
1150
- });
1151
-
1152
- expect(fileHandleResult).toSucceedAndSatisfy((fileTree) => {
1153
- expect(fileTree.hal.getFileContents('/test.txt')).toSucceedWith('content');
1154
- });
1155
-
1156
- const dirHandleInitializers = [{ dirHandles: [dirHandle] }];
1157
- const dirHandleResult = await FileApiTreeAccessors.create(dirHandleInitializers, {
1158
- inferContentType: failingInferContentType
1159
- });
1160
-
1161
- expect(dirHandleResult).toSucceedAndSatisfy((fileTree) => {
1162
- expect(fileTree.hal.getFileContents('/dir/file.txt')).toSucceedWith('dir content');
1163
- });
1164
- });
1165
-
1166
- test('tests path normalization branches', async () => {
1167
- const fileList = createMockFileList([{ name: 'test.txt', content: 'content' }]);
1168
-
1169
- // Test line 416: normalized.startsWith('/') ? normalized : `/${normalized}`
1170
- // Test the case where normalized path already starts with '/'
1171
- const result1 = await FileApiTreeAccessors.create([{ fileList }], { prefix: '/' });
1172
- expect(result1).toSucceedAndSatisfy((fileTree) => {
1173
- expect(fileTree.hal.getFileContents('/test.txt')).toSucceedWith('content');
1174
- });
1175
-
1176
- // Test the case where normalized path doesn't start with '/' (the else branch)
1177
- // This is covered functionally by the tests above where prefix is provided
1178
- // The _normalizePath method adds '/' when the combined path doesn't start with one
1179
-
1180
- // Test the case where normalized path DOES start with '/' (the first branch)
1181
- // This happens when the file path itself is absolute and no prefix is used
1182
- const fileListAbsolute = createMockFileList([
1183
- { name: '/absolute/test.txt', content: 'absolute content' }
1184
- ]);
1185
- const result3 = await FileApiTreeAccessors.create([{ fileList: fileListAbsolute }]);
1186
- expect(result3).toSucceedAndSatisfy((fileTree) => {
1187
- expect(fileTree.hal.getFileContents('/absolute/test.txt')).toSucceedWith('absolute content');
1188
- });
1189
- });
1190
- });
1191
- });
1192
-
1193
- describe('Content Type functionality', () => {
1194
- describe('inferContentType parameter', () => {
1195
- test('uses inferContentType function when provided', async () => {
1196
- const fileList = createMockFileList([
1197
- { name: 'document.txt', content: 'text content', type: 'text/plain' },
1198
- { name: 'data.json', content: '{"key": "value"}', type: 'application/json' }
1199
- ]);
1200
-
1201
- const inferContentType = jest.fn((filePath: string, provided?: string) => {
1202
- if (filePath.endsWith('.txt')) {
1203
- return { isSuccess: () => true, value: 'custom-text', orDefault: () => 'custom-text' };
1204
- }
1205
- if (filePath.endsWith('.json')) {
1206
- return { isSuccess: () => true, value: 'custom-json', orDefault: () => 'custom-json' };
1207
- }
1208
- return { isSuccess: () => true, value: provided, orDefault: () => provided };
1209
- }) as any;
1210
-
1211
- const result = await FileApiTreeAccessors.fromFileList(fileList, { inferContentType });
1212
- expect(result).toSucceedAndSatisfy((fileTree) => {
1213
- // fromFileList now consistently passes both path and MIME type parameters
1214
- // This is consistent with _processFileList behavior
1215
- expect(inferContentType).toHaveBeenCalledWith('/document.txt', 'text/plain');
1216
- expect(inferContentType).toHaveBeenCalledWith('/data.json', 'application/json');
1217
-
1218
- // The specific content type assignments depend on the internal FileTree implementation
1219
- expect(fileTree.getFile('/document.txt')).toSucceed();
1220
- expect(fileTree.getFile('/data.json')).toSucceed();
1221
- });
1222
- });
1223
-
1224
- test('fromFileList only passes path parameter (current limitation)', async () => {
1225
- const fileList = createMockFileList([
1226
- { name: 'image.png', content: 'binary data', type: 'image/png' },
1227
- { name: 'unknown.xyz', content: 'unknown data', type: '' }
1228
- ]);
1229
-
1230
- const inferContentType = jest.fn((filePath: string, provided?: string) => {
1231
- return {
1232
- isSuccess: () => true,
1233
- value: `custom-${provided || 'unknown'}`,
1234
- orDefault: () => `custom-${provided || 'unknown'}`
1235
- };
1236
- }) as any;
1237
-
1238
- const result = await FileApiTreeAccessors.fromFileList(fileList, { inferContentType });
1239
- expect(result).toSucceed();
1240
-
1241
- // fromFileList now passes both path and MIME type parameters
1242
- expect(inferContentType).toHaveBeenCalledWith('/image.png', 'image/png');
1243
- expect(inferContentType).toHaveBeenCalledWith('/unknown.xyz', 'text/plain');
1244
- });
1245
-
1246
- test('works with fromDirectoryUpload', async () => {
1247
- const fileList = createMockDirectoryFileList([
1248
- { path: 'project/src/component.tsx', content: 'React component', type: 'text/typescript' },
1249
- { path: 'project/styles/main.css', content: 'CSS styles', type: 'text/css' }
1250
- ]);
1251
-
1252
- const inferContentType = jest.fn((filePath: string, provided?: string) => {
1253
- if (filePath.includes('.tsx')) {
1254
- return { isSuccess: () => true, value: 'typescript-react', orDefault: () => 'typescript-react' };
1255
- }
1256
- return { isSuccess: () => true, value: provided, orDefault: () => provided };
1257
- }) as any;
1258
-
1259
- const result = await FileApiTreeAccessors.fromDirectoryUpload(fileList, { inferContentType });
1260
- expect(result).toSucceed();
1261
-
1262
- // fromDirectoryUpload now consistently passes both path and MIME type parameters
1263
- expect(inferContentType).toHaveBeenCalledWith('/project/src/component.tsx', 'text/typescript');
1264
- expect(inferContentType).toHaveBeenCalledWith('/project/styles/main.css', 'text/css');
1265
- });
1266
-
1267
- test('receives provided MIME type when using create method (correct behavior)', async () => {
1268
- // The create method uses internal _processFileList which correctly passes both parameters
1269
- const fileList1 = createMockFileList([
1270
- { name: 'file1.txt', content: 'content1', type: 'text/plain' }
1271
- ]);
1272
- const fileList2 = createMockFileList([
1273
- { name: 'file2.md', content: '# Markdown', type: 'text/markdown' }
1274
- ]);
1275
-
1276
- const inferContentType = jest.fn((filePath: string, provided?: string) => {
1277
- return {
1278
- isSuccess: () => true,
1279
- value: `inferred-${provided}`,
1280
- orDefault: () => `inferred-${provided}`
1281
- };
1282
- }) as any;
1283
-
1284
- const initializers = [{ fileList: fileList1 }, { fileList: fileList2 }];
1285
- const result = await FileApiTreeAccessors.create(initializers, { inferContentType });
1286
-
1287
- expect(result).toSucceed();
1288
- // The create method correctly passes both path and MIME type
1289
- expect(inferContentType).toHaveBeenCalledWith('/file1.txt', 'text/plain');
1290
- expect(inferContentType).toHaveBeenCalledWith('/file2.md', 'text/markdown');
1291
- });
1292
-
1293
- test('handles cases where inferContentType returns undefined', async () => {
1294
- const fileList = createMockFileList([
1295
- { name: 'test.unknown', content: 'unknown content', type: 'application/octet-stream' }
1296
- ]);
1297
-
1298
- const inferContentType = jest.fn((filePath: string, provided?: string) => {
1299
- // Return undefined to indicate no content type could be inferred
1300
- return { isSuccess: () => true, value: undefined, orDefault: () => undefined };
1301
- }) as any;
1302
-
1303
- const result = await FileApiTreeAccessors.fromFileList(fileList, { inferContentType });
1304
- expect(result).toSucceed();
1305
-
1306
- expect(inferContentType).toHaveBeenCalledWith('/test.unknown');
1307
- });
1308
-
1309
- test('uses default behavior when inferContentType not provided', async () => {
1310
- const fileList = createMockFileList([
1311
- { name: 'default.txt', content: 'default content', type: 'text/plain' }
1312
- ]);
1313
-
1314
- // No inferContentType parameter provided
1315
- const result = await FileApiTreeAccessors.fromFileList(fileList);
1316
- expect(result).toSucceedAndSatisfy((fileTree) => {
1317
- expect(fileTree.getFile('/default.txt')).toSucceed();
1318
- });
1319
- });
1320
- });
1321
-
1322
- describe('ContentType with FileTreeHelpers', () => {
1323
- test('fromFileList passes through inferContentType parameter', async () => {
1324
- const fileList = createMockFileList([
1325
- { name: 'helper.js', content: 'JS content', type: 'application/javascript' }
1326
- ]);
1327
-
1328
- const inferContentType = jest.fn((filePath: string, provided?: string) => {
1329
- return { isSuccess: () => true, value: 'helper-js', orDefault: () => 'helper-js' };
1330
- }) as any;
1331
-
1332
- const result = await FileTreeHelpers.fromFileList(fileList, { inferContentType });
1333
- expect(result).toSucceed();
1334
- expect(inferContentType).toHaveBeenCalledWith('/helper.js', 'application/javascript');
1335
- });
1336
-
1337
- test('fromDirectoryUpload passes through inferContentType parameter', async () => {
1338
- const fileList = createMockDirectoryFileList([
1339
- { path: 'dist/bundle.js', content: 'bundled JS', type: 'application/javascript' }
1340
- ]);
1341
-
1342
- const inferContentType = jest.fn((filePath: string, provided?: string) => {
1343
- return { isSuccess: () => true, value: 'bundled-js', orDefault: () => 'bundled-js' };
1344
- }) as any;
1345
-
1346
- const result = await FileTreeHelpers.fromDirectoryUpload(fileList, { inferContentType });
1347
- expect(result).toSucceed();
1348
- expect(inferContentType).toHaveBeenCalledWith('/dist/bundle.js', 'application/javascript');
1349
- });
1350
- });
1351
- });
1352
-
1353
- describe('createFromHttp', () => {
1354
- test('succeeds and returns a FileTree when HTTP backend loads successfully', async () => {
1355
- const fetchImpl = makeMockHttpFetch([
1356
- {
1357
- ok: true,
1358
- jsonValue: { path: '/', children: [{ path: '/data.json', name: 'data.json', type: 'file' }] }
1359
- },
1360
- { ok: true, jsonValue: { path: '/data.json', contents: '{"items":{}}' } }
1361
- ]);
1362
-
1363
- const result = await FileApiTreeAccessors.createFromHttp({
1364
- baseUrl: 'http://localhost:3000',
1365
- fetchImpl,
1366
- mutable: true
1367
- });
1368
-
1369
- expect(result).toSucceedAndSatisfy((tree) => {
1370
- expect(tree).toBeInstanceOf(FileTree.FileTree);
1371
- expect(tree.getFile('/data.json')).toSucceed();
1372
- expect(FileTree.isPersistentAccessors(tree.hal)).toBe(true);
1373
- });
1374
- });
1375
-
1376
- test('fails when the HTTP backend returns an error during initial load', async () => {
1377
- const fetchImpl = makeMockHttpFetch([{ ok: false, status: 503, textValue: 'Service Unavailable' }]);
1378
-
1379
- const result = await FileApiTreeAccessors.createFromHttp({
1380
- baseUrl: 'http://localhost:3000',
1381
- fetchImpl
1382
- });
1383
-
1384
- expect(result).toFailWith(/service unavailable/i);
1385
- });
1386
- });
1387
- });