@owlmeans/consent 0.1.18-rc.8 → 0.1.18
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/README.md +229 -0
- package/build/consts.d.ts +84 -23
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +110 -29
- package/build/consts.js.map +1 -1
- package/build/consts.local.d.ts +12 -0
- package/build/consts.local.d.ts.map +1 -0
- package/build/consts.local.js +12 -0
- package/build/consts.local.js.map +1 -0
- package/build/geo/types.d.ts +47 -0
- package/build/geo/types.d.ts.map +1 -0
- package/build/geo/types.js +2 -0
- package/build/geo/types.js.map +1 -0
- package/build/geo.d.ts +4 -0
- package/build/geo.d.ts.map +1 -0
- package/build/geo.js +96 -0
- package/build/geo.js.map +1 -0
- package/build/gtobserver/types.d.ts +75 -0
- package/build/gtobserver/types.d.ts.map +1 -0
- package/build/gtobserver/types.js +2 -0
- package/build/gtobserver/types.js.map +1 -0
- package/build/gtobserver.d.ts +7 -0
- package/build/gtobserver.d.ts.map +1 -0
- package/build/gtobserver.js +142 -0
- package/build/gtobserver.js.map +1 -0
- package/build/i18n/be.json +9 -4
- package/build/i18n/de.json +9 -4
- package/build/i18n/en.json +9 -4
- package/build/i18n/es.json +9 -4
- package/build/i18n/fr.json +9 -4
- package/build/i18n/pl.json +9 -4
- package/build/i18n/ru.json +9 -4
- package/build/i18n/types.d.ts +11 -0
- package/build/i18n/types.d.ts.map +1 -0
- package/build/i18n/types.js +2 -0
- package/build/i18n/types.js.map +1 -0
- package/build/i18n/uk.json +9 -4
- package/build/i18n.d.ts +4 -14
- package/build/i18n.d.ts.map +1 -1
- package/build/i18n.js +12 -27
- package/build/i18n.js.map +1 -1
- package/build/index.d.ts +13 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -4
- package/build/index.js.map +1 -1
- package/build/linker/types.d.ts +59 -0
- package/build/linker/types.d.ts.map +1 -0
- package/build/linker/types.js +2 -0
- package/build/linker/types.js.map +1 -0
- package/build/linker.d.ts +5 -76
- package/build/linker.d.ts.map +1 -1
- package/build/linker.js +290 -339
- package/build/linker.js.map +1 -1
- package/build/plugins/types.d.ts +42 -0
- package/build/plugins/types.d.ts.map +1 -0
- package/build/plugins/types.js +2 -0
- package/build/plugins/types.js.map +1 -0
- package/build/plugins.d.ts +4 -69
- package/build/plugins.d.ts.map +1 -1
- package/build/plugins.js +73 -62
- package/build/plugins.js.map +1 -1
- package/build/storage/types.d.ts +31 -0
- package/build/storage/types.d.ts.map +1 -0
- package/build/storage/types.js +2 -0
- package/build/storage/types.js.map +1 -0
- package/build/storage.d.ts +6 -22
- package/build/storage.d.ts.map +1 -1
- package/build/storage.js +72 -81
- package/build/storage.js.map +1 -1
- package/build/store.d.ts +2 -2
- package/build/store.d.ts.map +1 -1
- package/build/store.js +116 -67
- package/build/store.js.map +1 -1
- package/build/types.d.ts +147 -26
- package/build/types.d.ts.map +1 -1
- package/build/types.local.d.ts +9 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/package.json +3 -3
- package/src/consts.local.ts +16 -0
- package/src/consts.ts +126 -34
- package/src/geo/types.ts +49 -0
- package/src/geo.ts +122 -0
- package/src/gtobserver/types.ts +73 -0
- package/src/gtobserver.ts +182 -0
- package/src/i18n/be.json +9 -4
- package/src/i18n/de.json +9 -4
- package/src/i18n/en.json +9 -4
- package/src/i18n/es.json +9 -4
- package/src/i18n/fr.json +9 -4
- package/src/i18n/pl.json +9 -4
- package/src/i18n/ru.json +9 -4
- package/src/i18n/types.ts +11 -0
- package/src/i18n/uk.json +9 -4
- package/src/i18n.ts +20 -32
- package/src/index.ts +13 -13
- package/src/linker/types.ts +59 -0
- package/src/linker.ts +301 -364
- package/src/plugins/types.ts +42 -0
- package/src/plugins.ts +82 -106
- package/src/storage/types.ts +31 -0
- package/src/storage.ts +75 -77
- package/src/store.ts +122 -69
- package/src/types.local.ts +9 -0
- package/src/types.ts +153 -25
- package/tests/consent.spec.ts +26 -28
- package/tests/geo.spec.ts +474 -0
- package/tests/linker.spec.ts +144 -309
- package/tests/tracking.spec.ts +14 -14
- package/build/functional.d.ts +0 -35
- package/build/functional.d.ts.map +0 -1
- package/build/functional.js +0 -67
- package/build/functional.js.map +0 -1
- package/build/gtm.d.ts +0 -72
- package/build/gtm.d.ts.map +0 -1
- package/build/gtm.js +0 -182
- package/build/gtm.js.map +0 -1
- package/src/functional.ts +0 -73
- package/src/gtm.ts +0 -220
package/tests/linker.spec.ts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from '../src/
|
|
6
|
-
import { consentBootstrapScript } from '../src/gtm.js'
|
|
7
|
-
import {
|
|
8
|
-
consentLinker, consentLinkerScript, decodeConsentLink, encodeConsentLink, stripConsentLinkParam, writeConsentLanguage,
|
|
9
|
-
CONSENT_LINK_MAX_AGE, CONSENT_LINK_PARAM,
|
|
10
|
-
} from '../src/linker.js'
|
|
11
|
-
import { adoptConsentLanguage, consentDomains, decorateConsentUrl, registerConsentPlugin } from '../src/plugins.js'
|
|
2
|
+
import { CONSENT_ANALYTICS, CONSENT_ESSENTIAL, CONSENT_KEY, CONSENT_LANGUAGE_KEY, CONSENT_MARKETING, DEFAULT_CONSENT_CATEGORIES, CONSENT_LINK_MAX_AGE, CONSENT_LINK_PARAM } from '../src/consts.js'
|
|
3
|
+
import { consentModeHelper } from '../src/gtobserver.js'
|
|
4
|
+
import { consentLinkHelper } from '../src/linker.js'
|
|
5
|
+
import { consentPluginHelper } from '../src/plugins.js'
|
|
12
6
|
import { makeConsentStore } from '../src/store.js'
|
|
13
|
-
import {
|
|
14
|
-
import { readConsent, writeConsent } from '../src/storage.js'
|
|
7
|
+
import { consentStorageHelper } from '../src/storage.js'
|
|
15
8
|
import type { ConsentRecord } from '../src/types.js'
|
|
16
9
|
|
|
17
10
|
/** A minimal browser: storage, a settable referrer, and a `location`/`history` pair that actually
|
|
@@ -74,41 +67,40 @@ beforeEach(() => {
|
|
|
74
67
|
env.goto('https://platform.test/dispatcher')
|
|
75
68
|
delete (globalThis as any).dataLayer
|
|
76
69
|
delete (globalThis as any).cookieConsentSetup
|
|
77
|
-
delete (globalThis as any)[CONSENT_PENDING_LANGUAGE]
|
|
78
70
|
})
|
|
79
71
|
|
|
80
72
|
describe('encodeConsentLink / decodeConsentLink', () => {
|
|
81
73
|
test('round-trips a record through the optional categories only', () => {
|
|
82
74
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
83
|
-
const encoded = encodeConsentLink(record)
|
|
84
|
-
const decoded = decodeConsentLink(encoded)
|
|
75
|
+
const encoded = consentLinkHelper.encodeConsentLink(record)
|
|
76
|
+
const decoded = consentLinkHelper.decodeConsentLink(encoded)
|
|
85
77
|
|
|
86
78
|
expect(decoded?.v).toBe(2)
|
|
87
|
-
expect(decoded?.c).toEqual({ [
|
|
79
|
+
expect(decoded?.c).toEqual({ [CONSENT_ANALYTICS]: 1, [CONSENT_MARKETING]: 0 })
|
|
88
80
|
expect(decoded?.t).toBeGreaterThan(0)
|
|
89
81
|
})
|
|
90
82
|
|
|
91
83
|
test('rejects garbage rather than throwing', () => {
|
|
92
|
-
expect(decodeConsentLink('not-base64url-json')).toBeNull()
|
|
93
|
-
expect(decodeConsentLink('')).toBeNull()
|
|
84
|
+
expect(consentLinkHelper.decodeConsentLink('not-base64url-json')).toBeNull()
|
|
85
|
+
expect(consentLinkHelper.decodeConsentLink('')).toBeNull()
|
|
94
86
|
})
|
|
95
87
|
|
|
96
88
|
test('rejects a payload of the wrong version', () => {
|
|
97
89
|
const bad = Buffer.from(JSON.stringify({ v: 1, c: {}, t: nowSeconds() })).toString('base64url')
|
|
98
90
|
|
|
99
|
-
expect(decodeConsentLink(bad)).toBeNull()
|
|
91
|
+
expect(consentLinkHelper.decodeConsentLink(bad)).toBeNull()
|
|
100
92
|
})
|
|
101
93
|
})
|
|
102
94
|
|
|
103
95
|
describe('consentLinker().decorate', () => {
|
|
104
|
-
const plugin = consentLinker()
|
|
96
|
+
const plugin = consentLinkHelper.consentLinker()
|
|
105
97
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
106
98
|
|
|
107
99
|
test('decorates a listed, foreign host', () => {
|
|
108
100
|
const decorated = plugin.decorate!(new URL('https://site.test/legal/cookies'), record, linkerOpts)
|
|
109
101
|
|
|
110
102
|
expect(decorated).not.toBeNull()
|
|
111
|
-
const payload = decodeConsentLink(decorated!.searchParams.get(CONSENT_LINK_PARAM)!)
|
|
103
|
+
const payload = consentLinkHelper.decodeConsentLink(decorated!.searchParams.get(CONSENT_LINK_PARAM)!)
|
|
112
104
|
expect(payload?.c[CONSENT_ANALYTICS]).toBe(1)
|
|
113
105
|
})
|
|
114
106
|
|
|
@@ -134,8 +126,8 @@ describe('consentLinker().decorate', () => {
|
|
|
134
126
|
})
|
|
135
127
|
|
|
136
128
|
describe('consentLinker().adopt — every trust rule', () => {
|
|
137
|
-
const plugin = consentLinker()
|
|
138
|
-
const valid = () => encodeConsentLink({ [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false })
|
|
129
|
+
const plugin = consentLinkHelper.consentLinker()
|
|
130
|
+
const valid = () => consentLinkHelper.encodeConsentLink({ [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false })
|
|
139
131
|
|
|
140
132
|
test('adopts a fresh, referred, fully-covered decision', () => {
|
|
141
133
|
env.goto(`https://platform.test/dispatcher?owlcc=${valid()}`, 'https://site.test/')
|
|
@@ -201,21 +193,21 @@ describe('consentLinker().adopt — every trust rule', () => {
|
|
|
201
193
|
describe('stripConsentLinkParam', () => {
|
|
202
194
|
test('removes only the parameter, keeping the rest and the hash', () => {
|
|
203
195
|
env.goto('https://platform.test/dispatcher?owlcc=abc&next=%2Fhome&ref=site#panel')
|
|
204
|
-
stripConsentLinkParam(linkerOpts)
|
|
196
|
+
consentLinkHelper.stripConsentLinkParam(linkerOpts)
|
|
205
197
|
|
|
206
198
|
expect(env.href()).toBe('https://platform.test/dispatcher?next=%2Fhome&ref=site#panel')
|
|
207
199
|
})
|
|
208
200
|
|
|
209
201
|
test('is a no-op with no parameter present', () => {
|
|
210
202
|
env.goto('https://platform.test/dispatcher?next=%2Fhome')
|
|
211
|
-
stripConsentLinkParam(linkerOpts)
|
|
203
|
+
consentLinkHelper.stripConsentLinkParam(linkerOpts)
|
|
212
204
|
|
|
213
205
|
expect(env.href()).toBe('https://platform.test/dispatcher?next=%2Fhome')
|
|
214
206
|
})
|
|
215
207
|
|
|
216
208
|
test('is a no-op with no linker configured', () => {
|
|
217
209
|
env.goto('https://platform.test/dispatcher?owlcc=abc')
|
|
218
|
-
stripConsentLinkParam({})
|
|
210
|
+
consentLinkHelper.stripConsentLinkParam({})
|
|
219
211
|
|
|
220
212
|
expect(env.href()).toBe('https://platform.test/dispatcher?owlcc=abc')
|
|
221
213
|
})
|
|
@@ -223,8 +215,8 @@ describe('stripConsentLinkParam', () => {
|
|
|
223
215
|
|
|
224
216
|
describe('consentDomains / decorateConsentUrl (registry level)', () => {
|
|
225
217
|
test('discloses the current host plus every plugin-named domain, deduplicated', () => {
|
|
226
|
-
registerConsentPlugin(consentLinker())
|
|
227
|
-
const domains = consentDomains(linkerOpts)
|
|
218
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
219
|
+
const domains = consentPluginHelper.consentDomains(linkerOpts)
|
|
228
220
|
|
|
229
221
|
expect(domains).toContain('platform.test')
|
|
230
222
|
expect(domains).toContain('site.test')
|
|
@@ -232,18 +224,18 @@ describe('consentDomains / decorateConsentUrl (registry level)', () => {
|
|
|
232
224
|
})
|
|
233
225
|
|
|
234
226
|
test('decorateConsentUrl applies the registered plugin to a plain URL', () => {
|
|
235
|
-
registerConsentPlugin(consentLinker())
|
|
227
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
236
228
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false }
|
|
237
|
-
const decorated = decorateConsentUrl('https://site.test/legal/cookies', record, linkerOpts)
|
|
229
|
+
const decorated = consentPluginHelper.decorateConsentUrl('https://site.test/legal/cookies', record, linkerOpts)
|
|
238
230
|
|
|
239
231
|
expect(decorated.searchParams.has(CONSENT_LINK_PARAM)).toBe(true)
|
|
240
232
|
})
|
|
241
233
|
|
|
242
234
|
test('registering the same alias twice replaces rather than duplicates', () => {
|
|
243
|
-
registerConsentPlugin(consentLinker())
|
|
244
|
-
registerConsentPlugin(consentLinker())
|
|
235
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
236
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
245
237
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true }
|
|
246
|
-
const decorated = decorateConsentUrl('https://site.test/', record, linkerOpts)
|
|
238
|
+
const decorated = consentPluginHelper.decorateConsentUrl('https://site.test/', record, linkerOpts)
|
|
247
239
|
|
|
248
240
|
// A duplicate `decorate` implementation would still be idempotent (same value both times), so
|
|
249
241
|
// what this actually guards is that the registry never grows unbounded per alias.
|
|
@@ -254,7 +246,7 @@ describe('consentDomains / decorateConsentUrl (registry level)', () => {
|
|
|
254
246
|
describe('the store, with a linker configured', () => {
|
|
255
247
|
test('adopts on init when nothing is stored yet, and applies it', () => {
|
|
256
248
|
const record = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
257
|
-
env.goto(`https://platform.test/dispatcher?owlcc=${encodeConsentLink(record)}`, 'https://site.test/')
|
|
249
|
+
env.goto(`https://platform.test/dispatcher?owlcc=${consentLinkHelper.encodeConsentLink(record)}`, 'https://site.test/')
|
|
258
250
|
|
|
259
251
|
const store = makeConsentStore()
|
|
260
252
|
store.init({ silent: true, linker: { domains: DOMAINS } })
|
|
@@ -263,7 +255,7 @@ describe('the store, with a linker configured', () => {
|
|
|
263
255
|
expect(store.get().record?.[CONSENT_ANALYTICS]).toBe(true)
|
|
264
256
|
expect(store.granted(CONSENT_ESSENTIAL)).toBe(true)
|
|
265
257
|
// Written through, not just held in memory — a second page load must see it too.
|
|
266
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
258
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
267
259
|
})
|
|
268
260
|
|
|
269
261
|
test('strips the parameter from the URL either way', () => {
|
|
@@ -276,9 +268,9 @@ describe('the store, with a linker configured', () => {
|
|
|
276
268
|
})
|
|
277
269
|
|
|
278
270
|
test('never overwrites a decision this document already made', () => {
|
|
279
|
-
writeConsent({ [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
271
|
+
consentStorageHelper.writeConsent({ [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
280
272
|
const incoming = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: true }
|
|
281
|
-
env.goto(`https://platform.test/dispatcher?owlcc=${encodeConsentLink(incoming)}`, 'https://site.test/')
|
|
273
|
+
env.goto(`https://platform.test/dispatcher?owlcc=${consentLinkHelper.encodeConsentLink(incoming)}`, 'https://site.test/')
|
|
282
274
|
|
|
283
275
|
const store = makeConsentStore()
|
|
284
276
|
store.init({ silent: true, linker: { domains: DOMAINS } })
|
|
@@ -299,7 +291,7 @@ describe('the store, with a linker configured', () => {
|
|
|
299
291
|
|
|
300
292
|
describe('consentBootstrapScript with a linker: order and shape', () => {
|
|
301
293
|
test('the linker fragment sits after the default push and before the storage read', () => {
|
|
302
|
-
const script = consentBootstrapScript({ linker: { domains: DOMAINS } })
|
|
294
|
+
const script = consentModeHelper.consentBootstrapScript({ linker: { domains: DOMAINS } })
|
|
303
295
|
const defaultAt = script.indexOf("'consent','default'")
|
|
304
296
|
const linkerAt = script.indexOf('URLSearchParams')
|
|
305
297
|
const storageAt = script.indexOf('w.localStorage.getItem')
|
|
@@ -310,11 +302,11 @@ describe('consentBootstrapScript with a linker: order and shape', () => {
|
|
|
310
302
|
})
|
|
311
303
|
|
|
312
304
|
test('emits nothing extra with no linker configured', () => {
|
|
313
|
-
expect(consentBootstrapScript()).not.toContain('URLSearchParams')
|
|
305
|
+
expect(consentModeHelper.consentBootstrapScript()).not.toContain('URLSearchParams')
|
|
314
306
|
})
|
|
315
307
|
|
|
316
308
|
test('escapes "<" inside embedded strings', () => {
|
|
317
|
-
const script = consentLinkerScript({ linker: { domains: ['<script>evil.test'] } })
|
|
309
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: ['<script>evil.test'] } })
|
|
318
310
|
|
|
319
311
|
expect(script).not.toContain('<script>evil.test')
|
|
320
312
|
expect(script).toContain('\\u003cscript>evil.test')
|
|
@@ -324,31 +316,31 @@ describe('consentBootstrapScript with a linker: order and shape', () => {
|
|
|
324
316
|
describe('consentLinkerScript, executed', () => {
|
|
325
317
|
test('adopts a valid link and strips the parameter, via new Function', () => {
|
|
326
318
|
const record = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
327
|
-
env.goto(`https://platform.test/dispatcher?owlcc=${encodeConsentLink(record)}`, 'https://site.test/')
|
|
319
|
+
env.goto(`https://platform.test/dispatcher?owlcc=${consentLinkHelper.encodeConsentLink(record)}`, 'https://site.test/')
|
|
328
320
|
|
|
329
|
-
const script = consentLinkerScript({ linker: { domains: DOMAINS } })
|
|
321
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: DOMAINS } })
|
|
330
322
|
// eslint-disable-next-line no-new-func
|
|
331
323
|
new Function(script)()
|
|
332
324
|
|
|
333
325
|
expect(env.href()).toBe('https://platform.test/dispatcher')
|
|
334
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
326
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
335
327
|
})
|
|
336
328
|
|
|
337
329
|
test('never overwrites an existing record, and still strips', () => {
|
|
338
|
-
writeConsent({ [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
330
|
+
consentStorageHelper.writeConsent({ [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
339
331
|
const incoming = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: true }
|
|
340
|
-
env.goto(`https://platform.test/dispatcher?owlcc=${encodeConsentLink(incoming)}`, 'https://site.test/')
|
|
332
|
+
env.goto(`https://platform.test/dispatcher?owlcc=${consentLinkHelper.encodeConsentLink(incoming)}`, 'https://site.test/')
|
|
341
333
|
|
|
342
|
-
const script = consentLinkerScript({ linker: { domains: DOMAINS } })
|
|
334
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: DOMAINS } })
|
|
343
335
|
// eslint-disable-next-line no-new-func
|
|
344
336
|
new Function(script)()
|
|
345
337
|
|
|
346
338
|
expect(env.href()).toBe('https://platform.test/dispatcher')
|
|
347
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(false)
|
|
339
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(false)
|
|
348
340
|
})
|
|
349
341
|
|
|
350
342
|
test('is an empty string with no linker configured', () => {
|
|
351
|
-
expect(consentLinkerScript({})).toBe('')
|
|
343
|
+
expect(consentLinkHelper.consentLinkerScript({})).toBe('')
|
|
352
344
|
})
|
|
353
345
|
})
|
|
354
346
|
|
|
@@ -359,12 +351,11 @@ describe('every category the built-in bundle carries', () => {
|
|
|
359
351
|
expect(DEFAULT_CONSENT_CATEGORIES.filter(c => c.required === true).map(c => c.key)).toEqual([CONSENT_ESSENTIAL])
|
|
360
352
|
})
|
|
361
353
|
|
|
362
|
-
test('
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
expect(
|
|
366
|
-
|
|
367
|
-
expect(functional?.signals ?? []).toEqual([])
|
|
354
|
+
test('the optional categories are analytics and marketing — nothing asks about the language', () => {
|
|
355
|
+
// The interface language is strictly necessary storage: no category governs it, so the default
|
|
356
|
+
// set has no "functional" row that would offer a choice which changes nothing.
|
|
357
|
+
expect(DEFAULT_CONSENT_CATEGORIES.filter(c => c.required !== true).map(c => c.key))
|
|
358
|
+
.toEqual([CONSENT_ANALYTICS, CONSENT_MARKETING])
|
|
368
359
|
})
|
|
369
360
|
})
|
|
370
361
|
|
|
@@ -373,29 +364,29 @@ describe('language: the link parameter', () => {
|
|
|
373
364
|
|
|
374
365
|
test('carries the page language as `l`, lower-cased, when the sender turned it on', () => {
|
|
375
366
|
env.lang('PL')
|
|
376
|
-
const decoded = decodeConsentLink(encodeConsentLink({ [CONSENT_ANALYTICS]: true }, withLanguage))
|
|
367
|
+
const decoded = consentLinkHelper.decodeConsentLink(consentLinkHelper.encodeConsentLink({ [CONSENT_ANALYTICS]: true }, withLanguage))
|
|
377
368
|
|
|
378
369
|
expect(decoded?.l).toBe('pl')
|
|
379
|
-
expect(decoded?.c).toEqual({ [
|
|
370
|
+
expect(decoded?.c).toEqual({ [CONSENT_ANALYTICS]: 1, [CONSENT_MARKETING]: 0 })
|
|
380
371
|
})
|
|
381
372
|
|
|
382
373
|
test('carries no `l` when the sender did not turn it on, whatever the page says', () => {
|
|
383
374
|
env.lang('pl')
|
|
384
375
|
|
|
385
|
-
expect(decodeConsentLink(encodeConsentLink({ [CONSENT_ANALYTICS]: true }, linkerOpts))?.l).toBeUndefined()
|
|
376
|
+
expect(consentLinkHelper.decodeConsentLink(consentLinkHelper.encodeConsentLink({ [CONSENT_ANALYTICS]: true }, linkerOpts))?.l).toBeUndefined()
|
|
386
377
|
})
|
|
387
378
|
|
|
388
379
|
test('carries no `l` for a page with no usable language', () => {
|
|
389
380
|
for (const lang of ['', 'not a language!', '12']) {
|
|
390
381
|
env.lang(lang)
|
|
391
382
|
|
|
392
|
-
expect(decodeConsentLink(encodeConsentLink(null, withLanguage))?.l).toBeUndefined()
|
|
383
|
+
expect(consentLinkHelper.decodeConsentLink(consentLinkHelper.encodeConsentLink(null, withLanguage))?.l).toBeUndefined()
|
|
393
384
|
}
|
|
394
385
|
})
|
|
395
386
|
|
|
396
387
|
test('encodes no decision at all as an empty `c`', () => {
|
|
397
388
|
env.lang('de')
|
|
398
|
-
const decoded = decodeConsentLink(encodeConsentLink(null, withLanguage))
|
|
389
|
+
const decoded = consentLinkHelper.decodeConsentLink(consentLinkHelper.encodeConsentLink(null, withLanguage))
|
|
399
390
|
|
|
400
391
|
expect(decoded?.c).toEqual({})
|
|
401
392
|
expect(decoded?.l).toBe('de')
|
|
@@ -405,7 +396,7 @@ describe('language: the link parameter', () => {
|
|
|
405
396
|
const raw = Buffer.from(JSON.stringify({
|
|
406
397
|
v: 2, c: { [CONSENT_ANALYTICS]: 1 }, t: nowSeconds(), l: '../../etc',
|
|
407
398
|
})).toString('base64url')
|
|
408
|
-
const decoded = decodeConsentLink(raw)
|
|
399
|
+
const decoded = consentLinkHelper.decodeConsentLink(raw)
|
|
409
400
|
|
|
410
401
|
expect(decoded).not.toBeNull()
|
|
411
402
|
expect(decoded?.l).toBeUndefined()
|
|
@@ -414,7 +405,7 @@ describe('language: the link parameter', () => {
|
|
|
414
405
|
})
|
|
415
406
|
|
|
416
407
|
describe('language: consentLinker().decorate', () => {
|
|
417
|
-
const plugin = consentLinker()
|
|
408
|
+
const plugin = consentLinkHelper.consentLinker()
|
|
418
409
|
const withLanguage = { linker: { domains: DOMAINS, language: {} } }
|
|
419
410
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
420
411
|
|
|
@@ -423,14 +414,14 @@ describe('language: consentLinker().decorate', () => {
|
|
|
423
414
|
const decorated = plugin.decorate!(new URL('https://site.test/start'), null, withLanguage)
|
|
424
415
|
|
|
425
416
|
expect(decorated).not.toBeNull()
|
|
426
|
-
const payload = decodeConsentLink(decorated!.searchParams.get(CONSENT_LINK_PARAM)!)
|
|
417
|
+
const payload = consentLinkHelper.decodeConsentLink(decorated!.searchParams.get(CONSENT_LINK_PARAM)!)
|
|
427
418
|
expect(payload?.l).toBe('uk')
|
|
428
419
|
expect(payload?.c).toEqual({})
|
|
429
420
|
})
|
|
430
421
|
|
|
431
422
|
test('carries the decision and the language together', () => {
|
|
432
423
|
env.lang('pl')
|
|
433
|
-
const payload = decodeConsentLink(
|
|
424
|
+
const payload = consentLinkHelper.decodeConsentLink(
|
|
434
425
|
plugin.decorate!(new URL('https://site.test/start'), record, withLanguage)!.searchParams.get(CONSENT_LINK_PARAM)!
|
|
435
426
|
)
|
|
436
427
|
|
|
@@ -458,12 +449,12 @@ describe('language: consentLinker().decorate', () => {
|
|
|
458
449
|
})
|
|
459
450
|
|
|
460
451
|
describe('language: consentLinker().adoptLanguage', () => {
|
|
461
|
-
const plugin = consentLinker()
|
|
452
|
+
const plugin = consentLinkHelper.consentLinker()
|
|
462
453
|
const receiving = { linker: { domains: DOMAINS, language: { supported: ['en', 'pl', 'de', 'fr'] } } }
|
|
463
454
|
const carried = (lang: string, record: ConsentRecord | null = null): string => {
|
|
464
455
|
env.lang(lang)
|
|
465
456
|
|
|
466
|
-
return encodeConsentLink(record, { linker: { domains: DOMAINS, language: {} } })
|
|
457
|
+
return consentLinkHelper.encodeConsentLink(record, { linker: { domains: DOMAINS, language: {} } })
|
|
467
458
|
}
|
|
468
459
|
const arrive = (owlcc: string, referrer = 'https://site.test/') => {
|
|
469
460
|
env.goto(`https://platform.test/dispatcher?owlcc=${owlcc}`, referrer)
|
|
@@ -529,115 +520,64 @@ describe('language: consentLinker().adoptLanguage', () => {
|
|
|
529
520
|
})
|
|
530
521
|
|
|
531
522
|
test('registry: adoptConsentLanguage asks the registered plugin', () => {
|
|
532
|
-
registerConsentPlugin(consentLinker())
|
|
523
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
533
524
|
arrive(carried('pl'))
|
|
534
525
|
|
|
535
|
-
expect(adoptConsentLanguage(receiving)).toBe('pl')
|
|
536
|
-
expect(adoptConsentLanguage(linkerOpts)).toBeNull()
|
|
526
|
+
expect(consentPluginHelper.adoptConsentLanguage(receiving)).toBe('pl')
|
|
527
|
+
expect(consentPluginHelper.adoptConsentLanguage(linkerOpts)).toBeNull()
|
|
537
528
|
})
|
|
538
529
|
})
|
|
539
530
|
|
|
540
|
-
describe('language:
|
|
531
|
+
describe('language: strictly necessary, stored whatever the cookie decision is', () => {
|
|
541
532
|
const receiving = { silent: true, linker: { domains: DOMAINS, language: { supported: ['en', 'pl', 'de'] } } }
|
|
542
533
|
const arrive = (lang: string, record: ConsentRecord | null = null, referrer = 'https://site.test/') => {
|
|
543
534
|
env.lang(lang)
|
|
544
|
-
const owlcc = encodeConsentLink(record, { linker: { domains: DOMAINS, language: {} } })
|
|
535
|
+
const owlcc = consentLinkHelper.encodeConsentLink(record, { linker: { domains: DOMAINS, language: {} } })
|
|
545
536
|
env.goto(`https://platform.test/dispatcher?owlcc=${owlcc}`, referrer)
|
|
546
537
|
env.lang('en')
|
|
547
538
|
}
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
test('writeConsentLanguage writes only on a functional grant, and says so', () => {
|
|
554
|
-
expect(writeConsentLanguage('pl', receiving)).toBe(false)
|
|
555
|
-
|
|
556
|
-
writeConsent(refused)
|
|
557
|
-
expect(writeConsentLanguage('pl', receiving)).toBe(false)
|
|
558
|
-
|
|
559
|
-
writeConsent(legacy)
|
|
560
|
-
expect(writeConsentLanguage('pl', receiving)).toBe(false)
|
|
561
|
-
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
562
|
-
|
|
563
|
-
writeConsent(granted)
|
|
564
|
-
expect(writeConsentLanguage('pl', receiving)).toBe(true)
|
|
565
|
-
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
566
|
-
})
|
|
567
|
-
|
|
568
|
-
test('a grant that survives only in the cookie counts; a corrupt record does not', () => {
|
|
569
|
-
env.store.set(CONSENT_KEY, '{not json')
|
|
570
|
-
expect(writeConsentLanguage('pl', receiving)).toBe(false)
|
|
539
|
+
const accepted: ConsentRecord = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: true }
|
|
540
|
+
const refused: ConsentRecord = { [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false }
|
|
541
|
+
/** Saved before the `functional` category was retired: an extra key nobody reads any more. */
|
|
542
|
+
const withRetiredKey: ConsentRecord = { functional: false, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
571
543
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
544
|
+
test('writeConsentLanguage writes with no decision, a refusal, an acceptance or an old record', () => {
|
|
545
|
+
for (const record of [null, refused, accepted, withRetiredKey]) {
|
|
546
|
+
env.reset()
|
|
547
|
+
if (record != null) {
|
|
548
|
+
consentStorageHelper.writeConsent(record)
|
|
549
|
+
}
|
|
550
|
+
expect(consentLinkHelper.writeConsentLanguage('pl', receiving)).toBe(true)
|
|
551
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
552
|
+
}
|
|
576
553
|
})
|
|
577
554
|
|
|
578
|
-
test('
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
expect(functionalKeysOf({ functionalKeys: ['a', 'b'], linker: { domains: DOMAINS, language: { storageKey: 'a' } } }))
|
|
589
|
-
.toEqual(['a', 'b'])
|
|
555
|
+
test('storage that refuses the write is not an error', () => {
|
|
556
|
+
const storage = (globalThis as any).localStorage
|
|
557
|
+
const setItem = storage.setItem
|
|
558
|
+
storage.setItem = () => { throw new Error('blocked') }
|
|
559
|
+
try {
|
|
560
|
+
expect(consentLinkHelper.writeConsentLanguage('pl', receiving)).toBe(false)
|
|
561
|
+
} finally {
|
|
562
|
+
// The mock storage is shared by every test in the file.
|
|
563
|
+
storage.setItem = setItem
|
|
564
|
+
}
|
|
590
565
|
})
|
|
591
566
|
|
|
592
567
|
describe('the store', () => {
|
|
593
|
-
test('with no decision the language is
|
|
568
|
+
test('with no decision the language is stored at once, the dialog asks, the parameter is stripped', () => {
|
|
594
569
|
arrive('pl')
|
|
595
570
|
|
|
596
571
|
const store = makeConsentStore()
|
|
597
572
|
store.init(receiving)
|
|
598
573
|
|
|
599
|
-
expect(env.store.
|
|
600
|
-
expect(store.pendingLanguage()).toBe('pl')
|
|
574
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
601
575
|
expect(store.get().open).toBe(true)
|
|
602
576
|
expect(env.href()).toBe('https://platform.test/dispatcher')
|
|
603
577
|
})
|
|
604
578
|
|
|
605
|
-
test('
|
|
606
|
-
arrive('
|
|
607
|
-
const store = makeConsentStore()
|
|
608
|
-
store.init(receiving)
|
|
609
|
-
const heard: string[] = []
|
|
610
|
-
// Process-wide globals: put them back, whatever the assertions do (bun shares them across files).
|
|
611
|
-
const saved = { dispatch: (globalThis as any).dispatchEvent, custom: (globalThis as any).CustomEvent }
|
|
612
|
-
;(globalThis as any).dispatchEvent = (e: any) => { if (e.type === CONSENT_LANGUAGE_EVENT) heard.push(e.detail.language) }
|
|
613
|
-
;(globalThis as any).CustomEvent = class { constructor(public type: string, public init: any) {} get detail() { return this.init.detail } }
|
|
614
|
-
try {
|
|
615
|
-
store.save(granted)
|
|
616
|
-
} finally {
|
|
617
|
-
(globalThis as any).dispatchEvent = saved.dispatch
|
|
618
|
-
;(globalThis as any).CustomEvent = saved.custom
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
622
|
-
expect(store.pendingLanguage()).toBeNull()
|
|
623
|
-
expect(heard).toEqual(['pl'])
|
|
624
|
-
})
|
|
625
|
-
|
|
626
|
-
test('answering with a refusal stores nothing, and the language keeps waiting for a later grant', () => {
|
|
627
|
-
arrive('pl')
|
|
628
|
-
const store = makeConsentStore()
|
|
629
|
-
store.init(receiving)
|
|
630
|
-
|
|
631
|
-
store.save(refused)
|
|
632
|
-
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
633
|
-
expect(store.pendingLanguage()).toBe('pl')
|
|
634
|
-
|
|
635
|
-
store.save(granted)
|
|
636
|
-
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
637
|
-
})
|
|
638
|
-
|
|
639
|
-
test('a decision carried by the link that grants functional stores decision and language together', () => {
|
|
640
|
-
arrive('de', { [CONSENT_FUNCTIONAL]: true, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false })
|
|
579
|
+
test('a decision carried by the link is stored together with the language', () => {
|
|
580
|
+
arrive('de', { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false })
|
|
641
581
|
|
|
642
582
|
const store = makeConsentStore()
|
|
643
583
|
store.init(receiving)
|
|
@@ -647,80 +587,62 @@ describe('language: only while functional storage is granted', () => {
|
|
|
647
587
|
expect(store.get().record?.[CONSENT_ANALYTICS]).toBe(true)
|
|
648
588
|
})
|
|
649
589
|
|
|
650
|
-
test('a carried "reject all" stores the decision
|
|
590
|
+
test('a carried "reject all" stores the decision AND the language', () => {
|
|
651
591
|
arrive('de', refused)
|
|
652
592
|
|
|
653
593
|
const store = makeConsentStore()
|
|
654
594
|
store.init(receiving)
|
|
655
595
|
|
|
656
596
|
expect(store.get().open).toBe(false)
|
|
657
|
-
expect(store.get().record?.[
|
|
658
|
-
expect(env.store.
|
|
659
|
-
expect(store.pendingLanguage()).toBe('de')
|
|
597
|
+
expect(store.get().record?.[CONSENT_ANALYTICS]).toBe(false)
|
|
598
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('de')
|
|
660
599
|
})
|
|
661
600
|
|
|
662
|
-
test('a
|
|
663
|
-
|
|
664
|
-
|
|
601
|
+
test('a stored decision of any kind lets the link\'s language through, and still wins over its decision', () => {
|
|
602
|
+
for (const stored of [refused, accepted, withRetiredKey]) {
|
|
603
|
+
env.reset()
|
|
604
|
+
consentStorageHelper.writeConsent(stored)
|
|
605
|
+
arrive('pl', { [CONSENT_ANALYTICS]: !stored[CONSENT_ANALYTICS], [CONSENT_MARKETING]: true })
|
|
665
606
|
|
|
666
|
-
|
|
607
|
+
const store = makeConsentStore()
|
|
608
|
+
store.init(receiving)
|
|
667
609
|
|
|
668
|
-
|
|
610
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
611
|
+
expect(store.get().record?.[CONSENT_ANALYTICS]).toBe(stored[CONSENT_ANALYTICS])
|
|
612
|
+
}
|
|
669
613
|
})
|
|
670
614
|
|
|
671
|
-
test('the carried language outranks one chosen here earlier
|
|
672
|
-
writeConsent(granted)
|
|
615
|
+
test('the carried language outranks one chosen here earlier', () => {
|
|
673
616
|
env.store.set(CONSENT_LANGUAGE_KEY, 'de')
|
|
674
|
-
arrive('pl'
|
|
617
|
+
arrive('pl')
|
|
675
618
|
|
|
676
|
-
|
|
677
|
-
store.init(receiving)
|
|
619
|
+
makeConsentStore().init(receiving)
|
|
678
620
|
|
|
679
621
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
680
|
-
expect(store.get().record?.[CONSENT_ANALYTICS]).toBe(false)
|
|
681
622
|
})
|
|
682
623
|
|
|
683
|
-
test('
|
|
684
|
-
|
|
624
|
+
test('answering the dialog never touches the language, in either direction', () => {
|
|
625
|
+
arrive('pl')
|
|
685
626
|
const store = makeConsentStore()
|
|
686
627
|
store.init(receiving)
|
|
687
|
-
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
688
628
|
|
|
689
|
-
store.save(granted)
|
|
690
|
-
env.store.set(CONSENT_LANGUAGE_KEY, 'de')
|
|
691
629
|
store.save(refused)
|
|
692
|
-
expect(env.store.
|
|
693
|
-
})
|
|
694
|
-
|
|
695
|
-
test('a grant keeps what is stored, and every functional key is purged when it is withdrawn', () => {
|
|
696
|
-
writeConsent(granted)
|
|
697
|
-
env.store.set(CONSENT_LANGUAGE_KEY, 'de')
|
|
698
|
-
env.store.set('locale_chosen', '1')
|
|
699
|
-
const opts = { ...receiving, functionalKeys: [CONSENT_LANGUAGE_KEY, 'locale_chosen'] }
|
|
700
|
-
const store = makeConsentStore()
|
|
701
|
-
store.init(opts)
|
|
702
|
-
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('de')
|
|
703
|
-
expect(env.store.get('locale_chosen')).toBe('1')
|
|
630
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
704
631
|
|
|
705
|
-
store.save(
|
|
706
|
-
expect(env.store.
|
|
707
|
-
expect(env.store.has('locale_chosen')).toBe(false)
|
|
632
|
+
store.save(accepted)
|
|
633
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
708
634
|
})
|
|
709
635
|
|
|
710
|
-
test('a
|
|
711
|
-
writeConsent(legacy)
|
|
636
|
+
test('a language stored earlier survives every decision', () => {
|
|
712
637
|
env.store.set(CONSENT_LANGUAGE_KEY, 'de')
|
|
713
|
-
arrive('pl')
|
|
714
|
-
|
|
715
638
|
const store = makeConsentStore()
|
|
716
639
|
store.init(receiving)
|
|
640
|
+
store.save(refused)
|
|
717
641
|
|
|
718
|
-
expect(env.store.
|
|
719
|
-
expect(store.pendingLanguage()).toBe('pl')
|
|
642
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('de')
|
|
720
643
|
})
|
|
721
644
|
|
|
722
645
|
test('honours a custom storage key', () => {
|
|
723
|
-
writeConsent(granted)
|
|
724
646
|
arrive('pl')
|
|
725
647
|
|
|
726
648
|
makeConsentStore().init({ ...receiving, linker: { ...receiving.linker, language: { supported: ['pl'], storageKey: 'app-lng' } } })
|
|
@@ -729,110 +651,72 @@ describe('language: only while functional storage is granted', () => {
|
|
|
729
651
|
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
730
652
|
})
|
|
731
653
|
|
|
732
|
-
test('writes nothing for an unsupported language or an untrusted referrer
|
|
733
|
-
writeConsent(granted)
|
|
654
|
+
test('writes nothing for an unsupported language or an untrusted referrer', () => {
|
|
734
655
|
arrive('ja')
|
|
735
656
|
makeConsentStore().init(receiving)
|
|
736
657
|
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
737
658
|
|
|
738
659
|
arrive('pl', null, 'https://evil.test/')
|
|
739
|
-
|
|
740
|
-
store.init(receiving)
|
|
660
|
+
makeConsentStore().init(receiving)
|
|
741
661
|
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
742
|
-
expect(store.pendingLanguage()).toBeNull()
|
|
743
|
-
})
|
|
744
|
-
|
|
745
|
-
test('picks up what the inline fragment left on window, and clears it', () => {
|
|
746
|
-
;(globalThis as any)[CONSENT_PENDING_LANGUAGE] = 'pl'
|
|
747
|
-
env.goto('https://platform.test/dispatcher')
|
|
748
|
-
|
|
749
|
-
const store = makeConsentStore()
|
|
750
|
-
store.init(receiving)
|
|
751
|
-
|
|
752
|
-
expect(store.pendingLanguage()).toBe('pl')
|
|
753
|
-
expect((globalThis as any)[CONSENT_PENDING_LANGUAGE]).toBeUndefined()
|
|
754
|
-
|
|
755
|
-
store.save(granted)
|
|
756
|
-
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
757
662
|
})
|
|
758
663
|
})
|
|
759
664
|
|
|
760
665
|
describe('the inline fragment', () => {
|
|
761
|
-
const run = (opts: Parameters<typeof consentLinkerScript>[0] = receiving) => {
|
|
666
|
+
const run = (opts: Parameters<typeof consentLinkHelper.consentLinkerScript>[0] = receiving) => {
|
|
762
667
|
// eslint-disable-next-line no-new-func
|
|
763
|
-
new Function(consentLinkerScript(opts))()
|
|
668
|
+
new Function(consentLinkHelper.consentLinkerScript(opts))()
|
|
764
669
|
}
|
|
765
670
|
const all = { linker: { domains: DOMAINS, language: { supported: ['en', 'pl', 'de', 'fr'] } } }
|
|
766
671
|
|
|
767
|
-
test('with no decision anywhere it
|
|
672
|
+
test('with no decision anywhere it stores the language, adopts nothing, and still strips', () => {
|
|
768
673
|
arrive('pl')
|
|
769
674
|
run()
|
|
770
675
|
|
|
771
|
-
expect(env.store.
|
|
772
|
-
expect((
|
|
773
|
-
expect(readConsent()).toBeNull()
|
|
676
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
677
|
+
expect(consentStorageHelper.readConsent()).toBeNull()
|
|
774
678
|
expect(env.href()).toBe('https://platform.test/dispatcher')
|
|
775
679
|
})
|
|
776
680
|
|
|
777
|
-
test('a link that
|
|
778
|
-
arrive('fr', { [
|
|
681
|
+
test('a link that carries a decision writes decision and language together', () => {
|
|
682
|
+
arrive('fr', { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false })
|
|
779
683
|
run(all)
|
|
780
684
|
|
|
781
685
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('fr')
|
|
782
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
783
|
-
expect((globalThis as any)[CONSENT_PENDING_LANGUAGE]).toBeUndefined()
|
|
686
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
784
687
|
})
|
|
785
688
|
|
|
786
|
-
test('a link that carries "reject all" adopts the decision
|
|
689
|
+
test('a link that carries "reject all" adopts the decision and stores the language', () => {
|
|
787
690
|
arrive('fr', refused)
|
|
788
691
|
run(all)
|
|
789
692
|
|
|
790
|
-
expect(readConsent()?.[
|
|
791
|
-
expect(env.store.
|
|
792
|
-
expect((globalThis as any)[CONSENT_PENDING_LANGUAGE]).toBe('fr')
|
|
793
|
-
})
|
|
794
|
-
|
|
795
|
-
test('a functional grant stored here is enough, and the link\'s own decision never overwrites it', () => {
|
|
796
|
-
writeConsent({ ...granted, [CONSENT_ANALYTICS]: false })
|
|
797
|
-
arrive('de', { [CONSENT_FUNCTIONAL]: true, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: true })
|
|
798
|
-
run()
|
|
799
|
-
|
|
800
|
-
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('de')
|
|
801
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(false)
|
|
802
|
-
})
|
|
803
|
-
|
|
804
|
-
test('a functional grant stored here is enough for a link that carries only the language', () => {
|
|
805
|
-
writeConsent(granted)
|
|
806
|
-
arrive('pl')
|
|
807
|
-
run()
|
|
808
|
-
|
|
809
|
-
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
693
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(false)
|
|
694
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('fr')
|
|
810
695
|
})
|
|
811
696
|
|
|
812
|
-
test('a stored
|
|
813
|
-
for (const
|
|
697
|
+
test('a stored record of any kind lets the language through, and the link\'s decision never overwrites it', () => {
|
|
698
|
+
for (const stored of [refused, accepted, withRetiredKey]) {
|
|
814
699
|
env.reset()
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
arrive('pl')
|
|
700
|
+
consentStorageHelper.writeConsent(stored)
|
|
701
|
+
arrive('de', { [CONSENT_ANALYTICS]: !stored[CONSENT_ANALYTICS], [CONSENT_MARKETING]: true })
|
|
818
702
|
run()
|
|
819
703
|
|
|
820
|
-
expect(env.store.
|
|
821
|
-
expect((
|
|
704
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('de')
|
|
705
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(stored[CONSENT_ANALYTICS])
|
|
822
706
|
}
|
|
823
707
|
})
|
|
824
708
|
|
|
825
|
-
test('an unparseable stored record
|
|
709
|
+
test('an unparseable stored record still lets the language through, and nothing is adopted over it', () => {
|
|
826
710
|
env.store.set(CONSENT_KEY, '{not json')
|
|
827
|
-
arrive('pl', { [
|
|
711
|
+
arrive('pl', { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: true })
|
|
828
712
|
run()
|
|
829
713
|
|
|
830
|
-
expect(env.store.
|
|
714
|
+
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
831
715
|
expect(env.store.get(CONSENT_KEY)).toBe('{not json')
|
|
832
716
|
})
|
|
833
717
|
|
|
834
718
|
test('matches by base tag and by the receiver\'s spelling, like the TypeScript path', () => {
|
|
835
|
-
const grant = { [
|
|
719
|
+
const grant = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
836
720
|
arrive('de-CH', grant)
|
|
837
721
|
run()
|
|
838
722
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('de')
|
|
@@ -843,97 +727,48 @@ describe('language: only while functional storage is granted', () => {
|
|
|
843
727
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('PL')
|
|
844
728
|
})
|
|
845
729
|
|
|
846
|
-
test('agrees with the TypeScript path on what it refuses
|
|
847
|
-
writeConsent(granted)
|
|
730
|
+
test('agrees with the TypeScript path on what it refuses', () => {
|
|
848
731
|
arrive('ja')
|
|
849
732
|
run()
|
|
850
733
|
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
851
734
|
|
|
852
|
-
delete (globalThis as any)[CONSENT_PENDING_LANGUAGE]
|
|
853
735
|
arrive('pl', null, 'https://evil.test/')
|
|
854
736
|
run()
|
|
855
737
|
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
856
|
-
expect((globalThis as any)[CONSENT_PENDING_LANGUAGE]).toBeUndefined()
|
|
857
738
|
|
|
858
739
|
arrive('pl', null, '')
|
|
859
740
|
run()
|
|
860
741
|
expect(env.store.has(CONSENT_LANGUAGE_KEY)).toBe(false)
|
|
861
|
-
expect((globalThis as any)[CONSENT_PENDING_LANGUAGE]).toBeUndefined()
|
|
862
742
|
})
|
|
863
743
|
|
|
864
744
|
test('honours a custom storage key', () => {
|
|
865
|
-
arrive('pl', { [
|
|
745
|
+
arrive('pl', { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false })
|
|
866
746
|
run({ linker: { domains: DOMAINS, language: { supported: ['pl'], storageKey: 'app-lng' } } })
|
|
867
747
|
|
|
868
748
|
expect(env.store.get('app-lng')).toBe('pl')
|
|
869
749
|
})
|
|
870
750
|
|
|
871
751
|
test('a page that only sends carries no language code at all', () => {
|
|
872
|
-
const script = consentLinkerScript({ linker: { domains: DOMAINS, language: {} } })
|
|
752
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: DOMAINS, language: {} } })
|
|
873
753
|
|
|
874
754
|
expect(script).not.toContain(CONSENT_LANGUAGE_KEY)
|
|
875
|
-
expect(
|
|
876
|
-
expect(consentLinkerScript(linkerOpts)).toBe(script)
|
|
755
|
+
expect(consentLinkHelper.consentLinkerScript(linkerOpts)).toBe(script)
|
|
877
756
|
})
|
|
878
757
|
|
|
879
|
-
test('the bootstrap embeds it, after the
|
|
880
|
-
const script = consentBootstrapScript(receiving)
|
|
758
|
+
test('the bootstrap embeds it, after the defaults and before its own storage read', () => {
|
|
759
|
+
const script = consentModeHelper.consentBootstrapScript(receiving)
|
|
881
760
|
|
|
882
761
|
expect(script.indexOf(CONSENT_LANGUAGE_KEY)).toBeGreaterThan(script.indexOf("'consent','default'"))
|
|
883
|
-
// The fragment reads storage itself (to know
|
|
884
|
-
// the bootstrap's own read is the LAST one, after the whole fragment.
|
|
762
|
+
// The fragment reads storage itself (to know whether a decision exists) before it writes the
|
|
763
|
+
// language; the bootstrap's own read is the LAST one, after the whole fragment.
|
|
885
764
|
expect(script.indexOf(CONSENT_LANGUAGE_KEY)).toBeLessThan(script.lastIndexOf('w.localStorage.getItem'))
|
|
886
765
|
})
|
|
887
766
|
|
|
888
767
|
test('escapes "<" in a configured language too', () => {
|
|
889
|
-
const script = consentLinkerScript({ linker: { domains: DOMAINS, language: { supported: ['<b>'], storageKey: '<k>' } } })
|
|
768
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: DOMAINS, language: { supported: ['<b>'], storageKey: '<k>' } } })
|
|
890
769
|
|
|
891
770
|
expect(script).not.toContain('<b>')
|
|
892
771
|
expect(script).not.toContain('<k>')
|
|
893
772
|
})
|
|
894
773
|
})
|
|
895
774
|
})
|
|
896
|
-
|
|
897
|
-
describe('consentAllowsScript, executed', () => {
|
|
898
|
-
const run = (opts?: Parameters<typeof consentAllowsScript>[0]) => {
|
|
899
|
-
// eslint-disable-next-line no-new-func
|
|
900
|
-
new Function(consentAllowsScript(opts))()
|
|
901
|
-
|
|
902
|
-
return (globalThis as any).owlConsentAllows as (category: string) => boolean
|
|
903
|
-
}
|
|
904
|
-
afterEach(() => { delete (globalThis as any).owlConsentAllows })
|
|
905
|
-
|
|
906
|
-
test('answers from the stored record, category by category', () => {
|
|
907
|
-
writeConsent({ [CONSENT_FUNCTIONAL]: true, [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
908
|
-
const allows = run()
|
|
909
|
-
|
|
910
|
-
expect(allows(CONSENT_FUNCTIONAL)).toBe(true)
|
|
911
|
-
expect(allows(CONSENT_ANALYTICS)).toBe(false)
|
|
912
|
-
expect(allows('unknown')).toBe(false)
|
|
913
|
-
})
|
|
914
|
-
|
|
915
|
-
test('no record, a legacy record and a corrupt one are all a no', () => {
|
|
916
|
-
const allows = run()
|
|
917
|
-
expect(allows(CONSENT_FUNCTIONAL)).toBe(false)
|
|
918
|
-
|
|
919
|
-
writeConsent({ [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: true })
|
|
920
|
-
expect(allows(CONSENT_FUNCTIONAL)).toBe(false)
|
|
921
|
-
|
|
922
|
-
env.store.set(CONSENT_KEY, '{not json')
|
|
923
|
-
expect(allows(CONSENT_FUNCTIONAL)).toBe(false)
|
|
924
|
-
})
|
|
925
|
-
|
|
926
|
-
test('reads the cookie when localStorage has nothing, and honours a custom key', () => {
|
|
927
|
-
;(globalThis as any).document.cookie = `${CONSENT_KEY}=${JSON.stringify({ v: 2, functional: true })}`
|
|
928
|
-
expect(run()(CONSENT_FUNCTIONAL)).toBe(true)
|
|
929
|
-
|
|
930
|
-
env.reset()
|
|
931
|
-
env.store.set('my_consent', JSON.stringify({ v: 2, functional: true }))
|
|
932
|
-
expect(run({ storageKey: 'my_consent' })(CONSENT_FUNCTIONAL)).toBe(true)
|
|
933
|
-
expect(run()(CONSENT_FUNCTIONAL)).toBe(false)
|
|
934
|
-
})
|
|
935
|
-
|
|
936
|
-
test('escapes "<" in the key', () => {
|
|
937
|
-
expect(consentAllowsScript({ storageKey: '<k>' })).not.toContain('<k>')
|
|
938
|
-
})
|
|
939
|
-
})
|