Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1c37bb7d7 | ||
|
|
d2466d9a66 | ||
|
|
4af2d96294 | ||
|
|
d17aa962ce | ||
|
|
93acd32b51 | ||
|
|
67cf452e59 | ||
|
|
83ed53c7fc |
@@ -1,7 +1,9 @@
|
|||||||
# GitHub Actions twin of .gitea/workflows/macos.yml.
|
# GitHub Actions twin of .gitea/workflows/macos.yml.
|
||||||
# Deltas from the Gitea file (everything else is the same jobs/steps):
|
# Deltas from the Gitea file (everything else is the same jobs/steps):
|
||||||
# - runs-on macos-14: github.com retired macos-12 (the Gitea runner label).
|
# - runs-on macos-14: github.com retired macos-12. Do not use
|
||||||
# The Gitea workflow already notes macos-14 works for this pipeline.
|
# macos-latest — in 2026 that is macos-26-arm64, where a 1280×800
|
||||||
|
# window hangs off the virtual display and XCTest marks sidebar
|
||||||
|
# controls (x ≈ -116) as not hittable (run 34864198118).
|
||||||
# - actions/upload-artifact@v4: v3 is shut down on github.com. Gitea act_runner
|
# - actions/upload-artifact@v4: v3 is shut down on github.com. Gitea act_runner
|
||||||
# still uses v3.
|
# still uses v3.
|
||||||
# - No NODE_EXTRA_CA_CERTS / System keychain bundle: that is only for the
|
# - No NODE_EXTRA_CA_CERTS / System keychain bundle: that is only for the
|
||||||
@@ -25,7 +27,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
runs-on: macos-26-intel
|
runs-on: macos-14
|
||||||
env:
|
env:
|
||||||
DERIVED: build/DerivedData-test
|
DERIVED: build/DerivedData-test
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -56,10 +56,41 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// docs/21 §Shell: 1280×800 content, min 1100×700, centred.
|
/// docs/21 §Shell: 1280×800 content, min 1100×700, centred.
|
||||||
|
/// GitHub-hosted Macs (and any display smaller than 1280×800) must
|
||||||
|
/// not get a window that hangs off-screen — XCTest then reports
|
||||||
|
/// sidebar controls at negative x as not hittable (run 34864198118).
|
||||||
private func configureMainWindow(_ window: NSWindow) {
|
private func configureMainWindow(_ window: NSWindow) {
|
||||||
window.setContentSize(NSSize(width: 1280, height: 800))
|
let desired = NSSize(width: 1280, height: 800)
|
||||||
window.contentMinSize = NSSize(width: 1100, height: 700)
|
let minimum = NSSize(width: 1100, height: 700)
|
||||||
|
let visible = (window.screen ?? NSScreen.main)?.visibleFrame
|
||||||
|
?? NSRect(origin: .zero, size: desired)
|
||||||
|
|
||||||
|
window.contentMinSize = NSSize(
|
||||||
|
width: min(minimum.width, visible.width),
|
||||||
|
height: min(minimum.height, visible.height)
|
||||||
|
)
|
||||||
|
window.setContentSize(NSSize(
|
||||||
|
width: min(desired.width, visible.width),
|
||||||
|
height: min(desired.height, max(minimum.height, visible.height - 40))
|
||||||
|
))
|
||||||
window.center()
|
window.center()
|
||||||
|
|
||||||
|
var frame = window.frame
|
||||||
|
if frame.width > visible.width {
|
||||||
|
frame.size.width = visible.width
|
||||||
|
}
|
||||||
|
if frame.height > visible.height {
|
||||||
|
frame.size.height = visible.height
|
||||||
|
}
|
||||||
|
frame.origin.x = min(
|
||||||
|
max(frame.origin.x, visible.minX),
|
||||||
|
visible.maxX - frame.width
|
||||||
|
)
|
||||||
|
frame.origin.y = min(
|
||||||
|
max(frame.origin.y, visible.minY),
|
||||||
|
visible.maxY - frame.height
|
||||||
|
)
|
||||||
|
window.setFrame(frame, display: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dock-click reopen: let the WindowGroup re-show or recreate the
|
/// Dock-click reopen: let the WindowGroup re-show or recreate the
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ final class Milestone2UITests: XCTestCase {
|
|||||||
"ICCERY_UI_TESTING": "1",
|
"ICCERY_UI_TESTING": "1",
|
||||||
"ICCERY_TEST_ROOT": testRoot.path,
|
"ICCERY_TEST_ROOT": testRoot.path,
|
||||||
"ICCERY_ARGYLL_BINARY_DIR": binDir.path,
|
"ICCERY_ARGYLL_BINARY_DIR": binDir.path,
|
||||||
|
"ICCERY_CUPS_BIN_DIR": binDir.path,
|
||||||
"ICCERY_TEST_SAVE_TARGET":
|
"ICCERY_TEST_SAVE_TARGET":
|
||||||
workDir.appendingPathComponent("mytarget.ti1").path,
|
workDir.appendingPathComponent("mytarget.ti1").path,
|
||||||
"ICCERY_TEST_WORKDIR": workDir.path,
|
"ICCERY_TEST_WORKDIR": workDir.path,
|
||||||
@@ -218,11 +219,12 @@ final class Milestone2UITests: XCTestCase {
|
|||||||
XCTAssertTrue(FileManager.default.fileExists(
|
XCTAssertTrue(FileManager.default.fileExists(
|
||||||
atPath: workDir.appendingPathComponent("mytarget.ti2").path))
|
atPath: workDir.appendingPathComponent("mytarget.ti2").path))
|
||||||
|
|
||||||
// Print panel is live from M3; a default printer is selected
|
// Print panel is live from M3. GitHub macos-14 has no system
|
||||||
// so both the all-pages and per-page print buttons are enabled.
|
// queues, so mock CUPS (`ICCERY_CUPS_BIN_DIR`) must enumerate
|
||||||
|
// before Print All / per-page enable (run 34867767434).
|
||||||
XCTAssertTrue(element("rawPrintPanel").exists)
|
XCTAssertTrue(element("rawPrintPanel").exists)
|
||||||
XCTAssertTrue(app.buttons["btnPrintAll"].isEnabled)
|
_ = waitUntilEnabled("btnPrintAll", timeout: 15)
|
||||||
XCTAssertTrue(app.buttons["btnPrintPage-0"].isEnabled)
|
_ = waitUntilEnabled("btnPrintPage-0", timeout: 15)
|
||||||
XCTAssertTrue(app.buttons["btnAdvanceToStage3"].isEnabled)
|
XCTAssertTrue(app.buttons["btnAdvanceToStage3"].isEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,15 +154,34 @@ final class Milestone4UITests: XCTestCase {
|
|||||||
|
|
||||||
/// Runs the mock handheld chartread session to completion
|
/// Runs the mock handheld chartread session to completion
|
||||||
/// (start → calibrate → strip A → strip B → Done & Save).
|
/// (start → calibrate → strip A → strip B → Done & Save).
|
||||||
|
///
|
||||||
|
/// `isEnabled` can be true in AX while the SwiftUI action is still
|
||||||
|
/// a no-op (runs 35251, 35443). Re-click until the session is
|
||||||
|
/// actually running (`btnCancel` is shown whenever
|
||||||
|
/// `isChartreadRunning`), then wait for Calibrate.
|
||||||
private func driveOnePass(startButton: String) {
|
private func driveOnePass(startButton: String) {
|
||||||
let start = app.buttons[startButton]
|
let start = app.buttons[startButton]
|
||||||
XCTAssertTrue(start.waitForExistence(timeout: 10))
|
XCTAssertTrue(start.waitForExistence(timeout: 10), startButton)
|
||||||
let deadline = Date().addingTimeInterval(10)
|
let enabledBy = Date().addingTimeInterval(10)
|
||||||
while Date() < deadline, !start.isEnabled {
|
while Date() < enabledBy, !start.isEnabled {
|
||||||
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
|
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
|
||||||
}
|
}
|
||||||
XCTAssertTrue(start.isEnabled)
|
XCTAssertTrue(start.isEnabled, "\(startButton) never enabled")
|
||||||
start.click()
|
|
||||||
|
let sessionBy = Date().addingTimeInterval(25)
|
||||||
|
while Date() < sessionBy, !chartreadSessionRunning {
|
||||||
|
if start.exists, start.isEnabled {
|
||||||
|
start.click()
|
||||||
|
}
|
||||||
|
RunLoop.current.run(until: Date().addingTimeInterval(0.4))
|
||||||
|
}
|
||||||
|
XCTAssertTrue(
|
||||||
|
chartreadSessionRunning,
|
||||||
|
"\(startButton) click never started chartread; "
|
||||||
|
+ "notice=\(element("noticeText").value as? String ?? "") "
|
||||||
|
+ "start.exists=\(start.exists)"
|
||||||
|
)
|
||||||
|
|
||||||
_ = waitFor("btnCalibrate", timeout: 25)
|
_ = waitFor("btnCalibrate", timeout: 25)
|
||||||
app.buttons["btnCalibrate"].click()
|
app.buttons["btnCalibrate"].click()
|
||||||
driveStripsUntilDone()
|
driveStripsUntilDone()
|
||||||
@@ -170,6 +189,14 @@ final class Milestone4UITests: XCTestCase {
|
|||||||
app.buttons["btnDoneRead"].firstMatch.click()
|
app.buttons["btnDoneRead"].firstMatch.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `btnCancel` is in the tree for the whole chartread session;
|
||||||
|
/// Calibrate/Trigger only appear after the first classified prompt.
|
||||||
|
private var chartreadSessionRunning: Bool {
|
||||||
|
element("btnCancel").exists
|
||||||
|
|| element("btnCalibrate").exists
|
||||||
|
|| element("btnTrigger").exists
|
||||||
|
}
|
||||||
|
|
||||||
/// Clicks Trigger for each remaining strip until `btnDoneRead`
|
/// Clicks Trigger for each remaining strip until `btnDoneRead`
|
||||||
/// appears — the button is re-polled each pass so a click that
|
/// appears — the button is re-polled each pass so a click that
|
||||||
/// races a state transition isn't lost.
|
/// races a state transition isn't lost.
|
||||||
|
|||||||
Reference in New Issue
Block a user