Merge remote-tracking branch 'consent-banner/master' into consent-control-lib

Merge with consent-banner in Azure and solve the conflicts
This commit is contained in:
Bill Chou (Zen3 Infosolutions America Inc) 2020-07-22 15:22:31 -07:00
Родитель 4930adad86 db49f8d0ca
Коммит 6899e3e4e7
26 изменённых файлов: 16176 добавлений и 14 удалений

15
.gitignore поставляемый
Просмотреть файл

@ -44,6 +44,12 @@ jspm_packages/
# TypeScript v1 declaration files
typings/
# Host html file
!dist/index.html
# TypeScript v2 declaration files
!dist/consent-banner.d.ts
# TypeScript cache
*.tsbuildinfo
@ -102,3 +108,12 @@ dist
# TernJS port file
.tern-port
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

221
README.md
Просмотреть файл

@ -1,5 +1,224 @@
# Consent Banner
# Contributing
## Overview
Consent banner is the library which will generate a banner at the specified position for asking the cookie preferences.
It contains two buttons, `accept all` and `more info`. If the user clicks `more info` button, A dialog will pop up so that the user can set the cookie categories that he/she wants to share with us.
## Building and running on localhost
First install dependencies:
```sh
npm i
```
To create a production build:
```sh
npm run build-prod
```
To create a development build:
```sh
npm run build
```
## Running
```sh
npm run start
```
## Testing
```sh
npm run test
```
## Example use
This is part of your `html` page.
```HTML
<body>
...
<div id="app"></div>
...
</body>
```
If you want to insert a banner into the `<div id="app"></div>`, you can use the following example.
```TypeScript
let cookieCategories: ICookieCategory[] =
[
{
id: "c0",
name: "1. Essential cookies",
descHtml: "We use this cookie, read more <a href='link'>here</a>.",
isUnswitchable: true
},
{
id: "c1",
name: "2. Performance & analytics",
descHtml: "We use this cookie, read more <a href='link'>here</a>."
},
{
id: "c2",
name: "3. Advertising/Marketing",
descHtml: "Blah"
},
{
id: "c3",
name: "4. Targeting/personalization",
descHtml: "Blah"
}
];
let textResources: ITextResources = {
bannerMessageHtml = "We use optional cookies to provide... read <a href='link'>here</a>.",
acceptAllLabel = "Accept all",
moreInfoLabel = "More info",
preferencesDialogCloseLabel = "Close",
preferencesDialogTitle = "Manage cookie preferences",
preferencesDialogDescHtml = "Most Microsoft sites...",
acceptLabel = "Accept",
rejectLabel = "Reject",
saveLabel = "Save changes",
resetLabel = "Reset all"
};
let callBack = function(obj: any) { console.log(obj); };
let cc = new ConsentControl("app", "en", callBack, cookieCategories, textResources);
let cookieCategoriePreferences: ICookieCategoriesPreferences = { "c1": undefined, "c2": false, "c3": true };
cc.showBanner(cookieCategoriePreferences);
```
## Developer Guide
`ConsentControl` consists of 2 main elements: **Banner** and **Preferences Dialog**. Use `containerElementOrId`, `culture`, `onPreferencesChanged`, `cookieCategories`, `textResources` to create an instance. `culture` will be used to determine the direction of the banner and preferences dialog.
```JavaScript
var cc = new ConsentControl(
containerElementOrId: string | HTMLElement, // here the banner will be inserted, can be HTMLElement or element id
culture: string, // culture can be just language "en" or language-country "en-us". Based on language RTL should be applied (https://www.w3.org/International/questions/qa-scripts.en)
onPreferencesChanged: (cookieCategoriesPreferences: ICookieCategoriesPreferences) => void, // callback function, called on preferences changes (via "Accept All" or "Save changes"), must pass cookieCategoriePreferences, see ICookieCategoriesPreferences in Data types
cookieCategories?: ICookieCategory[], // optional, see ICookieCategory in Data types
textResources?: ITextResources // optional, see ITextResources in Data types
);
```
1. `setTextResources(textResources: ITextResources)` can be used to set the texts.
2. `setContainerElement(containerElementOrId: string | HTMLElement)` can be used to set the container element for the banner and preferences dialog.
If you pass `HTMLElement`, it will be used as the container. If you pass `string`, the method will use it as the `id` to find the container element.
```JavaScript
cc.setContainerElement("app");
```
or
```JavaScript
let container = document.getElementById('app');
cc.setContainerElement(container);
```
If the container can not be found, it will throw an error. `new Error("Container not found error")`
3. `getContainerElement()` will return the current container element.
4. You can set the direction manually by using `setDirection(dir?: string)`. `dir` can be `"ltr"` or `"rtl"`. If `dir` is not passed, it will determine the direction by `dir` attribute in `html`, `body` or `culture`.
```JavaScript
// There are 3 cases. dir="rtl", dir="ltr", empty
// Set direction to rtl (right-to-left)
cc.setDirection("rtl");
// Set direction to ltr (left-to-right)
cc.setDirection("ltr");
// It will use "dir" attribute in "html" or "body"
// If the "dir" attribute is not specified, it will apply the direction based on "culture"
cc.setDirection();
```
5. `getDirection()` will return the current direction.
### Data types
There are three data types: `ICookieCategory`, `ITextResources` and `ICookieCategoriesPreferences`.
+ `ICookieCategory` is used to create cookie categories that will be showed in the preferences dialog.
+ `ITextResources` is the texts that will be used in the banner and preferences dialog.
+ `ICookieCategoriesPreferences` is used to store the preferences in each cookie categories.
```TypeScript
// All categories should be replaced with the passed ones in the control
interface ICookieCategory {
id: string;
name: string;
descHtml: string;
isUnswitchable?: boolean; // optional, prevents toggling the category. True only for categories like Essential cookies.
}
// only the passed text resources should be replaced in the control. If any string is not passed the control should keep the default value
interface ITextResources {
bannerMessageHtml?: string;
acceptAllLabel?: string;
moreInfoLabel?: string;
preferencesDialogCloseLabel?: string;
preferencesDialogTitle?: string;
preferencesDialogDescHtml?: string;
acceptLabel?: string;
rejectLabel?: string;
saveLabel?: string;
resetLabel?: string;
}
interface ICookieCategoriesPreferences {
[key: string]: boolean | undefined;
}
```
### Provided methods
+ Methods related to **banner**: `showBanner(cookieCategoriesPreferences: ICookieCategoriesPreferences)` and `hideBanner()`
+ Methods related to **Preferences Dialog**: `showPreferences(cookieCategoriesPreferences: ICookieCategoriesPreferences)` and `hidePreferences()`
```JavaScript
// Insert all necessary HTML code and shows the banner. Until this method is called there should be no HTML elements of the Consent Control anywhere in the DOM. Only one banner will be created. If call it many times, it will only display the last one and remove all previous banners.
cc.showBanner(
cookieCategoriesPreferences: ICookieCategoriesPreferences // see ICookieCategoriesPreferences in Data types
);
// Hides the banner and the Preferences Dialog. Removes all HTML elements of the Consent Control from the DOM
cc.hideBanner();
// Shows Preferences Dialog. Leaves banner state unchanged. If there is a preferences dialog, it will show the dialog instead of creating a new one.
cc.showPreferences(
cookieCategoriesPreferences: ICookieCategoriesPreferences // see ICookieCategoriesPreferences in Data types
);
// Hides Preferences Dialog. Removes all HTML elements of the Preferences Dialog from the DOM. Leaves banner state unchanged
cc.hidePreferences();
```
### Custom settings
1. Change the width of buttons in banner: Change `$bannerBtnWidth` in `styles.scss`
2. `webpack.config.js` file is for development purpose, and `webpack.production.config.js` is for production. The only difference between `webpack.config.js` and `webpack.production.config.js` is `localIdentName` in `use/options/modules` under `module/rules`.
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us

23
azure-pipelines.yml Normal file
Просмотреть файл

@ -0,0 +1,23 @@
# Node.js
# Build a general Node.js project with npm.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger:
- master
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- script: |
npm install
npm run build-prod
npm run lint
npm run test-ci
displayName: 'npm install and build'

101
dist/consent-banner.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,101 @@
declare interface ICookieCategory {
id: string;
name: string;
descHtml: string;
isUnswitchable?: boolean;
}
declare interface ITextResources {
bannerMessageHtml?: string;
acceptAllLabel?: string;
moreInfoLabel?: string;
preferencesDialogCloseLabel?: string;
preferencesDialogTitle?: string;
preferencesDialogDescHtml?: string;
acceptLabel?: string;
rejectLabel?: string;
saveLabel?: string;
resetLabel?: string;
}
declare interface ICookieCategoriesPreferences {
[key: string]: boolean | undefined;
}
declare class PreferencesControl { }
export declare class ConsentControl {
private containerElement;
culture: string;
onPreferencesChanged: (cookieCategoriesPreferences: ICookieCategoriesPreferences) => void;
cookieCategories: ICookieCategory[];
textResources: ITextResources;
preferencesCtrl: PreferencesControl | null;
private direction;
defaultCookieCategories: ICookieCategory[];
defaultTextResources: ITextResources;
constructor(containerElementOrId: string | HTMLElement, culture: string, onPreferencesChanged: (cookieCategoriesPreferences: ICookieCategoriesPreferences) => void, cookieCategories?: ICookieCategory[], textResources?: ITextResources);
/**
* Set the text resources for the banner to display the text in each area
*
* @param {ITextResources} textResources the text want to be displayed
*/
setTextResources(textResources: ITextResources): void;
/**
* Insert all necessary HTML code and shows the banner.
* Until this method is called there should be no HTML elements of the Consent Control anywhere in the DOM
*
* @param {ICookieCategoriesPreferences} cookieCategoriesPreferences object that indicates cookie categories preferences
*/
showBanner(cookieCategoriesPreferences: ICookieCategoriesPreferences): void;
/**
* Hides the banner and the Preferences Dialog.
* Removes all HTML elements of the Consent Control from the DOM
*/
hideBanner(): void;
/**
* Shows Preferences Dialog. Leaves banner state unchanged
*
* @param {ICookieCategoriesPreferences} cookieCategoriesPreferences object that indicates cookie categories preferences
*/
showPreferences(cookieCategoriesPreferences: ICookieCategoriesPreferences): void;
/**
* Hides Preferences Dialog.
* Removes all HTML elements of the Preferences Dialog from the DOM. Leaves banner state unchanged
*/
hidePreferences(): void;
/**
* The method is used to initialize the preferences dialog.
*
* @param {ICookieCategoriesPreferences} cookieCategoriesPreferences object that indicates cookie categories preferences
*/
private initPreferencesCtrl;
/**
* Function that will be called when "Accept all" button is clicked
*
* @param {ICookieCategoriesPreferences} cookieCategoriesPreferences object that indicates cookie categories preferences
*/
private onAcceptAllClicked;
/**
* Function that is used to set preferencesCtrl property to null
*/
private onPreferencesClosed;
/**
* Set the container that will be used for the banner
*
* @param {string | HTMLElement} containerElementOrId here the banner will be inserted
*/
setContainerElement(containerElementOrId: string | HTMLElement): void;
/**
* Return the container that is used for the banner
*/
getContainerElement(): HTMLElement | null;
/**
* Set the direction by passing the parameter or by checking the culture property
*
* @param {string} dir direction for the web, ltr or rtl
*/
setDirection(dir?: string): void;
/**
* Return the direction
*/
getDirection(): string;
}

15
dist/index.html поставляемый Normal file
Просмотреть файл

@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<title>Test page</title>
<meta charset="utf-8">
</head>
<body>
<div id="app"></div>
<h1>aaabcdef</h1>
<script src="consent-banner.js"></script>
</body>
</html>

31
jest.config.js Normal file
Просмотреть файл

@ -0,0 +1,31 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
module.exports = {
clearMocks: true,
collectCoverageFrom: [
"src/**/*.{js,jsx,ts,tsx}",
"!*.d.ts"
],
coverageReporters: [
"html"
],
moduleNameMapper: {
"^.+\\.(css|scss)$": "identity-obj-proxy"
},
resolver: "jest-pnp-resolver",
testMatch: [
"<rootDir>/src/**/*.test.ts?(x)"
],
testEnvironment: "jsdom",
testURL: "http://localhost",
transform: {
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest",
},
transformIgnorePatterns: [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$"
],
testPathIgnorePatterns: [
],
//testResultsProcessor: "<rootDir>/node_modules/jest-junit-reporter",
};

12273
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

58
package.json Normal file
Просмотреть файл

@ -0,0 +1,58 @@
{
"name": "consent-banner",
"version": "2.0.0",
"description": "The library which will generate a banner at the specified position for asking the cookie preferences.",
"main": "index.js",
"types": "dist/consent-banner.d.ts",
"keywords": [
"cookie preferences",
"banner"
],
"author": "Microsoft",
"license": "MIT",
"files": [ "dist/consent-banner.js", "dist/consent-banner.d.ts" ],
"scripts": {
"prepare": "npm run build-prod",
"build": "webpack -d --mode development",
"build-prod": "webpack -p --mode production --config ./webpack.production.config.js",
"start": "webpack-dev-server --open",
"test": "jest --watchAll",
"test-coverage": "jest --coverage",
"test-ci": "jest",
"lint": "tslint -p tsconfig.json -t stylish"
},
"dependencies": {},
"devDependencies": {
"@types/jest": "^25.2.1",
"@types/node-sass": "^4.11.0",
"awesome-typescript-loader": "^5.2.1",
"css-loader": "^3.5.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^25.3.0",
"jest-junit-reporter": "^1.1.0",
"jest-pnp-resolver": "^1.2.1",
"node-sass": "^4.13.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.4",
"ts-jest": "^25.3.1",
"tslint": "^6.1.1",
"tslint-microsoft-contrib": "^6.2.0",
"typescript": "^3.8.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
},
"jest": {
"moduleNameMapper": {
"\\.(s?css|less)$": "identity-obj-proxy"
}
},
"browserslist": [
">0.2%",
"not ie <= 9",
"not dead",
"not op_mini all"
]
}

5
postcss.config.js Normal file
Просмотреть файл

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

1
src/declarations.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
declare module "*.scss";

14
src/htmlTools.ts Normal file
Просмотреть файл

@ -0,0 +1,14 @@
export class HtmlTools {
public static escapeHtml(s: string | undefined): string {
if (s) {
return s.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
else {
return "";
}
}
}

316
src/index-button.test.ts Normal file
Просмотреть файл

@ -0,0 +1,316 @@
import { ConsentControl } from "./index";
import * as styles from "./styles.scss";
describe("Test radio buttons and 'Reset all' button", () => {
let testId: string = "app";
function testRadioBtnState(radioButtons: String[]): void {
let cookieItemRadioBtn: HTMLInputElement[] = [].slice.call(document.getElementsByClassName(styles.cookieItemRadioBtn));
for (let i = 0; i < radioButtons.length; i++) {
if (radioButtons[i] === "checked") {
expect(cookieItemRadioBtn[i].checked).toBeTruthy();
}
else {
expect(cookieItemRadioBtn[i].checked).toBeFalsy();
}
}
}
beforeEach(() => {
let newDiv = document.createElement("div");
newDiv.setAttribute("id", testId);
document.body.appendChild(newDiv);
});
afterEach(() => {
let child = document.getElementById(testId);
if (child) {
let parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
else {
throw new Error("Parent not found error");
}
}
});
test("Click 'More info' button and then click radio buttons. All cookieCategoriePreferences will be reset to undefined when 'Reset all' is clicked", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": undefined, "c3": false };
cc.showBanner(cookieCategoriePreferences);
let cookieInfo = <HTMLElement> document.getElementsByClassName(styles.bannerButton)[1];
cookieInfo.click();
expect(cc.preferencesCtrl).toBeTruthy();
let cookieModal: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.cookieModal)[0];
expect(cookieModal.style.display).toBe("block");
let cookieItemRadioBtn: HTMLInputElement[] = [].slice.call(document.getElementsByClassName(styles.cookieItemRadioBtn));
cookieItemRadioBtn[1].click();
cookieItemRadioBtn[2].click();
cookieItemRadioBtn[4].click();
testRadioBtnState(["unchecked", "checked", "checked", "unchecked", "checked", "unchecked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c1": false, "c2": true, "c3": true });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
let resetAllBtn = <HTMLElement> document.getElementsByClassName(styles.modalButtonReset)[0];
resetAllBtn.click();
testRadioBtnState(["unchecked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c1": undefined, "c2": undefined, "c3": undefined });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
});
test("Call showPreferences(...) and then click radio buttons. All cookieCategoriePreferences will be reset to undefined when 'Reset all' is clicked", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": false, "c3": undefined };
cc.showPreferences(cookieCategoriePreferences);
expect(cc.preferencesCtrl).toBeTruthy();
let cookieModal: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.cookieModal)[0];
expect(cookieModal.style.display).toBe("block");
let cookieItemRadioBtn: HTMLInputElement[] = [].slice.call(document.getElementsByClassName(styles.cookieItemRadioBtn));
cookieItemRadioBtn[1].click();
cookieItemRadioBtn[2].click();
cookieItemRadioBtn[4].click();
testRadioBtnState(["unchecked", "checked", "checked", "unchecked", "checked", "unchecked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c1": false, "c2": true, "c3": true });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
let resetAllBtn = <HTMLElement> document.getElementsByClassName(styles.modalButtonReset)[0];
resetAllBtn.click();
testRadioBtnState(["unchecked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c1": undefined, "c2": undefined, "c3": undefined });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
});
test("Call showPreferences(...) with unswitchable id and click radio buttons. All cookiePreferences will be reset to undefined when 'Reset all' is clicked", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c0": true, "c1": true, "c2": false, "c3": undefined };
cc.showPreferences(cookieCategoriePreferences);
expect(cc.preferencesCtrl).toBeTruthy();
let cookieModal: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.cookieModal)[0];
expect(cookieModal.style.display).toBe("block");
let cookieItemRadioBtn: HTMLInputElement[] = [].slice.call(document.getElementsByClassName(styles.cookieItemRadioBtn));
cookieItemRadioBtn[1].click();
cookieItemRadioBtn[2].click();
cookieItemRadioBtn[4].click();
testRadioBtnState(["unchecked", "checked", "checked", "unchecked", "checked", "unchecked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c0": true, "c1": false, "c2": true, "c3": true });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
let resetAllBtn = <HTMLElement> document.getElementsByClassName(styles.modalButtonReset)[0];
resetAllBtn.click();
testRadioBtnState(["unchecked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c0": true, "c1": undefined, "c2": undefined, "c3": undefined });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
});
test("Click 'More info' button and then click radio buttons. cookieCategoriePreferences will be set", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": undefined, "c3": false };
cc.showBanner(cookieCategoriePreferences);
let cookieInfo = <HTMLElement> document.getElementsByClassName(styles.bannerButton)[1];
cookieInfo.click();
expect(cc.preferencesCtrl).toBeTruthy();
let cookieModal: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.cookieModal)[0];
expect(cookieModal.style.display).toBe("block");
let cookieItemRadioBtn: HTMLInputElement[] = [].slice.call(document.getElementsByClassName(styles.cookieItemRadioBtn));
cookieItemRadioBtn[0].click();
cookieItemRadioBtn[3].click();
cookieItemRadioBtn[5].click();
testRadioBtnState(["checked", "unchecked", "unchecked", "checked", "unchecked", "checked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c1": true, "c2": false, "c3": false });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
cookieItemRadioBtn[1].click();
cookieItemRadioBtn[2].click();
cookieItemRadioBtn[4].click();
testRadioBtnState(["unchecked", "checked", "checked", "unchecked", "checked", "unchecked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c1": false, "c2": true, "c3": true });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
});
test("Call showPreferences(...) and then click radio buttons. cookieCategoriePreferences will be set", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": false, "c3": undefined };
cc.showPreferences(cookieCategoriePreferences);
expect(cc.preferencesCtrl).toBeTruthy();
let cookieModal: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.cookieModal)[0];
expect(cookieModal.style.display).toBe("block");
let cookieItemRadioBtn: HTMLInputElement[] = [].slice.call(document.getElementsByClassName(styles.cookieItemRadioBtn));
cookieItemRadioBtn[0].click();
cookieItemRadioBtn[3].click();
cookieItemRadioBtn[5].click();
testRadioBtnState(["checked", "unchecked", "unchecked", "checked", "unchecked", "checked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c1": true, "c2": false, "c3": false });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
cookieItemRadioBtn[1].click();
cookieItemRadioBtn[2].click();
cookieItemRadioBtn[4].click();
testRadioBtnState(["unchecked", "checked", "checked", "unchecked", "checked", "unchecked"]);
if (cc.preferencesCtrl) {
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual({ "c1": false, "c2": true, "c3": true });
expect(cc.preferencesCtrl.cookieCategoriesPreferences).toEqual(cookieCategoriePreferences);
}
else {
throw new Error("Preference dialog not found error");
}
});
});
describe("Test 'Accept all' button", () => {
let testId: string = "app";
beforeEach(() => {
let newDiv = document.createElement("div");
newDiv.setAttribute("id", testId);
document.body.appendChild(newDiv);
});
afterEach(() => {
let child = document.getElementById(testId);
if (child) {
let parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
else {
throw new Error("Parent not found error");
}
}
});
test("Click 'Accept all' button and all cookieCategoriePreferences will be set to 'true'", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c2": undefined, "c3": false };
cc.showBanner(cookieCategoriePreferences);
let acceptAllBtn = <HTMLElement> document.getElementsByClassName(styles.bannerButton)[0];
acceptAllBtn.click();
for (let cookieCategory of cc.cookieCategories) {
if (!cookieCategory.isUnswitchable) {
if (cc.preferencesCtrl) {
let id = cookieCategory.id;
expect(cc.preferencesCtrl.cookieCategoriesPreferences[id]).toBeTruthy();
}
else {
throw new Error("Preference dialog not found error");
}
}
}
});
test("Initialize cookieCategoriesPreferences with unswitchable id and click 'Accept all' button. All cookieCategoriePreferences will be set to 'true'", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c0": true, "c2": undefined, "c3": false };
cc.showBanner(cookieCategoriePreferences);
let acceptAllBtn = <HTMLElement> document.getElementsByClassName(styles.bannerButton)[0];
acceptAllBtn.click();
for (let cookieCategory of cc.cookieCategories) {
if (!cookieCategory.isUnswitchable) {
if (cc.preferencesCtrl) {
let id = cookieCategory.id;
expect(cc.preferencesCtrl.cookieCategoriesPreferences[id]).toBeTruthy();
}
else {
throw new Error("Preference dialog not found error");
}
}
}
});
});

Просмотреть файл

@ -0,0 +1,587 @@
import { ConsentControl } from "./index";
describe("Test constructor", () => {
let testId: string = "app";
beforeEach(() => {
let newDiv = document.createElement("div");
newDiv.setAttribute("id", testId);
document.body.appendChild(newDiv);
});
afterEach(() => {
let child = document.getElementById(testId);
if (child) {
let parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
else {
throw new Error("Parent not found error");
}
}
});
test("CookieCategories and textResources full provided", () => {
let cookieCategories = [
{
id: "cookie1",
name: "Test cookie1",
descHtml: "This is for test cookie1"
},
{
id: "cookie2",
name: "Test cookie2",
descHtml: "This is for test cookie2 with 4th property",
isUnswitchable: true
}
];
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, cookieCategories, textResources);
expect(cc.culture).toBe("en");
expect(cc.cookieCategories).toEqual(cookieCategories);
expect(cc.textResources).toEqual(textResources);
});
test("No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
expect(cc.culture).toBe("en");
expect(cc.cookieCategories).toEqual([
{
id: "c0",
name: "1. Essential cookies",
descHtml: "We use this cookie, read more <a href='link'>here</a>.",
isUnswitchable: true
},
{
id: "c1",
name: "2. Performance & analytics",
descHtml: "We use this cookie, read more <a href='link'>here</a>."
},
{
id: "c2",
name: "3. Advertising/Marketing",
descHtml: "Blah"
},
{
id: "c3",
name: "4. Targeting/personalization",
descHtml: "Blah"
}
]);
expect(cc.textResources).toEqual({
bannerMessageHtml: "We use optional cookies to provide... read <a href='link'>here</a>.",
acceptAllLabel: "Accept all",
moreInfoLabel: "More info",
preferencesDialogCloseLabel: "Close",
preferencesDialogTitle: "Manage cookie preferences",
preferencesDialogDescHtml: "Most Microsoft sites...",
acceptLabel: "Accept",
rejectLabel: "Reject",
saveLabel: "Save changes",
resetLabel: "Reset all"
});
});
test("No cookieCategories, textResources full provided", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.culture).toBe("en");
expect(cc.cookieCategories).toEqual([
{
id: "c0",
name: "1. Essential cookies",
descHtml: "We use this cookie, read more <a href='link'>here</a>.",
isUnswitchable: true
},
{
id: "c1",
name: "2. Performance & analytics",
descHtml: "We use this cookie, read more <a href='link'>here</a>."
},
{
id: "c2",
name: "3. Advertising/Marketing",
descHtml: "Blah"
},
{
id: "c3",
name: "4. Targeting/personalization",
descHtml: "Blah"
}
]);
expect(cc.textResources).toEqual(textResources);
});
test("CookieCategories provided, no textResources", () => {
let cookieCategories = [
{
id: "cookie1",
name: "Test cookie1",
descHtml: "This is for test cookie1"
},
{
id: "cookie2",
name: "Test cookie2",
descHtml: "This is for test cookie2 with 4th property",
isUnswitchable: true
}
];
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, cookieCategories);
expect(cc.culture).toBe("en");
expect(cc.cookieCategories).toEqual(cookieCategories);
expect(cc.textResources).toEqual({
bannerMessageHtml: "We use optional cookies to provide... read <a href='link'>here</a>.",
acceptAllLabel: "Accept all",
moreInfoLabel: "More info",
preferencesDialogCloseLabel: "Close",
preferencesDialogTitle: "Manage cookie preferences",
preferencesDialogDescHtml: "Most Microsoft sites...",
acceptLabel: "Accept",
rejectLabel: "Reject",
saveLabel: "Save changes",
resetLabel: "Reset all"
});
});
test("No cookieCategories, textResources without bannerMessageHtml", () => {
let textResources = {
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.culture).toBe("en");
expect(cc.cookieCategories).toEqual([
{
id: "c0",
name: "1. Essential cookies",
descHtml: "We use this cookie, read more <a href='link'>here</a>.",
isUnswitchable: true
},
{
id: "c1",
name: "2. Performance & analytics",
descHtml: "We use this cookie, read more <a href='link'>here</a>."
},
{
id: "c2",
name: "3. Advertising/Marketing",
descHtml: "Blah"
},
{
id: "c3",
name: "4. Targeting/personalization",
descHtml: "Blah"
}
]);
expect(cc.textResources).toEqual({
bannerMessageHtml: "We use optional cookies to provide... read <a href='link'>here</a>.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without acceptAllLabel", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "Accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without moreInfoLabel", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "More info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without preferencesDialogCloseLabel", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without preferencesDialogTitle", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "Manage cookie preferences",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without preferencesDialogDescHtml", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "Most Microsoft sites...",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without acceptLabel", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "Accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without rejectLabel", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "Reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without saveLabel", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "Save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without resetLabel", () => {
let textResources = {
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "This is banner message.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "Reset all"
});
});
test("No cookieCategories, textResources without bannerMessageHtml, acceptAllLabel", () => {
let textResources = {
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.culture).toBe("en");
expect(cc.cookieCategories).toEqual([
{
id: "c0",
name: "1. Essential cookies",
descHtml: "We use this cookie, read more <a href='link'>here</a>.",
isUnswitchable: true
},
{
id: "c1",
name: "2. Performance & analytics",
descHtml: "We use this cookie, read more <a href='link'>here</a>."
},
{
id: "c2",
name: "3. Advertising/Marketing",
descHtml: "Blah"
},
{
id: "c3",
name: "4. Targeting/personalization",
descHtml: "Blah"
}
]);
expect(cc.textResources).toEqual({
bannerMessageHtml: "We use optional cookies to provide... read <a href='link'>here</a>.",
acceptAllLabel: "Accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "This is reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
test("No cookieCategories, textResources without bannerMessageHtml, rejectLabel", () => {
let textResources = {
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
};
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack, undefined, textResources);
expect(cc.textResources).toEqual({
bannerMessageHtml: "We use optional cookies to provide... read <a href='link'>here</a>.",
acceptAllLabel: "This is accept all",
moreInfoLabel: "This is more info",
preferencesDialogCloseLabel: "This is Close",
preferencesDialogTitle: "This is preferences dialog title",
preferencesDialogDescHtml: "This is preferences dialog text",
acceptLabel: "This is accept",
rejectLabel: "Reject",
saveLabel: "This is save changes",
resetLabel: "This is reset all"
});
});
});

200
src/index-direction.test.ts Normal file
Просмотреть файл

@ -0,0 +1,200 @@
import { ConsentControl } from "./index";
describe("Test language direction", () => {
let testId: string = "app";
beforeEach(() => {
let newDiv = document.createElement("div");
newDiv.setAttribute("id", testId);
document.body.appendChild(newDiv);
});
afterEach(() => {
let child = document.getElementById(testId);
if (child) {
let parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
else {
throw new Error("Parent not found error");
}
}
});
test("Language is ms (ltr). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ms", callBack);
expect(cc.getDirection()).toBe("ltr");
});
test("Language is ms (ltr). Set direction to rtl. No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ms", callBack);
cc.setDirection("rtl");
expect(cc.getDirection()).toBe("rtl");
});
test("Language is ar (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ar", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is he (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "he", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is ps (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ps", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is ur (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ur", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is fa (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "fa", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is pa (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "pa", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is sd (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "sd", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is tk (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "tk", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is ug (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ug", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is yi (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "yi", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is syr (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "syr", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is ks-arab (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ks-arab", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is en-US (ltr). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en-US", callBack);
expect(cc.getDirection()).toBe("ltr");
});
test("Language is ar-SA (rtl). No cookieCategories, no textResources", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ar-SA", callBack);
expect(cc.getDirection()).toBe("rtl");
});
});
describe("Test html and body direction", () => {
let testId: string = "app";
let htmlDir: string | null;
let bodyDir: string | null;
beforeAll(() => {
htmlDir = document.getElementsByTagName("html")[0].getAttribute("dir");
bodyDir = document.getElementsByTagName("body")[0].getAttribute("dir");
});
beforeEach(() => {
let newDiv = document.createElement("div");
newDiv.setAttribute("id", testId);
document.body.appendChild(newDiv);
});
afterEach(() => {
if (htmlDir) {
document.getElementsByTagName("html")[0].setAttribute("dir", htmlDir);
}
else {
document.getElementsByTagName("html")[0].removeAttribute("dir");
}
if (bodyDir) {
document.getElementsByTagName("body")[0].setAttribute("dir", bodyDir);
}
else {
document.getElementsByTagName("body")[0].removeAttribute("dir");
}
let child = document.getElementById(testId);
if (child) {
let parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
else {
throw new Error("Parent not found error");
}
}
});
test("Language is en (ltr). Html dir is rtl. No cookieCategories, no textResources", () => {
document.getElementsByTagName("html")[0].setAttribute("dir", "rtl");
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is ar (rtl). Html dir is ltr. No cookieCategories, no textResources", () => {
document.getElementsByTagName("html")[0].setAttribute("dir", "ltr");
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ar", callBack);
expect(cc.getDirection()).toBe("ltr");
});
test("Language is en (ltr). Body dir is rtl. No cookieCategories, no textResources", () => {
document.getElementsByTagName("body")[0].setAttribute("dir", "rtl");
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
expect(cc.getDirection()).toBe("rtl");
});
test("Language is ar (rtl). Body dir is ltr. No cookieCategories, no textResources", () => {
document.getElementsByTagName("body")[0].setAttribute("dir", "ltr");
let callBack = function() { return; };
let cc = new ConsentControl(testId, "ar", callBack);
expect(cc.getDirection()).toBe("ltr");
});
});

843
src/index.test.ts Normal file
Просмотреть файл

@ -0,0 +1,843 @@
import { ConsentControl } from "./index";
import * as styles from "./styles.scss";
import { PreferencesControl } from './preferencesControl';
import { ICookieCategoriesPreferences } from "./interfaces/CookieCategoriesPreferences";
function testShowingBanner(dir: string): void {
let bannerBody = document.getElementsByClassName(styles.bannerBody);
expect(bannerBody).toBeTruthy();
expect(bannerBody.length).toBe(1);
expect(bannerBody[0].getAttribute("dir")).toBe(dir);
expect(document.getElementsByClassName(styles.bannerInform).length).toBe(1);
expect(document.getElementsByClassName(styles.infoIcon).length).toBe(1);
expect(document.getElementsByClassName(styles.bannerInformBody).length).toBe(1);
expect(document.getElementsByClassName(styles.buttonGroup).length).toBe(1);
expect(document.getElementsByClassName(styles.bannerButton).length).toBe(2);
}
function testShowingPreferences(cc: ConsentControl, cookieCategoriePreferences: ICookieCategoriesPreferences, display: string): void {
expect(document.getElementsByClassName(styles.cookieModal)).toBeTruthy();
let cookieModal: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.cookieModal)[0];
expect(cookieModal.getAttribute("dir")).toBe(cc.getDirection());
expect(cookieModal.style.display).toBe(display);
expect(document.getElementsByClassName(styles.modalContainer).length).toBe(1);
expect(document.getElementsByClassName(styles.closeModalIcon).length).toBe(1);
expect(document.getElementsByClassName(styles.modalBody).length).toBe(1);
expect(document.getElementsByClassName(styles.modalTitle).length).toBe(1);
expect(document.getElementsByClassName(styles.modalContent).length).toBe(1);
expect(document.getElementsByClassName(styles.cookieStatement).length).toBe(1);
expect(document.getElementsByClassName(styles.cookieOrderedList).length).toBe(1);
expect(document.getElementsByClassName(styles.cookieListItem).length).toBe(cc.cookieCategories.length);
expect(document.getElementsByClassName(styles.cookieListItemTitle).length).toBe(cc.cookieCategories.length);
expect(document.getElementsByClassName(styles.cookieListItemDescription).length).toBe(cc.cookieCategories.length);
// test:
// c1: true => accept radio button should be checked
// c2: false => reject radio button should be checked
let i = 0;
for (let cookieCategory of cc.cookieCategories) {
if (!cookieCategory.isUnswitchable) {
if (cookieCategoriePreferences.hasOwnProperty(cookieCategory.id)) {
if (cookieCategoriePreferences[cookieCategory.id]) {
let acceptRadio: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i * 2];
expect(acceptRadio.checked).toBeTruthy();
}
else if (cookieCategoriePreferences[cookieCategory.id] === false) {
let rejectRadio: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i * 2 + 1];
expect(rejectRadio.checked).toBeTruthy();
}
}
i++;
}
}
expect(document.getElementsByClassName(styles.cookieListItemGroup).length).toBe(i);
expect(document.getElementsByClassName(styles.cookieItemRadioBtnGroup).length).toBe(i);
expect(document.getElementsByClassName(styles.cookieItemRadioBtnCtrl).length).toBe(i * 2);
expect(document.getElementsByClassName(styles.cookieItemRadioBtn).length).toBe(i * 2);
expect(document.getElementsByClassName(styles.cookieItemRadioBtnLabel).length).toBe(i * 2);
expect(document.getElementsByClassName(styles.modalButtonGroup).length).toBe(1);
expect(document.getElementsByClassName(styles.modalButtonSave).length).toBe(1);
expect(document.getElementsByClassName(styles.modalButtonReset).length).toBe(1);
}
function testModalSaveButton(i: number, defined: number[]): void {
let cookieItemRadioBtn: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i];
cookieItemRadioBtn.click();
if (defined.includes(i)) {
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonSave)[0]).disabled).toBeTruthy();
}
else {
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonSave)[0]).disabled).toBeFalsy();
}
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
}
function testRemovingPreferences(): void {
let cookieModal = document.getElementsByClassName(styles.cookieModal);
expect(cookieModal.length).toBe(0);
expect(document.getElementsByClassName(styles.modalContainer).length).toBe(0);
expect(document.getElementsByClassName(styles.closeModalIcon).length).toBe(0);
expect(document.getElementsByClassName(styles.modalBody).length).toBe(0);
expect(document.getElementsByClassName(styles.modalTitle).length).toBe(0);
expect(document.getElementsByClassName(styles.modalContent).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieStatement).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieOrderedList).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieListItem).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieListItemTitle).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieListItemDescription).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieListItemGroup).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieItemRadioBtnGroup).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieItemRadioBtnCtrl).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieItemRadioBtn).length).toBe(0);
expect(document.getElementsByClassName(styles.cookieItemRadioBtnLabel).length).toBe(0);
expect(document.getElementsByClassName(styles.modalButtonGroup).length).toBe(0);
expect(document.getElementsByClassName(styles.modalButtonSave).length).toBe(0);
expect(document.getElementsByClassName(styles.modalButtonReset).length).toBe(0);
}
describe("Test show and hide banner", () => {
let testId: string = "app";
let testElementString = `
<div class="${styles.bannerBody}" dir=ltr role="alert">
<div class="${styles.bannerInform}">
<span class="${styles.infoIcon}" aria-label="Information message"></span> <!-- used for icon -->
<p class="${styles.bannerInformBody}">We use </p>
</div>
<div class="${styles.buttonGroup}">
<button type="button" class="${styles.bannerButton}">Accept all</button>
<button type="button" class="${styles.bannerButton}">More info</button>
</div>
</div>
<!-- The Modal -->
<div class="${styles.cookieModal}" dir=ltr>
<div role="presentation" tabindex="-1"></div>
<div role="dialog" aria-modal="true" aria-label="Flow scroll" class="${styles.modalContainer}" tabindex="-1">
<button aria-label="Close dialog" class="${styles.closeModalIcon}" tabindex="0">&#x2715;</button>
<div role="document" class="${styles.modalBody}">
<div>
<h2 class="${styles.modalTitle}">Manage cookie preferences</h2>
</div>
<form class="${styles.modalContent}">
<p class="${styles.cookieStatement}">Most Microsoft sites use cookies</p>
<ol class="${styles.cookieOrderedList}">
<li class="${styles.cookieListItem}">
<h3 class="${styles.cookieListItemTitle}">1. Essential cookies</h3>
<p class="${styles.cookieListItemDescription}">We use essential cookies</p>
</li>
<li class="${styles.cookieListItem}">
<div class="${styles.cookieListItemGroup}" role="radiogroup" aria-label="Performance cookies setting">
<h3 class="${styles.cookieListItemTitle}">2. Performance</h3>
<p class="${styles.cookieListItemDescription}">We use performance</p>
<div class="${styles.cookieItemRadioBtnGroup}">
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Accept" class="${styles.cookieItemRadioBtn}" name="performanceCookies" value="accept">
<span class="${styles.cookieItemRadioBtnLabel}">Accept</span>
</label>
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Reject" class="${styles.cookieItemRadioBtn}" name="performanceCookies" value="reject">
<span class="${styles.cookieItemRadioBtnLabel}">Reject</span>
</label>
</div>
</div>
</li>
<li class="${styles.cookieListItem}">
<div class="${styles.cookieListItemGroup}" role="radiogroup" aria-label="Advertising cookies setting">
<h3 class="${styles.cookieListItemTitle}">3. Advertising</h3>
<p class="${styles.cookieListItemDescription}">We use advertising</p>
<div class="${styles.cookieItemRadioBtnGroup}">
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Accept" class="${styles.cookieItemRadioBtn}" name="advertisingCookies" value="accept" checked>
<span class="${styles.cookieItemRadioBtnLabel}">Accept</span>
</label>
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Reject" class="${styles.cookieItemRadioBtn}" name="advertisingCookies" value="reject">
<span class="${styles.cookieItemRadioBtnLabel}">Reject</span>
</label>
</div>
</div>
</li>
<li class="${styles.cookieListItem}">
<div class="${styles.cookieListItemGroup}" role="radiogroup" aria-label="Targeting cookies setting">
<h3 class="${styles.cookieListItemTitle}">4. Targeting</h3>
<p class="${styles.cookieListItemDescription}">We use targeting</p>
<div class="${styles.cookieItemRadioBtnGroup}">
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Accept" class="${styles.cookieItemRadioBtn}" name="targetingCookies" value="accept">
<span class="${styles.cookieItemRadioBtnLabel}">Accept</span>
</label>
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Reject" class="${styles.cookieItemRadioBtn}" name="targetingCookies" value="reject" checked>
<span class="${styles.cookieItemRadioBtnLabel}">Reject</span>
</label>
</div>
</div>
</li>
</ol>
</form>
<div class="${styles.modalButtonGroup}">
<button type="button" aria-label="Save changes" class="${styles.modalButtonSave}" disabled>Save changes</button>
<button type="button" aria-label="Reset all" class="${styles.modalButtonReset}" disabled>Reset all</button>
</div>
</div>
</div>
</div>
`;
function testRemovingBanner(): void {
let bannerBody = document.getElementsByClassName(styles.bannerBody);
expect(bannerBody.length).toBe(0);
expect(document.getElementsByClassName(styles.bannerInform).length).toBe(0);
expect(document.getElementsByClassName(styles.infoIcon).length).toBe(0);
expect(document.getElementsByClassName(styles.bannerInformBody).length).toBe(0);
expect(document.getElementsByClassName(styles.buttonGroup).length).toBe(0);
expect(document.getElementsByClassName(styles.bannerButton).length).toBe(0);
}
beforeEach(() => {
let newDiv = document.createElement("div");
newDiv.setAttribute("id", testId);
document.body.appendChild(newDiv);
});
afterEach(() => {
let child = document.getElementById(testId);
if (child) {
let parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
else {
throw new Error("Parent not found error");
}
}
});
test("Pass string in constructor, and banner will be inserted when showBanner(...) is called", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
cc.showBanner({ "c1": true, "c2": false,"c3": undefined });
testShowingBanner(cc.getDirection());
});
test("Pass HTMLElement in constructor, and banner will be inserted when showBanner(...) is called", () => {
let insert = document.getElementById(testId);
if (insert) {
let callBack = function() { return; };
let cc = new ConsentControl(insert, "en", callBack);
cc.showBanner({ "c1": true, "c2": false,"c3": undefined });
testShowingBanner(cc.getDirection());
}
});
test("Pass string in constructor, and preferences dialog will be inserted when showBanner(...) is called", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": undefined, "c3": false };
cc.showBanner(cookieCategoriePreferences);
testShowingPreferences(cc, cookieCategoriePreferences, "");
});
test("Pass HTMLElement in constructor, and preferences dialog will be inserted when showBanner(...) is called", () => {
let callBack = function() { return; };
let insert = document.getElementById(testId);
if (insert) {
let cc = new ConsentControl(insert, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": undefined, "c3": false };
cc.showBanner(cookieCategoriePreferences);
testShowingPreferences(cc, cookieCategoriePreferences, "");
}
});
test("Call showBanner(...) many times, only keep last one", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
cc.showBanner({ "c1": true, "c2": false,"c3": undefined });
cc.showBanner({ "c1": false, "c2": true,"c3": undefined });
cc.showBanner({ "c1": true, "c2": false,"c3": false });
testShowingBanner(cc.getDirection());
testShowingPreferences(cc, { "c1": true, "c2": false,"c3": false }, "");
});
test("If switchable id is not in cookieCategoriePreferences, the category in preferences dialog will not be set", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c2": true, "c3": undefined };
cc.showBanner(cookieCategoriePreferences);
testShowingPreferences(cc, cookieCategoriePreferences, "");
});
test("Preferences dialog will appear when 'More info' button is clicked", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": false, "c3": undefined };
cc.showBanner(cookieCategoriePreferences);
let cookieInfo: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.bannerButton)[1];
cookieInfo.click();
testShowingPreferences(cc, cookieCategoriePreferences, "block");
});
test("'Reset all' and 'Save changes' will be enabled when any radio buttons are clicked", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
cc.showBanner({ "c1": true, "c2": false,"c3": undefined });
let cookieItemRadioBtnLength = document.getElementsByClassName(styles.cookieItemRadioBtn).length;
for (let i = 0; i < cookieItemRadioBtnLength; i++) {
let container = document.getElementById(testId);
if (container) {
container.innerHTML = "";
let otherCallBack = function() { return; };
let otherCc = new ConsentControl(testId, "en", otherCallBack);
let cookieCategoriePreferences = { "c1": true, "c2": false, "c3": undefined };
let cookiePreferencesBtnArray = [0, 3];
otherCc.showBanner(cookieCategoriePreferences);
testModalSaveButton(i, cookiePreferencesBtnArray);
}
else {
throw new Error("Container not found error");
}
}
});
test("'Reset all' will be enabled when any cookieCategoriesPreferences is defined", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriesPreferences: ICookieCategoriesPreferences = { };
cc.showBanner(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeTruthy();
cc.hideBanner();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c1": true };
cc.showBanner(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hideBanner();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c1": false };
cc.showBanner(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hideBanner();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c2": true };
cc.showBanner(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hideBanner();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c2": false };
cc.showBanner(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hideBanner();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c3": true };
cc.showBanner(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hideBanner();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c3": false };
cc.showBanner(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hideBanner();
});
test("Preferences dialog will be removed from DOM when 'X' button is clicked", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
cc.showBanner({ "c1": true, "c2": false,"c3": undefined });
let closeModalIcon: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.closeModalIcon)[0];
closeModalIcon.click();
expect(cc.preferencesCtrl).toBeNull();
testRemovingPreferences();
});
test("'X' button is clicked, and then click 'More info' button. Preferences dialog should appear", () => {
let callBack = function() { return; };
let cookieCategoriePreferences = { "c1": true, "c2": false,"c3": undefined };
let cc = new ConsentControl(testId, "en", callBack);
cc.showBanner(cookieCategoriePreferences);
let closeModalIcon: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.closeModalIcon)[0];
closeModalIcon.click();
let cookieInfo: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.bannerButton)[1];
cookieInfo.click();
testShowingPreferences(cc, cookieCategoriePreferences, "block");
closeModalIcon.click();
cookieInfo.click();
testShowingPreferences(cc, cookieCategoriePreferences, "block");
});
test("Banner will be removed from DOM when hideBanner() is called", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let insert = document.getElementById(testId);
if (insert) {
insert.innerHTML = testElementString;
}
else {
throw new Error("Insert point not found error");
}
cc.hideBanner();
testRemovingBanner()
});
test("Preferences dialog will be removed from DOM when hideBanner() is called", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let insert = document.getElementById(testId);
if (insert) {
insert.innerHTML = testElementString;
}
else {
throw new Error("Insert point not found error");
}
// We only want to test hideBanner() function, so we create HTML elements and preferencesCtrl.
let cookieCategoriePreferences = { "c1": undefined, "c2": true, "c3": false };
cc.preferencesCtrl = new PreferencesControl(cc.cookieCategories,
cc.textResources,
cookieCategoriePreferences,
insert,
"ltr",
() => { cc.preferencesCtrl = null; });
cc.hideBanner();
expect(cc.preferencesCtrl).toBeNull();
testRemovingPreferences();
});
});
describe("Test show and hide preferences dialog", () => {
let testId: string = "app";
let testElementBanner = `
<div class="${styles.bannerBody}" dir=ltr role="alert">
<div class="${styles.bannerInform}">
<span class="${styles.infoIcon}" aria-label="Information message"></span> <!-- used for icon -->
<p class="${styles.bannerInformBody}">We use </p>
</div>
<div class="${styles.buttonGroup}">
<button type="button" class="${styles.bannerButton}">Accept all</button>
<button type="button" class="${styles.bannerButton}">More info</button>
</div>
</div>
`;
let testElementString = `
<!-- The Modal -->
<div class="${styles.cookieModal}" dir=ltr>
<div role="presentation" tabindex="-1"></div>
<div role="dialog" aria-modal="true" aria-label="Flow scroll" class="${styles.modalContainer}" tabindex="-1">
<button aria-label="Close dialog" class="${styles.closeModalIcon}" tabindex="0">&#x2715;</button>
<div role="document" class="${styles.modalBody}">
<div>
<h2 class="${styles.modalTitle}">Manage cookie preferences</h2>
</div>
<form class="${styles.modalContent}">
<p class="${styles.cookieStatement}">Most Microsoft sites use cookies</p>
<ol class="${styles.cookieOrderedList}">
<li class="${styles.cookieListItem}">
<h3 class="${styles.cookieListItemTitle}">1. Essential cookies</h3>
<p class="${styles.cookieListItemDescription}">We use essential cookies</p>
</li>
<li class="${styles.cookieListItem}">
<div class="${styles.cookieListItemGroup}" role="radiogroup" aria-label="Performance cookies setting">
<h3 class="${styles.cookieListItemTitle}">2. Performance</h3>
<p class="${styles.cookieListItemDescription}">We use performance</p>
<div class="${styles.cookieItemRadioBtnGroup}">
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Accept" class="${styles.cookieItemRadioBtn}" name="performanceCookies" value="accept">
<span class="${styles.cookieItemRadioBtnLabel}">Accept</span>
</label>
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Reject" class="${styles.cookieItemRadioBtn}" name="performanceCookies" value="reject">
<span class="${styles.cookieItemRadioBtnLabel}">Reject</span>
</label>
</div>
</div>
</li>
<li class="${styles.cookieListItem}">
<div class="${styles.cookieListItemGroup}" role="radiogroup" aria-label="Advertising cookies setting">
<h3 class="${styles.cookieListItemTitle}">3. Advertising</h3>
<p class="${styles.cookieListItemDescription}">We use advertising</p>
<div class="${styles.cookieItemRadioBtnGroup}">
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Accept" class="${styles.cookieItemRadioBtn}" name="advertisingCookies" value="accept" checked>
<span class="${styles.cookieItemRadioBtnLabel}">Accept</span>
</label>
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Reject" class="${styles.cookieItemRadioBtn}" name="advertisingCookies" value="reject">
<span class="${styles.cookieItemRadioBtnLabel}">Reject</span>
</label>
</div>
</div>
</li>
<li class="${styles.cookieListItem}">
<div class="${styles.cookieListItemGroup}" role="radiogroup" aria-label="Targeting cookies setting">
<h3 class="${styles.cookieListItemTitle}">4. Targeting</h3>
<p class="${styles.cookieListItemDescription}">We use targeting</p>
<div class="${styles.cookieItemRadioBtnGroup}">
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Accept" class="${styles.cookieItemRadioBtn}" name="targetingCookies" value="accept">
<span class="${styles.cookieItemRadioBtnLabel}">Accept</span>
</label>
<label class="${styles.cookieItemRadioBtnCtrl}" role="radio">
<input type="radio" aria-label="Reject" class="${styles.cookieItemRadioBtn}" name="targetingCookies" value="reject" checked>
<span class="${styles.cookieItemRadioBtnLabel}">Reject</span>
</label>
</div>
</div>
</li>
</ol>
</form>
<div class="${styles.modalButtonGroup}">
<button type="button" aria-label="Save changes" class="${styles.modalButtonSave}" disabled>Save changes</button>
<button type="button" aria-label="Reset all" class="${styles.modalButtonReset}" disabled>Reset all</button>
</div>
</div>
</div>
</div>
`;
beforeEach(() => {
let newDiv = document.createElement("div");
newDiv.setAttribute("id", testId);
newDiv.innerHTML = testElementBanner;
document.body.appendChild(newDiv);
});
afterEach(() => {
let child = document.getElementById(testId);
if (child) {
let parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
else {
throw new Error("Parent not found error");
}
}
});
test("Pass string in constructor, and preferences dialog will be inserted when showPreferences(...) is called", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": undefined, "c3": false };
cc.showPreferences(cookieCategoriePreferences);
testShowingBanner("ltr");
testShowingPreferences(cc, cookieCategoriePreferences, "block");
});
test("Pass HTMLElement in constructor, and preferences dialog will be inserted when showPreferences(...) is called", () => {
let callBack = function() { return; };
let insert = document.getElementById(testId);
if (insert) {
let cc = new ConsentControl(insert, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": undefined, "c3": false };
cc.showPreferences(cookieCategoriePreferences);
testShowingBanner("ltr");
testShowingPreferences(cc, cookieCategoriePreferences, "block");
}
});
test("If switchable id is not in cookieCategoriePreferences, the category in preferences dialog will not be set when showPreferences(...) is called", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c3": undefined };
cc.showPreferences(cookieCategoriePreferences);
testShowingBanner("ltr");
testShowingPreferences(cc, cookieCategoriePreferences, "block");
});
test("'Reset all' and 'Save changes' will be enabled when any radio buttons are clicked", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": false, "c3": undefined };
cc.showPreferences(cookieCategoriePreferences);
let cookieItemRadioBtnLength = document.getElementsByClassName(styles.cookieItemRadioBtn).length;
for (let i = 0; i < cookieItemRadioBtnLength; i++) {
let container = document.getElementById(testId);
if (container) {
container.innerHTML = "";
let otherCallBack = function() { return; };
let otherCc = new ConsentControl(testId, "en", otherCallBack);
let cookieCategoriePreferences = { "c1": true, "c2": false, "c3": undefined };
let cookiePreferencesBtnArray = [0, 3];
otherCc.showPreferences(cookieCategoriePreferences);
testModalSaveButton(i, cookiePreferencesBtnArray);
}
else {
throw new Error("Container not found error");
}
}
});
test("'Reset all' will be enabled when any cookieCategoriesPreferences is defined", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriesPreferences: ICookieCategoriesPreferences = { };
cc.showPreferences(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeTruthy();
cc.hidePreferences();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c1": true };
cc.showPreferences(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hidePreferences();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c1": false };
cc.showPreferences(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hidePreferences();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c2": true };
cc.showPreferences(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hidePreferences();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c2": false };
cc.showPreferences(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hidePreferences();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c3": true };
cc.showPreferences(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hidePreferences();
cc = new ConsentControl(testId, "en", callBack);
cookieCategoriesPreferences = { "c3": false };
cc.showPreferences(cookieCategoriesPreferences);
expect((<HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0]).disabled).toBeFalsy();
cc.hidePreferences();
});
test("Preferences dialog will be removed from DOM when 'X' button is clicked", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let cookieCategoriePreferences = { "c1": true, "c2": false, "c3": undefined };
cc.showPreferences(cookieCategoriePreferences);
let closeModalIcon: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.closeModalIcon)[0];
closeModalIcon.click();
testShowingBanner("ltr");
expect(cc.preferencesCtrl).toBeNull();
testRemovingPreferences();
});
test("Preferences dialog will be removed from DOM when hidePreferences() is called", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let insert = document.getElementById(testId);
if (insert) {
insert.innerHTML = testElementBanner + testElementString;
}
else {
throw new Error("Insert point not found error");
}
// We only want to test hidePreferences() function, so we create HTML elements and preferencesCtrl.
let cookieCategoriePreferences = { "c1": undefined, "c2": true, "c3": false };
cc.preferencesCtrl = new PreferencesControl(cc.cookieCategories,
cc.textResources,
cookieCategoriePreferences,
insert,
"ltr",
() => { cc.preferencesCtrl = null; });
cc.hidePreferences();
testShowingBanner("ltr");
expect(cc.preferencesCtrl).toBeNull();
testRemovingPreferences();
});
});
describe("Test containerElement", () => {
let testId: string = "app";
let testId2: string = "app2";
beforeEach(() => {
let newDiv = document.createElement("div");
newDiv.setAttribute("id", testId);
document.body.appendChild(newDiv);
let newDiv2 = document.createElement("div");
newDiv2.setAttribute("id", testId2);
document.body.appendChild(newDiv2);
});
afterEach(() => {
let child = document.getElementById(testId);
if (child) {
let parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
else {
throw new Error("Parent 1 not found error");
}
}
let child2 = document.getElementById(testId2);
if (child2) {
let parent2 = child2.parentNode;
if (parent2) {
parent2.removeChild(child2);
}
else {
throw new Error("Parent 2 not found error");
}
}
});
test("Use setContainerElement(containerElement: string) to change container", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
cc.setContainerElement(testId2);
expect(cc.getContainerElement()).toBeTruthy();
expect(cc.getContainerElement()?.nodeName).toBe("DIV");
expect(cc.getContainerElement()?.getAttribute("id")).toBe(testId2);
});
test("Use setContainerElement(containerElement: HTMLElement) to change container", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
let container = document.getElementById(testId2);
if (container) {
cc.setContainerElement(container);
expect(cc.getContainerElement()).toBeTruthy();
expect(cc.getContainerElement()?.nodeName).toBe("DIV");
expect(cc.getContainerElement()?.getAttribute("id")).toBe(testId2);
}
else {
throw new Error("Container not found error");
}
});
test("Use invalid id in setContainerElement(containerElement: string) to change container", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
expect(() => cc.setContainerElement("testId")).toThrow('Container not found error');
});
test("Use empty element in setContainerElement(containerElement) to change container", () => {
let callBack = function() { return; };
let cc = new ConsentControl(testId, "en", callBack);
expect(() => cc.setContainerElement("")).toThrow('Container not found error');
expect(() => cc.setContainerElement(<HTMLElement> document.getElementById("test"))).toThrow('Container not found error');
});
});

343
src/index.ts Normal file
Просмотреть файл

@ -0,0 +1,343 @@
import * as styles from './styles.scss';
import { PreferencesControl } from './preferencesControl';
import { HtmlTools } from './htmlTools';
import { RTL_LANGUAGE } from './language-list.const';
import { ICookieCategory } from './interfaces/CookieCategories';
import { ITextResources } from './interfaces/TextResources';
import { ICookieCategoriesPreferences } from './interfaces/CookieCategoriesPreferences';
export class ConsentControl {
private containerElement: HTMLElement | null = null; // here the banner will be inserted
culture: string;
// callback function, called on preferences changes (via "Accept All", or "Save changes")
onPreferencesChanged: (cookieCategoriesPreferences: ICookieCategoriesPreferences) => void;
cookieCategories: ICookieCategory[];
textResources: ITextResources;
preferencesCtrl: PreferencesControl | null = null;
private direction: string = 'ltr';
// All categories should be replaced with the passed ones in the control
defaultCookieCategories: ICookieCategory[] =
[
{
id: "c0",
name: "1. Essential cookies",
descHtml: "We use this cookie, read more <a href='link'>here</a>.",
isUnswitchable: true // optional, prevents toggling the category. True only for categories like Essential cookies.
},
{
id: "c1",
name: "2. Performance & analytics",
descHtml: "We use this cookie, read more <a href='link'>here</a>."
},
{
id: "c2",
name: "3. Advertising/Marketing",
descHtml: "Blah"
},
{
id: "c3",
name: "4. Targeting/personalization",
descHtml: "Blah"
}
];
// only the passed text resources should be replaced in the control.
// If any string is not passed the control should keep the default value
defaultTextResources: ITextResources = {
bannerMessageHtml: "We use optional cookies to provide... read <a href='link'>here</a>.",
acceptAllLabel: "Accept all",
moreInfoLabel: "More info",
preferencesDialogCloseLabel: "Close",
preferencesDialogTitle: "Manage cookie preferences",
preferencesDialogDescHtml: "Most Microsoft sites...",
acceptLabel: "Accept",
rejectLabel: "Reject",
saveLabel: "Save changes",
resetLabel: "Reset all"
};
constructor(containerElementOrId: string | HTMLElement,
culture: string,
onPreferencesChanged: (cookieCategoriesPreferences: ICookieCategoriesPreferences) => void,
cookieCategories?: ICookieCategory[],
textResources?: ITextResources) {
this.setContainerElement(containerElementOrId);
this.culture = culture;
this.onPreferencesChanged = onPreferencesChanged;
if (cookieCategories) {
this.cookieCategories = cookieCategories;
}
else {
this.cookieCategories = this.defaultCookieCategories;
}
this.textResources = this.defaultTextResources;
if (textResources) {
this.setTextResources(textResources);
}
this.setDirection();
}
/**
* Set the text resources for the banner to display the text in each area
*
* @param {ITextResources} textResources the text want to be displayed
*/
public setTextResources(textResources: ITextResources): void {
if (textResources.bannerMessageHtml) {
this.textResources.bannerMessageHtml = textResources.bannerMessageHtml;
}
if (textResources.acceptAllLabel) {
this.textResources.acceptAllLabel = textResources.acceptAllLabel;
}
if (textResources.moreInfoLabel) {
this.textResources.moreInfoLabel = textResources.moreInfoLabel;
}
if (textResources.preferencesDialogCloseLabel) {
this.textResources.preferencesDialogCloseLabel = textResources.preferencesDialogCloseLabel;
}
if (textResources.preferencesDialogTitle) {
this.textResources.preferencesDialogTitle = textResources.preferencesDialogTitle;
}
if (textResources.preferencesDialogDescHtml) {
this.textResources.preferencesDialogDescHtml = textResources.preferencesDialogDescHtml;
}
if (textResources.acceptLabel) {
this.textResources.acceptLabel = textResources.acceptLabel;
}
if (textResources.rejectLabel) {
this.textResources.rejectLabel = textResources.rejectLabel;
}
if (textResources.saveLabel) {
this.textResources.saveLabel = textResources.saveLabel;
}
if (textResources.resetLabel) {
this.textResources.resetLabel = textResources.resetLabel;
}
}
/**
* Insert all necessary HTML code and shows the banner.
* Until this method is called there should be no HTML elements of the Consent Control anywhere in the DOM
*
* @param {ICookieCategoriesPreferences} cookieCategoriesPreferences object that indicates cookie categories preferences
*/
public showBanner(cookieCategoriesPreferences: ICookieCategoriesPreferences): void {
// Add <meta name="viewport" content="width=device-width, initial-scale=1.0">
// for responsive web design
if (!document.querySelector('meta[name="viewport"]')) {
let meta = document.createElement('meta');
meta.name = "viewport";
meta.content = "width=device-width, initial-scale=1.0";
document.getElementsByTagName('head')[0].appendChild(meta);
}
// Remove existing banner and preference dialog
this.hideBanner();
let infoIcon = `
<svg xmlns="http://www.w3.org/2000/svg" x='0px' y='0px' viewBox='0 0 44 44' width='24px' height='24px' fill='none' stroke='currentColor'>
<circle cx='22' cy='22' r='20' stroke-width='2'></circle>
<line x1='22' x2='22' y1='18' y2='33' stroke-width='3'></line>
<line x1='22' x2='22' y1='12' y2='15' stroke-width='3'></line>
</svg>
`;
const bannerInnerHtml = `
<div class="${ styles.bannerInform }">
<span class="${ styles.infoIcon }">${ infoIcon }</span> <!-- used for icon -->
<p class="${ styles.bannerInformBody }">
${ this.textResources.bannerMessageHtml }
</p>
</div>
<div class="${ styles.buttonGroup }">
<button type="button" class="${ styles.bannerButton }">${ HtmlTools.escapeHtml(this.textResources.acceptAllLabel) }</button>
<button type="button" class="${ styles.bannerButton }">${ HtmlTools.escapeHtml(this.textResources.moreInfoLabel) }</button>
</div>
`;
const banner = document.createElement('div');
banner.setAttribute('id','wcpConsentBannerCtrl');
banner.setAttribute('class', styles.bannerBody);
banner.setAttribute('dir', this.direction);
banner.setAttribute('role', 'alert');
banner.innerHTML = bannerInnerHtml;
this.containerElement?.appendChild(banner);
if (!this.preferencesCtrl) {
this.initPreferencesCtrl(cookieCategoriesPreferences);
// Add event handler to show preferences dialog (from hidden state) when "More info" button is clicked
let cookieInfo = document.getElementsByClassName(styles.bannerButton)[1];
cookieInfo?.addEventListener('click', () => this.showPreferences(cookieCategoriesPreferences));
}
let acceptAllBtn = <HTMLElement> document.getElementsByClassName(styles.bannerButton)[0];
acceptAllBtn?.addEventListener('click', () => this.onAcceptAllClicked(cookieCategoriesPreferences));
}
/**
* Hides the banner and the Preferences Dialog.
* Removes all HTML elements of the Consent Control from the DOM
*/
public hideBanner(): void {
if (document.getElementsByClassName(styles.bannerBody)) {
let bannerArray = [].slice.call(document.getElementsByClassName(styles.bannerBody));
for (let singleBanner of bannerArray) {
this.containerElement?.removeChild(singleBanner);
}
this.hidePreferences();
}
}
/**
* Shows Preferences Dialog. Leaves banner state unchanged
*
* @param {ICookieCategoriesPreferences} cookieCategoriesPreferences object that indicates cookie categories preferences
*/
public showPreferences(cookieCategoriesPreferences: ICookieCategoriesPreferences): void {
if (!this.preferencesCtrl) {
this.initPreferencesCtrl(cookieCategoriesPreferences);
}
this.preferencesCtrl?.showPreferencesDialog();
}
/**
* Hides Preferences Dialog.
* Removes all HTML elements of the Preferences Dialog from the DOM. Leaves banner state unchanged
*/
public hidePreferences(): void {
if (!this.preferencesCtrl) {
return;
}
this.preferencesCtrl.hidePreferencesDialog();
}
/**
* The method is used to initialize the preferences dialog.
*
* @param {ICookieCategoriesPreferences} cookieCategoriesPreferences object that indicates cookie categories preferences
*/
private initPreferencesCtrl(cookieCategoriesPreferences: ICookieCategoriesPreferences): void {
this.preferencesCtrl = new PreferencesControl(this.cookieCategories,
this.textResources,
cookieCategoriesPreferences,
<HTMLElement> this.containerElement,
this.direction,
() => this.onPreferencesClosed());
this.preferencesCtrl.createPreferencesDialog();
// Add event handler to "Save changes" button event
this.preferencesCtrl.addSaveButtonEvent(() => this.onPreferencesChanged(cookieCategoriesPreferences));
}
/**
* Function that will be called when "Accept all" button is clicked
*
* @param {ICookieCategoriesPreferences} cookieCategoriesPreferences object that indicates cookie categories preferences
*/
private onAcceptAllClicked(cookieCategoriesPreferences: ICookieCategoriesPreferences): void {
for (let cookieCategory of this.cookieCategories) {
if (!cookieCategory.isUnswitchable) {
cookieCategoriesPreferences[cookieCategory.id] = true;
}
}
this.onPreferencesChanged(cookieCategoriesPreferences);
}
/**
* Function that is used to set preferencesCtrl property to null
*/
private onPreferencesClosed(): void {
this.preferencesCtrl = null;
}
/**
* Set the container that will be used for the banner
*
* @param {string | HTMLElement} containerElementOrId here the banner will be inserted
*/
public setContainerElement(containerElementOrId: string | HTMLElement): void {
if (containerElementOrId instanceof Element) {
this.containerElement = containerElementOrId;
}
else if (containerElementOrId && containerElementOrId.length > 0) { // containerElementOrId: string
this.containerElement = document.querySelector('#' + containerElementOrId);
}
else {
this.containerElement = null;
}
if (!this.containerElement) {
throw new Error("Container not found error");
}
}
/**
* Return the container that is used for the banner
*/
public getContainerElement(): HTMLElement | null {
return this.containerElement;
}
/**
* Set the direction by passing the parameter or by checking the culture property
*
* @param {string} dir direction for the web, ltr or rtl
*/
public setDirection(dir?: string): void {
if (dir) {
this.direction = dir;
}
else {
let formatCulture: string = this.culture.toLowerCase();
let cultureArray: string[] = formatCulture.split('-');
let lang: string = cultureArray[0];
// Check <html dir="rtl"> or <html dir="ltr">
if (document.dir) {
this.direction = document.dir;
}
// Check <body dir="rtl"> or <body dir="ltr">
else if (document.body.dir) {
this.direction = document.body.dir;
}
else {
if (RTL_LANGUAGE.indexOf(lang) !== -1) {
this.direction = 'rtl';
}
// ks-Arab-IN is right to left (in language-list.const.ts)
// ks-Deva-IN is left to right
else if (RTL_LANGUAGE.indexOf(cultureArray[0] + '-' + cultureArray[1]) !== -1) {
this.direction = 'rtl';
}
else {
this.direction = 'ltr';
}
}
}
}
/**
* Return the direction
*/
public getDirection(): string {
return this.direction;
}
}

Просмотреть файл

@ -0,0 +1,6 @@
export interface ICookieCategory {
id: string;
name: string;
descHtml: string;
isUnswitchable?: boolean; // optional, prevents toggling the category. True only for categories like Essential cookies.
}

Просмотреть файл

@ -0,0 +1,3 @@
export interface ICookieCategoriesPreferences {
[key: string]: boolean | undefined;
}

Просмотреть файл

@ -0,0 +1,12 @@
export interface ITextResources {
bannerMessageHtml?: string;
acceptAllLabel?: string;
moreInfoLabel?: string;
preferencesDialogCloseLabel?: string;
preferencesDialogTitle?: string;
preferencesDialogDescHtml?: string;
acceptLabel?: string;
rejectLabel?: string;
saveLabel?: string;
resetLabel?: string;
}

Просмотреть файл

@ -0,0 +1,20 @@
// culture can be just language "en" or language-country "en-us".
// Based on language RTL should be applied (https://www.w3.org/International/questions/qa-scripts.en)
// Locale IDs are from
// https://help.bing.microsoft.com/#apex/18/en-US/10004/-1
// https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a
export const RTL_LANGUAGE: string[] = [
'ar',
'he',
'ps',
'ur',
'fa',
'pa',
'sd',
'tk',
'ug',
'yi',
'syr',
'ks-arab' // Kashmiri (Arabic) is rtl
];

266
src/preferencesControl.ts Normal file
Просмотреть файл

@ -0,0 +1,266 @@
import * as styles from "./styles.scss";
import { HtmlTools } from './htmlTools';
import { ICookieCategory } from './interfaces/CookieCategories';
import { ITextResources } from './interfaces/TextResources';
import { ICookieCategoriesPreferences } from './interfaces/CookieCategoriesPreferences';
export class PreferencesControl {
cookieCategories: ICookieCategory[];
textResources: ITextResources;
cookieCategoriesPreferences: ICookieCategoriesPreferences;
private containerElement: HTMLElement;
private direction: string = 'ltr';
private onPreferencesClosed: () => void;
constructor(cookieCategories: ICookieCategory[],
textResources: ITextResources,
cookieCategoriesPreferences: ICookieCategoriesPreferences,
containerElement: HTMLElement,
direction: string,
onPreferencesClosed: () => void) {
this.cookieCategories = cookieCategories;
this.textResources = textResources;
this.cookieCategoriesPreferences = cookieCategoriesPreferences;
this.containerElement = containerElement;
this.direction = direction;
this.onPreferencesClosed = onPreferencesClosed;
}
/**
* Create a hidden Preferences Dialog and insert in the bottom of the container.
*
* @param {boolean} banner true for banner, false for preferences dialog.
*/
public createPreferencesDialog(): void {
let cookieModalInnerHtml = `
<div role="presentation" tabindex="-1"></div>
<div role="dialog" aria-modal="true" aria-label="${ HtmlTools.escapeHtml(this.textResources.preferencesDialogTitle) }" class="${ styles.modalContainer }" tabindex="-1">
<button aria-label="${ HtmlTools.escapeHtml(this.textResources.preferencesDialogCloseLabel) }" class="${ styles.closeModalIcon }" tabindex="0">&#x2715;</button>
<div role="document" class="${ styles.modalBody }">
<div>
<h2 class="${styles.modalTitle}">${ HtmlTools.escapeHtml(this.textResources.preferencesDialogTitle) }</h2>
</div>
<form class="${ styles.modalContent }">
<p class="${ styles.cookieStatement }">
${ this.textResources.preferencesDialogDescHtml }
</p>
<ol class="${ styles.cookieOrderedList }">
</ol>
</form>
<div class="${ styles.modalButtonGroup }">
<button type="button" aria-label="${ HtmlTools.escapeHtml(this.textResources.saveLabel) }" class="${ styles.modalButtonSave }" disabled>${ HtmlTools.escapeHtml(this.textResources.saveLabel) }</button>
<button type="button" aria-label="${ HtmlTools.escapeHtml(this.textResources.resetLabel) }" class="${ styles.modalButtonReset }" disabled>${ HtmlTools.escapeHtml(this.textResources.resetLabel) }</button>
</div>
</div>
</div>
`;
const cookieModal = document.createElement('div');
cookieModal.setAttribute('id','wcpCookiePreferenceCtrl');
cookieModal.setAttribute('class', styles.cookieModal);
cookieModal.setAttribute('dir', this.direction);
cookieModal.innerHTML = cookieModalInnerHtml;
this.containerElement.appendChild(cookieModal);
let enabledResetAll = false;
// Insert cookie category
for (let cookieCategory of this.cookieCategories) {
if (cookieCategory.isUnswitchable) {
let item = `
<li class="${ styles.cookieListItem }">
<h3 class="${ styles.cookieListItemTitle }">${ HtmlTools.escapeHtml(cookieCategory.name) }</h3>
<p class="${ styles.cookieListItemDescription }">${ cookieCategory.descHtml }</p>
</li>
`;
let cookieOrderedList = document.getElementsByClassName(styles.cookieOrderedList)[0];
cookieOrderedList.innerHTML += item;
}
else {
if (this.cookieCategoriesPreferences[cookieCategory.id] !== undefined) {
enabledResetAll = true;
}
let nameAttribute: string = cookieCategory.id;
let acceptValue = this.cookieCategoriesPreferences[cookieCategory.id] === true ? "checked" : "";
let rejectValue = this.cookieCategoriesPreferences[cookieCategory.id] === false ? "checked" : "";
let acceptRadio = `<input type="radio" aria-label="${ HtmlTools.escapeHtml(this.textResources.acceptLabel) }" class="${styles.cookieItemRadioBtn}" name="${nameAttribute}" value="accept" ${acceptValue}>`;
let rejectRadio = `<input type="radio" aria-label="${ HtmlTools.escapeHtml(this.textResources.rejectLabel) }" class="${styles.cookieItemRadioBtn}" name="${nameAttribute}" value="reject" ${rejectValue}>`;
let item = `
<li class="${ styles.cookieListItem }">
<div class="${ styles.cookieListItemGroup}" role="radiogroup" aria-label="${ HtmlTools.escapeHtml(cookieCategory.name) }">
<h3 class="${ styles.cookieListItemTitle }">${ HtmlTools.escapeHtml(cookieCategory.name) }</h3>
<p class="${ styles.cookieListItemDescription}">${cookieCategory.descHtml}</p>
<div class="${ styles.cookieItemRadioBtnGroup}">
<label class="${ styles.cookieItemRadioBtnCtrl}" role="radio">
${ acceptRadio}
<span class="${ styles.cookieItemRadioBtnLabel}">${ HtmlTools.escapeHtml(this.textResources.acceptLabel) }</span>
</label>
<label class="${ styles.cookieItemRadioBtnCtrl}" role="radio">
${ rejectRadio}
<span class="${ styles.cookieItemRadioBtnLabel}">${ HtmlTools.escapeHtml(this.textResources.rejectLabel) }</span>
</label>
</div>
</div>
</li>
`;
let cookieOrderedList = document.getElementsByClassName(styles.cookieOrderedList)[0];
cookieOrderedList.innerHTML += item;
}
}
if (enabledResetAll) {
let modalButtonReset: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0];
if (modalButtonReset) {
modalButtonReset.disabled = false;
}
}
// Add those event handler
this.addPreferencesButtonsEvent();
}
/**
* Show preferences dialog (from hidden state)
*/
public showPreferencesDialog(): void {
let modal: HTMLElement = <HTMLElement> document.getElementsByClassName(styles.cookieModal)[0];
if (modal) {
modal.style.display = 'block';
}
}
/**
* Hides Preferences Dialog. Removes all HTML elements of the Preferences Dialog from the DOM.
*/
public hidePreferencesDialog(): void {
let cookieModal = document.getElementsByClassName(styles.cookieModal)[0];
this.containerElement.removeChild(cookieModal);
this.onPreferencesClosed();
}
/**
* Add event handlers for handling button events
* 1. Click "X" button, preference dialog will be removed from the DOM
* 2. Click any "accept/reject" button, "Save changes" and "Reset all" button will be enabled
* 3. Click any "accept/reject" button, cookieCategoriesPreferences will be set
* 4. Click "Reset all" button, cookieCategoriesPreferences will be reset
*/
private addPreferencesButtonsEvent(): void {
let closeModalIcon = document.getElementsByClassName(styles.closeModalIcon)[0];
let cookieItemRadioBtn: Element[] = [].slice.call(document.getElementsByClassName(styles.cookieItemRadioBtn));
let modalButtonSave: HTMLInputElement = <HTMLInputElement>document.getElementsByClassName(styles.modalButtonSave)[0];
let modalButtonReset: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.modalButtonReset)[0];
closeModalIcon?.addEventListener('click', () => this.hidePreferencesDialog());
if (cookieItemRadioBtn && cookieItemRadioBtn.length) {
for (let radio of cookieItemRadioBtn) {
radio.addEventListener('click', () => {
let categId = radio.getAttribute('name');
if (categId) {
let oldCategValue = this.cookieCategoriesPreferences[categId];
// Change cookieCategoriesPreferences
let categValue = radio.getAttribute('value');
if (categValue === 'accept') {
this.cookieCategoriesPreferences[categId] = true;
}
else { // categValue === 'reject'
this.cookieCategoriesPreferences[categId] = false;
}
// Enable "Save changes" and "Reset all" buttons
if (oldCategValue !== this.cookieCategoriesPreferences[categId]) {
if (modalButtonSave) {
modalButtonSave.disabled = false;
}
}
if (modalButtonReset) {
modalButtonReset.disabled = false;
}
}
});
}
}
modalButtonReset?.addEventListener('click', () => {
if (modalButtonSave) {
modalButtonSave.disabled = false;
}
for (let cookieCategory of this.cookieCategories) {
if (!cookieCategory.isUnswitchable) {
this.cookieCategoriesPreferences[cookieCategory.id] = undefined;
}
}
// Reset UI
this.setRadioBtnState();
});
}
/**
* Add event handlers for handling "Save changes" button event.
* When "Save changes" button is clicked, "fn" will be executed.
*
* @param fn function that needs to be executed
*/
public addSaveButtonEvent(fn: () => void): void {
let modalButtonSave: HTMLInputElement = <HTMLInputElement>document.getElementsByClassName(styles.modalButtonSave)[0];
modalButtonSave?.addEventListener('click', () => fn());
}
/**
* Set radio buttons checked/unchecked in Preferences Dialog
*/
public setRadioBtnState(): void {
let i = 0;
for (let cookieCategory of this.cookieCategories) {
if (cookieCategory.isUnswitchable) {
continue;
}
let categId = cookieCategory.id;
if (this.cookieCategoriesPreferences[categId] === true) {
let acceptRadio: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i];
acceptRadio.checked = true;
i++;
let rejectRadio: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i];
rejectRadio.checked = false;
i++;
}
else if (this.cookieCategoriesPreferences[categId] === false) {
let acceptRadio: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i];
acceptRadio.checked = false;
i++;
let rejectRadio: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i];
rejectRadio.checked = true;
i++;
}
else { // cookieCategoriesPreferences[categId] === undefined
let acceptRadio: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i];
acceptRadio.checked = false;
i++;
let rejectRadio: HTMLInputElement = <HTMLInputElement> document.getElementsByClassName(styles.cookieItemRadioBtn)[i];
rejectRadio.checked = false;
i++;
}
}
}
}

536
src/styles.scss Normal file
Просмотреть файл

@ -0,0 +1,536 @@
$bannerBtnWidth: 150px;
$bannerBtnHeight: 36px;
@mixin bannerFont($weight, $size, $style: normal) {
font : {
family: Segoe UI, SegoeUI, Arial, sans-serif;
style: $style;
weight: $weight;
size: $size;
}
}
// For right-to-left direction
@mixin rtlDesign($margin, $padding, $float: none) {
div[dir="rtl"] & {
margin: $margin;
padding: $padding;
float: $float;
}
}
.bannerBody {
position: relative;
z-index: 9999; /* on top of the page */
width: 100%;
background-color: #F2F2F2;
text-align: left;
@at-root div[dir="rtl"]#{ & } {
text-align: right;
}
}
.bannerInform {
display: inline-table;
margin : {
left: 5%;
right: 5%;
top: 8px;
bottom: 8px;
}
width: 45%; /* If "calc()" is not supported */
width: calc(85% - (#{ $bannerBtnWidth } + 3 * 4px) * 2);
}
.infoIcon {
display: table-cell;
padding: 12px;
width: 24px;
height: 24px;
@include bannerFont(normal, 24px);
/* identical to box height */
line-height: 24px;
color: #000000;
}
.bannerInformBody {
display: table-cell;
vertical-align: middle;
@include bannerFont(normal, 13px);
line-height: 16px;
color: #000000;
/* Add styles to hyperlinks in case websites close the default styles for hyperlinks */
& a {
color: #0067B8;
text-decoration: underline;
}
}
.buttonGroup {
display: inline-block;
position: relative;
bottom: 4px;
}
.bannerButton {
margin: 4px;
padding: 0;
border: none;
width: $bannerBtnWidth;
height: $bannerBtnHeight;
background-color: #EBEBEB;
cursor: pointer;
@include bannerFont(normal, 15px);
line-height: 20px;
text-align: center;
color: #000000;
&:hover {
color: #000000;
background-color: #DBDBDB;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
}
&:focus {
background-color: #DBDBDB;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
border: 2px solid #000000;
box-sizing: border-box;
}
&:disabled {
color: rgba(0, 0, 0, 0.2);
background-color: rgba(0, 0, 0, 0.2);
}
}
.cookieModal {
display: none;
position: fixed;
z-index: 9999; /* on top of the page */
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.6);
overflow: auto;
text-align: left;
@at-root div[dir="rtl"]#{ & } {
text-align: right;
}
}
.modalContainer {
position: relative;
top: 8%;
margin : {
bottom: 40px;
left: auto;
right: auto;
}
background-color: #ffffff;
box-sizing: border-box;
border: 1px solid #0067B8;
width: 640px;
}
.closeModalIcon {
float: right;
margin: 2px;
padding: 12px;
border: none;
cursor: pointer;
@include bannerFont(normal, 13px);
line-height: 13px;
display: flex;
align-items: center;
text-align: center;
color: #666666;
background-color: #ffffff;
@include rtlDesign(2px, 12px, left);
}
.modalBody {
margin : {
top: 36px;
left: 36px;
right: 36px;
}
}
.modalTitle {
margin : {
bottom: 12px;
}
@include bannerFont(600, 20px);
line-height: 24px;
text-transform: none;
color: #000000;
}
.modalContent {
height: 446px;
overflow: auto;
}
.cookieStatement {
margin : {
top: 0;
}
@include bannerFont(normal, 15px);
line-height: 20px;
color: #000000;
/* Add styles to hyperlinks in case websites close the default styles for hyperlinks */
& a {
color: #0067B8;
text-decoration: underline;
}
}
ol.cookieOrderedList {
margin : {
top: 36px;
bottom: 0;
}
padding: 0;
/* Close the default styles which adds decimal numbers in front of list items */
list-style: none;
}
li.cookieListItem {
margin : {
top: 20px;
}
@include bannerFont(600, 18px);
line-height: 24px;
color: #000000;
/* Close the default styles which adds decimal numbers in front of list items */
list-style: none;
}
.cookieListItemGroup {
margin: 0;
padding: 0;
border: none;
}
.cookieListItemTitle {
margin: 0;
padding: 0;
border-bottom: none;
@include bannerFont(600, 18px);
line-height: 24px;
text-transform: none;
color: #000000;
}
.cookieListItemDescription {
display: inline-block;
margin : {
top: 0;
bottom: 16px;
}
@include bannerFont(normal, 15px);
line-height: 20px;
color: #000000;
}
.cookieItemRadioBtnGroup {
display: block;
}
.cookieItemRadioBtnCtrl {
display: inline-block;
margin : {
bottom: 16px;
}
}
@mixin defineRaioInput {
border-radius: 50%;
outline: none;
&:checked::after {
display: block;
position: absolute;
top: 4px;
left: 4px;
border-radius: 50%;
height: 10px;
width: 10px;
content: "";
background-color: #000000;
}
&:hover {
border: 1px solid #0067B8;
&:after {
display: block;
position: absolute;
top: 4px;
left: 4px;
border-radius: 50%;
height: 10px;
width: 10px;
content: "";
background-color: rgba(0, 0, 0, 0.8);
}
}
&:focus {
border: 1px solid #0067B8;
&:after {
display: block;
position: absolute;
top: 4px;
left: 4px;
border-radius: 50%;
height: 10px;
width: 10px;
content: "";
background-color: #000000;
}
}
&:disabled {
background-color: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(0, 0, 0, 0.2);
&:after {
display: block;
position: absolute;
top: 4px;
left: 4px;
border-radius: 50%;
height: 10px;
width: 10px;
content: "";
background-color: rgba(0, 0, 0, 0.2);
}
}
}
input[type="radio"].cookieItemRadioBtn {
display: inline-block;
position: relative; /* Adjust the position */
margin : {
top: 0;
left: 0;
right: 0;
}
width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid #000000;
box-sizing: border-box;
appearance: none;
/* Define our own radio input in case websites close the default styles for input type radio */
@include defineRaioInput;
}
.cookieItemRadioBtnLabel {
display: block;
float: right;
position: relative; /* Adjust the position */
margin : {
left: 8px;
right: 34px;
}
width: 80%; /* If "calc()" is not supported */
width: calc(100% - 62px);
@include bannerFont(normal, 15px);
line-height: 20px;
/* identical to box height, or 133% */
text-transform: none;
color: #000000;
@include rtlDesign(0 8px 0 34px, 0, left);
}
.modalButtonGroup {
margin : {
top: 20px;
bottom: 48px;
}
}
.modalButtonReset {
padding: 0;
border: none;
width: 278px;
height: 36px;
cursor: pointer;
@include bannerFont(normal, 15px);
line-height: 20px;
/* identical to box height, or 133% */
text-align: center;
color: #000000;
background-color: #EBEBEB;
&:hover {
color: #000000;
background-color: #DBDBDB;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
}
&:focus {
background-color: #DBDBDB;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
border: 2px solid #000000;
box-sizing: border-box;
}
&:disabled {
color: rgba(0, 0, 0, 0.2);
background-color: rgba(0, 0, 0, 0.2);
}
}
.modalButtonSave {
float: right;
margin : {
left: 10px;
}
padding: 0;
border: none;
width: 278px;
height: 36px;
cursor: pointer;
@include bannerFont(normal, 15px);
line-height: 20px;
/* identical to box height, or 133% */
text-align: center;
color: #FFFFFF;
background-color: #0067B8;
&:hover {
color: #FFFFFF;
background-color: #0067B8;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
}
&:focus {
background-color: #0067B8;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
border: 2px solid #000000;
box-sizing: border-box;
}
&:disabled {
color: rgba(0, 0, 0, 0.2);
background-color: rgba(0, 120, 215, 0.2);
}
@include rtlDesign(0 10px 0 0, 0, left);
}
@media only screen and (max-width: 800px) {
/* For mobile phones: */
.buttonGroup, .bannerInform {
margin : {
top: 8px;
bottom: 8px;
left: 3.75%;
right: 3.75%;
}
width: 92.5%;
}
.bannerButton {
margin : {
bottom: 8px;
left: 0;
right: 0;
}
width: 100%;
}
.cookieModal {
overflow: hidden;
}
.modalContainer {
top: 1.8%;
width: 93.33%;
height: 96.4%;
}
.modalBody {
margin : {
top: 24px;
left: 24px;
right: 24px;
}
height: 100%;
}
.modalContent {
height: 62%; /* If "calc()" is not supported */
height: calc(100% - 225px);
}
.modalButtonReset {
width: 100%;
}
.modalButtonSave {
margin : {
bottom: 12px;
left: 0;
}
width: 100%;
@include rtlDesign(0 0 12px 0, 0);
}
}

16
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,16 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"strict": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"module": "esnext",
"moduleResolution": "node",
"target": "es5",
"allowJs": true,
},
"include": [
"./src/**/*"
]
}

75
tslint.json Normal file
Просмотреть файл

@ -0,0 +1,75 @@
{
"extends": ["tslint-microsoft-contrib"],
"rules": {
// coding style
"align": [true, "elements", "members", "statements"],
"ban-types": [true, ["Object", "Strong typing preferred"], ["AnyAction"]],
"function-name": [true, {
"static-method-regex": "^[a-z][\\w\\d]+$"
}],
"quotemark": [true, "double"],
"linebreak-style": false,
"max-func-body-length": false,
"max-line-length": false,
"member-ordering": [true, {"order": [
"public-static-field",
"public-instance-field",
"protected-static-field",
"protected-instance-field",
"private-static-field",
"private-instance-field",
"public-constructor",
"protected-constructor",
"private-constructor",
"public-static-method",
"public-instance-method",
"protected-static-method",
"private-static-method",
"protected-instance-method",
"private-instance-method"
] }],
"newline-before-return": false,
"newline-per-chained-call": false,
"no-consecutive-blank-lines": [true, 2],
"typedef": [true, "parameter", "property-declaration", "member-variable-declaration", "array-destructuring"],
"variable-name": [true, "allow-pascal-case", "ban-keywords"],
// modules
"export-name": false,
"import-name": false,
"no-submodule-imports": false,
"no-relative-imports": false,
"no-default-export": false,
"no-import-side-effect": [true, {"ignore-module": "(\\.png|\\.jpg|\\.svg|\\.css|\\.scss)$"}],
"no-implicit-dependencies": [true, "dev"],
"ordered-imports": false,
// documentation
"completed-docs": false,
"missing-jsdoc": false,
// best practices
"no-floating-promises": true,
"no-increment-decrement": false,
"no-null-keyword": false,
"no-parameter-reassignment": false,
"no-unsafe-any": false,
"no-unused-expression": [true, "allow-fast-null-checks", "allow-new"],
"no-void-expression": [true, "ignore-arrow-function-shorthand"],
"jsx-no-lambda": false,
"jsx-no-multiline-js": false,
"strict-boolean-expressions": false,
"underscore-consistent-invocation": false,
"use-simple-attributes": false,
"no-console": false,
// tests
"mocha-no-side-effect-code": false
},
"linterOptions": {
"exclude": [
"*.js",
"node_modules/**/*.ts"
]
}
}

92
webpack.config.js Normal file
Просмотреть файл

@ -0,0 +1,92 @@
const webpack = require("webpack");
const path = require("path");
const config = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "consent-banner.js",
libraryTarget: 'umd',
library: 'ConsentControl'
},
module: {
rules: [
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1,
modules: true
}
},
{
loader: "postcss-loader", options: {
ident: "postcss",
plugins: () => [
postcssPresetEnv({
autoprefixer: {
flexbox: "no-2009",
},
stage: 2,
})
]
}
}
],
},
{
test: /\.scss$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1,
modules: {
exportGlobals: true,
localIdentName: "[path][name]__[local]--[hash:base64:5]" // use '[hash:base64]' for production
}
}
},
{
loader: "postcss-loader", options: {
ident: "postcss",
plugins: () => [
require("postcss-preset-env")({
autoprefixer: {
flexbox: "no-2009",
},
stage: 2
})
]
}
},
"sass-loader"
]
},
{
test: /\.ts(x)?$/,
exclude: /node_modules/,
use: [
"awesome-typescript-loader"
]
}
]
},
resolve: {
extensions: [
".ts",
".js"
]
},
devServer: {
contentBase: "./dist",
watchContentBase: true
}
};
module.exports = config;

Просмотреть файл

@ -0,0 +1,92 @@
const webpack = require("webpack");
const path = require("path");
const config = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "consent-banner.js",
libraryTarget: 'umd',
library: 'ConsentControl'
},
module: {
rules: [
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1,
modules: true
}
},
{
loader: "postcss-loader", options: {
ident: "postcss",
plugins: () => [
postcssPresetEnv({
autoprefixer: {
flexbox: "no-2009",
},
stage: 2,
})
]
}
}
],
},
{
test: /\.scss$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1,
modules: {
exportGlobals: true,
localIdentName: "[hash:base64]"
}
}
},
{
loader: "postcss-loader", options: {
ident: "postcss",
plugins: () => [
require("postcss-preset-env")({
autoprefixer: {
flexbox: "no-2009",
},
stage: 2
})
]
}
},
"sass-loader"
]
},
{
test: /\.ts(x)?$/,
exclude: /node_modules/,
use: [
"awesome-typescript-loader"
]
}
]
},
resolve: {
extensions: [
".ts",
".js"
]
},
devServer: {
contentBase: "./dist",
watchContentBase: true
}
};
module.exports = config;