Every board root keys once at its store — the hover path stops walking the filesystem
`DragLocality.isSameBoard` resolved symlinks on both URLs, which stats every path component. It ran five-plus times per `dropUpdated` and again per lane per body evaluation through `renderedCards` → `hiddenMembers` — order of 50–100 stat calls per mouse-move, and worse on iCloud-backed paths. A pickup stall sample had it on ~47 of 943 stacks. `BoardRootKey` mints that canonical spelling once, where the root is, and every locality comparison downstream is an `==` on two strings. The drag carriers hold keys rather than URLs, so re-deriving under the cursor is no longer expressible. `rootURL` keeps the user's spelling — the folder name is the display-name fallback, and canonicalizing there would visibly rename a board opened through a pin. The key follows the folder through `relocate(to:)`: a key frozen at open would collide with a new board opened at the vacated path, and two boards comparing as one is a cross-board drag silently behaving as a move. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -69,10 +69,15 @@ struct DragPayloadTests {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Locality
|
||||
// MARK: - Board identity
|
||||
|
||||
@Suite("DragLocality")
|
||||
struct DragLocalityTests {
|
||||
/// **The one canonical spelling every locality comparison is made against** (`BoardRootKey`).
|
||||
///
|
||||
/// Minting is where the filesystem is touched — once per board, at `BoardStore.rootKey` — and these
|
||||
/// are the claims that one touch has to buy: every spelling of a place is one key, and two places
|
||||
/// are two keys.
|
||||
@Suite("BoardRootKey")
|
||||
struct BoardRootKeyTests {
|
||||
|
||||
// Instance members, not `static`: every case below names them bare, and a static member is not
|
||||
// reachable unqualified from an instance method. Swift Testing builds a fresh instance per test,
|
||||
@@ -80,18 +85,84 @@ struct DragLocalityTests {
|
||||
private let here = URL(fileURLWithPath: "/Boards/Work.kanban", isDirectory: true)
|
||||
private let there = URL(fileURLWithPath: "/Boards/Home.kanban", isDirectory: true)
|
||||
|
||||
@Test("Roots key on their standardized path, so the same board is the same board")
|
||||
func standardization() {
|
||||
#expect(BoardRootKey(here) == BoardRootKey(here))
|
||||
#expect(BoardRootKey(here) == BoardRootKey(URL(fileURLWithPath: "/Boards/./Work.kanban/")))
|
||||
#expect(BoardRootKey(here) == BoardRootKey(URL(fileURLWithPath: "/Boards/Other/../Work.kanban")))
|
||||
#expect(BoardRootKey(here) != BoardRootKey(there))
|
||||
}
|
||||
|
||||
/// Why the key resolves at all: "a board reached through a pinned symlink is the same board as
|
||||
/// the one reached directly" (01-storage-format.md's symlink pins). A real link over a real
|
||||
/// folder, because the claim is a filesystem fact and nothing else can stand in for one.
|
||||
@Test("A board reached through a pin is the board the pin points at")
|
||||
func pinsNameTheirTarget() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
let real = fixture.url("Work.kanban")
|
||||
try FileManager.default.createDirectory(at: real, withIntermediateDirectories: true)
|
||||
let pin = fixture.url("Pinned.kanban")
|
||||
try FileManager.default.createSymbolicLink(at: pin, withDestinationURL: real)
|
||||
|
||||
#expect(BoardRootKey(pin) == BoardRootKey(real))
|
||||
// The key names the target rather than the link — the pin is a spelling, not a second board.
|
||||
#expect(BoardRootKey(pin).path.hasSuffix("Work.kanban"))
|
||||
// A sibling the link does not point at stays a board of its own.
|
||||
#expect(BoardRootKey(pin) != BoardRootKey(fixture.url("Other.kanban")))
|
||||
}
|
||||
|
||||
/// **The store mints once and keeps both**: the key is the board's identity, and `rootURL` keeps
|
||||
/// the user's spelling because the folder name is the board's display-name fallback
|
||||
/// (01-storage-format.md § Frontmatter) — canonicalizing there would visibly rename a board
|
||||
/// opened through a pin.
|
||||
@MainActor
|
||||
@Test("A board opened through a pin keeps the pin's spelling and the target's identity")
|
||||
func theStoreKeepsTheSpellingAndCachesTheIdentity() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
let real = try fixture.item("Work.kanban", Item.board)
|
||||
let pin = fixture.url("Pinned.kanban")
|
||||
try FileManager.default.createSymbolicLink(at: pin, withDestinationURL: real)
|
||||
|
||||
let store = try BoardStore(rootURL: pin)
|
||||
#expect(store.rootKey == BoardRootKey(real))
|
||||
#expect(store.rootURL.lastPathComponent == "Pinned.kanban")
|
||||
}
|
||||
|
||||
/// The key follows the folder. A board renamed away and a new board opened at the vacated path
|
||||
/// are two boards, and nothing about the drag they share may say otherwise — which is only true
|
||||
/// if the relocation re-mints.
|
||||
@MainActor
|
||||
@Test("An absorbed relocation re-mints the key, so the vacated path is somebody else's board")
|
||||
func relocationRemintsTheKey() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
let original = try fixture.item("Work.kanban", Item.board)
|
||||
let store = try BoardStore(rootURL: original)
|
||||
let before = store.rootKey
|
||||
|
||||
let moved = fixture.url("Renamed.kanban")
|
||||
try FileManager.default.moveItem(at: original, to: moved)
|
||||
store.relocate(to: moved)
|
||||
|
||||
#expect(store.rootKey == BoardRootKey(moved))
|
||||
#expect(store.rootKey != before)
|
||||
|
||||
let successor = try BoardStore(rootURL: try fixture.item("Work.kanban", Item.board))
|
||||
#expect(successor.rootKey != store.rootKey)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Locality
|
||||
|
||||
@Suite("DragLocality")
|
||||
struct DragLocalityTests {
|
||||
|
||||
private let none: NSEvent.ModifierFlags = []
|
||||
private let option: NSEvent.ModifierFlags = [.option]
|
||||
private let command: NSEvent.ModifierFlags = [.command]
|
||||
|
||||
@Test("Roots compare by their standardized path, so the same board is the same board")
|
||||
func rootComparison() {
|
||||
#expect(DragLocality.isSameBoard(here, here))
|
||||
#expect(DragLocality.isSameBoard(here, URL(fileURLWithPath: "/Boards/./Work.kanban/")))
|
||||
#expect(DragLocality.isSameBoard(here, URL(fileURLWithPath: "/Boards/Other/../Work.kanban")))
|
||||
#expect(!DragLocality.isSameBoard(here, there))
|
||||
}
|
||||
|
||||
/// The Finder volume model: within a board a drag rearranges, between boards it transfers.
|
||||
@Test("Locality picks the default — within is a move, across is a copy")
|
||||
func theDefault() {
|
||||
@@ -320,8 +391,8 @@ struct MixedTrashDragTests {
|
||||
@Suite("CommittedHold")
|
||||
struct CommittedHoldTests {
|
||||
|
||||
private static let here = URL(fileURLWithPath: "/Boards/Work.kanban", isDirectory: true)
|
||||
private static let there = URL(fileURLWithPath: "/Boards/Home.kanban", isDirectory: true)
|
||||
private static let here = BoardRootKey(URL(fileURLWithPath: "/Boards/Work.kanban", isDirectory: true))
|
||||
private static let there = BoardRootKey(URL(fileURLWithPath: "/Boards/Home.kanban", isDirectory: true))
|
||||
|
||||
private static let hold = CommittedHold(boardRoot: here, generation: 7)
|
||||
|
||||
@@ -343,7 +414,8 @@ struct CommittedHoldTests {
|
||||
|
||||
@Test("The board is matched by identity, not by string")
|
||||
func rootMatchingUsesTheLocalityComparison() {
|
||||
#expect(Self.hold.isRetired(byRoot: URL(fileURLWithPath: "/Boards/./Work.kanban/"), generation: 8))
|
||||
let sameBoard = BoardRootKey(URL(fileURLWithPath: "/Boards/./Work.kanban/"))
|
||||
#expect(Self.hold.isRetired(byRoot: sameBoard, generation: 8))
|
||||
}
|
||||
|
||||
@Test("A stale generation never retires it")
|
||||
@@ -404,7 +476,7 @@ struct DropSettleTests {
|
||||
) -> DragSession {
|
||||
let session = DragSession()
|
||||
pickUp(session, from: store, members: members)
|
||||
session.propose(DropTarget(boardRoot: store.rootURL, container: .lane(Self.lane1), index: index))
|
||||
session.propose(DropTarget(boardRoot: store.rootKey, container: .lane(Self.lane1), index: index))
|
||||
return session
|
||||
}
|
||||
|
||||
@@ -449,8 +521,8 @@ struct DropSettleTests {
|
||||
let session = proposing(store)
|
||||
|
||||
#expect(!session.isSettled)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootURL, laneID: Self.lane1) == 2)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.card1])
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootKey, laneID: Self.lane1) == 2)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.card1])
|
||||
}
|
||||
|
||||
// MARK: The hold
|
||||
@@ -469,8 +541,8 @@ struct DropSettleTests {
|
||||
session.commit(into: store)
|
||||
|
||||
#expect(session.isSettled)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootURL, laneID: Self.lane1) == 2)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.card1])
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootKey, laneID: Self.lane1) == 2)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.card1])
|
||||
}
|
||||
|
||||
@Test("A settled release is past retargeting: a late callback cannot move or withdraw it")
|
||||
@@ -482,9 +554,9 @@ struct DropSettleTests {
|
||||
session.commit(into: store)
|
||||
|
||||
session.propose(nil)
|
||||
session.propose(DropTarget(boardRoot: store.rootURL, container: .lane(Self.lane1), index: 0))
|
||||
session.propose(DropTarget(boardRoot: store.rootKey, container: .lane(Self.lane1), index: 0))
|
||||
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootURL, laneID: Self.lane1) == 2)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootKey, laneID: Self.lane1) == 2)
|
||||
}
|
||||
|
||||
// MARK: The trash's landing
|
||||
@@ -495,7 +567,7 @@ struct DropSettleTests {
|
||||
let session = DragSession()
|
||||
pickUp(session, from: store, members: members)
|
||||
session.propose(DropTarget(
|
||||
boardRoot: store.rootURL,
|
||||
boardRoot: store.rootKey,
|
||||
container: .trash,
|
||||
index: TrashDrop.landingIndex
|
||||
))
|
||||
@@ -509,13 +581,13 @@ struct DropSettleTests {
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let session = proposingIntoTheTrash(store)
|
||||
|
||||
#expect(session.trashProposal(onBoardRooted: store.rootURL) == 0)
|
||||
#expect(session.trashProposal(onBoardRooted: store.rootKey) == 0)
|
||||
// The proposal names one container and one only: the lane the cards came out of draws
|
||||
// nothing, and neither does the strip.
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootURL, laneID: Self.lane1) == nil)
|
||||
#expect(session.stripProposal(onBoardRooted: store.rootURL) == nil)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootKey, laneID: Self.lane1) == nil)
|
||||
#expect(session.stripProposal(onBoardRooted: store.rootKey) == nil)
|
||||
// And they are lifted out of the lane, as ever.
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.card1])
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.card1])
|
||||
}
|
||||
|
||||
/// The delete's own hold: the write takes the cards off the live side, and the shadow rows keep
|
||||
@@ -529,9 +601,9 @@ struct DropSettleTests {
|
||||
|
||||
session.commit(into: store)
|
||||
|
||||
#expect(session.trashProposal(onBoardRooted: store.rootURL) == 0)
|
||||
#expect(session.trashProposal(onBoardRooted: store.rootKey) == 0)
|
||||
#expect(session.shadowCount == 2)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.card1, Self.card2])
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.card1, Self.card2])
|
||||
}
|
||||
|
||||
/// The column draws the delete gesture's shadow for a **lane** session too (lanes extended
|
||||
@@ -549,12 +621,12 @@ struct DropSettleTests {
|
||||
units: [1],
|
||||
source: store
|
||||
)
|
||||
session.propose(DropTarget(boardRoot: store.rootURL, container: .trash, index: 0))
|
||||
session.propose(DropTarget(boardRoot: store.rootKey, container: .trash, index: 0))
|
||||
|
||||
#expect(session.trashProposal(onBoardRooted: store.rootURL) == 0)
|
||||
#expect(session.trashProposal(onBoardRooted: store.rootKey) == 0)
|
||||
#expect(session.shadowCount == 1)
|
||||
// Another board's column draws nothing, like every other proposal accessor.
|
||||
#expect(session.trashProposal(onBoardRooted: URL(filePath: "/tmp/other-board")) == nil)
|
||||
#expect(session.trashProposal(onBoardRooted: BoardRootKey(URL(filePath: "/tmp/other-board"))) == nil)
|
||||
}
|
||||
|
||||
// MARK: The hand-off
|
||||
@@ -567,12 +639,12 @@ struct DropSettleTests {
|
||||
let session = proposing(store)
|
||||
session.commit(into: store)
|
||||
|
||||
session.handOff(root: store.rootURL, generation: store.snapshotGeneration + 1)
|
||||
session.handOff(root: store.rootKey, generation: store.snapshotGeneration + 1)
|
||||
|
||||
#expect(!session.isSettled)
|
||||
#expect(!session.isActive)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootURL, laneID: Self.lane1) == nil)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootKey, laneID: Self.lane1) == nil)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
}
|
||||
|
||||
@Test("Ending the session outright ends the hold with it")
|
||||
@@ -586,8 +658,8 @@ struct DropSettleTests {
|
||||
session.end()
|
||||
|
||||
#expect(!session.isSettled)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootURL, laneID: Self.lane1) == nil)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootKey, laneID: Self.lane1) == nil)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
#expect(store.transient.dragMembers.ids.isEmpty)
|
||||
}
|
||||
|
||||
@@ -611,8 +683,8 @@ struct DropSettleTests {
|
||||
|
||||
#expect(await settles { !session.isSettled }, "the deadline must dissolve an overlay with no hand-off coming")
|
||||
#expect(!session.isActive)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootURL, laneID: Self.lane1) == nil)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.laneProposal(onBoardRooted: store.rootKey, laneID: Self.lane1) == nil)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
#expect(store.transient.dragMembers.ids.isEmpty)
|
||||
}
|
||||
|
||||
@@ -627,7 +699,7 @@ struct DropSettleTests {
|
||||
session.commit(into: store)
|
||||
let hold = try #require(session.hold)
|
||||
|
||||
session.expire(CommittedHold(boardRoot: store.rootURL, generation: 999))
|
||||
session.expire(CommittedHold(boardRoot: store.rootKey, generation: 999))
|
||||
#expect(session.isSettled, "a hold this session is not holding is not this session's to end")
|
||||
|
||||
session.expire(hold)
|
||||
@@ -646,7 +718,7 @@ struct DropSettleTests {
|
||||
|
||||
// The echo lands well inside the deadline, and the user starts another drag immediately —
|
||||
// the lifecycle trap the watchdog was written for, at the hold's end of the session.
|
||||
session.handOff(root: store.rootURL, generation: store.snapshotGeneration + 1)
|
||||
session.handOff(root: store.rootKey, generation: store.snapshotGeneration + 1)
|
||||
pickUp(session, from: store, members: [(Self.card2, "Second")])
|
||||
|
||||
try? await Task.sleep(for: .milliseconds(80))
|
||||
@@ -681,7 +753,7 @@ struct DragRestingLayoutTests {
|
||||
|
||||
/// Another board entirely — the right-hand side of every cross-board resolution below. It needs
|
||||
/// no fixture: locality compares paths, and nothing here reads the foreign board's contents.
|
||||
private let elsewhere = URL(fileURLWithPath: "/Boards/Elsewhere.kanban", isDirectory: true)
|
||||
private let elsewhere = BoardRootKey(URL(fileURLWithPath: "/Boards/Elsewhere.kanban", isDirectory: true))
|
||||
|
||||
/// One lane holding two cards, plus a trashed card so a `.trash`-container session has something
|
||||
/// real to name.
|
||||
@@ -725,12 +797,12 @@ struct DragRestingLayoutTests {
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let session = cardSession(store, members: [Self.card1, Self.card2])
|
||||
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootURL, modifiers: none) == .move)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.card1, Self.card2])
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootKey, modifiers: none) == .move)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.card1, Self.card2])
|
||||
|
||||
// ⌘ over a foreign board is the cross-board move, and it lifts them out just the same.
|
||||
#expect(session.resolveOperation(destinationRoot: elsewhere, modifiers: command) == .move)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.card1, Self.card2])
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.card1, Self.card2])
|
||||
}
|
||||
|
||||
/// The card the user filed: ⌥ says "leave the originals here", so the originals are drawn here.
|
||||
@@ -742,12 +814,12 @@ struct DragRestingLayoutTests {
|
||||
let session = cardSession(store, members: [Self.card1, Self.card2])
|
||||
|
||||
// The within-board ⌥-copy.
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootURL, modifiers: option) == .copy)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootKey, modifiers: option) == .copy)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
|
||||
// And the cross-board default, which is a copy with no modifier at all.
|
||||
#expect(session.resolveOperation(destinationRoot: elsewhere, modifiers: none) == .copy)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
}
|
||||
|
||||
/// The flip is a deliberate user action and the one-shot reflow is its feedback — which means
|
||||
@@ -760,8 +832,8 @@ struct DragRestingLayoutTests {
|
||||
let session = cardSession(store)
|
||||
|
||||
for modifiers in [none, option, none, option] {
|
||||
session.resolveOperation(destinationRoot: store.rootURL, modifiers: modifiers)
|
||||
let hidden = session.hiddenMembers(onBoardRooted: store.rootURL)
|
||||
session.resolveOperation(destinationRoot: store.rootKey, modifiers: modifiers)
|
||||
let hidden = session.hiddenMembers(onBoardRooted: store.rootKey)
|
||||
#expect(hidden == (modifiers == option ? [] : [Self.card1]))
|
||||
}
|
||||
}
|
||||
@@ -778,11 +850,11 @@ struct DragRestingLayoutTests {
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let session = cardSession(store, members: [ItemID(rawValue: Ident.card3)], container: .trash)
|
||||
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootURL, modifiers: none) == .move)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootKey, modifiers: none) == .move)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootURL, modifiers: option) == .copy)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootKey, modifiers: option) == .copy)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
}
|
||||
|
||||
@Test("Only the source board hides anything, whatever the operation")
|
||||
@@ -794,7 +866,7 @@ struct DragRestingLayoutTests {
|
||||
|
||||
session.resolveOperation(destinationRoot: elsewhere, modifiers: command)
|
||||
#expect(session.hiddenMembers(onBoardRooted: elsewhere).isEmpty)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.card1])
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.card1])
|
||||
}
|
||||
|
||||
/// The lane carve-out, read as a layout claim: a within-board lane drag never resolves to
|
||||
@@ -814,11 +886,11 @@ struct DragRestingLayoutTests {
|
||||
source: store
|
||||
)
|
||||
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootURL, modifiers: option) == .move)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.lane1])
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootKey, modifiers: option) == .move)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.lane1])
|
||||
|
||||
#expect(session.resolveOperation(destinationRoot: elsewhere, modifiers: none) == .copy)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
}
|
||||
|
||||
// MARK: The hold freezes it
|
||||
@@ -833,13 +905,13 @@ struct DragRestingLayoutTests {
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let session = cardSession(store)
|
||||
|
||||
session.resolveOperation(destinationRoot: store.rootURL, modifiers: option)
|
||||
session.resolveOperation(destinationRoot: store.rootKey, modifiers: option)
|
||||
session.commit(into: store)
|
||||
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootURL, modifiers: none) == .copy)
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootKey, modifiers: none) == .copy)
|
||||
#expect(session.resolveOperation(destinationRoot: elsewhere, modifiers: command) == .copy)
|
||||
#expect(session.operation == .copy)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL).isEmpty)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey).isEmpty)
|
||||
}
|
||||
|
||||
@Test("A settled move keeps its originals lifted, whatever the modifiers do next")
|
||||
@@ -849,11 +921,11 @@ struct DragRestingLayoutTests {
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let session = cardSession(store)
|
||||
|
||||
session.resolveOperation(destinationRoot: store.rootURL, modifiers: none)
|
||||
session.resolveOperation(destinationRoot: store.rootKey, modifiers: none)
|
||||
session.commit(into: store)
|
||||
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootURL, modifiers: option) == .move)
|
||||
#expect(session.resolveOperation(destinationRoot: store.rootKey, modifiers: option) == .move)
|
||||
#expect(session.operation == .move)
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootURL) == [Self.card1])
|
||||
#expect(session.hiddenMembers(onBoardRooted: store.rootKey) == [Self.card1])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user