UNPKG

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