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 type { TaskData } from "../task_context_interface.js"; | ||||
|  | ||||
| const CODE_MIME_TYPES: Record<string, boolean | string> = { | ||||
|     "text/plain": true, | ||||
|     "text/x-csrc": true, | ||||
|     "text/x-c++src": true, | ||||
|     "text/x-csharp": true, | ||||
|     "text/x-clojure": true, | ||||
|     "text/css": true, | ||||
|     "text/x-dockerfile": true, | ||||
|     "text/x-erlang": true, | ||||
|     "text/x-feature": true, | ||||
|     "text/x-go": true, | ||||
|     "text/x-groovy": true, | ||||
|     "text/x-haskell": true, | ||||
|     "text/html": true, | ||||
|     "message/http": true, | ||||
|     "text/x-java": true, | ||||
|     "application/javascript": "application/javascript;env=frontend", | ||||
|     "application/x-javascript": "application/javascript;env=frontend", | ||||
|     "application/json": true, | ||||
|     "text/x-kotlin": true, | ||||
|     "text/x-stex": true, | ||||
|     "text/x-lua": true, | ||||
| const CODE_MIME_TYPES = new Set([ | ||||
|     "application/json", | ||||
|     "message/http", | ||||
|     "text/css", | ||||
|     "text/html", | ||||
|     "text/plain", | ||||
|     "text/x-clojure", | ||||
|     "text/x-csharp", | ||||
|     "text/x-c++src", | ||||
|     "text/x-csrc", | ||||
|     "text/x-dockerfile", | ||||
|     "text/x-erlang", | ||||
|     "text/x-feature", | ||||
|     "text/x-go", | ||||
|     "text/x-groovy", | ||||
|     "text/x-haskell", | ||||
|     "text/x-java", | ||||
|     "text/x-kotlin", | ||||
|     "text/x-lua", | ||||
|     "text/x-markdown", | ||||
|     "text/xml", | ||||
|     "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 | ||||
|     "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 | ||||
| }; | ||||
|     ["text/markdown", "text/x-markdown"] | ||||
| ]); | ||||
|  | ||||
| // extensions missing in mime-db | ||||
| const EXTENSION_TO_MIME: Record<string, string> = { | ||||
|     ".c": "text/x-csrc", | ||||
|     ".cs": "text/x-csharp", | ||||
|     ".clj": "text/x-clojure", | ||||
|     ".erl": "text/x-erlang", | ||||
|     ".hrl": "text/x-erlang", | ||||
|     ".feature": "text/x-feature", | ||||
|     ".go": "text/x-go", | ||||
|     ".groovy": "text/x-groovy", | ||||
|     ".hs": "text/x-haskell", | ||||
|     ".lhs": "text/x-haskell", | ||||
|     ".http": "message/http", | ||||
|     ".kt": "text/x-kotlin", | ||||
|     ".m": "text/x-objectivec", | ||||
|     ".py": "text/x-python", | ||||
|     ".rb": "text/x-ruby", | ||||
|     ".scala": "text/x-scala", | ||||
|     ".swift": "text/x-swift" | ||||
| }; | ||||
| const EXTENSION_TO_MIME = new Map<string, string>([ | ||||
|     [".c", "text/x-csrc"], | ||||
|     [".cs", "text/x-csharp"], | ||||
|     [".clj", "text/x-clojure"], | ||||
|     [".erl", "text/x-erlang"], | ||||
|     [".hrl", "text/x-erlang"], | ||||
|     [".feature", "text/x-feature"], | ||||
|     [".go", "text/x-go"], | ||||
|     [".groovy", "text/x-groovy"], | ||||
|     [".hs", "text/x-haskell"], | ||||
|     [".lhs", "text/x-haskell"], | ||||
|     [".http", "message/http"], | ||||
|     [".kt", "text/x-kotlin"], | ||||
|     [".m", "text/x-objectivec"], | ||||
|     [".py", "text/x-python"], | ||||
|     [".rb", "text/x-ruby"], | ||||
|     [".scala", "text/x-scala"], | ||||
|     [".swift", "text/x-swift"] | ||||
| ]); | ||||
|  | ||||
| /** @returns false if MIME is not detected */ | ||||
| function getMime(fileName: string) { | ||||
|     if (fileName.toLowerCase() === "dockerfile") { | ||||
|     const fileNameLc = fileName?.toLowerCase(); | ||||
|  | ||||
|     if (fileNameLc === "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 EXTENSION_TO_MIME[ext]; | ||||
|     } | ||||
|  | ||||
|     return mimeTypes.lookup(fileName); | ||||
|     return mimeFromExt || mimeTypes.lookup(fileNameLc); | ||||
| } | ||||
|  | ||||
| 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"; | ||||
|     } else if (options.codeImportedAsCode && mime in CODE_MIME_TYPES) { | ||||
|  | ||||
|         case options.codeImportedAsCode && CODE_MIME_TYPES.has(mimeLc): | ||||
|             return "code"; | ||||
|     } else if (mime.startsWith("image/")) { | ||||
|  | ||||
|         case mime.startsWith("image/"): | ||||
|             return "image"; | ||||
|     } else { | ||||
|  | ||||
|         default: | ||||
|             return "file"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| function normalizeMimeType(mime: string) { | ||||
|     mime = mime ? mime.toLowerCase() : ""; | ||||
|     const mappedMime = CODE_MIME_TYPES[mime]; | ||||
|     const mimeLc = mime.toLowerCase(); | ||||
|  | ||||
|     if (mappedMime === true) { | ||||
|         return mime; | ||||
|     } else if (typeof mappedMime === "string") { | ||||
|         return mappedMime; | ||||
|     } | ||||
|  | ||||
|     return undefined; | ||||
|     //prettier-ignore | ||||
|     return CODE_MIME_TYPES.has(mimeLc) | ||||
|         ? mimeLc | ||||
|         : CODE_MIME_TYPES_OVERRIDE.get(mimeLc); | ||||
| } | ||||
|  | ||||
| export default { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user