mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Merge pull request #1014 from TriliumNext/refactor_import-mime
refactor: refactor and add tests for `services/import/mime`
This commit is contained in:
		
							
								
								
									
										151
									
								
								src/services/import/mime.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/services/import/mime.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | import { describe, it, expect } from "vitest"; | ||||||
|  | import mimeService from "./mime.js"; | ||||||
|  |  | ||||||
|  | type TestCase<T extends (...args: any) => any, W> = [desc: string, fnParams: Parameters<T>, expected: W]; | ||||||
|  |  | ||||||
|  | describe("#getMime", () => { | ||||||
|  |     // prettier-ignore | ||||||
|  |     const testCases: TestCase<typeof mimeService.getMime, string | false>[] = [ | ||||||
|  |         [ | ||||||
|  |             "Dockerfile should be handled correctly", | ||||||
|  |             ["Dockerfile"], "text/x-dockerfile" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "File extension that is defined in EXTENSION_TO_MIME", | ||||||
|  |             ["test.py"], "text/x-python" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "File extension with inconsisten capitalization that is defined in EXTENSION_TO_MIME", | ||||||
|  |             ["test.gRoOvY"], "text/x-groovy" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "File extension that is not defined in EXTENSION_TO_MIME should use mimeTypes.lookup", | ||||||
|  |             ["test.zip"], "application/zip" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "unknown MIME type not recognized by mimeTypes.lookup", | ||||||
|  |             ["test.fake"], false | ||||||
|  |         ], | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     testCases.forEach((testCase) => { | ||||||
|  |         const [testDesc, fnParams, expected] = testCase; | ||||||
|  |         it(`${testDesc}: '${fnParams} should return '${expected}'`, () => { | ||||||
|  |             const actual = mimeService.getMime(...fnParams); | ||||||
|  |             expect(actual).toEqual(expected); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | describe("#getType", () => { | ||||||
|  |     // prettier-ignore | ||||||
|  |     const testCases: TestCase<typeof mimeService.getType, string>[] = [ | ||||||
|  |         [ | ||||||
|  |             "w/ no import options set and mime type empty – it should return 'file'", | ||||||
|  |             [{}, ""], "file" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ no import options set and non-text or non-code mime type – it should return 'file'", | ||||||
|  |             [{}, "application/zip"], "file" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ import options set and an image mime type – it should return 'image'",  | ||||||
|  |             [{}, "image/jpeg"], "image" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ image mime type and codeImportedAsCode: true – it should still return 'image'", | ||||||
|  |             [{codeImportedAsCode: true}, "image/jpeg"], "image" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ image mime type and textImportedAsText: true – it should still return 'image'", | ||||||
|  |             [{textImportedAsText: true}, "image/jpeg"], "image" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ codeImportedAsCode: true and a mime type that is in CODE_MIME_TYPES – it should return 'code'",  | ||||||
|  |             [{codeImportedAsCode: true}, "text/css"], "code" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ codeImportedAsCode: false and a mime type that is in CODE_MIME_TYPES – it should return 'file' not 'code'",  | ||||||
|  |             [{codeImportedAsCode: false}, "text/css"], "file" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ textImportedAsText: true and 'text/html' mime type – it should return 'text'",  | ||||||
|  |             [{textImportedAsText: true}, "text/html"], "text" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ textImportedAsText: true and 'text/markdown' mime type – it should return 'text'",  | ||||||
|  |             [{textImportedAsText: true}, "text/markdown"], "text" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ textImportedAsText: true and 'text/x-markdown' mime type – it should return 'text'", | ||||||
|  |             [{textImportedAsText: true}, "text/x-markdown"], "text" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ textImportedAsText: false and 'text/x-markdown' mime type – it should return 'file'", | ||||||
|  |             [{textImportedAsText: false}, "text/x-markdown"], "file" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "w/ textImportedAsText: false and 'text/html' mime type – it should return 'file'", | ||||||
|  |             [{textImportedAsText: false}, "text/html"], "file" | ||||||
|  |         ], | ||||||
|  |  | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     testCases.forEach((testCase) => { | ||||||
|  |         const [desc, fnParams, expected] = testCase; | ||||||
|  |         it(desc, () => { | ||||||
|  |             const actual = mimeService.getType(...fnParams); | ||||||
|  |             expect(actual).toEqual(expected); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | describe("#normalizeMimeType", () => { | ||||||
|  |     // prettier-ignore | ||||||
|  |     const testCases: TestCase<typeof mimeService.normalizeMimeType, string | undefined>[] = [ | ||||||
|  |  | ||||||
|  |         [ | ||||||
|  |             "empty mime should return undefined",  | ||||||
|  |             [""], undefined | ||||||
|  |         ], | ||||||
|  |         [ | ||||||
|  |             "a mime that's defined in CODE_MIME_TYPES should return the same mime",  | ||||||
|  |             ["text/x-python"], "text/x-python" | ||||||
|  |         ], | ||||||
|  |         [ | ||||||
|  |             "a mime (with capitalization inconsistencies) that's defined in CODE_MIME_TYPES should return the same mime in lowercase",  | ||||||
|  |             ["text/X-pYthOn"], "text/x-python" | ||||||
|  |         ], | ||||||
|  |         [ | ||||||
|  |             "a mime that's non defined in CODE_MIME_TYPES should return undefined",  | ||||||
|  |             ["application/zip"], undefined | ||||||
|  |         ], | ||||||
|  |         [ | ||||||
|  |             "a mime that's defined in CODE_MIME_TYPES with a 'rewrite rule' should return the rewritten mime",  | ||||||
|  |             ["text/markdown"], "text/x-markdown" | ||||||
|  |         ] | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     testCases.forEach((testCase) => { | ||||||
|  |         const [desc, fnParams, expected] = testCase; | ||||||
|  |         it(desc, () => { | ||||||
|  |             const actual = mimeService.normalizeMimeType(...fnParams); | ||||||
|  |             expect(actual).toEqual(expected); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
| @@ -4,107 +4,109 @@ import mimeTypes from "mime-types"; | |||||||
| import path from "path"; | import path from "path"; | ||||||
| import type { TaskData } from "../task_context_interface.js"; | import type { TaskData } from "../task_context_interface.js"; | ||||||
|  |  | ||||||
| const CODE_MIME_TYPES: Record<string, boolean | string> = { | const CODE_MIME_TYPES = new Set([ | ||||||
|     "text/plain": true, |     "application/json", | ||||||
|     "text/x-csrc": true, |     "message/http", | ||||||
|     "text/x-c++src": true, |     "text/css", | ||||||
|     "text/x-csharp": true, |     "text/html", | ||||||
|     "text/x-clojure": true, |     "text/plain", | ||||||
|     "text/css": true, |     "text/x-clojure", | ||||||
|     "text/x-dockerfile": true, |     "text/x-csharp", | ||||||
|     "text/x-erlang": true, |     "text/x-c++src", | ||||||
|     "text/x-feature": true, |     "text/x-csrc", | ||||||
|     "text/x-go": true, |     "text/x-dockerfile", | ||||||
|     "text/x-groovy": true, |     "text/x-erlang", | ||||||
|     "text/x-haskell": true, |     "text/x-feature", | ||||||
|     "text/html": true, |     "text/x-go", | ||||||
|     "message/http": true, |     "text/x-groovy", | ||||||
|     "text/x-java": true, |     "text/x-haskell", | ||||||
|     "application/javascript": "application/javascript;env=frontend", |     "text/x-java", | ||||||
|     "application/x-javascript": "application/javascript;env=frontend", |     "text/x-kotlin", | ||||||
|     "application/json": true, |     "text/x-lua", | ||||||
|     "text/x-kotlin": true, |     "text/x-markdown", | ||||||
|     "text/x-stex": true, |     "text/xml", | ||||||
|     "text/x-lua": true, |     "text/x-objectivec", | ||||||
|  |     "text/x-pascal", | ||||||
|  |     "text/x-perl", | ||||||
|  |     "text/x-php", | ||||||
|  |     "text/x-python", | ||||||
|  |     "text/x-ruby", | ||||||
|  |     "text/x-rustsrc", | ||||||
|  |     "text/x-scala", | ||||||
|  |     "text/x-sh", | ||||||
|  |     "text/x-sql", | ||||||
|  |     "text/x-stex", | ||||||
|  |     "text/x-swift", | ||||||
|  |     "text/x-yaml" | ||||||
|  | ]); | ||||||
|  |  | ||||||
|  | const CODE_MIME_TYPES_OVERRIDE = new Map<string, string>([ | ||||||
|  |     ["application/javascript", "application/javascript;env=frontend"], | ||||||
|  |     ["application/x-javascript", "application/javascript;env=frontend"], | ||||||
|     // possibly later migrate to text/markdown as primary MIME |     // possibly later migrate to text/markdown as primary MIME | ||||||
|     "text/markdown": "text/x-markdown", |     ["text/markdown", "text/x-markdown"] | ||||||
|     "text/x-markdown": true, | ]); | ||||||
|     "text/x-objectivec": true, |  | ||||||
|     "text/x-pascal": true, |  | ||||||
|     "text/x-perl": true, |  | ||||||
|     "text/x-php": true, |  | ||||||
|     "text/x-python": true, |  | ||||||
|     "text/x-ruby": true, |  | ||||||
|     "text/x-rustsrc": true, |  | ||||||
|     "text/x-scala": true, |  | ||||||
|     "text/x-sh": true, |  | ||||||
|     "text/x-sql": true, |  | ||||||
|     "text/x-swift": true, |  | ||||||
|     "text/xml": true, |  | ||||||
|     "text/x-yaml": true |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // extensions missing in mime-db | // extensions missing in mime-db | ||||||
| const EXTENSION_TO_MIME: Record<string, string> = { | const EXTENSION_TO_MIME = new Map<string, string>([ | ||||||
|     ".c": "text/x-csrc", |     [".c", "text/x-csrc"], | ||||||
|     ".cs": "text/x-csharp", |     [".cs", "text/x-csharp"], | ||||||
|     ".clj": "text/x-clojure", |     [".clj", "text/x-clojure"], | ||||||
|     ".erl": "text/x-erlang", |     [".erl", "text/x-erlang"], | ||||||
|     ".hrl": "text/x-erlang", |     [".hrl", "text/x-erlang"], | ||||||
|     ".feature": "text/x-feature", |     [".feature", "text/x-feature"], | ||||||
|     ".go": "text/x-go", |     [".go", "text/x-go"], | ||||||
|     ".groovy": "text/x-groovy", |     [".groovy", "text/x-groovy"], | ||||||
|     ".hs": "text/x-haskell", |     [".hs", "text/x-haskell"], | ||||||
|     ".lhs": "text/x-haskell", |     [".lhs", "text/x-haskell"], | ||||||
|     ".http": "message/http", |     [".http", "message/http"], | ||||||
|     ".kt": "text/x-kotlin", |     [".kt", "text/x-kotlin"], | ||||||
|     ".m": "text/x-objectivec", |     [".m", "text/x-objectivec"], | ||||||
|     ".py": "text/x-python", |     [".py", "text/x-python"], | ||||||
|     ".rb": "text/x-ruby", |     [".rb", "text/x-ruby"], | ||||||
|     ".scala": "text/x-scala", |     [".scala", "text/x-scala"], | ||||||
|     ".swift": "text/x-swift" |     [".swift", "text/x-swift"] | ||||||
| }; | ]); | ||||||
|  |  | ||||||
| /** @returns false if MIME is not detected */ | /** @returns false if MIME is not detected */ | ||||||
| function getMime(fileName: string) { | function getMime(fileName: string) { | ||||||
|     if (fileName.toLowerCase() === "dockerfile") { |     const fileNameLc = fileName?.toLowerCase(); | ||||||
|  |  | ||||||
|  |     if (fileNameLc === "dockerfile") { | ||||||
|         return "text/x-dockerfile"; |         return "text/x-dockerfile"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const ext = path.extname(fileName).toLowerCase(); |     const ext = path.extname(fileNameLc); | ||||||
|  |     const mimeFromExt = EXTENSION_TO_MIME.get(ext); | ||||||
|  |  | ||||||
|     if (ext in EXTENSION_TO_MIME) { |     return mimeFromExt || mimeTypes.lookup(fileNameLc); | ||||||
|         return EXTENSION_TO_MIME[ext]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return mimeTypes.lookup(fileName); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function getType(options: TaskData, mime: string) { | function getType(options: TaskData, mime: string) { | ||||||
|     mime = mime ? mime.toLowerCase() : ""; |     const mimeLc = mime?.toLowerCase(); | ||||||
|  |  | ||||||
|     if (options.textImportedAsText && (mime === "text/html" || ["text/markdown", "text/x-markdown"].includes(mime))) { |     switch (true) { | ||||||
|  |         case options.textImportedAsText && ["text/html", "text/markdown", "text/x-markdown"].includes(mimeLc): | ||||||
|             return "text"; |             return "text"; | ||||||
|     } else if (options.codeImportedAsCode && mime in CODE_MIME_TYPES) { |  | ||||||
|  |         case options.codeImportedAsCode && CODE_MIME_TYPES.has(mimeLc): | ||||||
|             return "code"; |             return "code"; | ||||||
|     } else if (mime.startsWith("image/")) { |  | ||||||
|  |         case mime.startsWith("image/"): | ||||||
|             return "image"; |             return "image"; | ||||||
|     } else { |  | ||||||
|  |         default: | ||||||
|             return "file"; |             return "file"; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function normalizeMimeType(mime: string) { | function normalizeMimeType(mime: string) { | ||||||
|     mime = mime ? mime.toLowerCase() : ""; |     const mimeLc = mime.toLowerCase(); | ||||||
|     const mappedMime = CODE_MIME_TYPES[mime]; |  | ||||||
|  |  | ||||||
|     if (mappedMime === true) { |     //prettier-ignore | ||||||
|         return mime; |     return CODE_MIME_TYPES.has(mimeLc) | ||||||
|     } else if (typeof mappedMime === "string") { |         ? mimeLc | ||||||
|         return mappedMime; |         : CODE_MIME_TYPES_OVERRIDE.get(mimeLc); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return undefined; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user