Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 12 additions & 0 deletions Demo/Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
04DDF4622EE8AA1900FBDE49 /* PaymentButtonHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DDF4612EE8AA1200FBDE49 /* PaymentButtonHelper.swift */; };
08222B07C311489F90D08D41 /* Pods_Demo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D23244B5E9EE59BAB3F3003 /* Pods_Demo.framework */; };
084C2C6E2FD85A960018BB01 /* CardFields_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084C2C6D2FD85A960018BB01 /* CardFields_UITests.swift */; };
086561662EF1D4EF00B16439 /* BraintreeUIComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08A83F5A2EDF965E0076BD28 /* BraintreeUIComponents.framework */; };
086561672EF1D4EF00B16439 /* BraintreeUIComponents.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 08A83F5A2EDF965E0076BD28 /* BraintreeUIComponents.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
08A83F592EDE268E0076BD28 /* UIComponentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A83F582EDE268E0076BD28 /* UIComponentsViewController.swift */; };
Expand Down Expand Up @@ -132,6 +133,7 @@

/* Begin PBXFileReference section */
04DDF4612EE8AA1200FBDE49 /* PaymentButtonHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentButtonHelper.swift; sourceTree = "<group>"; };
084C2C6D2FD85A960018BB01 /* CardFields_UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardFields_UITests.swift; sourceTree = "<group>"; };
08A83F582EDE268E0076BD28 /* UIComponentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIComponentsViewController.swift; sourceTree = "<group>"; };
08A83F5A2EDF965E0076BD28 /* BraintreeUIComponents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BraintreeUIComponents.framework; sourceTree = BUILT_PRODUCTS_DIR; };
08A83F692EE9AB5A0076BD28 /* VenmoButton_UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VenmoButton_UITests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -252,6 +254,14 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
084C2C6C2FD85A740018BB01 /* CardFields UI Tests */ = {
isa = PBXGroup;
children = (
084C2C6D2FD85A960018BB01 /* CardFields_UITests.swift */,
);
path = "CardFields UI Tests";
sourceTree = "<group>";
};
08A83F682EE8D0F10076BD28 /* Payment Button UI Tests */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -421,6 +431,7 @@
A9B5ABDF24EB2A2200A4E1C8 /* UI Tests */ = {
isa = PBXGroup;
children = (
084C2C6C2FD85A740018BB01 /* CardFields UI Tests */,
08A83F682EE8D0F10076BD28 /* Payment Button UI Tests */,
42456E3B25474B620018374E /* Helpers */,
A9B5ABE224EB2A2200A4E1C8 /* Info.plist */,
Expand Down Expand Up @@ -732,6 +743,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
084C2C6E2FD85A960018BB01 /* CardFields_UITests.swift in Sources */,
A9C4E07F24EC2A0C002F6FF2 /* ThreeDSecure_UITests_Extensions.swift in Sources */,
42456E3F25474B620018374E /* DateGenerator.swift in Sources */,
08A83F6A2EE9AB5A0076BD28 /* VenmoButton_UITests.swift in Sources */,
Expand Down
171 changes: 171 additions & 0 deletions Demo/UI Tests/CardFields UI Tests/CardFields_UITests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import XCTest

final class CardFields_UITests: XCTestCase {

var app = XCUIApplication()

override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments.append("-EnvironmentSandbox")
app.launchArguments.append("-ClientToken")
app.launchArguments.append("-Integration:UIComponentsViewController")
app.launchEnvironment["UITEST_DISABLE_ANIMATIONS"] = "YES"
app.launch()
}

// MARK: - Pay Button State

func testCardFields_payButtonInitiallyDisabled_enabledAfterValidInput() {
let payButton = app.buttons["Pay"]
XCTAssertTrue(payButton.waitForExistence(timeout: 5), "Pay button should be visible")
XCTAssertFalse(payButton.isEnabled, "Pay button should be disabled when card fields are empty")

let cardNumberField = app.textFields["Card Number"]
waitForElementToBeHittable(cardNumberField)
cardNumberField.tap()
cardNumberField.typeText("4111111111111111")

let expirationField = app.textFields["Expiration Date"]
expirationField.tap()
expirationField.typeText("1245")

let cvvField = app.textFields["CVV"]
cvvField.tap()
cvvField.typeText("123")

XCTAssertTrue(payButton.isEnabled, "Pay button should be enabled after valid card details are entered")
}

// MARK: - Validation Errors

func testCardFields_showsError_whenInvalidDataIsEntered() {
let cardNumberField = app.textFields["Card Number"]
let expirationField = app.textFields["Expiration Date"]
let cvvField = app.textFields["CVV"]

// Enter incomplete card number (10 of 16 digits), then advance focus
waitForElementToBeHittable(cardNumberField)
cardNumberField.tap()
cardNumberField.typeText("4111111111")

expirationField.tap()
XCTAssertTrue(
app.staticTexts["Card number is invalid"].waitForExistence(timeout: 2),
"Error should appear below card number field after leaving with incomplete number"
)

// Advance to CVV without entering an expiration date
cvvField.tap()
XCTAssertTrue(
app.staticTexts["Expiration date is required"].waitForExistence(timeout: 2),
"Error should appear below expiration field after leaving it empty"
)

// Enter only 2 CVV digits, then advance focus
cvvField.typeText("12")
expirationField.tap()
XCTAssertTrue(
app.staticTexts["CVV is invalid"].waitForExistence(timeout: 2),
"Error should appear below CVV field after leaving with incomplete CVV"
)

// Correct all fields and verify Pay button becomes enabled
cardNumberField.tap(withNumberOfTaps: 3, numberOfTouches: 1)
cardNumberField.typeText("4111111111111111")

expirationField.typeText("1245")

cvvField.tap(withNumberOfTaps: 3, numberOfTouches: 1)
cvvField.typeText("123")

waitForElementToBeHittable(app.buttons["Pay"])

XCTAssertTrue(
app.buttons["Pay"].isEnabled,
"Pay button should be enabled after correcting all fields"
)
}

// MARK: - Card Brand Detection

func testCardFields_displaysVisaBrand_whenCardStartsWith4() {
let cardNumberField = app.textFields["Card Number"]
waitForElementToBeHittable(cardNumberField)
cardNumberField.tap()
cardNumberField.typeText("4")
XCTAssertTrue(app.images["Visa"].waitForExistence(timeout: 2))
}

func testCardFields_displaysMastercardBrand_whenCardStartsWith51() {
let cardNumberField = app.textFields["Card Number"]
waitForElementToBeHittable(cardNumberField)
cardNumberField.tap()
cardNumberField.typeText("51")
XCTAssertTrue(app.images["Mastercard"].waitForExistence(timeout: 2))
}

func testCardFields_displaysAmexBrand_whenCardStartsWith34() {
let cardNumberField = app.textFields["Card Number"]
waitForElementToBeHittable(cardNumberField)
cardNumberField.tap()
cardNumberField.typeText("34")
XCTAssertTrue(app.images["American Express"].waitForExistence(timeout: 2))
}

func testCardFields_displaysDiscoverBrand_whenCardStartsWith6011() {
let cardNumberField = app.textFields["Card Number"]
waitForElementToBeHittable(cardNumberField)
cardNumberField.tap()
cardNumberField.typeText("6011")
XCTAssertTrue(app.images["Discover"].waitForExistence(timeout: 2))
}

func testCardFields_displaysUnionPayBrand_whenCardStartsWith620() {
let cardNumberField = app.textFields["Card Number"]
waitForElementToBeHittable(cardNumberField)
cardNumberField.tap()
cardNumberField.typeText("620")
XCTAssertTrue(app.images["UnionPay"].waitForExistence(timeout: 2))
}

// MARK: - CVV Length Changes With Brand

func testCardFields_showsCVVError_whenSwitchingFromAmexToVisa() {
let cardNumberField = app.textFields["Card Number"]
let expirationField = app.textFields["Expiration Date"]
let cvvField = app.textFields["CVV"]

waitForElementToBeHittable(cardNumberField)
cardNumberField.tap()
cardNumberField.typeText("378282246310005")

expirationField.typeText("1245")

cvvField.tap()
cvvField.typeText("1234")

cardNumberField.tap(withNumberOfTaps: 3, numberOfTouches: 1)
cardNumberField.typeKey(.delete, modifierFlags: [])
waitForElementToBeHittable(cardNumberField)
cardNumberField.typeText("4111111111111111")

cvvField.tap()
expirationField.tap()

XCTAssertTrue(
app.staticTexts["CVV is invalid"].waitForExistence(timeout: 2),
"CVV error should appear when a 4-digit Amex CVV is used after switching to a Visa card"
)

cvvField.tap(withNumberOfTaps: 3, numberOfTouches: 1)
cvvField.typeText("123")

waitForElementToBeHittable(app.buttons["Pay"])

XCTAssertTrue(
app.buttons["Pay"].isEnabled,
"Pay button should be enabled after correcting CVV to 3 digits for Visa"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct CVVFieldView: View {
.foregroundColor(.clear)
.tint(Color(.label))
.font(.system(size: 16))
.accessibilityLabel("CVV")
Comment thread
jaxdesmarais marked this conversation as resolved.
.onChange(of: textFieldText) { _, newValue in
let digits = String(newValue.filter { $0.isNumber }.prefix(viewModel.maxLength))
if digits != textFieldText {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct CardBrandView: View {
brand.image
.resizable()
.scaledToFit()
.accessibilityLabel(brand.displayName)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct CardNumberFieldView: View {
.focused($isFocused)
.font(.system(size: 16))
.foregroundColor(Color(.label))
.accessibilityLabel("Card Number")
.onChange(of: textFieldText) { _, newValue in
let digits = String(newValue.filter { $0.isNumber }.prefix(viewModel.maxLength))
let formatted = viewModel.formatted(digits: digits)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct ExpirationDateFieldView: View {
.focused($isFocused)
.font(.body)
.foregroundColor(Color(.label))
.accessibilityLabel("Expiration Date")
Comment thread
jaxdesmarais marked this conversation as resolved.
.onChange(of: textFieldText) { _, newValue in
var digits = String(newValue.filter { $0.isNumber }.prefix(viewModel.maxLength))
if digits.count == 1, let digit = digits.first?.wholeNumberValue, digit >= 2 {
Expand Down
18 changes: 18 additions & 0 deletions Sources/BraintreeUIComponents/CardFields/Shared/CardBrand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@ enum CardBrand: CaseIterable {
self == .amex ? 4 : 3
}

// MARK: - Display

var displayName: String {
switch self {
case .visa: return "Visa"
case .mastercard: return "Mastercard"
case .amex: return "American Express"
case .discover: return "Discover"
case .jcb: return "JCB"
case .dinersClub: return "Diners Club"
case .unionPay: return "UnionPay"
case .maestro: return "Maestro"
case .hiper: return "Hiper"
case .hipercard: return "Hipercard"
case .unknown: return "Credit card"
}
}

// MARK: - Image

var image: Image {
Expand Down
Loading