@buivietphi/skill-mobile-mt 2.0.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.
@@ -0,0 +1,82 @@
1
+ # Release Checklist — Before Shipping
2
+
3
+ > Complete ALL items before submitting to App Store / Play Store.
4
+
5
+ ---
6
+
7
+ ## Code Quality
8
+
9
+ - [ ] No `console.log` / `print` / `NSLog` in production
10
+ - [ ] No hardcoded secrets or API keys
11
+ - [ ] No force unwraps without safety (`!` / `!!` / `as!`)
12
+ - [ ] No empty catch blocks
13
+ - [ ] No files > 500 lines
14
+ - [ ] All TODO/FIXME resolved or tracked
15
+
16
+ ## Security
17
+
18
+ - [ ] Tokens stored in SecureStore / Keychain / EncryptedSharedPreferences
19
+ - [ ] Deep links validated before navigation
20
+ - [ ] No sensitive data in logs
21
+ - [ ] SSL pinning enabled (if required)
22
+ - [ ] ProGuard / R8 enabled for Android release
23
+ - [ ] Debug mode stripped from release build
24
+
25
+ ## Performance
26
+
27
+ - [ ] Lists use FlatList / ListView.builder / LazyColumn (not ScrollView)
28
+ - [ ] Images optimized (resized, cached, lazy loaded)
29
+ - [ ] No main thread blocking
30
+ - [ ] No memory leaks (useEffect cleanup, dispose, [weak self])
31
+ - [ ] App launch time < 3 seconds
32
+ - [ ] Smooth scrolling (60fps)
33
+
34
+ ## UI / UX
35
+
36
+ - [ ] All states handled: loading, success, error, empty
37
+ - [ ] Touch targets >= 44pt (iOS) / 48dp (Android)
38
+ - [ ] Safe area / notch handled
39
+ - [ ] Keyboard dismissal handled
40
+ - [ ] Back button works (Android)
41
+ - [ ] Accessibility labels on interactive elements
42
+ - [ ] Dark mode works (if supported)
43
+
44
+ ## Platform-Specific
45
+
46
+ ### iOS
47
+ - [ ] Correct deployment target set
48
+ - [ ] App icons all sizes provided
49
+ - [ ] Privacy usage descriptions in Info.plist
50
+ - [ ] `pod install` runs clean
51
+ - [ ] Tested on physical device
52
+
53
+ ### Android
54
+ - [ ] `minSdk` / `targetSdk` correct
55
+ - [ ] Signing config for release
56
+ - [ ] Permissions declared in AndroidManifest.xml
57
+ - [ ] Tested on physical device
58
+ - [ ] 64-bit support included
59
+
60
+ ### React Native / Flutter
61
+ - [ ] Both iOS + Android tested
62
+ - [ ] Native modules compile on both platforms
63
+ - [ ] Bundle size acceptable
64
+ - [ ] Hermes enabled (RN) / tree-shaking (Flutter)
65
+
66
+ ## Testing
67
+
68
+ - [ ] Critical paths tested manually
69
+ - [ ] Edge cases: offline, slow network, empty data, expired token
70
+ - [ ] Fresh install tested (no cached data)
71
+ - [ ] Upgrade from previous version tested (if applicable)
72
+ - [ ] Crash reporting SDK integrated (Sentry / Firebase Crashlytics)
73
+
74
+ ## Final
75
+
76
+ - [ ] Version number bumped
77
+ - [ ] Changelog updated
78
+ - [ ] Screenshots / store listing updated (if UI changed)
79
+
80
+ ---
81
+
82
+ > Ship when ALL boxes are checked. Not before.
@@ -0,0 +1,332 @@
1
+ # Mobile Testing Strategy — Unit + E2E
2
+
3
+ > Test the right things at the right layer. Don't test implementation details.
4
+
5
+ ---
6
+
7
+ ## Testing Pyramid
8
+
9
+ ```
10
+ ╱ E2E ╲ ← Detox / Maestro / XCUITest / Espresso
11
+ ╱───────╲ ← Few, slow, high-confidence
12
+ ╱Integration╲ ← API mocking, navigation flows
13
+ ╱─────────────╲ ← Medium count
14
+ ╱ Unit Tests ╲ ← Jest / XCTest / JUnit
15
+ ╱─────────────────╲ ← Many, fast, cheap
16
+
17
+ RULE: Most tests = unit. E2E = critical flows only (login, checkout, onboarding).
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Unit Tests (Jest — React Native / TypeScript)
23
+
24
+ ```typescript
25
+ // Test hooks, not components
26
+ describe('useCart', () => {
27
+ it('adds item and updates total', () => {
28
+ const { result } = renderHook(() => useCart(), { wrapper: ReduxProvider })
29
+ act(() => { result.current.addItem(mockProduct) })
30
+ expect(result.current.total).toBe(mockProduct.price)
31
+ })
32
+
33
+ it('handles addToCart API error with rollback', async () => {
34
+ server.use(rest.post('/cart', (req, res, ctx) => res(ctx.status(500))))
35
+ const { result } = renderHook(() => useCart(), { wrapper: ReduxProvider })
36
+ await act(async () => { await result.current.addItem(mockProduct) })
37
+ expect(result.current.items).toHaveLength(0) // rolled back
38
+ expect(result.current.error).toBeTruthy()
39
+ })
40
+ })
41
+
42
+ // Test Redux slices directly
43
+ describe('cartSlice', () => {
44
+ it('sets loading state on fetchCart.pending', () => {
45
+ const state = cartReducer(initialState, fetchCart.pending('', undefined))
46
+ expect(state.status).toBe('loading')
47
+ })
48
+ })
49
+ ```
50
+
51
+ **Rules:**
52
+ - Test business logic (hooks, services, slices) — NOT component layout
53
+ - Mock API calls with `msw` (Mock Service Worker)
54
+ - 4 states per feature: loading / success / error / empty
55
+
56
+ ---
57
+
58
+ ## E2E Testing — Detox (React Native)
59
+
60
+ > Best for: React Native apps, full native bridge testing.
61
+
62
+ ### Setup
63
+
64
+ ```bash
65
+ # Install
66
+ npm install --save-dev detox @config/detox
67
+
68
+ # iOS build (required before tests)
69
+ detox build --configuration ios.sim.debug
70
+
71
+ # Run tests
72
+ detox test --configuration ios.sim.debug
73
+ detox test --configuration android.emu.debug
74
+ ```
75
+
76
+ ### Config (`detox.config.js`)
77
+
78
+ ```js
79
+ module.exports = {
80
+ testRunner: {
81
+ args: { '$0': 'jest', config: 'e2e/jest.config.js' },
82
+ jest: { setupTimeout: 120000 }
83
+ },
84
+ apps: {
85
+ 'ios.debug': { type: 'ios.app', binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/MyApp.app', build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build' },
86
+ 'android.debug': { type: 'android.apk', binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug' }
87
+ },
88
+ devices: {
89
+ simulator: { type: 'ios.simulator', device: { type: 'iPhone 15' } },
90
+ emulator: { type: 'android.emulator', device: { avdName: 'Pixel_7_API_34' } }
91
+ },
92
+ configurations: {
93
+ 'ios.sim.debug': { device: 'simulator', app: 'ios.debug' },
94
+ 'android.emu.debug': { device: 'emulator', app: 'android.debug' }
95
+ }
96
+ }
97
+ ```
98
+
99
+ ### Write Detox Tests
100
+
101
+ ```typescript
102
+ // e2e/login.test.ts
103
+ describe('Login Flow', () => {
104
+ beforeAll(async () => {
105
+ await device.launchApp({ newInstance: true })
106
+ })
107
+
108
+ beforeEach(async () => {
109
+ await device.reloadReactNative()
110
+ })
111
+
112
+ it('shows error on wrong password', async () => {
113
+ await element(by.id('email-input')).typeText('user@test.com')
114
+ await element(by.id('password-input')).typeText('wrongpass')
115
+ await element(by.id('login-button')).tap()
116
+ await expect(element(by.text('Invalid credentials'))).toBeVisible()
117
+ })
118
+
119
+ it('navigates to home on success', async () => {
120
+ await element(by.id('email-input')).typeText('user@test.com')
121
+ await element(by.id('password-input')).typeText('correctpass')
122
+ await element(by.id('login-button')).tap()
123
+ await expect(element(by.id('home-screen'))).toBeVisible()
124
+ })
125
+ })
126
+ ```
127
+
128
+ **testID rules:**
129
+ ```typescript
130
+ // Add testID to interactive elements
131
+ <TextInput testID="email-input" ... />
132
+ <TouchableOpacity testID="login-button" ... />
133
+ <View testID="home-screen" ... />
134
+ ```
135
+
136
+ **What to test with Detox:**
137
+ - Login / logout flow
138
+ - Onboarding (first-time user)
139
+ - Critical purchase / checkout path
140
+ - Push notification tap → navigation
141
+ - Deep link handling
142
+
143
+ **What NOT to test with Detox:** minor UI variations, loading spinners, animations.
144
+
145
+ ---
146
+
147
+ ## E2E Testing — Maestro (Cross-Platform)
148
+
149
+ > Best for: Simpler setup, works on React Native + Flutter + native iOS/Android.
150
+
151
+ ### Setup
152
+
153
+ ```bash
154
+ # macOS
155
+ brew tap mobile-dev-inc/tap
156
+ brew install maestro
157
+
158
+ # Run a flow
159
+ maestro test e2e/login.yaml
160
+ maestro test e2e/ # all flows in folder
161
+ ```
162
+
163
+ ### Write Maestro Flows (YAML)
164
+
165
+ ```yaml
166
+ # e2e/login.yaml
167
+ appId: com.myapp
168
+ ---
169
+ - launchApp:
170
+ clearState: true
171
+
172
+ - assertVisible: "Sign In"
173
+
174
+ - tapOn:
175
+ id: "email-input"
176
+ - inputText: "user@test.com"
177
+
178
+ - tapOn:
179
+ id: "password-input"
180
+ - inputText: "wrongpass"
181
+
182
+ - tapOn:
183
+ id: "login-button"
184
+
185
+ - assertVisible: "Invalid credentials"
186
+ ```
187
+
188
+ ```yaml
189
+ # e2e/checkout.yaml
190
+ appId: com.myapp
191
+ ---
192
+ - launchApp
193
+ - tapOn: "Products"
194
+ - tapOn:
195
+ index: 0 # first product
196
+ - tapOn: "Add to Cart"
197
+ - tapOn: "Checkout"
198
+ - assertVisible: "Order Confirmed"
199
+ ```
200
+
201
+ ### Maestro Cloud CI
202
+
203
+ ```bash
204
+ # Run on real devices in Maestro Cloud
205
+ maestro cloud --apiKey $MAESTRO_API_KEY e2e/
206
+ ```
207
+
208
+ **Maestro vs Detox:**
209
+
210
+ | | Maestro | Detox |
211
+ |--|---------|-------|
212
+ | Setup | Minutes | Hours |
213
+ | YAML / Code | YAML | TypeScript |
214
+ | Cross-platform | ✅ RN + Flutter + native | RN only |
215
+ | Speed | Slower | Faster |
216
+ | Power | Medium | High |
217
+ | CI integration | Maestro Cloud | Self-hosted |
218
+
219
+ **Use Maestro when:** simple flows, cross-platform team, quick setup.
220
+ **Use Detox when:** complex interactions, React Native only, full control.
221
+
222
+ ---
223
+
224
+ ## Flutter Testing
225
+
226
+ ### Unit + Widget Tests
227
+
228
+ ```dart
229
+ // Unit test — business logic
230
+ test('CartBloc adds item correctly', () {
231
+ final bloc = CartBloc(cartRepository: MockCartRepository());
232
+ bloc.add(AddToCart(product: mockProduct));
233
+ expectLater(bloc.stream, emits(CartLoaded(items: [mockProduct])));
234
+ });
235
+
236
+ // Widget test — UI
237
+ testWidgets('ProductCard shows title and price', (tester) async {
238
+ await tester.pumpWidget(MaterialApp(
239
+ home: ProductCard(product: mockProduct),
240
+ ));
241
+ expect(find.text(mockProduct.title), findsOneWidget);
242
+ expect(find.text('\$${mockProduct.price}'), findsOneWidget);
243
+ });
244
+ ```
245
+
246
+ ### Integration Test (Flutter Driver replacement)
247
+
248
+ ```dart
249
+ // integration_test/login_test.dart
250
+ void main() {
251
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
252
+
253
+ testWidgets('login flow', (tester) async {
254
+ app.main();
255
+ await tester.pumpAndSettle();
256
+
257
+ await tester.enterText(find.byKey(Key('email')), 'user@test.com');
258
+ await tester.enterText(find.byKey(Key('password')), 'pass123');
259
+ await tester.tap(find.byKey(Key('login-button')));
260
+ await tester.pumpAndSettle();
261
+
262
+ expect(find.byKey(Key('home-screen')), findsOneWidget);
263
+ });
264
+ }
265
+ ```
266
+
267
+ ```bash
268
+ # Run on simulator
269
+ flutter test integration_test/
270
+
271
+ # Run on real device
272
+ flutter test integration_test/ -d <device-id>
273
+ ```
274
+
275
+ ---
276
+
277
+ ## iOS — XCUITest
278
+
279
+ ```swift
280
+ func testLoginFlow() throws {
281
+ let app = XCUIApplication()
282
+ app.launch()
283
+
284
+ let emailField = app.textFields["email-input"]
285
+ emailField.tap()
286
+ emailField.typeText("user@test.com")
287
+
288
+ let passwordField = app.secureTextFields["password-input"]
289
+ passwordField.tap()
290
+ passwordField.typeText("pass123")
291
+
292
+ app.buttons["login-button"].tap()
293
+
294
+ XCTAssertTrue(app.otherElements["home-screen"].waitForExistence(timeout: 5))
295
+ }
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Android — Espresso
301
+
302
+ ```kotlin
303
+ @Test
304
+ fun testLoginFlow() {
305
+ onView(withId(R.id.emailInput))
306
+ .perform(typeText("user@test.com"), closeSoftKeyboard())
307
+ onView(withId(R.id.passwordInput))
308
+ .perform(typeText("pass123"), closeSoftKeyboard())
309
+ onView(withId(R.id.loginButton)).perform(click())
310
+ onView(withId(R.id.homeScreen)).check(matches(isDisplayed()))
311
+ }
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Anti-Patterns
317
+
318
+ ```
319
+ ❌ Testing implementation details (internal state, private methods)
320
+ ❌ Testing every UI pixel (visual regression belongs in Storybook/Percy)
321
+ ❌ E2E for every edge case (unit test those)
322
+ ❌ Skipping testID on interactive elements
323
+ ❌ Running Detox without a stable test build
324
+ ❌ Using sleep() instead of waitFor()
325
+
326
+ ✅ Test user flows, not code internals
327
+ ✅ E2E for critical paths: login, purchase, onboarding
328
+ ✅ Unit test all business logic (hooks, slices, services)
329
+ ✅ Mock API calls in unit tests
330
+ ✅ Add testID to every tappable + input element
331
+ ✅ waitFor() over sleep() in Detox
332
+ ```