Merge pull request #3238 from alphagov/cookies-update

New cookies banner and page
This commit is contained in:
Tom Byers
2020-01-15 10:37:14 +00:00
committed by GitHub
36 changed files with 1609 additions and 115 deletions
@@ -0,0 +1,62 @@
(function (window) {
"use strict";
window.GOVUK = window.GOVUK || {};
// Stripped-down wrapper for Google Analytics, based on:
// https://github.com/alphagov/static/blob/master/doc/analytics.md
const Analytics = function (config) {
window.ga('create', config.trackingId, config.cookieDomain, config.name, { 'cookieExpires': config.expires * 24 * 60 * 60 });
window.ga('set', 'anonymizeIp', config.anonymizeIp);
window.ga('set', 'displayFeaturesTask', config.displayFeaturesTask);
window.ga('set', 'transport', config.transport);
};
Analytics.load = function () {
/* jshint ignore:start */
(function(i, s, o, g, r, a, m){ i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments) }, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
/* jshint ignore:end */
};
Analytics.prototype.trackPageview = function (path, title, options) {
// strip UUIDs
const page = (window.location.pathname + window.location.search).replace(
/[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}/g, '…'
);
window.ga('send', 'pageview', page);
};
// https://developers.google.com/analytics/devguides/collection/analyticsjs/events
Analytics.prototype.trackEvent = function (category, action, options) {
options = options || {};
var evt = {
eventCategory: category,
eventAction: action
};
if (options.label) {
evt.eventLabel = options.label;
delete options.label;
}
if (typeof options === 'object') {
$.extend(evt, options);
}
window.ga('send', 'event', evt);
};
window.GOVUK.Analytics = Analytics;
})(window);
+41
View File
@@ -0,0 +1,41 @@
(function (window) {
"use strict";
window.GOVUK = window.GOVUK || {};
const trackingId = 'UA-26179049-1';
// Disable analytics by default
window[`ga-disable-${trackingId}`] = true;
const initAnalytics = function () {
// guard against being called more than once
if (!('analytics' in window.GOVUK)) {
window[`ga-disable-${trackingId}`] = false;
// Load Google Analytics libraries
window.GOVUK.Analytics.load();
// Configure profiles and make interface public
// for custom dimensions, virtual pageviews and events
window.GOVUK.analytics = new GOVUK.Analytics({
trackingId: trackingId,
cookieDomain: 'auto',
anonymizeIp: true,
displayFeaturesTask: null,
transport: 'beacon',
name: 'GOVUK.analytics',
expires: 365
});
// Track initial pageview
window.GOVUK.analytics.trackPageview();
}
};
window.GOVUK.initAnalytics = initAnalytics;
})(window);
+15
View File
@@ -0,0 +1,15 @@
(function (window) {
"use strict";
function hasConsentFor (cookieCategory) {
const consentCookie = window.GOVUK.getConsentCookie();
if (consentCookie === null) { return false; }
if (!(cookieCategory in consentCookie)) { return false; }
return consentCookie[cookieCategory];
}
window.GOVUK.hasConsentFor = hasConsentFor;
})(window);
+92 -11
View File
@@ -1,16 +1,97 @@
(function () { window.GOVUK = window.GOVUK || {};
"use strict"; window.GOVUK.Modules = window.GOVUK.Modules || {};
var root = this; (function (Modules) {
if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; } function CookieBanner () { }
GOVUK.addCookieMessage = function () { CookieBanner.clearOldCookies = function () {
var message = document.getElementById('global-cookie-message'), // clear any cookies set by the previous version
hasCookieMessage = (message && GOVUK.cookie('seen_cookie_message') === null); var oldCookies = ['seen_cookie_message', '_ga', '_gid'];
if (hasCookieMessage) { for (var i = 0; i < oldCookies.length; i++) {
message.style.display = 'block'; if (window.GOVUK.cookie(oldCookies[i])) {
GOVUK.cookie('seen_cookie_message', 'yes', { days: 28 }); var cookieString = oldCookies[i] + '=;expires=' + new Date() + ';domain=' + window.location.hostname.replace(/^www\./, '.') + ';path=/';
document.cookie = cookieString;
}
} }
}; };
}).call(this);
CookieBanner.prototype.start = function ($module) {
this.$module = $module[0];
this.$module.hideCookieMessage = this.hideCookieMessage.bind(this);
this.$module.showConfirmationMessage = this.showConfirmationMessage.bind(this);
this.$module.setCookieConsent = this.setCookieConsent.bind(this);
this.$module.cookieBanner = document.querySelector('.notify-cookie-banner');
this.$module.cookieBannerConfirmationMessage = this.$module.querySelector('.notify-cookie-banner__confirmation');
this.setupCookieMessage();
};
CookieBanner.prototype.setupCookieMessage = function () {
this.$hideLink = this.$module.querySelector('button[data-hide-cookie-banner]');
if (this.$hideLink) {
this.$hideLink.addEventListener('click', this.$module.hideCookieMessage);
}
this.$acceptCookiesLink = this.$module.querySelector('button[data-accept-cookies=true]');
if (this.$acceptCookiesLink) {
this.$acceptCookiesLink.addEventListener('click', () => this.$module.setCookieConsent(true));
}
this.$rejectCookiesLink = this.$module.querySelector('button[data-accept-cookies=false]');
if (this.$rejectCookiesLink) {
this.$rejectCookiesLink.addEventListener('click', () => this.$module.setCookieConsent(false));
}
this.showCookieMessage();
};
CookieBanner.prototype.showCookieMessage = function () {
// Show the cookie banner if not in the cookie settings page
if (!this.isInCookiesPage()) {
var hasCookiesPolicy = window.GOVUK.cookie('cookies_policy');
if (this.$module && !hasCookiesPolicy) {
this.$module.style.display = 'block';
}
}
};
CookieBanner.prototype.hideCookieMessage = function (event) {
if (this.$module) {
this.$module.style.display = 'none';
}
if (event.target) {
event.preventDefault();
}
};
CookieBanner.prototype.setCookieConsent = function (analyticsConsent) {
window.GOVUK.setConsentCookie({ 'analytics': analyticsConsent });
this.$module.showConfirmationMessage(analyticsConsent);
this.$module.cookieBannerConfirmationMessage.focus();
if (analyticsConsent) { window.GOVUK.initAnalytics(); }
};
CookieBanner.prototype.showConfirmationMessage = function (analyticsConsent) {
var messagePrefix = analyticsConsent ? 'Youve accepted analytics cookies.' : 'You told us not to use analytics cookies.';
this.$cookieBannerMainContent = document.querySelector('.notify-cookie-banner__wrapper');
this.$cookieBannerConfirmationMessage = document.querySelector('.notify-cookie-banner__confirmation-message');
this.$cookieBannerConfirmationMessage.insertAdjacentText('afterbegin', messagePrefix);
this.$cookieBannerMainContent.style.display = 'none';
this.$module.cookieBannerConfirmationMessage.style.display = 'block';
};
CookieBanner.prototype.isInCookiesPage = function () {
return window.location.pathname === '/cookies';
};
Modules.CookieBanner = CookieBanner;
})(window.GOVUK.Modules);
+84
View File
@@ -0,0 +1,84 @@
window.GOVUK = window.GOVUK || {};
window.GOVUK.Modules = window.GOVUK.Modules || {};
(function (Modules) {
function CookieSettings () {}
CookieSettings.prototype.start = function ($module) {
this.$module = $module[0];
this.$module.submitSettingsForm = this.submitSettingsForm.bind(this);
document.querySelector('form[data-module=cookie-settings]')
.addEventListener('submit', this.$module.submitSettingsForm);
this.setInitialFormValues();
};
CookieSettings.prototype.setInitialFormValues = function () {
var currentConsentCookie = window.GOVUK.getConsentCookie('consent');
if (!currentConsentCookie) { return; }
var radioButton;
if (currentConsentCookie.analytics) {
radioButton = document.querySelector('input[name=cookies-analytics][value=on]');
} else {
radioButton = document.querySelector('input[name=cookies-analytics][value=off]');
}
radioButton.checked = true;
};
CookieSettings.prototype.submitSettingsForm = function (event) {
event.preventDefault();
var formInputs = event.target.querySelectorAll("input[name=cookies-analytics]");
var options = {};
for ( var i = 0; i < formInputs.length; i++ ) {
var input = formInputs[i];
if (input.checked) {
var value = input.value === "on" ? true : false;
options.analytics = value;
break;
}
}
window.GOVUK.setConsentCookie(options);
this.showConfirmationMessage();
if(window.GOVUK.hasConsentFor('analytics')) {
window.GOVUK.initAnalytics();
}
return false;
};
CookieSettings.prototype.showConfirmationMessage = function () {
var confirmationMessage = document.querySelector('div[data-cookie-confirmation]');
var previousPageLink = document.querySelector('.cookie-settings__prev-page');
var referrer = CookieSettings.prototype.getReferrerLink();
document.body.scrollTop = document.documentElement.scrollTop = 0;
if (referrer && referrer !== document.location.pathname) {
previousPageLink.href = referrer;
previousPageLink.style.display = "block";
} else {
previousPageLink.style.display = "none";
}
confirmationMessage.style.display = "block";
};
CookieSettings.prototype.getReferrerLink = function () {
return document.referrer ? new URL(document.referrer).pathname : false;
};
Modules.CookieSettings = CookieSettings;
})(window.GOVUK.Modules);
+8 -8
View File
@@ -1,22 +1,22 @@
(function(Modules) { (function(window) {
"use strict"; "use strict";
Modules.TrackError = function() { window.GOVUK.Modules.TrackError = function() {
this.start = function(component) { this.start = function(component) {
if (!('ga' in window)) return; if (!('analytics' in window.GOVUK)) return;
ga( window.GOVUK.analytics.trackEvent(
'send',
'event',
'Error', 'Error',
$(component).data('error-type'), $(component).data('error-type'),
$(component).data('error-label') {
'label': $(component).data('error-label')
}
); );
}; };
}; };
})(window.GOVUK.Modules); })(window);
+118 -17
View File
@@ -1,8 +1,17 @@
(function () { // used by the cookie banner component
"use strict";
var root = this; (function (root) {
if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; } 'use strict';
window.GOVUK = window.GOVUK || {};
var DEFAULT_COOKIE_CONSENT = {
'analytics': false
};
var COOKIE_CATEGORIES = {
'_ga': 'analytics',
'_gid': 'analytics'
};
/* /*
Cookie methods Cookie methods
@@ -19,38 +28,129 @@
Deleting a cookie: Deleting a cookie:
GOVUK.cookie('hobnob', null); GOVUK.cookie('hobnob', null);
*/ */
GOVUK.cookie = function (name, value, options) { window.GOVUK.cookie = function (name, value, options) {
if (typeof value !== 'undefined') { if (typeof value !== 'undefined') {
if (value === false || value === null) { if (value === false || value === null) {
return GOVUK.setCookie(name, '', { days: -1 }); return window.GOVUK.setCookie(name, '', { days: -1 });
} else { } else {
return GOVUK.setCookie(name, value, options); // Default expiry date of 30 days
if (typeof options === 'undefined') {
options = { days: 30 };
}
return window.GOVUK.setCookie(name, value, options);
} }
} else { } else {
return GOVUK.getCookie(name); return window.GOVUK.getCookie(name);
} }
}; };
GOVUK.setCookie = function (name, value, options) {
window.GOVUK.getConsentCookie = function () {
var consentCookie = window.GOVUK.cookie('cookies_policy');
var consentCookieObj;
if (consentCookie) {
try {
consentCookieObj = JSON.parse(consentCookie);
} catch (err) {
return null;
}
if (typeof consentCookieObj !== 'object' && consentCookieObj !== null) {
consentCookieObj = JSON.parse(consentCookieObj);
}
} else {
return null;
}
return consentCookieObj;
};
window.GOVUK.setConsentCookie = function (options) {
var cookieConsent = window.GOVUK.getConsentCookie();
if (!cookieConsent) {
cookieConsent = JSON.parse(JSON.stringify(DEFAULT_COOKIE_CONSENT));
}
for (var cookieType in options) {
cookieConsent[cookieType] = options[cookieType];
// Delete cookies of that type if consent being set to false
if (!options[cookieType]) {
for (var cookie in COOKIE_CATEGORIES) {
if (COOKIE_CATEGORIES[cookie] === cookieType) {
window.GOVUK.cookie(cookie, null);
if (window.GOVUK.cookie(cookie)) {
document.cookie = cookie + '=;expires=' + new Date() + ';domain=' + window.location.hostname.replace(/^www\./, '.') + ';path=/';
}
}
}
}
}
window.GOVUK.setCookie('cookies_policy', JSON.stringify(cookieConsent), { days: 365 });
};
window.GOVUK.checkConsentCookieCategory = function (cookieName, cookieCategory) {
var currentConsentCookie = window.GOVUK.getConsentCookie();
// If the consent cookie doesn't exist, but the cookie is in our known list, return true
if (!currentConsentCookie && COOKIE_CATEGORIES[cookieName]) {
return true;
}
currentConsentCookie = window.GOVUK.getConsentCookie();
// Sometimes currentConsentCookie is malformed in some of the tests, so we need to handle these
try {
return currentConsentCookie[cookieCategory];
} catch (e) {
console.error(e);
return false;
}
};
window.GOVUK.checkConsentCookie = function (cookieName, cookieValue) {
// If we're setting the consent cookie OR deleting a cookie, allow by default
if (cookieName === 'cookies_policy' || (cookieValue === null || cookieValue === false)) {
return true;
}
if (COOKIE_CATEGORIES[cookieName]) {
var cookieCategory = COOKIE_CATEGORIES[cookieName];
return window.GOVUK.checkConsentCookieCategory(cookieName, cookieCategory);
} else {
// Deny the cookie if it is not known to us
return false;
}
};
window.GOVUK.setCookie = function (name, value, options) {
if (window.GOVUK.checkConsentCookie(name, value)) {
if (typeof options === 'undefined') { if (typeof options === 'undefined') {
options = {}; options = {};
} }
var cookieString = name + "=" + value + "; path=/"; var cookieString = name + '=' + value + '; path=/';
if (options.days) { if (options.days) {
var date = new Date(); var date = new Date();
date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000)); date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000));
cookieString = cookieString + "; expires=" + date.toGMTString(); cookieString = cookieString + '; expires=' + date.toGMTString();
} }
if (document.location.protocol == 'https:'){ if (document.location.protocol === 'https:') {
cookieString = cookieString + "; Secure"; cookieString = cookieString + '; Secure';
} }
document.cookie = cookieString; document.cookie = cookieString;
}
}; };
GOVUK.getCookie = function (name) {
var nameEQ = name + "="; window.GOVUK.getCookie = function (name) {
var nameEQ = name + '=';
var cookies = document.cookie.split(';'); var cookies = document.cookie.split(';');
for (var i = 0, len = cookies.length; i < len; i++) { for (var i = 0, len = cookies.length; i < len; i++) {
var cookie = cookies[i]; var cookie = cookies[i];
while (cookie.charAt(0) == ' ') { while (cookie.charAt(0) === ' ') {
cookie = cookie.substring(1, cookie.length); cookie = cookie.substring(1, cookie.length);
} }
if (cookie.indexOf(nameEQ) === 0) { if (cookie.indexOf(nameEQ) === 0) {
@@ -59,4 +159,5 @@
} }
return null; return null;
}; };
}).call(this); }(window));
+5 -1
View File
@@ -1,6 +1,10 @@
window.GOVUK.Frontend.initAll(); window.GOVUK.Frontend.initAll();
$(() => GOVUK.addCookieMessage()); window.GOVUK.Modules.CookieBanner.clearOldCookies();
if (window.GOVUK.hasConsentFor('analytics')) {
window.GOVUK.initAnalytics();
}
$(() => $("time.timeago").timeago()); $(() => $("time.timeago").timeago());
@@ -1,8 +1,123 @@
.notify-cookie-message { // GOV.UK Publishing components cookie banner styles
@include govuk-font($size: 16); // https://github.com/alphagov/govuk_publishing_components/blob/master/app/assets/stylesheets/govuk_publishing_components/components/_cookie-banner.scss
padding: govuk-spacing(3) 0; // sass-lint:disable mixins-before-declarations
.js-enabled & { // component uses .govuk-body and .govuk-button classes from govuk-frontend
@import 'core/typography';
@import 'components/button/_button';
.notify-cookie-banner__wrapper {
@include govuk-responsive-padding(4, "top");
@include govuk-responsive-padding(5, "bottom");
}
// component should only be shown if JS is available, by the cookieMessage JS, so hide by default
.notify-cookie-banner {
display: none; display: none;
} }
.notify-cookie-banner__buttons {
display: flex;
flex-wrap: wrap;
@include govuk-media-query($from: tablet) {
flex-wrap: nowrap;
}
}
.notify-cookie-banner__button,
.notify-cookie-banner__link {
vertical-align: baseline;
}
.notify-cookie-banner__button {
display: inline-block;
flex: 1 0;
padding-left: govuk-spacing(9);
padding-right: govuk-spacing(9);
margin-bottom: govuk-spacing(2);
@include govuk-media-query($from: tablet) {
flex: 0 0 150px;
padding-left: govuk-spacing(2);
padding-right: govuk-spacing(2);
margin-bottom: govuk-spacing(1);
}
}
.notify-cookie-banner__button-accept {
margin-right: govuk-spacing(4);
}
.notify-cookie-banner__link {
@include govuk-font(19);
line-height: 1;
display: block;
width: 100%;
padding: 9px 0px 6px;
@include govuk-media-query($from: tablet) {
display: inline;
width: auto;
margin-left: govuk-spacing(6);
}
}
.notify-cookie-banner__confirmation {
display: none;
position: relative;
padding: govuk-spacing(4) 0;
@include govuk-media-query($from: desktop) {
padding: govuk-spacing(4);
}
// This element is focused using JavaScript so that it's being read out by screen readers
// for this reason we don't want to show the default outline or emphasise it visually using `govuk-focused-text`
&:focus {
outline: none;
}
}
.notify-cookie-banner__confirmation-message,
.notify-cookie-banner__hide-button {
display: block;
@include govuk-media-query($from: desktop) {
display: inline-block;
}
}
.notify-cookie-banner__confirmation-message {
margin-right: govuk-spacing(4);
@include govuk-media-query($from: desktop) {
max-width: 90%;
}
}
.notify-cookie-banner__hide-button {
@include govuk-font($size: 19);
color: $govuk-link-colour;
outline: 0;
border: 0;
background: none;
text-decoration: underline;
padding: govuk-spacing(0);
margin-top: govuk-spacing(2);
right: govuk-spacing(3);
cursor: pointer;
@include govuk-media-query($from: desktop) {
margin-top: govuk-spacing(0);
position: absolute;
right: govuk-spacing(4);
}
}
// Additions
// Override margin-bottom, inherited from using .govuk-body class
.notify-cookie-banner__confirmation-message {
margin-bottom: 0;
} }
@@ -23,7 +23,9 @@ $govuk-assets-path: "/static/";
@import 'components/header/_header'; @import 'components/header/_header';
@import 'components/footer/_footer'; @import 'components/footer/_footer';
@import 'components/back-link/_back-link'; @import 'components/back-link/_back-link';
@import 'components/button/_button';
@import 'components/details/_details'; @import 'components/details/_details';
@import 'components/radios/_radios';
@import "utilities/all"; @import "utilities/all";
@import "overrides/all"; @import "overrides/all";
@@ -7,3 +7,4 @@
column-count: 4; column-count: 4;
} }
} }
+1
View File
@@ -82,6 +82,7 @@ $path: '/static/images/';
@import 'views/send'; @import 'views/send';
@import 'views/get_started'; @import 'views/get_started';
@import 'views/history'; @import 'views/history';
@import 'views/cookies';
// TODO: break this up // TODO: break this up
@import 'app'; @import 'app';
+17
View File
@@ -0,0 +1,17 @@
.cookie-settings__form-wrapper {
display: none;
.js-enabled & {
display: block;
}
}
.cookie-settings__no-js {
.js-enabled & {
display: none;
}
}
.cookie-settings__confirmation {
display: none;
}
+2 -14
View File
@@ -1,5 +1,6 @@
{% extends "template.njk" %} {% extends "template.njk" %}
{% from "components/banner.html" import banner %} {% from "components/banner.html" import banner %}
{% from "components/cookie-banner.html" import cookie_banner %}
{% block headIcons %} {% block headIcons %}
<link rel="shortcut icon" sizes="16x16 32x32 48x48" href="{{ asset_url('images/favicon.ico') }}" type="image/x-icon" /> <link rel="shortcut icon" sizes="16x16 32x32 48x48" href="{{ asset_url('images/favicon.ico') }}" type="image/x-icon" />
@@ -30,12 +31,7 @@
{% block bodyStart %} {% block bodyStart %}
{% block cookie_message %} {% block cookie_message %}
<div class="notify-cookie-message" id="global-cookie-message"> {{ cookie_banner() }}
<p class="govuk-width-container">
GOV.UK Notify uses cookies to make the site simpler.
<a href="{{ url_for("main.cookies") }}">Find out more about cookies</a>
</p>
</div>
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}
@@ -247,12 +243,4 @@
<!--[if gt IE 8]><!--> <!--[if gt IE 8]><!-->
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script> <script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script>
<!--<![endif]--> <!--<![endif]-->
<script>
if (window.GOVUK.cookie('_ga') !== null) {
document.cookie = '_ga' + '=;expires=' + new Date() + ';domain=' + window.location.hostname.replace(/^www\./, '.') + ';path=/';
}
if (window.GOVUK.cookie('_gid') !== null) {
document.cookie = '_gid' + '=;expires=' + new Date() + ';domain=' + window.location.hostname.replace(/^www\./, '.') + ';path=/';
}
</script>
{% endblock %} {% endblock %}
@@ -0,0 +1,25 @@
{% macro cookie_banner(id='global-cookie-message') %}
<div id="{{ id }}" class="notify-cookie-banner" data-module="cookie-banner" role="region" aria-describedby="notify-cookie-banner__heading">
<div class="notify-cookie-banner__wrapper govuk-width-container">
<h2 class="notify-cookie-banner__heading govuk-heading-m" id="notify-cookie-banner__heading">Can we store analytics cookies on your device?</h2>
<p class="govuk-body">Analytics cookies help us understand how our website is being used.</p>
<div class="notify-cookie-banner__buttons">
<button class="govuk-button notify-cookie-banner__button notify-cookie-banner__button-accept" type="submit" data-accept-cookies="true" aria-describedby="notify-cookie-banner__heading">
Yes<span class="govuk-visually-hidden">, Notify can store analytics cookies on your device</span>
</button>
<button class="govuk-button notify-cookie-banner__button notify-cookie-banner__button-reject" type="submit" data-accept-cookies="false" aria-describedby="notify-cookie-banner__heading">
No<span class="govuk-visually-hidden">, Notify cannot store analytics cookies on your device</span>
</button>
<a class="govuk-link notify-cookie-banner__link" href="/cookies">How Notify uses cookies</a>
</div>
</div>
<div class="notify-cookie-banner__confirmation govuk-width-container" tabindex="-1">
<p class="notify-cookie-banner__confirmation-message govuk-body">
You can <a class="govuk-link" href="/cookies">change your cookie settings</a> at any time.
</p>
<button class="notify-cookie-banner__hide-button govuk-link" data-hide-cookie-banner="true" role="link">Hide</button>
</div>
</div>
{% endmacro %}
+88 -18
View File
@@ -1,29 +1,34 @@
{% extends "withoutnav_template.html" %} {% extends "withoutnav_template.html" %}
{% from "components/banner.html" import banner %}
{% block per_page_title %} {% block per_page_title %}
Cookies Cookies
{% endblock %} {% endblock %}
{% block cookie_message %}{% endblock %}
{% block maincolumn_content %} {% block maincolumn_content %}
<div class="grid-row"> <div class="grid-row">
<div class="column-two-thirds"> <div class="column-two-thirds">
<div class="cookie-settings__confirmation banner banner-with-tick" data-cookie-confirmation="true" role="group" tabindex="-1">
<h2 class="banner-title">Your cookie settings were saved</h2>
<a class="govuk_link cookie-settings__prev-page govuk-!-margin-top-1" href="#" data-module="track-click" data-track-category="cookieSettings" data-track-action="Back to previous page">
Go back to the page you were looking at
</a>
</div>
<h1 class="heading-large">Cookies</h1> <h1 class="heading-large">Cookies</h1>
<p class="summary"> <p class="summary">
GOV.UK Notify puts small files (known as cookies) Cookies are small files saved on your phone, tablet or computer when you visit a website.
onto your computer.
</p>
<p>These cookies are used to remember you once youve logged in.</p>
<p>
Find out <a href="https://ico.org.uk/for-the-public/online/cookies/">how to manage cookies</a>.
</p> </p>
<p>We use cookies to make GOV.UK Notify work and collect information about how you use our service.</p>
<h2 class="heading-medium">Session cookies</h2> <h2 class="heading-medium">Essential cookies</h2>
<p> <p>
We store session cookies on your computer to help keep your information Essential cookies keep your information secure while you use Notify. We do not need to ask permission to use them.
secure while you use the service.
</p> </p>
<table> <table>
<caption class="govuk-visually-hidden">Essential cookies</caption>
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
@@ -37,22 +42,43 @@
notify_admin_session notify_admin_session
</td> </td>
<td width="50%"> <td width="50%">
Used to keep you logged in Used to keep you signed in
</td> </td>
<td> <td>
20 hours 20 hours
</td> </td>
</tr> </tr>
<tr>
<td>
cookie_policy
</td>
<td width="50%">
Saves your cookie consent settings
</td>
<td>
1 year
</td>
</tr>
</tbody> </tbody>
</table> </table>
<h2 class="heading-medium">Introductory message cookie</h2> <h2 class="heading-medium">Analytics cookies (optional)</h2>
<p> <p>
When you first use the service, you may see a pop-up welcome message. With your permission, we use Google Analytics to collect data about how you use Notify. This information helps us to improve our service.
Once youve seen the message, we store a cookie on your computer so it
knows not to show it again.
</p> </p>
<p>
Google is not allowed to use or share our analytics data with anyone.
</p>
<p>
Google Analytics stores anonymised information about:
</p>
<ul class="govuk-list govuk-list--bullet">
<li>how you got to GOV.UK Notify</li>
<li>the pages you visit on Notify and how long you spend on them</li>
<li>any errors you see while using Notify</li>
</ul>
<table> <table>
<caption class="govuk-visually-hidden">Google Analytics cookies</caption>
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
@@ -63,18 +89,62 @@
<tbody> <tbody>
<tr> <tr>
<td> <td>
seen_cookie_message _ga
</td> </td>
<td width="50%"> <td width="50%">
Saves a message to let us know that you have seen our cookie Checks if youve visited Notify before. This helps us count how many people visit our site.
message
</td> </td>
<td> <td>
1 month 2 years
</td>
</tr>
<tr>
<td>
_gid
</td>
<td width="50%">
Checks if youve visited Notify before. This helps us count how many people visit our site.
</td>
<td>
24 hours
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="cookie-settings__no-js">
<h2 class="govuk-heading-s govuk-!-margin-top-6">Do you want to accept analytics cookies?</h2>
<p>We use Javascript to set our analytics cookies. Unfortunately Javascript is not running on your browser, so you cannot change your settings. You can try:</p>
<ul class="govuk-list govuk-list--bullet">
<li>reloading the page</li>
<li>turning on Javascript in your browser</li>
</ul>
</div>
<div class="cookie-settings__form-wrapper">
<form data-module="cookie-settings">
<div class="govuk-form-group govuk-!-margin-top-6">
<fieldset class="govuk-fieldset" aria-describedby="changed-name-hint">
<legend class="govuk-fieldset__legend govuk-fieldset__legend--s">
Do you want to accept analytics cookies?
</legend>
<div class="govuk-radios govuk-radios--inline">
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="cookies-analytics-yes" name="cookies-analytics" type="radio" value="on">
<label class="govuk-label govuk-radios__label" for="cookies-analytics-yes">
Yes
</label>
</div>
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="cookies-analytics-no" name="cookies-analytics" type="radio" value="off">
<label class="govuk-label govuk-radios__label" for="cookies-analytics-no">
No
</label>
</div>
</div>
</fieldset>
</div>
<button class="govuk-button" type="submit">Save cookie settings</button>
</form>
</div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -61,7 +61,7 @@
<p>We will retain your personal data for as long as you have a GOV.UK Notify account.</p> <p>We will retain your personal data for as long as you have a GOV.UK Notify account.</p>
<h2 class="heading-medium">Where your data is processed and stores</h2> <h2 class="heading-medium">Where your data is processed and stored</h2>
<p>We design, build and run our systems to make sure that your data is as safe as possible at any stage, both while its <p>We design, build and run our systems to make sure that your data is as safe as possible at any stage, both while its
processed and when its stored.</p> processed and when its stored.</p>
+5 -1
View File
@@ -142,7 +142,11 @@ const javascripts = () => {
paths.toolkit + 'javascripts/govuk/modules.js', paths.toolkit + 'javascripts/govuk/modules.js',
paths.toolkit + 'javascripts/govuk/show-hide-content.js', paths.toolkit + 'javascripts/govuk/show-hide-content.js',
paths.src + 'javascripts/govuk/cookie-functions.js', paths.src + 'javascripts/govuk/cookie-functions.js',
paths.src + 'javascripts/consent.js',
paths.src + 'javascripts/analytics/analytics.js',
paths.src + 'javascripts/analytics/init.js',
paths.src + 'javascripts/cookieMessage.js', paths.src + 'javascripts/cookieMessage.js',
paths.src + 'javascripts/cookieSettings.js',
paths.src + 'javascripts/stick-to-window-when-scrolling.js', paths.src + 'javascripts/stick-to-window-when-scrolling.js',
paths.src + 'javascripts/apiKey.js', paths.src + 'javascripts/apiKey.js',
paths.src + 'javascripts/autofocus.js', paths.src + 'javascripts/autofocus.js',
@@ -160,7 +164,7 @@ const javascripts = () => {
paths.src + 'javascripts/templateFolderForm.js', paths.src + 'javascripts/templateFolderForm.js',
paths.src + 'javascripts/collapsibleCheckboxes.js', paths.src + 'javascripts/collapsibleCheckboxes.js',
paths.src + 'javascripts/radioSlider.js', paths.src + 'javascripts/radioSlider.js',
paths.src + 'javascripts/main.js' paths.src + 'javascripts/main.js',
]) ])
.pipe(plugins.prettyerror()) .pipe(plugins.prettyerror())
.pipe(plugins.babel({ .pipe(plugins.babel({
@@ -22,7 +22,7 @@ def test_should_render_email_verification_resend_show_email_address_and_resend_v
assert page.h1.string == 'Check your email' assert page.h1.string == 'Check your email'
expected = "A new confirmation email has been sent to {}".format(api_user_active['email_address']) expected = "A new confirmation email has been sent to {}".format(api_user_active['email_address'])
message = page.find_all('p')[1].text message = page.select('main p')[0].text
assert message == expected assert message == expected
mock_send_verify_email.assert_called_with(api_user_active['id'], api_user_active['email_address']) mock_send_verify_email.assert_called_with(api_user_active['id'], api_user_active['email_address'])
@@ -66,7 +66,7 @@ def test_should_render_correct_resend_template_for_pending_user(
assert page.h1.string == 'Check your mobile number' assert page.h1.string == 'Check your mobile number'
expected = 'Check your mobile phone number is correct and then resend the security code.' expected = 'Check your mobile phone number is correct and then resend the security code.'
message = page.find_all('p')[1].text message = page.select('main p')[0].text
assert message == expected assert message == expected
assert page.find('form').input['value'] == api_user_pending['mobile_number'] assert page.find('form').input['value'] == api_user_pending['mobile_number']
+1 -1
View File
@@ -508,7 +508,7 @@ def test_should_show_scheduled_job(
template_id='5d729fbd-239c-44ab-b498-75a985f3198f', template_id='5d729fbd-239c-44ab-b498-75a985f3198f',
version=1, version=1,
) )
assert page.select_one('button[type=submit]').text.strip() == 'Cancel sending' assert page.select_one('main button[type=submit]').text.strip() == 'Cancel sending'
def test_should_cancel_job( def test_should_cancel_job(
+2 -2
View File
@@ -1046,7 +1046,7 @@ def test_edit_user_email_page(
assert page.find('h1').text == "Change team members email address" assert page.find('h1').text == "Change team members email address"
assert page.select('p[id=user_name]')[0].text == "This will change the email address for {}.".format(user['name']) assert page.select('p[id=user_name]')[0].text == "This will change the email address for {}.".format(user['name'])
assert page.select('input[type=email]')[0].attrs["value"] == user['email_address'] assert page.select('input[type=email]')[0].attrs["value"] == user['email_address']
assert page.select('button[type=submit]')[0].text == "Save" assert page.select('main button[type=submit]')[0].text == "Save"
def test_edit_user_email_page_404_for_non_team_member( def test_edit_user_email_page_404_for_non_team_member(
@@ -1367,7 +1367,7 @@ def test_edit_user_mobile_number_page(
"This will change the mobile number for {}." "This will change the mobile number for {}."
).format(active_user_with_permissions['name']) ).format(active_user_with_permissions['name'])
assert page.select('input[name=mobile_number]')[0].attrs["value"] == "0770••••762" assert page.select('input[name=mobile_number]')[0].attrs["value"] == "0770••••762"
assert page.select('button[type=submit]')[0].text == "Save" assert page.select('main button[type=submit]')[0].text == "Save"
def test_edit_user_mobile_number_redirects_to_confirmation( def test_edit_user_mobile_number_redirects_to_confirmation(
+6 -6
View File
@@ -1003,7 +1003,7 @@ def test_send_test_doesnt_show_file_contents(
assert page.select('h1')[0].text.strip() == 'Preview of Two week reminder' assert page.select('h1')[0].text.strip() == 'Preview of Two week reminder'
assert len(page.select('table')) == 0 assert len(page.select('table')) == 0
assert len(page.select('.banner-dangerous')) == 0 assert len(page.select('.banner-dangerous')) == 0
assert page.select_one('button[type=submit]').text.strip() == 'Send 1 text message' assert page.select_one('main button[type=submit]').text.strip() == 'Send 1 text message'
@pytest.mark.parametrize('user, endpoint, template_type, content_has_placeholders, expected_recipient', [ @pytest.mark.parametrize('user, endpoint, template_type, content_has_placeholders, expected_recipient', [
@@ -2229,7 +2229,7 @@ def test_letter_can_only_be_sent_now(
assert 'name="scheduled_for"' not in page assert 'name="scheduled_for"' not in page
assert normalize_spaces( assert normalize_spaces(
page.select_one('[type=submit]').text page.select_one('main [type=submit]').text
) == ( ) == (
'Send 1 letter' 'Send 1 letter'
) )
@@ -2259,7 +2259,7 @@ def test_send_button_is_correctly_labelled(
) )
assert normalize_spaces( assert normalize_spaces(
page.select_one('[type=submit]').text page.select_one('main [type=submit]').text
) == ( ) == (
'Send 1,000 text messages' 'Send 1,000 text messages'
) )
@@ -2891,7 +2891,7 @@ def test_check_messages_does_not_allow_to_send_letter_longer_than_10_pages(
assert page.find('h1', {"data-error-type": "letter-too-long"}) assert page.find('h1', {"data-error-type": "letter-too-long"})
assert len(page.select('.letter img')) == 10 # if letter longer than 10 pages, only 10 first pages are displayed assert len(page.select('.letter img')) == 10 # if letter longer than 10 pages, only 10 first pages are displayed
assert not page.select('[type=submit]') assert not page.select('main [type=submit]')
def test_check_messages_shows_data_errors_before_trial_mode_errors_for_letters( def test_check_messages_shows_data_errors_before_trial_mode_errors_for_letters(
@@ -3218,7 +3218,7 @@ def test_send_one_off_letter_errors_in_trial_mode(
assert len(page.select('.letter img')) == 5 assert len(page.select('.letter img')) == 5
assert not page.select('[type=submit]') assert not page.select('main [type=submit]')
assert page.select_one('.govuk-back-link').text == 'Back' assert page.select_one('.govuk-back-link').text == 'Back'
assert page.select_one('a[download]').text == 'Download as a PDF' assert page.select_one('a[download]').text == 'Download as a PDF'
@@ -3259,7 +3259,7 @@ def test_send_one_off_letter_errors_if_letter_longer_than_10_pages(
assert page.find('h1', {"data-error-type": "letter-too-long"}) assert page.find('h1', {"data-error-type": "letter-too-long"})
assert len(page.select('.letter img')) == 10 assert len(page.select('.letter img')) == 10
assert not page.select('[type=submit]') assert not page.select('main [type=submit]')
def test_check_messages_shows_over_max_row_error( def test_check_messages_shows_over_max_row_error(
@@ -435,7 +435,7 @@ def test_show_restricted_service(
) )
assert page.find('h1').text == 'Settings' assert page.find('h1').text == 'Settings'
assert page.find_all('h2')[0].text == 'Your service is in trial mode' assert page.select('main h2')[0].text == 'Your service is in trial mode'
request_to_live = page.select('main p')[1] request_to_live = page.select('main p')[1]
request_to_live_link = request_to_live.select_one('a') request_to_live_link = request_to_live.select_one('a')
@@ -889,7 +889,7 @@ def test_should_not_show_go_live_button_if_checklist_not_complete(
page.select_one('[type=submit]').text.strip() == ('Request to go live') page.select_one('[type=submit]').text.strip() == ('Request to go live')
else: else:
assert not page.select('form') assert not page.select('form')
assert not page.select('[type=submit]') assert not page.select('main [type=submit]')
assert len(page.select('main p')) == 1 assert len(page.select('main p')) == 1
assert normalize_spaces(page.select_one('main p').text) == ( assert normalize_spaces(page.select_one('main p').text) == (
'You must complete these steps before you can request to go live.' 'You must complete these steps before you can request to go live.'
@@ -1192,8 +1192,8 @@ def test_non_gov_user_is_told_they_cant_go_live(
assert normalize_spaces(page.select_one('main p').text) == ( assert normalize_spaces(page.select_one('main p').text) == (
'Only team members with a government email address can request to go live.' 'Only team members with a government email address can request to go live.'
) )
assert len(page.select('form')) == 0 assert len(page.select('main form')) == 0
assert len(page.select('button')) == 1 assert len(page.select('main button')) == 0
@pytest.mark.parametrize('consent_to_research, displayed_consent', ( @pytest.mark.parametrize('consent_to_research, displayed_consent', (
@@ -835,18 +835,18 @@ def test_delete_template_folder_should_request_confirmation(
assert page.select_one('input[name=name]')['value'] == 'sacrifice' assert page.select_one('input[name=name]')['value'] == 'sacrifice'
assert len(page.select('form')) == 2 assert len(page.select('main form')) == 2
assert len(page.select('button')) == 3 assert len(page.select('main button')) == 2
assert 'action' not in page.select('form')[0] assert 'action' not in page.select('main form')[0]
assert page.select('form button')[0].text == 'Yes, delete' assert page.select('main form button')[0].text == 'Yes, delete'
assert page.select('form')[1]['action'] == url_for( assert page.select('main form')[1]['action'] == url_for(
'main.manage_template_folder', 'main.manage_template_folder',
service_id=service_one['id'], service_id=service_one['id'],
template_folder_id=folder_id, template_folder_id=folder_id,
) )
assert page.select('form button')[1].text == 'Save' assert page.select('main form button')[1].text == 'Save'
def test_delete_template_folder_should_detect_non_empty_folder_on_get( def test_delete_template_folder_should_detect_non_empty_folder_on_get(
+2 -2
View File
@@ -103,7 +103,7 @@ def test_post_upload_letter_redirects_for_valid_file(
assert not page.find(id='validation-error-message') assert not page.find(id='validation-error-message')
assert page.find('input', {'type': 'hidden', 'name': 'file_id', 'value': fake_uuid}) assert page.find('input', {'type': 'hidden', 'name': 'file_id', 'value': fake_uuid})
assert page.find('button', {'type': 'submit'}).text == 'Send 1 letter' assert page.select('main button[type=submit]')[0].text == 'Send 1 letter'
def test_post_upload_letter_shows_letter_preview_for_valid_file( def test_post_upload_letter_shows_letter_preview_for_valid_file(
@@ -406,7 +406,7 @@ def test_uploaded_letter_preview_does_not_show_send_button_if_service_in_trial_m
'Recipient: The Queen' 'Recipient: The Queen'
) )
assert not page.find('form') assert not page.find('form')
assert not page.find('button', {'type': 'submit'}) assert len(page.select('main button[type=submit]')) == 0
@pytest.mark.parametrize('invalid_pages, page_requested, overlay_expected', ( @pytest.mark.parametrize('invalid_pages, page_requested, overlay_expected', (
+1 -1
View File
@@ -22,7 +22,7 @@ def test_should_return_verify_template(
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.h1.text == 'Check your phone' assert page.h1.text == 'Check your phone'
message = page.find_all('p')[1].text message = page.select('main p')[0].text
assert message == "Weve sent you a text message with a security code." assert message == "Weve sent you a text message with a security code."
@@ -0,0 +1,127 @@
const helpers = require('../support/helpers');
beforeAll(() => {
// add the script GA looks for in the document
document.body.appendChild(document.createElement('script'));
require('../../../app/assets/javascripts/govuk/cookie-functions.js');
require('../../../app/assets/javascripts/analytics/analytics.js');
require('../../../app/assets/javascripts/analytics/init.js');
});
afterAll(() => {
require('../support/teardown.js');
});
describe("Analytics", () => {
let analytics;
beforeEach(() => {
window.ga = jest.fn();
analytics = new GOVUK.Analytics({
trackingId: 'UA-75215134-1',
cookieDomain: 'auto',
anonymizeIp: true,
displayFeaturesTask: null,
transport: 'beacon',
name: 'GOVUK.analytics',
expires: 365
});
});
afterEach(() => {
window.ga.mockClear();
});
describe("When created", () => {
test("It configures a tracker", () => {
setUpArguments = window.ga.mock.calls;
expect(setUpArguments[0]).toEqual(['create', 'UA-75215134-1', 'auto', 'GOVUK.analytics', { 'cookieExpires': 31536000 }]);
expect(setUpArguments[1]).toEqual(['set', 'anonymizeIp', true]);
expect(setUpArguments[2]).toEqual(['set', 'displayFeaturesTask', null]);
expect(setUpArguments[3]).toEqual(['set', 'transport', 'beacon']);
});
});
describe("When tracking pageviews", () => {
beforeEach(() => {
// clear calls to window.ga from set up
window.ga.mockClear();
});
test("It sends the right URL for the page if no arguments", () => {
jest.spyOn(window, 'location', 'get').mockImplementation(() => {
return {
'pathname': '/privacy',
'search': ''
};
});
analytics.trackPageview();
expect(window.ga.mock.calls[0]).toEqual(['send', 'pageview', '/privacy']);
});
test("It strips the UUIDs from URLs", () => {
jest.spyOn(window, 'location', 'get').mockImplementation(() => {
return {
'pathname': '/services/6658542f-0cad-491f-bec8-ab8457700ead',
'search': ''
};
});
analytics.trackPageview();
expect(window.ga.mock.calls[0]).toEqual(['send', 'pageview', '/services/…']);
});
});
describe("When tracking events", () => {
beforeEach(() => {
// clear calls to window.ga from set up
window.ga.mockClear();
});
test("It sends the right arguments to `ga`", () => {
analytics.trackEvent('Error', 'Enter a valid email address', {
'label': 'email_address'
});
expect(window.ga.mock.calls[0]).toEqual(['send', 'event', {
'eventCategory': 'Error',
'eventAction': 'Enter a valid email address',
'eventLabel': 'email_address'
}]);
});
});
});
+123
View File
@@ -0,0 +1,123 @@
const helpers = require('../support/helpers');
beforeAll(() => {
// add the script GA looks for in the document
document.body.appendChild(document.createElement('script'));
require('../../../app/assets/javascripts/govuk/cookie-functions.js');
require('../../../app/assets/javascripts/analytics/analytics.js');
require('../../../app/assets/javascripts/analytics/init.js');
});
afterAll(() => {
require('../support/teardown.js');
});
describe("Analytics init", () => {
beforeAll(() => {
window.ga = jest.fn();
jest.spyOn(window.GOVUK.Analytics, 'load');
// pretend we're on the /privacy page
jest.spyOn(window, 'location', 'get').mockImplementation(() => {
return {
'pathname': '/privacy',
'search': ''
};
});
});
afterEach(() => {
window.GOVUK.Analytics.load.mockClear();
window.ga.mockClear();
});
test("After the init.js script has been loaded, Google Analytics will be disabled", () => {
expect(window['ga-disable-UA-26179049-1']).toBe(true);
});
describe("If initAnalytics has already been called", () => {
beforeAll(() => {
// Fake a tracker instance
window.GOVUK.analytics = {};
});
beforeEach(() => {
window.GOVUK.initAnalytics();
});
afterAll(() => {
delete window.GOVUK.analytics;
});
test("The Google Analytics libraries will not be loaded", () => {
expect(window.GOVUK.Analytics.load).not.toHaveBeenCalled();
});
});
describe("If initAnalytics has not been called", () => {
beforeEach(() => {
window.GOVUK.initAnalytics();
});
afterEach(() => {
// window.GOVUK.initAnalytics sets up a new window.GOVUK.analytics which needs clearing
delete window.GOVUK.analytics;
});
test("Google Analytics will not be disabled", () => {
expect(window['ga-disable-UA-26179049-1']).toBe(false);
});
test("The Google Analytics libraries will have been loaded", () => {
expect(window.GOVUK.Analytics.load).toHaveBeenCalled();
});
test("There will be an interface with the Google Analytics API", () => {
expect(window.GOVUK.analytics).toBeDefined();
});
test("A pageview will be registered", () => {
expect(window.ga.mock.calls.length).toEqual(5);
// The first 4 calls configure the analytics tracker. All subsequent calls send data
expect(window.ga.mock.calls[4]).toEqual(['send', 'pageview', '/privacy']);
});
});
});
+55
View File
@@ -0,0 +1,55 @@
const helpers = require('./support/helpers');
beforeAll(() => {
require('../../app/assets/javascripts/govuk/cookie-functions.js');
require('../../app/assets/javascripts/consent.js');
});
afterAll(() => {
require('./support/teardown.js');
});
describe("Cookie consent", () => {
describe("hasConsentFor", () => {
afterEach(() => {
// remove cookie set by tests
helpers.deleteCookie('cookies_policy');
});
test("If there is no consent cookie, return false", () => {
expect(window.GOVUK.hasConsentFor('analytics')).toBe(false);
});
describe("If a consent cookie is set", () => {
test("If the category is not saved in the cookie, return false", () => {
window.GOVUK.setConsentCookie({ 'usage': true });
expect(window.GOVUK.hasConsentFor('analytics')).toBe(false);
});
test("If the category is saved in the cookie, return its value", () => {
window.GOVUK.setConsentCookie({ 'analytics': true });
expect(window.GOVUK.hasConsentFor('analytics')).toBe(true);
});
});
});
});
+237
View File
@@ -0,0 +1,237 @@
const helpers = require('./support/helpers');
beforeAll(() => {
require('../../app/assets/javascripts/govuk/cookie-functions.js');
require('../../app/assets/javascripts/analytics/analytics.js');
require('../../app/assets/javascripts/analytics/init.js');
require('../../app/assets/javascripts/cookieMessage.js');
});
afterAll(() => {
require('./support/teardown.js');
});
describe("Cookie message", () => {
let cookieMessage;
beforeAll(() => {
const cookieMessageStyles = document.createElement('style');
// add the CSS that hides the cookie message by default
cookieMessageStyles.textContent = '.notify-cookie-banner { display: none; }';
document.getElementsByTagName('head')[0].appendChild(cookieMessageStyles);
// protect against any previous tests setting a cookies-policy cookie
helpers.deleteCookie('cookies-policy');
});
beforeEach(() => {
// add the script GA looks for in the document
document.body.appendChild(document.createElement('script'));
jest.spyOn(window.GOVUK, 'initAnalytics');
cookieMessage = `
<div id="global-cookie-message" class="notify-cookie-banner" data-module="cookie-banner" role="region" aria-label="cookie banner" data-nosnippet="">
<div class="notify-cookie-banner__wrapper govuk-width-container govuk-!-padding-4">
<h2 class="notify-cookie-banner__heading govuk-heading-m">Cookies on GOV.UK Notify</h2>
<p class="notify-cookie-banner__message govuk-body">We use <a class="govuk-link" href="/cookies">small files called cookies</a> to make GOV.UK Notify work.</p>
<div class="notify-cookie-banner__buttons notify-cookie-banner__no-js">
<div class="notify-cookie-banner__button">
<a href="/cookies" class="govuk-button notify-cookie-banner-button--inline" role="button" data-accept-cookies="true">Set cookie preferences</a>
</div>
</div>
<div class="notify-cookie-banner__with-js">
<p class="notify-cookie-banner__message govuk-body">We'd also like to use analytics cookies to help us improve our service.</p>
<p class="notify-cookie-banner__message govuk-body">Please let us know if this is OK.</p>
<div class="notify-cookie-banner__buttons">
<div class="notify-cookie-banner__button notify-cookie-banner__button-accept">
<button class="govuk-button notify-cookie-banner-button--secondary notify-cookie-banner-button--inline" type="submit" data-accept-cookies="true">Yes, I accept analytics cookies</button>
</div>
<div class="notify-cookie-banner__button notify-cookie-banner__button-reject">
<button class="govuk-button notify-cookie-banner-button--secondary notify-cookie-banner-button--inline" type="submit" data-accept-cookies="false">No, do not use analytics cookies</button>
</div>
</div>
</div>
</div>
<div class="notify-cookie-banner__confirmation govuk-width-container" tabindex="-1">
<p class="notify-cookie-banner__confirmation-message govuk-body">
You can <a class="govuk-link" href="/cookies">change your cookie settings</a> at any time.
</p>
<button class="notify-cookie-banner__hide-button" data-hide-cookie-banner="true">Hide</button>
</div>
</div>`;
document.body.innerHTML += cookieMessage;
});
afterEach(() => {
document.body.innerHTML = '';
// remove cookie set by tests
helpers.deleteCookie('cookies_policy');
// reset spies
window.GOVUK.initAnalytics.mockClear();
// remove analytics tracker
delete window.GOVUK.analytics;
// reset global variable to state when init.js loaded
window['ga-disable-UA-26179049-1'] = true;
});
/*
Note: If no JS, the cookie banner is hidden.
This works through CSS, based on the presence of the `js-enabled` class on the <body> so is not tested here.
*/
test("If the cookies set by the old banner still exist, they can be cleared with the `clearOldCookies` method", () => {
helpers.setCookie('seen_cookie_message', 'true', { 'days': 365 });
helpers.setCookie('_ga', 'GA1.1.123.123', { 'days': 365 });
helpers.setCookie('_gid', 'GA1.1.456.456', { 'days': 1 });
window.GOVUK.Modules.CookieBanner.clearOldCookies();
expect(window.GOVUK.cookie('seen_cookie_message')).toBeNull();
expect(window.GOVUK.cookie('_ga')).toBeNull();
expect(window.GOVUK.cookie('_gid')).toBeNull();
});
test("If user has made a choice to give their consent or not, the cookie banner should be hidden", () => {
window.GOVUK.setConsentCookie({ 'analytics': false });
window.GOVUK.modules.start()
expect(helpers.element(document.querySelector('.notify-cookie-banner')).is('hidden')).toBe(true);
});
describe("If user hasn't made a choice to give their consent or not", () => {
beforeEach(() => {
window.GOVUK.modules.start();
});
test("The cookie banner should show", () => {
const banner = helpers.element(document.querySelector('.notify-cookie-banner'));
expect(banner.is('hidden')).toBe(false);
});
test("No analytics should run", () => {
expect(window.GOVUK.initAnalytics).not.toHaveBeenCalled();
});
describe("If the user clicks the button to accept analytics", () => {
beforeEach(() => {
const acceptButton = document.querySelector('.notify-cookie-banner__button-accept button');
helpers.triggerEvent(acceptButton, 'click');
});
test("the banner should confirm your choice and link to the cookies page as a way to change your mind", () => {
confirmation = helpers.element(document.querySelector('.notify-cookie-banner__confirmation'));
expect(confirmation.is('hidden')).toBe(false);
expect(confirmation.el.textContent.trim()).toEqual(expect.stringMatching(/^Youve accepted analytics cookies/));
});
test("If the user clicks the 'hide' button, the banner should be hidden", () => {
const hideButton = document.querySelector('.notify-cookie-banner__hide-button');
const banner = helpers.element(document.querySelector('.notify-cookie-banner'));
helpers.triggerEvent(hideButton, 'click');
expect(banner.is('hidden')).toBe(true);
});
test("The consent cookie should be set, with analytics set to 'true'", () => {
expect(window.GOVUK.getConsentCookie()).toEqual({ 'analytics': true });
});
test("The analytics should be set up", () => {
expect(window.GOVUK.analytics).toBeDefined();
});
});
describe("If the user clicks the button to reject analytics", () => {
beforeEach(() => {
const rejectButton = document.querySelector('.notify-cookie-banner__button-reject button');
helpers.triggerEvent(rejectButton, 'click');
});
test("the banner should confirm your choice and link to the cookies page as a way to change your mind", () => {
confirmation = helpers.element(document.querySelector('.notify-cookie-banner__confirmation'));
expect(confirmation.is('hidden')).toBe(false);
expect(confirmation.el.textContent.trim()).toEqual(expect.stringMatching(/^You told us not to use analytics cookies/));
});
test("If the user clicks the 'hide' button, the banner should be hidden", () => {
const hideButton = document.querySelector('.notify-cookie-banner__hide-button');
const banner = helpers.element(document.querySelector('.notify-cookie-banner'));
helpers.triggerEvent(hideButton, 'click');
expect(banner.is('hidden')).toBe(true);
});
test("The consent cookie should be set, with analytics set to 'false'", () => {
expect(window.GOVUK.getConsentCookie()).toEqual({ 'analytics': false });
});
test("The analytics should not be set up", () => {
expect(window.GOVUK.analytics).not.toBeDefined();
});
});
});
});
+255
View File
@@ -0,0 +1,255 @@
const helpers = require('./support/helpers');
beforeAll(() => {
require('../../app/assets/javascripts/govuk/cookie-functions.js');
require('../../app/assets/javascripts/consent.js');
require('../../app/assets/javascripts/analytics/analytics.js');
require('../../app/assets/javascripts/analytics/init.js');
require('../../app/assets/javascripts/cookieSettings.js');
});
afterAll(() => {
require('./support/teardown.js');
});
describe("Cookie settings", () => {
let cookiesPageContent;
let yesRadio;
let noRadio;
let saveButton;
beforeEach(() => {
// add the script GA looks for in the document
document.body.appendChild(document.createElement('script'));
window.ga = jest.fn();
jest.spyOn(window.GOVUK, 'initAnalytics');
cookiesPageContent = `
<div class="cookie-settings__confirmation banner banner-with-tick" data-cookie-confirmation="true" role="group" tabindex="-1">
<h2 class="banner-title">Your cookie settings were saved</h2>
<a class="govuk_link cookie-settings__prev-page" href="#" data-module="track-click" data-track-category="cookieSettings" data-track-action="Back to previous page">
Go back to the page you were looking at
</a>
</div>
<h1 class="heading-large">Cookies</h1>
<p class="summary">
Cookies are small files saved on your phone, tablet or computer when you visit a website.
</p>
<p>We use cookies to make GOV.UK Notify work and collect information about how you use our service.</p>
<div class="cookie-settings__no-js">
<h2 class="govuk-heading-s govuk-!-margin-top-6">Do you want to accept analytics cookies?</h2>
<p>We use Javascript to set most of our cookies. Unfortunately Javascript is not running on your browser, so you cannot change your settings. You can try:</p>
<ul class="govuk-list govuk-list--bullet">
<li>reloading the page</li>
<li>turning on Javascript in your browser</li>
</ul>
</div>
<h2 class="heading-medium">Analytics cookies (optional)</h2>
<div class="cookie-settings__form-wrapper">
<form data-module="cookie-settings">
<div class="govuk-form-group govuk-!-margin-top-6">
<fieldset class="govuk-fieldset" aria-describedby="changed-name-hint">
<legend class="govuk-fieldset__legend govuk-fieldset__legend--s">
Do you want to accept analytics cookies?
</legend>
<div class="govuk-radios govuk-radios--inline">
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="cookies-analytics-yes" name="cookies-analytics" type="radio" value="on">
<label class="govuk-label govuk-radios__label" for="cookies-analytics-yes">
Yes
</label>
</div>
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="cookies-analytics-no" name="cookies-analytics" type="radio" value="off">
<label class="govuk-label govuk-radios__label" for="cookies-analytics-no">
No
</label>
</div>
</div>
</fieldset>
</div>
<button class="govuk-button" type="submit">Save cookie settings</button>
</form>
</div>`;
document.body.innerHTML += cookiesPageContent;
yesRadio = document.querySelector('#cookies-analytics-yes');
noRadio = document.querySelector('#cookies-analytics-no');
saveButton = document.querySelector('.govuk-button');
});
afterEach(() => {
document.body.innerHTML = '';
// remove cookie set by tests
helpers.deleteCookie('cookies_policy');
// reset spies
window.ga.mockClear();
window.GOVUK.initAnalytics.mockClear();
// remove analytics tracker
delete window.GOVUK.analytics;
// reset global variable to state when init.js loaded
window['ga-disable-UA-26179049-1'] = true;
});
/*
Note: If no JS, the cookies page contains content to explain why JS is required to set analytics cookies.
This is hidden if JS is available when the page loads.
The message displayed to confirm any selection made is also in the page but hidden on load.
Both of these work through CSS, based on the presence of the `js-enabled` class on the <body> so are not tested here.
*/
describe("When the page loads", () => {
test("If user has not chosen to accept or reject analytics, the radios for making that choice should be set to unchecked", () => {
window.GOVUK.modules.start();
expect(yesRadio.checked).toBe(false);
expect(noRadio.checked).toBe(false);
});
test("If analytics are accepted, the radio for 'accept analytics' should be set to checked", () => {
window.GOVUK.setConsentCookie({ 'analytics': true });
window.GOVUK.modules.start();
expect(yesRadio.checked).toBe(true);
expect(noRadio.checked).toBe(false);
});
test("If analytics are rejected, the radio for 'reject analytics' should be set to checked", () => {
window.GOVUK.setConsentCookie({ 'analytics': false });
window.GOVUK.modules.start();
expect(yesRadio.checked).toBe(false);
expect(noRadio.checked).toBe(true);
});
});
describe("When the 'Save cookie settings' button is clicked", () => {
beforeEach(() => {
window.GOVUK.modules.start();
});
test("If no selection is made, set consent to reject analytics", () => {
helpers.triggerEvent(saveButton, 'click');
expect(window.GOVUK.getConsentCookie()).toEqual({ 'analytics': false });
});
test("If a selection is made, save this as consent", () => {
yesRadio.checked = true;
helpers.triggerEvent(saveButton, 'click');
expect(window.GOVUK.getConsentCookie()).toEqual({ 'analytics': true });
});
describe("The message confirming your choice", () => {
let confirmationMessage;
beforeEach(() => {
confirmationMessage = document.querySelector('.cookie-settings__confirmation');
helpers.triggerEvent(saveButton, 'click');
});
test("Should be shown when the 'Save cookie settings' button is clicked", () => {
expect(helpers.element(confirmationMessage).is('hidden')).toBe(false);
});
test("Should include a link to the last page visited, if information on the referrer is available", () => {
jest.spyOn(document, 'referrer', 'get').mockReturnValue('https://notifications.service.gov.uk/privacy');
helpers.triggerEvent(saveButton, 'click');
expect(confirmationMessage.querySelector('.cookie-settings__prev-page').getAttribute('href')).toEqual('/privacy');
});
});
describe("Analytics code", () => {
beforeAll(() => {
jest.spyOn(window, 'location', 'get').mockImplementation(() => {
return {
'pathname': '/privacy',
'search': ''
}
});
});
test("if user accepted analytics, the analytics code should initialise and register a pageview", () => {
window.GOVUK.modules.start();
yesRadio.checked = true;
helpers.triggerEvent(saveButton, 'click');
expect(window.GOVUK.initAnalytics).toHaveBeenCalled();
expect(window.ga).toHaveBeenCalled();
// the first 4 calls are configuration
expect(window.ga.mock.calls[4]).toEqual(['send', 'pageview', '/privacy']);
});
test("if user rejected analytics, the analytics code should not run", () => {
window.GOVUK.modules.start();
noRadio.checked = true;
helpers.triggerEvent(saveButton, 'click');
expect(window.GOVUK.initAnalytics).not.toHaveBeenCalled();
});
});
});
});
+9 -4
View File
@@ -18,18 +18,23 @@ describe('Error tracking', () => {
afterEach(() => { afterEach(() => {
document.body.innerHTML = ''; document.body.innerHTML = '';
delete window.GOVUK.analytics;
}); });
test("It should send the right data to Google Analytics", () => { test("If there is an analytics tracker set up, it should send details of the error to window.GOVUK.analytic", () => {
window.ga = jest.fn(() => {}); window.GOVUK.analytics = {
'trackEvent': jest.fn()
};
// start the module // start the module
window.GOVUK.modules.start(); window.GOVUK.modules.start();
expect(window.ga).toHaveBeenCalled(); expect(window.GOVUK.analytics.trackEvent).toHaveBeenCalled();
expect(window.ga.mock.calls[0]).toEqual(['send', 'event', 'Error', 'validation', 'missing field']); expect(window.GOVUK.analytics.trackEvent.mock.calls[0]).toEqual(['Error', 'validation', {
'label': 'missing field'
}]);
}); });
+3
View File
@@ -1,6 +1,7 @@
const globals = require('./helpers/globals.js'); const globals = require('./helpers/globals.js');
const events = require('./helpers/events.js'); const events = require('./helpers/events.js');
const domInterfaces = require('./helpers/dom_interfaces.js'); const domInterfaces = require('./helpers/dom_interfaces.js');
const cookies = require('./helpers/cookies.js');
const html = require('./helpers/html.js'); const html = require('./helpers/html.js');
const elements = require('./helpers/elements.js'); const elements = require('./helpers/elements.js');
const rendering = require('./helpers/rendering.js'); const rendering = require('./helpers/rendering.js');
@@ -14,6 +15,8 @@ exports.moveSelectionToRadio = events.moveSelectionToRadio;
exports.activateRadioWithSpace = events.activateRadioWithSpace; exports.activateRadioWithSpace = events.activateRadioWithSpace;
exports.RangeMock = domInterfaces.RangeMock; exports.RangeMock = domInterfaces.RangeMock;
exports.SelectionMock = domInterfaces.SelectionMock; exports.SelectionMock = domInterfaces.SelectionMock;
exports.deleteCookie = cookies.deleteCookie;
exports.setCookie = cookies.setCookie;
exports.getRadioGroup = html.getRadioGroup; exports.getRadioGroup = html.getRadioGroup;
exports.getRadios = html.getRadios; exports.getRadios = html.getRadios;
exports.templatesAndFoldersCheckboxes = html.templatesAndFoldersCheckboxes; exports.templatesAndFoldersCheckboxes = html.templatesAndFoldersCheckboxes;
@@ -0,0 +1,22 @@
// Helper for deleting a cookie
function deleteCookie (cookieName) {
document.cookie = cookieName + '=; path=/; expires=' + (new Date());
};
function setCookie (name, value, options) {
if (typeof options === 'undefined') {
options = {};
}
var cookieString = name + '=' + value + '; path=/;domain=' + window.location.hostname;
if (options.days) {
var date = new Date();
date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000));
cookieString = cookieString + '; expires=' + date.toGMTString();
}
document.cookie = cookieString;
};
exports.deleteCookie = deleteCookie;
exports.setCookie = setCookie;
+53
View File
@@ -0,0 +1,53 @@
// Polyfills for any parts of the DOM API available in browsers but not JSDOM
// From: https://gist.github.com/eligrey/1276030
HTMLElement.prototype.insertAdjacentHTML = function(position, html) {
"use strict";
var
ref = this
, container = ref.ownerDocument.createElementNS("http://www.w3.org/1999/xhtml", "_")
, ref_parent = ref.parentNode
, node, first_child, next_sibling
;
container.innerHTML = html;
switch (position.toLowerCase()) {
case "beforebegin":
while ((node = container.firstChild)) {
ref_parent.insertBefore(node, ref);
}
break;
case "afterbegin":
first_child = ref.firstChild;
while ((node = container.lastChild)) {
first_child = ref.insertBefore(node, first_child);
}
break;
case "beforeend":
while ((node = container.firstChild)) {
ref.appendChild(node);
}
break;
case "afterend":
next_sibling = ref.nextSibling;
while ((node = container.lastChild)) {
next_sibling = ref_parent.insertBefore(node, next_sibling);
}
break;
}
};
// from: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentText#Polyfill
if (!Element.prototype.insertAdjacentText) {
Element.prototype.insertAdjacentText = function(type, txt){
this.insertAdjacentHTML(
type,
(txt+'') // convert to string
.replace(/&/g, '&amp;') // embed ampersand symbols
.replace(/</g, '&lt;') // embed less-than symbols
)
}
}
+3
View File
@@ -1,3 +1,6 @@
// Polyfill holes in JSDOM
require('./polyfills.js');
// set up jQuery // set up jQuery
window.jQuery = require('jquery'); window.jQuery = require('jquery');
$ = window.jQuery; $ = window.jQuery;