Skip to content

Commit b64d0b4

Browse files
committed
test(datagrid): lock sort cache invalidation on row mutations
Four tests cover the four mutation pathways through the now-merged querySortCache: - addNewRow invalidates the cache (was view-side sortCache before merge — silently stale) - duplicateSelectedRow invalidates - deleteSelectedRows invalidates when physically removing inserted rows - deleteSelectedRows preserves the cache on soft-delete of existing rows (sortedIDs reference rows by ID, soft-delete only changes appearance, not order) Documents the soft-delete-preserves invariant that emerged during test writing — guards a future contributor from "helpfully" invalidating on every delete and breaking sort persistence.
1 parent 7f25c46 commit b64d0b4

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//
2+
// SortCacheInvalidationTests.swift
3+
// TableProTests
4+
//
5+
// Locks the contract that row mutations invalidate querySortCache for the
6+
// affected tab. Pre-merge, only the coordinator-side cache was invalidated;
7+
// the view-side @State sortCache stayed stale, so a sorted small table
8+
// returned out-of-date sortedIDs after add / undo / paste / delete. After
9+
// the merge there is one cache and these tests guard the invalidation set.
10+
//
11+
12+
import Foundation
13+
import Testing
14+
@testable import TablePro
15+
16+
@Suite("querySortCache invalidation on row mutations")
17+
@MainActor
18+
struct SortCacheInvalidationTests {
19+
private func makeCoordinator() -> (MainContentCoordinator, QueryTabManager, UUID) {
20+
let tabManager = QueryTabManager()
21+
let coordinator = MainContentCoordinator(
22+
connection: TestFixtures.makeConnection(),
23+
tabManager: tabManager,
24+
changeManager: DataChangeManager(),
25+
filterStateManager: FilterStateManager(),
26+
columnVisibilityManager: ColumnVisibilityManager(),
27+
toolbarState: ConnectionToolbarState()
28+
)
29+
tabManager.addTableTab(tableName: "users")
30+
let tabIndex = tabManager.selectedTabIndex ?? 0
31+
tabManager.tabs[tabIndex].tableContext.isEditable = true
32+
let tabId = tabManager.tabs[tabIndex].id
33+
return (coordinator, tabManager, tabId)
34+
}
35+
36+
private func seedCache(_ coordinator: MainContentCoordinator, for tabId: UUID) {
37+
coordinator.querySortCache[tabId] = QuerySortCacheEntry(
38+
sortedIDs: [.existing(0), .existing(1), .existing(2)],
39+
columnIndex: 1,
40+
direction: .ascending,
41+
schemaVersion: 0
42+
)
43+
}
44+
45+
private func seedRows(_ coordinator: MainContentCoordinator, for tabId: UUID, count: Int) {
46+
let columns = ["id", "name"]
47+
let rows = (0..<count).map { i in ["\(i)", "name\(i)"] }
48+
let columnTypes: [ColumnType] = Array(repeating: .text(rawType: nil), count: columns.count)
49+
let tableRows = TableRows.from(queryRows: rows, columns: columns, columnTypes: columnTypes)
50+
coordinator.setActiveTableRows(tableRows, for: tabId)
51+
}
52+
53+
@Test("addNewRow clears querySortCache for the tab")
54+
func addNewRowInvalidatesCache() {
55+
let (coordinator, _, tabId) = makeCoordinator()
56+
seedRows(coordinator, for: tabId, count: 3)
57+
seedCache(coordinator, for: tabId)
58+
59+
coordinator.addNewRow()
60+
61+
#expect(coordinator.querySortCache[tabId] == nil)
62+
}
63+
64+
@Test("deleteSelectedRows clears querySortCache when physically removing inserted rows")
65+
func physicalDeleteInvalidatesCache() {
66+
let (coordinator, _, tabId) = makeCoordinator()
67+
seedRows(coordinator, for: tabId, count: 3)
68+
coordinator.addNewRow()
69+
let insertedIndex = coordinator.tableRowsStore.tableRows(for: tabId).count - 1
70+
seedCache(coordinator, for: tabId)
71+
72+
coordinator.deleteSelectedRows(indices: [insertedIndex])
73+
74+
#expect(coordinator.querySortCache[tabId] == nil)
75+
}
76+
77+
@Test("deleteSelectedRows preserves querySortCache on soft delete of existing rows")
78+
func softDeletePreservesCache() {
79+
let (coordinator, _, tabId) = makeCoordinator()
80+
seedRows(coordinator, for: tabId, count: 5)
81+
seedCache(coordinator, for: tabId)
82+
83+
coordinator.deleteSelectedRows(indices: [0, 1])
84+
85+
#expect(coordinator.querySortCache[tabId] != nil)
86+
}
87+
88+
@Test("duplicateSelectedRow clears querySortCache for the tab")
89+
func duplicateRowInvalidatesCache() {
90+
let (coordinator, _, tabId) = makeCoordinator()
91+
seedRows(coordinator, for: tabId, count: 3)
92+
seedCache(coordinator, for: tabId)
93+
94+
coordinator.duplicateSelectedRow(index: 0)
95+
96+
#expect(coordinator.querySortCache[tabId] == nil)
97+
}
98+
}

0 commit comments

Comments
 (0)