Skip to content

Commit 30d0543

Browse files
author
Takeharu Oshida
committed
Fix #1 Write test with sinon.js
1 parent c30f488 commit 30d0543

File tree

2 files changed

+207
-18
lines changed

2 files changed

+207
-18
lines changed

components/Notification.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ class Notification extends React.Component {
1818

1919
let supported = false;
2020
let granted = false;
21-
if (!('Notification' in window)) {
22-
supported = false;
23-
} else {
21+
if (('Notification' in window) && window.Notification) {
2422
supported = true;
2523
if (window.Notification.permission === PERMISSION_GRANTED) {
2624
granted = true;
2725
}
26+
} else {
27+
supported = false;
2828
}
2929

3030
this.state = {
@@ -66,20 +66,24 @@ class Notification extends React.Component {
6666
opt.tag = 'web-notification-' + seq();
6767
}
6868

69-
let n = new window.Notification(this.props.title, opt);
70-
n.onshow = (e) => {
71-
this.props.onShow(e, opt.tag);
72-
setTimeout(() => {
73-
this.close(opt.tag);
74-
}, this.props.timeout);
75-
};
76-
n.onclick = (e) => {this.props.onClick(e, opt.tag); };
77-
n.onclose = (e) => {this.props.onClose(e, opt.tag); };
78-
n.onerror = (e) => {this.props.onError(e, opt.tag); };
79-
this.notifications[opt.tag] = n;
69+
if (!this.notifications[opt.tag]) {
70+
let n = new window.Notification(this.props.title, opt);
71+
n.onshow = (e) => {
72+
this.props.onShow(e, opt.tag);
73+
setTimeout(() => {
74+
this.close(opt.tag);
75+
}, this.props.timeout);
76+
};
77+
n.onclick = (e) => {this.props.onClick(e, opt.tag); };
78+
n.onclose = (e) => {this.props.onClose(e, opt.tag); };
79+
n.onerror = (e) => {this.props.onError(e, opt.tag); };
80+
81+
this.notifications[opt.tag] = n;
82+
}
8083
}
8184

82-
// is this neccessary?
85+
// return null cause
86+
// Error: Invariant Violation: Notification.render(): A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object.
8387
return (
8488
<input type='hidden' name='dummy-for-react-web-notification' style={{display: 'none'}} />
8589
);
@@ -90,6 +94,11 @@ class Notification extends React.Component {
9094
this.notifications[tag].close();
9195
}
9296
}
97+
98+
// for debug
99+
_getNotificationInstance(tag) {
100+
return this.notifications[tag];
101+
}
93102
}
94103

95104
Notification.propTypes = {

test/components/Notification_spec.js

Lines changed: 183 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
'use strict';
22
import React from 'react/addons';
33
import chai from 'chai';
4+
import sinon from 'sinon';
45
let expect = chai.expect;
6+
import events from 'events'
7+
let EventEmitter = events.EventEmitter;
58
import Notification from '../../components/Notification';
69
const {TestUtils} = React.addons;
710

11+
const PERMISSION_GRANTED = 'granted';
12+
const PERMISSION_DENIED = 'denied';
13+
814
describe('Test of Notification', () => {
915

1016
let component;
@@ -36,11 +42,185 @@ describe('Test of Notification', () => {
3642
});
3743

3844
describe('Handling HTML5 Web Notification API', () => {
39-
it('should Write test', function () {
45+
describe('When Notification is not supported', () => {
46+
let cached, stub;
47+
48+
before(() => {
49+
stub = sinon.stub(window.Notification, 'requestPermission');
50+
cached = window.Notification;
51+
window.Notification = null;
52+
});
53+
54+
after(() => {
55+
window.Notification = cached;
56+
stub.restore();
57+
});
58+
59+
it('should call notSupported prop', () => {
60+
let spy = sinon.spy();
61+
component = TestUtils.renderIntoDocument(<Notification title='test' notSupported={spy}/>);
62+
expect(spy.calledOnce).to.be.eql(true);
63+
expect(stub.called).to.be.eql(false);
64+
});
65+
});
66+
67+
describe('When Notification is supported', () => {
68+
describe('start request permission ', () =>{
69+
describe('When Notification is denied', () => {
70+
describe('Check callbacks', ()=> {
71+
72+
let stub, spy1, spy2;
73+
before(() => {
74+
spy1 = sinon.spy();
75+
spy2 = sinon.spy();
76+
stub = sinon.stub(window.Notification, 'requestPermission', function(cb){
77+
return cb(PERMISSION_DENIED);
78+
});
79+
component = TestUtils.renderIntoDocument(<Notification title='test' notSupported={spy1} onPermissionDenied={spy2}/>);
80+
});
81+
82+
after(() => {
83+
stub.restore();
84+
});
85+
86+
it('should call window.Notification.requestPermission', () => {
87+
expect(stub.calledOnce).to.be.eql(true);
88+
});
89+
90+
it('should call onPermissionDenied prop', () => {
91+
expect(spy1.called).to.be.eql(false);
92+
expect(spy2.calledOnce).to.be.eql(true);
93+
});
94+
});
95+
});
96+
97+
describe('When Notification is granted', () => {
98+
let stub;
99+
before(() => {
100+
stub = sinon.stub(window.Notification, 'requestPermission', function(cb){
101+
return cb(PERMISSION_GRANTED);
102+
});
103+
});
104+
105+
after(() => {
106+
stub.restore();
107+
});
108+
109+
describe('Check callbacks', ()=> {
110+
let spy1, spy2, spy3;
111+
before(() => {
112+
spy1 = sinon.spy();
113+
spy2 = sinon.spy();
114+
spy3 = sinon.spy();
115+
component = TestUtils.renderIntoDocument(<Notification title='test' notSupported={spy1} onPermissionDenied={spy2} onPermissionGranted={spy3}/>);
116+
});
117+
118+
it('should call window.Notification.requestPermission', () => {
119+
expect(stub.called).to.be.eql(true);
120+
});
121+
122+
it('should call onPermissionDenied prop', () => {
123+
expect(spy1.called).to.be.eql(false);
124+
expect(spy2.called).to.be.eql(false);
125+
expect(spy3.calledOnce).to.be.eql(true);
126+
});
127+
});
128+
129+
describe('Handle component properties', () => {
130+
131+
let stubConstructor, onShowSpy, onClickSpy, onCloseSpy, onErrorSpy, ee;
132+
133+
beforeEach(() => {
134+
EventEmitter.prototype.addEventListener = EventEmitter.prototype.addListener;
135+
ee = new EventEmitter();
136+
137+
stubConstructor = sinon.stub(window, 'Notification');
138+
stubConstructor.onFirstCall().returns(ee);
139+
});
140+
141+
afterEach(()=>{
142+
// stub.restore();
143+
stubConstructor.restore();
144+
});
145+
146+
describe('when ignore prop is true', () => {
147+
onShowSpy = sinon.spy();
148+
onClickSpy = sinon.spy();
149+
onCloseSpy = sinon.spy();
150+
onErrorSpy = sinon.spy();
151+
152+
it('does not trigger Notification', () => {
153+
component = TestUtils.renderIntoDocument(<Notification title='test' ignore={true} onShow={onShowSpy} onClick={onClickSpy} onClose={onCloseSpy} onError={onErrorSpy} />);
154+
expect(stubConstructor.calledWithNew()).to.be.eql(false);
155+
expect(onShowSpy.called).to.be.eql(false);
156+
expect(onClickSpy.called).to.be.eql(false);
157+
expect(onCloseSpy.called).to.be.eql(false);
158+
expect(onErrorSpy.called).to.be.eql(false);
159+
});
160+
});
161+
162+
describe('when ignore prop is false', () => {
163+
const MY_TITLE = 'mytitle';
164+
const MY_OPTIONS = {
165+
tag: 'mytag',
166+
body: 'mybody',
167+
icon: 'myicon',
168+
lang: 'en',
169+
dir: 'ltr'
170+
};
171+
onShowSpy = sinon.spy();
172+
onClickSpy = sinon.spy();
173+
onCloseSpy = sinon.spy();
174+
onErrorSpy = sinon.spy();
175+
176+
it('trigger Notification with specified title and options', () => {
177+
component = TestUtils.renderIntoDocument(<Notification title={MY_TITLE} options={MY_OPTIONS} ignore={false} onShow={onShowSpy} onClick={onClickSpy} onClose={onCloseSpy} onError={onErrorSpy} />);
178+
expect(stubConstructor.calledWithNew()).to.be.eql(true);
179+
expect(stubConstructor.calledWith(MY_TITLE, MY_OPTIONS)).to.be.eql(true);
180+
});
181+
182+
it('call onShow prop when notification is shown', () => {
183+
let n = component._getNotificationInstance('mytag');
184+
n.onshow('showEvent');
185+
expect(onShowSpy.calledOnce).to.be.eql(true);
186+
let args = onShowSpy.args[0];
187+
expect(args[0]).to.be.eql('showEvent');
188+
expect(args[1]).to.be.eql('mytag');
189+
expect(onClickSpy.called).to.be.eql(false);
190+
expect(onCloseSpy.called).to.be.eql(false);
191+
expect(onErrorSpy.called).to.be.eql(false);
192+
});
193+
194+
it('call onClick prop when notification is clicked', () => {
195+
let n = component._getNotificationInstance('mytag');
196+
n.onclick('clickEvent');
197+
expect(onClickSpy.calledOnce).to.be.eql(true);
198+
let args = onClickSpy.args[0];
199+
expect(args[0]).to.be.eql('clickEvent');
200+
expect(args[1]).to.be.eql('mytag');
201+
});
40202

41-
// See https://github.com/alexgibson/notify.js/blob/master/test/tests.js
203+
it('call onCLose prop when notification is closed', () => {
204+
let n = component._getNotificationInstance('mytag');
205+
n.onclose('closeEvent');
206+
expect(onCloseSpy.calledOnce).to.be.eql(true);
207+
let args = onCloseSpy.args[0];
208+
expect(args[0]).to.be.eql('closeEvent');
209+
expect(args[1]).to.be.eql('mytag');
210+
});
42211

43-
expect(false).to.be.eql(true);
212+
it('call onError prop when notification throw error', () => {
213+
let n = component._getNotificationInstance('mytag');
214+
n.onerror('errorEvent');
215+
expect(onErrorSpy.calledOnce).to.be.eql(true);
216+
let args = onErrorSpy.args[0];
217+
expect(args[0]).to.be.eql('errorEvent');
218+
expect(args[1]).to.be.eql('mytag');
219+
});
220+
});
221+
});
222+
});
223+
});
44224
});
45225
});
46226
});

0 commit comments

Comments
 (0)