├── .babelrc
├── .codebeatignore
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── Notifications_button_24.png
├── app.js
├── bundle.js
├── index.html
├── serviceWorker.html
├── sound.mp3
├── sound.ogg
└── sw.js
├── karma.conf.js
├── lib
└── components
│ └── Notification.js
├── package.json
├── src
└── components
│ └── Notification.js
├── test
└── components
│ └── Notification_spec.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/env",{
4 | "useBuiltIns": "entry",
5 | "corejs": 3
6 | }
7 | ],
8 | "@babel/react"
9 | ],
10 | }
11 |
--------------------------------------------------------------------------------
/.codebeatignore:
--------------------------------------------------------------------------------
1 | lib/**
2 | example/**
3 | karma.conf.js
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | [*.js]
12 | charset = utf-8
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [{package.json}]
17 | indent_style = space
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | example/**
2 | node_modules/**
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "es6": true,
6 | "mocha": true
7 | },
8 | "ecmaFeatures": {
9 | "jsx": true
10 | },
11 | "parser": "babel-eslint",
12 | "plugins": [
13 | "react"
14 | ],
15 | "rules": {
16 | "comma-spacing": 1,
17 | "key-spacing": 0,
18 | "no-underscore-dangle": 0,
19 | "no-unused-vars": [1, { "vars": "all", "args": "none" }],
20 | "no-var": 2,
21 | "quotes": [1, "single", "avoid-escape"],
22 | "react/display-name": 0,
23 | "react/jsx-no-undef": 1,
24 | "react/jsx-uses-react": 1,
25 | "react/no-did-mount-set-state": 1,
26 | "react/no-did-update-set-state": 1,
27 | "react/no-multi-comp": 1,
28 | "react/prop-types": [1, { "ignore": ["children", "className"] }],
29 | "react/react-in-jsx-scope": 1,
30 | "react/self-closing-comp": 1,
31 | "react/wrap-multilines": 1,
32 | "react/jsx-uses-vars": 1,
33 | "strict": 1
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
4 | dist
5 | coverage
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | example
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 | services:
5 | - xvfb
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Change Log
2 |
3 | ### Ver 0.8.0
4 | * Modern browsers support #66
5 |
6 | ### Ver 0.7.0
7 | * Enable to wait user interaction to close #60
8 |
9 | ### Ver 0.6.1
10 | * Update dependencies with `ncu -u`
11 | * Add codebeat
12 |
13 | ### Ver 0.6.0
14 | * Update dependencies, add core-js@3
15 | * Change url from georgeosddev to mobilusoss #48
16 |
17 | ### Ver 0.5.0
18 |
19 | * Update `Babel` from 6.x to 7.x
20 | * Update other dependencies
21 |
22 | ### Ver 0.4.0
23 |
24 | * [add service worker registration prop](https://github.com/mobilusoss/react-web-notification/pull/41)
25 |
26 | * [Add askAgain to propTypes in Readme](https://github.com/mobilusoss/react-web-notification/pull/42)
27 |
28 | ### Ver 0.3.2
29 |
30 | * [Typo issue](https://github.com/mobilusoss/react-web-notification/issues/39)
31 |
32 | ### Ver 0.3.1
33 |
34 | * [Change demo link to https](https://github.com/mobilusoss/react-web-notification/issues/33)
35 |
36 | ### Ver 0.3.0
37 |
38 | * [Update React and dependencies](https://github.com/mobilusoss/react-web-notification/issues/31)
39 |
40 | ### Ver 0.2.4
41 |
42 | * [#28 Use prop-types package instead of accessing PropTypes](https://github.com/mobilusoss/react-web-notification/issues/28), thanks @AlexNisnevich
43 |
44 | ### Ver 0.2.3
45 |
46 | * [#19 add support for react@^15](https://github.com/mobilusoss/react-web-notification/issues/19), thanks @emirotin
47 |
48 | ### Ver 0.2.2
49 |
50 | * [#15 Permission being repeatedly asked for in Safari](https://github.com/mobilusoss/react-web-notification/issues/11)
51 |
52 | ### Ver 0.2.1
53 |
54 | * [#11 depending on library](https://github.com/mobilusoss/react-web-notification/issues/11)
55 |
56 | ### Ver 0.2.0
57 | * [#7 Support React-v0.14](https://github.com/mobilusoss/react-web-notification/issues/7)
58 | * [#8 Publish transpiled code](https://github.com/mobilusoss/react-web-notification/issues/8)
59 |
60 | ### Ver 0.1.2
61 | * [#5 Add option `disableActiveWindow`](https://github.com/mobilusoss/react-web-notification/issues/5)
62 |
63 | ### Ver 0.1.1
64 | * [#2 Compile before publishing](https://github.com/mobilusoss/react-web-notification/issues/2)
65 |
66 | ### Ver 0.1.0 Initial release
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2019 Mobilus Corporation
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-web-notification [](https://travis-ci.org/mobilusoss/react-web-notification) [](http://badge.fury.io/js/react-web-notification) [](https://codebeat.co/projects/github-com-mobilusoss-react-web-notification-master) [](https://app.fossa.io/projects/git%2Bgithub.com%2Fmobilusoss%2Freact-web-notification?ref=badge_shield) [](https://codecov.io/gh/mobilusoss/react-web-notification)
2 |
3 | React component with [HTML5 Web Notification API](https://developer.mozilla.org/en/docs/Web/API/notification).
4 | This component show nothing in dom element, but trigger HTML5 Web Notification API with `render` method in the life cycle of React.js.
5 |
6 | ## Demo
7 |
8 | [View Demo](https://mobilusoss.github.io/react-web-notification/example/)
9 |
10 | ## Installation
11 |
12 | ```bash
13 | npm install --save react-web-notification
14 | ```
15 |
16 | ## API
17 |
18 | ### `Notification`
19 |
20 | React component which wrap web-notification.
21 |
22 | #### Props
23 |
24 | ```javascript
25 | Notification.propTypes = {
26 | ignore: bool,
27 | disableActiveWindow: bool,
28 | askAgain: bool,
29 | notSupported: func,
30 | onPermissionGranted: func,
31 | onPermissionDenied: func,
32 | onShow: func,
33 | onClick: func,
34 | onClose: func,
35 | onError: func,
36 | timeout: number,
37 | title: string.isRequired,
38 | options: object,
39 | swRegistration: object,
40 | };
41 |
42 | ```
43 |
44 | * `ignore` : if true, nothing will be happen
45 |
46 | * `disableActiveWindow` : if true, nothing will be happen when window is active
47 |
48 | * `askAgain` : if true, `window.Notification.requestPermission` will be called on `componentDidMount`, even if it was denied before,
49 |
50 | * `notSupported()` : Called when [HTML5 Web Notification API](https://developer.mozilla.org/en/docs/Web/API/notification) is not supported.
51 |
52 | * `onPermissionGranted()` : Called when permission granted.
53 |
54 | * `onPermissionDenied()` : Called when permission denied. `Notification` will do nothing until permission granted again.
55 |
56 | * `onShow(e, tag)` : Called when Desktop notification is shown.
57 |
58 | * `onClick(e, tag)` : Called when Desktop notification is clicked.
59 |
60 | * `onClose(e, tag)` : Called when Desktop notification is closed.
61 |
62 | * `onError(e, tag)` : Called when Desktop notification happen error.
63 |
64 | * `timeout` : milli sec to close notification automatically. Ignored if `0` or less. (Default `5000`)
65 |
66 | * `title` : Notification title.
67 |
68 | * `options` : Notification options. set `body`, `tag`, `icon` here.
69 | See also (https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification)
70 |
71 | * `swRegistration` : ServiceWorkerRegistration. Use this prop to delegate the notification creation to a service worker.
72 | See also (https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification)
73 | ⚠️ `onShow`, `onClick`, `onClose` and `onError` handlers won't work when notification is created by the service worker.
74 |
75 |
76 | ## Usage example
77 |
78 | See [example](https://github.com/mobilusoss/react-web-notification/tree/develop/example)
79 |
80 | ```bash
81 | yarn
82 | yarn run start:example
83 | ```
84 |
85 | ## Tests
86 |
87 | ```bash
88 | yarn test
89 | ```
90 |
91 | ## Update dependencies
92 |
93 | Use [npm-check-updates](https://www.npmjs.com/package/npm-check-updates)
94 |
95 | ### Known Issues
96 |
97 | * [Notification.sound](https://github.com/mobilusoss/react-web-notification/issues/13)
98 | `Notification.sound` is [not supported in any browser](https://developer.mozilla.org/en/docs/Web/API/notification/sound#Browser_compatibility).
99 | You can emulate it with `onShow` callback. see [example](https://github.com/mobilusoss/react-web-notification/tree/develop/example).
100 |
101 |
102 | ## License
103 | [](https://app.fossa.io/projects/git%2Bgithub.com%2Fmobilusoss%2Freact-web-notification?ref=badge_large)
104 |
--------------------------------------------------------------------------------
/example/Notifications_button_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobilusoss/react-web-notification/295c444b77d7d601ed56030bf40211d9773d8269/example/Notifications_button_24.png
--------------------------------------------------------------------------------
/example/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import ReactDom from 'react-dom';
5 | import Notification from '../lib/components/Notification';
6 |
7 | //allow react dev tools work
8 | window.React = React;
9 |
10 | class App extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | ignore: true,
15 | title: ''
16 | };
17 | }
18 |
19 | handlePermissionGranted(){
20 | console.log('Permission Granted');
21 | this.setState({
22 | ignore: false
23 | });
24 | }
25 | handlePermissionDenied(){
26 | console.log('Permission Denied');
27 | this.setState({
28 | ignore: true
29 | });
30 | }
31 | handleNotSupported(){
32 | console.log('Web Notification not Supported');
33 | this.setState({
34 | ignore: true
35 | });
36 | }
37 |
38 | handleNotificationOnClick(e, tag){
39 | console.log(e, 'Notification clicked tag:' + tag);
40 | }
41 |
42 | handleNotificationOnError(e, tag){
43 | console.log(e, 'Notification error tag:' + tag);
44 | }
45 |
46 | handleNotificationOnClose(e, tag){
47 | console.log(e, 'Notification closed tag:' + tag);
48 | }
49 |
50 | handleNotificationOnShow(e, tag){
51 | this.playSound();
52 | console.log(e, 'Notification shown tag:' + tag);
53 | }
54 |
55 | playSound(filename){
56 | document.getElementById('sound').play();
57 | }
58 |
59 | handleButtonClick() {
60 |
61 | if(this.state.ignore) {
62 | return;
63 | }
64 |
65 | const now = Date.now();
66 |
67 | const title = 'React-Web-Notification' + now;
68 | const body = 'Hello' + new Date();
69 | const tag = now;
70 | const icon = 'http://mobilusoss.github.io/react-web-notification/example/Notifications_button_24.png';
71 | // const icon = 'http://localhost:3000/Notifications_button_24.png';
72 |
73 | // Available options
74 | // See https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification
75 | const options = {
76 | tag: tag,
77 | body: body,
78 | icon: icon,
79 | lang: 'en',
80 | dir: 'ltr',
81 | sound: './sound.mp3' // no browsers supported https://developer.mozilla.org/en/docs/Web/API/notification/sound#Browser_compatibility
82 | }
83 | this.setState({
84 | title: title,
85 | options: options
86 | });
87 | }
88 |
89 | handleButtonClick2() {
90 | this.props.swRegistration.getNotifications({}).then(function(notifications) {
91 | console.log(notifications);
92 | });
93 | }
94 |
95 | render() {
96 | return (
97 |
98 |
99 | {document.title === 'swExample' &&
}
100 |
114 |
119 |
120 | )
121 | }
122 | };
123 | if (document.title === 'swExample') {
124 | navigator.serviceWorker.register('sw.js')
125 | .then(function(registration) {
126 | ReactDom.render(, document.getElementById('out'));
127 | });
128 | } else {
129 | ReactDom.render(, document.getElementById('out'));
130 | }
131 |
132 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React anchorify-text example
6 |
7 |
8 |
9 |
10 |
11 | Icons made by Google from www.flaticon.com is licensed by CC BY 3.0
12 |
25 |
26 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/example/serviceWorker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | swExample
6 |
7 |
8 |
9 |
10 |
11 | Icons made by Google from www.flaticon.com is licensed by CC BY 3.0
12 |
13 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/sound.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobilusoss/react-web-notification/295c444b77d7d601ed56030bf40211d9773d8269/example/sound.mp3
--------------------------------------------------------------------------------
/example/sound.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobilusoss/react-web-notification/295c444b77d7d601ed56030bf40211d9773d8269/example/sound.ogg
--------------------------------------------------------------------------------
/example/sw.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobilusoss/react-web-notification/295c444b77d7d601ed56030bf40211d9773d8269/example/sw.js
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Fri May 15 2015 12:31:20 GMT+0900 (JST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['browserify','mocha'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | 'test/**/*_spec.js',
19 | { pattern: 'node_modules/sinon/pkg/sinon.js', watched: false, included: true }
20 | ],
21 |
22 |
23 | // list of files to exclude
24 | exclude: [
25 | 'test/_helper/*.js'
26 | ],
27 |
28 |
29 | // preprocess matching files before serving them to the browser
30 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
31 | preprocessors: {
32 | 'test/**/*_spec.js': ['browserify'],
33 | 'src/**/*.js': ['coverage']
34 | },
35 |
36 | browserify: {
37 | debug: true,
38 | 'transform': [
39 | [
40 | 'babelify',{
41 | "presets": [
42 | ["@babel/env",{
43 | "useBuiltIns": "entry",
44 | "corejs": 3
45 | }
46 | ],
47 | "@babel/react"
48 | ],
49 | "plugins": [
50 | "istanbul",
51 | ],
52 | }
53 | ]
54 | ]
55 | },
56 |
57 | // test results reporter to use
58 | // possible values: 'dots', 'progress'
59 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
60 | reporters: ['spec', 'coverage'],
61 |
62 | coverageReporter: {
63 | reporters: [
64 | // reporters not supporting the `file` property
65 | {type:'lcovonly', subdir: '.'},
66 | { type: 'text' },
67 | ]
68 | },
69 |
70 | // client: {
71 | // mocha: {
72 | // // reporter: 'html', // change Karma's debug.html to the mocha web reporter
73 | // // ui: 'tdd'
74 | // }
75 | // },
76 |
77 | // web server port
78 | port: 9876,
79 |
80 |
81 | // enable / disable colors in the output (reporters and logs)
82 | colors: true,
83 |
84 |
85 | // level of logging
86 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
87 | logLevel: config.LOG_INFO,
88 |
89 |
90 | // enable / disable watching file and executing tests whenever any file changes
91 | autoWatch: true,
92 |
93 |
94 | // start these browsers
95 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
96 | // browsers: ['Chrome', 'Firefox', 'PhantomJS', 'IE'],
97 | browsers: [process.env.CONTINUOUS_INTEGRATION ? 'Firefox' : 'Chrome'],
98 |
99 |
100 | // Continuous Integration mode
101 | // if true, Karma captures browsers, runs the tests and exits
102 | singleRun: false
103 | });
104 | };
105 |
--------------------------------------------------------------------------------
/lib/components/Notification.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var _propTypes = require("prop-types");
11 |
12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
13 |
14 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
15 |
16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
17 |
18 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
19 |
20 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
21 |
22 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
23 |
24 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
25 |
26 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
27 |
28 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
29 |
30 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
31 |
32 | var PERMISSION_GRANTED = 'granted';
33 | var PERMISSION_DENIED = 'denied';
34 |
35 | var seqGen = function seqGen() {
36 | var i = 0;
37 | return function () {
38 | return i++;
39 | };
40 | };
41 |
42 | var seq = seqGen(); // https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
43 | // https://github.com/mobilusoss/react-web-notification/issues/66
44 |
45 | function checkNotificationPromise() {
46 | try {
47 | window.Notification.requestPermission().then();
48 | } catch (e) {
49 | return false;
50 | }
51 |
52 | return true;
53 | }
54 |
55 | var Notification =
56 | /*#__PURE__*/
57 | function (_React$Component) {
58 | _inherits(Notification, _React$Component);
59 |
60 | function Notification(props) {
61 | var _this;
62 |
63 | _classCallCheck(this, Notification);
64 |
65 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Notification).call(this, props));
66 | var supported = false;
67 | var granted = false;
68 |
69 | if ('Notification' in window && window.Notification) {
70 | supported = true;
71 |
72 | if (window.Notification.permission === PERMISSION_GRANTED) {
73 | granted = true;
74 | }
75 | }
76 |
77 | _this.state = {
78 | supported: supported,
79 | granted: granted
80 | }; // Do not save Notification instance in state
81 |
82 | _this.notifications = {};
83 | _this.windowFocus = true;
84 | _this.onWindowFocus = _this._onWindowFocus.bind(_assertThisInitialized(_this));
85 | _this.onWindowBlur = _this._onWindowBlur.bind(_assertThisInitialized(_this));
86 | return _this;
87 | }
88 |
89 | _createClass(Notification, [{
90 | key: "_onWindowFocus",
91 | value: function _onWindowFocus() {
92 | this.windowFocus = true;
93 | }
94 | }, {
95 | key: "_onWindowBlur",
96 | value: function _onWindowBlur() {
97 | this.windowFocus = false;
98 | }
99 | }, {
100 | key: "_askPermission",
101 | value: function _askPermission() {
102 | var _this2 = this;
103 |
104 | var handlePermission = function handlePermission(permission) {
105 | var result = permission === PERMISSION_GRANTED;
106 |
107 | _this2.setState({
108 | granted: result
109 | }, function () {
110 | if (result) {
111 | _this2.props.onPermissionGranted();
112 | } else {
113 | _this2.props.onPermissionDenied();
114 | }
115 | });
116 | };
117 |
118 | if (checkNotificationPromise()) {
119 | window.Notification.requestPermission().then(function (permission) {
120 | handlePermission(permission);
121 | });
122 | } else {
123 | window.Notification.requestPermission(function (permission) {
124 | handlePermission(permission);
125 | });
126 | }
127 | }
128 | }, {
129 | key: "componentDidMount",
130 | value: function componentDidMount() {
131 | if (this.props.disableActiveWindow) {
132 | window.addEventListener('focus', this.onWindowFocus);
133 | window.addEventListener('blur', this.onWindowBlur);
134 | }
135 |
136 | if (!this.state.supported) {
137 | this.props.notSupported();
138 | } else if (this.state.granted) {
139 | this.props.onPermissionGranted();
140 | } else {
141 | if (window.Notification.permission === PERMISSION_DENIED) {
142 | if (this.props.askAgain) {
143 | this._askPermission();
144 | } else {
145 | this.props.onPermissionDenied();
146 | }
147 | } else {
148 | this._askPermission();
149 | }
150 | }
151 | }
152 | }, {
153 | key: "componentWillUnmount",
154 | value: function componentWillUnmount() {
155 | if (this.props.disableActiveWindow) {
156 | window.removeEventListener('focus', this.onWindowFocus);
157 | window.removeEventListener('blur', this.onWindowBlur);
158 | }
159 | }
160 | }, {
161 | key: "doNotification",
162 | value: function doNotification() {
163 | var _this3 = this;
164 |
165 | var opt = this.props.options;
166 |
167 | if (typeof opt.tag !== 'string') {
168 | opt.tag = 'web-notification-' + seq();
169 | }
170 |
171 | if (this.notifications[opt.tag]) {
172 | return;
173 | }
174 |
175 | if (this.props.swRegistration && this.props.swRegistration.showNotification) {
176 | this.props.swRegistration.showNotification(this.props.title, opt);
177 | this.notifications[opt.tag] = {};
178 | } else {
179 | var n = new window.Notification(this.props.title, opt);
180 |
181 | n.onshow = function (e) {
182 | _this3.props.onShow(e, opt.tag);
183 |
184 | if (_this3.props.timeout > 0) {
185 | setTimeout(function () {
186 | _this3.close(n);
187 | }, _this3.props.timeout);
188 | }
189 | };
190 |
191 | n.onclick = function (e) {
192 | _this3.props.onClick(e, opt.tag);
193 | };
194 |
195 | n.onclose = function (e) {
196 | _this3.props.onClose(e, opt.tag);
197 | };
198 |
199 | n.onerror = function (e) {
200 | _this3.props.onError(e, opt.tag);
201 | };
202 |
203 | this.notifications[opt.tag] = n;
204 | }
205 | }
206 | }, {
207 | key: "render",
208 | value: function render() {
209 | var doNotShowOnActiveWindow = this.props.disableActiveWindow && this.windowFocus;
210 |
211 | if (!this.props.ignore && this.props.title && this.state.supported && this.state.granted && !doNotShowOnActiveWindow) {
212 | this.doNotification();
213 | } // return null cause
214 | // Error: Invariant Violation: Notification.render(): A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object.
215 |
216 |
217 | return _react["default"].createElement("input", {
218 | type: "hidden",
219 | name: "dummy-for-react-web-notification",
220 | style: {
221 | display: 'none'
222 | }
223 | });
224 | }
225 | }, {
226 | key: "close",
227 | value: function close(n) {
228 | if (n && typeof n.close === 'function') {
229 | n.close();
230 | }
231 | } // for debug
232 |
233 | }, {
234 | key: "_getNotificationInstance",
235 | value: function _getNotificationInstance(tag) {
236 | return this.notifications[tag];
237 | }
238 | }]);
239 |
240 | return Notification;
241 | }(_react["default"].Component);
242 |
243 | Notification.propTypes = {
244 | ignore: _propTypes.bool,
245 | disableActiveWindow: _propTypes.bool,
246 | askAgain: _propTypes.bool,
247 | notSupported: _propTypes.func,
248 | onPermissionGranted: _propTypes.func,
249 | onPermissionDenied: _propTypes.func,
250 | onShow: _propTypes.func,
251 | onClick: _propTypes.func,
252 | onClose: _propTypes.func,
253 | onError: _propTypes.func,
254 | timeout: _propTypes.number,
255 | title: _propTypes.string.isRequired,
256 | options: _propTypes.object,
257 | swRegistration: _propTypes.object
258 | };
259 | Notification.defaultProps = {
260 | ignore: false,
261 | disableActiveWindow: false,
262 | askAgain: false,
263 | notSupported: function notSupported() {},
264 | onPermissionGranted: function onPermissionGranted() {},
265 | onPermissionDenied: function onPermissionDenied() {},
266 | onShow: function onShow() {},
267 | onClick: function onClick() {},
268 | onClose: function onClose() {},
269 | onError: function onError() {},
270 | timeout: 5000,
271 | options: {},
272 | swRegistration: null
273 | };
274 | var _default = Notification;
275 | exports["default"] = _default;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-web-notification",
3 | "version": "0.8.0",
4 | "description": "React component with HTML5 Web Notification API",
5 | "main": "./lib/components/Notification.js",
6 | "scripts": {
7 | "browser": "browser-sync start --files example/* --server example",
8 | "watch:example": "watchify example/app.js -dv -o example/bundle.js",
9 | "start:example": "yarn run watch:example & yarn run browser",
10 | "test:local": "karma start",
11 | "test": "NODE_EVN=test ./node_modules/.bin/karma start --browsers Firefox --single-run && codecov",
12 | "clean": "rimraf lib",
13 | "build": "babel src --out-dir lib"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git://github.com/mobilusoss/react-web-notification"
18 | },
19 | "keywords": [
20 | "react",
21 | "react-component",
22 | "notification",
23 | "web notification"
24 | ],
25 | "author": "Takeharu.Oshida",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/mobilusoss/react-web-notification/issues"
29 | },
30 | "homepage": "https://github.com/mobilusoss/react-web-notification",
31 | "devDependencies": {
32 | "@babel/cli": "^7.5.0",
33 | "@babel/core": "^7.5.4",
34 | "@babel/preset-env": "^7.5.4",
35 | "@babel/preset-react": "^7.0.0",
36 | "babel-eslint": "^10.0.2",
37 | "babel-plugin-istanbul": "^5.2.0",
38 | "babelify": "^10.0.0",
39 | "browser-sync": "^2.26.7",
40 | "browserify": "^16.3.0",
41 | "chai": "^4.2.0",
42 | "codecov": "^3.5.0",
43 | "eslint": "^6.0.1",
44 | "eslint-plugin-react": "^7.14.2",
45 | "karma": "^4.2.0",
46 | "karma-browserify": "^6.1.0",
47 | "karma-chai": "^0.1.0",
48 | "karma-chrome-launcher": "^3.0.0",
49 | "karma-cli": "2.0.0",
50 | "karma-coverage": "^1.1.2",
51 | "karma-firefox-launcher": "^1.1.0",
52 | "karma-mocha": "^1.3.0",
53 | "karma-safari-launcher": "^1.0.0",
54 | "karma-spec-reporter": "0.0.32",
55 | "mocha": "^6.1.4",
56 | "prop-types": "^15.7.2",
57 | "react": "^16.8.6",
58 | "react-addons-test-utils": "^15.6.2",
59 | "react-dom": "^16.8.6",
60 | "rimraf": "^2.6.3",
61 | "sinon": "^7.3.2",
62 | "watchify": "^3.11.1"
63 | },
64 | "peerDependencies": {
65 | "react": "^16.8.6"
66 | },
67 | "browserify": {
68 | "transform": [
69 | [
70 | "babelify"
71 | ]
72 | ]
73 | },
74 | "dependencies": {
75 | "core-js": "3"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/components/Notification.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bool, func, number, object, string } from 'prop-types';
3 |
4 | const PERMISSION_GRANTED = 'granted';
5 | const PERMISSION_DENIED = 'denied';
6 |
7 | const seqGen = () => {
8 | let i = 0;
9 | return () => {
10 | return i++;
11 | };
12 | };
13 | const seq = seqGen();
14 |
15 | // https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
16 | // https://github.com/mobilusoss/react-web-notification/issues/66
17 | export const checkNotificationPromise = function() {
18 | try {
19 | window.Notification.requestPermission().then();
20 | } catch(e) {
21 | return false;
22 | }
23 | return true;
24 | }
25 | class Notification extends React.Component {
26 | constructor(props) {
27 | super(props);
28 |
29 | let supported = false;
30 | let granted = false;
31 | if (('Notification' in window) && window.Notification) {
32 | supported = true;
33 | if (window.Notification.permission === PERMISSION_GRANTED) {
34 | granted = true;
35 | }
36 | }
37 |
38 | this.state = {
39 | supported: supported,
40 | granted: granted
41 | };
42 | // Do not save Notification instance in state
43 | this.notifications = {};
44 | this.windowFocus = true;
45 | this.onWindowFocus = this._onWindowFocus.bind(this);
46 | this.onWindowBlur = this._onWindowBlur.bind(this);
47 | }
48 |
49 | _onWindowFocus(){
50 | this.windowFocus = true;
51 | }
52 |
53 | _onWindowBlur(){
54 | this.windowFocus = false;
55 | }
56 |
57 | _askPermission(){
58 | const handlePermission = (permission) => {
59 | let result = permission === PERMISSION_GRANTED;
60 | this.setState({
61 | granted: result
62 | }, () => {
63 | if (result) {
64 | this.props.onPermissionGranted();
65 | } else {
66 | this.props.onPermissionDenied();
67 | }
68 | });
69 | }
70 | if (checkNotificationPromise()) {
71 | window.Notification.requestPermission()
72 | .then((permission) => {
73 | handlePermission(permission);
74 | })
75 | } else {
76 | window.Notification.requestPermission((permission) => {
77 | handlePermission(permission);
78 | });
79 | }
80 | }
81 |
82 | componentDidMount(){
83 | if (this.props.disableActiveWindow) {
84 | window.addEventListener('focus', this.onWindowFocus);
85 | window.addEventListener('blur', this.onWindowBlur);
86 | }
87 |
88 | if (!this.state.supported) {
89 | this.props.notSupported();
90 | } else if (this.state.granted) {
91 | this.props.onPermissionGranted();
92 | } else {
93 | if (window.Notification.permission === PERMISSION_DENIED){
94 | if (this.props.askAgain){
95 | this._askPermission();
96 | } else {
97 | this.props.onPermissionDenied();
98 | }
99 | } else {
100 | this._askPermission();
101 | }
102 | }
103 | }
104 |
105 | componentWillUnmount(){
106 | if (this.props.disableActiveWindow) {
107 | window.removeEventListener('focus', this.onWindowFocus);
108 | window.removeEventListener('blur', this.onWindowBlur);
109 | }
110 | }
111 |
112 | doNotification() {
113 | let opt = this.props.options;
114 | if (typeof opt.tag !== 'string') {
115 | opt.tag = 'web-notification-' + seq();
116 | }
117 | if (this.notifications[opt.tag]) {
118 | return;
119 | }
120 |
121 | if (this.props.swRegistration && this.props.swRegistration.showNotification) {
122 | this.props.swRegistration.showNotification(this.props.title, opt)
123 | this.notifications[opt.tag] = {};
124 | } else {
125 | const n = new window.Notification(this.props.title, opt);
126 | n.onshow = e => {
127 | this.props.onShow(e, opt.tag);
128 | if (this.props.timeout > 0) {
129 | setTimeout(() => {
130 | this.close(n);
131 | }, this.props.timeout);
132 | }
133 | };
134 | n.onclick = e => { this.props.onClick(e, opt.tag); };
135 | n.onclose = e => { this.props.onClose(e, opt.tag); };
136 | n.onerror = e => { this.props.onError(e, opt.tag); };
137 |
138 | this.notifications[opt.tag] = n;
139 | }
140 | }
141 |
142 | render() {
143 | let doNotShowOnActiveWindow = this.props.disableActiveWindow && this.windowFocus;
144 | if (!this.props.ignore && this.props.title && this.state.supported && this.state.granted && !doNotShowOnActiveWindow) {
145 | this.doNotification();
146 | }
147 |
148 | // return null cause
149 | // Error: Invariant Violation: Notification.render(): A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object.
150 | return (
151 |
152 | );
153 | }
154 |
155 | close(n) {
156 | if (n && typeof n.close === 'function') {
157 | n.close();
158 | }
159 | }
160 |
161 | // for debug
162 | _getNotificationInstance(tag) {
163 | return this.notifications[tag];
164 | }
165 | }
166 |
167 | Notification.propTypes = {
168 | ignore: bool,
169 | disableActiveWindow: bool,
170 | askAgain: bool,
171 | notSupported: func,
172 | onPermissionGranted: func,
173 | onPermissionDenied: func,
174 | onShow: func,
175 | onClick: func,
176 | onClose: func,
177 | onError: func,
178 | timeout: number,
179 | title: string.isRequired,
180 | options: object,
181 | swRegistration: object,
182 | };
183 |
184 | Notification.defaultProps = {
185 | ignore: false,
186 | disableActiveWindow: false,
187 | askAgain: false,
188 | notSupported: () => {},
189 | onPermissionGranted: () => {},
190 | onPermissionDenied: () => {},
191 | onShow: () => {},
192 | onClick: () => {},
193 | onClose: () => {},
194 | onError: () => {},
195 | timeout: 5000,
196 | options: {},
197 | swRegistration: null,
198 | };
199 |
200 | export default Notification;
201 |
--------------------------------------------------------------------------------
/test/components/Notification_spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDom from 'react-dom';
3 | import ReactTestUtils from 'react-dom/test-utils';
4 |
5 | import chai from 'chai';
6 | import sinon from 'sinon';
7 | let expect = chai.expect;
8 | import events from 'events'
9 | let EventEmitter = events.EventEmitter;
10 | import Notification, {checkNotificationPromise} from '../../src/components/Notification';
11 |
12 | const PERMISSION_GRANTED = 'granted';
13 | const PERMISSION_DENIED = 'denied';
14 | const PERMISSION_DEFAULT = 'default';
15 |
16 | describe('Test of Notification', () => {
17 |
18 | let component;
19 |
20 | describe('checkNotificationPromise', () => {
21 | describe('promise-based version of Notification.requestPermission() is supported', () => {
22 | let stub;
23 | before(() => {
24 | stub = sinon.stub(window.Notification, 'requestPermission').returns(Promise.resolve());
25 | })
26 | after(() => {
27 | stub.restore();
28 | })
29 | it('will return true', () => {
30 | expect(checkNotificationPromise()).to.be.eql(true);
31 | })
32 | });
33 | describe('when it does not support promise-based version', () => {
34 | let stub;
35 | before(() => {
36 | stub = sinon.stub(window.Notification, 'requestPermission').callsFake((cb) => {cb()});
37 | })
38 | after(() => {
39 | stub.restore();
40 | })
41 | it('will return false', () => {
42 | expect(checkNotificationPromise()).to.be.eql(false);
43 | });
44 | });
45 | });
46 | describe('Notification component', () => {
47 | it('should have default properties', function () {
48 | component = ReactTestUtils.renderIntoDocument();
49 | expect(component.props.ignore).to.be.eql(false);
50 | expect(component.props.disableActiveWindow).to.be.eql(false);
51 | expect(component.props.askAgain).to.be.eql(false);
52 | expect(typeof component.props.notSupported).to.be.eql('function');
53 | expect(typeof component.props.onPermissionGranted).to.be.eql('function');
54 | expect(typeof component.props.onPermissionDenied).to.be.eql('function');
55 | expect(typeof component.props.onShow).to.be.eql('function');
56 | expect(typeof component.props.onClick).to.be.eql('function');
57 | expect(typeof component.props.onClose).to.be.eql('function');
58 | expect(typeof component.props.onError).to.be.eql('function');
59 | expect(component.props.timeout).to.be.eql(5000);
60 | expect(component.props.options).to.be.empty;
61 | });
62 |
63 | it('should render dummy hidden tag', function () {
64 | component = ReactTestUtils.renderIntoDocument();
65 | const el = ReactTestUtils.scryRenderedDOMComponentsWithTag(component, 'input');
66 | expect(el.length).to.be.eql(1);
67 | expect(ReactDom.findDOMNode(el[0]).type).to.be.eql('hidden');
68 | });
69 | });
70 |
71 | describe('Handling HTML5 Web Notification API', () => {
72 | describe('When Notification is not supported', () => {
73 | let cached, stub;
74 |
75 | before(() => {
76 | stub = sinon.stub(window.Notification, 'requestPermission');
77 | cached = window.Notification;
78 | window.Notification = null;
79 | });
80 |
81 | after(() => {
82 | window.Notification = cached;
83 | stub.restore();
84 | });
85 |
86 | it('should call notSupported prop', () => {
87 | let spy = sinon.spy();
88 | component = ReactTestUtils.renderIntoDocument();
89 | expect(spy.calledOnce).to.be.eql(true);
90 | expect(stub.called).to.be.eql(false);
91 | });
92 | });
93 |
94 | describe('When Notification is supported', () => {
95 | describe('start request permission ', () =>{
96 | describe('When Notification is denied', () => {
97 | describe('Check callbacks', ()=> {
98 |
99 | let stub, spy1, spy2;
100 | before(() => {
101 | spy1 = sinon.spy();
102 | spy2 = sinon.spy();
103 | stub = sinon.stub(window.Notification, 'requestPermission').callsFake(function(cb){
104 | if (typeof cb === 'function') cb(PERMISSION_DENIED);
105 | });
106 | component = ReactTestUtils.renderIntoDocument();
107 | });
108 |
109 | after(() => {
110 | stub.restore();
111 | });
112 |
113 | it('should call window.Notification.requestPermission twice', () => {
114 | expect(stub.calledTwice).to.be.eql(true);
115 | expect(stub.getCall(0).args).to.be.eql([]);
116 | expect(stub.getCall(1).args.length).to.be.eql(1);
117 | expect(stub.getCall(1).args[0]).to.be.a('function');
118 | });
119 |
120 | it('should call onPermissionDenied prop', () => {
121 | expect(spy1.called).to.be.eql(false);
122 | expect(spy2.calledOnce).to.be.eql(true);
123 | });
124 | });
125 | });
126 |
127 | describe('When Notification is already denied', () => {
128 | describe('Check callbacks', ()=> {
129 |
130 | let stub1, stub2, spy1, spy2;
131 | before(() => {
132 | spy1 = sinon.spy();
133 | spy2 = sinon.spy();
134 | stub1 = sinon.stub(window.Notification, 'permission').get(function getterFn() {
135 | return PERMISSION_DENIED;
136 | });
137 | stub2 = sinon.stub(window.Notification, 'requestPermission').callsFake(function(cb){
138 | return cb(PERMISSION_DENIED);
139 | });
140 | component = ReactTestUtils.renderIntoDocument();
141 | });
142 |
143 | after(() => {
144 | stub1.restore();
145 | stub2.restore();
146 | });
147 |
148 | it('should not call window.Notification.requestPermission', () => {
149 | expect(stub2.called).to.be.eql(false);
150 | });
151 |
152 | it('should call onPermissionDenied prop', () => {
153 | expect(spy1.called).to.be.eql(false);
154 | expect(spy2.calledOnce).to.be.eql(true);
155 | });
156 | });
157 | });
158 |
159 | describe('When Notification is already denied, but `askAgain` prop is true', () => {
160 | describe('Check callbacks', ()=> {
161 |
162 | let stub1, stub2, spy1, spy2, spy3;
163 | before(() => {
164 | spy1 = sinon.spy();
165 | spy2 = sinon.spy();
166 | spy3 = sinon.spy();
167 | stub1 = sinon.stub(window.Notification, 'permission').get(function getterFn() {
168 | return PERMISSION_DENIED;
169 | });
170 | stub2 = sinon.stub(window.Notification, 'requestPermission').callsFake(function(cb){
171 | return cb(PERMISSION_GRANTED);
172 | });
173 | component = ReactTestUtils.renderIntoDocument();
174 | });
175 |
176 | after(() => {
177 | stub1.restore();
178 | stub2.restore();
179 | });
180 |
181 | it('should call window.Notification.requestPermission', () => {
182 | it('should call window.Notification.requestPermission twice', () => {
183 | expect(stub.calledTwice).to.be.eql(true);
184 | expect(stub.getCall(0).args).to.be.eql([]);
185 | expect(stub.getCall(1).args.length).to.be.eql(1);
186 | expect(stub.getCall(1).args[0]).to.be.a('function');
187 | });
188 | });
189 |
190 | it('should call onPermissionGranted prop', () => {
191 | expect(spy1.called).to.be.eql(false);
192 | expect(spy2.called).to.be.eql(false);
193 | expect(spy3.calledOnce).to.be.eql(true);
194 | });
195 | });
196 | });
197 |
198 | describe('When Notification is granted', () => {
199 | let stub;
200 | before(() => {
201 | stub = sinon.stub(window.Notification, 'requestPermission').callsFake(function(cb){
202 | return cb(PERMISSION_GRANTED);
203 | });
204 | });
205 |
206 | after(() => {
207 | stub.restore();
208 | });
209 |
210 | describe('Check callbacks', ()=> {
211 | let spy1, spy2, spy3;
212 | before(() => {
213 | spy1 = sinon.spy();
214 | spy2 = sinon.spy();
215 | spy3 = sinon.spy();
216 | component = ReactTestUtils.renderIntoDocument();
217 | });
218 |
219 | it('should call window.Notification.requestPermission', () => {
220 | expect(stub.called).to.be.eql(true);
221 | });
222 |
223 | it('should call onPermissionDenied prop', () => {
224 | expect(spy1.called).to.be.eql(false);
225 | expect(spy2.called).to.be.eql(false);
226 | expect(spy3.calledOnce).to.be.eql(true);
227 | });
228 | });
229 |
230 | describe('Handle component properties', () => {
231 |
232 | let stubConstructor, onShowSpy, onClickSpy, onCloseSpy, onErrorSpy, ee;
233 |
234 | beforeEach(() => {
235 | EventEmitter.prototype.addEventListener = EventEmitter.prototype.addListener;
236 | ee = new EventEmitter();
237 |
238 | stubConstructor = sinon.stub(window, 'Notification');
239 | stubConstructor.onFirstCall().returns(ee);
240 | });
241 |
242 | afterEach(()=>{
243 | // stub.restore();
244 | stubConstructor.restore();
245 | });
246 |
247 | describe('when ignore prop is true', () => {
248 | onShowSpy = sinon.spy();
249 | onClickSpy = sinon.spy();
250 | onCloseSpy = sinon.spy();
251 | onErrorSpy = sinon.spy();
252 |
253 | it('does not trigger Notification', () => {
254 | component = ReactTestUtils.renderIntoDocument();
255 | expect(stubConstructor.calledWithNew()).to.be.eql(false);
256 | expect(onShowSpy.called).to.be.eql(false);
257 | expect(onClickSpy.called).to.be.eql(false);
258 | expect(onCloseSpy.called).to.be.eql(false);
259 | expect(onErrorSpy.called).to.be.eql(false);
260 | });
261 | });
262 |
263 | describe('when ignore prop is false', () => {
264 | const MY_TITLE = 'mytitle';
265 | const MY_OPTIONS = {
266 | tag: 'mytag',
267 | body: 'mybody',
268 | icon: 'myicon',
269 | lang: 'en',
270 | dir: 'ltr'
271 | };
272 | onShowSpy = sinon.spy();
273 | onClickSpy = sinon.spy();
274 | onCloseSpy = sinon.spy();
275 | onErrorSpy = sinon.spy();
276 |
277 | it('trigger Notification with specified title and options', () => {
278 | component = ReactTestUtils.renderIntoDocument();
279 | expect(stubConstructor.calledWithNew()).to.be.eql(true);
280 | expect(stubConstructor.calledWith(MY_TITLE, MY_OPTIONS)).to.be.eql(true);
281 | });
282 |
283 | it('call onShow prop when notification is shown', () => {
284 | let n = component._getNotificationInstance('mytag');
285 | n.onshow('showEvent');
286 | expect(onShowSpy.calledOnce).to.be.eql(true);
287 | let args = onShowSpy.args[0];
288 | expect(args[0]).to.be.eql('showEvent');
289 | expect(args[1]).to.be.eql('mytag');
290 | expect(onClickSpy.called).to.be.eql(false);
291 | expect(onCloseSpy.called).to.be.eql(false);
292 | expect(onErrorSpy.called).to.be.eql(false);
293 | });
294 |
295 | it('call onClick prop when notification is clicked', () => {
296 | let n = component._getNotificationInstance('mytag');
297 | n.onclick('clickEvent');
298 | expect(onClickSpy.calledOnce).to.be.eql(true);
299 | let args = onClickSpy.args[0];
300 | expect(args[0]).to.be.eql('clickEvent');
301 | expect(args[1]).to.be.eql('mytag');
302 | });
303 |
304 | it('call onCLose prop when notification is closed', () => {
305 | let n = component._getNotificationInstance('mytag');
306 | n.onclose('closeEvent');
307 | expect(onCloseSpy.calledOnce).to.be.eql(true);
308 | let args = onCloseSpy.args[0];
309 | expect(args[0]).to.be.eql('closeEvent');
310 | expect(args[1]).to.be.eql('mytag');
311 | });
312 |
313 | it('call onError prop when notification throw error', () => {
314 | let n = component._getNotificationInstance('mytag');
315 | n.onerror('errorEvent');
316 | expect(onErrorSpy.calledOnce).to.be.eql(true);
317 | let args = onErrorSpy.args[0];
318 | expect(args[0]).to.be.eql('errorEvent');
319 | expect(args[1]).to.be.eql('mytag');
320 | });
321 | });
322 | describe('test of autoClose timer', () => {
323 | const MY_TITLE = 'mytitle';
324 | const MY_OPTIONS = {
325 | tag: 'mytag',
326 | body: 'mybody',
327 | icon: 'myicon',
328 | lang: 'en',
329 | dir: 'ltr'
330 | };
331 | describe('when `props.timeout` is less than eql 0', () => {
332 | let n;
333 | before(() => {
334 | component = ReactTestUtils.renderIntoDocument();
335 | n = component._getNotificationInstance('mytag');
336 | sinon.stub(n, 'close');
337 | n.onshow('showEvent');
338 | });
339 | after(() => {
340 | n.close.restore();
341 | })
342 | it('will not trigger close automatically', (done) => {
343 | setTimeout(() => {
344 | expect(n.close.called).to.be.eql(false);
345 | done();
346 | }, 200);
347 | })
348 | })
349 | describe('when `props.timeout` is greater than 0', () => {
350 | let n;
351 | before(() => {
352 | component = ReactTestUtils.renderIntoDocument();
353 | n = component._getNotificationInstance('mytag');
354 | sinon.stub(n, 'close');
355 | n.onshow('showEvent');
356 | });
357 | after(() => {
358 | n.close.restore();
359 | });
360 | it('will trigger close automatically', (done) => {
361 | setTimeout(() => {
362 | expect(n.close.called).to.be.eql(true);
363 | done();
364 | }, 200);
365 | })
366 | })
367 | })
368 | describe('when swRegistration prop is defined', () => {
369 | const swRegistrationMock = { showNotification: sinon.stub().resolves({ notification: ee }) }
370 | const MY_TITLE = 'mytitle';
371 | const MY_OPTIONS = {
372 | tag: 'mytag',
373 | body: 'mybody',
374 | icon: 'myicon',
375 | lang: 'en',
376 | dir: 'ltr',
377 | requireInteraction: true,
378 | };
379 |
380 | it('does not trigger Notification but trigger swRegistration.showNotification', () => {
381 | component = ReactTestUtils.renderIntoDocument();
382 | expect(stubConstructor.calledWithNew()).to.be.eql(false);
383 | expect(swRegistrationMock.showNotification.calledWith(MY_TITLE, MY_OPTIONS)).to.be.eql(true);
384 | });
385 | });
386 | });
387 | });
388 | });
389 | });
390 | });
391 | });
392 |
--------------------------------------------------------------------------------