Skip to content

Commit

Permalink
Refactor cookies library and cookie banner (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahosgood authored Jan 28, 2025
1 parent a37d62e commit b522365
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 190 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
### Changed

- Cookie library settings can now be set at a document level in the `<html>` element rather than at a cookie banner level - in addition to `data-tna-cookies-domain` and `data-tna-cookies-path` parameters, there is now `tna-cookies-policies-key`, `tna-cookies-default-age` and `tna-cookies-insecure`

### Deprecated
### Removed

- Removed `policies`, `policiesKey`, `cookiesDomain`, `cookiesPath` and `allowInsecure` options from the cookie banner component

### Fixed
### Security

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"test:statichtml": "node tasks/generate-fixture-html.js && html-validate fixtures-html",
"test:lint": "prettier --check '{src,.storybook,tasks,.}/**/*.{js,mjs,scss,json,html}' && stylelint 'src/**/*.scss' && eslint 'src/**/*.{js,mjs}'",
"test:package": "node tasks/test-package.js",
"test:storybook": "test-storybook",
"test:storybook": "test-storybook --browsers=chromium",
"test:unit": "jest --verbose",
"update:fixtures": "node tasks/update-fixtures.js"
},
Expand Down
13 changes: 1 addition & 12 deletions src/nationalarchives/components/cookie-banner/cookie-banner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,7 @@ export class CookieBanner {
return;
}

const domain = this.$module.dataset.domain || undefined;
const path = this.$module.dataset.path || undefined;
const secure = this.$module.dataset.secure || undefined;
const policiesKey = this.$module.dataset.policiesKey || undefined;

this.cookies = new Cookies({
domain,
path,
secure,
policiesKey,
newInstance: true,
});
this.cookies = new Cookies({ newInstance: true });

this.cookiePreferencesSetKey =
this.$module.dataset.preferencesKey || "cookie_preferences_set";
Expand Down
202 changes: 134 additions & 68 deletions src/nationalarchives/components/cookie-banner/cookie-banner.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import Cookies from "../../lib/cookies.mjs";
const argTypes = {
serviceName: { control: "text" },
cookiesUrl: { control: "text" },
policies: { control: "text" },
policiesKey: { control: "text" },
preferencesSetKey: { control: "text" },
cookiesDomain: { control: "text" },
cookiesPath: { control: "text" },
allowInsecure: { control: "boolean" },
style: {
control: "inline-radio",
options: ["none", "contrast", "accent", "tint"],
Expand All @@ -34,11 +29,7 @@ export default {
const Template = ({
serviceName,
cookiesUrl,
policiesKey,
preferencesSetKey,
cookiesDomain,
cookiesPath,
allowInsecure,
style,
classes,
attributes,
Expand All @@ -47,11 +38,7 @@ const Template = ({
params: {
serviceName,
cookiesUrl,
policiesKey,
preferencesSetKey,
cookiesDomain,
cookiesPath,
allowInsecure,
style,
classes,
attributes,
Expand All @@ -70,12 +57,11 @@ export const Accept = Template.bind({});
Accept.args = {
serviceName: "My service",
cookiesUrl: "#",
allowInsecure: true,
classes: "tna-cookie-banner--demo",
disableMockAnalytics: true,
};
Accept.play = async ({ canvasElement }) => {
const cookies = new Cookies({ newInstance: true });
const cookies = new Cookies({ secure: false, noInit: true });
await expect(cookies.isPolicyAccepted("essential")).toEqual(true);
await expect(cookies.isPolicyAccepted("usage")).toEqual(false);
await expect(cookies.isPolicyAccepted("settings")).toEqual(false);
Expand Down Expand Up @@ -105,7 +91,19 @@ Accept.play = async ({ canvasElement }) => {

// await expect(closeButton).not.toBeVisible();

await cookies.deleteAll();
document.cookie.replace(/(?<=^|;).+?(?==|;|$)/g, (name) =>
location.hostname
.split(".")
.reverse()
.reduce(
(domain) => (
(domain = domain.replace(/^\.?[^.]+/, "")),
(document.cookie = `${name}=;max-age=0;path=/;domain=${domain}`),
domain
),
location.hostname,
),
);
};

export const Reject = Template.bind({});
Expand All @@ -116,7 +114,7 @@ Reject.args = {
disableMockAnalytics: true,
};
Reject.play = async ({ canvasElement }) => {
const cookies = new Cookies({ newInstance: true });
const cookies = new Cookies({ secure: false, noInit: true });
await expect(cookies.isPolicyAccepted("essential")).toEqual(true);
await expect(cookies.isPolicyAccepted("usage")).toEqual(false);
await expect(cookies.isPolicyAccepted("settings")).toEqual(false);
Expand All @@ -143,34 +141,43 @@ Reject.play = async ({ canvasElement }) => {
await expect(acceptButton).not.toBeVisible();
await expect(rejectButton).not.toBeVisible();

await cookies.deleteAll();
document.cookie.replace(/(?<=^|;).+?(?==|;|$)/g, (name) =>
location.hostname
.split(".")
.reverse()
.reduce(
(domain) => (
(domain = domain.replace(/^\.?[^.]+/, "")),
(document.cookie = `${name}=;max-age=0;path=/;domain=${domain}`),
domain
),
location.hostname,
),
);
};

export const Existing = Template.bind({});
Existing.args = {
serviceName: "My service",
cookiesUrl: "#",
allowInsecure: true,
classes: "tna-cookie-banner--demo",
disableMockAnalytics: true,
};
Existing.decorators = [
(Story) => {
const cookies = new Cookies({ newInstance: true });
cookies.set(
"cookies_policy",
JSON.stringify({
usage: true,
settings: true,
marketing: false,
essential: false,
}),
);
const cookies = new Cookies({ secure: false, noInit: true });
cookies.init();
cookies.acceptPolicy("settings");
cookies.acceptPolicy("usage");
return Story();
},
];
Existing.play = async ({ canvasElement }) => {
const cookies = new Cookies();
const cookies = new Cookies({
newInstance: true,
secure: false,
noInit: true,
});
await expect(cookies.isPolicyAccepted("essential")).toEqual(true);
await expect(cookies.isPolicyAccepted("usage")).toEqual(true);
await expect(cookies.isPolicyAccepted("settings")).toEqual(true);
Expand All @@ -184,20 +191,31 @@ Existing.play = async ({ canvasElement }) => {
await expect(acceptButton).not.toBeVisible();
await expect(rejectButton).not.toBeVisible();

await cookies.deleteAll();
document.cookie.replace(/(?<=^|;).+?(?==|;|$)/g, (name) =>
location.hostname
.split(".")
.reverse()
.reduce(
(domain) => (
(domain = domain.replace(/^\.?[^.]+/, "")),
(document.cookie = `${name}=;max-age=0;path=/;domain=${domain}`),
domain
),
location.hostname,
),
);
};

export const Partial = Template.bind({});
Partial.args = {
serviceName: "My service",
cookiesUrl: "#",
allowInsecure: true,
classes: "tna-cookie-banner--demo",
disableMockAnalytics: true,
};
Partial.decorators = [
(Story) => {
const cookies = new Cookies({ newInstance: true });
const cookies = new Cookies({ secure: false, noInit: true });
cookies.set(
"cookies_policy",
JSON.stringify({
Expand All @@ -209,7 +227,11 @@ Partial.decorators = [
},
];
Partial.play = async ({ canvasElement }) => {
const cookies = new Cookies();
const cookies = new Cookies({
newInstance: true,
secure: false,
noInit: true,
});
await expect(cookies.isPolicyAccepted("essential")).toEqual(true);
await expect(cookies.isPolicyAccepted("usage")).toEqual(true);
await expect(cookies.isPolicyAccepted("settings")).toEqual(false);
Expand All @@ -223,26 +245,42 @@ Partial.play = async ({ canvasElement }) => {
await expect(acceptButton).toBeVisible();
await expect(rejectButton).toBeVisible();

await cookies.deleteAll();
document.cookie.replace(/(?<=^|;).+?(?==|;|$)/g, (name) =>
location.hostname
.split(".")
.reverse()
.reduce(
(domain) => (
(domain = domain.replace(/^\.?[^.]+/, "")),
(document.cookie = `${name}=;max-age=0;path=/;domain=${domain}`),
domain
),
location.hostname,
),
);
};

export const Malformed = Template.bind({});
Malformed.args = {
serviceName: "My service",
cookiesUrl: "#",
allowInsecure: true,
classes: "tna-cookie-banner--demo",
disableMockAnalytics: true,
};
Malformed.decorators = [
(Story) => {
const cookies = new Cookies({ newInstance: true });
document.documentElement.setAttribute("data-tna-cookies-insecure", "true");
const cookies = new Cookies({ secure: false, noInit: true });
cookies.set("cookies_policy", "foobar");
return Story();
},
];
Malformed.play = async ({ canvasElement }) => {
const cookies = new Cookies();
const cookies = new Cookies({
newInstance: true,
secure: false,
noInit: true,
});
await expect(cookies.isPolicyAccepted("essential")).toEqual(true);
await expect(cookies.isPolicyAccepted("usage")).toEqual(false);
await expect(cookies.isPolicyAccepted("settings")).toEqual(false);
Expand All @@ -256,37 +294,65 @@ Malformed.play = async ({ canvasElement }) => {
await expect(acceptButton).toBeVisible();
await expect(rejectButton).toBeVisible();

await cookies.deleteAll();
document.cookie.replace(/(?<=^|;).+?(?==|;|$)/g, (name) =>
location.hostname
.split(".")
.reverse()
.reduce(
(domain) => (
(domain = domain.replace(/^\.?[^.]+/, "")),
(document.cookie = `${name}=;max-age=0;path=/;domain=${domain}`),
domain
),
location.hostname,
),
);
};

//Hidden w. no pref saved

// export const EventHandling = Template.bind({});
// EventHandling.args = {
// serviceName: "My service",
// cookiesUrl: "#",
// cookiesPath: "/tna-frontend/",
// policies: "custom",
// classes: "tna-cookie-banner--demo",
// };
// EventHandling.play = async ({ args, canvasElement }) => {
// deleteAllCookies();

// const cookies = new Cookies();

// const onChangePolicy = jest.fn(data => {
// console.log(data)
// })
// cookies.on("changePolicy", onChangePolicy)

// const canvas = within(canvasElement);
// const acceptButton = canvas.getByText("Accept cookies");
// await userEvent.click(acceptButton);
export const UnexpectedHidden = Template.bind({});
UnexpectedHidden.args = {
serviceName: "My service",
cookiesUrl: "#",
classes: "tna-cookie-banner--demo",
disableMockAnalytics: true,
};
UnexpectedHidden.decorators = [
(Story) => {
document.documentElement.setAttribute("data-tna-cookies-insecure", "true");
document.cookie = "cookie_preferences_set=true";
return Story();
},
];
UnexpectedHidden.play = async ({ canvasElement }) => {
const cookies = new Cookies({
newInstance: true,
secure: false,
noInit: true,
});
await expect(cookies.isPolicyAccepted("essential")).toEqual(true);
await expect(cookies.isPolicyAccepted("usage")).toEqual(false);
await expect(cookies.isPolicyAccepted("settings")).toEqual(false);
await expect(cookies.isPolicyAccepted("marketing")).toEqual(false);
await expect(cookies.exists("cookie_preferences_set")).toEqual(true);
await expect(cookies.get("cookie_preferences_set")).toEqual("false");

// await expect(onChangePolicy.mock).toHaveBeenCalledTimes(1);
// await expect(onChangePolicy.mock.calls).toHaveLength(1);
// await expect(onChangePolicy.mock.results[0].value).toHaveProperty("custom");
// await expect(onChangePolicy.mock.results[0].value.custom).toEqual(true);
const canvas = within(canvasElement);
const acceptButton = canvas.getByText("Accept cookies");
const rejectButton = canvas.getByText("Reject cookies");
await expect(acceptButton).toBeVisible();
await expect(rejectButton).toBeVisible();

// deleteAllCookies();
// };
document.cookie.replace(/(?<=^|;).+?(?==|;|$)/g, (name) =>
location.hostname
.split(".")
.reverse()
.reduce(
(domain) => (
(domain = domain.replace(/^\.?[^.]+/, "")),
(document.cookie = `${name}=;max-age=0;path=/;domain=${domain}`),
domain
),
location.hostname,
),
);
};
Loading

0 comments on commit b522365

Please sign in to comment.