WIP: add text clip editing

This commit is contained in:
Akshay Kolli
2026-06-30 01:56:40 -07:00
parent 099126d3a2
commit 0567c62310
6 changed files with 205 additions and 4 deletions

View File

@@ -271,6 +271,57 @@ final class ClipboardPanelViewModelTests: XCTestCase {
XCTAssertEqual(NSPasteboard.general.string(forType: .URL), item.payload)
}
func testUpdateSelectedTextRefreshesVisibleItemAndSearch() {
let settings = makeSettings()
let cacheService = makeCacheService()
let store = makeStore(settings: settings, cacheService: cacheService)
let item = makeTextItem("draft meeting note", createdAt: Date(timeIntervalSince1970: 100))
store.upsert(item)
store.flushPersistenceForTesting()
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
waitForVisibleItems(in: viewModel, count: 1)
XCTAssertEqual(viewModel.editableTextForSelected(), "draft meeting note")
viewModel.updateSelectedText(to: "final launch note")
store.flushPersistenceForTesting()
waitForVisibleItems(in: viewModel, count: 1)
XCTAssertEqual(viewModel.statusMessage, "Updated text clip")
XCTAssertEqual(viewModel.selectedItem?.id, item.id)
XCTAssertEqual(viewModel.selectedItem?.displayText, "final launch note")
XCTAssertEqual(viewModel.selectedItem?.payload, "final launch note")
viewModel.searchText = "launch"
XCTAssertEqual(viewModel.visibleItems.map(\.payload), ["final launch note"])
viewModel.searchText = "draft"
XCTAssertTrue(viewModel.visibleItems.isEmpty)
}
func testUpdateSelectedTextRejectsEmptyAndNonTextSelections() {
let settings = makeSettings()
let cacheService = makeCacheService()
let store = makeStore(settings: settings, cacheService: cacheService)
let text = makeTextItem("editable note", createdAt: Date(timeIntervalSince1970: 100))
let file = makeMissingFileItem(useCount: 0)
store.upsert(text)
store.upsert(file)
store.flushPersistenceForTesting()
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
waitForVisibleItems(in: viewModel, count: 2)
XCTAssertNil(viewModel.editableTextForItem(at: 0))
viewModel.selectItem(at: 0)
viewModel.updateSelectedText(to: "should not apply")
XCTAssertEqual(store.items.first?.payload, file.payload)
viewModel.selectItem(at: 1)
viewModel.updateSelectedText(to: " \n")
XCTAssertEqual(viewModel.statusMessage, "Text clip cannot be empty")
XCTAssertEqual(store.items.last?.payload, "editable note")
}
func testFailedCopyDoesNotMarkItemUsed() {
let settings = makeSettings()
let cacheService = makeCacheService()

View File

@@ -69,6 +69,18 @@ final class ClipboardPanelViewTests: XCTestCase {
XCTAssertFalse(fixture.view.debugStatusText.contains("Enter paste"))
}
func testEditedTextStatusUsesActionTone() {
let fixture = makePanelFixture()
fixture.store.upsert(makeTextItem("Editable footer item", store: fixture.store))
drainMainQueue()
fixture.viewModel.updateSelectedText(to: "Edited footer item")
drainMainQueue()
XCTAssertEqual(fixture.view.debugStatusText, "Updated text clip")
XCTAssertEqual(fixture.view.debugStatusTone, "action")
}
func testSkippedCaptureStatusUsesWarningTone() {
let fixture = makePanelFixture()
@@ -172,8 +184,8 @@ final class ClipboardPanelViewTests: XCTestCase {
fixture.store.upsert(makeTextItem("Plain text", store: fixture.store))
drainMainQueue()
XCTAssertEqual(fixture.view.debugFirstCardVisibleActionLabels, ["Paste", "Copy", "Pin", "Delete"])
XCTAssertEqual(fixture.view.debugFirstCardVisibleActionRailWidth, 126)
XCTAssertEqual(fixture.view.debugFirstCardVisibleActionLabels, ["Paste", "Copy", "Pin", "Edit", "Delete"])
XCTAssertEqual(fixture.view.debugFirstCardVisibleActionRailWidth, 154)
XCTAssertFalse(fixture.view.debugFirstCardFooterDetailIsHidden)
XCTAssertTrue(fixture.view.debugFirstCardHeaderBadgeIsHidden)
@@ -321,7 +333,7 @@ final class ClipboardPanelViewTests: XCTestCase {
XCTAssertEqual(
fixture.view.debugFirstCardMenuTitles,
["Paste", "Copy", "Pin", "Add to Collection", "-", "Open", "Reveal in Finder", "-", "Delete"]
["Paste", "Copy", "Edit", "Pin", "Add to Collection", "-", "Open", "Reveal in Finder", "-", "Delete"]
)
XCTAssertEqual(
fixture.view.debugFirstCardCollectionMenuTitles,

View File

@@ -138,6 +138,50 @@ final class ClipboardStoreTests: XCTestCase {
XCTAssertNil(cleared.items.first?.collectionName)
}
func testUpdateTextPersistsAcrossReloadAndPreservesMetadata() {
let settings = makeSettings(maxHistory: 50)
let store = makeStore(settings: settings)
var item = makeItem("alpha", displayText: "Alpha", created: Date(timeIntervalSince1970: 100))
item.isPinned = true
item.collectionName = "Important Notes"
item.sourceApp = "Notes"
item.useCount = 4
store.upsert(item)
store.flushPersistenceForTesting()
let itemID = try! XCTUnwrap(store.items.first?.id)
XCTAssertTrue(store.updateText(itemID, text: "Edited alpha"))
store.flushPersistenceForTesting()
let restored = makeStore(settings: settings)
restored.flushPersistenceForTesting()
let restoredItem = try! XCTUnwrap(restored.items.first)
XCTAssertEqual(restoredItem.id, itemID)
XCTAssertEqual(restoredItem.kind, .text)
XCTAssertEqual(restoredItem.displayText, "Edited alpha")
XCTAssertEqual(restoredItem.payload, "Edited alpha")
XCTAssertEqual(restoredItem.payloadHash, restored.hashString("Edited alpha"))
XCTAssertEqual(restoredItem.isPinned, true)
XCTAssertEqual(restoredItem.collectionName, "Important Notes")
XCTAssertEqual(restoredItem.sourceApp, "Notes")
XCTAssertEqual(restoredItem.useCount, 4)
}
func testUpdateTextRejectsNonTextItems() {
let settings = makeSettings(maxHistory: 50)
let store = makeStore(settings: settings)
let pdf = makePDFItem(path: "/tmp/report.pdf", hash: "pdf-hash", created: Date(timeIntervalSince1970: 100))
store.upsert(pdf)
store.flushPersistenceForTesting()
let itemID = try! XCTUnwrap(store.items.first?.id)
XCTAssertFalse(store.updateText(itemID, text: "Edited"))
XCTAssertEqual(store.items.first?.payload, "/tmp/report.pdf")
XCTAssertEqual(store.items.first?.payloadHash, "pdf-hash")
}
func testLegacyJSONHistoryMigratesToSQLite() throws {
let settings = makeSettings(maxHistory: 50)
let itemID = UUID()