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

Contextual consent #115

Draft
wants to merge 2 commits into
base: v3
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ purpose:
</template>
```

> [!NOTE] There is more you can do with templates! Learn about
> [contextual consent](#contextual-consent).

<details>
<summary>Integration tips</summary>

Expand Down Expand Up @@ -389,6 +392,55 @@ Romanian, Spanish, Swedish.
> [!NOTE] Each and every translated text is overridable via
> [the configuration](#configuration).

### Contextual consent

Content embedded from other websites might be restricted by user consent (i.e. a
YouTube video).

In that case, using templates would work just like with scripts:

```js
<template data-purpose="youtube">
<iframe src="https://www.youtube.com/embed/toto"></iframe>
</template>
```

However, this won't show anything until the user consents to the related
purpose.

To be a little more user friendly, adding the `data-contextual` attribute will
display a fallback notice until consent is given, detailing the reason and
offering a way to consent in place.

```diff
- <template data-purpose="youtube">
+ <template data-purpose="youtube" data-contextual>
<iframe src="https://www.youtube.com/embed/toto"></iframe>
</template>
```

<details>
<summary>Integration tips</summary>

#### WordPress

Should you use Orejime in a WordPress website, you could alter the rendering of
embeds so they use contextual consent:

```php
function orejimeWrapEmbeds($content, $block) {
if ($block['blockName'] === 'core/embed') {
return '<template data-purpose="embeds" data-contextual>' . $content . '</template>';
}

return $content;
}

add_filter('render_block', 'orejimeWrapEmbeds', 10, 2);
```

</details>

## API

Functions and references are made available on the global scope:
Expand Down
9 changes: 7 additions & 2 deletions e2e/OrejimePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class OrejimePage {
public readonly context: BrowserContext
) {}

async load(config: Partial<Config>, scripts: string) {
async load(config: Partial<Config>, body: string) {
await this.page.route('/', async (route) => {
await route.fulfill({
body: `
Expand All @@ -21,11 +21,12 @@ export class OrejimePage {
</head>

<body>
${body}

<script>
window.orejimeConfig = ${JSON.stringify(config)}
</script>
<script src="orejime-standard-en.js"></script>
${scripts}
</body>
</html>
`
Expand Down Expand Up @@ -102,6 +103,10 @@ export class OrejimePage {
await this.page.keyboard.press('Escape');
}

async acceptContextualNotice() {
await this.page.locator('.orejime-ContextualNotice-button').click();
}

async expectElement(selector: string) {
await expect(this.page.locator(selector)).toBeAttached();
}
Expand Down
116 changes: 65 additions & 51 deletions e2e/orejime.spec.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,49 @@
import {test, expect} from '@playwright/test';
import {Config} from '../src/ui/types';
import {OrejimePage} from './OrejimePage';

test.describe('Orejime', () => {
const BaseConfig: Partial<Config> = {
privacyPolicyUrl: 'https://example.org/privacy',
purposes: [
{
id: 'mandatory',
title: 'Mandatory',
cookies: ['mandatory'],
isMandatory: true
},
let orejimePage: OrejimePage;

test.beforeEach(async ({page, context}) => {
orejimePage = new OrejimePage(page, context);
await orejimePage.load(
{
id: 'group',
title: 'Group',
privacyPolicyUrl: 'https://example.org/privacy',
purposes: [
{
id: 'child-1',
title: 'First child',
cookies: ['child-1']
id: 'mandatory',
title: 'Mandatory',
cookies: ['mandatory'],
isMandatory: true
},
{
id: 'child-2',
title: 'Second child',
cookies: ['child-2']
id: 'group',
title: 'Group',
purposes: [
{
id: 'contextual',
title: 'Contextual',
cookies: ['contextual']
},
{
id: 'other',
title: 'Other',
cookies: ['other']
}
]
}
]
}
]
};

const BaseScripts = `
<template data-purpose="mandatory">
<script id="mandatory"></script>
</template>

<template data-purpose="child-1">
<iframe id="child-1"></iframe>
</template>
`;

let orejimePage: OrejimePage;

test.beforeEach(async ({page, context}) => {
orejimePage = new OrejimePage(page, context);
await orejimePage.load(BaseConfig, BaseScripts);
},
`
<template data-purpose="contextual" data-contextual>
<iframe id="contextual" src=""></iframe>
</template>

<template data-purpose="mandatory">
<script id="mandatory"></script>
</template>
`
);
});

test('should show a banner', async () => {
Expand All @@ -62,12 +60,12 @@ test.describe('Orejime', () => {

orejimePage.expectConsents({
'mandatory': true,
'child-1': true,
'child-2': true
'contextual': true,
'other': true
});

orejimePage.expectElement('#mandatory');
orejimePage.expectElement('#child-1');
orejimePage.expectElement('#contextual');
});

test('should decline all purposes from the banner', async () => {
Expand All @@ -76,12 +74,12 @@ test.describe('Orejime', () => {

orejimePage.expectConsents({
'mandatory': true,
'child-1': false,
'child-2': false
'contextual': false,
'other': false
});

orejimePage.expectElement('#mandatory');
orejimePage.expectMissingElement('#child-1');
orejimePage.expectMissingElement('#contextual');
});

test('should open a modal', async () => {
Expand Down Expand Up @@ -129,39 +127,39 @@ test.describe('Orejime', () => {
test('should accept all purposes from the modal', async () => {
await orejimePage.openModalFromBanner();
await orejimePage.enableAllFromModal();
await expect(orejimePage.purposeCheckbox('child-1')).toBeChecked();
await expect(orejimePage.purposeCheckbox('contextual')).toBeChecked();
await expect(orejimePage.purposeCheckbox('mandatory')).toBeChecked();
await orejimePage.saveFromModal();

orejimePage.expectConsents({
'mandatory': true,
'child-1': true,
'child-2': true
'contextual': true,
'other': true
});
});

test('should decline all purposes from the modal', async () => {
await orejimePage.openModalFromBanner();
await orejimePage.enableAllFromModal();
await orejimePage.disableAllFromModal();
await expect(orejimePage.purposeCheckbox('child-1')).not.toBeChecked();
await expect(orejimePage.purposeCheckbox('contextual')).not.toBeChecked();
await expect(orejimePage.purposeCheckbox('mandatory')).toBeChecked();
await orejimePage.saveFromModal();

orejimePage.expectConsents({
'mandatory': true,
'child-1': false,
'child-2': false
'contextual': false,
'other': false
});
});

test('should sync grouped purposes', async () => {
await orejimePage.openModalFromBanner();

const checkbox = orejimePage.purposeCheckbox('child-1');
const checkbox = orejimePage.purposeCheckbox('contextual');
await expect(checkbox).not.toBeChecked();

const checkbox2 = orejimePage.purposeCheckbox('child-2');
const checkbox2 = orejimePage.purposeCheckbox('other');
await expect(checkbox2).not.toBeChecked();

const groupCheckbox = orejimePage.purposeCheckbox('group');
Expand All @@ -181,4 +179,20 @@ test.describe('Orejime', () => {
await expect(checkbox).not.toBeChecked();
await expect(checkbox2).not.toBeChecked();
});

test('should show a contextual consent notice', async () => {
await orejimePage.expectElement('.orejime-ContextualNotice');
});

test('should accept contextual consent from the notice', async () => {
await orejimePage.acceptContextualNotice();
await orejimePage.expectElement('#contextual');
await orejimePage.expectMissingElement('.orejime-ContextualNotice');
});

test('should accept contextual consent from the banner', async () => {
await orejimePage.acceptAllFromBanner();
await orejimePage.expectElement('#contextual');
await orejimePage.expectMissingElement('.orejime-ContextualNotice');
});
});
5 changes: 5 additions & 0 deletions rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ module.exports = {
lang: 'fr'
}),
featureTemplatePlugin({title: 'Styling', feature: 'styling'}),
featureTemplatePlugin({
title: 'Contextual consent',
feature: 'contextual',
template: 'contextual'
}),
featureTemplatePlugin({
title: "Intégration au système de design de l'état",
feature: 'dsfr',
Expand Down
5 changes: 5 additions & 0 deletions site/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ iframe {
color: var(--color-black--100);
}

.ExamplePage iframe {
aspect-ratio: 16 / 9;
min-height: 0;
}

.ExamplePage .orejime-Env {
font-size: 0.875rem;
}
Expand Down
61 changes: 61 additions & 0 deletions site/features/contextual.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!doctype html>

<html class="ExamplePage" lang="en">
<head>
<title>Orejime</title>

<link
rel="stylesheet"
href="https://boscop.fr/wp-content/themes/boscop/dist/app.css"
/>

<link rel="stylesheet" href="../assets/style.css" />
<link rel="stylesheet" href="../orejime-standard.css" />
</head>

<body>
<main class="ExampleMain" role="main">
<template data-purpose="youtube" data-contextual>
<figure role="group">
<iframe
title="YouTube video player"
src="https://www.youtube-nocookie.com/embed/pghz5vpi5q4?si=npJInLIEM7XiD0MB"
allowfullscreen
></iframe>

<figcaption class="fr-content-media__caption">
Cooking cookies with Philippe Etchebest
</figcaption>
</figure>
</template>

<button class="ExampleReset Button">Reset consent</button>
</main>

<script>
window.orejimeConfig = {
purposes: [
{
id: 'youtube',
title: 'YouTube videos'
},
{
id: 'other',
title: 'Another purpose'
}
],
privacyPolicyUrl: '#'
};
</script>

<script src="../orejime-standard-en.js"></script>

<script>
document
.querySelector('.ExampleReset')
.addEventListener('click', function () {
window.orejime.manager.clearConsents();
});
</script>
</body>
</html>
17 changes: 17 additions & 0 deletions site/features/dsfr.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,23 @@ <h2>Politique de confidentialité</h2>
</li>
</ul>

<h2>Media</h2>

<template data-purpose="youtube" data-contextual>
<figure role="group" class="fr-content-media">
<iframe
class="fr-responsive-vid"
title="YouTube video player"
src="https://www.youtube-nocookie.com/embed/pghz5vpi5q4?si=npJInLIEM7XiD0MB"
allowfullscreen
></iframe>

<figcaption class="fr-content-media__caption">
Les cookies de Philippe Etchebest
</figcaption>
</figure>
</template>

<h2>Configuration</h2>

<%= js.highlightedCode %>
Expand Down
Loading
Loading