Skip to content
Open
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
65 changes: 65 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"idb-keyval": "^3.2.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-notifications": "^1.7.2",
"react-scripts": "4.0.1"
},
"scripts": {
Expand Down
3 changes: 3 additions & 0 deletions src/components/App/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { useEffect } from 'react';
import { NotificationContainer } from 'react-notifications';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There does seem to be a warning in VS Code about a "type" for this import. It doesn't stop things from working, but I couldn't find a means of solving this warning. If it's something we should look into further I can do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's an open issue here that suggests a workaround: minhtranite/react-notifications#29

import 'react-notifications/lib/notifications.css';

import ListHeader from '../ListHeader';
import ShoppingList from '../ShoppingList';
Expand All @@ -20,6 +22,7 @@ const App = () => {
<ListHeader list={selectList(state)} />
<ShoppingList items={selectItems(state)} actions={actions} />
<ListSelector lists={selectLists(state)} selectedIndex={state.index} actions={actions} />
<NotificationContainer />
</>
);
};
Expand Down
2 changes: 2 additions & 0 deletions src/components/ListHeader/ListHeader.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useRef, useState } from 'react';
import { NotificationManager } from 'react-notifications';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faShareAlt, faCopy, faPencilAlt, faClock, faTrash } from '@fortawesome/free-solid-svg-icons';
import { ReactComponent as Logo } from '../../static/logo.svg';
Expand All @@ -18,6 +19,7 @@ const ListHeader = ({ list }) => {
});
} else if (navigator.clipboard) {
navigator.clipboard.writeText(url);
NotificationManager.success('Get sharing your list with others!', 'Copied to Clipboard!', 3000);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

"success" can also be changed to error etc for different notification types

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should add a test for this, we'll need to mock out 'react-notifications', allowing us to know when it is called.

jest.mock('react-notifications');

We can then simulate clicking on the button and check that the text is copied to clipboard and that the notification is shown

describe('without the web share API available', () => {
  const writeText = jest.fn(); // create a mock function for writeText
  beforeEach(() => {
    // set the navigator items up for our test
    global.navigator.share = null;
    global.navigator.clipboard = { writeText };

    // render and click on the share icon
    const { getByTitle } = render(<ListHeader />);
    const shareIcon = getByTitle('Share this list');
    fireEvent.click(shareIcon);
  });

  it('copies the item to the clipboard', () => {
    expect(writeText).toHaveBeenCalledWith('https://thelist.app');
  });

  it('triggers a notification', () => {
    expect(NotificationManager.success).toHaveBeenCalledWith('Get sharing your list with others!', 'Copied to Clipboard!', 3000);
  });
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great, thanks will get a test added :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not sure where I'm supposed to place "jest.mock('react-notifications');" and in what context. I understand when you make a mock function and assign it to a constant, but how does the test know to utilise this line of code?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's some great documentation about it here, but the short answer is that it replaces every method in react-notifications with a mocked version that:

  1. doesn't call the actual underlying implementation
  2. allows you to check that the methods were called in the way you expect.

https://jestjs.io/docs/en/mock-functions#mocking-modules

}
};

Expand Down
6 changes: 0 additions & 6 deletions src/components/ListHeader/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,7 @@
justify-content: center;
}

/* .displayListDetails {
height: 100%;
} */

.hideListDetails {
/* height: 0;
opacity: 0; */
display: none;
}

Expand Down
5 changes: 5 additions & 0 deletions src/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ ul, li {
list-style: none;
padding-inline-start: 0px;
}

.notification-success {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For some reason placing this css code in the component file didn't have any affect on the built in styling

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this is great, but we should have a go at creating our own version of these, it'll be quite fun 😄

background-color: var(--check_yellow);
border-radius: 5px;
}
64 changes: 64 additions & 0 deletions src/typings/react-notifications.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
declare module 'react-notifications' {
import { ReactNode } from 'react';
import { EventEmitter } from 'events';

enum NotificationType {
INFO = 'info',
SUCCESS = 'success',
WARNING = 'warning',
ERROR = 'error'
}

enum EventType {
CHANGE = 'change',
INFO = 'info',
SUCCESS = 'success',
WARNING = 'warning',
ERROR = 'error'
}

interface NotificationProps {
type: NotificationType,
title?: ReactNode,
message: ReactNode,
timeOut?: number,
onClick: () => any,
onRequestHide: () => any
}

interface NotificationsProps {
notifications: Notification[];
onRequestHide?: (notification: Notification) => any;
enterTimeout?: number;
leaveTimeout?: number;
}

interface NotificationContainerProps {
enterTimeout?: number;
leaveTimeout?: number;
}

interface INotificationManagerCreate {
type: EventType,
title?: NotificationProps['title']
message?: NotificationProps['message']
timeout?: number,
onClick?: () => any,
priority?: boolean
}

class Notification extends React.Component<NotificationProps, {}> {}

class Notifications extends React.Component<NotificationsProps, {}> {}

class NotificationContainer extends React.Component<NotificationContainerProps, {}> {}

class NotificationManager extends EventEmitter {
static create(INotificationManagerCreate) : void
static info(message?: INotificationManagerCreate['message'], title?: INotificationManagerCreate['title'], timeOut?: INotificationManagerCreate['timeout'], onClick?: INotificationManagerCreate['onClick'], priority?: INotificationManagerCreate['priority']) : void
static success(message?: INotificationManagerCreate['message'], title?: INotificationManagerCreate['title'], timeOut?: INotificationManagerCreate['timeout'], onClick?: INotificationManagerCreate['onClick'], priority?: INotificationManagerCreate['priority']) : void
static warning(message?: INotificationManagerCreate['message'], title?: INotificationManagerCreate['title'], timeOut?: INotificationManagerCreate['timeout'], onClick?: INotificationManagerCreate['onClick'], priority?: INotificationManagerCreate['priority']) : void
static error(message?: INotificationManagerCreate['message'], title?: INotificationManagerCreate['title'], timeOut?: INotificationManagerCreate['timeout'], onClick?: INotificationManagerCreate['onClick'], priority?: INotificationManagerCreate['priority']) : void
static remove(notification: Notification) : void
}
}