mirror of
https://github.com/aculix/knotchy.git
synced 2026-10-27 07:15:53 +00:00
Initial release
This commit is contained in:
commit
4b67728cce
108 files changed
+13310
No files matched your search
@@ -0,0 +1,340 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import KnotchyCore
|
||||
|
||||
@MainActor
|
||||
@Suite(.timeLimit(.minutes(1)))
|
||||
struct SessionPanelTests {
|
||||
@Test func aFreshPanelStaysOpenUntilEnteredOrFor30s() async {
|
||||
let h = Harness()
|
||||
await h.goLive()
|
||||
h.advance(by: .milliseconds(29_999))
|
||||
#expect(h.state == .live(.peek))
|
||||
h.advance(by: .milliseconds(1))
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func theFirstClickOutsideFoldsAnUnengagedPanel() async {
|
||||
let h = Harness()
|
||||
await h.goLive()
|
||||
h.controller.clickedOutside()
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func anEnteredPeekFolds400msAfterThePointerLeaves() async {
|
||||
let h = Harness()
|
||||
await h.goLive()
|
||||
h.controller.pointerEntered()
|
||||
h.advance(by: .seconds(60))
|
||||
#expect(h.state == .live(.peek)) // Entered: the 30 s fold no longer applies.
|
||||
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .milliseconds(399))
|
||||
#expect(h.state == .live(.peek))
|
||||
h.advance(by: .milliseconds(1))
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func comingBackWithinTheGraceKeepsItOpen() async {
|
||||
let h = Harness()
|
||||
await h.goLive()
|
||||
h.controller.pointerEntered()
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .milliseconds(200))
|
||||
h.controller.pointerEntered()
|
||||
h.advance(by: .seconds(1))
|
||||
#expect(h.state == .live(.peek))
|
||||
}
|
||||
|
||||
@Test func afterAClickInsideOnlyAClickOutsideFolds() async {
|
||||
let h = Harness()
|
||||
await h.goLive()
|
||||
h.controller.pointerEntered()
|
||||
h.controller.panelClicked()
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .seconds(60))
|
||||
#expect(h.state == .live(.peek))
|
||||
h.controller.clickedOutside()
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func expandedFoldsOnlyOnAClickOutside() async {
|
||||
let h = Harness()
|
||||
await h.goLive()
|
||||
h.controller.pointerEntered()
|
||||
h.controller.expand()
|
||||
#expect(h.state == .live(.expanded))
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .seconds(60))
|
||||
#expect(h.state == .live(.expanded))
|
||||
|
||||
h.controller.shrink()
|
||||
#expect(h.state == .live(.peek))
|
||||
h.controller.pointerEntered()
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .seconds(1))
|
||||
#expect(h.state == .live(.peek)) // Still key after Expanded: pointer-away doesn't fold.
|
||||
|
||||
h.controller.expand()
|
||||
h.controller.clickedOutside()
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func expandAndShrinkOnlyApplyToTheirModes() async {
|
||||
let h = Harness()
|
||||
h.controller.expand()
|
||||
#expect(h.state == .idle)
|
||||
await h.goLive()
|
||||
h.controller.shrink()
|
||||
#expect(h.state == .live(.peek))
|
||||
h.controller.clickedOutside()
|
||||
h.controller.expand()
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func hoveringThePillReopensPeek() async {
|
||||
let h = Harness()
|
||||
await h.goLive()
|
||||
h.controller.clickedOutside()
|
||||
h.controller.pointerEntered()
|
||||
#expect(h.state == .live(.peek))
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .milliseconds(400))
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func theIdleCountdownRunsOnlyWhileCollapsedAndResetsOnReopen() async {
|
||||
let h = Harness()
|
||||
await h.goLive()
|
||||
#expect(h.controller.countdown == nil)
|
||||
h.controller.clickedOutside()
|
||||
#expect(h.controller.countdown == .seconds(300))
|
||||
|
||||
h.advance(by: .seconds(240))
|
||||
#expect(h.controller.countdown == .seconds(60))
|
||||
h.controller.pointerEntered()
|
||||
#expect(h.controller.countdown == nil)
|
||||
h.advance(by: .seconds(120))
|
||||
#expect(h.state == .live(.peek))
|
||||
|
||||
h.controller.clickedOutside()
|
||||
#expect(h.controller.countdown == .seconds(300))
|
||||
h.advance(by: .seconds(300) - .milliseconds(1))
|
||||
#expect(h.state == .live(.collapsed))
|
||||
h.advance(by: .milliseconds(1))
|
||||
#expect(h.state == .destroying)
|
||||
await waitUntil("destroyed") { h.state == .destroyed }
|
||||
}
|
||||
|
||||
@Test func decodingPausesWhileCollapsed() async {
|
||||
let h = Harness()
|
||||
let stream = await h.goLive()
|
||||
h.controller.clickedOutside()
|
||||
#expect(stream.decodeChanges == [false])
|
||||
h.controller.pointerEntered()
|
||||
#expect(stream.decodeChanges == [false, true])
|
||||
}
|
||||
|
||||
@Test func foldingWhileStartingShowsThePillAndStaysFoldedWhenReady() async {
|
||||
let h = Harness()
|
||||
h.connector.hold()
|
||||
h.controller.open(Harness.url)
|
||||
h.controller.clickedOutside()
|
||||
#expect(h.state == .starting(.booting, folded: true))
|
||||
|
||||
await waitUntil("connecting") { h.state == .starting(.connecting, folded: true) }
|
||||
h.connector.release()
|
||||
await waitUntil("loading") { h.state == .starting(.loading, folded: true) }
|
||||
|
||||
let stream = h.connector.connections[0]
|
||||
stream.emit(.contentReady)
|
||||
await waitUntil("collapsed") { h.state == .live(.collapsed) }
|
||||
#expect(h.controller.countdown == .seconds(300))
|
||||
#expect(stream.decodeChanges == [false])
|
||||
}
|
||||
|
||||
@Test func hoveringTheStartingPillUnfoldsIt() async {
|
||||
let h = Harness()
|
||||
h.backend.hold(.start)
|
||||
h.controller.open(Harness.url)
|
||||
h.controller.clickedOutside()
|
||||
h.controller.pointerEntered()
|
||||
#expect(h.state == .starting(.booting, folded: false))
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .milliseconds(400))
|
||||
#expect(h.state == .starting(.booting, folded: true))
|
||||
h.backend.release(.start)
|
||||
}
|
||||
|
||||
@Test func aGraceFoldStartedWhileStartingCarriesIntoPeek() async {
|
||||
let h = Harness()
|
||||
let stream = await h.openAndConnect()
|
||||
h.controller.pointerEntered()
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .milliseconds(200))
|
||||
stream.emit(.contentReady)
|
||||
await waitUntil("live") { h.state == .live(.peek) }
|
||||
h.advance(by: .milliseconds(200))
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func retryKeepsThePanelEnteredWhenThePointerIsOnIt() async {
|
||||
let h = Harness()
|
||||
h.backend.failStarts(with: VMBackendError.failed("boom"))
|
||||
h.controller.open(Harness.url)
|
||||
await waitUntil("failed") { h.state == .failed(.start("boom")) }
|
||||
h.backend.failStarts(with: nil)
|
||||
h.controller.pointerEntered()
|
||||
h.controller.retry()
|
||||
await waitUntil("loading") { h.state == .starting(.loading, folded: false) }
|
||||
h.connector.connections[0].emit(.contentReady)
|
||||
await waitUntil("live") { h.state == .live(.peek) }
|
||||
|
||||
h.advance(by: .seconds(30))
|
||||
#expect(h.state == .live(.peek))
|
||||
h.controller.pointerExited()
|
||||
h.advance(by: .milliseconds(400))
|
||||
#expect(h.state == .live(.collapsed))
|
||||
}
|
||||
|
||||
@Test func pointerInputDoesNothingWithoutAnOpenPanel() async {
|
||||
let h = Harness()
|
||||
h.controller.clickedOutside()
|
||||
h.controller.pointerEntered()
|
||||
h.controller.pointerExited()
|
||||
h.controller.panelClicked()
|
||||
#expect(h.state == .idle)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Suite(.timeLimit(.minutes(1)))
|
||||
struct SessionProgressTests {
|
||||
@Test func showsTheLatestProgressWhileStarting() async {
|
||||
let h = Harness()
|
||||
let stream = await h.openAndConnect()
|
||||
stream.emit(.progress(10))
|
||||
await waitUntil("10 %") { h.controller.progress == 10 }
|
||||
stream.emit(.progress(60))
|
||||
await waitUntil("60 %") { h.controller.progress == 60 }
|
||||
h.advance(by: .seconds(10))
|
||||
#expect(h.controller.progress == 60) // No hiding before content is ready.
|
||||
}
|
||||
|
||||
@Test func hidesAtProgress100() async {
|
||||
let h = Harness()
|
||||
let stream = await h.openAndConnect()
|
||||
stream.emit(.progress(30))
|
||||
await waitUntil("30 %") { h.controller.progress == 30 }
|
||||
stream.emit(.progress(100))
|
||||
await waitUntil("hidden") { h.controller.progress == nil }
|
||||
}
|
||||
|
||||
@Test func onceReadyItHides2sAfterTheLastProgress() async {
|
||||
let h = Harness()
|
||||
let stream = await h.openAndConnect()
|
||||
stream.emit(.progress(30))
|
||||
stream.emit(.contentReady)
|
||||
await waitUntil("live") { h.state == .live(.peek) }
|
||||
#expect(h.controller.progress == 30)
|
||||
h.advance(by: .milliseconds(1999))
|
||||
#expect(h.controller.progress == 30)
|
||||
h.advance(by: .milliseconds(1))
|
||||
#expect(h.controller.progress == nil)
|
||||
}
|
||||
|
||||
@Test func aLoadThatNeverReports100StillHidesWhenReady() async {
|
||||
let h = Harness()
|
||||
let stream = await h.openAndConnect()
|
||||
stream.emit(.progress(30))
|
||||
await waitUntil("30 %") { h.controller.progress == 30 }
|
||||
h.advance(by: .seconds(5))
|
||||
stream.emit(.contentReady)
|
||||
await waitUntil("live") { h.state == .live(.peek) }
|
||||
#expect(h.controller.progress == nil)
|
||||
}
|
||||
|
||||
@Test func endingASessionHidesTheBar() async {
|
||||
let h = Harness()
|
||||
let stream = await h.openAndConnect()
|
||||
stream.emit(.progress(40))
|
||||
await waitUntil("40 %") { h.controller.progress == 40 }
|
||||
stream.emit(.ended(.closed))
|
||||
await waitUntil("failed") { h.state == .failed(.start("The VM closed the connection.")) }
|
||||
#expect(h.controller.progress == nil)
|
||||
}
|
||||
|
||||
@Test func progressAfterReadyShowsAgainThenHides() async {
|
||||
let h = Harness()
|
||||
let stream = await h.goLive()
|
||||
stream.emit(.progress(20))
|
||||
await waitUntil("20 %") { h.controller.progress == 20 }
|
||||
h.advance(by: .seconds(2))
|
||||
#expect(h.controller.progress == nil)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Suite(.timeLimit(.minutes(1)))
|
||||
struct SessionToolbarTests {
|
||||
@Test func navigateSendsTheURLAndUpdatesTheAddress() async throws {
|
||||
let h = Harness()
|
||||
let stream = await h.goLive()
|
||||
let url = try #require(URL(string: "https://example.net/page"))
|
||||
h.controller.navigate(url)
|
||||
#expect(stream.sent == [.navigate("https://example.net/page")])
|
||||
#expect(h.controller.openedURL == url)
|
||||
}
|
||||
|
||||
@Test func backForwardAndReloadRunScripts() async {
|
||||
let h = Harness()
|
||||
let stream = await h.goLive()
|
||||
h.controller.goBack()
|
||||
h.controller.goForward()
|
||||
h.controller.reload()
|
||||
#expect(stream.sent == [.script("history.back()"), .script("history.forward()"), .script("location.reload()")])
|
||||
}
|
||||
|
||||
@Test func toolbarActionsNeedALiveSession() async {
|
||||
let h = Harness()
|
||||
let stream = await h.openAndConnect()
|
||||
h.controller.reload()
|
||||
h.controller.navigate(Harness.otherURL)
|
||||
#expect(stream.sent.isEmpty)
|
||||
#expect(h.controller.openedURL == Harness.url)
|
||||
}
|
||||
|
||||
@Test func aNonWebLinkShowsANoticeFor2s() async throws {
|
||||
let h = Harness()
|
||||
h.controller.open(try #require(URL(string: "ftp://files.example.com")))
|
||||
#expect(h.controller.notice == .noWebLink)
|
||||
#expect(h.state == .idle)
|
||||
h.advance(by: .milliseconds(1999))
|
||||
#expect(h.controller.notice == .noWebLink)
|
||||
h.advance(by: .milliseconds(1))
|
||||
#expect(h.controller.notice == nil)
|
||||
}
|
||||
|
||||
@Test func navigateRejectsNonWebLinks() async throws {
|
||||
let h = Harness()
|
||||
let stream = await h.goLive()
|
||||
h.controller.navigate(try #require(URL(string: "file:///etc/passwd")))
|
||||
#expect(stream.sent.isEmpty)
|
||||
#expect(h.controller.notice == .noWebLink)
|
||||
#expect(h.controller.openedURL == Harness.url)
|
||||
}
|
||||
|
||||
@Test func textWithoutALinkShowsANotice() {
|
||||
let h = Harness()
|
||||
h.controller.open(text: "call me maybe")
|
||||
#expect(h.controller.notice == .noWebLink)
|
||||
#expect(h.state == .idle)
|
||||
}
|
||||
|
||||
@Test func textWithALinkOpensIt() {
|
||||
let h = Harness()
|
||||
h.controller.open(text: "Your parcel: https://example.com/ (tracking)")
|
||||
#expect(h.state == .starting(.booting, folded: false))
|
||||
#expect(h.controller.openedURL == Harness.url)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user