Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

CORE-670 Switch from Osano to CookieYes #72

Merged
merged 5 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openstax/ui-components",
"version": "1.13.0",
"version": "1.14.0",
"license": "MIT",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions src/components/ManageCookies.node.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import renderer from 'react-test-renderer';
import { ManageCookiesLink } from "./ManageCookies";

// For prerendering
describe('ManageCookies outside a browser', () => {
it('renders nothing', () => {
const tree = renderer.create(
Expand Down
118 changes: 81 additions & 37 deletions src/components/ManageCookies.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import renderer from 'react-test-renderer';
import renderer, { act } from 'react-test-renderer';
import { ManageCookiesLink } from "./ManageCookies";

describe('ManageCookies', () => {
describe('without osano', () => {
describe('without cookieYes', () => {

it('renders nothing', () => {
const tree = renderer.create(
Expand All @@ -20,64 +20,108 @@ describe('ManageCookies', () => {
});
});

describe('with osano', () => {
let showDrawerSpy: jest.SpyInstance;
describe('when CookieYes loads', () => {
const bannerLoadEvent = new CustomEvent('cookieyes_banner_load', {});
let component: renderer.ReactTestRenderer;

beforeEach(() => {
jest.clearAllMocks();
});

it('renders button', () => {
act(() => {
component = renderer.create(
<ManageCookiesLink />
);
});
act(() => { document.dispatchEvent(bannerLoadEvent); });
Copy link
Member Author

@Dantemss Dantemss Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand why I had to do 2 separate acts here. I thought everything should work with renderer.create() not in act. But unless I wrap renderer.create() in its own act, the component never renders.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need that act to make sure that hooks and state changes finish before the test runs. otherwise cookieyes never loads and the component renders null.

expect(component.toJSON()).toMatchSnapshot();
});

it('renders button with content and correct class', () => {
act(() => {
component = renderer.create(
<ManageCookiesLink>some content</ManageCookiesLink>
);
});
act(() => { document.dispatchEvent(bannerLoadEvent); });
expect(component.toJSON()).toMatchSnapshot();
});

it('renders button in wrapper', () => {
act(() => {
component = renderer.create(
<ManageCookiesLink wrapper={button => <div>{button}</div>} />
);
});
act(() => { document.dispatchEvent(bannerLoadEvent); });
expect(component.toJSON()).toMatchSnapshot();
});

it('calls additional callback', () => {
const onClick = jest.fn();
act(() => {
component = renderer.create(
<ManageCookiesLink onClick={onClick} />
);
});
act(() => { document.dispatchEvent(bannerLoadEvent); });
component.root.findByType('button').props.onClick();
expect(onClick).toHaveBeenCalled();
});
});

describe('with CookieYes already loaded', () => {
let component: renderer.ReactTestRenderer;

beforeAll(() => {
showDrawerSpy = jest.fn();
(window as any).Osano = {cm: {mode: 'production',
showDrawer: showDrawerSpy}
};
(window as any).getCkyConsent = jest.fn();
});

afterAll(() => {
delete (window as any).Osano;
delete (window as any).getCkyConsent;
});

beforeEach(() => {
jest.clearAllMocks();
});

it('renders button', () => {
const tree = renderer.create(
<ManageCookiesLink />
).toJSON();
expect(tree).toMatchSnapshot();
act(() => {
component = renderer.create(
<ManageCookiesLink />
);
});
expect(component.toJSON()).toMatchSnapshot();
});

it('renders button with content', () => {
const tree = renderer.create(
<ManageCookiesLink>some content</ManageCookiesLink>
).toJSON();
expect(tree).toMatchSnapshot();
it('renders button with content and correct class', () => {
act(() => {
component = renderer.create(
<ManageCookiesLink>some content</ManageCookiesLink>
);
});
expect(component.toJSON()).toMatchSnapshot();
});

it('renders button in wrapper', () => {
const tree = renderer.create(
<ManageCookiesLink wrapper={button => <div>{button}</div>} />
).toJSON();
expect(tree).toMatchSnapshot();
});

it('calls osano showDrawer', () => {
const component = renderer.create(
<ManageCookiesLink />
);

expect(showDrawerSpy).not.toHaveBeenCalled();
component.root.findByType('button').props.onClick();
expect(showDrawerSpy).toHaveBeenCalled();
act(() => {
component = renderer.create(
<ManageCookiesLink wrapper={button => <div>{button}</div>} />
);
});
expect(component.toJSON()).toMatchSnapshot();
});

it('calls additional callback', () => {
const onClick = jest.fn();
const component = renderer.create(
<ManageCookiesLink onClick={onClick} />
);
act(() => {
component = renderer.create(
<ManageCookiesLink onClick={onClick} />
);
});

expect(showDrawerSpy).not.toHaveBeenCalled();
component.root.findByType('button').props.onClick();
expect(showDrawerSpy).toHaveBeenCalled();
expect(onClick).toHaveBeenCalled();
});
});
Expand Down
47 changes: 23 additions & 24 deletions src/components/ManageCookies.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ import React from 'react';
import styled from "styled-components";
import { ManageCookiesLink } from "./ManageCookies";

export const WithoutOsano = () => {
const [show, setShow] = React.useState<boolean>(false);
export const WithoutCookieYes = () => <>
<div className="cky-btn-revisit">mock CookieYes cookie button</div>

// small delay so that going back and forth from WithOsano works
React.useEffect(() => setShow(true),[]);
<h2>Standalone</h2>
<ManageCookiesLink />

return <>
<div className="osano-cm-widget">mock osano cookie button</div>
{show ? <ManageCookiesLink /> : null}
<h2>Inside a styled container</h2>
<SomeContainer>
if there is some container like a footer or whatever that sets colors: <ManageCookiesLink />
</SomeContainer>

<ul>
<li>in a list</li>
<li>use a function child to provide wrappers that will only be included if the link is shown</li>
<ManageCookiesLink wrapper={button => <li>{button}</li>} />
</ul>
</>;
};
<h2>As a list item</h2>
<ul>
<li>use a function child to provide wrappers that will only be included if the link is shown</li>
<ManageCookiesLink wrapper={button => <li>{button}</li>} />
</ul>
</>;

const SomeContainer = styled.div`
a, button {
Expand All @@ -30,25 +30,24 @@ const SomeContainer = styled.div`
}
`;

export const WithOsano = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).Osano = {cm: {mode: 'production', showDrawer: (...args: any[]) => alert('showDrawer called ' + JSON.stringify(args))}};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
React.useEffect(() => () => { delete (window as any).Osano }, []);
export const WithCookieYes = () => {
React.useEffect(() => {
document.dispatchEvent(new CustomEvent('cookieyes_banner_load', {}));
}, []);

return <>
<div className="osano-cm-widget">mock osano cookie button</div>
<div className="cky-btn-revisit">mock CookieYes cookie button</div>

<h2>Standalone</h2>
<ManageCookiesLink />

<h2>Inside a styled container</h2>
<SomeContainer>
if there is some container like a footer or whatever that sets colors, it works
<ManageCookiesLink />
if there is some container like a footer or whatever that sets colors: <ManageCookiesLink />
</SomeContainer>

<h2>As a list item</h2>
<ul>
<li>in a list</li>
<li>use a function child to provide wrappers that will only be included if the link is shown</li>
<ManageCookiesLink wrapper={button => <li>{button}</li>} />
</ul>
Expand Down
50 changes: 30 additions & 20 deletions src/components/ManageCookies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,45 @@ import { ButtonLink } from "./Button";
import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
.osano-cm-widget { display: none; }
`
.cky-btn-revisit { display: none; }
`;

type ManageCookiesLinkProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
wrapper?: (button: React.ReactElement) => React.ReactElement
}
wrapper?: (button: React.ReactElement) => React.ReactElement;
};

// documentation for this at https://www.cookieyes.com/documentation/change-cookie-consent-using-cookieyes/
export const ManageCookiesLink = ({children, className, wrapper, ...props}: ManageCookiesLinkProps) => {
const inBrowser = typeof window === 'object';
const [cookieYesLoaded, setCookieYesLoaded] = React.useState(false);

// documentation for this at https://docs.osano.com/hiding-the-cookie-widget
export const ManageCookiesLink = (props: ManageCookiesLinkProps) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const osano = typeof window === 'undefined' ? undefined : (window as any).Osano
React.useEffect(() => {
if (inBrowser && !cookieYesLoaded) {
const handleCkyLoaded = () => setCookieYesLoaded(true);
if ('getCkyConsent' in window) {
handleCkyLoaded();
} else {
document.addEventListener('cookieyes_banner_load', handleCkyLoaded);
return () => document.removeEventListener('cookieyes_banner_load', handleCkyLoaded);
}
}
return;
}, [cookieYesLoaded, inBrowser]);

if (osano === undefined || osano.cm.mode === 'debug') {
return null;
}
if (!inBrowser) { return <><GlobalStyle /></>; }

const button = <ButtonLink
className={`cky-banner-element${className ? ` ${className}` : ''}`}
{...props}
onClick={e => {
osano.cm.showDrawer('osano-cm-dom-info-dialog-open')
props.onClick?.(e)
}}
>{props.children || 'Manage Cookies'}</ButtonLink>;
>{children || 'Manage Cookies'}</ButtonLink>;

return <>
<GlobalStyle />
{typeof props.wrapper === 'function'
? props.wrapper(button)
: button
{cookieYesLoaded
? typeof wrapper === 'function'
? wrapper(button)
: button
: null
}
</>
</>;
};
45 changes: 34 additions & 11 deletions src/components/__snapshots__/ManageCookies.spec.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,34 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ManageCookies with osano renders button 1`] = `
exports[`ManageCookies when CookieYes loads renders button 1`] = `
<button
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct"
onClick={[Function]}
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct cky-banner-element"
>
Manage Cookies
</button>
`;

exports[`ManageCookies with osano renders button in wrapper 1`] = `
exports[`ManageCookies when CookieYes loads renders button in wrapper 1`] = `
<div>
<button
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct"
onClick={[Function]}
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct cky-banner-element"
>
Manage Cookies
</button>
</div>
`;

exports[`ManageCookies with osano renders button with content 1`] = `
exports[`ManageCookies when CookieYes loads renders button with content and correct class 1`] = `
<button
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct"
onClick={[Function]}
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct cky-banner-element"
>
some content
</button>
`;

exports[`ManageCookies without osano renders nothing 1`] = `null`;
exports[`ManageCookies with CookieYes already loaded renders button 1`] = `
<button
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct cky-banner-element"
>
Manage Cookies
</button>
`;

exports[`ManageCookies with CookieYes already loaded renders button in wrapper 1`] = `
<div>
<button
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct cky-banner-element"
>
Manage Cookies
</button>
</div>
`;

exports[`ManageCookies with CookieYes already loaded renders button with content and correct class 1`] = `
<button
className="sc-gsnTZi sc-dkzDqf czjxlp jmylct cky-banner-element"
>
some content
</button>
`;

exports[`ManageCookies without cookieYes renders nothing 1`] = `null`;

exports[`ManageCookies without osano renders nothing, even a wrapper 1`] = `null`;
exports[`ManageCookies without cookieYes renders nothing, even a wrapper 1`] = `null`;
Loading