Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,12 @@
var yContentAdjustment: CGFloat = 0
var maxFoundLineWidth = maxLineWidth

// The layout view draws its own decorations into a backing store nothing else invalidates when the
// viewport moves, so a band drawn before its lines were laid out would stay blank forever.
var relaidOutRect: CGRect = .null
// The vertical span this pass laid out. The layout view draws its own decorations into a backing store
// nothing else invalidates when the viewport moves, so a band drawn before its lines were laid out would
// stay blank forever. Tracked as a span rather than a rect because a rect union silently drops an operand
// of zero width, which is what an unparented layout view reports.
var relaidOutMinY: CGFloat = .greatestFiniteMagnitude
var relaidOutMaxY: CGFloat = -.greatestFiniteMagnitude

#if DEBUG
var laidOutLines: Set<TextLine.ID> = []
Expand All @@ -111,13 +114,10 @@
maxFoundLineWidth: &maxFoundLineWidth
)
yContentAdjustment += yAdjustment
relaidOutRect = relaidOutRect.union(
CGRect(
x: 0,
y: linePosition.yPos,
width: layoutView?.frame.width ?? 0,
height: max(linePosition.height, linePosition.data.lineFragments.height)
)
relaidOutMinY = min(relaidOutMinY, linePosition.yPos)
relaidOutMaxY = max(
relaidOutMaxY,
linePosition.yPos + max(linePosition.height, linePosition.data.lineFragments.height)
)
#if DEBUG
laidOutLines.insert(linePosition.data.id)
Expand Down Expand Up @@ -175,17 +175,18 @@
delegate?.layoutManagerHeightDidUpdate(newHeight: lineStorage.height)
}

if !relaidOutRect.isNull {
if let layoutView, relaidOutMinY <= relaidOutMaxY {
// A height change or a y adjustment moves every line below the first one this pass touched.
let movedEverythingBelow = didLayoutChange || yContentAdjustment != 0
let invalidRect = movedEverythingBelow
? CGRect(
x: relaidOutRect.minX,
y: relaidOutRect.minY,
width: relaidOutRect.width,
height: max(maxY - relaidOutRect.minY, relaidOutRect.height)
let bottom = movedEverythingBelow ? max(maxY, relaidOutMaxY) : relaidOutMaxY
layoutView.setNeedsDisplay(
CGRect(
x: 0,
y: relaidOutMinY,
width: layoutView.frame.width,
height: bottom - relaidOutMinY
)
: relaidOutRect
layoutView?.setNeedsDisplay(invalidRect)
)
}

#if DEBUG
Expand Down Expand Up @@ -279,7 +280,7 @@
var height: CGFloat = 0
var width: CGFloat = 0
let relativeMinY = max(layoutData.minY - position.yPos, 0)
let relativeMaxY = max(layoutData.maxY - position.yPos, relativeMinY)

Check warning on line 283 in LocalPackages/CodeEditTextView/Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Layout.swift

View workflow job for this annotation

GitHub Actions / Build for testing

initialization of immutable value 'relativeMaxY' was never used; consider replacing with assignment to '_' or removing it

// for lineFragmentPosition in line.lineFragments.linesStartingAt(
// relativeMinY,
Expand Down
55 changes: 39 additions & 16 deletions TableProUITests/EditorTabReorderUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@

drag(tab(named: before[0], in: window), onto: tab(named: before[2], in: window))

let after = tabLabels(in: window)

XCTAssertNotEqual(
before,
after,
XCTAssertTrue(

Check failure on line 26 in TableProUITests/EditorTabReorderUITests.swift

View workflow job for this annotation

GitHub Actions / UI tests 0/3

testDraggingATabReordersTheStrip, XCTAssertTrue failed - Dragging the first tab across the strip must change the tab order, was ["Track", "Album", "Artist", "Customer"]

Check failure on line 26 in TableProUITests/EditorTabReorderUITests.swift

View workflow job for this annotation

GitHub Actions / UI tests 0/3

testDraggingATabReordersTheStrip, XCTAssertTrue failed - Dragging the first tab across the strip must change the tab order, was ["Track", "Album", "Artist", "Customer"]
waitForTabOrder(toChangeFrom: before, in: window),
"Dragging the first tab across the strip must change the tab order, was \(before)"
)
}
Expand All @@ -53,9 +50,8 @@

drag(tab(named: unselected, in: window), onto: tab(named: before[before.count - 1], in: window))

XCTAssertNotEqual(
before,
tabLabels(in: window),
XCTAssertTrue(

Check failure on line 53 in TableProUITests/EditorTabReorderUITests.swift

View workflow job for this annotation

GitHub Actions / UI tests 1/3

testDraggingAnUnselectedTabReordersTheStrip, XCTAssertTrue failed - Dragging an unselected tab must change the tab order, was ["Track", "Album", "Artist", "Customer"]

Check failure on line 53 in TableProUITests/EditorTabReorderUITests.swift

View workflow job for this annotation

GitHub Actions / UI tests 1/3

testDraggingAnUnselectedTabReordersTheStrip, XCTAssertTrue failed - Dragging an unselected tab must change the tab order, was ["Track", "Album", "Artist", "Customer"]
waitForTabOrder(toChangeFrom: before, in: window),
"Dragging an unselected tab must change the tab order, was \(before)"
)
}
Expand All @@ -81,9 +77,8 @@

drag(tab(named: selected, in: window), onto: tab(named: target, in: window))

XCTAssertNotEqual(
before,
tabLabels(in: window),
XCTAssertTrue(

Check failure on line 80 in TableProUITests/EditorTabReorderUITests.swift

View workflow job for this annotation

GitHub Actions / UI tests 2/3

testDraggingTheSelectedTabReordersTheStrip, XCTAssertTrue failed - Dragging the selected tab must change the tab order, was ["Track", "Album", "Artist", "Customer"]

Check failure on line 80 in TableProUITests/EditorTabReorderUITests.swift

View workflow job for this annotation

GitHub Actions / UI tests 2/3

testDraggingTheSelectedTabReordersTheStrip, XCTAssertTrue failed - Dragging the selected tab must change the tab order, was ["Track", "Album", "Artist", "Customer"]
waitForTabOrder(toChangeFrom: before, in: window),
"Dragging the selected tab must change the tab order, was \(before)"
)
}
Expand All @@ -107,11 +102,22 @@

let before = tabLabels(in: window)

drag(tab(named: before[0], in: window), onto: tab(named: before[2], in: window))
// Not `before[0]`: once the track scrolls, the leading tabs stay in the accessibility tree
// at frames outside the viewport, and the pointer cannot land on one.
let reachable = onScreenTabs(in: window).map { $0.label }
XCTAssertGreaterThanOrEqual(
reachable.count,
4,
"The viewport must hold four tabs to drag between, showed \(reachable) of \(before)"
)

XCTAssertNotEqual(
before,
tabLabels(in: window),
// Interior tabs only. A tab at either edge of a scrolled track is half outside the
// viewport, and a click at its centre lands on the chrome beside the track rather than on
// the tab, so the drag never reaches the strip at all.
drag(tab(named: reachable[1], in: window), onto: tab(named: reachable[reachable.count - 2], in: window))

XCTAssertTrue(

Check failure on line 119 in TableProUITests/EditorTabReorderUITests.swift

View workflow job for this annotation

GitHub Actions / UI tests 2/3

testDraggingATabReordersAnOverflowingStrip, XCTAssertTrue failed - Dragging a tab in an overflowing strip must change the tab order, was ["Track", "Album", "Artist", "Customer", "Employee", "Genre", "Invoice", "MediaType", "Playlist"]

Check failure on line 119 in TableProUITests/EditorTabReorderUITests.swift

View workflow job for this annotation

GitHub Actions / UI tests 2/3

testDraggingATabReordersAnOverflowingStrip, XCTAssertTrue failed - Dragging a tab in an overflowing strip must change the tab order, was ["Track", "Album", "Artist", "Customer", "Employee", "Genre", "Invoice", "MediaType", "Playlist"]
waitForTabOrder(toChangeFrom: before, in: window),
"Dragging a tab in an overflowing strip must change the tab order, was \(before)"
)
}
Expand Down Expand Up @@ -171,6 +177,23 @@
forDuration: 0.6,
thenDragTo: destination.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
)
Thread.sleep(forTimeInterval: 1.0)
}

/// The strip animates the move and commits it on release, so the new order arrives some time
/// after the gesture returns. Waiting for it rather than sleeping a fixed amount is what keeps
/// this readable on a loaded machine: a sleep long enough for CI is dead time on every local
/// run, and one short enough for a local run reads the pre-drag order on CI and reports the
/// reorder as broken.
private func waitForTabOrder(toChangeFrom before: [String], in window: XCUIElement) -> Bool {
waitForPredicate(timeout: 15) { self.tabLabels(in: window) != before }
}

/// The tabs the pointer can actually reach.
///
/// Once the strip overflows, the track scrolls and the tabs outside the viewport stay in the
/// accessibility tree with frames the pointer cannot land on. Dragging one of those is not a
/// weaker version of the gesture, it is no gesture at all.
private func onScreenTabs(in window: XCUIElement) -> [XCUIElement] {
tabElements(in: window).filter { $0.exists && $0.isHittable }
}
}
Loading