feat: 优化账号注销流程
This commit is contained in:
@@ -193,32 +193,47 @@ final class StoreAccountDeregistrationCombinedTests: XCTestCase {
|
||||
/// 两步页面的实际UIKit布局和交互测试;独立窗口不触碰用户真实会话。
|
||||
@MainActor
|
||||
final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
/// 明确受理后立即调用一次退出,不等待后续GET;在途连点及成功后再次点击均不重发。
|
||||
func testAcceptedSubmissionExitsOnceWithoutPostSubmissionQuery() async throws {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
GlobalLoadingManager.shared.hideAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
GlobalLoadingManager.shared.hideAll()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
/// 明确受理后只读获取截止时间并调用一次退出;在途连点及成功后再次点击均不重发。
|
||||
func testAcceptedSubmissionCapturesCoolingDeadlineAndExitsOnce() async throws {
|
||||
let service = CombinedDeregistrationService()
|
||||
service.walletWaived = true
|
||||
service.pointsWaived = true
|
||||
service.holdApply = true
|
||||
service.onApply = { service.readError = APIError.networkFailed("成功后不应再查询") }
|
||||
let coolingStatus = try StoreAccountDeregistrationFixtures.lifecycleStatus()
|
||||
service.onApply = { service.statusOverride = coolingStatus }
|
||||
let suite = "DeregistrationLogout.\(UUID().uuidString)"
|
||||
let defaults = UserDefaults(suiteName: suite)!
|
||||
let store = StoreAccountDeregistrationSubmissionStore(storeUserID: "101", environment: .testing, defaults: defaults)
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let model = StoreAccountDeregistrationViewModel(storeUserID: 101, submissionStore: store)
|
||||
var logoutCount = 0
|
||||
var acceptedDeadline: String?
|
||||
var unresolvedCount = 0
|
||||
let controller = StoreAccountDeregistrationViewController(
|
||||
identityName: "测试门店", viewModel: model, api: service,
|
||||
onUnresolvedSubmission: { unresolvedCount += 1 }, onSubmissionAccepted: { logoutCount += 1 })
|
||||
onUnresolvedSubmission: { unresolvedCount += 1 }, onSubmissionAccepted: { deadline in
|
||||
acceptedDeadline = deadline
|
||||
logoutCount += 1
|
||||
})
|
||||
let window = makeWindow(controller, size: UIScreen.main.bounds.size)
|
||||
defer { service.finishApply(); close(window) }
|
||||
await waitUntil { model.canContinue }
|
||||
model.beginVerification()
|
||||
let initialStatusCount = service.statusCount
|
||||
let initialEligibilityCount = service.eligibilityCount
|
||||
controller.submitConfirmedApplication(smsCode: "654321", reason: "不再使用")
|
||||
controller.submitConfirmedApplication(smsCode: "654321", reason: " 不再使用 ")
|
||||
await waitUntil { service.applyContinuation != nil }
|
||||
controller.submitConfirmedApplication(smsCode: "654321", reason: "不再使用")
|
||||
controller.submitConfirmedApplication(smsCode: "654321", reason: " 不再使用 ")
|
||||
XCTAssertEqual(logoutCount, 0)
|
||||
XCTAssertTrue(model.isBusy)
|
||||
XCTAssertTrue(GlobalLoadingManager.shared.isShowing)
|
||||
@@ -226,13 +241,16 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
capture(window, name: "deregister-submitting-global-loading")
|
||||
service.finishApply()
|
||||
await waitUntil { logoutCount == 1 }
|
||||
controller.submitConfirmedApplication(smsCode: "654321", reason: "不再使用")
|
||||
controller.submitConfirmedApplication(smsCode: "654321", reason: " 不再使用 ")
|
||||
XCTAssertEqual(logoutCount, 1)
|
||||
XCTAssertEqual(unresolvedCount, 0)
|
||||
XCTAssertEqual(service.mutations, ["apply"])
|
||||
XCTAssertEqual(service.statusCount, initialStatusCount + 1, "只保留提交前校验")
|
||||
XCTAssertEqual(service.lastReason, "不再使用")
|
||||
XCTAssertEqual(service.statusCount, initialStatusCount + 2, "提交前校验并在成功后只读获取截止时间")
|
||||
XCTAssertEqual(service.eligibilityCount, initialEligibilityCount + 1)
|
||||
XCTAssertTrue(model.submissionAccepted)
|
||||
XCTAssertEqual(model.submittedCoolingUntil, coolingStatus.coolingUntil)
|
||||
XCTAssertEqual(acceptedDeadline, coolingStatus.coolingUntil)
|
||||
XCTAssertNil(model.errorMessage)
|
||||
XCTAssertTrue(store.hasUnresolvedSubmission, "保留意图供下次登录核实")
|
||||
XCTAssertFalse(GlobalLoadingManager.shared.isShowing)
|
||||
@@ -250,17 +268,29 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
var unresolvedCount = 0
|
||||
let controller = StoreAccountDeregistrationViewController(
|
||||
identityName: "测试门店", viewModel: model, api: service,
|
||||
onUnresolvedSubmission: { unresolvedCount += 1 }, onSubmissionAccepted: { logoutCount += 1 })
|
||||
onUnresolvedSubmission: { unresolvedCount += 1 }, onSubmissionAccepted: { _ in logoutCount += 1 })
|
||||
let window = makeWindow(controller, size: UIScreen.main.bounds.size)
|
||||
defer { close(window) }
|
||||
await waitUntil { model.canContinue }
|
||||
model.beginVerification()
|
||||
let codeField = try XCTUnwrap(find(controller.view, "deregister.code") as? UITextField)
|
||||
let reasonField = try XCTUnwrap(find(controller.view, "deregister.reason") as? UITextView)
|
||||
codeField.text = "654321"
|
||||
reasonField.text = "不再使用"
|
||||
controller.submitConfirmedApplication(smsCode: "654321", reason: "不再使用")
|
||||
await waitUntil { model.errorMessage != nil && !model.isBusy }
|
||||
XCTAssertEqual(logoutCount, 0)
|
||||
XCTAssertEqual(unresolvedCount, uncertain ? 1 : 0)
|
||||
XCTAssertEqual(model.submissionAttempted, uncertain)
|
||||
XCTAssertFalse(model.submissionAccepted)
|
||||
XCTAssertEqual(model.step, uncertain ? .unresolvedRequest : .verification)
|
||||
XCTAssertEqual(model.canContinue, !uncertain)
|
||||
let submit = try XCTUnwrap(find(controller.view, "deregister.submit") as? UIButton)
|
||||
XCTAssertEqual(submit.isHidden, uncertain)
|
||||
if !uncertain {
|
||||
XCTAssertEqual(codeField.text, "654321")
|
||||
XCTAssertEqual(reasonField.text, "不再使用")
|
||||
}
|
||||
XCTAssertEqual(service.mutations, ["apply"])
|
||||
XCTAssertFalse(GlobalLoadingManager.shared.isShowing)
|
||||
}
|
||||
@@ -281,7 +311,7 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
next.sendActions(for: .touchUpInside)
|
||||
await waitUntil { model.step == .verification }
|
||||
let code = try XCTUnwrap(find(controller.view, "deregister.code") as? UITextField)
|
||||
let reason = try XCTUnwrap(find(controller.view, "deregister.reason") as? UITextField)
|
||||
let reason = try XCTUnwrap(find(controller.view, "deregister.reason") as? UITextView)
|
||||
code.text = "654321"
|
||||
reason.text = "不再使用"
|
||||
let scroll = try XCTUnwrap(allViews(controller.view).first { $0 is UIScrollView } as? UIScrollView)
|
||||
@@ -324,13 +354,16 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
await waitUntil { service.statusCount == count + 1 && !model.isBusy }
|
||||
let texts = allViews(controller.view).compactMap { ($0 as? UILabel)?.text }.joined(separator: "\n")
|
||||
XCTAssertFalse(texts.contains("景区"))
|
||||
XCTAssertTrue(texts.contains("同手机号的其他身份不受影响"))
|
||||
XCTAssertTrue(texts.contains("仅注销当前身份"))
|
||||
XCTAssertFalse(texts.contains("同手机号的其他身份不受影响"))
|
||||
XCTAssertFalse(texts.contains("到期由服务端复核"))
|
||||
XCTAssertFalse(texts.contains("历史订单、财务及审计记录"))
|
||||
XCTAssertFalse(StoreAccountDeregistrationError.unsupportedIdentity.localizedDescription.contains("景区"))
|
||||
XCTAssertTrue(service.mutations.isEmpty)
|
||||
XCTAssertFalse(refresh.isRefreshing)
|
||||
}
|
||||
|
||||
/// 点击提交仅显示最终确认,明确退出登录和冷静期,不自动申请或发送短信。
|
||||
/// 点击提交仅显示高保真最终确认;原因必填并在确认弹层展示规范化内容。
|
||||
func testSubmitRequiresFinalConfirmationWithLogoutNotice() async throws {
|
||||
let service = CombinedDeregistrationService()
|
||||
service.walletWaived = true
|
||||
@@ -338,7 +371,8 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
let model = StoreAccountDeregistrationViewModel(storeUserID: 101)
|
||||
var logoutCount = 0
|
||||
let controller = StoreAccountDeregistrationViewController(
|
||||
identityName: "当前测试身份", viewModel: model, api: service, onSubmissionAccepted: { logoutCount += 1 })
|
||||
identityName: "当前测试身份", viewModel: model, api: service,
|
||||
onSubmissionAccepted: { _ in logoutCount += 1 })
|
||||
let window = makeWindow(controller, size: UIScreen.main.bounds.size)
|
||||
defer { close(window) }
|
||||
await waitUntil { model.canContinue }
|
||||
@@ -346,23 +380,107 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
next.sendActions(for: .touchUpInside)
|
||||
await waitUntil { model.step == .verification }
|
||||
let code = try XCTUnwrap(find(controller.view, "deregister.code") as? UITextField)
|
||||
let reason = try XCTUnwrap(find(controller.view, "deregister.reason") as? UITextField)
|
||||
let reason = try XCTUnwrap(find(controller.view, "deregister.reason") as? UITextView)
|
||||
code.text = "654321"
|
||||
reason.text = "不再使用"
|
||||
reason.sendActions(for: .editingChanged)
|
||||
code.sendActions(for: .editingChanged)
|
||||
let submit = try XCTUnwrap(find(controller.view, "deregister.submit") as? UIButton)
|
||||
XCTAssertFalse(submit.isEnabled, "验证码和注销原因均为必填项")
|
||||
reason.text = " 门店停业 "
|
||||
controller.textViewDidChange(reason)
|
||||
XCTAssertTrue(submit.isEnabled)
|
||||
submit.sendActions(for: .touchUpInside)
|
||||
await waitUntil { controller.presentedViewController is UIAlertController }
|
||||
let alert = try XCTUnwrap(controller.presentedViewController as? UIAlertController)
|
||||
XCTAssertTrue(alert.message?.contains("提交成功后将退出登录") == true)
|
||||
XCTAssertTrue(alert.message?.contains("当前测试身份") == true)
|
||||
XCTAssertTrue(alert.message?.contains("7 天冷静期") == true)
|
||||
XCTAssertTrue(alert.message?.contains("正式完成不可恢复") == true)
|
||||
XCTAssertEqual(alert.actions.map(\.title), ["暂不提交", "确认提交申请"])
|
||||
await waitUntil { controller.presentedViewController is StoreAccountDeregistrationConfirmationSheetViewController }
|
||||
let sheet = try XCTUnwrap(controller.presentedViewController as? StoreAccountDeregistrationConfirmationSheetViewController)
|
||||
sheet.loadViewIfNeeded()
|
||||
let texts = allViews(sheet.view).compactMap { ($0 as? UILabel)?.text }.joined(separator: "\n")
|
||||
XCTAssertTrue(texts.contains("确认提交注销申请?"))
|
||||
XCTAssertTrue(texts.contains("当前测试身份"))
|
||||
XCTAssertTrue(texts.contains("7 天"))
|
||||
XCTAssertTrue(texts.contains("注销原因"))
|
||||
XCTAssertTrue(texts.contains("门店停业"))
|
||||
XCTAssertTrue(texts.contains("正式完成后不可恢复"))
|
||||
XCTAssertEqual((find(sheet.view, "deregister.sheet.cancel") as? UIButton)?.configuration?.title, "我再想想")
|
||||
XCTAssertEqual((find(sheet.view, "deregister.sheet.confirm") as? UIButton)?.configuration?.title, "确认提交")
|
||||
try? await Task.sleep(nanoseconds: 350_000_000)
|
||||
try assertSheetHugsContent(sheet)
|
||||
capture(window, name: "redesign-submit-confirmation")
|
||||
XCTAssertTrue(service.mutations.isEmpty)
|
||||
XCTAssertEqual(logoutCount, 0)
|
||||
}
|
||||
|
||||
/// 申请受理后的退出态结果页只保留“完成”,不能返回或继续操作注销接口。
|
||||
func testSubmittedPageOnlyAllowsCompletingToLogin() throws {
|
||||
var doneCount = 0
|
||||
let controller = StoreAccountDeregistrationSubmittedViewController(
|
||||
identityName: " 当前测试身份 ",
|
||||
coolingUntil: "2026-09-07 18:30:00"
|
||||
) {
|
||||
doneCount += 1
|
||||
}
|
||||
let window = makeWindow(controller, size: UIScreen.main.bounds.size)
|
||||
defer { close(window) }
|
||||
window.layoutIfNeeded()
|
||||
|
||||
let title = try XCTUnwrap(find(controller.view, "deregister.submitted.title") as? UILabel)
|
||||
let identity = try XCTUnwrap(find(controller.view, "deregister.submitted.identity") as? UILabel)
|
||||
let deadline = try XCTUnwrap(find(controller.view, "deregister.submitted.deadline") as? UILabel)
|
||||
let done = try XCTUnwrap(find(controller.view, "deregister.submitted.done") as? UIButton)
|
||||
XCTAssertEqual(title.text, "注销申请已提交")
|
||||
XCTAssertEqual(identity.text, "当前身份:当前测试身份")
|
||||
XCTAssertEqual(deadline.text, "2026-09-07 18:30:00")
|
||||
XCTAssertEqual(done.configuration?.title, "完成")
|
||||
XCTAssertNil(find(controller.view, "deregister.access.logout"))
|
||||
XCTAssertNil(find(controller.view, "deregister.access.cancel"))
|
||||
XCTAssertTrue(controller.navigationItem.hidesBackButton)
|
||||
XCTAssertFalse(controller.navigationController?.interactivePopGestureRecognizer?.isEnabled ?? true)
|
||||
XCTAssertTrue(window.bounds.contains(done.convert(done.bounds, to: window)))
|
||||
capture(window, name: "redesign-submitted-signed-out")
|
||||
|
||||
done.sendActions(for: .touchUpInside)
|
||||
done.sendActions(for: .touchUpInside)
|
||||
XCTAssertEqual(doneCount, 1)
|
||||
}
|
||||
|
||||
/// iPhone 11 画布按最终设计稿渲染资产页和资产确认弹层;步骤标题必须保持横排。
|
||||
func testDesignReferenceAssetScreensOnPhysicalDevice() async throws {
|
||||
let service = CombinedDeregistrationService()
|
||||
service.walletFen = 1260
|
||||
service.points = 20
|
||||
let model = StoreAccountDeregistrationViewModel(storeUserID: 101)
|
||||
let controller = StoreAccountDeregistrationViewController(identityName: "示例门店", viewModel: model, api: service)
|
||||
let window = makeWindow(controller, size: UIScreen.main.bounds.size)
|
||||
defer { close(window) }
|
||||
await waitUntil { model.canConfirmAssetsAndContinue }
|
||||
window.layoutIfNeeded()
|
||||
let labels = allViews(controller.view).compactMap { $0 as? UILabel }
|
||||
for title in ["确认资产", "手机验证"] {
|
||||
let label = try XCTUnwrap(labels.first { $0.text == title })
|
||||
XCTAssertEqual(label.numberOfLines, 1)
|
||||
XCTAssertLessThan(label.bounds.height, 28)
|
||||
}
|
||||
let assetStep = try XCTUnwrap(find(controller.view, "deregister.step.assets"))
|
||||
let verificationStep = try XCTUnwrap(find(controller.view, "deregister.step.verification"))
|
||||
XCTAssertEqual(assetStep.bounds.width, verificationStep.bounds.width, accuracy: 0.5)
|
||||
XCTAssertEqual(assetStep.bounds.height, 46, accuracy: 0.5)
|
||||
XCTAssertEqual(verificationStep.bounds.height, 46, accuracy: 0.5)
|
||||
XCTAssertEqual(assetStep.backgroundColor, StoreAccountDeregistrationStyle.primary)
|
||||
XCTAssertNotEqual(assetStep.backgroundColor, verificationStep.backgroundColor)
|
||||
XCTAssertTrue(assetStep.accessibilityTraits.contains(.selected))
|
||||
let pageTexts = labels.compactMap(\.text).joined(separator: "\n")
|
||||
XCTAssertFalse(pageTexts.contains("同手机号的其他身份不受影响"))
|
||||
XCTAssertFalse(pageTexts.contains("到期由服务端复核"))
|
||||
XCTAssertFalse(pageTexts.contains("历史订单、财务及审计记录"))
|
||||
capture(window, name: "redesign-assets-iphone11")
|
||||
let next = try XCTUnwrap(find(controller.view, "deregister.continue") as? UIButton)
|
||||
next.sendActions(for: .touchUpInside)
|
||||
await waitUntil { controller.presentedViewController is StoreAccountDeregistrationConfirmationSheetViewController }
|
||||
try? await Task.sleep(nanoseconds: 350_000_000)
|
||||
let sheet = try XCTUnwrap(controller.presentedViewController as? StoreAccountDeregistrationConfirmationSheetViewController)
|
||||
try assertSheetHugsContent(sheet)
|
||||
capture(window, name: "redesign-asset-confirmation")
|
||||
XCTAssertTrue(service.mutations.isEmpty)
|
||||
}
|
||||
|
||||
/// 375pt小屏上仅一个主按钮,零资产确认也先弹窗,没有自动提交。
|
||||
func testSingleAssetButtonShowsOneExplicitConfirmation() async throws {
|
||||
let service = CombinedDeregistrationService()
|
||||
@@ -383,16 +501,21 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
XCTAssertTrue(window.bounds.contains(next.convert(next.bounds, to: window)))
|
||||
capture(window, name: "redesign-assets-375")
|
||||
next.sendActions(for: .touchUpInside)
|
||||
await waitUntil { controller.presentedViewController is UIAlertController }
|
||||
let alert = try XCTUnwrap(controller.presentedViewController as? UIAlertController)
|
||||
XCTAssertEqual(alert.actions.map(\.title), ["暂不确认", "确认并继续"])
|
||||
XCTAssertTrue(alert.message?.contains("现金余额:¥0.00") == true)
|
||||
XCTAssertTrue(alert.message?.contains("积分:0") == true)
|
||||
XCTAssertTrue(alert.message?.contains("确认自愿放弃以上现金余额,并确认自愿放弃以上积分") == true)
|
||||
await waitUntil { controller.presentedViewController is StoreAccountDeregistrationConfirmationSheetViewController }
|
||||
let sheet = try XCTUnwrap(controller.presentedViewController as? StoreAccountDeregistrationConfirmationSheetViewController)
|
||||
sheet.loadViewIfNeeded()
|
||||
let sheetTexts = allViews(sheet.view).compactMap { ($0 as? UILabel)?.text }.joined(separator: "\n")
|
||||
XCTAssertTrue(sheetTexts.contains("确认放弃账号资产"))
|
||||
XCTAssertTrue(sheetTexts.contains("¥0.00"))
|
||||
XCTAssertTrue(sheetTexts.contains("积分\n0"))
|
||||
XCTAssertFalse(sheetTexts.contains("自愿放弃以上资产"))
|
||||
XCTAssertFalse(sheetTexts.contains("零余额也需要确认"))
|
||||
XCTAssertEqual((find(sheet.view, "deregister.sheet.cancel") as? UIButton)?.configuration?.title, "暂不确认")
|
||||
XCTAssertEqual((find(sheet.view, "deregister.sheet.confirm") as? UIButton)?.configuration?.title, "确认放弃")
|
||||
XCTAssertTrue(service.mutations.isEmpty)
|
||||
}
|
||||
|
||||
/// 已确认资产不再弹窗;验证码与获取按钮同行,输入完整前禁用提交,键盘不遮挡操作区。
|
||||
/// 已确认资产不再弹窗;验证码和原因均必填,多行输入与错误提示不被键盘遮挡。
|
||||
func testVerificationLayoutInputAndKeyboard() async throws {
|
||||
let service = CombinedDeregistrationService()
|
||||
service.walletWaived = true
|
||||
@@ -406,24 +529,37 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
next.sendActions(for: .touchUpInside)
|
||||
await waitUntil { model.step == .verification }
|
||||
window.layoutIfNeeded()
|
||||
let assetStep = try XCTUnwrap(find(controller.view, "deregister.step.assets"))
|
||||
let verificationStep = try XCTUnwrap(find(controller.view, "deregister.step.verification"))
|
||||
XCTAssertNotEqual(assetStep.backgroundColor, verificationStep.backgroundColor)
|
||||
XCTAssertTrue(verificationStep.accessibilityTraits.contains(.selected))
|
||||
XCTAssertFalse(assetStep.accessibilityTraits.contains(.selected))
|
||||
let code = try XCTUnwrap(find(controller.view, "deregister.code") as? UITextField)
|
||||
let reason = try XCTUnwrap(find(controller.view, "deregister.reason") as? UITextField)
|
||||
let reason = try XCTUnwrap(find(controller.view, "deregister.reason") as? UITextView)
|
||||
let sms = try XCTUnwrap(find(controller.view, "deregister.sms") as? UIButton)
|
||||
let submit = try XCTUnwrap(find(controller.view, "deregister.submit") as? UIButton)
|
||||
XCTAssertEqual(code.textContentType, .oneTimeCode)
|
||||
XCTAssertEqual(code.keyboardType, .numberPad)
|
||||
XCTAssertEqual(code.superview, sms.superview)
|
||||
XCTAssertEqual(find(controller.view, "deregister.reason.title")?.accessibilityLabel, "注销原因,必填")
|
||||
XCTAssertFalse(submit.isEnabled)
|
||||
capture(window, name: "redesign-verification")
|
||||
code.text = "123456"
|
||||
code.sendActions(for: .editingChanged)
|
||||
XCTAssertFalse(submit.isEnabled)
|
||||
reason.becomeFirstResponder()
|
||||
reason.resignFirstResponder()
|
||||
controller.textViewDidEndEditing(reason)
|
||||
let reasonError = try XCTUnwrap(find(controller.view, "deregister.reason.error") as? UILabel)
|
||||
XCTAssertFalse(reasonError.isHidden)
|
||||
XCTAssertEqual(reasonError.text, "请填写注销原因")
|
||||
reason.text = "测试"
|
||||
reason.sendActions(for: .editingChanged)
|
||||
controller.textViewDidChange(reason)
|
||||
XCTAssertTrue(submit.isEnabled)
|
||||
XCTAssertTrue(reasonError.isHidden)
|
||||
let navigationFrame = controller.navigationController?.view.frame
|
||||
code.becomeFirstResponder()
|
||||
await waitUntil { controller.view.keyboardLayoutGuide.layoutFrame.height > controller.view.safeAreaInsets.bottom + 80 }
|
||||
XCTAssertTrue(code.becomeFirstResponder())
|
||||
try? await Task.sleep(nanoseconds: 100_000_000)
|
||||
window.layoutIfNeeded()
|
||||
let frame = submit.convert(submit.bounds, to: controller.view)
|
||||
XCTAssertLessThanOrEqual(frame.maxY, controller.view.keyboardLayoutGuide.layoutFrame.minY + 1)
|
||||
@@ -515,6 +651,14 @@ final class StoreAccountDeregistrationRedesignTests: XCTestCase {
|
||||
}
|
||||
private func allViews(_ view: UIView) -> [UIView] { view.subviews.flatMap { [$0] + allViews($0) } }
|
||||
private func find(_ view: UIView, _ id: String) -> UIView? { allViews(view).first { $0.accessibilityIdentifier == id } }
|
||||
private func assertSheetHugsContent(_ sheet: StoreAccountDeregistrationConfirmationSheetViewController) throws {
|
||||
sheet.view.layoutIfNeeded()
|
||||
let confirm = try XCTUnwrap(find(sheet.view, "deregister.sheet.confirm"))
|
||||
let buttonBottom = confirm.convert(confirm.bounds, to: sheet.view).maxY
|
||||
let visualGap = sheet.view.bounds.height - sheet.view.safeAreaInsets.bottom - buttonBottom
|
||||
XCTAssertGreaterThanOrEqual(visualGap, 10)
|
||||
XCTAssertLessThanOrEqual(visualGap, 18, "弹层底部只应保留 12pt 操作间距")
|
||||
}
|
||||
private func waitUntil(_ condition: () -> Bool) async {
|
||||
for _ in 0..<150 {
|
||||
if condition() { return }
|
||||
@@ -559,6 +703,7 @@ private final class CombinedDeregistrationService: StoreAccountDeregistrationSer
|
||||
var applyContinuation: CheckedContinuation<Void, Never>?
|
||||
var holdNextStatus = false
|
||||
var statusContinuation: CheckedContinuation<Void, Never>?
|
||||
var lastReason: String?
|
||||
|
||||
func eligibility() async throws -> StoreAccountDeregistrationEligibility {
|
||||
eligibilityCount += 1
|
||||
@@ -600,6 +745,7 @@ private final class CombinedDeregistrationService: StoreAccountDeregistrationSer
|
||||
func sendSMS() async throws { mutations.append("sms") }
|
||||
func apply(smsCode: String, reason: String) async throws {
|
||||
mutations.append("apply")
|
||||
lastReason = reason
|
||||
if holdApply { await withCheckedContinuation { applyContinuation = $0 } }
|
||||
if let applyError { throw applyError }
|
||||
onApply?()
|
||||
|
||||
Reference in New Issue
Block a user