Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New privacy-footer-localiser component #842

Closed
wants to merge 12 commits into from
42 changes: 42 additions & 0 deletions packages/dotcom-privacy-footer-localiser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# @financial-times/dotcom-privacy-footer-localiser

This package is meant to modify the default footer of dotcom pages based on the requirements of any local legislation that might apply to a user.


## Getting started

This package is compatible with Node 12+ and is distributed on npm.

```bash
npm install --save @financial-times/dotcom-privacy-footer-localiser
```

### Server-side

This module doesn't export any server-side functionality.


### Client-side

This package exports two methods to manipulate the footer

- `addDNSLinkToFooter`: adds a "Do Not Sell My Info" link above the "Privacy" if the CCPA legislation applies to the user.
- `adaptPrivacyLinkToLegislation`: replaces the text of the "Privacy" link according to the legislation that applies to the user, if needed.

Neither of those two methods accept any parameters. Both methods rely on the presence of a DOM element that matches the selector: `#site-footer [href='http://help.ft.com/help/legal-privacy/privacy/']`

#### Examples

```js
import { addDNSLinkToFooter } from '@financial-times/dotcom-privacy-footer-localiser'

// ... JS operations with higher priority
addDNSLinkToFooter()
```

```js
import { adaptPrivacyLinkToLegislation } from '@financial-times/dotcom-privacy-footer-localiser'

// ... JS operations with higher priority
adaptPrivacyLinkToLegislation()
```
32 changes: 32 additions & 0 deletions packages/dotcom-privacy-footer-localiser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@financial-times/dotcom-privacy-footer-localiser",
"version": "0.0.0",
"description": "",
"browser": "src/main.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"tsc": "../../node_modules/.bin/tsc --incremental",
"clean": "npm run clean:dist && npm run clean:node_modules",
"clean:dist": "rm -rf dist",
"clean:node_modules": "rm -rf node_modules",
"clean:install": "npm run clean && npm i",
"build": "npm run build:node",
"build:node": "npm run tsc -- --module commonjs --outDir ./dist/node",
"dev": "npm run build:node -- --watch"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@financial-times/privacy-legislation-client": "^0.3.7"
},
"engines": {
"node": ">= 12.0.0"
},
"repository": {
"type": "git",
"repository": "https://github.com/Financial-Times/dotcom-page-kit.git",
"directory": "packages/dotcom-privacy-footer-localiser"
},
"homepage": "https://github.com/Financial-Times/dotcom-page-kit/tree/master/packages/dotcom-privacy-footer-localiser"
}
114 changes: 114 additions & 0 deletions packages/dotcom-privacy-footer-localiser/src/__test__/main.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* @jest-environment jsdom
*/
import 'jest-enzyme'
import React from 'react'
import { mount } from 'enzyme'
import { addDNSLinkToFooter, adaptPrivacyLinkToLegislation } from '../main'

const consentPagePath = 'foo'

jest.mock('@financial-times/privacy-legislation-client', () => {
return {
fetchLegislation: jest.fn().mockResolvedValue({ legislation: new Set(['ccpa', 'gdpr']) }),
buildConsentPageUrl: jest.fn(() => consentPagePath)
}
})

const wait = (ms) => setTimeout(() => Promise.resolve(), ms)

const ValidFooter = () => (
<div>
<div id="site-footer">
<a href="http://help.ft.com/help/legal-privacy/privacy/">Privacy</a>
</div>
</div>
)

const InvalidFooter = () => (
<div>
<div id="site-footer">
<a href="http://help.ft.com/help/legal-privacy/something-else/">Privacy</a>
</div>
</div>
)

describe('dotcom-privacy-footer-localiser', () => {
describe('the addDNSLinkToFooter method', () => {
describe('if there is a Privacy link in the footer', () => {
let links

beforeAll(async () => {
document.body.innerHTML = ''
const div = document.createElement('div')
document.body.appendChild(div)
mount(<ValidFooter />, { attachTo: document.querySelector('div') })
await addDNSLinkToFooter()
links = document.querySelectorAll('a')
})

it('should insert a new link', () => {
expect(links.length).toBe(2)
})

it('should insert the new link before the privacy link', () => {
expect(links[0].href).toMatch(consentPagePath)
})

it('should insert the new link with the right label', () => {
expect(links[0].text).toBe('Do Not Sell My Info')
})
})

describe('if there is no Privacy link in the footer', () => {
it('should call console.error', async () => {
const consoleErr = window.console.error
window.console.error = jest.fn()
const div = document.createElement('div')
document.body.appendChild(div)
mount(<InvalidFooter />, { attachTo: document.querySelector('div') })
await addDNSLinkToFooter()
expect(window.console.error).toHaveBeenCalled()
window.console.error = consoleErr
})
})
})

describe('the adaptPrivacyLinkToLegislation method', () => {
describe('if there is a Privacy link in the footer', () => {
let links

beforeAll(async () => {
document.body.innerHTML = ''
const div = document.createElement('div')
document.body.appendChild(div)
mount(<ValidFooter />, { attachTo: document.querySelector('div') })
await wait(1000)
await adaptPrivacyLinkToLegislation()
await wait(1000)
links = document.querySelectorAll('a')
})

it('should NOT insert a new link', () => {
expect(links.length).toBe(1)
})

it('should change the link text to the right label', () => {
expect(links[0].text).toBe('Privacy - CCPA UPDATES')
})
})

describe('if there is no Privacy link in the footer', () => {
it('should call console.error', async () => {
const consoleErr = window.console.error
window.console.error = jest.fn()
const div = document.createElement('div')
document.body.appendChild(div)
mount(<InvalidFooter />, { attachTo: document.querySelector('div') })
await adaptPrivacyLinkToLegislation()
expect(window.console.error).toHaveBeenCalled()
window.console.error = consoleErr
})
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { privacyLinkOnFooter } from './selectors'

export function createCCPALink(consentPageUrl: string): void {
// Get a reference to the node we want to insert our link before
const termsLink = document.querySelector(privacyLinkOnFooter) as HTMLAnchorElement

if (!termsLink || !termsLink.parentNode) {
throw new Error('A DOM node for Privacy link could not be found')
}

// Clone the node to keep consistent with structure and style
const ccpaLink = termsLink.cloneNode(true) as HTMLAnchorElement
const ccpaLabel = 'Do Not Sell My Info'

// Customise the attributes
ccpaLink.href = consentPageUrl
ccpaLink.dataset.trackable = ccpaLabel
ccpaLink.textContent = ccpaLabel

// Prepend our new link
const parent = termsLink.parentNode
parent.insertBefore(ccpaLink, termsLink)
}

export function changePrivacyLinkText(newText: string): void {
// Get a reference to the node we want to insert our link before
const termsLink = document.querySelector(privacyLinkOnFooter) as HTMLAnchorElement

if (!termsLink) {
throw new Error('A Privacy link could not be found in the footer')
}

termsLink.innerHTML = newText
}
33 changes: 33 additions & 0 deletions packages/dotcom-privacy-footer-localiser/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { fetchLegislation, buildConsentPageUrl } from '@financial-times/privacy-legislation-client'
import { createCCPALink, changePrivacyLinkText } from './footer-manipulations'

const CONSENT_URL = 'https://www.ft.com/preferences/privacy-ccpa'

export async function addDNSLinkToFooter(): Promise<void> {
try {
// Get a list of the applicable legislationIds for the user's region
const { legislation } = await fetchLegislation()
const consentPageUrl = buildConsentPageUrl({ url: CONSENT_URL, legislation })

// If the user is in California update our UI to meet CCPA requirements
if (legislation.has('ccpa')) {
createCCPALink(consentPageUrl)
}
} catch (err) {
console.error(err) //eslint-disable-line no-console
}
}

export async function adaptPrivacyLinkToLegislation(): Promise<void> {
try {
// Get a list of the applicable legislationIds for the user's region
const { legislation } = await fetchLegislation()

// If the user is in California update our UI to meet CCPA requirements
if (legislation.has('ccpa')) {
changePrivacyLinkText('Privacy - CCPA UPDATES')
}
} catch (err) {
console.error(err) //eslint-disable-line no-console
}
}
1 change: 1 addition & 0 deletions packages/dotcom-privacy-footer-localiser/src/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const privacyLinkOnFooter = "#site-footer [href='http://help.ft.com/help/legal-privacy/privacy/']"
4 changes: 4 additions & 0 deletions packages/dotcom-privacy-footer-localiser/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"rootDir": "./src/",
"extends": "../../tsconfig"
}