Skip to content

Commit

Permalink
consent: handle cases where show banner is unchecked (#987)
Browse files Browse the repository at this point in the history
  • Loading branch information
silesky authored Nov 7, 2023
1 parent 1faabf1 commit a63f6c1
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 57 deletions.
215 changes: 215 additions & 0 deletions examples/standalone-playground/pages/index-consent-no-banner.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<html>

<head>


<!-- Form: WriteKey -->
<form method="get">
<input type="text" name="writeKey" placeholder="Writekey" />
<button>Load</button>
</form>
<script>
const { searchParams } = new URL(document.location);
const writeKey = searchParams.get("writeKey");
document.querySelector("input").value = writeKey;
</script>

<!-- Form: Clear OneTrust Cookie -->
<button id="clear-ot-cookies">Clear OneTrust Cookies & Reload</button>
<script>
document.getElementById('clear-ot-cookies').addEventListener('click', () => {
['OptanonConsent', 'OptanonAlertBoxClosed'].forEach((name) => {
document.cookie =
name + "=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;";
});
window.location.reload();
console.log("OneTrust Cookies cleared.");
})
</script>


<!-- OneTrust Vendor Script -->
<script src="https://cdn.cookielaw.org/scripttemplates/otSDKStub.js" type="text/javascript"
data-domain-script="09b87b90-1b30-4d68-9610-9d2fe11234f3-test"></script>
<script type="text/javascript">
const wk = document.querySelector("input").value
wk && console.log('should be on the following url ->', `${window.location.origin + window.location.pathname}?writeKey=${wk}&otpreview=true&otgeo=za`)
function OptanonWrapper() {
// debugging.
if (!window.OnetrustActiveGroups.replaceAll(',', '')) {
window.OnetrustActiveGroups = ',CAT0001' // very hacky, simulates at least one group if none exist
}
console.log('ShowAlertNotice: should be false (initially) ->', window.OneTrust.GetDomainData().ShowAlertNotice)
console.log('OneTrust.testLog() ->', window.OneTrust.testLog())
}
</script>
<!-- OneTrust Wrapper -->
<script
src="/node_modules/@segment/analytics-consent-wrapper-onetrust/dist/umd/analytics-onetrust.umd.development.js">
</script>
<!-- Segment Snippet (Modified)-->
<script>
if (writeKey) {
!(function () {
var analytics = (window.analytics = window.analytics || [])
if (!analytics.initialize)
if (analytics.invoked)
window.console &&
console.error &&
console.error('Segment snippet included twice.')
else {
analytics.invoked = !0
analytics.methods = [
'screen',
'register',
'deregister',
'trackSubmit',
'trackClick',
'trackLink',
'trackForm',
'pageview',
'identify',
'reset',
'group',
'track',
'ready',
'alias',
'debug',
'page',
'once',
'off',
'on',
'addSourceMiddleware',
'addIntegrationMiddleware',
'setAnonymousId',
'addDestinationMiddleware',
'emit'
]
analytics.factory = function (e) {
return function () {
if (window.analytics.initialized) {
// Sometimes users assigned analytics to a variable before analytics is done loading, resulting in a stale reference.
// If so, proxy any calls to the 'real' analytics instance.
return window.analytics[e].apply(window.analytics, arguments);
}
var t = Array.prototype.slice.call(arguments)
t.unshift(e)
analytics.push(t)
return analytics
}
}
for (var e = 0; e < analytics.methods.length; e++) {
var key = analytics.methods[e]
analytics[key] = analytics.factory(key)
}
analytics.load = function (key, e) {
var t = document.createElement('script')
t.type = 'text/javascript'
t.async = !0
t.src =
'https://cdn.segment.com/analytics.js/v1/' +
writeKey +
'/analytics.min.js'
var n = document.getElementsByTagName('script')[0]
n.parentNode.insertBefore(t, n)
analytics._loadOptions = e
}
analytics.SNIPPET_VERSION = '4.13.1'
analytics._writeKey = writeKey
withOneTrust(analytics).load()
analytics.page()
}
})()
}
</script>

</head>

<body>
<form>
<textarea name="event" id="event">
{
"name": "hi",
"properties": { },
"traits": { },
"options": { }
}
</textarea>
<div>
<button id="track">Track</button>
<button id="identify">Identify</button>
</div>
</form>
<h2>Consent Changed Event</h2>
<pre id="consent-changed"></pre>
<h2>Logs</h2>
<pre id="logs"></pre>

<script type="text/javascript">
const displayConsentLogs = (str) => document.querySelector('#consent-changed').textContent = str
analytics.on('track', (name, properties, options) => {
if (name.includes("Segment Consent")) {
displayConsentLogs("Consent Changed Event Fired")
setTimeout(() => displayConsentLogs(''), 3000)
}
})
const displayRegularLogs = (str) => document.querySelector('#logs').textContent = str
const logEvent = (promise) => {
if (Array.isArray(promise)) {
displayRegularLogs('Analytics is not initialized!')
setTimeout(() => displayRegularLogs(''), 5000)
return
}
if (promise) {
promise.then((ctx) => {
ctx.flush()
displayRegularLogs(JSON.stringify(
ctx.event,
null,
2
))
})
}
}


document.querySelector('#track').addEventListener('click', async (e) => {
e.preventDefault()
const contents = document.querySelector('#event').value
const evt = JSON.parse(contents)
const promise = window.analytics.track(
evt.name ?? '',
evt.properties ?? {},
evt.options ?? {}
)
logEvent(promise)
})

document
.querySelector('#identify')
.addEventListener('click', async (e) => {
e.preventDefault()
const contents = document.querySelector('#event').value
const evt = JSON.parse(contents)
const promise = window.analytics.identify(
evt.name ?? '',
evt.properties ?? {},
evt.options ?? {}
)
logEvent(promise)
})
</script>
<style>
body {
font-family: monospace;
}

#event {
margin: 2em 0;
min-height: 200px;
min-width: 700px;
}
</style>
</body>

</html>
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,12 @@ import * as ConsentTools from '@segment/analytics-consent-tools'
import * as OneTrustAPI from '../../lib/onetrust-api'
import { sleep } from '@internal/test-helpers'
import { withOneTrust } from '../wrapper'
import { OneTrustMockGlobal, analyticsMock } from '../../test-helpers/mocks'

const grpFixture = {
StrictlyNeccessary: {
CustomGroupId: 'C0001',
},
Targeting: {
CustomGroupId: 'C0004',
},
Performance: {
CustomGroupId: 'C0005',
},
}
import {
OneTrustMockGlobal,
analyticsMock,
domainDataMock,
domainGroupMock,
} from '../../test-helpers/mocks'

const getConsentedGroupIdsSpy = jest
.spyOn(OneTrustAPI, 'getConsentedGroupIds')
Expand Down Expand Up @@ -43,7 +36,7 @@ const createWrapperSpyHelper = {
* We should prefer unit tests for most functionality (see lib/__tests__)
*/
describe('High level "integration" tests', () => {
let resolveResolveWhen = () => {}
let checkResolveWhen = () => {}
beforeEach(() => {
jest
.spyOn(OneTrustAPI, 'getOneTrustGlobal')
Expand All @@ -54,53 +47,73 @@ describe('High level "integration" tests', () => {
* Typically, resolveWhen triggers when a predicate is true. We can manually 'check' so we don't have to use timeouts.
*/
jest.spyOn(ConsentTools, 'resolveWhen').mockImplementation(async (fn) => {
return new Promise((_resolve) => {
resolveResolveWhen = () => {
if (fn()) {
_resolve()
} else {
throw new Error('Refuse to resolve, resolveWhen condition is false')
}
return new Promise((_resolve, _reject) => {
checkResolveWhen = () => {
fn() ? _resolve() : _reject('predicate failed.')
}
})
})
})

describe('shouldLoadSegment', () => {
it('should be resolved successfully', async () => {
it('should load if alert box is closed and groups are defined', async () => {
withOneTrust(analyticsMock)

const shouldLoadSegment = Promise.resolve(
createWrapperSpyHelper.shouldLoadSegment({} as any)
)
OneTrustMockGlobal.GetDomainData.mockReturnValueOnce(domainDataMock)
OneTrustMockGlobal.IsAlertBoxClosed.mockReturnValueOnce(true)
getConsentedGroupIdsSpy.mockImplementation(() => [
domainGroupMock.StrictlyNeccessary.CustomGroupId,
])
checkResolveWhen()
await expect(shouldLoadSegment).resolves.toBeUndefined()
})

it('should not load at all if no groups are defined', async () => {
withOneTrust(analyticsMock)
getConsentedGroupIdsSpy.mockImplementation(() => [])
const shouldLoadSegment = Promise.resolve(
createWrapperSpyHelper.shouldLoadSegment({} as any)
)
void shouldLoadSegment.catch(() => {})
OneTrustMockGlobal.IsAlertBoxClosed.mockReturnValueOnce(true)
checkResolveWhen()
await expect(shouldLoadSegment).rejects.toEqual(expect.anything())
})

it("should load regardless of AlertBox status if showAlertNotice is true (e.g. 'show banner is unchecked')", async () => {
withOneTrust(analyticsMock)
OneTrustMockGlobal.GetDomainData.mockReturnValueOnce({
Groups: [grpFixture.StrictlyNeccessary, grpFixture.Performance],
...domainDataMock,
ShowAlertNotice: false, // meaning, it's open
})
getConsentedGroupIdsSpy.mockImplementation(() => [
grpFixture.StrictlyNeccessary.CustomGroupId,
domainGroupMock.StrictlyNeccessary.CustomGroupId,
])
const shouldLoadP = Promise.resolve(
const shouldLoadSegment = Promise.resolve(
createWrapperSpyHelper.shouldLoadSegment({} as any)
)
let shouldLoadResolved = false
void shouldLoadP.then(() => (shouldLoadResolved = true))
await sleep(0)
expect(shouldLoadResolved).toBe(false)
OneTrustMockGlobal.IsAlertBoxClosed.mockReturnValueOnce(true)
resolveResolveWhen()
const result = await shouldLoadP
expect(result).toBe(undefined)
OneTrustMockGlobal.IsAlertBoxClosed.mockReturnValueOnce(false) // alert box is _never open
checkResolveWhen()
await expect(shouldLoadSegment).resolves.toBeUndefined()
})
})

describe('getCategories', () => {
it('should get categories successfully', async () => {
withOneTrust(analyticsMock)
OneTrustMockGlobal.GetDomainData.mockReturnValue({
...domainDataMock,
Groups: [
grpFixture.StrictlyNeccessary,
grpFixture.Performance,
grpFixture.Targeting,
domainGroupMock.StrictlyNeccessary,
domainGroupMock.Performance,
domainGroupMock.Targeting,
],
})
getConsentedGroupIdsSpy.mockImplementation(() => [
grpFixture.StrictlyNeccessary.CustomGroupId,
domainGroupMock.StrictlyNeccessary.CustomGroupId,
])
const categories = createWrapperSpyHelper.getCategories()
// contain both consented and denied category
Expand All @@ -116,10 +129,11 @@ describe('High level "integration" tests', () => {
it('should enable consent changed by default', async () => {
withOneTrust(analyticsMock)
OneTrustMockGlobal.GetDomainData.mockReturnValue({
...domainDataMock,
Groups: [
grpFixture.StrictlyNeccessary,
grpFixture.Performance,
grpFixture.Targeting,
domainGroupMock.StrictlyNeccessary,
domainGroupMock.Performance,
domainGroupMock.Targeting,
],
})
const onCategoriesChangedCb = jest.fn()
Expand All @@ -128,7 +142,7 @@ describe('High level "integration" tests', () => {
createWrapperSpyHelper.registerOnConsentChanged(onCategoriesChangedCb)
onCategoriesChangedCb()

resolveResolveWhen() // wait for OneTrust global to be available
checkResolveWhen() // wait for OneTrust global to be available
await sleep(0)

analyticsMock.track.mockImplementationOnce(() => {}) // ignore track event sent by consent changed
Expand All @@ -138,17 +152,17 @@ describe('High level "integration" tests', () => {
onConsentChangedArg(
new CustomEvent('', {
detail: [
grpFixture.StrictlyNeccessary.CustomGroupId,
grpFixture.Performance.CustomGroupId,
domainGroupMock.StrictlyNeccessary.CustomGroupId,
domainGroupMock.Performance.CustomGroupId,
],
})
)

// expect to be normalized!
expect(onCategoriesChangedCb.mock.lastCall[0]).toEqual({
[grpFixture.StrictlyNeccessary.CustomGroupId]: true,
[grpFixture.Performance.CustomGroupId]: true,
[grpFixture.Targeting.CustomGroupId]: false,
[domainGroupMock.StrictlyNeccessary.CustomGroupId]: true,
[domainGroupMock.Performance.CustomGroupId]: true,
[domainGroupMock.Targeting.CustomGroupId]: false,
})
})
})
Expand Down
Loading

0 comments on commit a63f6c1

Please sign in to comment.