@cometchat/skills 3.0.0 → 4.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.
- package/LICENSE +21 -0
- package/README.md +44 -17
- package/bin/install.js +389 -43
- package/package.json +19 -9
- package/skills/cometchat/SKILL.md +1066 -265
- package/skills/cometchat-android-v5/SKILL.md +120 -0
- package/skills/cometchat-android-v5-components/SKILL.md +430 -0
- package/skills/cometchat-android-v5-core/SKILL.md +468 -0
- package/skills/cometchat-android-v5-customization/SKILL.md +305 -0
- package/skills/cometchat-android-v5-extensions/SKILL.md +124 -0
- package/skills/cometchat-android-v5-features/SKILL.md +130 -0
- package/skills/cometchat-android-v5-placement/SKILL.md +276 -0
- package/skills/cometchat-android-v5-production/SKILL.md +94 -0
- package/skills/cometchat-android-v5-push/SKILL.md +620 -0
- package/skills/cometchat-android-v5-testing/SKILL.md +187 -0
- package/skills/cometchat-android-v5-theming/SKILL.md +166 -0
- package/skills/cometchat-android-v5-troubleshooting/SKILL.md +73 -0
- package/skills/cometchat-android-v6/SKILL.md +107 -0
- package/skills/cometchat-android-v6-builder-settings/SKILL.md +145 -0
- package/skills/cometchat-android-v6-compose-components/SKILL.md +202 -0
- package/skills/cometchat-android-v6-compose-customization/SKILL.md +280 -0
- package/skills/cometchat-android-v6-compose-placement/SKILL.md +213 -0
- package/skills/cometchat-android-v6-compose-theming/SKILL.md +224 -0
- package/skills/cometchat-android-v6-core/SKILL.md +354 -0
- package/skills/cometchat-android-v6-events/SKILL.md +212 -0
- package/skills/cometchat-android-v6-extensions/SKILL.md +191 -0
- package/skills/cometchat-android-v6-features/SKILL.md +130 -0
- package/skills/cometchat-android-v6-kotlin-components/SKILL.md +197 -0
- package/skills/cometchat-android-v6-kotlin-customization/SKILL.md +268 -0
- package/skills/cometchat-android-v6-kotlin-placement/SKILL.md +248 -0
- package/skills/cometchat-android-v6-kotlin-theming/SKILL.md +202 -0
- package/skills/cometchat-android-v6-production/SKILL.md +187 -0
- package/skills/cometchat-android-v6-push/SKILL.md +274 -0
- package/skills/cometchat-android-v6-testing/SKILL.md +279 -0
- package/skills/cometchat-android-v6-troubleshooting/SKILL.md +249 -0
- package/skills/cometchat-angular-components/SKILL.md +706 -0
- package/skills/cometchat-angular-core/SKILL.md +444 -0
- package/skills/cometchat-angular-customization/SKILL.md +424 -0
- package/skills/cometchat-angular-features/SKILL.md +447 -0
- package/skills/cometchat-angular-patterns/SKILL.md +447 -0
- package/skills/cometchat-angular-placement/SKILL.md +685 -0
- package/skills/cometchat-angular-production/SKILL.md +415 -0
- package/skills/cometchat-angular-theming/SKILL.md +365 -0
- package/skills/cometchat-angular-troubleshooting/SKILL.md +329 -0
- package/skills/cometchat-astro-patterns/SKILL.md +3 -3
- package/skills/cometchat-components/SKILL.md +11 -0
- package/skills/cometchat-core/SKILL.md +11 -6
- package/skills/cometchat-customization/references/component-catalog.md +1 -1
- package/skills/cometchat-features/SKILL.md +176 -108
- package/skills/cometchat-flutter-v5/SKILL.md +167 -0
- package/skills/cometchat-flutter-v5-calls/SKILL.md +192 -0
- package/skills/cometchat-flutter-v5-conversations/SKILL.md +117 -0
- package/skills/cometchat-flutter-v5-core/SKILL.md +221 -0
- package/skills/cometchat-flutter-v5-customization/SKILL.md +227 -0
- package/skills/cometchat-flutter-v5-events/SKILL.md +155 -0
- package/skills/cometchat-flutter-v5-messages/SKILL.md +258 -0
- package/skills/cometchat-flutter-v5-production/SKILL.md +207 -0
- package/skills/cometchat-flutter-v5-push/SKILL.md +284 -0
- package/skills/cometchat-flutter-v5-theming/SKILL.md +160 -0
- package/skills/cometchat-flutter-v5-troubleshooting/SKILL.md +129 -0
- package/skills/cometchat-flutter-v5-users-groups/SKILL.md +151 -0
- package/skills/cometchat-flutter-v6/SKILL.md +445 -0
- package/skills/cometchat-flutter-v6-calls/SKILL.md +333 -0
- package/skills/cometchat-flutter-v6-components/SKILL.md +365 -0
- package/skills/cometchat-flutter-v6-conversations/SKILL.md +210 -0
- package/skills/cometchat-flutter-v6-core/SKILL.md +305 -0
- package/skills/cometchat-flutter-v6-customization/SKILL.md +666 -0
- package/skills/cometchat-flutter-v6-events/SKILL.md +216 -0
- package/skills/cometchat-flutter-v6-features/SKILL.md +406 -0
- package/skills/cometchat-flutter-v6-messages/SKILL.md +333 -0
- package/skills/cometchat-flutter-v6-migration/SKILL.md +381 -0
- package/skills/cometchat-flutter-v6-placement/SKILL.md +398 -0
- package/skills/cometchat-flutter-v6-production/SKILL.md +632 -0
- package/skills/cometchat-flutter-v6-theming/SKILL.md +260 -0
- package/skills/cometchat-flutter-v6-troubleshooting/SKILL.md +726 -0
- package/skills/cometchat-flutter-v6-users-groups/SKILL.md +216 -0
- package/skills/cometchat-ios/SKILL.md +323 -0
- package/skills/cometchat-ios-components/SKILL.md +651 -0
- package/skills/cometchat-ios-core/SKILL.md +822 -0
- package/skills/cometchat-ios-customization/SKILL.md +825 -0
- package/skills/cometchat-ios-features/SKILL.md +725 -0
- package/skills/cometchat-ios-placement/SKILL.md +750 -0
- package/skills/cometchat-ios-production/SKILL.md +730 -0
- package/skills/cometchat-ios-push/SKILL.md +582 -0
- package/skills/cometchat-ios-theming/SKILL.md +631 -0
- package/skills/cometchat-ios-troubleshooting/SKILL.md +983 -0
- package/skills/cometchat-native-bare-patterns/SKILL.md +470 -0
- package/skills/cometchat-native-components/SKILL.md +594 -0
- package/skills/cometchat-native-core/SKILL.md +407 -0
- package/skills/cometchat-native-customization/SKILL.md +594 -0
- package/skills/cometchat-native-expo-patterns/SKILL.md +485 -0
- package/skills/cometchat-native-features/SKILL.md +608 -0
- package/skills/cometchat-native-placement/SKILL.md +500 -0
- package/skills/cometchat-native-production/SKILL.md +694 -0
- package/skills/cometchat-native-push/SKILL.md +593 -0
- package/skills/cometchat-native-testing/SKILL.md +583 -0
- package/skills/cometchat-native-theming/SKILL.md +656 -0
- package/skills/cometchat-native-troubleshooting/SKILL.md +433 -0
- package/skills/cometchat-nextjs-patterns/SKILL.md +5 -5
- package/skills/cometchat-production/SKILL.md +60 -0
- package/skills/cometchat-react-router-patterns/SKILL.md +23 -21
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cometchat-android-v6-push
|
|
3
|
+
description: "CometChat Android UIKit v6 push notifications — FCM setup, chat/call notification handling, VoIP integration, and deep-linking"
|
|
4
|
+
license: "MIT"
|
|
5
|
+
compatibility: "Android 9.0+ (API 28); Kotlin 1.9+; com.cometchat:chatuikit-compose-android:6.x / com.cometchat:chatuikit-kotlin-android:6.x"
|
|
6
|
+
allowed-tools: "executeBash, readFile, fileSearch, listDirectory"
|
|
7
|
+
metadata:
|
|
8
|
+
author: "CometChat"
|
|
9
|
+
version: "3.0.0"
|
|
10
|
+
tags: "cometchat, android, push, fcm, firebase, voip, notifications, calls"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
> **Companion skills:** cometchat-android-v6-core (init/login), cometchat-android-v6-events (call events), cometchat-android-v6-builder-settings (calling config)
|
|
14
|
+
|
|
15
|
+
## Purpose
|
|
16
|
+
|
|
17
|
+
Set up push notifications for CometChat v6 using Firebase Cloud Messaging (FCM), handle chat and call notifications, and integrate VoIP for background call handling.
|
|
18
|
+
|
|
19
|
+
## Use this skill when
|
|
20
|
+
|
|
21
|
+
- Adding FCM push notifications for chat messages
|
|
22
|
+
- Handling incoming call notifications (foreground and background)
|
|
23
|
+
- Setting up VoIP with TelecomManager for native call UI
|
|
24
|
+
- Implementing deep-link navigation from notifications
|
|
25
|
+
|
|
26
|
+
## Do not use this skill when
|
|
27
|
+
|
|
28
|
+
- Just initializing the SDK (use `cometchat-android-v6-core`)
|
|
29
|
+
- Working with UI components (use `cometchat-*-components`)
|
|
30
|
+
|
|
31
|
+
## 1. FCM Setup
|
|
32
|
+
|
|
33
|
+
### 1.1 Gradle Dependencies
|
|
34
|
+
|
|
35
|
+
```kotlin
|
|
36
|
+
// In project-level build.gradle
|
|
37
|
+
plugins {
|
|
38
|
+
id("com.google.gms.google-services") version "4.4.2" apply false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// In app-level build.gradle.kts
|
|
42
|
+
plugins {
|
|
43
|
+
id("com.google.gms.google-services")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
dependencies {
|
|
47
|
+
implementation(platform("com.google.firebase:firebase-bom:33.x.x"))
|
|
48
|
+
implementation("com.google.firebase:firebase-messaging")
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Add `google-services.json` to your app module's root directory.
|
|
53
|
+
|
|
54
|
+
### 1.2 Initialize Firebase
|
|
55
|
+
|
|
56
|
+
```kotlin
|
|
57
|
+
// In Application.onCreate()
|
|
58
|
+
FirebaseApp.initializeApp(this)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 2. FCMService Implementation
|
|
62
|
+
|
|
63
|
+
> **Heads-up — this is a reference pattern.** The classes referenced below (`CometChatVoIP`, `CometChatVoIPConnectionService`, `FCMMessageDTO`, `FCMCallDto`, `FCMService`, `VoIPPermissionListener`, `CometChatVoIPUtils`, `FCMMessageNotificationUtils`) are NOT exported from `com.cometchat:chatuikit-{compose,kotlin}-android:6.x`. They live in the `master-app-jetpack` sample app — copy the relevant source files into your project, or use them as a guide for writing your own equivalents. The kit's only public push surface is `CometChatNotifications.registerPushToken(...)` / `unregisterPushToken(...)`; everything below is glue you control.
|
|
64
|
+
|
|
65
|
+
Pattern from `master-app-jetpack/fcm/FCMService.kt` (sample-app code, copy into your project):
|
|
66
|
+
|
|
67
|
+
```kotlin
|
|
68
|
+
class FCMService : FirebaseMessagingService() {
|
|
69
|
+
|
|
70
|
+
companion object {
|
|
71
|
+
var fcmToken: String? = null
|
|
72
|
+
private set
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun onNewToken(token: String) {
|
|
76
|
+
super.onNewToken(token)
|
|
77
|
+
fcmToken = token
|
|
78
|
+
// Store token for later use (e.g., SharedPreferences)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override fun onMessageReceived(message: RemoteMessage) {
|
|
82
|
+
super.onMessageReceived(message)
|
|
83
|
+
|
|
84
|
+
if (message.data.isEmpty()) return
|
|
85
|
+
|
|
86
|
+
val type = message.data["type"]?.lowercase()
|
|
87
|
+
when (type) {
|
|
88
|
+
"chat" -> handleChatNotification(message)
|
|
89
|
+
"call" -> handleCallNotification(message)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2.1 Chat Notification Handling
|
|
96
|
+
|
|
97
|
+
```kotlin
|
|
98
|
+
private fun handleChatNotification(message: RemoteMessage) {
|
|
99
|
+
val fcmMessageDTO = Gson().fromJson(
|
|
100
|
+
Gson().toJson(message.data),
|
|
101
|
+
FCMMessageDTO::class.java
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// Mark as delivered for read receipts (only if SDK is initialized)
|
|
105
|
+
if (CometChatUIKit.isSDKInitialized()) {
|
|
106
|
+
CometChat.markAsDelivered(
|
|
107
|
+
fcmMessageDTO.tag!!.toLong(),
|
|
108
|
+
fcmMessageDTO.sender!!,
|
|
109
|
+
fcmMessageDTO.receiverType!!,
|
|
110
|
+
fcmMessageDTO.receiver!!
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Show notification if user is NOT in the same chat
|
|
115
|
+
val isUser = fcmMessageDTO.receiverType == CometChatConstants.RECEIVER_TYPE_USER
|
|
116
|
+
val uid = if (isUser) fcmMessageDTO.sender!! else fcmMessageDTO.receiver!!
|
|
117
|
+
|
|
118
|
+
if (uid != currentOpenChatId) {
|
|
119
|
+
// Build and show notification
|
|
120
|
+
FCMMessageNotificationUtils.showNotification(
|
|
121
|
+
this, fcmMessageDTO, intent,
|
|
122
|
+
NOTIFICATION_KEY_REPLY_ACTION,
|
|
123
|
+
NotificationCompat.CATEGORY_MESSAGE
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2.2 Call Notification Handling
|
|
130
|
+
|
|
131
|
+
```kotlin
|
|
132
|
+
private fun handleCallNotification(message: RemoteMessage) {
|
|
133
|
+
val sessionId = message.data["sessionId"]
|
|
134
|
+
val callAction = message.data["callAction"]
|
|
135
|
+
|
|
136
|
+
// CRITICAL: Check if SDK is initialized
|
|
137
|
+
if (!CometChatUIKit.isSDKInitialized()) {
|
|
138
|
+
return // Cannot handle call without SDK
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check VoIP permissions
|
|
142
|
+
if (!CometChatVoIP.hasReadPhoneStatePermission(this) ||
|
|
143
|
+
!CometChatVoIP.hasManageOwnCallsPermission(this) ||
|
|
144
|
+
!CometChatVoIP.hasAnswerPhoneCallsPermission(this)) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check phone account is enabled
|
|
149
|
+
CometChatVoIP.hasEnabledPhoneAccountForVoIP(this, object : VoIPPermissionListener {
|
|
150
|
+
override fun onPermissionsGranted() {
|
|
151
|
+
handleCallFlow(message)
|
|
152
|
+
}
|
|
153
|
+
override fun onPermissionsDenied(error: CometChatVoIPError?) {
|
|
154
|
+
// Cannot show VoIP UI
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## 3. VoIP Integration
|
|
161
|
+
|
|
162
|
+
### 3.1 Required Permissions
|
|
163
|
+
|
|
164
|
+
```xml
|
|
165
|
+
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
|
166
|
+
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
|
167
|
+
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 3.2 VoIP Call Flow
|
|
171
|
+
|
|
172
|
+
```kotlin
|
|
173
|
+
private fun handleCallFlow(message: RemoteMessage) {
|
|
174
|
+
val callData = Gson().fromJson(
|
|
175
|
+
Gson().toJson(message.data),
|
|
176
|
+
FCMCallDto::class.java
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
// Initialize VoIP
|
|
180
|
+
CometChatVoIP.init(this, applicationInfo.loadLabel(packageManager).toString())
|
|
181
|
+
|
|
182
|
+
when (callData.callAction) {
|
|
183
|
+
"initiated" -> {
|
|
184
|
+
// Show incoming call UI (only if app is in background)
|
|
185
|
+
if (!isAppInForeground()) {
|
|
186
|
+
voipIncomingCall(callData)
|
|
187
|
+
}
|
|
188
|
+
// If foreground, UIKit's CometChatIncomingCall handles it
|
|
189
|
+
}
|
|
190
|
+
"cancelled", "unanswered" -> {
|
|
191
|
+
// End the native call UI if session matches
|
|
192
|
+
if (CometChatVoIPUtils.currentSessionId == callData.sessionId) {
|
|
193
|
+
CometChatVoIP.telecomManager?.endCall()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 3.3 Foreground vs Background Routing
|
|
201
|
+
|
|
202
|
+
| App State | Incoming Call Handler |
|
|
203
|
+
|---|---|
|
|
204
|
+
| Foreground | UIKit's `CometChatIncomingCall` via SDK call listener |
|
|
205
|
+
| Background | VoIP via `TelecomManager` + `CometChatVoIPConnectionService` |
|
|
206
|
+
| Killed | FCM wakes app, but SDK may not be initialized — cannot handle call |
|
|
207
|
+
|
|
208
|
+
### 3.4 Busy Rejection
|
|
209
|
+
|
|
210
|
+
```kotlin
|
|
211
|
+
private fun rejectCallWithBusyStatus(call: Call) {
|
|
212
|
+
CometChat.rejectCall(
|
|
213
|
+
call.sessionId,
|
|
214
|
+
CometChatConstants.CALL_STATUS_BUSY,
|
|
215
|
+
object : CometChat.CallbackListener<Call>() {
|
|
216
|
+
override fun onSuccess(rejectedCall: Call?) {
|
|
217
|
+
rejectedCall?.let {
|
|
218
|
+
CometChatEvents.emitCallEvent(CometChatCallEvent.CallRejected(it))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
override fun onError(e: CometChatException) { }
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## 4. AndroidManifest Registration
|
|
228
|
+
|
|
229
|
+
```xml
|
|
230
|
+
<service
|
|
231
|
+
android:name=".fcm.FCMService"
|
|
232
|
+
android:exported="false">
|
|
233
|
+
<intent-filter>
|
|
234
|
+
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
235
|
+
</intent-filter>
|
|
236
|
+
</service>
|
|
237
|
+
|
|
238
|
+
<!-- VoIP Connection Service -->
|
|
239
|
+
<service
|
|
240
|
+
android:name=".voip.CometChatVoIPConnectionService"
|
|
241
|
+
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
|
242
|
+
android:exported="true">
|
|
243
|
+
<intent-filter>
|
|
244
|
+
<action android:name="android.telecom.ConnectionService" />
|
|
245
|
+
</intent-filter>
|
|
246
|
+
</service>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 5. WebSocket Connection Management
|
|
250
|
+
|
|
251
|
+
From `master-app-jetpack/Application.kt` — manage WebSocket connections based on app lifecycle:
|
|
252
|
+
|
|
253
|
+
```kotlin
|
|
254
|
+
// When app comes to foreground
|
|
255
|
+
CometChat.connect(object : CometChat.CallbackListener<String?>() {
|
|
256
|
+
override fun onSuccess(s: String?) { /* connected */ }
|
|
257
|
+
override fun onError(e: CometChatException) { /* failed */ }
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
// When app goes to background
|
|
261
|
+
CometChat.disconnect(object : CometChat.CallbackListener<String?>() {
|
|
262
|
+
override fun onSuccess(s: String?) { /* disconnected */ }
|
|
263
|
+
override fun onError(e: CometChatException) { /* failed */ }
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Hard rules
|
|
268
|
+
|
|
269
|
+
- ALWAYS check `CometChatUIKit.isSDKInitialized()` before making SDK calls in FCM service — the app may have been killed
|
|
270
|
+
- NEVER show VoIP incoming call UI when app is in foreground — UIKit's `CometChatIncomingCall` handles foreground calls
|
|
271
|
+
- VoIP requires READ_PHONE_STATE, MANAGE_OWN_CALLS, and ANSWER_PHONE_CALLS permissions — check all three before handling calls
|
|
272
|
+
- `CometChat.markAsDelivered()` requires the SDK to be initialized — skip it if not
|
|
273
|
+
- For push token registration, use `CometChatNotifications.registerPushToken(...)` / `unregisterPushToken(...)` — that is the kit's only public push API. The `CometChatVoIP*` / `FCM*` classes shown above are sample-app glue (`master-app-jetpack`), not part of `chatuikit-{compose,kotlin}`
|
|
274
|
+
- Always manage WebSocket connections based on app lifecycle to avoid battery drain
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cometchat-android-v6-testing
|
|
3
|
+
description: "CometChat Android UIKit v6 testing — unit testing ViewModels, Compose UI testing, Espresso, Maestro, and CI configuration"
|
|
4
|
+
license: "MIT"
|
|
5
|
+
compatibility: "Android 9.0+ (API 28); Kotlin 1.9+; com.cometchat:chatuikit-compose-android:6.x / com.cometchat:chatuikit-kotlin-android:6.x"
|
|
6
|
+
allowed-tools: "executeBash, readFile, fileSearch, listDirectory"
|
|
7
|
+
metadata:
|
|
8
|
+
author: "CometChat"
|
|
9
|
+
version: "3.0.0"
|
|
10
|
+
tags: "cometchat, android, testing, junit, kotest, compose-testing, espresso, maestro"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
> **Companion skills:** cometchat-android-v6-extensions (ViewModel/DataSource architecture), cometchat-android-v6-compose-components, cometchat-android-v6-kotlin-components
|
|
14
|
+
|
|
15
|
+
## Purpose
|
|
16
|
+
|
|
17
|
+
Test CometChat UIKit v6 components and ViewModels — unit tests for the shared core module, Compose UI tests, Espresso tests for Views, and end-to-end tests with Maestro.
|
|
18
|
+
|
|
19
|
+
## Use this skill when
|
|
20
|
+
|
|
21
|
+
- Writing unit tests for CometChat ViewModels
|
|
22
|
+
- Writing Compose UI tests for CometChat components
|
|
23
|
+
- Writing Espresso tests for Kotlin Views components
|
|
24
|
+
- Setting up Maestro flows for end-to-end testing
|
|
25
|
+
- Configuring CI for CometChat test suites
|
|
26
|
+
|
|
27
|
+
## Do not use this skill when
|
|
28
|
+
|
|
29
|
+
- Debugging runtime issues (use `cometchat-android-v6-troubleshooting`)
|
|
30
|
+
- Working with component APIs (use `cometchat-*-components`)
|
|
31
|
+
|
|
32
|
+
## 1. Test Dependencies
|
|
33
|
+
|
|
34
|
+
From `chatuikit-compose/build.gradle.kts`:
|
|
35
|
+
|
|
36
|
+
```kotlin
|
|
37
|
+
dependencies {
|
|
38
|
+
// Unit testing
|
|
39
|
+
testImplementation("junit:junit:4.13.2")
|
|
40
|
+
testImplementation("io.kotest:kotest-runner-junit5:5.x")
|
|
41
|
+
testImplementation("io.kotest:kotest-assertions-core:5.x")
|
|
42
|
+
testImplementation("io.kotest:kotest-property:5.x")
|
|
43
|
+
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.x")
|
|
44
|
+
testImplementation("androidx.arch.core:core-testing:2.x")
|
|
45
|
+
testImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.x")
|
|
46
|
+
testImplementation("org.mockito:mockito-core:5.x")
|
|
47
|
+
testImplementation("org.mockito.kotlin:mockito-kotlin:5.x")
|
|
48
|
+
|
|
49
|
+
// Android instrumented testing
|
|
50
|
+
androidTestImplementation("androidx.test.ext:junit:1.x")
|
|
51
|
+
androidTestImplementation("androidx.test.espresso:espresso-core:3.x")
|
|
52
|
+
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
|
53
|
+
androidTestImplementation(platform("androidx.compose:compose-bom:2024.x.x"))
|
|
54
|
+
|
|
55
|
+
// Debug-only Compose tooling
|
|
56
|
+
debugImplementation("androidx.compose.ui:ui-tooling")
|
|
57
|
+
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Enable JUnit Platform for Kotest:
|
|
62
|
+
|
|
63
|
+
```kotlin
|
|
64
|
+
android {
|
|
65
|
+
testOptions {
|
|
66
|
+
unitTests.all {
|
|
67
|
+
it.useJUnitPlatform()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 2. Unit Testing ViewModels
|
|
74
|
+
|
|
75
|
+
ViewModels live in `chatuikit-core` and are shared across both stacks. Test them with coroutines-test and Mockito:
|
|
76
|
+
|
|
77
|
+
```kotlin
|
|
78
|
+
import kotlinx.coroutines.test.runTest
|
|
79
|
+
import org.mockito.kotlin.mock
|
|
80
|
+
import org.mockito.kotlin.whenever
|
|
81
|
+
|
|
82
|
+
class CometChatConversationsViewModelTest {
|
|
83
|
+
|
|
84
|
+
@Test
|
|
85
|
+
fun `loadConversations emits success state`() = runTest {
|
|
86
|
+
val mockDataSource = mock<ConversationListDataSource>()
|
|
87
|
+
// Setup mock responses...
|
|
88
|
+
|
|
89
|
+
val viewModel = CometChatConversationsViewModel(/* inject mocks */)
|
|
90
|
+
|
|
91
|
+
// Collect state and assert
|
|
92
|
+
viewModel.uiState.test {
|
|
93
|
+
val state = awaitItem()
|
|
94
|
+
// Assert state is success with expected data
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 2.1 Testing Events
|
|
101
|
+
|
|
102
|
+
```kotlin
|
|
103
|
+
import com.cometchat.uikit.core.events.CometChatEvents
|
|
104
|
+
import com.cometchat.uikit.core.events.CometChatMessageEvent
|
|
105
|
+
|
|
106
|
+
@Test
|
|
107
|
+
fun `emitting message event is received by collector`() = runTest {
|
|
108
|
+
val events = mutableListOf<CometChatMessageEvent>()
|
|
109
|
+
|
|
110
|
+
val job = launch {
|
|
111
|
+
CometChatEvents.messageEvents.collect { events.add(it) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
CometChatEvents.emitMessageEventSync(
|
|
115
|
+
CometChatMessageEvent.MessageSent(mockMessage, MessageStatus.SUCCESS)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
advanceUntilIdle()
|
|
119
|
+
assertEquals(1, events.size)
|
|
120
|
+
job.cancel()
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 3. Compose UI Testing
|
|
125
|
+
|
|
126
|
+
```kotlin
|
|
127
|
+
import androidx.compose.ui.test.junit4.createComposeRule
|
|
128
|
+
import androidx.compose.ui.test.onNodeWithText
|
|
129
|
+
import androidx.compose.ui.test.performClick
|
|
130
|
+
|
|
131
|
+
class CometChatConversationsTest {
|
|
132
|
+
|
|
133
|
+
@get:Rule
|
|
134
|
+
val composeTestRule = createComposeRule()
|
|
135
|
+
|
|
136
|
+
@Test
|
|
137
|
+
fun conversationsList_displaysTitle() {
|
|
138
|
+
composeTestRule.setContent {
|
|
139
|
+
CometChatTheme {
|
|
140
|
+
CometChatConversations()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Assert UI elements
|
|
145
|
+
composeTestRule.onNodeWithText("Chats").assertExists()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@Test
|
|
149
|
+
fun conversationItem_clickNavigates() {
|
|
150
|
+
var clickedConversation: Conversation? = null
|
|
151
|
+
|
|
152
|
+
composeTestRule.setContent {
|
|
153
|
+
CometChatTheme {
|
|
154
|
+
CometChatConversations(
|
|
155
|
+
onItemClick = { clickedConversation = it }
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Interact and assert
|
|
161
|
+
// composeTestRule.onNodeWithText("User Name").performClick()
|
|
162
|
+
// assertNotNull(clickedConversation)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 4. Espresso Testing (Kotlin Views)
|
|
168
|
+
|
|
169
|
+
```kotlin
|
|
170
|
+
import androidx.test.espresso.Espresso.onView
|
|
171
|
+
import androidx.test.espresso.assertion.ViewAssertions.matches
|
|
172
|
+
import androidx.test.espresso.matcher.ViewMatchers.*
|
|
173
|
+
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
|
174
|
+
|
|
175
|
+
class ConversationsActivityTest {
|
|
176
|
+
|
|
177
|
+
@get:Rule
|
|
178
|
+
val activityRule = ActivityScenarioRule(ConversationsActivity::class.java)
|
|
179
|
+
|
|
180
|
+
@Test
|
|
181
|
+
fun conversationsList_isDisplayed() {
|
|
182
|
+
onView(withId(R.id.conversations))
|
|
183
|
+
.check(matches(isDisplayed()))
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 5. Property-Based Testing with Kotest
|
|
189
|
+
|
|
190
|
+
The project uses Kotest for property-based tests (e.g., `MessageAdapterPropertyTest.kt`):
|
|
191
|
+
|
|
192
|
+
```kotlin
|
|
193
|
+
import io.kotest.core.spec.style.FunSpec
|
|
194
|
+
import io.kotest.property.forAll
|
|
195
|
+
import io.kotest.property.Arb
|
|
196
|
+
import io.kotest.property.arbitrary.string
|
|
197
|
+
|
|
198
|
+
class BubbleFactoryKeyTest : FunSpec({
|
|
199
|
+
|
|
200
|
+
test("factory key format is always category_type") {
|
|
201
|
+
forAll(Arb.string(1..20), Arb.string(1..20)) { category, type ->
|
|
202
|
+
val key = BubbleFactory.getKey(category, type)
|
|
203
|
+
key == "${category}_${type}"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## 6. Maestro End-to-End Testing
|
|
210
|
+
|
|
211
|
+
The `master-app-jetpack/ai-testing/` directory contains Maestro test configurations:
|
|
212
|
+
|
|
213
|
+
```yaml
|
|
214
|
+
# maestro/login_flow.yaml
|
|
215
|
+
appId: com.example.jetpackuikit
|
|
216
|
+
---
|
|
217
|
+
- launchApp
|
|
218
|
+
- tapOn: "Login"
|
|
219
|
+
- inputText: "superhero1"
|
|
220
|
+
- tapOn: "Submit"
|
|
221
|
+
- assertVisible: "Chats"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Run with:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
maestro test maestro/login_flow.yaml
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## 7. CI Configuration
|
|
231
|
+
|
|
232
|
+
### 7.1 GitHub Actions
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
name: Test
|
|
236
|
+
on: [push, pull_request]
|
|
237
|
+
jobs:
|
|
238
|
+
unit-tests:
|
|
239
|
+
runs-on: ubuntu-latest
|
|
240
|
+
steps:
|
|
241
|
+
- uses: actions/checkout@v4
|
|
242
|
+
- uses: actions/setup-java@v4
|
|
243
|
+
with:
|
|
244
|
+
java-version: '17'
|
|
245
|
+
distribution: 'temurin'
|
|
246
|
+
- run: ./gradlew testDebugUnitTest
|
|
247
|
+
|
|
248
|
+
instrumented-tests:
|
|
249
|
+
runs-on: macos-latest
|
|
250
|
+
steps:
|
|
251
|
+
- uses: actions/checkout@v4
|
|
252
|
+
- uses: actions/setup-java@v4
|
|
253
|
+
with:
|
|
254
|
+
java-version: '17'
|
|
255
|
+
distribution: 'temurin'
|
|
256
|
+
- uses: reactivecircus/android-emulator-runner@v2
|
|
257
|
+
with:
|
|
258
|
+
api-level: 34
|
|
259
|
+
script: ./gradlew connectedDebugAndroidTest
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 8. Test Organization
|
|
263
|
+
|
|
264
|
+
| Test Type | Location | Framework | What to Test |
|
|
265
|
+
|---|---|---|---|
|
|
266
|
+
| Unit tests | `chatuikit-core/src/test/` | JUnit + Kotest + Mockito | ViewModels, DataSources, Repositories, Events |
|
|
267
|
+
| Unit tests | `chatuikit-compose/src/test/` | JUnit + Kotest | BubbleFactory, style resolution |
|
|
268
|
+
| Compose UI tests | `chatuikit-compose/src/androidTest/` | Compose Test | Component rendering, interactions |
|
|
269
|
+
| Views UI tests | `chatuikit-kotlin/src/androidTest/` | Espresso | Component rendering, interactions |
|
|
270
|
+
| E2E tests | `master-app-*/ai-testing/` | Maestro | Full user flows |
|
|
271
|
+
|
|
272
|
+
## Hard rules
|
|
273
|
+
|
|
274
|
+
- ALWAYS use `CometChatTheme {}` wrapper in Compose test `setContent` blocks — components depend on CompositionLocal values
|
|
275
|
+
- Use `runTest` from `kotlinx-coroutines-test` for testing coroutines and SharedFlow
|
|
276
|
+
- Use `useJUnitPlatform()` in test options for Kotest compatibility
|
|
277
|
+
- Mock DataSources and Repositories when unit testing ViewModels — do NOT make real SDK calls in tests
|
|
278
|
+
- CometChat SDK must NOT be initialized in unit tests — mock all SDK interactions
|
|
279
|
+
- For Compose UI tests, use `createComposeRule()` not `createAndroidComposeRule()` unless you need Activity context
|