@maccesar/titools 2.0.6 → 2.2.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/{AGENTS-TEMPLATE.md → AGENTS-VERCEL-RESEARCH.md} +16 -18
- package/README.md +250 -168
- package/agents/{ti-researcher.md → ti-pro.md} +10 -10
- package/bin/titools.js +16 -12
- package/lib/cleanup.js +95 -0
- package/lib/commands/agents.js +146 -70
- package/lib/commands/skills.js +349 -0
- package/lib/commands/uninstall.js +189 -154
- package/lib/commands/update.js +104 -21
- package/lib/config.js +26 -20
- package/lib/downloader.js +1 -1
- package/lib/installer.js +37 -97
- package/lib/platform.js +9 -5
- package/lib/prompts/checkboxCancel.js +264 -0
- package/lib/prompts/selectCancel.js +204 -0
- package/lib/symlink.js +19 -7
- package/lib/utils.js +21 -31
- package/package.json +10 -10
- package/skills/alloy-guides/SKILL.md +8 -30
- package/skills/alloy-guides/references/CLI_TASKS.md +14 -24
- package/skills/alloy-guides/references/CONCEPTS.md +5 -25
- package/skills/alloy-guides/references/CONTROLLERS.md +2 -19
- package/skills/alloy-guides/references/MODELS.md +40 -10
- package/skills/alloy-guides/references/PURGETSS.md +1 -11
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +8 -14
- package/skills/alloy-guides/references/VIEWS_STYLES.md +14 -25
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +2 -8
- package/skills/alloy-guides/references/VIEWS_XML.md +29 -40
- package/skills/alloy-guides/references/WIDGETS.md +1 -17
- package/skills/alloy-howtos/SKILL.md +1 -22
- package/skills/alloy-howtos/references/best_practices.md +0 -17
- package/skills/alloy-howtos/references/cli_reference.md +1 -23
- package/skills/alloy-howtos/references/config_files.md +86 -15
- package/skills/alloy-howtos/references/custom_tags.md +14 -13
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +3 -22
- package/skills/alloy-howtos/references/samples.md +4 -14
- package/skills/purgetss/SKILL.md +11 -37
- package/skills/purgetss/references/EXAMPLES.md +106 -17
- package/skills/purgetss/references/animation-system.md +0 -67
- package/skills/purgetss/references/apply-directive.md +0 -18
- package/skills/purgetss/references/arbitrary-values.md +0 -25
- package/skills/purgetss/references/class-index.md +0 -46
- package/skills/purgetss/references/cli-commands.md +0 -41
- package/skills/purgetss/references/configurable-properties.md +0 -55
- package/skills/purgetss/references/custom-rules.md +0 -14
- package/skills/purgetss/references/customization-deep-dive.md +0 -49
- package/skills/purgetss/references/dynamic-component-creation.md +56 -37
- package/skills/purgetss/references/grid-layout.md +0 -32
- package/skills/purgetss/references/icon-fonts.md +0 -50
- package/skills/purgetss/references/installation-setup.md +61 -32
- package/skills/purgetss/references/migration-guide.md +127 -0
- package/skills/purgetss/references/opacity-modifier.md +0 -34
- package/skills/purgetss/references/performance-tips.md +261 -0
- package/skills/purgetss/references/platform-modifiers.md +0 -46
- package/skills/purgetss/references/smart-mappings.md +0 -12
- package/skills/purgetss/references/tikit-components.md +393 -0
- package/skills/purgetss/references/titanium-resets.md +1 -27
- package/skills/purgetss/references/ui-ux-design.md +467 -117
- package/skills/{alloy-expert → ti-expert}/SKILL.md +45 -54
- package/skills/ti-expert/references/alloy-builtins.md +387 -0
- package/skills/{alloy-expert → ti-expert}/references/alloy-structure.md +147 -40
- package/skills/ti-expert/references/anti-patterns.md +90 -0
- package/skills/ti-expert/references/cli-expert.md +109 -0
- package/skills/{alloy-expert → ti-expert}/references/code-conventions.md +182 -49
- package/skills/{alloy-expert → ti-expert}/references/controller-patterns.md +103 -93
- package/skills/{alloy-expert → ti-expert}/references/error-handling.md +12 -12
- package/skills/{alloy-expert → ti-expert}/references/examples.md +144 -85
- package/skills/{alloy-expert → ti-expert}/references/migration-patterns.md +38 -36
- package/skills/{alloy-expert → ti-expert}/references/patterns.md +8 -8
- package/skills/ti-expert/references/performance-listview.md +251 -0
- package/skills/{alloy-expert/references/performance-patterns.md → ti-expert/references/performance-optimization.md} +67 -283
- package/skills/{alloy-expert/references/security-patterns.md → ti-expert/references/security-device.md} +12 -295
- package/skills/ti-expert/references/security-fundamentals.md +284 -0
- package/skills/{alloy-expert → ti-expert}/references/state-management.md +10 -10
- package/skills/ti-expert/references/testing-e2e-ci.md +432 -0
- package/skills/ti-expert/references/testing-unit.md +433 -0
- package/skills/ti-expert/references/theming.md +394 -0
- package/skills/ti-guides/SKILL.md +1 -14
- package/skills/ti-guides/references/advanced-data-and-images.md +126 -10
- package/skills/ti-guides/references/app-distribution.md +48 -10
- package/skills/ti-guides/references/application-frameworks.md +9 -2
- package/skills/ti-guides/references/cli-reference.md +338 -82
- package/skills/ti-guides/references/coding-best-practices.md +14 -4
- package/skills/ti-guides/references/commonjs-advanced.md +103 -6
- package/skills/ti-guides/references/hello-world.md +5 -2
- package/skills/ti-guides/references/hyperloop-native-access.md +439 -46
- package/skills/ti-guides/references/javascript-primer.md +4 -3
- package/skills/ti-guides/references/resources.md +0 -1
- package/skills/ti-guides/references/style-and-conventions.md +57 -9
- package/skills/ti-guides/references/tiapp-config.md +48 -6
- package/skills/ti-howtos/SKILL.md +2 -27
- package/skills/ti-howtos/references/android-platform-deep-dives.md +41 -90
- package/skills/ti-howtos/references/automation-fastlane-appium.md +15 -14
- package/skills/ti-howtos/references/buffer-codec-streams.md +22 -22
- package/skills/ti-howtos/references/cross-platform-development.md +68 -37
- package/skills/ti-howtos/references/debugging-profiling.md +8 -53
- package/skills/ti-howtos/references/extending-titanium.md +12 -51
- package/skills/ti-howtos/references/google-maps-v2.md +30 -29
- package/skills/ti-howtos/references/ios-map-kit.md +19 -25
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +95 -283
- package/skills/ti-howtos/references/local-data-sources.md +45 -37
- package/skills/ti-howtos/references/location-and-maps.md +47 -28
- package/skills/ti-howtos/references/media-apis.md +84 -27
- package/skills/ti-howtos/references/notification-services.md +40 -89
- package/skills/ti-howtos/references/remote-data-sources.md +21 -36
- package/skills/ti-howtos/references/tutorials.md +79 -39
- package/skills/ti-howtos/references/using-modules.md +4 -30
- package/skills/ti-howtos/references/web-content-integration.md +28 -45
- package/skills/ti-howtos/references/webpack-build-pipeline.md +54 -18
- package/skills/ti-ui/SKILL.md +1 -21
- package/skills/ti-ui/references/accessibility-deep-dive.md +71 -25
- package/skills/ti-ui/references/animation-and-matrices.md +0 -47
- package/skills/ti-ui/references/application-structures.md +0 -46
- package/skills/ti-ui/references/custom-fonts-styling.md +9 -57
- package/skills/ti-ui/references/event-handling.md +32 -40
- package/skills/ti-ui/references/gestures.md +0 -40
- package/skills/ti-ui/references/icons-and-splash-screens.md +21 -44
- package/skills/ti-ui/references/layouts-and-positioning.md +47 -39
- package/skills/ti-ui/references/listviews-and-performance.md +187 -73
- package/skills/ti-ui/references/orientation.md +57 -71
- package/skills/ti-ui/references/platform-ui-android.md +114 -48
- package/skills/ti-ui/references/platform-ui-ios.md +63 -38
- package/skills/ti-ui/references/scrolling-views.md +14 -23
- package/skills/ti-ui/references/tableviews.md +6 -56
- package/lib/commands/install.js +0 -188
- package/skills/alloy-expert/references/anti-patterns.md +0 -133
- package/skills/alloy-expert/references/testing.md +0 -872
- package/skills/ti-guides/references/alloy-cli-advanced.md +0 -84
- package/skills/ti-guides/references/alloy-data-mastery.md +0 -29
- package/skills/ti-guides/references/alloy-widgets-and-themes.md +0 -19
- /package/skills/{alloy-expert → ti-expert}/assets/ControllerAutoCleanup.js +0 -0
- /package/skills/{alloy-expert → ti-expert}/references/contracts.md +0 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# Unit Testing Guide for Titanium + Alloy
|
|
2
|
+
|
|
3
|
+
> **OPTIONAL - Advanced Topic**
|
|
4
|
+
> This guide covers automated testing, which is OPTIONAL for Titanium/Alloy projects.
|
|
5
|
+
> Testing is useful for teams practicing CI/CD, projects with complex business logic, or refactoring confidence.
|
|
6
|
+
> If you prefer manual testing on device, you can safely skip this guide.
|
|
7
|
+
|
|
8
|
+
## What CAN Be Tested (Without Compiling)
|
|
9
|
+
|
|
10
|
+
**Pure JavaScript Logic:**
|
|
11
|
+
- Services (authService, navigationService, etc.)
|
|
12
|
+
- Helpers/Utils (formatters, validators, parsers)
|
|
13
|
+
- Business logic (calculations, data transformations)
|
|
14
|
+
- Models (with mocked database)
|
|
15
|
+
|
|
16
|
+
## What CANNOT Be Easily Tested
|
|
17
|
+
|
|
18
|
+
**Requires Compiled App:**
|
|
19
|
+
- Controllers (UI interactions)
|
|
20
|
+
- Views/XML (Titanium UI components)
|
|
21
|
+
- Native modules (device-specific features)
|
|
22
|
+
- End-to-end user flows (use Appium for this - see [testing-e2e-ci.md](testing-e2e-ci.md))
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Testing Philosophy
|
|
27
|
+
|
|
28
|
+
**Test behavior, not implementation.** Focus on what the code does from the outside, not how it achieves it internally.
|
|
29
|
+
|
|
30
|
+
## Project Structure
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
app/
|
|
34
|
+
├── specs/ # Test files
|
|
35
|
+
│ ├── unit/ # Unit tests
|
|
36
|
+
│ │ ├── services/ # Service layer tests
|
|
37
|
+
│ │ │ ├── authService.spec.js
|
|
38
|
+
│ │ │ └── tokenStorage.spec.js
|
|
39
|
+
│ │ ├── helpers/ # Helper function tests
|
|
40
|
+
│ │ │ └── i18n.spec.js
|
|
41
|
+
│ │ ├── utils/
|
|
42
|
+
│ │ │ └── validator.spec.js
|
|
43
|
+
│ │ └── models/ # Model tests (if using SQLite)
|
|
44
|
+
│ └── integration/ # Integration tests
|
|
45
|
+
│ ├── controllers/ # Controller flow tests
|
|
46
|
+
│ │ └── login.spec.js
|
|
47
|
+
│ └── api/ # API integration tests
|
|
48
|
+
├── lib/
|
|
49
|
+
│ ├── testing/ # Test utilities
|
|
50
|
+
│ │ ├── mocks.js # Mock factories
|
|
51
|
+
│ │ ├── helpers.js # Test helper functions
|
|
52
|
+
│ │ └── setup.js # Test environment setup
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Writing Testable Code
|
|
56
|
+
|
|
57
|
+
### Dependency Injection for Testability
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// BAD: Hard to test - direct dependency
|
|
61
|
+
exports.getUser = async function getUser(id) {
|
|
62
|
+
const response = await api.get(`/users/${id}`)
|
|
63
|
+
return response.data
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// GOOD: Testable - injectable dependency
|
|
67
|
+
exports.getUser = async function getUser(id, apiClient = defaultApiClient) {
|
|
68
|
+
const response = await apiClient.get(`/users/${id}`)
|
|
69
|
+
return response.data
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Test with mock
|
|
73
|
+
describe('getUser', () => {
|
|
74
|
+
it('should fetch user from API', async () => {
|
|
75
|
+
const mockApi = {
|
|
76
|
+
get: jasmine.createSpy('get').and.resolveTo({
|
|
77
|
+
data: { id: 1, name: 'Test User' }
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const user = await getUser(1, mockApi)
|
|
82
|
+
|
|
83
|
+
expect(mockApi.get).toHaveBeenCalledWith('/users/1')
|
|
84
|
+
expect(user.name).toBe('Test User')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Unit Testing Services
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// specs/unit/services/authService.spec.js
|
|
93
|
+
const { login, logout } = require('services/authService')
|
|
94
|
+
|
|
95
|
+
describe('AuthService', () => {
|
|
96
|
+
let mockApi
|
|
97
|
+
let mockTokenStorage
|
|
98
|
+
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
mockApi = {
|
|
101
|
+
post: jasmine.createSpy('post')
|
|
102
|
+
}
|
|
103
|
+
mockTokenStorage = {
|
|
104
|
+
save: jasmine.createSpy('save'),
|
|
105
|
+
get: jasmine.createSpy('get'),
|
|
106
|
+
clear: jasmine.createSpy('clear')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
jasmine.clock().install()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
afterEach(() => {
|
|
113
|
+
jasmine.clock().uninstall()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('login', () => {
|
|
117
|
+
it('should save token on successful login', async () => {
|
|
118
|
+
mockApi.post.and.resolveTo({
|
|
119
|
+
token: 'abc123',
|
|
120
|
+
user: { id: 1, name: 'Test User' }
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const result = await login('user@test.com', 'password', mockApi, mockTokenStorage)
|
|
124
|
+
|
|
125
|
+
expect(mockTokenStorage.save).toHaveBeenCalledWith('abc123')
|
|
126
|
+
expect(result).toEqual({ id: 1, name: 'Test User' })
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('should throw on network error', async () => {
|
|
130
|
+
mockApi.post.and.rejectWith(new Error('Network error'))
|
|
131
|
+
|
|
132
|
+
await expectAsync(
|
|
133
|
+
login('user@test.com', 'password', mockApi, mockTokenStorage)
|
|
134
|
+
).toBeRejectedWith('Network error')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should throw on invalid credentials', async () => {
|
|
138
|
+
const error = new Error()
|
|
139
|
+
error.status = 401
|
|
140
|
+
mockApi.post.and.rejectWith(error)
|
|
141
|
+
|
|
142
|
+
await expectAsync(
|
|
143
|
+
login('user@test.com', 'wrong', mockApi, mockTokenStorage)
|
|
144
|
+
).toBeRejected()
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Unit Testing Helpers
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
// specs/unit/helpers/i18n.spec.js
|
|
154
|
+
const { getPluralMessages } = require('helpers/i18n')
|
|
155
|
+
|
|
156
|
+
describe('i18n Helper', () => {
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
// Mock L() function
|
|
159
|
+
global.L = jasmine.createSpy('L').and.callFake((key) => {
|
|
160
|
+
const strings = {
|
|
161
|
+
'one_message': 'You have 1 message',
|
|
162
|
+
'many_messages': 'You have %d messages'
|
|
163
|
+
}
|
|
164
|
+
return strings[key] || key
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
describe('getPluralMessages', () => {
|
|
169
|
+
it('should return singular for count = 1', () => {
|
|
170
|
+
const result = getPluralMessages(1)
|
|
171
|
+
expect(result).toBe('You have 1 message')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('should return plural for count > 1', () => {
|
|
175
|
+
const result = getPluralMessages(5)
|
|
176
|
+
expect(result).toBe('You have 5 messages')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('should handle zero messages', () => {
|
|
180
|
+
const result = getPluralMessages(0)
|
|
181
|
+
expect(result).toBe('You have 0 messages')
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Integration Testing Controllers
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// specs/integration/controllers/login.spec.js
|
|
191
|
+
describe('Login Controller', () => {
|
|
192
|
+
let controller
|
|
193
|
+
let mockAuthService
|
|
194
|
+
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
mockAuthService = {
|
|
197
|
+
login: jasmine.createSpy('login')
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
controller = Alloy.createController('login')
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
afterEach(() => {
|
|
204
|
+
// Always cleanup
|
|
205
|
+
if (controller.cleanup) {
|
|
206
|
+
controller.cleanup()
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
describe('doLogin', () => {
|
|
211
|
+
it('should call authService with form data', async () => {
|
|
212
|
+
controller.emailTextField.value = 'user@test.com'
|
|
213
|
+
controller.passwordTextField.value = 'password123'
|
|
214
|
+
|
|
215
|
+
mockAuthService.login.and.resolveTo({ id: 1, name: 'User' })
|
|
216
|
+
|
|
217
|
+
await controller.doLogin()
|
|
218
|
+
|
|
219
|
+
expect(mockAuthService.login).toHaveBeenCalledWith(
|
|
220
|
+
'user@test.com',
|
|
221
|
+
'password123'
|
|
222
|
+
)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should show error on login failure', async () => {
|
|
226
|
+
controller.emailTextField.value = 'user@test.com'
|
|
227
|
+
controller.passwordTextField.value = 'wrong'
|
|
228
|
+
|
|
229
|
+
mockAuthService.login.and.rejectWith(new Error('Invalid credentials'))
|
|
230
|
+
|
|
231
|
+
await controller.doLogin()
|
|
232
|
+
|
|
233
|
+
expect(controller.errorLabel.text).toBe('Invalid credentials')
|
|
234
|
+
expect(controller.errorLabel.visible).toBe(true)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('should disable button during login', async () => {
|
|
238
|
+
const button = controller.loginButton
|
|
239
|
+
|
|
240
|
+
mockAuthService.login.and.callFake(() => {
|
|
241
|
+
expect(button.enabled).toBe(false)
|
|
242
|
+
return Promise.resolve({ id: 1 })
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
await controller.doLogin()
|
|
246
|
+
|
|
247
|
+
expect(button.enabled).toBe(true)
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
describe('cleanup', () => {
|
|
252
|
+
it('should remove event listeners', () => {
|
|
253
|
+
const spy = spyOn(Ti.App, 'removeEventListener')
|
|
254
|
+
|
|
255
|
+
controller.cleanup()
|
|
256
|
+
|
|
257
|
+
expect(controller._isCleanedUp).toBe(true)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('should destroy Alloy bindings', () => {
|
|
261
|
+
const destroySpy = spyOn(controller, '$').and.returnValue({
|
|
262
|
+
destroy: jasmine.createSpy('destroy')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
controller.cleanup()
|
|
266
|
+
|
|
267
|
+
expect(controller.$.destroy).toHaveBeenCalled()
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Mock Factory
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
// lib/testing/mocks.js
|
|
277
|
+
exports.Mocks = {
|
|
278
|
+
createHTTPClient() {
|
|
279
|
+
return {
|
|
280
|
+
open: jasmine.createSpy('open'),
|
|
281
|
+
send: jasmine.createSpy('send'),
|
|
282
|
+
setRequestHeader: jasmine.createSpy('setRequestHeader'),
|
|
283
|
+
onload: null,
|
|
284
|
+
onerror: null,
|
|
285
|
+
status: null,
|
|
286
|
+
responseText: null
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
createTokenStorage() {
|
|
291
|
+
return {
|
|
292
|
+
save: jasmine.createSpy('save'),
|
|
293
|
+
get: jasmine.createSpy('get'),
|
|
294
|
+
clear: jasmine.createSpy('clear')
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
createLogger() {
|
|
299
|
+
return {
|
|
300
|
+
debug: jasmine.createSpy('debug'),
|
|
301
|
+
info: jasmine.createSpy('info'),
|
|
302
|
+
warn: jasmine.createSpy('warn'),
|
|
303
|
+
error: jasmine.createSpy('error')
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
createView() {
|
|
308
|
+
return {
|
|
309
|
+
addEventListener: jasmine.createSpy('addEventListener'),
|
|
310
|
+
removeEventListener: jasmine.createSpy('removeEventListener'),
|
|
311
|
+
applyProperties: jasmine.createSpy('applyProperties'),
|
|
312
|
+
visible: true,
|
|
313
|
+
enabled: true,
|
|
314
|
+
text: '',
|
|
315
|
+
value: ''
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
createNetworkMock() {
|
|
320
|
+
return {
|
|
321
|
+
addEventListener: jasmine.createSpy('addEventListener'),
|
|
322
|
+
removeEventListener: jasmine.createSpy('removeEventListener'),
|
|
323
|
+
online: true
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Test Helper Functions
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
// lib/testing/helpers.js
|
|
333
|
+
exports.TestHelpers = {
|
|
334
|
+
// Wait for async operations
|
|
335
|
+
async waitFor(condition, timeout = 1000) {
|
|
336
|
+
const start = Date.now()
|
|
337
|
+
|
|
338
|
+
while (Date.now() - start < timeout) {
|
|
339
|
+
if (await condition()) {
|
|
340
|
+
return true
|
|
341
|
+
}
|
|
342
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
throw new Error('Condition not met within timeout')
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// Trigger Alloy event
|
|
349
|
+
triggerEvent(controller, eventName, data) {
|
|
350
|
+
const listeners = controller._eventListeners?.[eventName] || []
|
|
351
|
+
|
|
352
|
+
listeners.forEach(listener => {
|
|
353
|
+
listener(data)
|
|
354
|
+
})
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
// Mock Ti.Platform properties
|
|
358
|
+
mockPlatform(properties) {
|
|
359
|
+
const original = {}
|
|
360
|
+
|
|
361
|
+
Object.keys(properties).forEach(key => {
|
|
362
|
+
original[key] = Ti.Platform[key]
|
|
363
|
+
Ti.Platform[key] = properties[key]
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
return () => {
|
|
367
|
+
// Restore function
|
|
368
|
+
Object.keys(original).forEach(key => {
|
|
369
|
+
Ti.Platform[key] = original[key]
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Test Setup Configuration
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
// lib/testing/setup.js
|
|
380
|
+
beforeAll(() => {
|
|
381
|
+
// Disable Alloy auto-cleanup for testing
|
|
382
|
+
Alloy.Globals.testMode = true
|
|
383
|
+
|
|
384
|
+
// Mock Ti.Platform for deterministic tests
|
|
385
|
+
spyOn(Ti.Platform, 'displayCaps').and.returnValue({
|
|
386
|
+
platformWidth: 375,
|
|
387
|
+
platformHeight: 667
|
|
388
|
+
})
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
afterAll(() => {
|
|
392
|
+
// Cleanup test resources
|
|
393
|
+
if (Alloy.Globals.testMode) {
|
|
394
|
+
Ti.App.Properties.removeAllProperties()
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## Running Tests
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
# Run specific test suite
|
|
403
|
+
titanium test --platform android
|
|
404
|
+
|
|
405
|
+
# Run with coverage
|
|
406
|
+
titanium test --coverage
|
|
407
|
+
|
|
408
|
+
# Run specific file
|
|
409
|
+
titanium test --specs app/specs/unit/services/authService.spec.js
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Anti-Patterns to Avoid
|
|
413
|
+
|
|
414
|
+
| Anti-Pattern | Problem | Solution |
|
|
415
|
+
| -------------------------- | ------------------------------ | ------------------------------- |
|
|
416
|
+
| Testing private methods | Breaks on refactoring | Test public interface only |
|
|
417
|
+
| Mocking everything | Tests pass, code fails | Mock only external dependencies |
|
|
418
|
+
| No cleanup in tests | Memory leaks, interference | Always cleanup in afterEach |
|
|
419
|
+
| Testing XML/TSS | Fragile, implementation detail | Test rendered behavior instead |
|
|
420
|
+
| Hard dependencies | Untestable code | Use dependency injection |
|
|
421
|
+
| Shared state between tests | Flaky tests | Reset state in beforeEach |
|
|
422
|
+
|
|
423
|
+
## Test Checklist
|
|
424
|
+
|
|
425
|
+
Before committing code, verify:
|
|
426
|
+
|
|
427
|
+
- [ ] All unit tests pass
|
|
428
|
+
- [ ] New functionality has test coverage
|
|
429
|
+
- [ ] Controllers cleanup properly in tests
|
|
430
|
+
- [ ] Mocks are reset between tests
|
|
431
|
+
- [ ] No hard dependencies (use injection)
|
|
432
|
+
- [ ] Tests are independent (no shared state)
|
|
433
|
+
- [ ] Edge cases are covered (null, empty, error)
|