Skip to content

Commit 5043c0b

Browse files
authored
Merge pull request #44 from lightninglabs/tooltips-toasts
Add tooltips and toast alerts
2 parents c1a3708 + 4a0e95e commit 5043c0b

38 files changed

+809
-192
lines changed

app/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@
3232
"mobx": "5.15.4",
3333
"mobx-react-lite": "2.0.6",
3434
"mobx-utils": "5.5.7",
35+
"rc-tooltip": "4.2.0",
3536
"react": "^16.13.1",
3637
"react-dom": "^16.13.1",
3738
"react-i18next": "11.4.0",
3839
"react-scripts": "3.4.1",
40+
"react-toastify": "6.0.5",
3941
"react-virtualized": "^9.21.2"
4042
},
4143
"devDependencies": {

app/src/App.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
@import '../node_modules/bootstrap/scss/grid';
1515
@import '../node_modules/bootstrap/scss/custom-forms';
1616

17+
// rc-tooltip component styles
18+
@import '../node_modules/rc-tooltip/assets/bootstrap_white.css';
19+
20+
// react-toastify styles
21+
@import '../node_modules/react-toastify/dist/ReactToastify.css';
22+
1723
@font-face {
1824
font-family: 'OpenSans Light';
1925
src: url('./assets/fonts/OpenSans-Light.ttf') format('truetype');

app/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import './App.scss';
33
import { createStore, StoreProvider } from 'store';
4+
import AlertContainer from 'components/common/AlertContainer';
45
import { Layout } from 'components/layout';
56
import Pages from 'components/Pages';
67
import { ThemeProvider } from 'components/theme';
@@ -13,6 +14,7 @@ const App = () => {
1314
<ThemeProvider>
1415
<Layout>
1516
<Pages />
17+
<AlertContainer />
1618
</Layout>
1719
</ThemeProvider>
1820
</StoreProvider>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
import { useStore } from 'store';
3+
import AlertContainer from 'components/common/AlertContainer';
4+
import { Button } from 'components/common/base';
5+
6+
export default {
7+
title: 'Components/Alerts',
8+
component: AlertContainer,
9+
parameters: { centered: true },
10+
};
11+
12+
export const Default = () => {
13+
const store = useStore();
14+
const handleClick = () => {
15+
store.uiStore.notify(
16+
'This is a sample message to be displayed inside of a toast alert',
17+
'Sample Alert Title',
18+
);
19+
};
20+
21+
return (
22+
<>
23+
<Button onClick={handleClick}>Show Alert</Button>
24+
<AlertContainer />
25+
</>
26+
);
27+
};

app/src/__stories__/Tip.stories.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import Tip from 'components/common/Tip';
3+
4+
export default {
5+
title: 'Components/Tooltip',
6+
component: Tip,
7+
parameters: { contained: true },
8+
};
9+
10+
const placements = [
11+
'top',
12+
'topRight',
13+
'right',
14+
'bottomRight',
15+
'bottom',
16+
'bottomLeft',
17+
'left',
18+
'topLeft',
19+
];
20+
21+
export const Placements = () => {
22+
return (
23+
<div style={{ textAlign: 'center', marginTop: 300 }}>
24+
{placements.map(p => (
25+
<Tip key={p} placement={p} overlay="Tip of the day">
26+
<span style={{ margin: 10 }}>{p}</span>
27+
</Tip>
28+
))}
29+
</div>
30+
);
31+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
import { renderWithProviders } from 'util/tests';
3+
import { createStore, Store } from 'store';
4+
import AlertContainer from 'components/common/AlertContainer';
5+
6+
describe('AlertContainer component', () => {
7+
let store: Store;
8+
9+
const render = () => {
10+
store = createStore();
11+
return renderWithProviders(<AlertContainer />, store);
12+
};
13+
14+
it('should display an alert when added to the store', async () => {
15+
const { findByText } = render();
16+
store.uiStore.notify('test error', 'test title');
17+
expect(await findByText('test error')).toBeInTheDocument();
18+
expect(await findByText('test title')).toBeInTheDocument();
19+
});
20+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react';
2+
import { fireEvent, waitFor } from '@testing-library/react';
3+
import { renderWithProviders } from 'util/tests';
4+
import { createStore, Store } from 'store';
5+
import Tip from 'components/common/Tip';
6+
7+
describe('Tip component', () => {
8+
let store: Store;
9+
10+
const render = (placement?: string) => {
11+
store = createStore();
12+
const cmp = (
13+
<Tip placement={placement} overlay="test tip">
14+
<span>test content</span>
15+
</Tip>
16+
);
17+
return renderWithProviders(cmp, store);
18+
};
19+
20+
it('should display a tooltip on hover', async () => {
21+
const { getByText } = render();
22+
fireEvent.mouseEnter(getByText('test content'));
23+
expect(getByText('test tip')).toBeInTheDocument();
24+
});
25+
26+
it('should display a tooltip on bottom', async () => {
27+
const { getByText, container } = render('bottom');
28+
fireEvent.mouseEnter(getByText('test content'));
29+
waitFor(() => {
30+
expect(container.querySelector('.rc-tooltip-placement-bottom')).toBeInTheDocument();
31+
});
32+
});
33+
34+
it('should display a tooltip on left', async () => {
35+
const { getByText, container } = render('left');
36+
fireEvent.mouseEnter(getByText('test content'));
37+
waitFor(() => {
38+
expect(container.querySelector('.rc-tooltip-placement-left')).toBeInTheDocument();
39+
});
40+
});
41+
42+
it('should display a tooltip on right', async () => {
43+
const { getByText, container } = render('right');
44+
fireEvent.mouseEnter(getByText('test content'));
45+
waitFor(() => {
46+
expect(container.querySelector('.rc-tooltip-placement-right')).toBeInTheDocument();
47+
});
48+
});
49+
});

app/src/__tests__/components/layout/Layout.spec.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ describe('Layout component', () => {
99
};
1010

1111
it('should display the hamburger menu', () => {
12-
const { getByTitle } = render();
13-
expect(getByTitle('menu')).toBeInTheDocument();
12+
const { getByText } = render();
13+
expect(getByText('menu.svg')).toBeInTheDocument();
1414
});
1515

1616
it('should toggle collapsed state', () => {
17-
const { getByTitle, store } = render();
17+
const { getByText, store } = render();
1818
expect(store.settingsStore.sidebarVisible).toBe(true);
19-
fireEvent.click(getByTitle('menu'));
19+
fireEvent.click(getByText('menu.svg'));
2020
expect(store.settingsStore.sidebarVisible).toBe(false);
21-
fireEvent.click(getByTitle('menu'));
21+
fireEvent.click(getByText('menu.svg'));
2222
expect(store.settingsStore.sidebarVisible).toBe(true);
2323
});
2424

app/src/__tests__/components/loop/SwapWizard.spec.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,5 @@ describe('SwapWizard component', () => {
107107
const { getByText } = render();
108108
expect(getByText('Configuring Loops')).toBeInTheDocument();
109109
});
110-
111-
it('should display an error message', () => {
112-
store.buildSwapStore.swapError = new Error('error-test');
113-
const { getByText } = render();
114-
expect(getByText('error-test')).toBeInTheDocument();
115-
});
116110
});
117111
});

app/src/__tests__/store/buildSwapStore.spec.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { values } from 'mobx';
12
import { SwapDirection } from 'types/state';
23
import { grpc } from '@improbable-eng/grpc-web';
34
import { waitFor } from '@testing-library/react';
@@ -42,6 +43,19 @@ describe('BuildSwapStore', () => {
4243
expect(store.terms.out).toEqual({ min: 250000, max: 1000000 });
4344
});
4445

46+
it('should handle errors fetching loop terms', async () => {
47+
grpcMock.unary.mockImplementationOnce(desc => {
48+
if (desc.methodName === 'GetLoopInTerms') throw new Error('test-err');
49+
return undefined as any;
50+
});
51+
expect(rootStore.uiStore.alerts.size).toBe(0);
52+
await store.getTerms();
53+
await waitFor(() => {
54+
expect(rootStore.uiStore.alerts.size).toBe(1);
55+
expect(values(rootStore.uiStore.alerts)[0].message).toBe('test-err');
56+
});
57+
});
58+
4559
it('should adjust the amount after fetching the loop terms', async () => {
4660
store.setAmount(100);
4761
await store.getTerms();
@@ -78,6 +92,21 @@ describe('BuildSwapStore', () => {
7892
expect(store.quote.prepayAmount).toEqual(1337);
7993
});
8094

95+
it('should handle errors fetching loop quote', async () => {
96+
grpcMock.unary.mockImplementationOnce(desc => {
97+
if (desc.methodName === 'LoopOutQuote') throw new Error('test-err');
98+
return undefined as any;
99+
});
100+
store.setDirection(SwapDirection.OUT);
101+
store.setAmount(600);
102+
expect(rootStore.uiStore.alerts.size).toBe(0);
103+
await store.getQuote();
104+
await waitFor(() => {
105+
expect(rootStore.uiStore.alerts.size).toBe(1);
106+
expect(values(rootStore.uiStore.alerts)[0].message).toBe('test-err');
107+
});
108+
});
109+
81110
it('should perform a loop in', async () => {
82111
store.setDirection(SwapDirection.IN);
83112
store.setAmount(600);
@@ -129,18 +158,18 @@ describe('BuildSwapStore', () => {
129158
await waitFor(() => expect(deadline).toEqual(0));
130159
});
131160

132-
it('should handle loop errors', async () => {
161+
it('should handle errors when performing a loop', async () => {
133162
grpcMock.unary.mockImplementationOnce(desc => {
134-
if (desc.methodName === 'LoopIn') throw new Error('asdf');
163+
if (desc.methodName === 'LoopIn') throw new Error('test-err');
135164
return undefined as any;
136165
});
137166
store.setDirection(SwapDirection.IN);
138167
store.setAmount(600);
139-
140-
expect(store.swapError).toBeUndefined();
168+
expect(rootStore.uiStore.alerts.size).toBe(0);
141169
store.requestSwap();
142170
await waitFor(() => {
143-
expect(store.swapError).toBeDefined();
171+
expect(rootStore.uiStore.alerts.size).toBe(1);
172+
expect(values(rootStore.uiStore.alerts)[0].message).toBe('test-err');
144173
});
145174
});
146175

0 commit comments

Comments
 (0)