capacitor-mobilecron 0.2.2 → 0.2.4

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.
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(File.dirname(__FILE__), 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapacitorMobilecron'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = 'https://github.com/rogelioRuiz/capacitor-mobilecron'
11
+ s.author = 'Rogelio Ruiz'
12
+ s.source = { :git => 'https://github.com/rogelioRuiz/capacitor-mobilecron.git', :tag => s.version.to_s }
13
+
14
+ s.source_files = 'ios/Plugin/**/*.{swift,h,m}'
15
+ s.ios.deployment_target = '14.0'
16
+
17
+ s.dependency 'Capacitor'
18
+ s.swift_version = '5.9'
19
+ end
@@ -29,6 +29,19 @@ export interface MobileCronPlugin {
29
29
  source: WakeSource;
30
30
  paused?: boolean;
31
31
  }) => void): Promise<PluginListenerHandle>;
32
+ testNativeEvaluate(): Promise<{
33
+ firedCount: number;
34
+ }>;
35
+ testSetNextDueAt(options: {
36
+ id: string;
37
+ nextDueAtMs: number;
38
+ }): Promise<void>;
39
+ testInjectPendingEvent(options: {
40
+ event: Record<string, unknown>;
41
+ }): Promise<void>;
42
+ testGetPendingCount(): Promise<{
43
+ count: number;
44
+ }>;
32
45
  }
33
46
  export interface CronJobOptions {
34
47
  name: string;
package/dist/esm/web.d.ts CHANGED
@@ -33,4 +33,17 @@ export declare class MobileCronWeb extends WebPlugin implements MobileCronPlugin
33
33
  source: WakeSource;
34
34
  paused?: boolean;
35
35
  }) => void): Promise<PluginListenerHandle>;
36
+ testNativeEvaluate(): Promise<{
37
+ firedCount: number;
38
+ }>;
39
+ testSetNextDueAt(_options: {
40
+ id: string;
41
+ nextDueAtMs: number;
42
+ }): Promise<void>;
43
+ testInjectPendingEvent(_options: {
44
+ event: Record<string, unknown>;
45
+ }): Promise<void>;
46
+ testGetPendingCount(): Promise<{
47
+ count: number;
48
+ }>;
36
49
  }
package/dist/esm/web.js CHANGED
@@ -51,4 +51,16 @@ export class MobileCronWeb extends WebPlugin {
51
51
  async addListener(eventName, listenerFunc) {
52
52
  return super.addListener(eventName, listenerFunc);
53
53
  }
54
+ async testNativeEvaluate() {
55
+ return { firedCount: 0 };
56
+ }
57
+ async testSetNextDueAt(_options) {
58
+ return;
59
+ }
60
+ async testInjectPendingEvent(_options) {
61
+ return;
62
+ }
63
+ async testGetPendingCount() {
64
+ return { count: 0 };
65
+ }
54
66
  }
@@ -16,7 +16,10 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
16
16
  CAPPluginMethod(name: "resumeAll", returnType: CAPPluginReturnPromise),
17
17
  CAPPluginMethod(name: "setMode", returnType: CAPPluginReturnPromise),
18
18
  CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise),
19
- CAPPluginMethod(name: "__testNativeEvaluate", returnType: CAPPluginReturnPromise)
19
+ CAPPluginMethod(name: "testNativeEvaluate", returnType: CAPPluginReturnPromise),
20
+ CAPPluginMethod(name: "testSetNextDueAt", returnType: CAPPluginReturnPromise),
21
+ CAPPluginMethod(name: "testInjectPendingEvent", returnType: CAPPluginReturnPromise),
22
+ CAPPluginMethod(name: "testGetPendingCount", returnType: CAPPluginReturnPromise)
20
23
  ]
21
24
 
22
25
  private static let storageKey = "mobilecron:state"
@@ -94,6 +97,8 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
94
97
  guard let data = try? JSONSerialization.data(withJSONObject: state),
95
98
  let raw = String(data: data, encoding: .utf8) else { return }
96
99
  UserDefaults.standard.set(raw, forKey: Self.storageKey)
100
+ // Force immediate disk flush so state survives simctl terminate / force-kill
101
+ UserDefaults.standard.synchronize()
97
102
  }
98
103
 
99
104
  // ── Background wake ───────────────────────────────────────────────────────
@@ -129,6 +134,7 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
129
134
  if let newData = try? JSONSerialization.data(withJSONObject: state),
130
135
  let newRaw = String(data: newData, encoding: .utf8) {
131
136
  UserDefaults.standard.set(newRaw, forKey: Self.storageKey)
137
+ UserDefaults.standard.synchronize()
132
138
  }
133
139
  }
134
140
 
@@ -255,14 +261,67 @@ public class MobileCronPlugin: CAPPlugin, CAPBridgedPlugin {
255
261
  call.resolve(buildStatus())
256
262
  }
257
263
 
258
- /// E2E test hook: directly calls NativeJobEvaluator.evaluate() and fires pending events.
259
- /// Used to test native background evaluation without BGTaskScheduler.
260
- @objc func __testNativeEvaluate(_ call: CAPPluginCall) {
264
+ // ── E2E test hooks (not for production use) ───────────────────────────────
265
+
266
+ /// Calls NativeJobEvaluator.evaluate() directly and fires pending events.
267
+ @objc func testNativeEvaluate(_ call: CAPPluginCall) {
261
268
  let events = NativeJobEvaluator.evaluate(source: "test_trigger")
262
269
  firePendingNativeEvents()
263
270
  call.resolve(["firedCount": events.count])
264
271
  }
265
272
 
273
+ /// Sets a job's nextDueAt in memory and saves to UserDefaults.
274
+ /// Allows tests to mark a job as "due" without waiting for real time to pass.
275
+ @objc func testSetNextDueAt(_ call: CAPPluginCall) {
276
+ guard let id = call.getString("id"),
277
+ let nextDueAtMs = call.getInt("nextDueAtMs"),
278
+ jobs[id] != nil else {
279
+ call.reject("id or nextDueAtMs missing, or job not found")
280
+ return
281
+ }
282
+ jobs[id]?["nextDueAt"] = nextDueAtMs
283
+ saveState()
284
+ call.resolve()
285
+ }
286
+
287
+ /// Appends a pending native event to UserDefaults storage.
288
+ /// Allows tests to simulate what NativeJobEvaluator writes during background execution.
289
+ @objc func testInjectPendingEvent(_ call: CAPPluginCall) {
290
+ guard let event = call.getObject("event") else {
291
+ call.reject("event is required")
292
+ return
293
+ }
294
+ guard let raw = UserDefaults.standard.string(forKey: Self.storageKey),
295
+ let data = raw.data(using: .utf8),
296
+ var state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
297
+ call.reject("State not found in UserDefaults")
298
+ return
299
+ }
300
+ var pending = (state["pendingNativeEvents"] as? [[String: Any]]) ?? []
301
+ pending.append(event)
302
+ state["pendingNativeEvents"] = pending
303
+ guard let newData = try? JSONSerialization.data(withJSONObject: state),
304
+ let newRaw = String(data: newData, encoding: .utf8) else {
305
+ call.reject("Serialization failed")
306
+ return
307
+ }
308
+ UserDefaults.standard.set(newRaw, forKey: Self.storageKey)
309
+ UserDefaults.standard.synchronize()
310
+ call.resolve()
311
+ }
312
+
313
+ /// Returns the count of pendingNativeEvents currently in UserDefaults storage.
314
+ @objc func testGetPendingCount(_ call: CAPPluginCall) {
315
+ guard let raw = UserDefaults.standard.string(forKey: Self.storageKey),
316
+ let data = raw.data(using: .utf8),
317
+ let state = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
318
+ call.resolve(["count": 0])
319
+ return
320
+ }
321
+ let count = (state["pendingNativeEvents"] as? [[String: Any]])?.count ?? 0
322
+ call.resolve(["count": count])
323
+ }
324
+
266
325
  private func buildStatus() -> [String: Any] {
267
326
  let diagnostics = bgManager?.status ?? .init()
268
327
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-mobilecron",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Capacitor scheduling primitive that emits job due events across web, Android, and iOS",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -20,6 +20,7 @@
20
20
  "ios",
21
21
  "dist",
22
22
  "src",
23
+ "CapacitorMobilecron.podspec",
23
24
  "package.json",
24
25
  "README.md"
25
26
  ],
@@ -59,6 +60,7 @@
59
60
  "test:watch": "vitest",
60
61
  "test:coverage": "vitest run --coverage",
61
62
  "test:e2e": "node tests/e2e/test-e2e.mjs",
63
+ "test:e2e:ios": "node tests/e2e/test-e2e-ios.mjs",
62
64
  "prepack": "npm run build"
63
65
  },
64
66
  "peerDependencies": {
@@ -17,6 +17,12 @@ export interface MobileCronPlugin {
17
17
  addListener(event: 'overdueJobs', handler: (data: OverdueEvent) => void): Promise<PluginListenerHandle>
18
18
  addListener(event: 'statusChanged', handler: (data: CronStatus) => void): Promise<PluginListenerHandle>
19
19
  addListener(event: 'nativeWake', handler: (data: { source: WakeSource; paused?: boolean }) => void): Promise<PluginListenerHandle>
20
+
21
+ // E2E test hooks (not for production use)
22
+ testNativeEvaluate(): Promise<{ firedCount: number }>
23
+ testSetNextDueAt(options: { id: string; nextDueAtMs: number }): Promise<void>
24
+ testInjectPendingEvent(options: { event: Record<string, unknown> }): Promise<void>
25
+ testGetPendingCount(): Promise<{ count: number }>
20
26
  }
21
27
 
22
28
  export interface CronJobOptions {
package/src/web.ts CHANGED
@@ -83,4 +83,20 @@ export class MobileCronWeb extends WebPlugin implements MobileCronPlugin {
83
83
  async addListener(eventName: string, listenerFunc: (data: any) => void): Promise<PluginListenerHandle> {
84
84
  return super.addListener(eventName, listenerFunc)
85
85
  }
86
+
87
+ async testNativeEvaluate(): Promise<{ firedCount: number }> {
88
+ return { firedCount: 0 }
89
+ }
90
+
91
+ async testSetNextDueAt(_options: { id: string; nextDueAtMs: number }): Promise<void> {
92
+ return
93
+ }
94
+
95
+ async testInjectPendingEvent(_options: { event: Record<string, unknown> }): Promise<void> {
96
+ return
97
+ }
98
+
99
+ async testGetPendingCount(): Promise<{ count: number }> {
100
+ return { count: 0 }
101
+ }
86
102
  }