WIP: add video clip support
This commit is contained in:
@@ -119,6 +119,20 @@ final class ClipboardCacheServiceTests: XCTestCase {
|
||||
XCTAssertEqual(try posixPermissions(URL(fileURLWithPath: path)), 0o600)
|
||||
}
|
||||
|
||||
func testVideoCacheFilesAreEncryptedAndReadable() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let videoData = Data([0, 0, 0, 24, 102, 116, 121, 112, 109, 112, 52, 50])
|
||||
|
||||
let path = try XCTUnwrap(cacheService.cacheVideo(videoData, id: UUID(), fileExtension: "mp4"))
|
||||
let rawVideo = try Data(contentsOf: URL(fileURLWithPath: path))
|
||||
|
||||
XCTAssertTrue(ClipboardEncryptionService.isProtected(rawVideo))
|
||||
XCTAssertNotEqual(rawVideo, videoData)
|
||||
XCTAssertEqual(cacheService.data(for: path), videoData)
|
||||
XCTAssertEqual(try posixPermissions(URL(fileURLWithPath: path)), 0o600)
|
||||
}
|
||||
|
||||
func testRichTextCacheFilesAreEncryptedAndReadable() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
@@ -178,6 +192,19 @@ final class ClipboardCacheServiceTests: XCTestCase {
|
||||
XCTAssertEqual(try posixPermissions(previewURL), 0o600)
|
||||
}
|
||||
|
||||
func testTemporaryReadableURLWorksForVideo() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let videoData = Data([0, 0, 0, 24, 102, 116, 121, 112, 109, 112, 52, 50])
|
||||
let path = try XCTUnwrap(cacheService.cacheVideo(videoData, id: UUID(), fileExtension: "mp4"))
|
||||
|
||||
let previewURL = try XCTUnwrap(cacheService.temporaryReadableURL(for: videoItem(path: path)))
|
||||
|
||||
XCTAssertEqual(try Data(contentsOf: previewURL), videoData)
|
||||
XCTAssertEqual(previewURL.pathExtension, "mp4")
|
||||
XCTAssertEqual(try posixPermissions(previewURL), 0o600)
|
||||
}
|
||||
|
||||
func testTemporaryReadableURLWorksForRichText() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
@@ -325,6 +352,22 @@ final class ClipboardCacheServiceTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
private func videoItem(path: String) -> ClipboardItem {
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .video,
|
||||
displayText: "Video",
|
||||
payload: path,
|
||||
payloadHash: "hash",
|
||||
createdAt: Date(),
|
||||
lastUsedAt: Date(),
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func richTextItem(path: String) -> ClipboardItem {
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
|
||||
@@ -218,6 +218,34 @@ final class ClipboardMonitorServiceTests: XCTestCase {
|
||||
XCTAssertEqual(NSPasteboard.general.data(forType: .sound), audioData)
|
||||
}
|
||||
|
||||
func testPollNowCapturesVideoAsRestorableAttachment() throws {
|
||||
let settings = SettingsModel(defaults: makeTestDefaults())
|
||||
let (store, cacheService) = makeStoreAndCache(settings: settings)
|
||||
let monitor = ClipboardMonitorService(store: store, cacheService: cacheService, settings: settings)
|
||||
let videoData = Data([0, 0, 0, 24, 102, 116, 121, 112, 109, 112, 52, 50])
|
||||
let captured = expectation(description: "video captured")
|
||||
|
||||
store.observeItems { items in
|
||||
if items.contains(where: { $0.kind == .video }) {
|
||||
captured.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
XCTAssertTrue(pasteboard.setData(videoData, forType: VideoPayload.pasteboardTypes[0]))
|
||||
|
||||
monitor.pollNowAndWait()
|
||||
wait(for: [captured], timeout: 1.0)
|
||||
|
||||
let item = try XCTUnwrap(store.items.first(where: { $0.kind == .video }))
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: item.payload))
|
||||
XCTAssertEqual(item.displayText, VideoPayload.displayTitle(byteCount: videoData.count))
|
||||
XCTAssertEqual(cacheService.data(for: item.payload), videoData)
|
||||
XCTAssertEqual(PasteActionService(cacheService: cacheService).copy(item), .copied)
|
||||
XCTAssertEqual(NSPasteboard.general.data(forType: VideoPayload.pasteboardTypes[0]), videoData)
|
||||
}
|
||||
|
||||
func testPollNowCapturesColorAsRestorableSwatch() throws {
|
||||
let settings = SettingsModel(defaults: makeTestDefaults())
|
||||
let (store, cacheService) = makeStoreAndCache(settings: settings)
|
||||
@@ -726,6 +754,25 @@ final class ClipboardMonitorServiceTests: XCTestCase {
|
||||
XCTAssertEqual(settings.captureStatusMessage, "Skipped: Audio items are ignored in capture settings.")
|
||||
}
|
||||
|
||||
func testIgnoredVideoKindDoesNotWriteAttachmentFiles() throws {
|
||||
let settings = SettingsModel(defaults: makeTestDefaults())
|
||||
settings.ignoredItemKindsRaw = [ClipboardItemKind.video.rawValue]
|
||||
let (store, cacheService, baseURL) = makeStoreCacheAndBaseURL(settings: settings)
|
||||
let monitor = ClipboardMonitorService(store: store, cacheService: cacheService, settings: settings)
|
||||
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
XCTAssertTrue(pasteboard.setData(Data([0, 0, 0, 24, 102, 116, 121, 112]), forType: VideoPayload.pasteboardTypes[0]))
|
||||
|
||||
monitor.pollNowAndWait()
|
||||
RunLoop.main.run(until: Date().addingTimeInterval(0.05))
|
||||
cacheService.flushForTesting()
|
||||
|
||||
XCTAssertTrue(store.items.isEmpty)
|
||||
XCTAssertTrue(try attachmentFileURLs(in: baseURL).isEmpty)
|
||||
XCTAssertEqual(settings.captureStatusMessage, "Skipped: Video items are ignored in capture settings.")
|
||||
}
|
||||
|
||||
func testIgnoredRichTextKindDoesNotWriteHTMLAttachmentFiles() throws {
|
||||
let settings = SettingsModel(defaults: makeTestDefaults())
|
||||
settings.ignoredItemKindsRaw = [ClipboardItemKind.richText.rawValue]
|
||||
|
||||
@@ -129,6 +129,51 @@ final class ClipboardPanelViewModelTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testComputeVisibleItemsFiltersVideoClipsAndStructuredType() {
|
||||
let settings = makeSettings()
|
||||
let store = makeStore(settings: settings)
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: ClipboardCacheService())
|
||||
let video = ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .video,
|
||||
displayText: "Video (12 KB)",
|
||||
payload: "/tmp/clip.mp4",
|
||||
payloadHash: hash("clip-video"),
|
||||
createdAt: Date(timeIntervalSince1970: 200),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 200),
|
||||
useCount: 0,
|
||||
sourceApp: "QuickTime Player",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
let image = ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .image,
|
||||
displayText: "Image",
|
||||
payload: "/tmp/image.png",
|
||||
payloadHash: hash("image"),
|
||||
createdAt: Date(timeIntervalSince1970: 100),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 100),
|
||||
useCount: 0,
|
||||
sourceApp: "Preview",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [video, image], query: "", sortMode: .videos).map(\.kind),
|
||||
[.video]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [video, image], query: "type:movie", sortMode: .mostRecent).map(\.kind),
|
||||
[.video]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [video, image], query: "mp4", sortMode: .mostRecent).map(\.kind),
|
||||
[.video]
|
||||
)
|
||||
}
|
||||
|
||||
func testSearchMatchesIndependentTokensCaseInsensitively() {
|
||||
let settings = makeSettings()
|
||||
let store = makeStore(settings: settings)
|
||||
|
||||
@@ -246,11 +246,11 @@ final class ClipboardPanelViewTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(
|
||||
fixture.view.debugCollectionTitles,
|
||||
["Clipboard", "Frequent", "Text", "Links", "Images", "Colors", "Audio", "Files", "Pinned", "Code"]
|
||||
["Clipboard", "Frequent", "Text", "Links", "Images", "Colors", "Audio", "Videos", "Files", "Pinned", "Code"]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
fixture.view.debugCollectionLeadingSymbols,
|
||||
["doc.on.clipboard", "chart.bar.fill", "text.alignleft", "link", "photo", "paintpalette", "music.note", "doc.fill", "pin.fill", "chevron.left.forwardslash.chevron.right"]
|
||||
["doc.on.clipboard", "chart.bar.fill", "text.alignleft", "link", "photo", "paintpalette", "music.note", "film", "doc.fill", "pin.fill", "chevron.left.forwardslash.chevron.right"]
|
||||
)
|
||||
XCTAssertEqual(fixture.view.debugSelectedCollectionTitle, "Clipboard")
|
||||
|
||||
@@ -680,6 +680,7 @@ final class ClipboardPanelViewTests: XCTestCase {
|
||||
let image = makeItem(kind: .image, text: "image payload", store: fixture.store)
|
||||
let color = makeItem(kind: .color, displayText: "#0A84FF", payload: "#0A84FF", store: fixture.store)
|
||||
let audio = makeItem(kind: .audio, text: "audio payload", store: fixture.store)
|
||||
let video = makeItem(kind: .video, text: "/tmp/movie.mp4", store: fixture.store)
|
||||
let file = makeItem(kind: .file, text: "/tmp/report.pdf", store: fixture.store)
|
||||
let code = makeItem(
|
||||
kind: .code,
|
||||
@@ -688,14 +689,14 @@ final class ClipboardPanelViewTests: XCTestCase {
|
||||
store: fixture.store
|
||||
)
|
||||
|
||||
[pinned, rich, link, image, color, audio, file, code].forEach {
|
||||
[pinned, rich, link, image, color, audio, video, file, code].forEach {
|
||||
fixture.store.upsert($0)
|
||||
drainMainQueue()
|
||||
}
|
||||
|
||||
XCTAssertEqual(fixture.viewModel.visibleItems.count, 8)
|
||||
XCTAssertEqual(ClipboardSortMode.allCases.map { fixture.viewModel.collectionCount(for: $0) }, [8, 8, 3, 1, 1, 1, 1, 1, 1, 1])
|
||||
XCTAssertEqual(fixture.view.debugCollectionCounts, [8, 8, 3, 1, 1, 1, 1, 1, 1, 1])
|
||||
XCTAssertEqual(fixture.viewModel.visibleItems.count, 9)
|
||||
XCTAssertEqual(ClipboardSortMode.allCases.map { fixture.viewModel.collectionCount(for: $0) }, [9, 9, 3, 1, 1, 1, 1, 1, 1, 1, 1])
|
||||
XCTAssertEqual(fixture.view.debugCollectionCounts, [9, 9, 3, 1, 1, 1, 1, 1, 1, 1, 1])
|
||||
XCTAssertEqual(fixture.view.debugCollectionCountLabelHiddenStates, Array(repeating: false, count: ClipboardSortMode.allCases.count))
|
||||
}
|
||||
|
||||
@@ -1366,6 +1367,25 @@ final class ClipboardPanelViewTests: XCTestCase {
|
||||
XCTAssertEqual(fixture.view.debugCardPreviewStyles, ["audio-preview"])
|
||||
}
|
||||
|
||||
func testVideoCardsUseFilmPreview() {
|
||||
let fixture = makePanelFixture()
|
||||
let item = makeItem(
|
||||
kind: .video,
|
||||
displayText: "Video (24 KB)",
|
||||
payload: "/tmp/clipbored-video.mp4",
|
||||
store: fixture.store
|
||||
)
|
||||
|
||||
fixture.store.upsert(item)
|
||||
fixture.viewModel.sortMode = .videos
|
||||
drainMainQueue()
|
||||
fixture.window.contentView?.layoutSubtreeIfNeeded()
|
||||
|
||||
XCTAssertEqual(fixture.view.debugCardAccessibilityLabels, ["Video: Video (24 KB)"])
|
||||
XCTAssertEqual(fixture.view.debugCardPreviewSummaries, ["Video (24 KB)|Video clip|MP4"])
|
||||
XCTAssertEqual(fixture.view.debugCardPreviewStyles, ["video-preview"])
|
||||
}
|
||||
|
||||
func testColorCardsUseSwatchPreview() {
|
||||
let fixture = makePanelFixture()
|
||||
let item = makeItem(
|
||||
|
||||
@@ -534,6 +534,32 @@ final class PasteActionServiceTests: XCTestCase {
|
||||
XCTAssertEqual(NSPasteboard.general.data(forType: .sound), audioData)
|
||||
}
|
||||
|
||||
func testCopyWritesVideoData() throws {
|
||||
let directory = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: directory, encryptionService: fixedEncryptionService())
|
||||
let videoData = Data([0, 0, 0, 24, 102, 116, 121, 112, 109, 112, 52, 50])
|
||||
let path = try XCTUnwrap(cacheService.cacheVideo(videoData, id: UUID(), fileExtension: "mp4"))
|
||||
let service = PasteActionService(cacheService: cacheService)
|
||||
let item = ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .video,
|
||||
displayText: "Video",
|
||||
payload: path,
|
||||
payloadHash: "hash",
|
||||
createdAt: Date(),
|
||||
lastUsedAt: Date(),
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
|
||||
XCTAssertEqual(service.copy(item), .copied)
|
||||
XCTAssertEqual(NSPasteboard.general.data(forType: VideoPayload.pasteboardTypes[0]), videoData)
|
||||
XCTAssertEqual(service.copyPlainText(item), .copiedPlainText)
|
||||
XCTAssertEqual(NSPasteboard.general.string(forType: .string), "Video")
|
||||
}
|
||||
|
||||
func testCopyWritesEncryptedPDFData() throws {
|
||||
let directory = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: directory, encryptionService: fixedEncryptionService())
|
||||
|
||||
Reference in New Issue
Block a user