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

make sidekick opening interactive with storage #375

Merged
merged 4 commits into from
Feb 27, 2023
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
4 changes: 3 additions & 1 deletion src/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ export class Container extends React.Component<Properties> {
<WalletManager className='main__wallet-manager' />
</div>
</div>
<Sidekick className='main__sidekick' />

{this.props.context.isAuthenticated && <Sidekick className='main__sidekick' />}

<ThemeEngine />

{this.props.context.isAuthenticated && <MessengerChat />}
Expand Down
78 changes: 62 additions & 16 deletions src/components/sidekick/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,28 @@ import { shallow } from 'enzyme';
import { Container } from '.';
import { IfAuthenticated } from '../authentication/if-authenticated';
import { MessengerList } from '../messenger/list';
import { SidekickTabs } from './types';

describe('Sidekick', () => {
const subject = (props: any = {}) => {
const allProps = {
className: '',
updateLayout: () => undefined,
updateSidekick: jest.fn(),
setActiveSidekickTab: jest.fn(),
syncSidekickState: jest.fn(),
...props,
};

return shallow(<Container {...allProps} />);
};

it('sync state', () => {
const syncSidekickState = jest.fn();
subject({ syncSidekickState });

expect(syncSidekickState).toHaveBeenCalled();
});

it('adds className', () => {
const wrapper = subject({ className: 'todo' });

Expand All @@ -24,6 +34,22 @@ describe('Sidekick', () => {
expect(ifAuthenticated.find('.todo').exists()).toBe(true);
});

it('renders sidekick with class animation in', () => {
const wrapper = subject({ isOpen: true });

const sidekick = wrapper.find('.sidekick');

expect(sidekick.hasClass('sidekick--slide-in')).toBe(true);
});

it('it should not render out class animation', () => {
const wrapper = subject({ isOpen: false });

const sidekick = wrapper.find('.sidekick');

expect(sidekick.hasClass('sidekick--slide-out')).toBe(false);
});

it('renders sidekick panel', () => {
const wrapper = subject();

Expand All @@ -33,42 +59,62 @@ describe('Sidekick', () => {
});

it('renders sidekick when panel tab is clicked', () => {
const updateLayout = jest.fn();
const wrapper = subject(updateLayout);
const updateSidekick = jest.fn();
const wrapper = subject({ updateSidekick, isOpen: false });

const ifAuthenticated = wrapper.find(IfAuthenticated).find({ showChildren: true });
ifAuthenticated.find('.app-sidekick-panel__target').simulate('click');
expect(wrapper.find('.sidekick').hasClass('sidekick--slide-out')).toBe(false);

expect(ifAuthenticated.find('.sidekick__slide-out').exists()).toBe(false);
});
wrapper.find('.app-sidekick-panel__target').simulate('click');

it('renders default active tab', () => {
const wrapper = subject();

expect(wrapper.find(MessengerList).exists()).toBe(true);
expect(updateSidekick).toHaveBeenCalledWith({ isOpen: true });
});

it('handle network tab content', () => {
const wrapper = subject();
const setActiveSidekickTab = jest.fn();
const wrapper = subject({ setActiveSidekickTab });
wrapper.find('.sidekick__tabs-network').simulate('click');

expect(wrapper.find('.sidekick__tab-content--network').exists()).toBe(true);
expect(setActiveSidekickTab).toHaveBeenCalledWith({ activeTab: SidekickTabs.NETWORK });
});

it('handle messages tab content', () => {
const wrapper = subject();
const setActiveSidekickTab = jest.fn();
const wrapper = subject({ setActiveSidekickTab });
wrapper.find('.sidekick__tabs-messages').simulate('click');

expect(wrapper.find('.sidekick__tab-content--messages').exists()).toBe(true);
expect(setActiveSidekickTab).toHaveBeenCalledWith({ activeTab: SidekickTabs.MESSAGES });
});

it('handle notifications tab content', () => {
const wrapper = subject();
const setActiveSidekickTab = jest.fn();
const wrapper = subject({ setActiveSidekickTab });
wrapper.find('.sidekick__tabs-notifications').simulate('click');

expect(setActiveSidekickTab).toHaveBeenCalledWith({ activeTab: SidekickTabs.NOTIFICATIONS });
});

it('render notifications tab content', () => {
const wrapper = subject({ activeTab: SidekickTabs.NOTIFICATIONS });
wrapper.find('.sidekick__tabs-notifications').simulate('click');

expect(wrapper.find('.sidekick__tab-content--notifications').exists()).toBe(true);
});

it('render messages tab content', () => {
const wrapper = subject({ activeTab: SidekickTabs.MESSAGES });
wrapper.find('.sidekick__tabs-messages').simulate('click');

expect(wrapper.find(MessengerList).exists()).toBe(true);
expect(wrapper.find('.sidekick__tab-content--messages').exists()).toBe(true);
});

it('render network tab content', () => {
const wrapper = subject({ activeTab: SidekickTabs.NETWORK });
wrapper.find('.sidekick__tabs-network').simulate('click');

expect(wrapper.find('.sidekick__tab-content--network').exists()).toBe(true);
});

it('renders total unread messages', () => {
const wrapper = subject({ countAllUnreadMessages: 10 });

Expand Down
68 changes: 43 additions & 25 deletions src/components/sidekick/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,78 +5,92 @@ import { IfAuthenticated } from '../authentication/if-authenticated';
import { IconButton, Icons } from '@zer0-os/zos-component-library';
import classNames from 'classnames';
import { AuthenticationState } from '../../store/authentication/types';
import { AppLayout, update as updateLayout } from '../../store/layout';
import {
UpdateSidekickPayload,
updateSidekick,
setActiveSidekickTab,
SetActiveSidekickTabPayload,
syncSidekickState,
} from '../../store/layout';
import { MessengerList } from '../messenger/list';
import { denormalize } from '../../store/channels';
import { SidekickTabs as Tabs } from './types';

import './styles.scss';

enum Tabs {
NETWORK,
MESSAGES,
NOTIFICATIONS,
}

interface PublicProperties {
className?: string;
}

export interface Properties extends PublicProperties {
user: AuthenticationState['user'];
updateLayout: (layout: Partial<AppLayout>) => void;
updateSidekick: (action: UpdateSidekickPayload) => void;
setActiveSidekickTab: (action: SetActiveSidekickTabPayload) => void;
syncSidekickState: () => void;
countAllUnreadMessages: number;
isOpen: boolean;
activeTab: Tabs;
}

export interface State {
isOpen: boolean;
activeTab: Tabs;
canStartAnimation: boolean;
}

export class Container extends React.Component<Properties, State> {
state = { isOpen: true, activeTab: Tabs.MESSAGES };
state = { canStartAnimation: false };
defaultProps: {
activeTab: Tabs.MESSAGES;
};

static mapState(state: RootState): Partial<Properties> {
const directMessages = denormalize(state.channelsList.value, state).filter((channel) => Boolean(channel.isChannel));

const countAllUnreadMessages = directMessages.reduce(
(count, directMessage) => count + directMessage.unreadCount,
0
);

const {
authentication: { user },
layout: { value },
} = state;

return {
user,
countAllUnreadMessages,
isOpen: value.isSidekickOpen,
activeTab: value.activeSidekickTab,
};
}

static mapActions(_props: Properties): Partial<Properties> {
return { updateLayout };
return { updateSidekick, setActiveSidekickTab, syncSidekickState };
}

slideAnimationEnded = (): void => {
if (!this.state.isOpen) {
this.setState({ isOpen: false });
}
};
componentDidMount() {
this.props.syncSidekickState();
}

get isOpen() {
return this.props.isOpen;
}

clickTab(tab: Tabs): void {
this.setState({
this.props.setActiveSidekickTab({
activeTab: tab,
});
}

handleSidekickPanel = (): void => {
this.props.updateLayout({ isSidekickOpen: !this.state.isOpen });
this.setState({ isOpen: !this.state.isOpen });
toggleSidekickPanel = (): void => {
this.setState({ canStartAnimation: true });
this.props.updateSidekick({ isOpen: !this.isOpen });
};

renderSidekickPanel(): JSX.Element {
return (
<div
className='app-sidekick-panel__target'
onClick={this.handleSidekickPanel}
onClick={this.toggleSidekickPanel}
>
<svg
className='sidekick-panel-tab__tab'
Expand Down Expand Up @@ -135,7 +149,7 @@ export class Container extends React.Component<Properties, State> {
}

renderTabContent(): JSX.Element {
switch (this.state.activeTab) {
switch (this.props.activeTab) {
case Tabs.NETWORK:
return <div className='sidekick__tab-content--network'>NETWORK</div>;
case Tabs.MESSAGES:
Expand All @@ -155,8 +169,12 @@ export class Container extends React.Component<Properties, State> {
return (
<IfAuthenticated showChildren>
<div
className={classNames('sidekick', this.props.className, { 'sidekick__slide-out': !this.state.isOpen })}
onAnimationEnd={this.slideAnimationEnded}
className={classNames(
'sidekick',
this.props.className,
{ 'sidekick--slide-out': this.state.canStartAnimation && !this.isOpen },
{ 'sidekick--slide-in': this.isOpen }
)}
>
{this.renderSidekickPanel()}
<div className='sidekick-panel'>{this.renderTabs()}</div>
Expand Down
14 changes: 9 additions & 5 deletions src/components/sidekick/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
background-color: theme.$background-color-tertiary-hover;
width: $width-sidekick;
height: 100%;
animation: sidekick-slide-in animation.$animation-duration-double ease-in forwards;
margin-right: -$width-sidekick;

&--slide-in {
animation: sidekick-slide-in animation.$animation-duration-double ease-in forwards;
}
&--slide-out {
animation: sidekick-slide-out animation.$animation-duration-double ease-out forwards;
}

&__tabs {
position: absolute;
Expand Down Expand Up @@ -41,10 +48,6 @@
}
}

&__slide-out {
animation: sidekick-slide-out animation.$animation-duration-double ease-out forwards;
}

.scroll-container__gradient {
background: linear-gradient(to bottom, transparent, theme.$background-color-tertiary-hover 100%);
}
Expand Down Expand Up @@ -85,6 +88,7 @@
height: 34px;
width: 34px;
border-radius: 50%;
cursor: pointer;
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/components/sidekick/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum SidekickTabs {
NETWORK = 'network',
MESSAGES = 'messages',
NOTIFICATIONS = 'notifications',
}
31 changes: 31 additions & 0 deletions src/lib/storage/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { resolveFromLocalStorageAsBoolean } from './';

describe('storage', () => {
describe('resolveFromLocalStorage', () => {
it('should use the key to get the data', () => {
const storageKey = 'key-store';

resolveFromLocalStorageAsBoolean(storageKey);
expect(global.localStorage.getItem).toHaveBeenCalledWith(storageKey);
});

it('returns false in case no data is saved', () => {
const value = resolveFromLocalStorageAsBoolean('key');
expect(value).toEqual(false);
});

it('returns false', () => {
global.localStorage.getItem = jest.fn().mockReturnValue('data hjere');

const value = resolveFromLocalStorageAsBoolean('key');
expect(value).toEqual(false);
});

it('returns true', () => {
global.localStorage.getItem = jest.fn().mockReturnValue('true');

const value = resolveFromLocalStorageAsBoolean('key');
expect(value).toEqual(true);
});
});
});
5 changes: 5 additions & 0 deletions src/lib/storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function resolveFromLocalStorageAsBoolean(key) {
const value = localStorage.getItem(key);

return value === null || value === 'true';
}
11 changes: 11 additions & 0 deletions src/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,14 @@ import { configure } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';

configure({ adapter: new Adapter() });

const localStorageMock = {
setItem: jest.fn(),
getItem: jest.fn(),
removeItem: jest.fn(),
length: 0,
clear: jest.fn(),
key: jest.fn(),
};

global.localStorage = localStorageMock;
7 changes: 7 additions & 0 deletions src/store/layout/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SidekickTabs } from './../../components/sidekick/types';

export const SIDEKICK_OPEN_STORAGE = 'isSidekickOpen';

export const SIDEKICK_TAB_KEY = 'sidekick-tab';

export const DEFAULT_ACTIVE_TAB = SidekickTabs.MESSAGES;
Loading