@owlmeans/consent 0.1.18-rc.10
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/build/consts.d.ts +64 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +90 -0
- package/build/consts.js.map +1 -0
- package/build/consts.local.d.ts +4 -0
- package/build/consts.local.d.ts.map +1 -0
- package/build/consts.local.js +4 -0
- package/build/consts.local.js.map +1 -0
- package/build/gtm.d.ts +72 -0
- package/build/gtm.d.ts.map +1 -0
- package/build/gtm.js +182 -0
- package/build/gtm.js.map +1 -0
- package/build/i18n/be.json +36 -0
- package/build/i18n/de.json +36 -0
- package/build/i18n/en.json +36 -0
- package/build/i18n/es.json +36 -0
- package/build/i18n/fr.json +36 -0
- package/build/i18n/pl.json +36 -0
- package/build/i18n/ru.json +36 -0
- package/build/i18n/uk.json +36 -0
- package/build/i18n.d.ts +16 -0
- package/build/i18n.d.ts.map +1 -0
- package/build/i18n.js +29 -0
- package/build/i18n.js.map +1 -0
- package/build/index.d.ts +12 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +9 -0
- package/build/index.js.map +1 -0
- package/build/linker.d.ts +55 -0
- package/build/linker.d.ts.map +1 -0
- package/build/linker.js +334 -0
- package/build/linker.js.map +1 -0
- package/build/plugins.d.ts +30 -0
- package/build/plugins.d.ts.map +1 -0
- package/build/plugins.js +73 -0
- package/build/plugins.js.map +1 -0
- package/build/storage.d.ts +27 -0
- package/build/storage.d.ts.map +1 -0
- package/build/storage.js +86 -0
- package/build/storage.js.map +1 -0
- package/build/store.d.ts +16 -0
- package/build/store.d.ts.map +1 -0
- package/build/store.js +100 -0
- package/build/store.js.map +1 -0
- package/build/types.d.ts +205 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/types.local.d.ts +5 -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 +33 -0
- package/src/consts.local.ts +4 -0
- package/src/consts.ts +103 -0
- package/src/gtm.ts +216 -0
- package/src/i18n/be.json +36 -0
- package/src/i18n/de.json +36 -0
- package/src/i18n/en.json +36 -0
- package/src/i18n/es.json +36 -0
- package/src/i18n/fr.json +36 -0
- package/src/i18n/pl.json +36 -0
- package/src/i18n/ru.json +36 -0
- package/src/i18n/uk.json +36 -0
- package/src/i18n.ts +39 -0
- package/src/index.ts +14 -0
- package/src/linker.ts +369 -0
- package/src/plugins.ts +88 -0
- package/src/storage.ts +96 -0
- package/src/store.ts +126 -0
- package/src/types.local.ts +4 -0
- package/src/types.ts +218 -0
- package/tests/consent.spec.ts +256 -0
- package/tests/linker.spec.ts +774 -0
- package/tests/tracking.spec.ts +191 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test'
|
|
2
|
+
import { CONSENT_ANALYTICS, CONSENT_ESSENTIAL, CONSENT_EVENT, CONSENT_KEY, CONSENT_MARKETING } from '../src/consts.js'
|
|
3
|
+
import { consentGateScript, trackingGranted } from '../src/gtm.js'
|
|
4
|
+
import type { ConsentCategory, ConsentRecord } from '../src/types.js'
|
|
5
|
+
|
|
6
|
+
describe('trackingGranted', () => {
|
|
7
|
+
test('no record at all is not granted', () => {
|
|
8
|
+
expect(trackingGranted(null)).toBe(false)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('an essential-only record is not granted — required categories never count', () => {
|
|
12
|
+
const record: ConsentRecord = {
|
|
13
|
+
[CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
expect(trackingGranted(record)).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('analytics granted is tracking-granted', () => {
|
|
20
|
+
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true }
|
|
21
|
+
|
|
22
|
+
expect(trackingGranted(record)).toBe(true)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('marketing granted is tracking-granted too', () => {
|
|
26
|
+
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_MARKETING]: true }
|
|
27
|
+
|
|
28
|
+
expect(trackingGranted(record)).toBe(true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('a required category is ignored even when the record marks it granted', () => {
|
|
32
|
+
// A category can be BOTH required and signal-bearing (`essential` drives security_storage /
|
|
33
|
+
// functionality_storage by default) — required must still veto it, because everyone gets it
|
|
34
|
+
// regardless of what the record says.
|
|
35
|
+
const categories: ConsentCategory[] = [
|
|
36
|
+
{
|
|
37
|
+
key: 'essential', required: true, labelKey: 'a', descriptionKey: 'b',
|
|
38
|
+
signals: ['security_storage'],
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
expect(trackingGranted({ essential: true }, categories)).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('a category with no signals is ignored even when granted', () => {
|
|
46
|
+
const categories: ConsentCategory[] = [
|
|
47
|
+
{ key: 'preferences', labelKey: 'a', descriptionKey: 'b' },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
expect(trackingGranted({ preferences: true }, categories)).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('consentGateScript — the two reachable paths', () => {
|
|
55
|
+
/** A minimal window/document the generated script can run against, with an event bus. */
|
|
56
|
+
const makeBrowser = (stored?: { key?: string, record: Record<string, unknown> }) => {
|
|
57
|
+
const storage = new Map<string, string>()
|
|
58
|
+
if (stored != null) {
|
|
59
|
+
storage.set(stored.key ?? CONSENT_KEY, JSON.stringify(stored.record))
|
|
60
|
+
}
|
|
61
|
+
const listeners = new Map<string, ((event: unknown) => void)[]>()
|
|
62
|
+
const win: Record<string, unknown> & {
|
|
63
|
+
addEventListener: (type: string, cb: (event: unknown) => void) => void
|
|
64
|
+
removeEventListener: (type: string, cb: (event: unknown) => void) => void
|
|
65
|
+
} = {
|
|
66
|
+
localStorage: { getItem: (k: string) => storage.get(k) ?? null },
|
|
67
|
+
addEventListener: (type, cb) => {
|
|
68
|
+
listeners.set(type, [...(listeners.get(type) ?? []), cb])
|
|
69
|
+
},
|
|
70
|
+
removeEventListener: (type, cb) => {
|
|
71
|
+
listeners.set(type, (listeners.get(type) ?? []).filter(listener => listener !== cb))
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
const doc = { cookie: '' }
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
win,
|
|
78
|
+
run: (script: string) => {
|
|
79
|
+
// eslint-disable-next-line no-new-func
|
|
80
|
+
new Function('window', 'document', script)(win, doc)
|
|
81
|
+
},
|
|
82
|
+
dispatch: (detail: unknown) => {
|
|
83
|
+
for (const cb of listeners.get(CONSENT_EVENT) ?? []) {
|
|
84
|
+
cb({ detail })
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
listenerCount: () => listeners.get(CONSENT_EVENT)?.length ?? 0,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
test('a returning visitor with a stored grant runs the loader immediately, synchronously', () => {
|
|
92
|
+
const browser = makeBrowser({ record: { essential: true, analytics: true, v: 2 } })
|
|
93
|
+
browser.win.__loaded = 0
|
|
94
|
+
browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
95
|
+
|
|
96
|
+
expect(browser.win.__loaded).toBe(1)
|
|
97
|
+
// Nothing left listening — the immediate branch never subscribes.
|
|
98
|
+
expect(browser.listenerCount()).toBe(0)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('a stored record that grants nothing does not run the loader immediately', () => {
|
|
102
|
+
const browser = makeBrowser({ record: { essential: true, analytics: false, v: 2 } })
|
|
103
|
+
browser.win.__loaded = 0
|
|
104
|
+
browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
105
|
+
|
|
106
|
+
expect(browser.win.__loaded).toBe(0)
|
|
107
|
+
expect(browser.listenerCount()).toBe(1)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('no stored record at all falls to the event-listener branch and waits', () => {
|
|
111
|
+
const browser = makeBrowser()
|
|
112
|
+
browser.win.__loaded = 0
|
|
113
|
+
browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
114
|
+
|
|
115
|
+
expect(browser.win.__loaded).toBe(0)
|
|
116
|
+
|
|
117
|
+
// A denying event changes nothing.
|
|
118
|
+
browser.dispatch({ record: { essential: true, analytics: false } })
|
|
119
|
+
expect(browser.win.__loaded).toBe(0)
|
|
120
|
+
|
|
121
|
+
// The first granting event runs the loader and removes the listener.
|
|
122
|
+
browser.dispatch({ record: { essential: true, analytics: true } })
|
|
123
|
+
expect(browser.win.__loaded).toBe(1)
|
|
124
|
+
expect(browser.listenerCount()).toBe(0)
|
|
125
|
+
|
|
126
|
+
// A later event does nothing further — the listener already removed itself.
|
|
127
|
+
browser.dispatch({ record: { essential: true, analytics: true } })
|
|
128
|
+
expect(browser.win.__loaded).toBe(1)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('the cookie answers when localStorage has nothing stored', () => {
|
|
132
|
+
// Same fallback order `consentBootstrapScript`/`readConsent` use: localStorage first, the
|
|
133
|
+
// cookie second. Simulated here by handing the script a `document` whose cookie carries the
|
|
134
|
+
// record and a `window` whose localStorage is empty.
|
|
135
|
+
const record = JSON.stringify({ essential: true, analytics: true, v: 2 })
|
|
136
|
+
const win: Record<string, unknown> & {
|
|
137
|
+
addEventListener: (type: string, cb: (event: unknown) => void) => void
|
|
138
|
+
removeEventListener: (type: string, cb: (event: unknown) => void) => void
|
|
139
|
+
} = {
|
|
140
|
+
localStorage: { getItem: () => null },
|
|
141
|
+
addEventListener: () => {},
|
|
142
|
+
removeEventListener: () => {},
|
|
143
|
+
__loaded: 0,
|
|
144
|
+
}
|
|
145
|
+
const doc = { cookie: `${CONSENT_KEY}=${record}` }
|
|
146
|
+
// eslint-disable-next-line no-new-func
|
|
147
|
+
new Function(
|
|
148
|
+
'window', 'document', consentGateScript('window.__loaded = (window.__loaded || 0) + 1')
|
|
149
|
+
)(win, doc)
|
|
150
|
+
|
|
151
|
+
expect(win.__loaded).toBe(1)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('only a signal-bearing, non-required category can satisfy the gate', () => {
|
|
155
|
+
const categories: ConsentCategory[] = [
|
|
156
|
+
{ key: 'essential', required: true, labelKey: 'a', descriptionKey: 'b', signals: ['security_storage'] },
|
|
157
|
+
{ key: 'analytics', labelKey: 'c', descriptionKey: 'd', signals: ['analytics_storage'] },
|
|
158
|
+
]
|
|
159
|
+
const browser = makeBrowser({ record: { essential: true, analytics: false, v: 2 } })
|
|
160
|
+
browser.win.__loaded = 0
|
|
161
|
+
browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1', { categories }))
|
|
162
|
+
|
|
163
|
+
expect(browser.win.__loaded).toBe(0)
|
|
164
|
+
|
|
165
|
+
browser.dispatch({ record: { essential: true, analytics: true } })
|
|
166
|
+
expect(browser.win.__loaded).toBe(1)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('the generated script carries both reachable code paths in its text', () => {
|
|
170
|
+
const script = consentGateScript('window.__loaded = (window.__loaded || 0) + 1')
|
|
171
|
+
|
|
172
|
+
// The immediate branch: `load()` called right after the stored-record check, before any
|
|
173
|
+
// listener is attached.
|
|
174
|
+
expect(script.indexOf('load()')).toBeLessThan(script.indexOf('addEventListener'))
|
|
175
|
+
// The event-listener branch: the same loader, reachable a second time from inside the handler
|
|
176
|
+
// — plus the `function load(){...}` declaration itself, three occurrences of `load()` in all.
|
|
177
|
+
expect(script.match(/load\(\)/g)?.length).toBe(3)
|
|
178
|
+
expect(script).toContain(JSON.stringify(CONSENT_EVENT))
|
|
179
|
+
expect(script).toContain(JSON.stringify(CONSENT_KEY))
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('a custom storage key is honoured, matching the bootstrap script\'s own lookup', () => {
|
|
183
|
+
const browser = makeBrowser({ key: 'custom_key', record: { essential: true, analytics: true, v: 2 } })
|
|
184
|
+
browser.win.__loaded = 0
|
|
185
|
+
browser.run(consentGateScript(
|
|
186
|
+
'window.__loaded = (window.__loaded || 0) + 1', { storageKey: 'custom_key' }
|
|
187
|
+
))
|
|
188
|
+
|
|
189
|
+
expect(browser.win.__loaded).toBe(1)
|
|
190
|
+
})
|
|
191
|
+
})
|
package/tsconfig.json
ADDED