174 lines
8.2 KiB
Swift
174 lines
8.2 KiB
Swift
import Foundation
|
|
|
|
enum AppStep: Equatable {
|
|
case splash
|
|
case main
|
|
case userCreatorChatRoom(roomId: Int)
|
|
case contentDetail(contentId: Int)
|
|
case seriesDetail(seriesId: Int)
|
|
case creatorCommunityAll(creatorId: Int)
|
|
case creatorChannelCommunityPostDetail(postId: Int)
|
|
case message
|
|
case audition
|
|
case giftDetail(applicationNo: String)
|
|
}
|
|
|
|
enum UserDefaultsKey { case userId }
|
|
enum UserDefaults {
|
|
static var userId = 0
|
|
static func int(forKey: UserDefaultsKey) -> Int { userId }
|
|
}
|
|
|
|
final class AppState {
|
|
static let shared = AppState()
|
|
var rootStep: AppStep = .main
|
|
var pendingDeepLinkAction: AppDeepLinkAction?
|
|
var appliedStep: AppStep?
|
|
var isPushRoomFromDeepLink = false
|
|
var pushRoomId = 0
|
|
var pendingCommentPostId: Int?
|
|
func setAppStep(step: AppStep) { appliedStep = step }
|
|
func setPendingCommunityCommentDeepLink(creatorId: Int, postId: Int) { pendingCommentPostId = postId }
|
|
func clearPendingCommunityCommentDeepLink() { pendingCommentPostId = nil }
|
|
}
|
|
|
|
let APPSCHEME = "soda"
|
|
|
|
@main
|
|
enum GiftDetailDeepLinkCheck {
|
|
static var assertions = 0
|
|
|
|
static func check(_ condition: @autoclosure () -> Bool, _ message: String) {
|
|
assertions += 1
|
|
guard condition() else { fatalError("FAIL: \(message)") }
|
|
}
|
|
|
|
static func settle() {
|
|
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
|
|
}
|
|
|
|
static func main() {
|
|
let valid = "voiceon://?DeepLinkValue=gift_detail&DEEPLINKID=GIFT-2026-%30%30%31"
|
|
check(AppDeepLinkHandler.handle(urlString: valid), "valid gift detail accepted")
|
|
settle()
|
|
check(AppState.shared.appliedStep == .giftDetail(applicationNo: "GIFT-2026-001"), "decoded identifier routes to gift detail")
|
|
|
|
for url in [
|
|
"voiceon://gift-detail/A-1002609300001",
|
|
"voiceon-test://gift-detail/A-1002609300001"
|
|
] {
|
|
AppState.shared.appliedStep = nil
|
|
check(AppDeepLinkHandler.handle(urlString: url), "path gift detail accepted for \(url)")
|
|
settle()
|
|
check(AppState.shared.appliedStep == .giftDetail(applicationNo: "A-1002609300001"), "path identifier routes to gift detail")
|
|
}
|
|
|
|
AppState.shared.appliedStep = nil
|
|
check(AppDeepLinkHandler.handle(urlString: "voiceon://?deepLinkValue=%20GIFT_DETAIL%20&deepLinkId=%20GIFT-2026-002%20"), "trimmed semantic values accepted")
|
|
settle()
|
|
check(AppState.shared.appliedStep == .giftDetail(applicationNo: "GIFT-2026-002"), "identifier whitespace trimmed")
|
|
|
|
for url in [
|
|
"voiceon://?deepLinkValue=GIFT_DETAIL",
|
|
"voiceon://?deepLinkId=GIFT-2026-003",
|
|
"voiceon://?deepLinkValue=GIFT_DETAIL&deepLinkId=%20%20",
|
|
"voiceon://?deepLinkValue=OTHER&deepLinkId=GIFT-2026-004",
|
|
"voiceon://gift-detail/",
|
|
"http://?deepLinkValue=GIFT_DETAIL&deepLinkId=GIFT-2026-005"
|
|
] {
|
|
check(!AppDeepLinkHandler.handle(urlString: url), "rejects invalid or unsupported \(url)")
|
|
}
|
|
|
|
AppState.shared.rootStep = .splash
|
|
AppState.shared.pendingDeepLinkAction = nil
|
|
AppState.shared.appliedStep = nil
|
|
check(AppDeepLinkHandler.handle(urlString: "voiceon://?deepLinkValue=GIFT_DETAIL&deepLinkId=GIFT-2026-006"), "valid splash link accepted")
|
|
settle()
|
|
if case .giftDetail(let applicationNo)? = AppState.shared.pendingDeepLinkAction {
|
|
check(applicationNo == "GIFT-2026-006", "splash stores gift action")
|
|
} else {
|
|
check(false, "splash stores gift action")
|
|
}
|
|
check(AppState.shared.appliedStep == nil, "splash does not apply gift action")
|
|
|
|
AppState.shared.pendingDeepLinkAction = nil
|
|
check(AppDeepLinkHandler.handle(urlString: "voiceon-test://gift-detail/A-1002609300001"), "splash accepts path gift detail")
|
|
settle()
|
|
if case .giftDetail(let applicationNo)? = AppState.shared.pendingDeepLinkAction {
|
|
check(applicationNo == "A-1002609300001", "splash stores path gift action")
|
|
} else {
|
|
check(false, "splash stores path gift action")
|
|
}
|
|
check(AppState.shared.appliedStep == nil, "splash does not apply path gift action")
|
|
|
|
AppState.shared.rootStep = .main
|
|
AppState.shared.appliedStep = nil
|
|
AppState.shared.pendingDeepLinkAction = nil
|
|
check(AppDeepLinkHandler.handle(urlString: "voiceon://?content_id=42&deepLinkValue=GIFT_DETAIL&deepLinkId=GIFT-2026-007"), "existing content route accepted")
|
|
settle()
|
|
check(AppState.shared.appliedStep == .contentDetail(contentId: 42), "existing content route still applies")
|
|
checkCommunityLinks()
|
|
print("PASS AppDeepLinkGiftDetailCheck assertions=\(assertions)")
|
|
}
|
|
|
|
static func checkCommunityLinks() {
|
|
for userId in [0, 42] {
|
|
UserDefaults.userId = userId
|
|
for source in [AppDeepLinkSource.external, .notificationList] {
|
|
for url in [
|
|
"voiceon://community?postId=12345",
|
|
"voiceon-test://community?post_id=12345",
|
|
"voiceon:///community?POSTID=12345",
|
|
"voiceon://community/77?postId=12345",
|
|
"voiceon://?community_id=77&postId=12345"
|
|
] {
|
|
AppState.shared.appliedStep = nil
|
|
AppState.shared.pendingCommentPostId = nil
|
|
check(AppDeepLinkHandler.handle(urlString: url, source: source), "community link accepted: \(url), userId=\(userId)")
|
|
settle()
|
|
check(AppState.shared.appliedStep == .creatorChannelCommunityPostDetail(postId: 12345), "community link opens requested post detail: \(url)")
|
|
check(AppState.shared.pendingCommentPostId == nil, "detail link does not queue legacy comment sheet")
|
|
}
|
|
}
|
|
}
|
|
|
|
for rootStep in [AppStep.main, .splash] {
|
|
AppState.shared.rootStep = rootStep
|
|
for route in ["voiceon://community", "voiceon:///community", "voiceon://community/77", "voiceon://?community_id=77"] {
|
|
for parameter in ["", "postId=", "postId=abc", "postId=0", "postId=-1", "postId=1.5", "postId=999999999999999999999999", "post_id=0"] {
|
|
let separator = route.contains("?") ? "&" : "?"
|
|
let url = route + separator + parameter
|
|
AppState.shared.appliedStep = .contentDetail(contentId: 42)
|
|
AppState.shared.pendingDeepLinkAction = .content(contentId: 99)
|
|
check(!AppDeepLinkHandler.handle(urlString: url), "invalid community link rejected: \(url)")
|
|
settle()
|
|
check(AppState.shared.appliedStep == .contentDetail(contentId: 42), "invalid postId leaves current route unchanged")
|
|
if case .content(let contentId)? = AppState.shared.pendingDeepLinkAction {
|
|
check(contentId == 99, "invalid postId preserves existing deferred action")
|
|
} else {
|
|
check(false, "invalid postId preserves existing deferred action")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
check(!AppDeepLinkHandler.handle(urlString: "voiceon://community?postId=bad&content_id=42"), "invalid community route does not fall through to content")
|
|
check(!AppDeepLinkHandler.handle(urlString: "voiceon:///community?message_id=42"), "missing postId does not fall through to message")
|
|
|
|
AppState.shared.rootStep = .splash
|
|
AppState.shared.appliedStep = nil
|
|
AppState.shared.pendingDeepLinkAction = nil
|
|
check(AppDeepLinkHandler.handle(urlString: "voiceon://community?postId=12345"), "splash accepts community detail")
|
|
settle()
|
|
check(AppState.shared.appliedStep == nil, "splash defers detail navigation")
|
|
guard let action = AppState.shared.pendingDeepLinkAction else {
|
|
check(false, "splash stores community detail action")
|
|
return
|
|
}
|
|
AppState.shared.rootStep = .main
|
|
AppState.shared.pendingDeepLinkAction = nil
|
|
AppDeepLinkHandler.apply(action: action)
|
|
check(AppState.shared.appliedStep == .creatorChannelCommunityPostDetail(postId: 12345), "deferred community link opens requested post detail")
|
|
}
|
|
}
|