UNPKG

10.3 kBJavaScriptView Raw
1'use strict'
2
3/* global describe, beforeEach, afterEach, it */
4/* eslint-disable no-unused-expressions */
5
6// Assertions and Stubbing
7const chai = require('chai')
8const sinon = require('sinon')
9chai.use(require('sinon-chai'))
10
11const expect = chai.expect
12
13// Hubot classes
14const Brain = require('../src/brain')
15const User = require('../src/user')
16
17describe('Brain', function () {
18 beforeEach(function () {
19 this.clock = sinon.useFakeTimers()
20 this.mockRobot = {
21 emit () {},
22 on () {}
23 }
24
25 // This *should* be callsArgAsync to match the 'on' API, but that makes
26 // the tests more complicated and seems irrelevant.
27 sinon.stub(this.mockRobot, 'on').withArgs('running').callsArg(1)
28
29 this.brain = new Brain(this.mockRobot)
30
31 this.user1 = this.brain.userForId('1', {name: 'Guy One'})
32 this.user2 = this.brain.userForId('2', {name: 'Guy One Two'})
33 this.user3 = this.brain.userForId('3', {name: 'Girl Three'})
34 })
35
36 afterEach(function () {
37 this.clock.restore()
38 })
39
40 describe('Unit Tests', function () {
41 describe('#mergeData', function () {
42 it('performs a proper merge with the new data taking precedent', function () {
43 this.brain.data = {
44 1: 'old',
45 2: 'old'
46 }
47
48 this.brain.mergeData({2: 'new'})
49
50 expect(this.brain.data).to.deep.equal({
51 1: 'old',
52 2: 'new'
53 })
54 })
55
56 it('emits a loaded event with the new data', function () {
57 sinon.spy(this.brain, 'emit')
58 this.brain.mergeData({})
59 expect(this.brain.emit).to.have.been.calledWith('loaded', this.brain.data)
60 })
61 })
62
63 describe('#save', () => it('emits a save event', function () {
64 sinon.spy(this.brain, 'emit')
65 this.brain.save()
66 expect(this.brain.emit).to.have.been.calledWith('save', this.brain.data)
67 }))
68
69 describe('#resetSaveInterval', () => it('updates the auto-save interval', function () {
70 sinon.spy(this.brain, 'save')
71 // default is 5s
72 this.brain.resetSaveInterval(10)
73 // make sure autosave is on
74 this.brain.setAutoSave(true)
75
76 this.clock.tick(5000)
77 // old interval has passed
78 expect(this.brain.save).to.not.have.been.called
79 this.clock.tick(5000)
80 // new interval has passed
81 expect(this.brain.save).to.have.been.calledOnce
82 }))
83
84 describe('#close', function () {
85 it('saves', function () {
86 sinon.spy(this.brain, 'save')
87 this.brain.close()
88 expect(this.brain.save).to.have.been.calledOnce
89 })
90
91 it('emits a close event', function () {
92 sinon.spy(this.brain, 'emit')
93 this.brain.close()
94 expect(this.brain.emit).to.have.been.calledWith('close')
95 })
96
97 it('saves before emitting the close event', function () {
98 sinon.spy(this.brain, 'save')
99 sinon.spy(this.brain, 'emit').withArgs('close')
100 this.brain.close()
101 expect(this.brain.save).to.have.been.calledBefore(this.brain.emit)
102 })
103
104 it('stops auto-saving', function () {
105 // make sure autosave is on
106 this.brain.setAutoSave(true)
107 this.brain.close()
108
109 // set up the spy after because 'close' calls 'save'
110 sinon.spy(this.brain, 'save')
111
112 this.clock.tick(2 * 5000)
113 expect(this.brain.save).to.not.have.been.called
114 })
115 })
116
117 describe('#get', function () {
118 it('returns the saved value', function () {
119 this.brain.data._private['test-key'] = 'value'
120 expect(this.brain.get('test-key')).to.equal('value')
121 })
122
123 it('returns null if object is not found', function () {
124 expect(this.brain.get('not a real key')).to.be.null
125 })
126 })
127
128 describe('#set', function () {
129 it('saves the value', function () {
130 this.brain.set('test-key', 'value')
131 expect(this.brain.data._private['test-key']).to.equal('value')
132 })
133
134 it('sets multiple keys at once if an object is provided', function () {
135 this.brain.data._private = {
136 key1: 'val1',
137 key2: 'val1'
138 }
139
140 this.brain.set({
141 key2: 'val2',
142 key3: 'val2'
143 })
144
145 expect(this.brain.data._private).to.deep.equal({
146 key1: 'val1',
147 key2: 'val2',
148 key3: 'val2'
149 })
150 })
151
152 // Unable to understand why this behavior is needed, but adding a test
153 // case to protect it
154 it('emits loaded', function () {
155 sinon.spy(this.brain, 'emit')
156 this.brain.set('test-key', 'value')
157 expect(this.brain.emit).to.have.been.calledWith('loaded', this.brain.data)
158 })
159
160 it('returns the brain', function () {
161 expect(this.brain.set('test-key', 'value')).to.equal(this.brain)
162 })
163 })
164
165 describe('#remove', () => it('removes the specified key', function () {
166 this.brain.data._private['test-key'] = 'value'
167 this.brain.remove('test-key')
168 expect(this.brain.data._private).to.not.include.keys('test-key')
169 }))
170
171 describe('#userForId', function () {
172 it('returns the user object', function () {
173 expect(this.brain.userForId(1)).to.equal(this.user1)
174 })
175
176 it('does an exact match', function () {
177 const user4 = this.brain.userForId('FOUR')
178 expect(this.brain.userForId('four')).to.not.equal(user4)
179 })
180
181 // Cannot understand why this behavior is needed, but adding a test case
182 // to protect it
183 it('recreates the user if the room option differs from the user object', function () {
184 expect(this.brain.userForId(1).room).to.be.undefined
185
186 // undefined -> having a room
187 const newUser1 = this.brain.userForId(1, { room: 'room1' })
188 expect(newUser1).to.not.equal(this.user1)
189
190 // changing the room
191 const newUser2 = this.brain.userForId(1, { room: 'room2' })
192 expect(newUser2).to.not.equal(newUser1)
193 })
194
195 describe('when there is no matching user ID', function () {
196 it('creates a new User', function () {
197 expect(this.brain.data.users).to.not.include.key('all-new-user')
198 const newUser = this.brain.userForId('all-new-user')
199 expect(newUser).to.be.instanceof(User)
200 expect(newUser.id).to.equal('all-new-user')
201 expect(this.brain.data.users).to.include.key('all-new-user')
202 })
203
204 it('passes the provided options to the new User', function () {
205 const newUser = this.brain.userForId('all-new-user', {name: 'All New User', prop: 'mine'})
206 expect(newUser.name).to.equal('All New User')
207 expect(newUser.prop).to.equal('mine')
208 })
209 })
210 })
211
212 describe('#userForName', function () {
213 it('returns the user with a matching name', function () {
214 expect(this.brain.userForName('Guy One')).to.equal(this.user1)
215 })
216
217 it('does a case-insensitive match', function () {
218 expect(this.brain.userForName('guy one')).to.equal(this.user1)
219 })
220
221 it('returns null if no user matches', function () {
222 expect(this.brain.userForName('not a real user')).to.be.null
223 })
224 })
225
226 describe('#usersForRawFuzzyName', function () {
227 it('does a case-insensitive match', function () {
228 expect(this.brain.usersForRawFuzzyName('guy')).to.have.members([this.user1, this.user2])
229 })
230
231 it('returns all matching users (prefix match) when there is not an exact match (case-insensitive)', function () {
232 expect(this.brain.usersForRawFuzzyName('Guy')).to.have.members([this.user1, this.user2])
233 })
234
235 it('returns all matching users (prefix match) when there is an exact match (case-insensitive)', function () {
236 // Matched case
237 expect(this.brain.usersForRawFuzzyName('Guy One')).to.deep.equal([this.user1, this.user2])
238 // Mismatched case
239 expect(this.brain.usersForRawFuzzyName('guy one')).to.deep.equal([this.user1, this.user2])
240 })
241
242 it('returns an empty array if no users match', function () {
243 const result = this.brain.usersForRawFuzzyName('not a real user')
244 expect(result).to.be.an('array')
245 expect(result).to.be.empty
246 })
247 })
248
249 describe('#usersForFuzzyName', function () {
250 it('does a case-insensitive match', function () {
251 expect(this.brain.usersForFuzzyName('guy')).to.have.members([this.user1, this.user2])
252 })
253
254 it('returns all matching users (prefix match) when there is not an exact match', function () {
255 expect(this.brain.usersForFuzzyName('Guy')).to.have.members([this.user1, this.user2])
256 })
257
258 it('returns just the user when there is an exact match (case-insensitive)', function () {
259 // Matched case
260 expect(this.brain.usersForFuzzyName('Guy One')).to.deep.equal([this.user1])
261 // Mismatched case
262 expect(this.brain.usersForFuzzyName('guy one')).to.deep.equal([this.user1])
263 })
264
265 it('returns an empty array if no users match', function () {
266 const result = this.brain.usersForFuzzyName('not a real user')
267 expect(result).to.be.an('array')
268 expect(result).to.be.empty
269 })
270 })
271 })
272
273 describe('Auto-Save', function () {
274 it('is on by default', function () {
275 expect(this.brain.autoSave).to.equal(true)
276 })
277
278 it('automatically saves every 5 seconds when turned on', function () {
279 sinon.spy(this.brain, 'save')
280
281 this.brain.setAutoSave(true)
282
283 this.clock.tick(5000)
284 expect(this.brain.save).to.have.been.called
285 })
286
287 it('does not auto-save when turned off', function () {
288 sinon.spy(this.brain, 'save')
289
290 this.brain.setAutoSave(false)
291
292 this.clock.tick(2 * 5000)
293 expect(this.brain.save).to.not.have.been.called
294 })
295 })
296
297 describe('User Searching', function () {
298 it('finds users by ID', function () {
299 expect(this.brain.userForId('1')).to.equal(this.user1)
300 })
301
302 it('finds users by exact name', function () {
303 expect(this.brain.userForName('Guy One')).to.equal(this.user1)
304 })
305
306 it('finds users by fuzzy name (prefix match)', function () {
307 const result = this.brain.usersForFuzzyName('Guy')
308 expect(result).to.have.members([this.user1, this.user2])
309 expect(result).to.not.have.members([this.user3])
310 })
311 })
312})