@chipmobilesdk/rn-ads 0.5.1 → 0.7.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.
@@ -16,85 +16,23 @@
16
16
  * so test doubles cannot reach a production bundle.
17
17
  */
18
18
 
19
- import type { FullScreenFormat, Reward, Unsubscribe } from '../config/types';
20
- import type { Scheduler } from '../core/timers';
19
+ import type { AdFormat, FullScreenFormat, Reward, Unsubscribe } from '../config/types';
20
+ export { createTestClock, type TestClock } from './clock';
21
+ import { createTestClock, type TestClock } from './clock';
22
+
23
+ import { testAdUnitFor } from '../engine/testAdUnits';
21
24
  import type {
22
25
  AdsEngine,
23
26
  ConsentPort,
24
27
  EngineAdEvent,
25
28
  EngineAdHandle,
29
+ EngineBannerProps,
26
30
  EngineConsentInfo,
27
31
  EngineConsentOptions,
28
32
  EnginePrivacy,
29
33
  EngineRequestOptions,
30
34
  } from '../engine/port';
31
35
 
32
- /**
33
- * A clock a test can move by hand.
34
- *
35
- * Retry backoff, app-open expiry, the undecided-state warning, and the
36
- * ad-originated-return windows are all time-dependent, and all four must be
37
- * assertable exactly rather than approximately. Advancing a clock makes "two
38
- * retries, 2s then 4s" a precise assertion; sleeping would make it a flaky one.
39
- *
40
- * Callbacks fire in due order, and a callback scheduled *during* an advance is
41
- * honored within the same advance if its deadline has also passed — matching how
42
- * real timers behave when work chains.
43
- */
44
- export interface TestClock {
45
- now(): number;
46
- advance(ms: number): void;
47
- readonly scheduler: Scheduler;
48
- pending(): number;
49
- }
50
-
51
- export function createTestClock(startMs = 1_000_000): TestClock {
52
- let current = startMs;
53
- let sequence = 0;
54
- const timers = new Map<number, { at: number; callback: () => void }>();
55
-
56
- const scheduler: Scheduler = {
57
- setTimeout(callback, ms) {
58
- sequence += 1;
59
- timers.set(sequence, { at: current + ms, callback });
60
- return sequence;
61
- },
62
- clearTimeout(handle) {
63
- timers.delete(handle as number);
64
- },
65
- };
66
-
67
- return {
68
- now: () => current,
69
- advance(ms) {
70
- const target = current + ms;
71
- // Loop rather than iterate once: a fired callback may schedule another
72
- // timer that is also due before `target`, which is exactly what a retry
73
- // chain does.
74
- for (;;) {
75
- let nextId: number | null = null;
76
- let nextAt = Number.POSITIVE_INFINITY;
77
- for (const [id, timer] of timers) {
78
- if (timer.at <= target && timer.at < nextAt) {
79
- nextAt = timer.at;
80
- nextId = id;
81
- }
82
- }
83
- if (nextId === null) {
84
- break;
85
- }
86
- const timer = timers.get(nextId)!;
87
- timers.delete(nextId);
88
- current = timer.at;
89
- timer.callback();
90
- }
91
- current = target;
92
- },
93
- scheduler,
94
- pending: () => timers.size,
95
- };
96
- }
97
-
98
36
  export type FakeConsentCall =
99
37
  | { readonly kind: 'requestInfoUpdate'; readonly options: EngineConsentOptions }
100
38
  | { readonly kind: 'showFormIfRequired' }
@@ -203,6 +141,20 @@ export interface FakeAdsEngine extends AdsEngine {
203
141
  failConsentInfoRead(cause?: unknown): void;
204
142
  }
205
143
 
144
+ /**
145
+ * Runs `task` on the next microtask, which is when a provider's callbacks would
146
+ * arrive. Written out because React Native's type declarations omit
147
+ * `queueMicrotask`, although Hermes has it.
148
+ */
149
+ function nextMicrotask(task: () => void): void {
150
+ Promise.resolve().then(task);
151
+ }
152
+
153
+ /** The fake engine's banner component. */
154
+ function FakeBanner(_props: EngineBannerProps): null {
155
+ return null;
156
+ }
157
+
206
158
  export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEngine {
207
159
  const calls: FakeEngineCall[] = [];
208
160
  const consentCalls: FakeConsentCall[] = [];
@@ -344,6 +296,14 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
344
296
  return options.available !== false;
345
297
  },
346
298
 
299
+ // Renders nothing. A test finds it in the tree by its `adUnitId` prop and
300
+ // calls `onLoaded`, `onFailed`, `onImpression` or `onClicked` itself, which
301
+ // is how a banner is driven without mocking a native module (ADS-12).
302
+ banner: {
303
+ isAvailable: () => options.available !== false,
304
+ Component: FakeBanner,
305
+ },
306
+
347
307
  async initialize(privacy) {
348
308
  record({ kind: 'initialize', privacy });
349
309
  if (options.failInitialize !== undefined) {
@@ -375,7 +335,7 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
375
335
  if (!options.neverCallBack) {
376
336
  // `show` is called with a listener already attached, so this one
377
337
  // can go out directly on the next microtask.
378
- queueMicrotask(() => {
338
+ nextMicrotask(() => {
379
339
  for (const listener of Array.from(listeners.get(handle.id) ?? [])) {
380
340
  listener({ type: 'opened' });
381
341
  }
@@ -395,7 +355,7 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
395
355
  const queued = pending.get(handle.id);
396
356
  if (queued) {
397
357
  pending.delete(handle.id);
398
- queueMicrotask(() => {
358
+ nextMicrotask(() => {
399
359
  for (const target of Array.from(listeners.get(handle.id) ?? [])) {
400
360
  target(queued);
401
361
  }
@@ -461,3 +421,16 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
461
421
  },
462
422
  };
463
423
  }
424
+
425
+ export { adsEngineChecks, runAdsEngineConformance } from './conformance';
426
+ export type { AdsEngineHarness, AdsEngineSubject } from './conformance';
427
+
428
+ /** Google's official test ad unit for each format, as the package substitutes them (FR-011). */
429
+ export function allTestAdUnits(): Readonly<Record<AdFormat, string>> {
430
+ return {
431
+ banner: testAdUnitFor('banner'),
432
+ interstitial: testAdUnitFor('interstitial'),
433
+ rewarded: testAdUnitFor('rewarded'),
434
+ app_open: testAdUnitFor('app_open'),
435
+ };
436
+ }