Skip to content

Commit 6cf58a1

Browse files
authored
Merge pull request #20 from GoodRequest/realtime
Realtime validation
2 parents 7482156 + 1f75a76 commit 6cf58a1

File tree

16 files changed

+763
-357
lines changed

16 files changed

+763
-357
lines changed

GoodSwiftUI-Sample/GoodSwiftUI-Sample.xcodeproj/project.pbxproj

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
/* End PBXBuildFile section */
1313

1414
/* Begin PBXContainerItemProxy section */
15-
3FBFD65D2D41194000E6E35A /* PBXContainerItemProxy */ = {
15+
0942DDE22D91CB61006E7B2D /* PBXContainerItemProxy */ = {
1616
isa = PBXContainerItemProxy;
1717
containerPortal = 5D740EA129B0BE0100975B8C /* Project object */;
1818
proxyType = 1;
@@ -22,7 +22,8 @@
2222
/* End PBXContainerItemProxy section */
2323

2424
/* Begin PBXFileReference section */
25-
3FBFD6572D41194000E6E35A /* sourcery_iOSUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = sourcery_iOSUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
25+
0942DDDC2D91CB61006E7B2D /* GoodSwiftUI-UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "GoodSwiftUI-UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
26+
094FDF232D91D1CB00DB03E3 /* GoodSwiftUI-Sample.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "GoodSwiftUI-Sample.xctestplan"; sourceTree = "<group>"; };
2627
5D740EA929B0BE0100975B8C /* GoodSwiftUI-Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "GoodSwiftUI-Sample.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2728
5D740EC329B0BE5F00975B8C /* GoodSwiftUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = GoodSwiftUI; path = ..; sourceTree = "<group>"; };
2829
78062C8D2CEDD3BE00281C04 /* GRButtonSampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRButtonSampleView.swift; sourceTree = "<group>"; };
@@ -40,12 +41,12 @@
4041
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
4142

4243
/* Begin PBXFileSystemSynchronizedRootGroup section */
44+
0942DDDD2D91CB61006E7B2D /* GoodSwiftUI-UITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "GoodSwiftUI-UITests"; sourceTree = "<group>"; };
4345
3F19A5DC2D41278600C91600 /* GoodSwiftUI-Sample */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (3F19A5E92D41278600C91600 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = "GoodSwiftUI-Sample"; sourceTree = "<group>"; };
44-
3FBFD6582D41194000E6E35A /* sample_iOSUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = sample_iOSUITests; sourceTree = "<group>"; };
4546
/* End PBXFileSystemSynchronizedRootGroup section */
4647

4748
/* Begin PBXFrameworksBuildPhase section */
48-
3FBFD6542D41194000E6E35A /* Frameworks */ = {
49+
0942DDD92D91CB61006E7B2D /* Frameworks */ = {
4950
isa = PBXFrameworksBuildPhase;
5051
buildActionMask = 2147483647;
5152
files = (
@@ -76,9 +77,10 @@
7677
5D740EA029B0BE0100975B8C = {
7778
isa = PBXGroup;
7879
children = (
80+
094FDF232D91D1CB00DB03E3 /* GoodSwiftUI-Sample.xctestplan */,
7981
5D740EC229B0BE5F00975B8C /* Packages */,
8082
3F19A5DC2D41278600C91600 /* GoodSwiftUI-Sample */,
81-
3FBFD6582D41194000E6E35A /* sample_iOSUITests */,
83+
0942DDDD2D91CB61006E7B2D /* GoodSwiftUI-UITests */,
8284
5D740EAA29B0BE0100975B8C /* Products */,
8385
3FA663A22D6F164F00269137 /* Recovered References */,
8486
);
@@ -88,7 +90,7 @@
8890
isa = PBXGroup;
8991
children = (
9092
5D740EA929B0BE0100975B8C /* GoodSwiftUI-Sample.app */,
91-
3FBFD6572D41194000E6E35A /* sourcery_iOSUITests.xctest */,
93+
0942DDDC2D91CB61006E7B2D /* GoodSwiftUI-UITests.xctest */,
9294
);
9395
name = Products;
9496
sourceTree = "<group>";
@@ -104,27 +106,27 @@
104106
/* End PBXGroup section */
105107

106108
/* Begin PBXNativeTarget section */
107-
3FBFD6562D41194000E6E35A /* sourcery_iOSUITests */ = {
109+
0942DDDB2D91CB61006E7B2D /* GoodSwiftUI-UITests */ = {
108110
isa = PBXNativeTarget;
109-
buildConfigurationList = 3FBFD65F2D41194000E6E35A /* Build configuration list for PBXNativeTarget "sourcery_iOSUITests" */;
111+
buildConfigurationList = 0942DDE42D91CB61006E7B2D /* Build configuration list for PBXNativeTarget "GoodSwiftUI-UITests" */;
110112
buildPhases = (
111-
3FBFD6532D41194000E6E35A /* Sources */,
112-
3FBFD6542D41194000E6E35A /* Frameworks */,
113-
3FBFD6552D41194000E6E35A /* Resources */,
113+
0942DDD82D91CB61006E7B2D /* Sources */,
114+
0942DDD92D91CB61006E7B2D /* Frameworks */,
115+
0942DDDA2D91CB61006E7B2D /* Resources */,
114116
);
115117
buildRules = (
116118
);
117119
dependencies = (
118-
3FBFD65E2D41194000E6E35A /* PBXTargetDependency */,
120+
0942DDE32D91CB61006E7B2D /* PBXTargetDependency */,
119121
);
120122
fileSystemSynchronizedGroups = (
121-
3FBFD6582D41194000E6E35A /* sample_iOSUITests */,
123+
0942DDDD2D91CB61006E7B2D /* GoodSwiftUI-UITests */,
122124
);
123-
name = sourcery_iOSUITests;
125+
name = "GoodSwiftUI-UITests";
124126
packageProductDependencies = (
125127
);
126-
productName = sourcery_iOSUITests;
127-
productReference = 3FBFD6572D41194000E6E35A /* sourcery_iOSUITests.xctest */;
128+
productName = "GoodSwiftUI-UITests";
129+
productReference = 0942DDDC2D91CB61006E7B2D /* GoodSwiftUI-UITests.xctest */;
128130
productType = "com.apple.product-type.bundle.ui-testing";
129131
};
130132
5D740EA829B0BE0100975B8C /* GoodSwiftUI-Sample */ = {
@@ -158,11 +160,11 @@
158160
isa = PBXProject;
159161
attributes = {
160162
BuildIndependentTargetsInParallel = 1;
161-
LastSwiftUpdateCheck = 1600;
163+
LastSwiftUpdateCheck = 1620;
162164
LastUpgradeCheck = 1420;
163165
TargetAttributes = {
164-
3FBFD6562D41194000E6E35A = {
165-
CreatedOnToolsVersion = 16.0;
166+
0942DDDB2D91CB61006E7B2D = {
167+
CreatedOnToolsVersion = 16.2;
166168
TestTargetID = 5D740EA829B0BE0100975B8C;
167169
};
168170
5D740EA829B0BE0100975B8C = {
@@ -184,13 +186,13 @@
184186
projectRoot = "";
185187
targets = (
186188
5D740EA829B0BE0100975B8C /* GoodSwiftUI-Sample */,
187-
3FBFD6562D41194000E6E35A /* sourcery_iOSUITests */,
189+
0942DDDB2D91CB61006E7B2D /* GoodSwiftUI-UITests */,
188190
);
189191
};
190192
/* End PBXProject section */
191193

192194
/* Begin PBXResourcesBuildPhase section */
193-
3FBFD6552D41194000E6E35A /* Resources */ = {
195+
0942DDDA2D91CB61006E7B2D /* Resources */ = {
194196
isa = PBXResourcesBuildPhase;
195197
buildActionMask = 2147483647;
196198
files = (
@@ -207,7 +209,7 @@
207209
/* End PBXResourcesBuildPhase section */
208210

209211
/* Begin PBXSourcesBuildPhase section */
210-
3FBFD6532D41194000E6E35A /* Sources */ = {
212+
0942DDD82D91CB61006E7B2D /* Sources */ = {
211213
isa = PBXSourcesBuildPhase;
212214
buildActionMask = 2147483647;
213215
files = (
@@ -224,56 +226,66 @@
224226
/* End PBXSourcesBuildPhase section */
225227

226228
/* Begin PBXTargetDependency section */
227-
3FBFD65E2D41194000E6E35A /* PBXTargetDependency */ = {
229+
0942DDE32D91CB61006E7B2D /* PBXTargetDependency */ = {
228230
isa = PBXTargetDependency;
229231
target = 5D740EA829B0BE0100975B8C /* GoodSwiftUI-Sample */;
230-
targetProxy = 3FBFD65D2D41194000E6E35A /* PBXContainerItemProxy */;
232+
targetProxy = 0942DDE22D91CB61006E7B2D /* PBXContainerItemProxy */;
231233
};
232234
/* End PBXTargetDependency section */
233235

234236
/* Begin XCBuildConfiguration section */
235-
3FBFD6602D41194000E6E35A /* Debug */ = {
237+
0942DDE52D91CB61006E7B2D /* Debug */ = {
236238
isa = XCBuildConfiguration;
237239
buildSettings = {
238240
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
239241
CODE_SIGN_STYLE = Automatic;
240242
CURRENT_PROJECT_VERSION = 1;
241-
DEVELOPMENT_TEAM = P9F25DCA84;
243+
DEVELOPMENT_TEAM = FFZN8CA2AB;
242244
ENABLE_USER_SCRIPT_SANDBOXING = YES;
243245
GCC_C_LANGUAGE_STANDARD = gnu17;
244246
GENERATE_INFOPLIST_FILE = YES;
245247
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
246248
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
249+
MACOSX_DEPLOYMENT_TARGET = 15.0;
247250
MARKETING_VERSION = 1.0;
248-
PRODUCT_BUNDLE_IDENTIFIER = "com.goodrequest.sourcery-iOSUITests";
251+
PRODUCT_BUNDLE_IDENTIFIER = "com.goodrequest.GoodSwiftUI-UITests";
249252
PRODUCT_NAME = "$(TARGET_NAME)";
253+
SDKROOT = auto;
254+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
255+
SUPPORTS_MACCATALYST = NO;
250256
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
251257
SWIFT_EMIT_LOC_STRINGS = NO;
252258
SWIFT_VERSION = 5.0;
253-
TARGETED_DEVICE_FAMILY = "1,2";
259+
TARGETED_DEVICE_FAMILY = 1;
254260
TEST_TARGET_NAME = "GoodSwiftUI-Sample";
261+
XROS_DEPLOYMENT_TARGET = 2.2;
255262
};
256263
name = Debug;
257264
};
258-
3FBFD6612D41194000E6E35A /* Release */ = {
265+
0942DDE62D91CB61006E7B2D /* Release */ = {
259266
isa = XCBuildConfiguration;
260267
buildSettings = {
261268
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
262269
CODE_SIGN_STYLE = Automatic;
263270
CURRENT_PROJECT_VERSION = 1;
264-
DEVELOPMENT_TEAM = P9F25DCA84;
271+
DEVELOPMENT_TEAM = FFZN8CA2AB;
265272
ENABLE_USER_SCRIPT_SANDBOXING = YES;
266273
GCC_C_LANGUAGE_STANDARD = gnu17;
267274
GENERATE_INFOPLIST_FILE = YES;
268275
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
269276
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
277+
MACOSX_DEPLOYMENT_TARGET = 15.0;
270278
MARKETING_VERSION = 1.0;
271-
PRODUCT_BUNDLE_IDENTIFIER = "com.goodrequest.sourcery-iOSUITests";
279+
PRODUCT_BUNDLE_IDENTIFIER = "com.goodrequest.GoodSwiftUI-UITests";
272280
PRODUCT_NAME = "$(TARGET_NAME)";
281+
SDKROOT = auto;
282+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
283+
SUPPORTS_MACCATALYST = NO;
273284
SWIFT_EMIT_LOC_STRINGS = NO;
274285
SWIFT_VERSION = 5.0;
275-
TARGETED_DEVICE_FAMILY = "1,2";
286+
TARGETED_DEVICE_FAMILY = 1;
276287
TEST_TARGET_NAME = "GoodSwiftUI-Sample";
288+
XROS_DEPLOYMENT_TARGET = 2.2;
277289
};
278290
name = Release;
279291
};
@@ -452,11 +464,11 @@
452464
/* End XCBuildConfiguration section */
453465

454466
/* Begin XCConfigurationList section */
455-
3FBFD65F2D41194000E6E35A /* Build configuration list for PBXNativeTarget "sourcery_iOSUITests" */ = {
467+
0942DDE42D91CB61006E7B2D /* Build configuration list for PBXNativeTarget "GoodSwiftUI-UITests" */ = {
456468
isa = XCConfigurationList;
457469
buildConfigurations = (
458-
3FBFD6602D41194000E6E35A /* Debug */,
459-
3FBFD6612D41194000E6E35A /* Release */,
470+
0942DDE52D91CB61006E7B2D /* Debug */,
471+
0942DDE62D91CB61006E7B2D /* Release */,
460472
);
461473
defaultConfigurationIsVisible = 0;
462474
defaultConfigurationName = Release;

GoodSwiftUI-Sample/GoodSwiftUI-Sample.xcodeproj/xcshareddata/xcschemes/GoodSwiftUI-Sample.xcscheme

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,21 @@
2727
buildConfiguration = "Debug"
2828
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2929
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30-
shouldUseLaunchSchemeArgsEnv = "YES"
31-
shouldAutocreateTestPlan = "YES">
30+
shouldUseLaunchSchemeArgsEnv = "YES">
31+
<TestPlans>
32+
<TestPlanReference
33+
reference = "container:GoodSwiftUI-Sample.xctestplan"
34+
default = "YES">
35+
</TestPlanReference>
36+
</TestPlans>
3237
<Testables>
3338
<TestableReference
34-
skipped = "NO"
35-
parallelizable = "YES">
39+
skipped = "NO">
3640
<BuildableReference
3741
BuildableIdentifier = "primary"
38-
BlueprintIdentifier = "3FBFD6562D41194000E6E35A"
39-
BuildableName = "sourcery_iOSUITests.xctest"
40-
BlueprintName = "sourcery_iOSUITests"
42+
BlueprintIdentifier = "0942DDDB2D91CB61006E7B2D"
43+
BuildableName = "GoodSwiftUI-UITests.xctest"
44+
BlueprintName = "GoodSwiftUI-UITests"
4145
ReferencedContainer = "container:GoodSwiftUI-Sample.xcodeproj">
4246
</BuildableReference>
4347
</TestableReference>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"configurations" : [
3+
{
4+
"id" : "A9E859BF-4B7D-4824-A00F-E395EC0CB8EB",
5+
"name" : "Test Scheme Action",
6+
"options" : {
7+
8+
}
9+
}
10+
],
11+
"defaultOptions" : {
12+
"targetForVariableExpansion" : {
13+
"containerPath" : "container:GoodSwiftUI-Sample.xcodeproj",
14+
"identifier" : "5D740EA829B0BE0100975B8C",
15+
"name" : "GoodSwiftUI-Sample"
16+
}
17+
},
18+
"testTargets" : [
19+
{
20+
"target" : {
21+
"containerPath" : "container:GoodSwiftUI-Sample.xcodeproj",
22+
"identifier" : "0942DDDB2D91CB61006E7B2D",
23+
"name" : "GoodSwiftUI-UITests"
24+
}
25+
}
26+
],
27+
"version" : 1
28+
}

GoodSwiftUI-Sample/GoodSwiftUI-Sample/Screens/InputFieldSampleView.swift

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct InputFieldSampleView: View {
1515

1616
case notFilip
1717
case pinTooShort
18+
case pinTooLong
1819

1920
var errorDescription: String? {
2021
switch self {
@@ -23,6 +24,9 @@ struct InputFieldSampleView: View {
2324

2425
case .pinTooShort:
2526
"PIN code must be at least 6 numbers long"
27+
28+
case .pinTooLong:
29+
"PIN code is too long"
2630
}
2731
}
2832

@@ -119,11 +123,19 @@ extension InputFieldSampleView {
119123
// Focus state binding to advance focus from keyboard action button (Continue)
120124
.bindFocusState($focusState, to: .name)
121125
.disabled(!nameEnabled)
126+
127+
// Accessibility identifier for UI tests
128+
.accessibilityIdentifier("nameTextField")
122129
}
123130

124131
private var pinCodeInputField: some View {
125132
// Text field
126-
InputField(text: $password, title: "PIN code", hint: "At least 6 numbers")
133+
InputField(
134+
value: $password,
135+
format: PinCodeFormatStyle(),
136+
title: "PIN code",
137+
hint: "At least 6 numbers"
138+
)
127139

128140
// Multiple custom traits
129141
.inputFieldTraits(
@@ -138,11 +150,21 @@ extension InputFieldSampleView {
138150
password?.count ?? 0 >= 6
139151
}
140152
.failWith(error: RegistrationError.pinTooShort)
153+
.realtime()
154+
155+
Criterion { password in
156+
password?.count ?? 1000 <= 8
157+
}
158+
.failWith(error: RegistrationError.pinTooLong)
159+
.realtime()
141160
}
142161

143162
.validityGroup($validityGroup)
144163
.bindFocusState($focusState, to: .pin)
145164
.disabled(!passwordEnabled)
165+
166+
// Accessibility identifier for UI tests
167+
.accessibilityIdentifier("pinTextField")
146168
}
147169

148170
private var percentFormattedInputField: some View {
@@ -185,7 +207,8 @@ extension InputFieldSampleView {
185207
}
186208
}
187209
)
188-
.alert("Alert", isPresented: $showsRightAlert, actions: {})
210+
.alert("Right button alert", isPresented: $showsRightAlert, actions: {})
211+
.accessibilityIdentifier("customViewsInputField")
189212
}
190213

191214
private var validityGroups: some View {
@@ -270,6 +293,42 @@ extension InputFieldSampleView {
270293

271294
}
272295

296+
// MARK: - PinCodeFormatStyle
297+
298+
struct PinCodeFormatStyle: ParseableFormatStyle {
299+
300+
typealias Strategy = PinCodeParseStrategy
301+
typealias FormatInput = String
302+
typealias FormatOutput = String
303+
304+
/// Formats the input string by adding spaces..
305+
func format(_ value: String) -> String {
306+
let separator: Character = " "
307+
let stride: Int = if value.count <= 6 { 3 } else { 4 }
308+
309+
return String(value.enumerated().map {
310+
$0 > 0 && $0 % stride == 0 ? [separator, $1] : [$1]
311+
}.joined())
312+
}
313+
314+
var parseStrategy: PinCodeParseStrategy {
315+
PinCodeParseStrategy()
316+
}
317+
}
318+
319+
// MARK: - ParseStrategy
320+
321+
/// Parses a formatted identifier code by removing spaces before validation.
322+
struct PinCodeParseStrategy: ParseStrategy {
323+
typealias ParseInput = String
324+
typealias ParseOutput = String
325+
326+
func parse(_ value: String) throws -> String {
327+
return value.replacingOccurrences(of: " ", with: "") // Remove spaces before validation
328+
}
329+
}
330+
331+
273332
// MARK: - SwiftUI preview
274333

275334
#Preview {

0 commit comments

Comments
 (0)