133 lines
7.3 KiB
Swift
133 lines
7.3 KiB
Swift
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``<pixel [x] `1` #%.png>``\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 { ")\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 == "`<image-1.png>`")
|
|
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: [:]))
|
|
}
|