import AppKit import ConjureKit import Testing @Test @MainActor func markdownExportPreservesWholeTextAndSnapshotsBeforeWriting() throws { let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: directory) } try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false) let text = "\n日本語 🦊 \r\nplain and code\nordinary task\n\u{fffc}\n" let source = NSMutableAttributedString(string: text) source.addAttribute(.attachment, value: NSTextAttachment(), range: NSRange(location: 0, length: 1)) let export = try NoteMarkdownExport(contents: source) #expect(!export.hasImages) #expect(export.clipboardText == text) #expect(source.string == text) source.mutableString.setString("Edited after export began") let file = directory.appendingPathComponent("Note.md") try Data("old file".utf8).write(to: file) try export.write(to: file) #expect(try Data(contentsOf: file) == Data(text.utf8)) #expect(try FileManager.default.contentsOfDirectory(atPath: directory.path) == ["Note.md"]) } @Test @MainActor func markdownExportWritesOriginalImageBytesAndEscapedLinksWithoutChangingSource() throws { let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: directory) } let png = try markdownExportPNG() let wrapper = FileWrapper(regularFileWithContents: png) let name = "pixel [x] `1` #%.png" wrapper.preferredFilename = name let originalFilename = wrapper.filename let attachment = NSTextAttachment(fileWrapper: wrapper) let source = NSMutableAttributedString(attributedString: NoteDocument.importMarkdown(NSAttributedString(string: "\n# 🦊\n"))) source.append(NSAttributedString(attachment: attachment)) source.append(NoteDocument.importMarkdown(NSAttributedString(string: "\n- [ ] todo \n"))) let before = NSAttributedString(attributedString: source) let export = try NoteMarkdownExport(contents: source) #expect(export.hasImages) #expect(export.clipboardText == "\n# 🦊\n````\n- [ ] todo \n") #expect(source.isEqual(to: before)) #expect(wrapper.preferredFilename == name) #expect(wrapper.filename == originalFilename) #expect(wrapper.regularFileContents == png) source.mutableString.setString("Changed") wrapper.preferredFilename = "changed.png" try export.write(to: directory) let markdown = "\n# 🦊\n![pixel \\[x\\] \\`1\\` #%.png](pixel%20%5Bx%5D%20%601%60%20%23%25.png)\n- [ ] todo \n" #expect(try String(contentsOf: directory.appendingPathComponent("note.md"), encoding: .utf8) == markdown) #expect(try Data(contentsOf: directory.appendingPathComponent(name)) == png) #expect(Set(try FileManager.default.contentsOfDirectory(atPath: directory.path)) == ["note.md", name]) } @Test @MainActor func markdownExportUsesSafeUniqueImageNamesAndReservesMarkdownFile() throws { let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: directory) } let png = try markdownExportPNG() let names = ["same.png", "SAME.png", "../escape.png", "note.md", "CON.png", "bad\\name.png"] let source = NSMutableAttributedString(string: "") var wrappers: [FileWrapper] = [] for name in names { let wrapper = FileWrapper(regularFileWithContents: png) wrapper.preferredFilename = name wrappers.append(wrapper) source.append(NSAttributedString(attachment: NSTextAttachment(fileWrapper: wrapper))) source.append(NSAttributedString(string: "\n")) } let expected = ["same.png", "SAME-2.png", "image-3.png", "note.png", "image-5.png", "image-6.png"] let export = try NoteMarkdownExport(contents: source) #expect(export.clipboardText == expected.map { "`<\($0)>`\n" }.joined()) #expect(wrappers.map(\.preferredFilename) == names.map(Optional.some)) try export.write(to: directory) #expect(try String(contentsOf: directory.appendingPathComponent("note.md"), encoding: .utf8) == expected.map { "![\($0)](\($0))\n" }.joined()) #expect(Set(try FileManager.default.contentsOfDirectory(atPath: directory.path)) == Set(expected + ["note.md"])) for name in expected { #expect(try Data(contentsOf: directory.appendingPathComponent(name)) == png) } } @Test @MainActor func markdownExportPreservesExistingDirectoryAndRemovesFailedStagingDirectory() throws { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: root) } let existing = root.appendingPathComponent("Existing") try FileManager.default.createDirectory(at: existing, withIntermediateDirectories: true) let marker = existing.appendingPathComponent("keep.txt") let saved = Data("User data".utf8) try saved.write(to: marker) let wrapper = FileWrapper(regularFileWithContents: try markdownExportPNG()) wrapper.preferredFilename = "pixel.png" let export = try NoteMarkdownExport(contents: NSAttributedString(attachment: NSTextAttachment(fileWrapper: wrapper))) #expect(throws: (any Error).self) { try export.write(to: existing) } #expect(try Data(contentsOf: marker) == saved) #expect(try FileManager.default.contentsOfDirectory(atPath: existing.path) == ["keep.txt"]) let invalidDestination = root.appendingPathComponent(String(repeating: "x", count: 256)) #expect(throws: (any Error).self) { try export.write(to: invalidDestination) } #expect(try FileManager.default.contentsOfDirectory(atPath: root.path) == ["Existing"]) } @Test @MainActor func markdownExportSupportsNativeImageOnlyAttachmentsAndRejectsUnreadableAttachments() throws { let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: directory) } let image = try #require(NSImage(data: markdownExportPNG())) let attachment = NSTextAttachment() attachment.attachmentCell = NSTextAttachmentCell(imageCell: image) #expect(attachment.fileWrapper == nil) let export = try NoteMarkdownExport(contents: NSAttributedString(attachment: attachment)) #expect(export.clipboardText == "``") try export.write(to: directory) let data = try Data(contentsOf: directory.appendingPathComponent("image-1.png")) #expect(NSBitmapImageRep(data: data)?.pixelsWide == 1) #expect(NSBitmapImageRep(data: data)?.pixelsHigh == 1) for wrapper in [FileWrapper(regularFileWithContents: Data("not an image".utf8)), FileWrapper(directoryWithFileWrappers: [:])] { let source = NSAttributedString(attachment: NSTextAttachment(fileWrapper: wrapper)) #expect(throws: (any Error).self) { try NoteMarkdownExport(contents: source) } #expect(source.string == "\u{fffc}") } } @MainActor private func markdownExportPNG() throws -> Data { let bitmap = try #require(NSBitmapImageRep( bitmapDataPlanes: nil, pixelsWide: 1, pixelsHigh: 1, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0 )) bitmap.setColor(.red, atX: 0, y: 0) return try #require(bitmap.representation(using: .png, properties: [:])) }