react-native-klarna-inapp-sdk 2.4.9 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/android/build.gradle +3 -1
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/KlarnaMobileSDKPackage.java +5 -1
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/checkout/KlarnaCheckoutViewManager.java +4 -10
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/WebViewResizeObserver.java +28 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/event/ComponentEventSender.java +5 -1
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/ResizeObserverWrapperView.java +2 -2
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/WrapperView.java +2 -2
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/YogaLayoutHelper.java +39 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewCallback.kt +30 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewEventSender.kt +80 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewManager.java +544 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventHandler.java +30 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventSender.java +64 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewManager.java +356 -0
- package/android/src/newarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaExpressCheckoutViewSpec.java +31 -0
- package/android/src/newarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaOSMViewSpec.java +31 -0
- package/android/src/oldarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaExpressCheckoutViewSpec.java +36 -0
- package/android/src/oldarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaOSMViewSpec.java +32 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/KlarnaMobileSDKPackageTest.java +5 -1
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/common/ui/YogaLayoutHelperTest.java +79 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewManagerTest.java +259 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventHandlerTest.java +136 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventSenderTest.java +184 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewManagerTest.java +385 -0
- package/ios/RNKlarnaMobileSDK.xcodeproj/project.pbxproj +55 -1
- package/ios/Sources/KlarnaExpressCheckoutViewManager.h +5 -0
- package/ios/Sources/KlarnaExpressCheckoutViewManager.mm +39 -0
- package/ios/Sources/KlarnaOSMViewManager.h +5 -0
- package/ios/Sources/KlarnaOSMViewManager.mm +59 -0
- package/ios/Sources/view/KlarnaExpressCheckoutButtonDelegateProxy.swift +145 -0
- package/ios/Sources/view/KlarnaExpressCheckoutViewWrapper.h +41 -0
- package/ios/Sources/view/KlarnaOSMViewWrapper.h +48 -0
- package/ios/Sources/view/newarch/KlarnaExpressCheckoutViewWrapper.mm +228 -0
- package/ios/Sources/view/newarch/KlarnaOSMViewWrapper.mm +294 -0
- package/ios/Sources/view/newarch/KlarnaStandaloneWebViewWrapper.mm +20 -13
- package/ios/Sources/view/oldarch/KlarnaExpressCheckoutViewWrapper.mm +222 -0
- package/ios/Sources/view/oldarch/KlarnaOSMViewWrapper.mm +273 -0
- package/ios/Sources/view/oldarch/KlarnaStandaloneWebViewWrapper.mm +11 -13
- package/ios/Tests/KlarnaExpressCheckoutViewWrapperTests.m +391 -0
- package/ios/Tests/KlarnaOSMViewWrapperTests.m +450 -0
- package/ios/Tests/KlarnaStandaloneWebViewWrapperTests.m +105 -0
- package/lib/commonjs/KlarnaExpressCheckoutView.js +75 -0
- package/lib/commonjs/KlarnaExpressCheckoutView.js.map +1 -0
- package/lib/commonjs/KlarnaOSMView.js +94 -0
- package/lib/commonjs/KlarnaOSMView.js.map +1 -0
- package/lib/commonjs/index.js +99 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/KlarnaExpressCheckoutViewNativeComponent.js +11 -0
- package/lib/commonjs/specs/KlarnaExpressCheckoutViewNativeComponent.js.map +1 -0
- package/lib/commonjs/specs/KlarnaOSMViewNativeComponent.js +16 -0
- package/lib/commonjs/specs/KlarnaOSMViewNativeComponent.js.map +1 -0
- package/lib/commonjs/types/button/KlarnaButtonShape.js +14 -0
- package/lib/commonjs/types/button/KlarnaButtonShape.js.map +1 -0
- package/lib/commonjs/types/button/KlarnaButtonStyle.js +13 -0
- package/lib/commonjs/types/button/KlarnaButtonStyle.js.map +1 -0
- package/lib/commonjs/types/button/KlarnaButtonTheme.js +14 -0
- package/lib/commonjs/types/button/KlarnaButtonTheme.js.map +1 -0
- package/lib/commonjs/types/common/KlarnaExpressCheckoutSessionOptions.js +9 -0
- package/lib/commonjs/types/common/KlarnaExpressCheckoutSessionOptions.js.map +1 -0
- package/lib/commonjs/types/common/KlarnaOSMPlacementKey.js +2 -0
- package/lib/commonjs/types/common/KlarnaOSMPlacementKey.js.map +1 -0
- package/lib/commonjs/types/common/KlarnaTheme.js +14 -0
- package/lib/commonjs/types/common/KlarnaTheme.js.map +1 -0
- package/lib/module/KlarnaExpressCheckoutView.js +64 -0
- package/lib/module/KlarnaExpressCheckoutView.js.map +1 -0
- package/lib/module/KlarnaOSMView.js +84 -0
- package/lib/module/KlarnaOSMView.js.map +1 -0
- package/lib/module/index.js +9 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/KlarnaExpressCheckoutViewNativeComponent.js +3 -0
- package/lib/module/specs/KlarnaExpressCheckoutViewNativeComponent.js.map +1 -0
- package/lib/module/specs/KlarnaOSMViewNativeComponent.js +7 -0
- package/lib/module/specs/KlarnaOSMViewNativeComponent.js.map +1 -0
- package/lib/module/types/button/KlarnaButtonShape.js +7 -0
- package/lib/module/types/button/KlarnaButtonShape.js.map +1 -0
- package/lib/module/types/button/KlarnaButtonStyle.js +6 -0
- package/lib/module/types/button/KlarnaButtonStyle.js.map +1 -0
- package/lib/module/types/button/KlarnaButtonTheme.js +7 -0
- package/lib/module/types/button/KlarnaButtonTheme.js.map +1 -0
- package/lib/module/types/common/KlarnaExpressCheckoutSessionOptions.js +2 -0
- package/lib/module/types/common/KlarnaExpressCheckoutSessionOptions.js.map +1 -0
- package/lib/module/types/common/KlarnaOSMPlacementKey.js +2 -0
- package/lib/module/types/common/KlarnaOSMPlacementKey.js.map +1 -0
- package/lib/module/types/common/KlarnaTheme.js +7 -0
- package/lib/module/types/common/KlarnaTheme.js.map +1 -0
- package/lib/typescript/KlarnaExpressCheckoutView.d.ts +47 -0
- package/lib/typescript/KlarnaExpressCheckoutView.d.ts.map +1 -0
- package/lib/typescript/KlarnaOSMView.d.ts +36 -0
- package/lib/typescript/KlarnaOSMView.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +9 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/specs/KlarnaExpressCheckoutViewNativeComponent.d.ts +45 -0
- package/lib/typescript/specs/KlarnaExpressCheckoutViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/specs/KlarnaOSMViewNativeComponent.d.ts +35 -0
- package/lib/typescript/specs/KlarnaOSMViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/types/button/KlarnaButtonShape.d.ts +6 -0
- package/lib/typescript/types/button/KlarnaButtonShape.d.ts.map +1 -0
- package/lib/typescript/types/button/KlarnaButtonStyle.d.ts +5 -0
- package/lib/typescript/types/button/KlarnaButtonStyle.d.ts.map +1 -0
- package/lib/typescript/types/button/KlarnaButtonTheme.d.ts +6 -0
- package/lib/typescript/types/button/KlarnaButtonTheme.d.ts.map +1 -0
- package/lib/typescript/types/common/KlarnaExpressCheckoutSessionOptions.d.ts +10 -0
- package/lib/typescript/types/common/KlarnaExpressCheckoutSessionOptions.d.ts.map +1 -0
- package/lib/typescript/types/common/KlarnaOSMPlacementKey.d.ts +2 -0
- package/lib/typescript/types/common/KlarnaOSMPlacementKey.d.ts.map +1 -0
- package/lib/typescript/types/common/KlarnaTheme.d.ts +6 -0
- package/lib/typescript/types/common/KlarnaTheme.d.ts.map +1 -0
- package/package.json +8 -3
- package/react-native-klarna-inapp-sdk.podspec +2 -1
- package/src/KlarnaExpressCheckoutView.tsx +169 -0
- package/src/KlarnaOSMView.tsx +157 -0
- package/src/index.tsx +9 -0
- package/src/specs/KlarnaExpressCheckoutViewNativeComponent.ts +66 -0
- package/src/specs/KlarnaOSMViewNativeComponent.ts +50 -0
- package/src/types/button/KlarnaButtonShape.ts +5 -0
- package/src/types/button/KlarnaButtonStyle.ts +4 -0
- package/src/types/button/KlarnaButtonTheme.ts +5 -0
- package/src/types/common/KlarnaExpressCheckoutSessionOptions.ts +13 -0
- package/src/types/common/KlarnaOSMPlacementKey.ts +3 -0
- package/src/types/common/KlarnaTheme.ts +5 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
//
|
|
2
|
+
// KlarnaExpressCheckoutViewWrapperTests.m
|
|
3
|
+
// RNKlarnaMobileSDKTests
|
|
4
|
+
//
|
|
5
|
+
// Tests for the old-arch KlarnaExpressCheckoutViewWrapper (UIView-based).
|
|
6
|
+
//
|
|
7
|
+
// Coverage:
|
|
8
|
+
// • Initial state after -init
|
|
9
|
+
// • All React-Native prop setters store their values
|
|
10
|
+
// • -didSetProps: calls -resetButton then -createButtonIfNeeded when button
|
|
11
|
+
// already exists, or just -createButtonIfNeeded on first render
|
|
12
|
+
// • -resetButton removes the button subview, nils the reference, clears
|
|
13
|
+
// the isButtonCreated flag
|
|
14
|
+
// • -createButtonIfNeeded exits early (no SDK call) when isButtonCreated==YES
|
|
15
|
+
// • -didMoveToWindow with nil window resets the recreation guard
|
|
16
|
+
// • Delegate-proxy handler closures forward events to the React Native blocks
|
|
17
|
+
//
|
|
18
|
+
|
|
19
|
+
#import <XCTest/XCTest.h>
|
|
20
|
+
#import <OCMock/OCMock.h>
|
|
21
|
+
#import <KlarnaMobileSDK/KlarnaMobileSDK.h>
|
|
22
|
+
#import <KlarnaMobileSDK/KlarnaMobileSDK-Swift.h>
|
|
23
|
+
|
|
24
|
+
#import "../Sources/view/KlarnaExpressCheckoutViewWrapper.h"
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Expose private state for white-box testing.
|
|
28
|
+
// delegateProxy is typed as `id` to avoid importing the Swift bridge header
|
|
29
|
+
// into the test target; handler blocks are accessed via KVC below.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
@interface KlarnaExpressCheckoutViewWrapper ()
|
|
32
|
+
|
|
33
|
+
@property (nonatomic, strong) KlarnaExpressCheckoutButton *expressCheckoutButton;
|
|
34
|
+
@property (nonatomic, strong) id delegateProxy;
|
|
35
|
+
@property (nonatomic, assign) BOOL isButtonCreated;
|
|
36
|
+
|
|
37
|
+
- (void)createButtonIfNeeded;
|
|
38
|
+
- (void)resetButton;
|
|
39
|
+
|
|
40
|
+
@end
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
@interface KlarnaExpressCheckoutViewWrapperTests : XCTestCase
|
|
45
|
+
|
|
46
|
+
@property (nonatomic, strong) KlarnaExpressCheckoutViewWrapper *wrapper;
|
|
47
|
+
/// OCMPartialMock wrapping wrapper — used to stub / verify internal method calls.
|
|
48
|
+
@property (nonatomic, strong) id wrapperMock;
|
|
49
|
+
|
|
50
|
+
@end
|
|
51
|
+
|
|
52
|
+
@implementation KlarnaExpressCheckoutViewWrapperTests
|
|
53
|
+
|
|
54
|
+
- (void)setUp {
|
|
55
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
56
|
+
self.wrapper = w;
|
|
57
|
+
self.wrapperMock = OCMPartialMock(w);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
- (void)tearDown {
|
|
61
|
+
[self.wrapperMock stopMocking];
|
|
62
|
+
self.wrapperMock = nil;
|
|
63
|
+
self.wrapper = nil;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
#pragma mark - Initial State
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
- (void)test_initialState_buttonNotCreated {
|
|
71
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
72
|
+
XCTAssertFalse(w.isButtonCreated);
|
|
73
|
+
XCTAssertNil(w.expressCheckoutButton);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
- (void)test_initialState_delegateProxyIsConfigured {
|
|
77
|
+
// setupDelegateProxy must run during -init and install both handlers.
|
|
78
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
79
|
+
XCTAssertNotNil(w.delegateProxy);
|
|
80
|
+
|
|
81
|
+
void (^authHandler)(NSDictionary *) = [w.delegateProxy valueForKey:@"onAuthorizedHandler"];
|
|
82
|
+
void (^errHandler)(NSString *, NSString *, BOOL) = [w.delegateProxy valueForKey:@"onErrorHandler"];
|
|
83
|
+
XCTAssertNotNil(authHandler, @"onAuthorizedHandler must be wired during -init");
|
|
84
|
+
XCTAssertNotNil(errHandler, @"onErrorHandler must be wired during -init");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)test_initialState_autoFinalizeDefaultsToYES {
|
|
88
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
89
|
+
XCTAssertTrue(w.autoFinalize);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)test_initialState_collectShippingAddressDefaultsToNO {
|
|
93
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
94
|
+
XCTAssertFalse(w.collectShippingAddress);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
#pragma mark - Prop Storage
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
- (void)test_setClientId_storesValue {
|
|
102
|
+
[self.wrapper setClientId:@"client-abc"];
|
|
103
|
+
XCTAssertEqualObjects(self.wrapper.clientId, @"client-abc");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (void)test_setClientToken_storesValue {
|
|
107
|
+
[self.wrapper setClientToken:@"token-xyz"];
|
|
108
|
+
XCTAssertEqualObjects(self.wrapper.clientToken, @"token-xyz");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
- (void)test_setReturnUrl_storesValue {
|
|
112
|
+
[self.wrapper setReturnUrl:@"testapp://checkout/return"];
|
|
113
|
+
XCTAssertEqualObjects(self.wrapper.returnUrl, @"testapp://checkout/return");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
- (void)test_setLocale_storesValue {
|
|
117
|
+
[self.wrapper setLocale:@"sv-SE"];
|
|
118
|
+
XCTAssertEqualObjects(self.wrapper.locale, @"sv-SE");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
- (void)test_setEnvironment_storesValue {
|
|
122
|
+
[self.wrapper setEnvironment:@"playground"];
|
|
123
|
+
XCTAssertEqualObjects(self.wrapper.environment, @"playground");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
- (void)test_setRegion_storesValue {
|
|
127
|
+
[self.wrapper setRegion:@"na"];
|
|
128
|
+
XCTAssertEqualObjects(self.wrapper.region, @"na");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
- (void)test_setTheme_storesValue {
|
|
132
|
+
[self.wrapper setTheme:@"dark"];
|
|
133
|
+
XCTAssertEqualObjects(self.wrapper.theme, @"dark");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
- (void)test_setShape_storesValue {
|
|
137
|
+
[self.wrapper setShape:@"pill"];
|
|
138
|
+
XCTAssertEqualObjects(self.wrapper.shape, @"pill");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
- (void)test_setButtonStyle_storesValue {
|
|
142
|
+
[self.wrapper setButtonStyle:@"outlined"];
|
|
143
|
+
XCTAssertEqualObjects(self.wrapper.buttonStyle, @"outlined");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (void)test_setAutoFinalize_storesNO {
|
|
147
|
+
[self.wrapper setAutoFinalize:NO];
|
|
148
|
+
XCTAssertFalse(self.wrapper.autoFinalize);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
- (void)test_setCollectShippingAddress_storesYES {
|
|
152
|
+
[self.wrapper setCollectShippingAddress:YES];
|
|
153
|
+
XCTAssertTrue(self.wrapper.collectShippingAddress);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
- (void)test_setSessionData_storesValue {
|
|
157
|
+
[self.wrapper setSessionData:@"{\"key\":\"val\"}"];
|
|
158
|
+
XCTAssertEqualObjects(self.wrapper.sessionData, @"{\"key\":\"val\"}");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
#pragma mark - didSetProps
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
- (void)test_didSetProps_callsCreateButtonIfNeeded_onFirstRender {
|
|
166
|
+
// isButtonCreated starts as NO — only createButtonIfNeeded should be invoked.
|
|
167
|
+
OCMExpect([self.wrapperMock createButtonIfNeeded]);
|
|
168
|
+
|
|
169
|
+
[self.wrapper didSetProps:@[]];
|
|
170
|
+
|
|
171
|
+
OCMVerifyAll(self.wrapperMock);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
- (void)test_didSetProps_whenButtonAlreadyCreated_resetsBeforeRecreating {
|
|
175
|
+
// Every prop change while a button exists must remove it before recreating.
|
|
176
|
+
self.wrapper.isButtonCreated = YES;
|
|
177
|
+
|
|
178
|
+
// resetButton must run with its real implementation (clearing the flag).
|
|
179
|
+
OCMExpect([self.wrapperMock resetButton]).andForwardToRealObject();
|
|
180
|
+
// createButtonIfNeeded is a no-op here to avoid real SDK calls.
|
|
181
|
+
OCMExpect([self.wrapperMock createButtonIfNeeded]);
|
|
182
|
+
|
|
183
|
+
[self.wrapper didSetProps:@[@"clientToken"]];
|
|
184
|
+
|
|
185
|
+
OCMVerifyAll(self.wrapperMock);
|
|
186
|
+
// resetButton must have cleared the flag to prevent stale-button leaks.
|
|
187
|
+
XCTAssertFalse(self.wrapper.isButtonCreated);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
- (void)test_didSetProps_whenButtonNotCreated_skipsReset {
|
|
191
|
+
// resetButton must NOT be called on the initial render pass.
|
|
192
|
+
self.wrapper.isButtonCreated = NO;
|
|
193
|
+
|
|
194
|
+
OCMReject([self.wrapperMock resetButton]);
|
|
195
|
+
OCMExpect([self.wrapperMock createButtonIfNeeded]);
|
|
196
|
+
|
|
197
|
+
[self.wrapper didSetProps:@[]];
|
|
198
|
+
|
|
199
|
+
OCMVerifyAll(self.wrapperMock);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
#pragma mark - resetButton
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
- (void)test_resetButton_clearsIsButtonCreated {
|
|
207
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
208
|
+
w.isButtonCreated = YES;
|
|
209
|
+
|
|
210
|
+
[w resetButton];
|
|
211
|
+
|
|
212
|
+
XCTAssertFalse(w.isButtonCreated);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
- (void)test_resetButton_nilsButtonReference {
|
|
216
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
217
|
+
id mockButton = OCMClassMock([KlarnaExpressCheckoutButton class]);
|
|
218
|
+
w.expressCheckoutButton = mockButton;
|
|
219
|
+
w.isButtonCreated = YES;
|
|
220
|
+
|
|
221
|
+
[w resetButton];
|
|
222
|
+
|
|
223
|
+
XCTAssertNil(w.expressCheckoutButton);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
- (void)test_resetButton_callsRemoveFromSuperview {
|
|
227
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
228
|
+
id mockButton = OCMClassMock([KlarnaExpressCheckoutButton class]);
|
|
229
|
+
OCMExpect([mockButton removeFromSuperview]);
|
|
230
|
+
w.expressCheckoutButton = mockButton;
|
|
231
|
+
w.isButtonCreated = YES;
|
|
232
|
+
|
|
233
|
+
[w resetButton];
|
|
234
|
+
|
|
235
|
+
OCMVerifyAll(mockButton);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
- (void)test_resetButton_withNoExistingButton_doesNotCrash {
|
|
239
|
+
// Called when isButtonCreated==NO and expressCheckoutButton==nil — must be safe.
|
|
240
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
241
|
+
XCTAssertNoThrow([w resetButton]);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
#pragma mark - createButtonIfNeeded guard
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
- (void)test_createButtonIfNeeded_whenAlreadyCreated_isNoOp {
|
|
249
|
+
// The early-exit guard must prevent double-creation on rapid successive calls.
|
|
250
|
+
// If it were to fall through it would attempt real SDK initialisation and crash.
|
|
251
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
252
|
+
w.isButtonCreated = YES;
|
|
253
|
+
|
|
254
|
+
[w createButtonIfNeeded];
|
|
255
|
+
|
|
256
|
+
XCTAssertTrue(w.isButtonCreated, @"isButtonCreated must stay YES — guard failed");
|
|
257
|
+
XCTAssertNil(w.expressCheckoutButton, @"no button should be created by a no-op path");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
#pragma mark - didMoveToWindow Lifecycle
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
- (void)test_didMoveToWindow_withNilWindow_resetsIsButtonCreated {
|
|
265
|
+
// Removal from the view hierarchy must clear the recreation guard so that
|
|
266
|
+
// re-insertion triggers a fresh button build.
|
|
267
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
268
|
+
w.isButtonCreated = YES;
|
|
269
|
+
|
|
270
|
+
// Wrapper is not attached to any UIWindow → window == nil.
|
|
271
|
+
[w didMoveToWindow];
|
|
272
|
+
|
|
273
|
+
XCTAssertFalse(w.isButtonCreated);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
- (void)test_didMoveToWindow_withNilWindow_clearsButtonReference {
|
|
277
|
+
// resetButton is called when the view is detached, which nils the button
|
|
278
|
+
// reference and clears the recreation guard.
|
|
279
|
+
KlarnaExpressCheckoutViewWrapper *w = [KlarnaExpressCheckoutViewWrapper new];
|
|
280
|
+
id mockButton = OCMClassMock([KlarnaExpressCheckoutButton class]);
|
|
281
|
+
w.expressCheckoutButton = mockButton;
|
|
282
|
+
w.isButtonCreated = YES;
|
|
283
|
+
|
|
284
|
+
[w didMoveToWindow];
|
|
285
|
+
|
|
286
|
+
XCTAssertNil(w.expressCheckoutButton);
|
|
287
|
+
XCTAssertFalse(w.isButtonCreated);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
#pragma mark - Delegate Proxy Event Forwarding
|
|
292
|
+
//
|
|
293
|
+
// The proxy handler blocks (installed by -setupDelegateProxy) are accessed via
|
|
294
|
+
// KVC to avoid importing the Swift bridge header into the test target.
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
/// Simulates the SDK calling the authorized delegate method by invoking the
|
|
298
|
+
/// closure that -setupDelegateProxy installed on the delegate proxy.
|
|
299
|
+
- (void)simulateAuthorizedEvent:(NSDictionary *)response {
|
|
300
|
+
void (^handler)(NSDictionary *) = [self.wrapper.delegateProxy valueForKey:@"onAuthorizedHandler"];
|
|
301
|
+
XCTAssertNotNil(handler, @"onAuthorizedHandler must be wired up by setupDelegateProxy");
|
|
302
|
+
handler(response);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// Simulates the SDK calling the error delegate method.
|
|
306
|
+
- (void)simulateErrorEventWithName:(NSString *)name
|
|
307
|
+
message:(NSString *)message
|
|
308
|
+
isFatal:(BOOL)isFatal {
|
|
309
|
+
void (^handler)(NSString *, NSString *, BOOL) =
|
|
310
|
+
[self.wrapper.delegateProxy valueForKey:@"onErrorHandler"];
|
|
311
|
+
XCTAssertNotNil(handler, @"onErrorHandler must be wired up by setupDelegateProxy");
|
|
312
|
+
handler(name, message, isFatal);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
- (void)test_onAuthorized_forwardsFullResponseToReactBlock {
|
|
316
|
+
__block NSDictionary *receivedBody = nil;
|
|
317
|
+
[self.wrapper setOnAuthorized:^(NSDictionary *body) {
|
|
318
|
+
receivedBody = body;
|
|
319
|
+
}];
|
|
320
|
+
|
|
321
|
+
NSDictionary *authResponse = @{
|
|
322
|
+
@"showForm": @YES,
|
|
323
|
+
@"approved": @YES,
|
|
324
|
+
@"finalizedRequired": @NO,
|
|
325
|
+
@"clientToken": @"ct-123",
|
|
326
|
+
@"authorizationToken": @"at-456",
|
|
327
|
+
@"sessionId": @"sid-789",
|
|
328
|
+
@"collectedShippingAddress": @"",
|
|
329
|
+
@"merchantReference1": @"ref1",
|
|
330
|
+
@"merchantReference2": @"ref2",
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
[self simulateAuthorizedEvent:authResponse];
|
|
334
|
+
|
|
335
|
+
XCTAssertNotNil(receivedBody);
|
|
336
|
+
NSDictionary *result = receivedBody[@"authorizationResponse"];
|
|
337
|
+
XCTAssertEqualObjects(result[@"clientToken"], @"ct-123");
|
|
338
|
+
XCTAssertEqualObjects(result[@"authorizationToken"], @"at-456");
|
|
339
|
+
XCTAssertEqualObjects(result[@"sessionId"], @"sid-789");
|
|
340
|
+
XCTAssertEqualObjects(result[@"approved"], @YES);
|
|
341
|
+
XCTAssertEqualObjects(result[@"showForm"], @YES);
|
|
342
|
+
XCTAssertEqualObjects(result[@"finalizedRequired"], @NO);
|
|
343
|
+
XCTAssertEqualObjects(result[@"merchantReference1"], @"ref1");
|
|
344
|
+
XCTAssertEqualObjects(result[@"merchantReference2"], @"ref2");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
- (void)test_onAuthorized_withNilCallback_doesNotCrash {
|
|
348
|
+
// The handler must guard against nil onAuthorized to prevent a crash when
|
|
349
|
+
// the JS side has not registered a listener yet.
|
|
350
|
+
XCTAssertNoThrow([self simulateAuthorizedEvent:@{}]);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
- (void)test_onError_forwardsFatalErrorToReactBlock {
|
|
354
|
+
__block NSDictionary *receivedBody = nil;
|
|
355
|
+
[self.wrapper setOnError:^(NSDictionary *body) {
|
|
356
|
+
receivedBody = body;
|
|
357
|
+
}];
|
|
358
|
+
|
|
359
|
+
[self simulateErrorEventWithName:@"NetworkError"
|
|
360
|
+
message:@"Request timed out"
|
|
361
|
+
isFatal:YES];
|
|
362
|
+
|
|
363
|
+
XCTAssertNotNil(receivedBody);
|
|
364
|
+
NSDictionary *error = receivedBody[@"error"];
|
|
365
|
+
XCTAssertEqualObjects(error[@"name"], @"NetworkError");
|
|
366
|
+
XCTAssertEqualObjects(error[@"message"], @"Request timed out");
|
|
367
|
+
XCTAssertEqualObjects(error[@"isFatal"], @YES);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
- (void)test_onError_nonFatalError_propagatesIsFatalNO {
|
|
371
|
+
__block NSDictionary *receivedBody = nil;
|
|
372
|
+
[self.wrapper setOnError:^(NSDictionary *body) {
|
|
373
|
+
receivedBody = body;
|
|
374
|
+
}];
|
|
375
|
+
|
|
376
|
+
[self simulateErrorEventWithName:@"ValidationError"
|
|
377
|
+
message:@"Invalid card number"
|
|
378
|
+
isFatal:NO];
|
|
379
|
+
|
|
380
|
+
XCTAssertEqualObjects(receivedBody[@"error"][@"isFatal"], @NO);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
- (void)test_onError_withNilCallback_doesNotCrash {
|
|
384
|
+
// The handler must guard against nil onError to prevent a crash when the
|
|
385
|
+
// JS side has not registered a listener yet.
|
|
386
|
+
XCTAssertNoThrow([self simulateErrorEventWithName:@"err"
|
|
387
|
+
message:@"msg"
|
|
388
|
+
isFatal:NO]);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
@end
|