@zigrivers/scaffold 3.7.0 → 3.8.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 +43 -3
- package/content/knowledge/library/library-api-design.md +306 -0
- package/content/knowledge/library/library-architecture.md +247 -0
- package/content/knowledge/library/library-bundling.md +244 -0
- package/content/knowledge/library/library-conventions.md +229 -0
- package/content/knowledge/library/library-dev-environment.md +220 -0
- package/content/knowledge/library/library-documentation.md +300 -0
- package/content/knowledge/library/library-project-structure.md +237 -0
- package/content/knowledge/library/library-requirements.md +173 -0
- package/content/knowledge/library/library-security.md +257 -0
- package/content/knowledge/library/library-testing.md +319 -0
- package/content/knowledge/library/library-type-definitions.md +284 -0
- package/content/knowledge/library/library-versioning.md +300 -0
- package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
- package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
- package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
- package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
- package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
- package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
- package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
- package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
- package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
- package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
- package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
- package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
- package/content/methodology/library-overlay.yml +67 -0
- package/content/methodology/mobile-app-overlay.yml +71 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +82 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +70 -0
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/config/schema.d.ts +592 -32
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +34 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config/schema.test.js +147 -1
- package/dist/config/schema.test.js.map +1 -1
- package/dist/core/assembly/overlay-loader.test.js +22 -0
- package/dist/core/assembly/overlay-loader.test.js.map +1 -1
- package/dist/e2e/project-type-overlays.test.d.ts +2 -1
- package/dist/e2e/project-type-overlays.test.d.ts.map +1 -1
- package/dist/e2e/project-type-overlays.test.js +302 -2
- package/dist/e2e/project-type-overlays.test.js.map +1 -1
- package/dist/types/config.d.ts +7 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/wizard/questions.d.ts +12 -1
- package/dist/wizard/questions.d.ts.map +1 -1
- package/dist/wizard/questions.js +56 -1
- package/dist/wizard/questions.js.map +1 -1
- package/dist/wizard/questions.test.js +89 -4
- package/dist/wizard/questions.test.js.map +1 -1
- package/dist/wizard/wizard.d.ts +9 -0
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +12 -1
- package/dist/wizard/wizard.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mobile-app-testing
|
|
3
|
+
description: Unit tests, UI tests (XCTest/Espresso/Detox), snapshot tests, accessibility testing, and test architecture for iOS and Android
|
|
4
|
+
topics: [mobile-app, testing, xctest, espresso, detox, snapshot-testing, accessibility-testing, unit-tests]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Mobile testing requires a multi-layer strategy: unit tests for business logic, integration tests for repository and network layers, UI tests for critical user flows, and snapshot tests for visual regression. The test pyramid applies — fast unit tests outnumber slow UI tests. Mobile UI tests are inherently fragile (timing, simulator state, animations) — structure them to minimize flakiness and run only the highest-value flows in CI.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
iOS testing uses XCTest for unit and UI tests, with third-party additions (Quick/Nimble for BDD, snapshot-testing for visual regression). Android uses JUnit4/5 + Mockito/MockK for unit tests, Espresso for UI tests (in-process), and optional Detox for cross-platform end-to-end testing. Snapshot tests catch unintended UI changes automatically. Test architecture follows the same clean separation as production code — inject fakes, not mocks, for stable tests. Run unit tests on every commit; UI tests on PR merge.
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### iOS Unit Testing (XCTest)
|
|
16
|
+
|
|
17
|
+
**Test file structure**
|
|
18
|
+
```swift
|
|
19
|
+
import XCTest
|
|
20
|
+
@testable import MyApp
|
|
21
|
+
|
|
22
|
+
final class UserProfileViewModelTests: XCTestCase {
|
|
23
|
+
var sut: UserProfileViewModel! // System Under Test
|
|
24
|
+
var mockRepository: MockUserRepository!
|
|
25
|
+
|
|
26
|
+
override func setUp() {
|
|
27
|
+
super.setUp()
|
|
28
|
+
mockRepository = MockUserRepository()
|
|
29
|
+
sut = UserProfileViewModel(repository: mockRepository)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override func tearDown() {
|
|
33
|
+
sut = nil
|
|
34
|
+
mockRepository = nil
|
|
35
|
+
super.tearDown()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func test_loadUser_success_setsUserOnState() async throws {
|
|
39
|
+
// Arrange
|
|
40
|
+
let expectedUser = User(id: "1", name: "Jane", email: "jane@example.com")
|
|
41
|
+
mockRepository.stubbedUser = expectedUser
|
|
42
|
+
|
|
43
|
+
// Act
|
|
44
|
+
await sut.loadUser(id: "1")
|
|
45
|
+
|
|
46
|
+
// Assert
|
|
47
|
+
XCTAssertEqual(sut.user, expectedUser)
|
|
48
|
+
XCTAssertFalse(sut.isLoading)
|
|
49
|
+
XCTAssertNil(sut.error)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func test_loadUser_failure_setsErrorOnState() async {
|
|
53
|
+
// Arrange
|
|
54
|
+
mockRepository.stubbedError = NetworkError.serverError(500)
|
|
55
|
+
|
|
56
|
+
// Act
|
|
57
|
+
await sut.loadUser(id: "1")
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
XCTAssertNil(sut.user)
|
|
61
|
+
XCTAssertNotNil(sut.error)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Protocol-based fakes over Mocks**
|
|
67
|
+
```swift
|
|
68
|
+
protocol UserRepository {
|
|
69
|
+
func fetchUser(id: String) async throws -> User
|
|
70
|
+
func updateUser(_ user: User) async throws
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Fake — implements the real behavior with in-memory data
|
|
74
|
+
final class FakeUserRepository: UserRepository {
|
|
75
|
+
var stubbedUser: User?
|
|
76
|
+
var stubbedError: Error?
|
|
77
|
+
var updatedUsers: [User] = []
|
|
78
|
+
|
|
79
|
+
func fetchUser(id: String) async throws -> User {
|
|
80
|
+
if let error = stubbedError { throw error }
|
|
81
|
+
return stubbedUser ?? User(id: id, name: "Test User", email: "test@example.com")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func updateUser(_ user: User) async throws {
|
|
85
|
+
if let error = stubbedError { throw error }
|
|
86
|
+
updatedUsers.append(user)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Fakes are preferable to generated mocks (Cuckoo, Mockingbird) because they compile without code generation, work in Swift Previews, and test realistic behavior rather than exact call sequences.
|
|
92
|
+
|
|
93
|
+
**Testing async code**
|
|
94
|
+
```swift
|
|
95
|
+
// Test async throws with async test methods (Swift concurrency)
|
|
96
|
+
func test_loadUser_callsRepository() async throws {
|
|
97
|
+
let result = try await sut.loadUser(id: "1")
|
|
98
|
+
XCTAssertEqual(result.id, "1")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Test Combine publishers
|
|
102
|
+
func test_isLoading_trueWhileLoading() {
|
|
103
|
+
var loadingStates: [Bool] = []
|
|
104
|
+
let expectation = expectation(description: "Loading state changes")
|
|
105
|
+
expectation.expectedFulfillmentCount = 3 // false → true → false
|
|
106
|
+
|
|
107
|
+
let cancellable = sut.$isLoading.sink { loadingStates.append($0) }
|
|
108
|
+
|
|
109
|
+
Task { await sut.loadUser(id: "1") }
|
|
110
|
+
|
|
111
|
+
wait(for: [expectation], timeout: 2.0)
|
|
112
|
+
XCTAssertEqual(loadingStates, [false, true, false])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Performance tests
|
|
116
|
+
func test_fetchAllUsers_performance() throws {
|
|
117
|
+
measure {
|
|
118
|
+
let _ = try! mockRepository.fetchAllUsers()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Android Unit Testing (JUnit + MockK)
|
|
124
|
+
|
|
125
|
+
**ViewModel unit test**
|
|
126
|
+
```kotlin
|
|
127
|
+
@OptIn(ExperimentalCoroutinesApi::class)
|
|
128
|
+
class UserProfileViewModelTest {
|
|
129
|
+
|
|
130
|
+
@get:Rule val mainDispatcherRule = MainDispatcherRule() // replaces Main dispatcher with test dispatcher
|
|
131
|
+
|
|
132
|
+
private val userRepository = mockk<UserRepository>()
|
|
133
|
+
private lateinit var viewModel: UserProfileViewModel
|
|
134
|
+
|
|
135
|
+
@BeforeEach
|
|
136
|
+
fun setUp() {
|
|
137
|
+
viewModel = UserProfileViewModel(userRepository)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@Test
|
|
141
|
+
fun `loadUser success sets user in state`() = runTest {
|
|
142
|
+
// Arrange
|
|
143
|
+
val expectedUser = User(id = "1", name = "Jane", email = "jane@example.com")
|
|
144
|
+
coEvery { userRepository.fetchUser("1") } returns Result.success(expectedUser)
|
|
145
|
+
|
|
146
|
+
// Act
|
|
147
|
+
viewModel.loadUser("1")
|
|
148
|
+
advanceUntilIdle()
|
|
149
|
+
|
|
150
|
+
// Assert
|
|
151
|
+
val state = viewModel.uiState.value
|
|
152
|
+
assertThat(state.user).isEqualTo(expectedUser)
|
|
153
|
+
assertThat(state.isLoading).isFalse()
|
|
154
|
+
assertThat(state.error).isNull()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@Test
|
|
158
|
+
fun `loadUser failure sets error in state`() = runTest {
|
|
159
|
+
coEvery { userRepository.fetchUser(any()) } returns Result.failure(IOException("Network error"))
|
|
160
|
+
|
|
161
|
+
viewModel.loadUser("1")
|
|
162
|
+
advanceUntilIdle()
|
|
163
|
+
|
|
164
|
+
assertThat(viewModel.uiState.value.error).isNotNull()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**MainDispatcherRule (required for ViewModel tests)**
|
|
170
|
+
```kotlin
|
|
171
|
+
class MainDispatcherRule(
|
|
172
|
+
private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
|
|
173
|
+
) : TestWatcher() {
|
|
174
|
+
override fun starting(description: Description) {
|
|
175
|
+
Dispatchers.setMain(dispatcher)
|
|
176
|
+
}
|
|
177
|
+
override fun finished(description: Description) {
|
|
178
|
+
Dispatchers.resetMain()
|
|
179
|
+
dispatcher.cleanupTestCoroutines()
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Repository integration test (with in-memory Room)**
|
|
185
|
+
```kotlin
|
|
186
|
+
@RunWith(AndroidJUnit4::class)
|
|
187
|
+
class UserRepositoryTest {
|
|
188
|
+
private lateinit var database: AppDatabase
|
|
189
|
+
private lateinit var userDao: UserDao
|
|
190
|
+
|
|
191
|
+
@Before
|
|
192
|
+
fun setUp() {
|
|
193
|
+
database = Room.inMemoryDatabaseBuilder(
|
|
194
|
+
ApplicationProvider.getApplicationContext(),
|
|
195
|
+
AppDatabase::class.java
|
|
196
|
+
).allowMainThreadQueries().build()
|
|
197
|
+
userDao = database.userDao()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@After
|
|
201
|
+
fun tearDown() {
|
|
202
|
+
database.close()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@Test
|
|
206
|
+
fun insertAndRetrieveUser() = runTest {
|
|
207
|
+
val user = UserEntity(id = "1", name = "Jane", email = "jane@example.com", updatedAt = 0L)
|
|
208
|
+
userDao.upsert(user)
|
|
209
|
+
val retrieved = userDao.observeUsers().first()
|
|
210
|
+
assertThat(retrieved).contains(user)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### UI Testing
|
|
216
|
+
|
|
217
|
+
**iOS XCUITest**
|
|
218
|
+
```swift
|
|
219
|
+
final class LoginUITests: XCTestCase {
|
|
220
|
+
var app: XCUIApplication!
|
|
221
|
+
|
|
222
|
+
override func setUpWithError() throws {
|
|
223
|
+
continueAfterFailure = false
|
|
224
|
+
app = XCUIApplication()
|
|
225
|
+
app.launchArguments = ["--uitesting", "--reset-state"]
|
|
226
|
+
app.launch()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func test_login_validCredentials_navigatesToHome() throws {
|
|
230
|
+
let emailField = app.textFields["login_email_field"]
|
|
231
|
+
XCTAssertTrue(emailField.waitForExistence(timeout: 5))
|
|
232
|
+
emailField.tap()
|
|
233
|
+
emailField.typeText("test@example.com")
|
|
234
|
+
|
|
235
|
+
let passwordField = app.secureTextFields["login_password_field"]
|
|
236
|
+
passwordField.tap()
|
|
237
|
+
passwordField.typeText("password123")
|
|
238
|
+
|
|
239
|
+
app.buttons["login_submit_button"].tap()
|
|
240
|
+
|
|
241
|
+
let homeTitle = app.staticTexts["home_screen_title"]
|
|
242
|
+
XCTAssertTrue(homeTitle.waitForExistence(timeout: 10))
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Accessibility identifiers:
|
|
248
|
+
```swift
|
|
249
|
+
// Set in SwiftUI
|
|
250
|
+
Button("Submit") { submitAction() }
|
|
251
|
+
.accessibilityIdentifier("login_submit_button")
|
|
252
|
+
|
|
253
|
+
// Set in UIKit
|
|
254
|
+
loginButton.accessibilityIdentifier = "login_submit_button"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Use `accessibilityIdentifier` for test targeting — never use display text or position-based queries which break on localization or layout changes.
|
|
258
|
+
|
|
259
|
+
**Android Espresso**
|
|
260
|
+
```kotlin
|
|
261
|
+
@RunWith(AndroidJUnit4::class)
|
|
262
|
+
class LoginInstrumentedTest {
|
|
263
|
+
|
|
264
|
+
@get:Rule val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
|
265
|
+
|
|
266
|
+
@Test
|
|
267
|
+
fun login_validCredentials_navigatesToHome() {
|
|
268
|
+
onView(withId(R.id.email_field))
|
|
269
|
+
.perform(typeText("test@example.com"), closeSoftKeyboard())
|
|
270
|
+
onView(withId(R.id.password_field))
|
|
271
|
+
.perform(typeText("password123"), closeSoftKeyboard())
|
|
272
|
+
onView(withId(R.id.submit_button)).perform(click())
|
|
273
|
+
|
|
274
|
+
onView(withId(R.id.home_screen_title))
|
|
275
|
+
.check(matches(isDisplayed()))
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Compose UI testing**
|
|
281
|
+
```kotlin
|
|
282
|
+
@RunWith(AndroidJUnit4::class)
|
|
283
|
+
class UserProfileScreenTest {
|
|
284
|
+
|
|
285
|
+
@get:Rule val composeTestRule = createComposeRule()
|
|
286
|
+
|
|
287
|
+
@Test
|
|
288
|
+
fun userCard_displaysName() {
|
|
289
|
+
val user = User(id = "1", name = "Jane Smith", email = "jane@example.com")
|
|
290
|
+
composeTestRule.setContent {
|
|
291
|
+
MyAppTheme { UserCard(user = user) }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
composeTestRule.onNodeWithText("Jane Smith").assertIsDisplayed()
|
|
295
|
+
composeTestRule.onNodeWithContentDescription("User avatar").assertExists()
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Snapshot Testing
|
|
301
|
+
|
|
302
|
+
**iOS: swift-snapshot-testing (Point-Free)**
|
|
303
|
+
```swift
|
|
304
|
+
import SnapshotTesting
|
|
305
|
+
|
|
306
|
+
final class UserCardSnapshotTests: XCTestCase {
|
|
307
|
+
func test_userCard_defaultState() {
|
|
308
|
+
let view = UserCardView(user: .fixture)
|
|
309
|
+
assertSnapshot(of: view, as: .image(on: .iPhone13Pro))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
func test_userCard_loadingState() {
|
|
313
|
+
let view = UserCardView(isLoading: true)
|
|
314
|
+
assertSnapshot(of: view, as: .image(on: .iPhone13Pro))
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
func test_userCard_darkMode() {
|
|
318
|
+
let view = UserCardView(user: .fixture)
|
|
319
|
+
assertSnapshot(of: view, as: .image(on: .iPhone13Pro, traits: .init(userInterfaceStyle: .dark)))
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Run snapshots in `record` mode once to generate reference images, then in normal mode to detect regressions. Commit reference images to git. Update references only for intentional UI changes.
|
|
325
|
+
|
|
326
|
+
**Android: Paparazzi (Cashapp)**
|
|
327
|
+
```kotlin
|
|
328
|
+
@RunWith(AndroidJUnit4::class)
|
|
329
|
+
class UserCardSnapshotTest {
|
|
330
|
+
@get:Rule val paparazzi = Paparazzi(deviceConfig = DeviceConfig.PIXEL_6)
|
|
331
|
+
|
|
332
|
+
@Test
|
|
333
|
+
fun userCard_defaultState() {
|
|
334
|
+
paparazzi.snapshot {
|
|
335
|
+
MyAppTheme {
|
|
336
|
+
UserCard(user = previewUser)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Paparazzi runs on the JVM without a device or emulator — fast, CI-friendly.
|
|
344
|
+
|
|
345
|
+
### Accessibility Testing
|
|
346
|
+
|
|
347
|
+
**iOS: Accessibility Inspector + XCTest**
|
|
348
|
+
```swift
|
|
349
|
+
func test_loginButton_hasAccessibilityLabel() {
|
|
350
|
+
let button = app.buttons["login_submit_button"]
|
|
351
|
+
XCTAssertTrue(button.exists)
|
|
352
|
+
XCTAssertFalse(button.label.isEmpty) // has accessibility label
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Automated accessibility audit:
|
|
357
|
+
```swift
|
|
358
|
+
func test_homeScreen_passesAccessibilityAudit() throws {
|
|
359
|
+
// iOS 17+: built-in accessibility audit
|
|
360
|
+
try app.performAccessibilityAudit()
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`performAccessibilityAudit()` checks: missing labels, insufficient contrast, small touch targets, and other WCAG violations automatically.
|
|
365
|
+
|
|
366
|
+
**Android: Accessibility Test Framework**
|
|
367
|
+
```kotlin
|
|
368
|
+
@RunWith(AndroidJUnit4::class)
|
|
369
|
+
class AccessibilityTest {
|
|
370
|
+
@get:Rule val rule = AccessibilityChecksRule() // auto-runs checks on every view interaction
|
|
371
|
+
|
|
372
|
+
@Test
|
|
373
|
+
fun loginScreen_allElementsAccessible() {
|
|
374
|
+
onView(withId(R.id.submit_button)).perform(click())
|
|
375
|
+
// AccessibilityChecksRule automatically fails on accessibility issues
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Compose accessibility testing:
|
|
381
|
+
```kotlin
|
|
382
|
+
composeTestRule.onNodeWithText("Submit")
|
|
383
|
+
.assertHasClickAction()
|
|
384
|
+
.assertContentDescriptionEquals("Submit order")
|
|
385
|
+
.assertIsEnabled()
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### CI Test Strategy
|
|
389
|
+
|
|
390
|
+
**What runs when**
|
|
391
|
+
- Every commit: unit tests (fast, < 60 seconds)
|
|
392
|
+
- Every PR: unit tests + instrumented tests on emulator (< 10 minutes)
|
|
393
|
+
- Every merge to main: full suite including UI tests and snapshot tests (< 30 minutes)
|
|
394
|
+
- Nightly: device farm run across full device matrix
|
|
395
|
+
|
|
396
|
+
**Handling flaky tests**
|
|
397
|
+
- Retry mechanism in CI: rerun failed tests up to 2 times before marking as failed
|
|
398
|
+
- iOS: `--retry-tests-on-failure` flag in `xcodebuild test`
|
|
399
|
+
- Android: `android { testOptions { unitTests { isReturnDefaultValues = true } } }`
|
|
400
|
+
- Track flaky tests in a flakiness registry — tests flagged as flaky do not block merges but get prioritized for fixing
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# methodology/library-overlay.yml
|
|
2
|
+
name: library
|
|
3
|
+
description: >
|
|
4
|
+
Library overlay — injects library domain knowledge into existing pipeline
|
|
5
|
+
steps for API design, bundling, type definitions, documentation, and
|
|
6
|
+
versioning patterns.
|
|
7
|
+
project-type: library
|
|
8
|
+
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
# knowledge-overrides
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# Map library knowledge entries into existing pipeline steps so that library
|
|
13
|
+
# domain expertise is injected during prompt assembly.
|
|
14
|
+
knowledge-overrides:
|
|
15
|
+
# Foundational
|
|
16
|
+
create-prd:
|
|
17
|
+
append: [library-requirements]
|
|
18
|
+
user-stories:
|
|
19
|
+
append: [library-requirements]
|
|
20
|
+
coding-standards:
|
|
21
|
+
append: [library-conventions, library-documentation]
|
|
22
|
+
project-structure:
|
|
23
|
+
append: [library-project-structure]
|
|
24
|
+
dev-env-setup:
|
|
25
|
+
append: [library-dev-environment]
|
|
26
|
+
git-workflow:
|
|
27
|
+
append: [library-versioning]
|
|
28
|
+
|
|
29
|
+
# Architecture & Design
|
|
30
|
+
system-architecture:
|
|
31
|
+
append: [library-architecture]
|
|
32
|
+
tech-stack:
|
|
33
|
+
append: [library-architecture, library-bundling, library-type-definitions]
|
|
34
|
+
adrs:
|
|
35
|
+
append: [library-architecture]
|
|
36
|
+
api-contracts:
|
|
37
|
+
append: [library-api-design]
|
|
38
|
+
security:
|
|
39
|
+
append: [library-security]
|
|
40
|
+
operations:
|
|
41
|
+
append: [library-versioning, library-documentation]
|
|
42
|
+
|
|
43
|
+
# Testing
|
|
44
|
+
tdd:
|
|
45
|
+
append: [library-testing]
|
|
46
|
+
add-e2e-testing:
|
|
47
|
+
append: [library-testing]
|
|
48
|
+
create-evals:
|
|
49
|
+
append: [library-testing]
|
|
50
|
+
story-tests:
|
|
51
|
+
append: [library-testing]
|
|
52
|
+
|
|
53
|
+
# Reviews (mirror authoring steps)
|
|
54
|
+
review-architecture:
|
|
55
|
+
append: [library-architecture]
|
|
56
|
+
review-api:
|
|
57
|
+
append: [library-api-design]
|
|
58
|
+
review-security:
|
|
59
|
+
append: [library-security]
|
|
60
|
+
review-operations:
|
|
61
|
+
append: [library-versioning, library-documentation]
|
|
62
|
+
review-testing:
|
|
63
|
+
append: [library-testing]
|
|
64
|
+
|
|
65
|
+
# Planning
|
|
66
|
+
implementation-plan:
|
|
67
|
+
append: [library-architecture]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# methodology/mobile-app-overlay.yml
|
|
2
|
+
name: mobile-app
|
|
3
|
+
description: >
|
|
4
|
+
Mobile app overlay — injects mobile domain knowledge into existing pipeline
|
|
5
|
+
steps for deployment, offline patterns, push notifications, distribution,
|
|
6
|
+
and platform-specific architecture.
|
|
7
|
+
project-type: mobile-app
|
|
8
|
+
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
# knowledge-overrides
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# Map mobile-app knowledge entries into existing pipeline steps so that mobile
|
|
13
|
+
# domain expertise is injected during prompt assembly.
|
|
14
|
+
knowledge-overrides:
|
|
15
|
+
# Foundational
|
|
16
|
+
create-prd:
|
|
17
|
+
append: [mobile-app-requirements]
|
|
18
|
+
user-stories:
|
|
19
|
+
append: [mobile-app-requirements]
|
|
20
|
+
coding-standards:
|
|
21
|
+
append: [mobile-app-conventions]
|
|
22
|
+
project-structure:
|
|
23
|
+
append: [mobile-app-project-structure]
|
|
24
|
+
dev-env-setup:
|
|
25
|
+
append: [mobile-app-dev-environment]
|
|
26
|
+
git-workflow:
|
|
27
|
+
append: [mobile-app-deployment]
|
|
28
|
+
|
|
29
|
+
# Architecture & Design
|
|
30
|
+
system-architecture:
|
|
31
|
+
append: [mobile-app-architecture, mobile-app-offline-patterns, mobile-app-push-notifications]
|
|
32
|
+
tech-stack:
|
|
33
|
+
append: [mobile-app-architecture, mobile-app-deployment]
|
|
34
|
+
adrs:
|
|
35
|
+
append: [mobile-app-architecture]
|
|
36
|
+
ux-spec:
|
|
37
|
+
append: [mobile-app-architecture]
|
|
38
|
+
design-system:
|
|
39
|
+
append: [mobile-app-conventions]
|
|
40
|
+
domain-modeling:
|
|
41
|
+
append: [mobile-app-offline-patterns]
|
|
42
|
+
security:
|
|
43
|
+
append: [mobile-app-security]
|
|
44
|
+
operations:
|
|
45
|
+
append: [mobile-app-deployment, mobile-app-distribution, mobile-app-observability]
|
|
46
|
+
|
|
47
|
+
# Testing
|
|
48
|
+
tdd:
|
|
49
|
+
append: [mobile-app-testing]
|
|
50
|
+
add-e2e-testing:
|
|
51
|
+
append: [mobile-app-testing]
|
|
52
|
+
create-evals:
|
|
53
|
+
append: [mobile-app-testing]
|
|
54
|
+
story-tests:
|
|
55
|
+
append: [mobile-app-testing]
|
|
56
|
+
|
|
57
|
+
# Reviews (mirror authoring steps)
|
|
58
|
+
review-architecture:
|
|
59
|
+
append: [mobile-app-architecture, mobile-app-offline-patterns, mobile-app-push-notifications]
|
|
60
|
+
review-ux:
|
|
61
|
+
append: [mobile-app-architecture]
|
|
62
|
+
review-security:
|
|
63
|
+
append: [mobile-app-security]
|
|
64
|
+
review-operations:
|
|
65
|
+
append: [mobile-app-deployment, mobile-app-distribution, mobile-app-observability]
|
|
66
|
+
review-testing:
|
|
67
|
+
append: [mobile-app-testing]
|
|
68
|
+
|
|
69
|
+
# Planning
|
|
70
|
+
implementation-plan:
|
|
71
|
+
append: [mobile-app-architecture]
|
|
@@ -34,6 +34,15 @@ interface InitArgs {
|
|
|
34
34
|
'cli-interactivity'?: string;
|
|
35
35
|
'cli-distribution'?: string[];
|
|
36
36
|
'cli-structured-output'?: boolean;
|
|
37
|
+
'lib-visibility'?: string;
|
|
38
|
+
'lib-runtime-target'?: string;
|
|
39
|
+
'lib-bundle-format'?: string;
|
|
40
|
+
'lib-type-definitions'?: boolean;
|
|
41
|
+
'lib-doc-level'?: string;
|
|
42
|
+
'mobile-platform'?: string;
|
|
43
|
+
'mobile-distribution'?: string;
|
|
44
|
+
'mobile-offline'?: string;
|
|
45
|
+
'mobile-push-notifications'?: boolean;
|
|
37
46
|
}
|
|
38
47
|
declare const initCommand: CommandModule<Record<string, unknown>, InitArgs>;
|
|
39
48
|
export default initCommand;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAQ,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAQ,MAAM,OAAO,CAAA;AAoChD,UAAU,QAAQ;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAEhC,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAEjC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,2BAA2B,CAAC,EAAE,OAAO,CAAA;CACtC;AAED,QAAA,MAAM,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAwgBjE,CAAA;AAED,eAAe,WAAW,CAAA"}
|