Skip to content
Snippets Groups Projects
Commit fc11105a authored by mateusz-winiarczyk's avatar mateusz-winiarczyk
Browse files

Merge branch 'MIN-201-initial-cookie-banner' into 'development'

feat(cookies): MIN-201 add initial cookie banner

See merge request !80
parents eb6edcc7 05927669
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!80feat(cookies): MIN-201 add initial cookie banner
Pipeline #83785 passed
Showing
with 266 additions and 0 deletions
......@@ -9,3 +9,26 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({
}));
jest.mock('next/router', () => require('next-router-mock'));
const localStorageMock = (() => {
let store: {
[key: PropertyKey]: string;
} = {};
return {
getItem: jest.fn((key: PropertyKey) => store[key] || null),
setItem: jest.fn((key: PropertyKey, value: any) => {
store[key] = value.toString();
}),
removeItem: jest.fn((key: PropertyKey) => {
delete store[key];
}),
clear: jest.fn(() => {
store = {};
}),
};
})();
Object.defineProperty(global, 'localStorage', {
value: localStorageMock,
});
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ToolkitStoreWithSingleSlice } from '@/utils/createStoreInstanceUsingSliceReducer';
import { CookieBannerState } from '@/redux/cookieBanner/cookieBanner.types';
import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer';
import cookieBannerReducer from '@/redux/cookieBanner/cookieBanner.slice';
import { act } from 'react-dom/test-utils';
import { CookieBanner } from './CookieBanner.component';
import {
USER_ACCEPTED_COOKIES_COOKIE_NAME,
USER_ACCEPTED_COOKIES_COOKIE_VALUE,
} from './CookieBanner.constants';
const renderComponent = (): { store: ToolkitStoreWithSingleSlice<CookieBannerState> } => {
const { Wrapper, store } = getReduxWrapperUsingSliceReducer('cookieBanner', cookieBannerReducer);
return (
render(
<Wrapper>
<CookieBanner />
</Wrapper>,
),
{
store,
}
);
};
describe('CookieBanner component', () => {
beforeEach(() => {
localStorage.clear();
});
it('renders cookie banner correctly first time', () => {
renderComponent();
expect(localStorage.getItem).toHaveBeenCalledWith(USER_ACCEPTED_COOKIES_COOKIE_NAME);
const button = screen.getByLabelText(/accept cookies/i);
expect(button).toBeInTheDocument();
});
it('hides the banner after accepting cookies', () => {
renderComponent();
const button = screen.getByLabelText(/accept cookies/i);
act(() => {
button.click();
});
expect(button).not.toBeInTheDocument();
expect(localStorage.setItem).toHaveBeenCalledWith(
USER_ACCEPTED_COOKIES_COOKIE_NAME,
USER_ACCEPTED_COOKIES_COOKIE_VALUE.ACCEPTED,
);
});
it('does not render the cookies banner when cookies are accepted', () => {
renderComponent();
const button = screen.getByLabelText(/accept cookies/i);
expect(button).toBeInTheDocument();
act(() => {
button.click();
});
renderComponent();
expect(localStorage.getItem).toHaveBeenCalledWith(USER_ACCEPTED_COOKIES_COOKIE_NAME);
expect(button).not.toBeInTheDocument();
});
});
import { selectCookieBanner } from '@/redux/cookieBanner/cookieBanner.selector';
import { acceptCookies, showBanner } from '@/redux/cookieBanner/cookieBanner.slice';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { Button } from '@/shared/Button';
import Link from 'next/link';
import { useEffect } from 'react';
import {
USER_ACCEPTED_COOKIES_COOKIE_NAME,
USER_ACCEPTED_COOKIES_COOKIE_VALUE,
} from './CookieBanner.constants';
export const CookieBanner = (): React.ReactNode => {
const dispatch = useAppDispatch();
const { visible, accepted } = useAppSelector(selectCookieBanner);
useEffect(() => {
const isAccepted =
localStorage.getItem(USER_ACCEPTED_COOKIES_COOKIE_NAME) ===
USER_ACCEPTED_COOKIES_COOKIE_VALUE.ACCEPTED;
if (isAccepted) {
dispatch(acceptCookies());
} else {
dispatch(showBanner());
}
}, [dispatch]);
const handleAcceptCookies = (): void => {
dispatch(acceptCookies());
localStorage.setItem(
USER_ACCEPTED_COOKIES_COOKIE_NAME,
USER_ACCEPTED_COOKIES_COOKIE_VALUE.ACCEPTED,
);
};
if (!visible || accepted) {
return null;
}
return (
<div className="absolute bottom-8 left-1/2 z-10 -translate-x-1/2 rounded-lg bg-white p-6 drop-shadow">
<h4 className="text-2xl font-bold">We use cookies!</h4>
<p className="my-4 leading-loose">
Minerva platform uses essential cookies to ensure its proper operation. For any queries in
relation to our policy on cookies and your choices, please{' '}
<Link href="/" className="font-semibold text-[#1C00DE]">
read here
</Link>
</p>
<Button
className="h-10 w-36 justify-center"
onClick={handleAcceptCookies}
aria-label="accept cookies"
>
Ok
</Button>
</div>
);
};
export const USER_ACCEPTED_COOKIES_COOKIE_NAME = 'cookiesAccepted';
export const USER_ACCEPTED_COOKIES_COOKIE_VALUE = {
ACCEPTED: 'true',
DECLINED: 'false',
};
export { CookieBanner } from './CookieBanner.component';
......@@ -5,6 +5,7 @@ import { useReduxBusQueryManager } from '@/utils/query-manager/useReduxBusQueryM
import { twMerge } from 'tailwind-merge';
import { useInitializeStore } from '../../utils/initialize/useInitializeStore';
import { Modal } from '../FunctionalArea/Modal';
import { CookieBanner } from '../FunctionalArea/CookieBanner';
export const MinervaSPA = (): JSX.Element => {
useInitializeStore();
......@@ -15,6 +16,7 @@ export const MinervaSPA = (): JSX.Element => {
<FunctionalArea />
<Map />
<Modal />
<CookieBanner />
</div>
);
};
import { CookieBannerState } from './cookieBanner.types';
export const COOKIE_BANNER_INITIAL_STATE_MOCK: CookieBannerState = {
visible: false,
accepted: false,
};
import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '@/utils/createStoreInstanceUsingSliceReducer';
import { CookieBannerState } from './cookieBanner.types';
import cookieBannerReducer, { acceptCookies, hideBanner, showBanner } from './cookieBanner.slice';
const INITIAL_STATE: CookieBannerState = {
accepted: false,
visible: false,
};
describe('cookieBanner reducers', () => {
let store = {} as ToolkitStoreWithSingleSlice<CookieBannerState>;
beforeEach(() => {
store = createStoreInstanceUsingSliceReducer('cookieBanner', cookieBannerReducer);
});
it('should match initial state', () => {
const action = { type: 'unknown' };
expect(cookieBannerReducer(undefined, action)).toEqual(INITIAL_STATE);
});
it('should handle showBanner action', () => {
store.dispatch(showBanner());
const { visible } = store.getState().cookieBanner;
expect(visible).toEqual(true);
});
it('should handle hideBanner action', () => {
store.dispatch(hideBanner());
const { visible } = store.getState().cookieBanner;
expect(visible).toEqual(false);
});
it('should handle acceptCookies action', () => {
store.dispatch(acceptCookies());
const expectedState: CookieBannerState = {
visible: false,
accepted: true,
};
const currentState = store.getState().cookieBanner;
expect(currentState).toEqual(expectedState);
});
});
import { CookieBannerState } from './cookieBanner.types';
export const showBannerReducer = (state: CookieBannerState): void => {
state.visible = true;
};
export const hideBannerReducer = (state: CookieBannerState): void => {
state.visible = false;
};
export const acceptCookiesReducer = (state: CookieBannerState): void => {
state.accepted = true;
state.visible = false;
};
import { createSelector } from '@reduxjs/toolkit';
import { rootSelector } from '../root/root.selectors';
export const selectCookieBanner = createSelector(rootSelector, state => state.cookieBanner);
import { createSlice } from '@reduxjs/toolkit';
import {
acceptCookiesReducer,
hideBannerReducer,
showBannerReducer,
} from './cookieBanner.reducers';
import { CookieBannerState } from './cookieBanner.types';
const initialState: CookieBannerState = {
visible: false,
accepted: false,
};
const cookieBannerSlice = createSlice({
name: 'cookieBanner',
initialState,
reducers: {
showBanner: showBannerReducer,
hideBanner: hideBannerReducer,
acceptCookies: acceptCookiesReducer,
},
});
export const { showBanner, hideBanner, acceptCookies } = cookieBannerSlice.actions;
export default cookieBannerSlice.reducer;
export type CookieBannerState = {
accepted: boolean;
visible: boolean;
};
import { BACKGROUND_INITIAL_STATE_MOCK } from '../backgrounds/background.mock';
import { BIOENTITY_INITIAL_STATE_MOCK } from '../bioEntity/bioEntity.mock';
import { CHEMICALS_INITIAL_STATE_MOCK } from '../chemicals/chemicals.mock';
import { COOKIE_BANNER_INITIAL_STATE_MOCK } from '../cookieBanner/cookieBanner.mock';
import { CONFIGURATION_INITIAL_STATE } from '../configuration/configuration.adapter';
import { initialStateFixture as drawerInitialStateMock } from '../drawer/drawerFixture';
import { DRUGS_INITIAL_STATE_MOCK } from '../drugs/drugs.mock';
......@@ -30,5 +31,6 @@ export const INITIAL_STORE_STATE_MOCK: RootState = {
configuration: CONFIGURATION_INITIAL_STATE,
overlayBioEntity: OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
modal: MODAL_INITIAL_STATE_MOCK,
cookieBanner: COOKIE_BANNER_INITIAL_STATE_MOCK,
user: USER_INITIAL_STATE_MOCK,
};
......@@ -10,6 +10,7 @@ import overlaysReducer from '@/redux/overlays/overlays.slice';
import projectReducer from '@/redux/project/project.slice';
import reactionsReducer from '@/redux/reactions/reactions.slice';
import searchReducer from '@/redux/search/search.slice';
import cookieBannerReducer from '@/redux/cookieBanner/cookieBanner.slice';
import userReducer from '@/redux/user/user.slice';
import configurationReducer from '@/redux/configuration/configuration.slice';
import overlayBioEntityReducer from '@/redux/overlayBioEntity/overlayBioEntity.slice';
......@@ -35,6 +36,7 @@ export const reducers = {
overlays: overlaysReducer,
models: modelsReducer,
reactions: reactionsReducer,
cookieBanner: cookieBannerReducer,
user: userReducer,
configuration: configurationReducer,
overlayBioEntity: overlayBioEntityReducer,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment