Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update UI tests for the new WG Obfuscation views #7371

Merged
merged 3 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
447F3D8A2CDE1853006E3462 /* ShadowsocksObfuscationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 447F3D882CDE1852006E3462 /* ShadowsocksObfuscationSettingsViewModel.swift */; };
447F3D8B2CDE1853006E3462 /* ShadowsocksObfuscationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 447F3D892CDE1853006E3462 /* ShadowsocksObfuscationSettingsView.swift */; };
449275422C3570CA000526DE /* ICMP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449275412C3570CA000526DE /* ICMP.swift */; };
4495ECD12D0B170700A7358B /* UDPOverTCPObfuscationSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4495ECD02D0B16F700A7358B /* UDPOverTCPObfuscationSettingsPage.swift */; };
4495ECD52D131A4800A7358B /* ShadowsocksObfuscationSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4495ECD42D131A3E00A7358B /* ShadowsocksObfuscationSettingsPage.swift */; };
449872E12B7BBC5400094DDC /* TunnelSettingsUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */; };
449872E42B7CB96300094DDC /* TunnelSettingsUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449872E32B7CB96300094DDC /* TunnelSettingsUpdateTests.swift */; };
449EBA262B975B9700DFA4EB /* EphemeralPeerReceiving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449EBA252B975B9700DFA4EB /* EphemeralPeerReceiving.swift */; };
Expand Down Expand Up @@ -1437,6 +1439,8 @@
447F3D892CDE1853006E3462 /* ShadowsocksObfuscationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksObfuscationSettingsView.swift; sourceTree = "<group>"; };
449275412C3570CA000526DE /* ICMP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICMP.swift; sourceTree = "<group>"; };
449275432C3C3029000526DE /* TunnelPinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelPinger.swift; sourceTree = "<group>"; };
4495ECD02D0B16F700A7358B /* UDPOverTCPObfuscationSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPOverTCPObfuscationSettingsPage.swift; sourceTree = "<group>"; };
4495ECD42D131A3E00A7358B /* ShadowsocksObfuscationSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksObfuscationSettingsPage.swift; sourceTree = "<group>"; };
449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsUpdate.swift; sourceTree = "<group>"; };
449872E32B7CB96300094DDC /* TunnelSettingsUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsUpdateTests.swift; sourceTree = "<group>"; };
449EB9FC2B95F8AD00DFA4EB /* DeviceMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceMock.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4196,8 +4200,10 @@
8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */,
850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */,
850201E22B51A93C00EF8C96 /* SettingsPage.swift */,
4495ECD42D131A3E00A7358B /* ShadowsocksObfuscationSettingsPage.swift */,
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */,
850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */,
4495ECD02D0B16F700A7358B /* UDPOverTCPObfuscationSettingsPage.swift */,
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */,
85FB5A0B2B6903990015DCED /* WelcomePage.swift */,
);
Expand Down Expand Up @@ -6398,11 +6404,13 @@
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */,
85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */,
85021CAE2BDBC4290098B400 /* AppLogsPage.swift in Sources */,
4495ECD12D0B170700A7358B /* UDPOverTCPObfuscationSettingsPage.swift in Sources */,
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */,
7A45CFC62C05FF6A00D80B21 /* ScreenshotTests.swift in Sources */,
852D054D2BC3DE3A008578D2 /* APIAccessPage.swift in Sources */,
85139B2D2B84B4A700734217 /* OutOfTimePage.swift in Sources */,
852969362B4E9724007EAD4C /* AccessbilityIdentifier.swift in Sources */,
4495ECD52D131A4800A7358B /* ShadowsocksObfuscationSettingsPage.swift in Sources */,
85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */,
856952E22BD6B04C008C1F84 /* XCUIElement+Extensions.swift in Sources */,
85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ public enum AccessibilityIdentifier: Equatable {
case wireGuardObfuscationOff
case wireGuardObfuscationUdpOverTcp
case wireGuardObfuscationShadowsocks
case wireGuardObfuscationUdpOverTcpPort
case wireGuardObfuscationShadowsocksPort
case wireGuardPort(UInt16?)
case udpOverTcpObfuscationSettings

Expand Down Expand Up @@ -210,6 +212,10 @@ public enum AccessibilityIdentifier: Equatable {
// Multihop
case multihopSwitch

// WireGuard obfuscation settings
case wireGuardObfuscationUdpOverTcpTable
case wireGuardObfuscationShadowsocksTable

// Error
case unknown
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct ShadowsocksObfuscationSettingsView<VM>: View where VM: ShadowsocksObfusca
title: portString,
options: [WireGuardObfuscationShadowsocksPort.automatic],
value: $viewModel.value,
tableAccessibilityIdentifier: AccessibilityIdentifier.wireGuardObfuscationShadowsocksTable.asString,
itemDescription: { item in NSLocalizedString(
"SHADOWSOCKS_PORT_VALUE_\(item)",
tableName: "Shadowsocks",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct UDPOverTCPObfuscationSettingsView<VM>: View where VM: UDPOverTCPObfuscati
title: portString,
options: [WireGuardObfuscationUdpOverTcpPort.automatic, .port80, .port5001],
value: $viewModel.value,
tableAccessibilityIdentifier: AccessibilityIdentifier.wireGuardObfuscationUdpOverTcpTable.asString,
itemDescription: { item in NSLocalizedString(
"UDP_TCP_PORT_VALUE_\(item)",
tableName: "UdpToTcp",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
private let options: [OptionSpec]
var value: Binding<Value>
@State var initialValue: Value?
let tableAccessibilityIdentifier: String
let itemDescription: (Value) -> String
let itemAccessibilityIdentifier: (Value) -> String
let customFieldMode: CustomFieldMode

/// The configuration for the field for a custom value row
Expand All @@ -84,7 +84,6 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
// this row consists of a text field into which the user can enter a custom value, which may yield a valid Value. This has accompanying text, and functions to translate between text field contents and the Value. (The fromValue method only needs to give a non-nil value if its input is a custom value that could have come from this row.)
case custom(
label: String,
accessibilityIdentifier: String,
prompt: String,
legend: String?,
minInputWidth: CGFloat?,
Expand All @@ -103,15 +102,15 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
title: String,
optionSpecs: [OptionSpec.OptValue],
value: Binding<Value>,
tableAccessibilityIdentifier: String?,
itemDescription: ((Value) -> String)? = nil,
itemAccessibilityIdentifier: ((Value) -> String)? = nil,
customFieldMode: CustomFieldMode = .freeText
) {
self.title = title
self.options = optionSpecs.enumerated().map { OptionSpec(id: $0.offset, value: $0.element) }
self.value = value
self.itemDescription = itemDescription ?? { "\($0)" }
self.itemAccessibilityIdentifier = itemAccessibilityIdentifier ?? { "\($0)" }
self.tableAccessibilityIdentifier = tableAccessibilityIdentifier ?? "SingleChoiceList"
self.customFieldMode = customFieldMode
self.initialValue = value.wrappedValue
}
Expand All @@ -122,20 +121,20 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
/// - title: The title of the list, which is typically the name of the item being chosen.
/// - options: A list of `Value`s to be presented.
/// - itemDescription: An optional function that, when given a `Value`, returns the string representation to present in the list. If not provided, this will be generated naïvely using string interpolation.
/// - itemAccessibilityIdentifier: An optional function that, when given a `Value`, returns the accessibility identifier for the value's list item. If not provided, this will be generated naïvely using string interpolation.
init(
title: String,
options: [Value],
value: Binding<Value>,
tableAccessibilityIdentifier: String? = nil,
itemDescription: ((Value) -> String)? = nil,
itemAccessibilityIdentifier: ((Value) -> String)? = nil
) {
self.init(
title: title,
optionSpecs: options.map { .literal($0) },
value: value,
itemDescription: itemDescription,
itemAccessibilityIdentifier: itemAccessibilityIdentifier
tableAccessibilityIdentifier: tableAccessibilityIdentifier,
itemDescription: itemDescription
)
}

Expand All @@ -144,12 +143,11 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
/// - Parameters:
/// - title: The title of the list, which is typically the name of the item being chosen.
/// - options: A list of fixed `Value`s to be presented.
/// - tableAccessibilityIdentifier: an optional string value for the accessibility identifier of the table element enclosing the list. If not present, it will be "SingleChoiceList"
/// - itemDescription: An optional function that, when given a `Value`, returns the string representation to present in the list. If not provided, this will be generated naïvely using string interpolation. This is only used for the non-custom values.
/// - itemAccessibilityIdentifier: An optional function that, when given a `Value`, returns the accessibility identifier for the value's list item. If not provided, this will be generated naïvely using string interpolation.
/// - parseCustomValue: A function that attempts to parse the text entered into the text field and produce a `Value` (typically the tagged custom value with an argument applied to it). If the text is not valid for a value, it should return `nil`
/// - formatCustomValue: A function that, when passed a `Value` containing user-entered custom data, formats that data into a string, which should match what the user would have entered. This function can expect to only be called for the custom value, and should return `nil` in the event of its argument not being a valid custom value.
/// - customLabel: The caption to display in the custom row, next to the text field.
/// - customAccessibilityIdentifier: The accessibility identifier to use for the custom row. If not provided, "customValue" will be used. The accessibility identifier for the text field will be this value with ".input" appended.
/// - customPrompt: The text to display, greyed, in the text field when it is empty. This also serves to set the width of the field, and should be right-padded with spaces as appropriate.
/// - customLegend: Optional text to display below the custom field, i.e., to explain sensible values
/// - customInputWidth: An optional minimum width (in pseudo-pixels) for the custom input field
Expand All @@ -159,12 +157,11 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
title: String,
options: [Value],
value: Binding<Value>,
tableAccessibilityIdentifier: String? = nil,
itemDescription: ((Value) -> String)? = nil,
itemAccessibilityIdentifier: ((Value) -> String)? = nil,
parseCustomValue: @escaping ((String) -> Value?),
formatCustomValue: @escaping ((Value) -> String?),
customLabel: String,
customAccessibilityIdentifier: String = "customValue",
customPrompt: String,
customLegend: String? = nil,
customInputMinWidth: CGFloat? = nil,
Expand All @@ -175,7 +172,6 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
title: title,
optionSpecs: options.map { .literal($0) } + [.custom(
label: customLabel,
accessibilityIdentifier: customAccessibilityIdentifier,
prompt: customPrompt,
legend: customLegend,
minInputWidth: customInputMinWidth,
Expand All @@ -184,8 +180,8 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
fromValue: formatCustomValue
)],
value: value,
tableAccessibilityIdentifier: tableAccessibilityIdentifier,
itemDescription: itemDescription,
itemAccessibilityIdentifier: itemAccessibilityIdentifier,
customFieldMode: customFieldMode
)
}
Expand Down Expand Up @@ -220,14 +216,12 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
customValueIsFocused = false
customValueInput = ""
}
.accessibilityIdentifier(itemAccessibilityIdentifier(item))
}

// Construct the one row with a custom input field for a custom value
// swiftlint:disable function_body_length
private func customRow(
label: String,
accessibilityIdentifier: String,
prompt: String,
inputWidth: CGFloat?,
maxInputLength: Int?,
Expand Down Expand Up @@ -308,7 +302,6 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
customValueInput = valueText
}
}
.accessibilityIdentifier(accessibilityIdentifier + ".input")
}
.onTapGesture {
if let v = toValue(customValueInput) {
Expand All @@ -317,7 +310,6 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
customValueIsFocused = true
}
}
.accessibilityIdentifier(accessibilityIdentifier)
}

// swiftlint:enable function_body_length
Expand All @@ -331,6 +323,10 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
}
.padding(.horizontal, UIMetrics.SettingsCell.layoutMargins.leading)
.padding(.vertical, 4)
.background(
Color(.secondaryColor)
)
.foregroundColor(Color(UIColor.Cell.titleTextColor))
}

var body: some View {
Expand All @@ -341,34 +337,41 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
}
.padding(EdgeInsets(UIMetrics.SettingsCell.layoutMargins))
.background(Color(UIColor.Cell.Background.normal))
ForEach(options) { opt in
switch opt.value {
case let .literal(v):
literalRow(v)
case let .custom(
label,
accessibilityIdentifier,
prompt,
legend,
inputWidth,
maxInputLength,
toValue,
fromValue
):
customRow(
label: label,
accessibilityIdentifier: accessibilityIdentifier,
prompt: prompt,
inputWidth: inputWidth,
maxInputLength: maxInputLength,
toValue: toValue,
fromValue: fromValue
)
if let legend {
subtitleRow(legend)
List {
Section {
ForEach(options) { opt in
switch opt.value {
case let .literal(v):
literalRow(v)
case let .custom(
label,
prompt,
legend,
inputWidth,
maxInputLength,
toValue,
fromValue
):
customRow(
label: label,
prompt: prompt,
inputWidth: inputWidth,
maxInputLength: maxInputLength,
toValue: toValue,
fromValue: fromValue
)
if let legend {
subtitleRow(legend)
}
}
}
}
.listRowInsets(.init()) // remove insets
}
.accessibilityIdentifier(tableAccessibilityIdentifier)
.listStyle(.plain)
.listRowSpacing(UIMetrics.TableView.separatorHeight)
.environment(\.defaultMinListRowHeight, 0)
Spacer()
}
.padding(EdgeInsets(top: 24, leading: 0, bottom: 0, trailing: 0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {

// swiftlint:disable:next cyclomatic_complexity function_body_length
func configureCell(_ cell: UITableViewCell, item: VPNSettingsDataSource.Item, indexPath: IndexPath) {
(cell as? SettingsCell)?.detailTitleLabel.accessibilityIdentifier = nil
switch item {
case .dnsSettings:
guard let cell = cell as? SettingsCell else { return }
Expand Down Expand Up @@ -154,6 +155,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
), viewModel.obfuscationUpdOverTcpPort.description)

cell.setAccessibilityIdentifier(item.accessibilityIdentifier)
cell.detailTitleLabel.setAccessibilityIdentifier(.wireGuardObfuscationUdpOverTcpPort)
cell.applySubCellStyling()

cell.buttonAction = { [weak self] in
Expand All @@ -178,6 +180,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
), viewModel.obfuscationShadowsocksPort.description)

cell.setAccessibilityIdentifier(item.accessibilityIdentifier)
cell.detailTitleLabel.setAccessibilityIdentifier(.wireGuardObfuscationShadowsocksPort)
cell.applySubCellStyling()

cell.buttonAction = { [weak self] in
Expand Down
Loading
Loading