@chipmobilesdk/rn-ads 0.3.0 → 0.5.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.
@@ -26,6 +26,7 @@ import type {
26
26
  EngineConsentInfo,
27
27
  EngineConsentOptions,
28
28
  EnginePrivacy,
29
+ EngineRequestOptions,
29
30
  } from '../engine/port';
30
31
 
31
32
  /**
@@ -97,9 +98,27 @@ export function createTestClock(startMs = 1_000_000): TestClock {
97
98
  export type FakeConsentCall =
98
99
  | { readonly kind: 'requestInfoUpdate'; readonly options: EngineConsentOptions }
99
100
  | { readonly kind: 'showFormIfRequired' }
101
+ | { readonly kind: 'showPrivacyOptionsForm' }
100
102
  | { readonly kind: 'getInfo' }
101
103
  | { readonly kind: 'reset' };
102
104
 
105
+ /**
106
+ * A privacy options form the test is holding open (0.4.0).
107
+ *
108
+ * Settle it whenever the test chooses, which is what lets `busy`, a second
109
+ * call, a consent update, and `dispose()` all be exercised *while* the form is
110
+ * on screen — in any order, rather than only the one a resolved promise allows.
111
+ */
112
+ export interface HeldPrivacyOptionsForm {
113
+ /**
114
+ * The user closes the form. `info` is what the provider reports afterwards,
115
+ * merged over the current information; omit it for a user who changed nothing.
116
+ */
117
+ close(info?: Partial<EngineConsentInfo>): void;
118
+ /** The form fails while open. */
119
+ fail(cause?: unknown): void;
120
+ }
121
+
103
122
  export type FakeEngineCall =
104
123
  | { readonly kind: 'initialize'; readonly privacy: EnginePrivacy }
105
124
  | {
@@ -107,6 +126,8 @@ export type FakeEngineCall =
107
126
  readonly format: FullScreenFormat;
108
127
  readonly adUnitId: string;
109
128
  readonly handleId: string;
129
+ /** Per-request privacy as the package sent it (FR-017's personalization half). */
130
+ readonly request?: EngineRequestOptions;
110
131
  }
111
132
  | { readonly kind: 'show'; readonly handleId: string }
112
133
  | { readonly kind: 'dispose'; readonly handleId: string };
@@ -133,6 +154,7 @@ const DEFAULT_CONSENT: EngineConsentInfo = {
133
154
  status: 'not_required',
134
155
  canRequestAds: true,
135
156
  isFormAvailable: false,
157
+ privacyOptions: 'not_required',
136
158
  };
137
159
 
138
160
  export interface FakeAdsEngine extends AdsEngine {
@@ -166,6 +188,19 @@ export interface FakeAdsEngine extends AdsEngine {
166
188
  failConsentRequest(cause?: unknown): void;
167
189
  /** Makes `showFormIfRequired` reject — dismissal is not consent (FR-106). */
168
190
  failConsentForm(cause?: unknown): void;
191
+
192
+ /**
193
+ * What the privacy options form reports when the user closes it, merged over
194
+ * the current information. Unset, the form closes with nothing changed — the
195
+ * same as a user who opens it and backs out.
196
+ */
197
+ setPrivacyOptionsFormResult(info: Partial<EngineConsentInfo>): void;
198
+ /** Makes `showPrivacyOptionsForm` reject — a failure is never consent (FR-106). */
199
+ failPrivacyOptionsForm(cause?: unknown): void;
200
+ /** Holds the next privacy options form open until the test settles it. */
201
+ holdPrivacyOptionsForm(): HeldPrivacyOptionsForm;
202
+ /** Makes `getInfo` reject, for the case where even the re-read after a failed form fails. */
203
+ failConsentInfoRead(cause?: unknown): void;
169
204
  }
170
205
 
171
206
  export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEngine {
@@ -175,6 +210,11 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
175
210
  let consentInfo: EngineConsentInfo = { ...DEFAULT_CONSENT, ...options.consentInfo };
176
211
  let consentRequestFailure: unknown;
177
212
  let consentFormFailure: unknown;
213
+ let privacyFormFailure: unknown;
214
+ let privacyFormResult: Partial<EngineConsentInfo> = {};
215
+ let consentReadFailure: unknown;
216
+ /** Set by `holdPrivacyOptionsForm`; consumed by the next form. */
217
+ let heldForm: Promise<Partial<EngineConsentInfo>> | null = null;
178
218
 
179
219
  const consent: ConsentPort = {
180
220
  async requestInfoUpdate(consentOptions) {
@@ -192,18 +232,49 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
192
232
  // A presented form that the user completes moves the status on. The
193
233
  // fake only does this when a form was actually required, mirroring the
194
234
  // provider: showing nothing cannot change an answer.
235
+ // A region that required a form is one that requires a way back into
236
+ // it, which is what the provider reports once consent is obtained.
195
237
  if (consentInfo.status === 'required' && consentInfo.isFormAvailable) {
196
- consentInfo = { status: 'obtained', canRequestAds: true, isFormAvailable: false };
238
+ consentInfo = {
239
+ status: 'obtained',
240
+ canRequestAds: true,
241
+ isFormAvailable: false,
242
+ privacyOptions: 'required',
243
+ };
244
+ }
245
+ return consentInfo;
246
+ },
247
+ async showPrivacyOptionsForm() {
248
+ consentCalls.push({ kind: 'showPrivacyOptionsForm' });
249
+ const held = heldForm;
250
+ heldForm = null;
251
+ if (held) {
252
+ // Rejects if the test calls `fail`, which is exactly the provider's
253
+ // failure path; resolves with what the user left behind otherwise.
254
+ consentInfo = { ...consentInfo, ...(await held) };
255
+ return consentInfo;
197
256
  }
257
+ if (privacyFormFailure !== undefined) {
258
+ throw privacyFormFailure;
259
+ }
260
+ consentInfo = { ...consentInfo, ...privacyFormResult };
198
261
  return consentInfo;
199
262
  },
200
263
  async getInfo() {
201
264
  consentCalls.push({ kind: 'getInfo' });
265
+ if (consentReadFailure !== undefined) {
266
+ throw consentReadFailure;
267
+ }
202
268
  return consentInfo;
203
269
  },
204
270
  reset() {
205
271
  consentCalls.push({ kind: 'reset' });
206
- consentInfo = { status: 'unknown', canRequestAds: false, isFormAvailable: true };
272
+ consentInfo = {
273
+ status: 'unknown',
274
+ canRequestAds: false,
275
+ isFormAvailable: true,
276
+ privacyOptions: 'unknown',
277
+ };
207
278
  },
208
279
  };
209
280
  const listeners = new Map<string, Set<(event: EngineAdEvent) => void>>();
@@ -238,6 +309,37 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
238
309
  consentFormFailure = cause;
239
310
  },
240
311
 
312
+ setPrivacyOptionsFormResult(info) {
313
+ privacyFormResult = info;
314
+ },
315
+
316
+ failPrivacyOptionsForm(cause = new Error('privacy options form failed')) {
317
+ privacyFormFailure = cause;
318
+ },
319
+
320
+ holdPrivacyOptionsForm() {
321
+ let settle!: {
322
+ resolve: (info: Partial<EngineConsentInfo>) => void;
323
+ reject: (cause: unknown) => void;
324
+ };
325
+ const promise = new Promise<Partial<EngineConsentInfo>>((resolve, reject) => {
326
+ settle = { resolve, reject };
327
+ });
328
+ heldForm = promise;
329
+ return {
330
+ close(info = {}) {
331
+ settle.resolve(info);
332
+ },
333
+ fail(cause = new Error('privacy options form failed')) {
334
+ settle.reject(cause);
335
+ },
336
+ };
337
+ },
338
+
339
+ failConsentInfoRead(cause = new Error('consent information read failed')) {
340
+ consentReadFailure = cause;
341
+ },
342
+
241
343
  isAvailable() {
242
344
  return options.available !== false;
243
345
  },
@@ -249,10 +351,10 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
249
351
  }
250
352
  },
251
353
 
252
- async createFullScreen(format, adUnitId) {
354
+ async createFullScreen(format, adUnitId, request) {
253
355
  counter += 1;
254
356
  const id = `fake-${counter}`;
255
- record({ kind: 'createFullScreen', format, adUnitId, handleId: id });
357
+ record({ kind: 'createFullScreen', format, adUnitId, handleId: id, request });
256
358
  live.add(id);
257
359
  // A real provider delivers `loaded` to a listener attached after the
258
360
  // ad object exists, so the callback is queued here and flushed when