@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.
- package/CHANGELOG.md +178 -0
- package/README.md +12 -3
- package/package.json +5 -2
- package/skills/sdk-rn-ads/SKILL.md +3 -2
- package/src/config/validate.ts +3 -1
- package/src/core/adsInstance.ts +85 -1872
- package/src/core/consentFlow.ts +1 -20
- package/src/core/instance/consent.ts +162 -0
- package/src/core/instance/context.ts +328 -0
- package/src/core/instance/gate.ts +147 -0
- package/src/core/instance/init.ts +262 -0
- package/src/core/instance/internals.ts +138 -0
- package/src/core/instance/load.ts +426 -0
- package/src/core/instance/show.ts +202 -0
- package/src/core/instance/types.ts +198 -0
- package/src/core/requestThrottle.ts +11 -17
- package/src/core/retryPolicy.ts +12 -2
- package/src/core/timers.ts +17 -69
- package/src/diagnostics/errors.ts +52 -63
- package/src/diagnostics/events.ts +77 -66
- package/src/engine/admobBanner.tsx +55 -0
- package/src/engine/admobEngine.ts +38 -18
- package/src/engine/admobErrors.ts +44 -0
- package/src/engine/port.ts +46 -1
- package/src/engine/testAdUnits.ts +0 -5
- package/src/index.ts +19 -1
- package/src/react/AdsBanner.tsx +94 -79
- package/src/react/AdsProvider.tsx +6 -7
- package/src/react/hooks.ts +26 -31
- package/src/testing/clock.ts +75 -0
- package/src/testing/conformance.ts +112 -0
- package/src/testing/index.ts +43 -70
package/src/testing/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|