@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.
Files changed (132) hide show
  1. package/{AGENTS-TEMPLATE.md → AGENTS-VERCEL-RESEARCH.md} +16 -18
  2. package/README.md +250 -168
  3. package/agents/{ti-researcher.md → ti-pro.md} +10 -10
  4. package/bin/titools.js +16 -12
  5. package/lib/cleanup.js +95 -0
  6. package/lib/commands/agents.js +146 -70
  7. package/lib/commands/skills.js +349 -0
  8. package/lib/commands/uninstall.js +189 -154
  9. package/lib/commands/update.js +104 -21
  10. package/lib/config.js +26 -20
  11. package/lib/downloader.js +1 -1
  12. package/lib/installer.js +37 -97
  13. package/lib/platform.js +9 -5
  14. package/lib/prompts/checkboxCancel.js +264 -0
  15. package/lib/prompts/selectCancel.js +204 -0
  16. package/lib/symlink.js +19 -7
  17. package/lib/utils.js +21 -31
  18. package/package.json +10 -10
  19. package/skills/alloy-guides/SKILL.md +8 -30
  20. package/skills/alloy-guides/references/CLI_TASKS.md +14 -24
  21. package/skills/alloy-guides/references/CONCEPTS.md +5 -25
  22. package/skills/alloy-guides/references/CONTROLLERS.md +2 -19
  23. package/skills/alloy-guides/references/MODELS.md +40 -10
  24. package/skills/alloy-guides/references/PURGETSS.md +1 -11
  25. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +8 -14
  26. package/skills/alloy-guides/references/VIEWS_STYLES.md +14 -25
  27. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +2 -8
  28. package/skills/alloy-guides/references/VIEWS_XML.md +29 -40
  29. package/skills/alloy-guides/references/WIDGETS.md +1 -17
  30. package/skills/alloy-howtos/SKILL.md +1 -22
  31. package/skills/alloy-howtos/references/best_practices.md +0 -17
  32. package/skills/alloy-howtos/references/cli_reference.md +1 -23
  33. package/skills/alloy-howtos/references/config_files.md +86 -15
  34. package/skills/alloy-howtos/references/custom_tags.md +14 -13
  35. package/skills/alloy-howtos/references/debugging_troubleshooting.md +3 -22
  36. package/skills/alloy-howtos/references/samples.md +4 -14
  37. package/skills/purgetss/SKILL.md +11 -37
  38. package/skills/purgetss/references/EXAMPLES.md +106 -17
  39. package/skills/purgetss/references/animation-system.md +0 -67
  40. package/skills/purgetss/references/apply-directive.md +0 -18
  41. package/skills/purgetss/references/arbitrary-values.md +0 -25
  42. package/skills/purgetss/references/class-index.md +0 -46
  43. package/skills/purgetss/references/cli-commands.md +0 -41
  44. package/skills/purgetss/references/configurable-properties.md +0 -55
  45. package/skills/purgetss/references/custom-rules.md +0 -14
  46. package/skills/purgetss/references/customization-deep-dive.md +0 -49
  47. package/skills/purgetss/references/dynamic-component-creation.md +56 -37
  48. package/skills/purgetss/references/grid-layout.md +0 -32
  49. package/skills/purgetss/references/icon-fonts.md +0 -50
  50. package/skills/purgetss/references/installation-setup.md +61 -32
  51. package/skills/purgetss/references/migration-guide.md +127 -0
  52. package/skills/purgetss/references/opacity-modifier.md +0 -34
  53. package/skills/purgetss/references/performance-tips.md +261 -0
  54. package/skills/purgetss/references/platform-modifiers.md +0 -46
  55. package/skills/purgetss/references/smart-mappings.md +0 -12
  56. package/skills/purgetss/references/tikit-components.md +393 -0
  57. package/skills/purgetss/references/titanium-resets.md +1 -27
  58. package/skills/purgetss/references/ui-ux-design.md +467 -117
  59. package/skills/{alloy-expert → ti-expert}/SKILL.md +45 -54
  60. package/skills/ti-expert/references/alloy-builtins.md +387 -0
  61. package/skills/{alloy-expert → ti-expert}/references/alloy-structure.md +147 -40
  62. package/skills/ti-expert/references/anti-patterns.md +90 -0
  63. package/skills/ti-expert/references/cli-expert.md +109 -0
  64. package/skills/{alloy-expert → ti-expert}/references/code-conventions.md +182 -49
  65. package/skills/{alloy-expert → ti-expert}/references/controller-patterns.md +103 -93
  66. package/skills/{alloy-expert → ti-expert}/references/error-handling.md +12 -12
  67. package/skills/{alloy-expert → ti-expert}/references/examples.md +144 -85
  68. package/skills/{alloy-expert → ti-expert}/references/migration-patterns.md +38 -36
  69. package/skills/{alloy-expert → ti-expert}/references/patterns.md +8 -8
  70. package/skills/ti-expert/references/performance-listview.md +251 -0
  71. package/skills/{alloy-expert/references/performance-patterns.md → ti-expert/references/performance-optimization.md} +67 -283
  72. package/skills/{alloy-expert/references/security-patterns.md → ti-expert/references/security-device.md} +12 -295
  73. package/skills/ti-expert/references/security-fundamentals.md +284 -0
  74. package/skills/{alloy-expert → ti-expert}/references/state-management.md +10 -10
  75. package/skills/ti-expert/references/testing-e2e-ci.md +432 -0
  76. package/skills/ti-expert/references/testing-unit.md +433 -0
  77. package/skills/ti-expert/references/theming.md +394 -0
  78. package/skills/ti-guides/SKILL.md +1 -14
  79. package/skills/ti-guides/references/advanced-data-and-images.md +126 -10
  80. package/skills/ti-guides/references/app-distribution.md +48 -10
  81. package/skills/ti-guides/references/application-frameworks.md +9 -2
  82. package/skills/ti-guides/references/cli-reference.md +338 -82
  83. package/skills/ti-guides/references/coding-best-practices.md +14 -4
  84. package/skills/ti-guides/references/commonjs-advanced.md +103 -6
  85. package/skills/ti-guides/references/hello-world.md +5 -2
  86. package/skills/ti-guides/references/hyperloop-native-access.md +439 -46
  87. package/skills/ti-guides/references/javascript-primer.md +4 -3
  88. package/skills/ti-guides/references/resources.md +0 -1
  89. package/skills/ti-guides/references/style-and-conventions.md +57 -9
  90. package/skills/ti-guides/references/tiapp-config.md +48 -6
  91. package/skills/ti-howtos/SKILL.md +2 -27
  92. package/skills/ti-howtos/references/android-platform-deep-dives.md +41 -90
  93. package/skills/ti-howtos/references/automation-fastlane-appium.md +15 -14
  94. package/skills/ti-howtos/references/buffer-codec-streams.md +22 -22
  95. package/skills/ti-howtos/references/cross-platform-development.md +68 -37
  96. package/skills/ti-howtos/references/debugging-profiling.md +8 -53
  97. package/skills/ti-howtos/references/extending-titanium.md +12 -51
  98. package/skills/ti-howtos/references/google-maps-v2.md +30 -29
  99. package/skills/ti-howtos/references/ios-map-kit.md +19 -25
  100. package/skills/ti-howtos/references/ios-platform-deep-dives.md +95 -283
  101. package/skills/ti-howtos/references/local-data-sources.md +45 -37
  102. package/skills/ti-howtos/references/location-and-maps.md +47 -28
  103. package/skills/ti-howtos/references/media-apis.md +84 -27
  104. package/skills/ti-howtos/references/notification-services.md +40 -89
  105. package/skills/ti-howtos/references/remote-data-sources.md +21 -36
  106. package/skills/ti-howtos/references/tutorials.md +79 -39
  107. package/skills/ti-howtos/references/using-modules.md +4 -30
  108. package/skills/ti-howtos/references/web-content-integration.md +28 -45
  109. package/skills/ti-howtos/references/webpack-build-pipeline.md +54 -18
  110. package/skills/ti-ui/SKILL.md +1 -21
  111. package/skills/ti-ui/references/accessibility-deep-dive.md +71 -25
  112. package/skills/ti-ui/references/animation-and-matrices.md +0 -47
  113. package/skills/ti-ui/references/application-structures.md +0 -46
  114. package/skills/ti-ui/references/custom-fonts-styling.md +9 -57
  115. package/skills/ti-ui/references/event-handling.md +32 -40
  116. package/skills/ti-ui/references/gestures.md +0 -40
  117. package/skills/ti-ui/references/icons-and-splash-screens.md +21 -44
  118. package/skills/ti-ui/references/layouts-and-positioning.md +47 -39
  119. package/skills/ti-ui/references/listviews-and-performance.md +187 -73
  120. package/skills/ti-ui/references/orientation.md +57 -71
  121. package/skills/ti-ui/references/platform-ui-android.md +114 -48
  122. package/skills/ti-ui/references/platform-ui-ios.md +63 -38
  123. package/skills/ti-ui/references/scrolling-views.md +14 -23
  124. package/skills/ti-ui/references/tableviews.md +6 -56
  125. package/lib/commands/install.js +0 -188
  126. package/skills/alloy-expert/references/anti-patterns.md +0 -133
  127. package/skills/alloy-expert/references/testing.md +0 -872
  128. package/skills/ti-guides/references/alloy-cli-advanced.md +0 -84
  129. package/skills/ti-guides/references/alloy-data-mastery.md +0 -29
  130. package/skills/ti-guides/references/alloy-widgets-and-themes.md +0 -19
  131. /package/skills/{alloy-expert → ti-expert}/assets/ControllerAutoCleanup.js +0 -0
  132. /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)