UNPKG

emailjs-imap-client

Version:
1,239 lines (1,076 loc) 32.9 kB
/* eslint-disable no-unused-expressions */ import ImapClient, { STATE_SELECTED, STATE_LOGOUT } from './client' import { parser } from 'emailjs-imap-handler' import { toTypedArray, LOG_LEVEL_NONE as logLevel } from './common' describe('browserbox unit tests', () => { var br beforeEach(() => { const auth = { user: 'baldrian', pass: 'sleeper.de' } br = new ImapClient('somehost', 1234, { auth, logLevel }) br.client.socket = { send: () => { }, upgradeToSecure: () => { } } }) describe('#_onIdle', () => { it('should call enterIdle', () => { sinon.stub(br, 'enterIdle') br._authenticated = true br._enteredIdle = false br._onIdle() expect(br.enterIdle.callCount).to.equal(1) }) it('should not call enterIdle', () => { sinon.stub(br, 'enterIdle') br._enteredIdle = true br._onIdle() expect(br.enterIdle.callCount).to.equal(0) }) }) describe('#openConnection', () => { beforeEach(() => { sinon.stub(br.client, 'connect') sinon.stub(br.client, 'close') sinon.stub(br.client, 'enqueueCommand') }) it('should open connection', () => { br.client.connect.returns(Promise.resolve()) br.client.enqueueCommand.returns(Promise.resolve({ capability: ['capa1', 'capa2'] })) setTimeout(() => br.client.onready(), 0) return br.openConnection().then(() => { expect(br.client.connect.calledOnce).to.be.true expect(br.client.enqueueCommand.calledOnce).to.be.true expect(br._capability.length).to.equal(2) expect(br._capability[0]).to.equal('capa1') expect(br._capability[1]).to.equal('capa2') }) }) }) describe('#connect', () => { beforeEach(() => { sinon.stub(br.client, 'connect') sinon.stub(br.client, 'close') sinon.stub(br, 'updateCapability') sinon.stub(br, 'upgradeConnection') sinon.stub(br, 'updateId') sinon.stub(br, 'login') sinon.stub(br, 'compressConnection') }) it('should connect', () => { br.client.connect.returns(Promise.resolve()) br.updateCapability.returns(Promise.resolve()) br.upgradeConnection.returns(Promise.resolve()) br.updateId.returns(Promise.resolve()) br.login.returns(Promise.resolve()) br.compressConnection.returns(Promise.resolve()) setTimeout(() => br.client.onready(), 0) return br.connect().then(() => { expect(br.client.connect.calledOnce).to.be.true expect(br.updateCapability.calledOnce).to.be.true expect(br.upgradeConnection.calledOnce).to.be.true expect(br.updateId.calledOnce).to.be.true expect(br.login.calledOnce).to.be.true expect(br.compressConnection.calledOnce).to.be.true }) }) it('should fail to login', (done) => { br.client.connect.returns(Promise.resolve()) br.updateCapability.returns(Promise.resolve()) br.upgradeConnection.returns(Promise.resolve()) br.updateId.returns(Promise.resolve()) br.login.throws(new Error()) setTimeout(() => br.client.onready(), 0) br.connect().catch((err) => { expect(err).to.exist expect(br.client.connect.calledOnce).to.be.true expect(br.client.close.calledOnce).to.be.true expect(br.updateCapability.calledOnce).to.be.true expect(br.upgradeConnection.calledOnce).to.be.true expect(br.updateId.calledOnce).to.be.true expect(br.login.calledOnce).to.be.true expect(br.compressConnection.called).to.be.false done() }) }) it('should timeout', (done) => { br.client.connect.returns(Promise.resolve()) br.timeoutConnection = 1 br.connect().catch((err) => { expect(err).to.exist expect(br.client.connect.calledOnce).to.be.true expect(br.client.close.calledOnce).to.be.true expect(br.updateCapability.called).to.be.false expect(br.upgradeConnection.called).to.be.false expect(br.updateId.called).to.be.false expect(br.login.called).to.be.false expect(br.compressConnection.called).to.be.false done() }) }) }) describe('#close', () => { it('should force-close', () => { sinon.stub(br.client, 'close').returns(Promise.resolve()) return br.close().then(() => { expect(br._state).to.equal(STATE_LOGOUT) expect(br.client.close.calledOnce).to.be.true }) }) }) describe('#exec', () => { beforeEach(() => { sinon.stub(br, 'breakIdle') }) it('should send string command', () => { sinon.stub(br.client, 'enqueueCommand').returns(Promise.resolve({})) return br.exec('TEST').then((res) => { expect(res).to.deep.equal({}) expect(br.client.enqueueCommand.args[0][0]).to.equal('TEST') }) }) it('should update capability from response', () => { sinon.stub(br.client, 'enqueueCommand').returns(Promise.resolve({ capability: ['A', 'B'] })) return br.exec('TEST').then((res) => { expect(res).to.deep.equal({ capability: ['A', 'B'] }) expect(br._capability).to.deep.equal(['A', 'B']) }) }) }) describe('#enterIdle', () => { it('should periodically send NOOP if IDLE not supported', (done) => { sinon.stub(br, 'exec').callsFake((command) => { expect(command).to.equal('NOOP') done() }) br._capability = [] br._selectedMailbox = 'FOO' br.timeoutNoop = 1 br.enterIdle() }) it('should periodically send NOOP if no mailbox selected', (done) => { sinon.stub(br, 'exec').callsFake((command) => { expect(command).to.equal('NOOP') done() }) br._capability = ['IDLE'] br._selectedMailbox = undefined br.timeoutNoop = 1 br.enterIdle() }) it('should break IDLE after timeout', (done) => { sinon.stub(br.client, 'enqueueCommand') sinon.stub(br.client.socket, 'send').callsFake((payload) => { expect(br.client.enqueueCommand.args[0][0].command).to.equal('IDLE') expect([].slice.call(new Uint8Array(payload))).to.deep.equal([0x44, 0x4f, 0x4e, 0x45, 0x0d, 0x0a]) done() }) br._capability = ['IDLE'] br._selectedMailbox = 'FOO' br.timeoutIdle = 1 br.enterIdle() }) }) describe('#breakIdle', () => { it('should send DONE to socket', () => { sinon.stub(br.client.socket, 'send') br._enteredIdle = 'IDLE' br.breakIdle() expect([].slice.call(new Uint8Array(br.client.socket.send.args[0][0]))).to.deep.equal([0x44, 0x4f, 0x4e, 0x45, 0x0d, 0x0a]) }) }) describe('#upgradeConnection', () => { it('should do nothing if already secured', () => { br.client.secureMode = true br._capability = ['starttls'] return br.upgradeConnection() }) it('should do nothing if STARTTLS not available', () => { br.client.secureMode = false br._capability = [] return br.upgradeConnection() }) it('should run STARTTLS', () => { sinon.stub(br.client, 'upgrade') sinon.stub(br, 'exec').withArgs('STARTTLS').returns(Promise.resolve()) sinon.stub(br, 'updateCapability').returns(Promise.resolve()) br._capability = ['STARTTLS'] return br.upgradeConnection().then(() => { expect(br.client.upgrade.callCount).to.equal(1) expect(br._capability.length).to.equal(0) }) }) }) describe('#updateCapability', () => { beforeEach(() => { sinon.stub(br, 'exec') }) it('should do nothing if capability is set', () => { br._capability = ['abc'] return br.updateCapability() }) it('should run CAPABILITY if capability not set', () => { br.exec.returns(Promise.resolve()) br._capability = [] return br.updateCapability().then(() => { expect(br.exec.args[0][0]).to.equal('CAPABILITY') }) }) it('should force run CAPABILITY', () => { br.exec.returns(Promise.resolve()) br._capability = ['abc'] return br.updateCapability(true).then(() => { expect(br.exec.args[0][0]).to.equal('CAPABILITY') }) }) it('should do nothing if connection is not yet upgraded', () => { br._capability = [] br.client.secureMode = false br._requireTLS = true br.updateCapability() }) }) describe('#listNamespaces', () => { beforeEach(() => { sinon.stub(br, 'exec') }) it('should run NAMESPACE if supported', () => { br.exec.returns(Promise.resolve({ payload: { NAMESPACE: [{ attributes: [ [ [{ type: 'STRING', value: 'INBOX.' }, { type: 'STRING', value: '.' }] ], null, null ] }] } })) br._capability = ['NAMESPACE'] return br.listNamespaces().then((namespaces) => { expect(namespaces).to.deep.equal({ personal: [{ prefix: 'INBOX.', delimiter: '.' }], users: false, shared: false }) expect(br.exec.args[0][0]).to.equal('NAMESPACE') expect(br.exec.args[0][1]).to.equal('NAMESPACE') }) }) it('should do nothing if not supported', () => { br._capability = [] return br.listNamespaces().then((namespaces) => { expect(namespaces).to.be.false expect(br.exec.callCount).to.equal(0) }) }) }) describe('#compressConnection', () => { beforeEach(() => { sinon.stub(br, 'exec') sinon.stub(br.client, 'enableCompression') }) it('should run COMPRESS=DEFLATE if supported', () => { br.exec.withArgs({ command: 'COMPRESS', attributes: [{ type: 'ATOM', value: 'DEFLATE' }] }).returns(Promise.resolve({})) br._enableCompression = true br._capability = ['COMPRESS=DEFLATE'] return br.compressConnection().then(() => { expect(br.exec.callCount).to.equal(1) expect(br.client.enableCompression.callCount).to.equal(1) }) }) it('should do nothing if not supported', () => { br._capability = [] return br.compressConnection().then(() => { expect(br.exec.callCount).to.equal(0) }) }) it('should do nothing if not enabled', () => { br._enableCompression = false br._capability = ['COMPRESS=DEFLATE'] return br.compressConnection().then(() => { expect(br.exec.callCount).to.equal(0) }) }) }) describe('#login', () => { it('should call LOGIN', () => { sinon.stub(br, 'exec').returns(Promise.resolve({})) sinon.stub(br, 'updateCapability').returns(Promise.resolve(true)) return br.login({ user: 'u1', pass: 'p1' }).then(() => { expect(br.exec.callCount).to.equal(1) expect(br.exec.args[0][0]).to.deep.equal({ command: 'login', attributes: [{ type: 'STRING', value: 'u1' }, { type: 'STRING', value: 'p1', sensitive: true }] }) }) }) it('should call XOAUTH2', () => { sinon.stub(br, 'exec').returns(Promise.resolve({})) sinon.stub(br, 'updateCapability').returns(Promise.resolve(true)) br._capability = ['AUTH=XOAUTH2'] br.login({ user: 'u1', xoauth2: 'abc' }).then(() => { expect(br.exec.callCount).to.equal(1) expect(br.exec.args[0][0]).to.deep.equal({ command: 'AUTHENTICATE', attributes: [{ type: 'ATOM', value: 'XOAUTH2' }, { type: 'ATOM', value: 'dXNlcj11MQFhdXRoPUJlYXJlciBhYmMBAQ==', sensitive: true }] }) }) }) }) describe('#updateId', () => { beforeEach(() => { sinon.stub(br, 'exec') }) it('should not nothing if not supported', () => { br._capability = [] return br.updateId({ a: 'b', c: 'd' }).then(() => { expect(br.serverId).to.be.false }) }) it('should send NIL', () => { br.exec.withArgs({ command: 'ID', attributes: [ null ] }).returns(Promise.resolve({ payload: { ID: [{ attributes: [ null ] }] } })) br._capability = ['ID'] return br.updateId(null).then(() => { expect(br.serverId).to.deep.equal({}) }) }) it('should exhange ID values', () => { br.exec.withArgs({ command: 'ID', attributes: [ ['ckey1', 'cval1', 'ckey2', 'cval2'] ] }).returns(Promise.resolve({ payload: { ID: [{ attributes: [ [{ value: 'skey1' }, { value: 'sval1' }, { value: 'skey2' }, { value: 'sval2' }] ] }] } })) br._capability = ['ID'] return br.updateId({ ckey1: 'cval1', ckey2: 'cval2' }).then(() => { expect(br.serverId).to.deep.equal({ skey1: 'sval1', skey2: 'sval2' }) }) }) }) describe('#listMailboxes', () => { beforeEach(() => { sinon.stub(br, 'exec') }) it('should call LIST and LSUB in sequence', () => { br.exec.withArgs({ command: 'LIST', attributes: ['', '*'] }).returns(Promise.resolve({ payload: { LIST: [false] } })) br.exec.withArgs({ command: 'LSUB', attributes: ['', '*'] }).returns(Promise.resolve({ payload: { LSUB: [false] } })) return br.listMailboxes().then((tree) => { expect(tree).to.exist }) }) it('should not die on NIL separators', () => { br.exec.withArgs({ command: 'LIST', attributes: ['', '*'] }).returns(Promise.resolve({ payload: { LIST: [ parser(toTypedArray('* LIST (\\NoInferiors) NIL "INBOX"')) ] } })) br.exec.withArgs({ command: 'LSUB', attributes: ['', '*'] }).returns(Promise.resolve({ payload: { LSUB: [ parser(toTypedArray('* LSUB (\\NoInferiors) NIL "INBOX"')) ] } })) return br.listMailboxes().then((tree) => { expect(tree).to.exist }) }) }) describe('#createMailbox', () => { beforeEach(() => { sinon.stub(br, 'exec') }) it('should call CREATE with a string payload', () => { // The spec allows unquoted ATOM-style syntax too, but for // simplicity we always generate a string even if it could be // expressed as an atom. br.exec.withArgs({ command: 'CREATE', attributes: ['mailboxname'] }).returns(Promise.resolve()) return br.createMailbox('mailboxname').then(() => { expect(br.exec.callCount).to.equal(1) }) }) it('should call mutf7 encode the argument', () => { // From RFC 3501 br.exec.withArgs({ command: 'CREATE', attributes: ['~peter/mail/&U,BTFw-/&ZeVnLIqe-'] }).returns(Promise.resolve()) return br.createMailbox('~peter/mail/\u53f0\u5317/\u65e5\u672c\u8a9e').then(() => { expect(br.exec.callCount).to.equal(1) }) }) it('should treat an ALREADYEXISTS response as success', () => { var fakeErr = { code: 'ALREADYEXISTS' } br.exec.withArgs({ command: 'CREATE', attributes: ['mailboxname'] }).returns(Promise.reject(fakeErr)) return br.createMailbox('mailboxname').then(() => { expect(br.exec.callCount).to.equal(1) }) }) }) describe('#deleteMailbox', () => { beforeEach(() => { sinon.stub(br, 'exec') }) it('should call DELETE with a string payload', () => { br.exec.withArgs({ command: 'DELETE', attributes: ['mailboxname'] }).returns(Promise.resolve()) return br.deleteMailbox('mailboxname').then(() => { expect(br.exec.callCount).to.equal(1) }) }) it('should call mutf7 encode the argument', () => { // From RFC 3501 br.exec.withArgs({ command: 'DELETE', attributes: ['~peter/mail/&U,BTFw-/&ZeVnLIqe-'] }).returns(Promise.resolve()) return br.deleteMailbox('~peter/mail/\u53f0\u5317/\u65e5\u672c\u8a9e').then(() => { expect(br.exec.callCount).to.equal(1) }) }) }) describe.skip('#listMessages', () => { beforeEach(() => { sinon.stub(br, 'exec') sinon.stub(br, '_buildFETCHCommand') sinon.stub(br, '_parseFETCH') }) it('should call FETCH', () => { br.exec.returns(Promise.resolve('abc')) br._buildFETCHCommand.withArgs(['1:2', ['uid', 'flags'], { byUid: true }]).returns({}) return br.listMessages('INBOX', '1:2', ['uid', 'flags'], { byUid: true }).then(() => { expect(br._buildFETCHCommand.callCount).to.equal(1) expect(br._parseFETCH.withArgs('abc').callCount).to.equal(1) }) }) }) describe.skip('#search', () => { beforeEach(() => { sinon.stub(br, 'exec') sinon.stub(br, '_buildSEARCHCommand') sinon.stub(br, '_parseSEARCH') }) it('should call SEARCH', () => { br.exec.returns(Promise.resolve('abc')) br._buildSEARCHCommand.withArgs({ uid: 1 }, { byUid: true }).returns({}) return br.search('INBOX', { uid: 1 }, { byUid: true }).then(() => { expect(br._buildSEARCHCommand.callCount).to.equal(1) expect(br.exec.callCount).to.equal(1) expect(br._parseSEARCH.withArgs('abc').callCount).to.equal(1) }) }) }) describe('#upload', () => { beforeEach(() => { sinon.stub(br, 'exec') }) it('should call APPEND with custom flag', () => { br.exec.returns(Promise.resolve()) return br.upload('mailbox', 'this is a message', { flags: ['\\$MyFlag'] }).then(() => { expect(br.exec.callCount).to.equal(1) }) }) it('should call APPEND w/o flags', () => { br.exec.returns(Promise.resolve()) return br.upload('mailbox', 'this is a message').then(() => { expect(br.exec.callCount).to.equal(1) }) }) }) describe.skip('#setFlags', () => { beforeEach(() => { sinon.stub(br, 'exec') sinon.stub(br, '_buildSTORECommand') sinon.stub(br, '_parseFETCH') }) it('should call STORE', () => { br.exec.returns(Promise.resolve('abc')) br._buildSTORECommand.withArgs('1:2', 'FLAGS', ['\\Seen', '$MyFlag'], { byUid: true }).returns({}) return br.setFlags('INBOX', '1:2', ['\\Seen', '$MyFlag'], { byUid: true }).then(() => { expect(br.exec.callCount).to.equal(1) expect(br._parseFETCH.withArgs('abc').callCount).to.equal(1) }) }) }) describe.skip('#store', () => { beforeEach(() => { sinon.stub(br, 'exec') sinon.stub(br, '_buildSTORECommand') sinon.stub(br, '_parseFETCH') }) it('should call STORE', () => { br.exec.returns(Promise.resolve('abc')) br._buildSTORECommand.withArgs('1:2', '+X-GM-LABELS', ['\\Sent', '\\Junk'], { byUid: true }).returns({}) return br.store('INBOX', '1:2', '+X-GM-LABELS', ['\\Sent', '\\Junk'], { byUid: true }).then(() => { expect(br._buildSTORECommand.callCount).to.equal(1) expect(br.exec.callCount).to.equal(1) expect(br._parseFETCH.withArgs('abc').callCount).to.equal(1) }) }) }) describe('#deleteMessages', () => { beforeEach(() => { sinon.stub(br, 'setFlags') sinon.stub(br, 'exec') }) it('should call UID EXPUNGE', () => { br.exec.withArgs({ command: 'UID EXPUNGE', attributes: [{ type: 'sequence', value: '1:2' }] }).returns(Promise.resolve('abc')) br.setFlags.withArgs('INBOX', '1:2', { add: '\\Deleted' }).returns(Promise.resolve()) br._capability = ['UIDPLUS'] return br.deleteMessages('INBOX', '1:2', { byUid: true }).then(() => { expect(br.exec.callCount).to.equal(1) }) }) it('should call EXPUNGE', () => { br.exec.withArgs('EXPUNGE').returns(Promise.resolve('abc')) br.setFlags.withArgs('INBOX', '1:2', { add: '\\Deleted' }).returns(Promise.resolve()) br._capability = [] return br.deleteMessages('INBOX', '1:2', { byUid: true }).then(() => { expect(br.exec.callCount).to.equal(1) }) }) }) describe('#copyMessages', () => { beforeEach(() => { sinon.stub(br, 'exec') }) it('should call COPY', () => { br.exec.withArgs({ command: 'UID COPY', attributes: [{ type: 'sequence', value: '1:2' }, { type: 'atom', value: '[Gmail]/Trash' }] }).returns(Promise.resolve({ copyuid: ['1', '1:2', '4,3'] })) return br.copyMessages('INBOX', '1:2', '[Gmail]/Trash', { byUid: true }).then((response) => { expect(response).to.deep.equal({ srcSeqSet: '1:2', destSeqSet: '4,3' }) expect(br.exec.callCount).to.equal(1) }) }) }) describe('#moveMessages', () => { beforeEach(() => { sinon.stub(br, 'exec') sinon.stub(br, 'copyMessages') sinon.stub(br, 'deleteMessages') }) it('should call MOVE if supported', () => { br.exec.withArgs({ command: 'UID MOVE', attributes: [{ type: 'sequence', value: '1:2' }, { type: 'atom', value: '[Gmail]/Trash' }] }, ['OK']).returns(Promise.resolve('abc')) br._capability = ['MOVE'] return br.moveMessages('INBOX', '1:2', '[Gmail]/Trash', { byUid: true }).then(() => { expect(br.exec.callCount).to.equal(1) }) }) it('should fallback to copy+expunge', () => { br.copyMessages.withArgs('INBOX', '1:2', '[Gmail]/Trash', { byUid: true }).returns(Promise.resolve()) br.deleteMessages.withArgs('1:2', { byUid: true }).returns(Promise.resolve()) br._capability = [] return br.moveMessages('INBOX', '1:2', '[Gmail]/Trash', { byUid: true }).then(() => { expect(br.deleteMessages.callCount).to.equal(1) }) }) }) describe('#_shouldSelectMailbox', () => { it('should return true when ctx is undefined', () => { expect(br._shouldSelectMailbox('path')).to.be.true }) it('should return true when a different path is queued', () => { sinon.stub(br.client, 'getPreviouslyQueued').returns({ request: { command: 'SELECT', attributes: [{ type: 'STRING', value: 'queued path' }] } }) expect(br._shouldSelectMailbox('path', {})).to.be.true }) it('should return false when the same path is queued', () => { sinon.stub(br.client, 'getPreviouslyQueued').returns({ request: { command: 'SELECT', attributes: [{ type: 'STRING', value: 'queued path' }] } }) expect(br._shouldSelectMailbox('queued path', {})).to.be.false }) }) describe('#selectMailbox', () => { const path = '[Gmail]/Trash' beforeEach(() => { sinon.stub(br, 'exec') }) it('should run SELECT', () => { br.exec.withArgs({ command: 'SELECT', attributes: [{ type: 'STRING', value: path }] }).returns(Promise.resolve({ code: 'READ-WRITE' })) return br.selectMailbox(path).then(() => { expect(br.exec.callCount).to.equal(1) expect(br._state).to.equal(STATE_SELECTED) }) }) it('should run SELECT with CONDSTORE', () => { br.exec.withArgs({ command: 'SELECT', attributes: [{ type: 'STRING', value: path }, [{ type: 'ATOM', value: 'CONDSTORE' }] ] }).returns(Promise.resolve({ code: 'READ-WRITE' })) br._capability = ['CONDSTORE'] return br.selectMailbox(path, { condstore: true }).then(() => { expect(br.exec.callCount).to.equal(1) expect(br._state).to.equal(STATE_SELECTED) }) }) describe('should emit onselectmailbox before selectMailbox is resolved', () => { beforeEach(() => { br.exec.returns(Promise.resolve({ code: 'READ-WRITE' })) }) it('when it returns a promise', () => { var promiseResolved = false br.onselectmailbox = () => new Promise((resolve) => { resolve() promiseResolved = true }) var onselectmailboxSpy = sinon.spy(br, 'onselectmailbox') return br.selectMailbox(path).then(() => { expect(onselectmailboxSpy.withArgs(path).callCount).to.equal(1) expect(promiseResolved).to.equal(true) }) }) it('when it does not return a promise', () => { br.onselectmailbox = () => { } var onselectmailboxSpy = sinon.spy(br, 'onselectmailbox') return br.selectMailbox(path).then(() => { expect(onselectmailboxSpy.withArgs(path).callCount).to.equal(1) }) }) }) it('should emit onclosemailbox', () => { let called = false br.exec.returns(Promise.resolve('abc')).returns(Promise.resolve({ code: 'READ-WRITE' })) br.onclosemailbox = (path) => { expect(path).to.equal('yyy') called = true } br._selectedMailbox = 'yyy' return br.selectMailbox(path).then(() => { expect(called).to.be.true }) }) }) describe('#hasCapability', () => { it('should detect existing capability', () => { br._capability = ['ZZZ'] expect(br.hasCapability('zzz')).to.be.true }) it('should detect non existing capability', () => { br._capability = ['ZZZ'] expect(br.hasCapability('ooo')).to.be.false expect(br.hasCapability()).to.be.false }) }) describe('#_untaggedOkHandler', () => { it('should update capability if present', () => { br._untaggedOkHandler({ capability: ['abc'] }, () => { }) expect(br._capability).to.deep.equal(['abc']) }) }) describe('#_untaggedCapabilityHandler', () => { it('should update capability', () => { br._untaggedCapabilityHandler({ attributes: [{ value: 'abc' }] }, () => { }) expect(br._capability).to.deep.equal(['ABC']) }) }) describe('#_untaggedExistsHandler', () => { it('should emit onupdate', () => { br.onupdate = sinon.stub() br._selectedMailbox = 'FOO' br._untaggedExistsHandler({ nr: 123 }, () => { }) expect(br.onupdate.withArgs('FOO', 'exists', 123).callCount).to.equal(1) }) }) describe('#_untaggedExpungeHandler', () => { it('should emit onupdate', () => { br.onupdate = sinon.stub() br._selectedMailbox = 'FOO' br._untaggedExpungeHandler({ nr: 123 }, () => { }) expect(br.onupdate.withArgs('FOO', 'expunge', 123).callCount).to.equal(1) }) }) describe.skip('#_untaggedFetchHandler', () => { it('should emit onupdate', () => { br.onupdate = sinon.stub() sinon.stub(br, '_parseFETCH').returns('abc') br._selectedMailbox = 'FOO' br._untaggedFetchHandler({ nr: 123 }, () => { }) expect(br.onupdate.withArgs('FOO', 'fetch', 'abc').callCount).to.equal(1) expect(br._parseFETCH.args[0][0]).to.deep.equal({ payload: { FETCH: [{ nr: 123 }] } }) }) }) describe('#_changeState', () => { it('should set the state value', () => { br._changeState(12345) expect(br._state).to.equal(12345) }) it('should emit onclosemailbox if mailbox was closed', () => { br.onclosemailbox = sinon.stub() br._state = STATE_SELECTED br._selectedMailbox = 'aaa' br._changeState(12345) expect(br._selectedMailbox).to.be.false expect(br.onclosemailbox.withArgs('aaa').callCount).to.equal(1) }) }) describe('#_ensurePath', () => { it('should create the path if not present', () => { var tree = { children: [] } expect(br._ensurePath(tree, 'hello/world', '/')).to.deep.equal({ name: 'world', delimiter: '/', path: 'hello/world', children: [] }) expect(tree).to.deep.equal({ children: [{ name: 'hello', delimiter: '/', path: 'hello', children: [{ name: 'world', delimiter: '/', path: 'hello/world', children: [] }] }] }) }) it('should return existing path if possible', () => { var tree = { children: [{ name: 'hello', delimiter: '/', path: 'hello', children: [{ name: 'world', delimiter: '/', path: 'hello/world', children: [], abc: 123 }] }] } expect(br._ensurePath(tree, 'hello/world', '/')).to.deep.equal({ name: 'world', delimiter: '/', path: 'hello/world', children: [], abc: 123 }) }) it('should handle case insensitive Inbox', () => { var tree = { children: [] } expect(br._ensurePath(tree, 'Inbox/world', '/')).to.deep.equal({ name: 'world', delimiter: '/', path: 'Inbox/world', children: [] }) expect(br._ensurePath(tree, 'INBOX/worlds', '/')).to.deep.equal({ name: 'worlds', delimiter: '/', path: 'INBOX/worlds', children: [] }) expect(tree).to.deep.equal({ children: [{ name: 'Inbox', delimiter: '/', path: 'Inbox', children: [{ name: 'world', delimiter: '/', path: 'Inbox/world', children: [] }, { name: 'worlds', delimiter: '/', path: 'INBOX/worlds', children: [] }] }] }) }) }) describe('untagged updates', () => { it('should receive information about untagged exists', (done) => { br.client._connectionReady = true br._selectedMailbox = 'FOO' br.onupdate = (path, type, value) => { expect(path).to.equal('FOO') expect(type).to.equal('exists') expect(value).to.equal(123) done() } br.client._onData({ /* * 123 EXISTS\r\n */ data: new Uint8Array([42, 32, 49, 50, 51, 32, 69, 88, 73, 83, 84, 83, 13, 10]).buffer }) }) it('should receive information about untagged expunge', (done) => { br.client._connectionReady = true br._selectedMailbox = 'FOO' br.onupdate = (path, type, value) => { expect(path).to.equal('FOO') expect(type).to.equal('expunge') expect(value).to.equal(456) done() } br.client._onData({ /* * 456 EXPUNGE\r\n */ data: new Uint8Array([42, 32, 52, 53, 54, 32, 69, 88, 80, 85, 78, 71, 69, 13, 10]).buffer }) }) it('should receive information about untagged fetch', (done) => { br.client._connectionReady = true br._selectedMailbox = 'FOO' br.onupdate = (path, type, value) => { expect(path).to.equal('FOO') expect(type).to.equal('fetch') expect(value).to.deep.equal({ '#': 123, flags: ['\\Seen'], modseq: '4' }) done() } br.client._onData({ /* * 123 FETCH (FLAGS (\\Seen) MODSEQ (4))\r\n */ data: new Uint8Array([42, 32, 49, 50, 51, 32, 70, 69, 84, 67, 72, 32, 40, 70, 76, 65, 71, 83, 32, 40, 92, 83, 101, 101, 110, 41, 32, 77, 79, 68, 83, 69, 81, 32, 40, 52, 41, 41, 13, 10]).buffer }) }) }) })