@kafitra/react-native-live-tracking 0.1.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/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/android/build.gradle +71 -0
  4. package/android/gradle.properties +7 -0
  5. package/android/src/main/AndroidManifest.xml +40 -0
  6. package/android/src/main/java/com/livetracking/LiveTrackingModuleImpl.kt +728 -0
  7. package/android/src/main/java/com/livetracking/LiveTrackingPackage.kt +16 -0
  8. package/android/src/main/java/com/livetracking/location/LocationEngine.kt +93 -0
  9. package/android/src/main/java/com/livetracking/network/NetworkListener.kt +127 -0
  10. package/android/src/main/java/com/livetracking/optimizer/ActivityRecognitionHandler.kt +248 -0
  11. package/android/src/main/java/com/livetracking/optimizer/MotionSleepManager.kt +130 -0
  12. package/android/src/main/java/com/livetracking/permissions/PermissionHandler.kt +145 -0
  13. package/android/src/main/java/com/livetracking/queue/QueueEngine.kt +167 -0
  14. package/android/src/main/java/com/livetracking/queue/QueuedLocation.kt +16 -0
  15. package/android/src/main/java/com/livetracking/queue/TrackingDatabase.kt +239 -0
  16. package/android/src/main/java/com/livetracking/receiver/BootReceiver.kt +53 -0
  17. package/android/src/main/java/com/livetracking/service/TrackingForegroundService.kt +145 -0
  18. package/android/src/main/java/com/livetracking/sync/FirebaseSyncEngine.kt +277 -0
  19. package/android/src/main/java/com/livetracking/sync/LocationDataPoint.kt +31 -0
  20. package/android/src/main/java/com/livetracking/sync/SyncEngineController.kt +220 -0
  21. package/android/src/main/java/com/livetracking/sync/SyncTargetConfig.kt +20 -0
  22. package/android/src/main/java/com/livetracking/sync/TargetHandler.kt +601 -0
  23. package/android/src/newarch/java/com/livetracking/LiveTrackingModule.kt +64 -0
  24. package/android/src/oldarch/java/com/livetracking/LiveTrackingModule.kt +70 -0
  25. package/android/src/test/java/com/livetracking/BackoffCalculationTest.kt +216 -0
  26. package/android/src/test/java/com/livetracking/BatchAccumulatorTest.kt +391 -0
  27. package/android/src/test/java/com/livetracking/BootReceiverTest.kt +247 -0
  28. package/android/src/test/java/com/livetracking/FirebaseSyncEngineTest.kt +337 -0
  29. package/android/src/test/java/com/livetracking/LocationEngineTest.kt +202 -0
  30. package/android/src/test/java/com/livetracking/MotionSleepManagerTest.kt +420 -0
  31. package/android/src/test/java/com/livetracking/OfflineQueueTest.kt +462 -0
  32. package/android/src/test/java/com/livetracking/PermissionHandlerTest.kt +200 -0
  33. package/android/src/test/java/com/livetracking/QueueEngineTest.kt +335 -0
  34. package/android/src/test/java/com/livetracking/SyncEngineControllerTest.kt +855 -0
  35. package/ios/ActivityRecognitionHandler.swift +196 -0
  36. package/ios/BackgroundModeHelper.swift +132 -0
  37. package/ios/FirebaseSyncEngine.swift +276 -0
  38. package/ios/LiveTracking-Bridging-Header.h +2 -0
  39. package/ios/LiveTracking.m +37 -0
  40. package/ios/LiveTracking.swift +773 -0
  41. package/ios/LocationDataPoint.swift +56 -0
  42. package/ios/LocationEngine.swift +160 -0
  43. package/ios/MotionSleepManager.swift +151 -0
  44. package/ios/NetworkListener.swift +105 -0
  45. package/ios/OfflineQueueManager.swift +503 -0
  46. package/ios/PermissionHandler.swift +148 -0
  47. package/ios/QueueEngine.swift +249 -0
  48. package/ios/SyncEngineController.swift +396 -0
  49. package/ios/SyncTargetConfig.swift +36 -0
  50. package/ios/TargetHandler.swift +715 -0
  51. package/ios/Tests/ActivityRecognitionHandlerTests.swift +259 -0
  52. package/ios/Tests/FirebaseSyncEngineTests.swift +303 -0
  53. package/ios/Tests/LocationEngineTests.swift +244 -0
  54. package/ios/Tests/MotionSleepManagerTests.swift +355 -0
  55. package/ios/Tests/NetworkListenerTests.swift +188 -0
  56. package/ios/Tests/OfflineQueueFlushTests.swift +375 -0
  57. package/ios/Tests/PermissionHandlerTests.swift +238 -0
  58. package/ios/Tests/QueueEngineTests.swift +346 -0
  59. package/ios/TrackingCleanup.swift +93 -0
  60. package/ios/TrackingNotificationManager.swift +187 -0
  61. package/lib/commonjs/EventEmitter.js +113 -0
  62. package/lib/commonjs/EventEmitter.js.map +1 -0
  63. package/lib/commonjs/LiveTracking.js +134 -0
  64. package/lib/commonjs/LiveTracking.js.map +1 -0
  65. package/lib/commonjs/NativeLiveTracking.js +21 -0
  66. package/lib/commonjs/NativeLiveTracking.js.map +1 -0
  67. package/lib/commonjs/filters/distanceTimeFilter.js +63 -0
  68. package/lib/commonjs/filters/distanceTimeFilter.js.map +1 -0
  69. package/lib/commonjs/index.js +103 -0
  70. package/lib/commonjs/index.js.map +1 -0
  71. package/lib/commonjs/serialization/locationSerializer.js +51 -0
  72. package/lib/commonjs/serialization/locationSerializer.js.map +1 -0
  73. package/lib/commonjs/types.js +77 -0
  74. package/lib/commonjs/types.js.map +1 -0
  75. package/lib/commonjs/utils/distance.js +63 -0
  76. package/lib/commonjs/utils/distance.js.map +1 -0
  77. package/lib/commonjs/utils/retry.js +80 -0
  78. package/lib/commonjs/utils/retry.js.map +1 -0
  79. package/lib/commonjs/validation.js +463 -0
  80. package/lib/commonjs/validation.js.map +1 -0
  81. package/lib/module/EventEmitter.js +105 -0
  82. package/lib/module/EventEmitter.js.map +1 -0
  83. package/lib/module/LiveTracking.js +127 -0
  84. package/lib/module/LiveTracking.js.map +1 -0
  85. package/lib/module/NativeLiveTracking.js +16 -0
  86. package/lib/module/NativeLiveTracking.js.map +1 -0
  87. package/lib/module/filters/distanceTimeFilter.js +58 -0
  88. package/lib/module/filters/distanceTimeFilter.js.map +1 -0
  89. package/lib/module/index.js +32 -0
  90. package/lib/module/index.js.map +1 -0
  91. package/lib/module/serialization/locationSerializer.js +45 -0
  92. package/lib/module/serialization/locationSerializer.js.map +1 -0
  93. package/lib/module/types.js +94 -0
  94. package/lib/module/types.js.map +1 -0
  95. package/lib/module/utils/distance.js +56 -0
  96. package/lib/module/utils/distance.js.map +1 -0
  97. package/lib/module/utils/retry.js +72 -0
  98. package/lib/module/utils/retry.js.map +1 -0
  99. package/lib/module/validation.js +456 -0
  100. package/lib/module/validation.js.map +1 -0
  101. package/lib/typescript/EventEmitter.d.ts +65 -0
  102. package/lib/typescript/EventEmitter.d.ts.map +1 -0
  103. package/lib/typescript/LiveTracking.d.ts +23 -0
  104. package/lib/typescript/LiveTracking.d.ts.map +1 -0
  105. package/lib/typescript/NativeLiveTracking.d.ts +25 -0
  106. package/lib/typescript/NativeLiveTracking.d.ts.map +1 -0
  107. package/lib/typescript/filters/distanceTimeFilter.d.ts +44 -0
  108. package/lib/typescript/filters/distanceTimeFilter.d.ts.map +1 -0
  109. package/lib/typescript/index.d.ts +21 -0
  110. package/lib/typescript/index.d.ts.map +1 -0
  111. package/lib/typescript/serialization/locationSerializer.d.ts +39 -0
  112. package/lib/typescript/serialization/locationSerializer.d.ts.map +1 -0
  113. package/lib/typescript/types.d.ts +217 -0
  114. package/lib/typescript/types.d.ts.map +1 -0
  115. package/lib/typescript/utils/distance.d.ts +38 -0
  116. package/lib/typescript/utils/distance.d.ts.map +1 -0
  117. package/lib/typescript/utils/retry.d.ts +60 -0
  118. package/lib/typescript/utils/retry.d.ts.map +1 -0
  119. package/lib/typescript/validation.d.ts +26 -0
  120. package/lib/typescript/validation.d.ts.map +1 -0
  121. package/package.json +126 -0
  122. package/react-native-live-tracking.podspec +47 -0
  123. package/src/EventEmitter.ts +118 -0
  124. package/src/LiveTracking.ts +159 -0
  125. package/src/NativeLiveTracking.ts +29 -0
  126. package/src/filters/distanceTimeFilter.ts +75 -0
  127. package/src/index.ts +51 -0
  128. package/src/serialization/locationSerializer.ts +57 -0
  129. package/src/types.ts +252 -0
  130. package/src/utils/distance.ts +68 -0
  131. package/src/utils/retry.ts +75 -0
  132. package/src/validation.ts +552 -0
@@ -0,0 +1,335 @@
1
+ package com.livetracking
2
+
3
+ import com.livetracking.queue.QueuedLocation
4
+ import com.livetracking.queue.QueuedLocationDao
5
+ import io.mockk.*
6
+ import org.junit.Assert.*
7
+ import org.junit.Before
8
+ import org.junit.Test
9
+ import java.util.UUID
10
+ import java.util.concurrent.ExecutorService
11
+ import java.util.concurrent.Executors
12
+ import java.util.concurrent.Future
13
+ import java.util.concurrent.Callable
14
+
15
+ /**
16
+ * Unit tests for QueueEngine and QueuedLocation.
17
+ *
18
+ * NOTE: QueueEngine depends on Room Database (TrackingDatabase) which requires Android Context.
19
+ * These tests verify:
20
+ * - QueuedLocation data class structure and field correctness
21
+ * - UUID generation for unique IDs
22
+ * - Executor-based background threading pattern
23
+ * - DAO interface contract
24
+ *
25
+ * Full Room database integration tests (insert, query, delete) require instrumented tests
26
+ * (androidTest) running on a device or emulator with a real or in-memory Room database.
27
+ */
28
+ class QueueEngineTest {
29
+
30
+ private lateinit var mockDao: QueuedLocationDao
31
+ private lateinit var executor: ExecutorService
32
+
33
+ @Before
34
+ fun setup() {
35
+ mockDao = mockk(relaxed = true)
36
+ executor = Executors.newSingleThreadExecutor()
37
+ }
38
+
39
+ // --- QueuedLocation Data Class Tests ---
40
+
41
+ @Test
42
+ fun `QueuedLocation has all required fields`() {
43
+ val location = QueuedLocation(
44
+ id = "test-id-123",
45
+ latitude = 37.7749,
46
+ longitude = -122.4194,
47
+ timestamp = 1700000000000L,
48
+ accuracy = 5.0f,
49
+ speed = 2.5f,
50
+ altitude = 100.0,
51
+ bearing = 180.0f,
52
+ createdAt = 1700000000100L
53
+ )
54
+
55
+ assertEquals("test-id-123", location.id)
56
+ assertEquals(37.7749, location.latitude, 0.0001)
57
+ assertEquals(-122.4194, location.longitude, 0.0001)
58
+ assertEquals(1700000000000L, location.timestamp)
59
+ assertEquals(5.0f, location.accuracy, 0.01f)
60
+ assertEquals(2.5f, location.speed!!, 0.01f)
61
+ assertEquals(100.0, location.altitude!!, 0.01)
62
+ assertEquals(180.0f, location.bearing!!, 0.01f)
63
+ assertEquals(1700000000100L, location.createdAt)
64
+ }
65
+
66
+ @Test
67
+ fun `QueuedLocation allows nullable speed`() {
68
+ val location = QueuedLocation(
69
+ id = "test-id",
70
+ latitude = 0.0,
71
+ longitude = 0.0,
72
+ timestamp = 0L,
73
+ accuracy = 0.0f,
74
+ speed = null,
75
+ altitude = null,
76
+ bearing = null,
77
+ createdAt = 0L
78
+ )
79
+
80
+ assertNull(location.speed)
81
+ }
82
+
83
+ @Test
84
+ fun `QueuedLocation allows nullable altitude`() {
85
+ val location = QueuedLocation(
86
+ id = "test-id",
87
+ latitude = 0.0,
88
+ longitude = 0.0,
89
+ timestamp = 0L,
90
+ accuracy = 0.0f,
91
+ speed = 1.0f,
92
+ altitude = null,
93
+ bearing = null,
94
+ createdAt = 0L
95
+ )
96
+
97
+ assertNull(location.altitude)
98
+ }
99
+
100
+ @Test
101
+ fun `QueuedLocation allows nullable bearing`() {
102
+ val location = QueuedLocation(
103
+ id = "test-id",
104
+ latitude = 0.0,
105
+ longitude = 0.0,
106
+ timestamp = 0L,
107
+ accuracy = 0.0f,
108
+ speed = 1.0f,
109
+ altitude = 50.0,
110
+ bearing = null,
111
+ createdAt = 0L
112
+ )
113
+
114
+ assertNull(location.bearing)
115
+ }
116
+
117
+ @Test
118
+ fun `QueuedLocation data class supports equality`() {
119
+ val loc1 = QueuedLocation(
120
+ id = "same-id",
121
+ latitude = 1.0,
122
+ longitude = 2.0,
123
+ timestamp = 100L,
124
+ accuracy = 5.0f,
125
+ speed = null,
126
+ altitude = null,
127
+ bearing = null,
128
+ createdAt = 200L
129
+ )
130
+
131
+ val loc2 = QueuedLocation(
132
+ id = "same-id",
133
+ latitude = 1.0,
134
+ longitude = 2.0,
135
+ timestamp = 100L,
136
+ accuracy = 5.0f,
137
+ speed = null,
138
+ altitude = null,
139
+ bearing = null,
140
+ createdAt = 200L
141
+ )
142
+
143
+ assertEquals(loc1, loc2)
144
+ }
145
+
146
+ @Test
147
+ fun `QueuedLocation data class supports copy`() {
148
+ val original = QueuedLocation(
149
+ id = "original-id",
150
+ latitude = 1.0,
151
+ longitude = 2.0,
152
+ timestamp = 100L,
153
+ accuracy = 5.0f,
154
+ speed = null,
155
+ altitude = null,
156
+ bearing = null,
157
+ createdAt = 200L
158
+ )
159
+
160
+ val copy = original.copy(latitude = 99.0)
161
+
162
+ assertEquals("original-id", copy.id)
163
+ assertEquals(99.0, copy.latitude, 0.0001)
164
+ assertEquals(2.0, copy.longitude, 0.0001)
165
+ }
166
+
167
+ // --- UUID Generation Tests ---
168
+
169
+ @Test
170
+ fun `UUID randomUUID generates unique IDs`() {
171
+ val ids = (1..100).map { UUID.randomUUID().toString() }.toSet()
172
+ assertEquals(100, ids.size) // All 100 should be unique
173
+ }
174
+
175
+ @Test
176
+ fun `UUID randomUUID generates valid UUID format`() {
177
+ val uuid = UUID.randomUUID().toString()
178
+ // UUID format: 8-4-4-4-12 hex characters
179
+ val uuidRegex = Regex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
180
+ assertTrue("UUID should match standard format: $uuid", uuidRegex.matches(uuid))
181
+ }
182
+
183
+ @Test
184
+ fun `UUID is used as primary key for QueuedLocation`() {
185
+ val id = UUID.randomUUID().toString()
186
+ val location = QueuedLocation(
187
+ id = id,
188
+ latitude = 0.0,
189
+ longitude = 0.0,
190
+ timestamp = System.currentTimeMillis(),
191
+ accuracy = 0.0f,
192
+ speed = null,
193
+ altitude = null,
194
+ bearing = null,
195
+ createdAt = System.currentTimeMillis()
196
+ )
197
+
198
+ assertNotNull(location.id)
199
+ assertTrue(location.id.isNotEmpty())
200
+ assertEquals(36, location.id.length) // UUID string length is always 36
201
+ }
202
+
203
+ // --- Executor Background Thread Tests ---
204
+
205
+ @Test
206
+ fun `executor submits tasks and returns Future`() {
207
+ val future: Future<String> = executor.submit(Callable { "result" })
208
+ assertEquals("result", future.get())
209
+ }
210
+
211
+ @Test
212
+ fun `executor runs tasks on background thread`() {
213
+ val mainThread = Thread.currentThread()
214
+ var taskThread: Thread? = null
215
+
216
+ val future = executor.submit(Callable {
217
+ taskThread = Thread.currentThread()
218
+ "done"
219
+ })
220
+ future.get() // Wait for completion
221
+
222
+ assertNotNull(taskThread)
223
+ assertNotEquals(mainThread, taskThread)
224
+ }
225
+
226
+ @Test
227
+ fun `executor processes tasks sequentially with single thread`() {
228
+ val singleThreadExecutor = Executors.newSingleThreadExecutor()
229
+ val executionOrder = mutableListOf<Int>()
230
+
231
+ val futures = (1..5).map { index ->
232
+ singleThreadExecutor.submit(Callable {
233
+ executionOrder.add(index)
234
+ index
235
+ })
236
+ }
237
+
238
+ // Wait for all to complete
239
+ futures.forEach { it.get() }
240
+
241
+ assertEquals(listOf(1, 2, 3, 4, 5), executionOrder)
242
+ singleThreadExecutor.shutdown()
243
+ }
244
+
245
+ // --- DAO Interface Contract Tests ---
246
+
247
+ @Test
248
+ fun `QueuedLocationDao insert is called with correct location`() {
249
+ val location = QueuedLocation(
250
+ id = "test-id",
251
+ latitude = 37.0,
252
+ longitude = -122.0,
253
+ timestamp = 1000L,
254
+ accuracy = 5.0f,
255
+ speed = null,
256
+ altitude = null,
257
+ bearing = null,
258
+ createdAt = 2000L
259
+ )
260
+
261
+ mockDao.insert(location)
262
+
263
+ verify(exactly = 1) { mockDao.insert(location) }
264
+ }
265
+
266
+ @Test
267
+ fun `QueuedLocationDao getOldestBatch respects limit parameter`() {
268
+ val batch = listOf(
269
+ QueuedLocation("1", 0.0, 0.0, 100L, 5.0f, null, null, null, 100L),
270
+ QueuedLocation("2", 0.0, 0.0, 200L, 5.0f, null, null, null, 200L)
271
+ )
272
+
273
+ every { mockDao.getOldestBatch(10) } returns batch
274
+
275
+ val result = mockDao.getOldestBatch(10)
276
+
277
+ assertEquals(2, result.size)
278
+ verify { mockDao.getOldestBatch(10) }
279
+ }
280
+
281
+ @Test
282
+ fun `QueuedLocationDao deleteByIds accepts list of IDs`() {
283
+ val ids = listOf("id-1", "id-2", "id-3")
284
+
285
+ mockDao.deleteByIds(ids)
286
+
287
+ verify(exactly = 1) { mockDao.deleteByIds(ids) }
288
+ }
289
+
290
+ @Test
291
+ fun `QueuedLocationDao getCount returns integer`() {
292
+ every { mockDao.getCount() } returns 42
293
+
294
+ val count = mockDao.getCount()
295
+
296
+ assertEquals(42, count)
297
+ }
298
+
299
+ // --- Enqueue Pattern Tests ---
300
+
301
+ @Test
302
+ fun `enqueue pattern creates QueuedLocation with UUID and current time`() {
303
+ val beforeTime = System.currentTimeMillis()
304
+
305
+ val location = QueuedLocation(
306
+ id = UUID.randomUUID().toString(),
307
+ latitude = 37.7749,
308
+ longitude = -122.4194,
309
+ timestamp = 1700000000000L,
310
+ accuracy = 10.0f,
311
+ speed = 5.0f,
312
+ altitude = 50.0,
313
+ bearing = 90.0f,
314
+ createdAt = System.currentTimeMillis()
315
+ )
316
+
317
+ val afterTime = System.currentTimeMillis()
318
+
319
+ // Verify UUID format
320
+ assertTrue(location.id.length == 36)
321
+
322
+ // Verify createdAt is approximately now
323
+ assertTrue(location.createdAt >= beforeTime)
324
+ assertTrue(location.createdAt <= afterTime)
325
+
326
+ // Verify passed parameters are stored correctly
327
+ assertEquals(37.7749, location.latitude, 0.0001)
328
+ assertEquals(-122.4194, location.longitude, 0.0001)
329
+ assertEquals(1700000000000L, location.timestamp)
330
+ assertEquals(10.0f, location.accuracy, 0.01f)
331
+ assertEquals(5.0f, location.speed!!, 0.01f)
332
+ assertEquals(50.0, location.altitude!!, 0.01)
333
+ assertEquals(90.0f, location.bearing!!, 0.01f)
334
+ }
335
+ }