From ae08cf2de99558b9edc5e63bfcd54c2f4513050e Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 11:31:30 +0530 Subject: [PATCH 01/12] Adding tests for delay --- src/util/delay.test.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/util/delay.test.js diff --git a/src/util/delay.test.js b/src/util/delay.test.js new file mode 100644 index 0000000000..1fde16478d --- /dev/null +++ b/src/util/delay.test.js @@ -0,0 +1,26 @@ +import delay from './delay' + +jest.useFakeTimers() + +describe('delay', () => { + test('should set a timer with the given delay', () => { + delay(1000) + expect(setTimeout.mock.calls.length).toBe(1) + expect(setTimeout.mock.calls[0][1]).toBe(1000) + }) + + test('should return a promise which resolves after the timer finishes', async () => { + const assertFunc = jest.fn() + delay(1000) + .then(assertFunc) + .catch(err => {}) + + await null + expect(assertFunc).not.toHaveBeenCalled() + + jest.runAllTimers() + + await null + expect(assertFunc).toHaveBeenCalled() + }) +}) From ab4b2b0d2a7d50f1451db4e2429ba71047b51bdd Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 11:40:09 +0530 Subject: [PATCH 02/12] Adding tests for event-to-promise --- src/util/event-to-promise.test.js | 138 ++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/util/event-to-promise.test.js diff --git a/src/util/event-to-promise.test.js b/src/util/event-to-promise.test.js new file mode 100644 index 0000000000..5846d74a7f --- /dev/null +++ b/src/util/event-to-promise.test.js @@ -0,0 +1,138 @@ +import 'core-js/fn/object/entries' // shim Object.entries +import pull from 'lodash/pull' + +import eventToPromise from './event-to-promise' + +class MockEvent { + constructor() { + this.listeners = [] + } + + addListener(listener) { + this.listeners.push(listener) + } + + removeListener(listener) { + pull(this.listeners, listener) + } + + trigger(...args) { + this.listeners.forEach(listener => listener.apply(null, args)) + } +} + +describe('eventToPromise', () => { + test('should return a promise', () => { + const promise = eventToPromise({}) + expect(promise).toBeInstanceOf(Promise) + }) + + test('should listen and unlisten to the events', async () => { + // We try both passing multiple events (for resolveOpts) and a single event (for rejectOpts). + const resolveOpts = [ + { event: new MockEvent() }, + { event: new MockEvent() }, + ] + const rejectOpts = { event: new MockEvent() } + eventToPromise({ + resolve: resolveOpts, + reject: rejectOpts, + }) + + // We use a bogus await statement to let any resolved promises invoke their callbacks. + // XXX Not sure if we can rely on this in every ES implementation. + await null + expect(resolveOpts[0].event.listeners.length).toBe(1) + expect(resolveOpts[1].event.listeners.length).toBe(1) + expect(rejectOpts.event.listeners.length).toBe(1) + + // Trigger any of the events. + resolveOpts[1].event.trigger() + + await null + expect(resolveOpts[0].event.listeners.length).toBe(0) + expect(resolveOpts[1].event.listeners.length).toBe(0) + expect(rejectOpts.event.listeners.length).toBe(0) + }) + + describe('should resolve with given value when a resolve-event occurs', () => { + const values = { + object: { someKey: 'someValue' }, + function: () => ({ someKey: 'someValue' }), + } + Object.entries(values).forEach(([type, value]) => { + test(`when value is a: ${type}`, async () => { + const resolveOpts = { event: new MockEvent(), value } + const resolveHandler = jest.fn() + eventToPromise({ + resolve: resolveOpts, + }).then(resolveHandler) + + await null + expect(resolveHandler).not.toBeCalled() + + resolveOpts.event.trigger() + + await null + expect(resolveHandler).toBeCalledWith({ someKey: 'someValue' }) + }) + }) + }) + + const reasons = { + string: 'something', + function: () => 'something else', + object: { someKey: 'something' }, + } + describe('should reject with Error(string) if a reject-event occurs', () => { + Object.entries(reasons).forEach(([type, reason]) => { + test(`when reason is a: ${type}`, async () => { + const rejectOpts = { + event: new MockEvent(), + reason, + } + const rejectHandler = jest.fn() + eventToPromise({ + reject: rejectOpts, + }).catch(rejectHandler) + + await null + expect(rejectHandler).not.toBeCalled() + + rejectOpts.event.trigger() + + await null + expect(rejectHandler).toBeCalled() + const error = rejectHandler.mock.calls[0][0] + expect(error).toBeInstanceOf(Error) + expect(error.message).toMatch(/.*something.*/) + }) + }) + }) + + test('should apply filter to events', async () => { + const resolveOpts = { + event: new MockEvent(), + filter: jest.fn(), + } + const resolveHandler = jest.fn() + eventToPromise({ + resolve: resolveOpts, + }).then(resolveHandler) + + await null + expect(resolveHandler).not.toBeCalled() + + resolveOpts.filter.mockReturnValueOnce(false) + resolveOpts.event.trigger() + + await null + expect(resolveHandler).not.toBeCalled() + + resolveOpts.filter.mockReturnValueOnce(true) + resolveOpts.event.trigger() + + await null + expect(resolveHandler).toBeCalled() + }) +}) From 4d78db2dc42f97c59a31d68c989421f4e96c2387 Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 11:40:31 +0530 Subject: [PATCH 03/12] Adding test for make-range-transform --- src/util/make-range-transform.test.js | 107 ++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/util/make-range-transform.test.js diff --git a/src/util/make-range-transform.test.js b/src/util/make-range-transform.test.js new file mode 100644 index 0000000000..d5f4a486e3 --- /dev/null +++ b/src/util/make-range-transform.test.js @@ -0,0 +1,107 @@ +import { + makeRangeTransform, + makeNonlinearTransform, +} from './make-range-transform' + +describe('makeRangeTransform', () => { + test('should work for basic cases', () => { + const transformFunction = makeRangeTransform({ + domain: [66, 100], + range: [9, 200], + }) + expect(transformFunction(79)).toBeCloseTo(82.029, 2) + expect(transformFunction(43)).toBeCloseTo(-120.205, 2) + expect(transformFunction(170)).toBeCloseTo(593.235, 2) + }) + + test('should clamp its outputs with clampOutput true', () => { + const transformFunction = makeRangeTransform({ + domain: [93, 117], + range: [3, 10], + clampOutput: true, + }) + expect(transformFunction(99)).toBeCloseTo(4.75, 2) + expect(transformFunction(83)).toBe(3) + expect(transformFunction(150)).toBe(10) + }) + + test('should work with descending domain and/or range', () => { + const transformFunction1 = makeRangeTransform({ + domain: [100, 0], + range: [0, 10], + }) + expect(transformFunction1(80)).toBe(2) + expect(transformFunction1(-40)).toBe(14) + expect(transformFunction1(120)).toBe(-2) + const transformFunction2 = makeRangeTransform({ + domain: [0, 100], + range: [10, 0], + }) + expect(transformFunction2(80)).toBe(2) + expect(transformFunction2(-40)).toBe(14) + expect(transformFunction2(120)).toBe(-2) + const transformFunction3 = makeRangeTransform({ + domain: [100, 0], + range: [10, 0], + }) + expect(transformFunction3(80)).toBe(8) + expect(transformFunction3(-40)).toBe(-4) + expect(transformFunction3(120)).toBe(12) + }) + + test('should work with descending domain and/or range with clamping', () => { + const transformFunction1 = makeRangeTransform({ + domain: [100, 0], + range: [0, 10], + clampOutput: true, + }) + expect(transformFunction1(80)).toBe(2) + expect(transformFunction1(-40)).toBe(10) + expect(transformFunction1(120)).toBe(0) + const transformFunction2 = makeRangeTransform({ + domain: [0, 100], + range: [10, 0], + clampOutput: true, + }) + expect(transformFunction2(80)).toBe(10) + expect(transformFunction2(-40)).toBe(10) + expect(transformFunction2(120)).toBe(10) + const transformFunction3 = makeRangeTransform({ + domain: [100, 0], + range: [10, 0], + clampOutput: true, + }) + expect(transformFunction3(80)).toBe(10) + expect(transformFunction3(-40)).toBe(10) + expect(transformFunction3(120)).toBe(10) + }) +}) + +describe('makeNonlinearTransform', () => { + test('should work for basic cases', () => { + const transformFunction = makeNonlinearTransform({ + domain: [5, 5603], + range: [0, 100], + nonlinearity: Math.log, + }) + expect(transformFunction(5)).toBe(0) + expect(transformFunction(5603)).toBe(100) + expect(transformFunction(3)).toBeCloseTo(-7.275, 2) + expect(transformFunction(1997)).toBeCloseTo(85.3074, 2) + expect(transformFunction(6000)).toBeCloseTo(100.974, 2) + }) + + test('should clamp its outputs with clampOutput true', () => { + const transformFunction = makeNonlinearTransform({ + domain: [5, 5603], + range: [0, 100], + clampOutput: true, + nonlinearity: Math.log, + }) + expect(transformFunction(5)).toBe(0) + expect(transformFunction(5603)).toBe(100) + expect(transformFunction(3)).toBe(0) + expect(transformFunction(1997)).toBeCloseTo(85.3074, 2) + expect(transformFunction(6000)).toBe(100) + }) +}) From 38b704a132bbf186f2c90835716cbe52e8b8f19f Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 12:30:31 +0530 Subject: [PATCH 04/12] Adding test for nice-time --- src/util/nice-time.test.js | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/util/nice-time.test.js diff --git a/src/util/nice-time.test.js b/src/util/nice-time.test.js new file mode 100644 index 0000000000..a3d50f5478 --- /dev/null +++ b/src/util/nice-time.test.js @@ -0,0 +1,50 @@ +import niceTime from './nice-time' + +describe('niceTime', () => { + test('should return now for timeperiod of less than 90 seconds', () => { + const date = new Date() + expect(niceTime(date)).toBe('now') + }) + + test('should return minutes for timeperiod of less than 600 seconds', () => { + const date = new Date(2016, 7, 2, 14, 25) + const now = new Date(2016, 7, 2, 14, 45) + expect(niceTime(date, { now })).toBe('20 minutes ago') + }) + + test('should return timeperiod stamp for timeperiod less than 24 hours', () => { + const date = new Date(2016, 7, 2, 14, 25) + const now = new Date(2016, 7, 2, 18, 55) + expect(niceTime(date, { now })).toBe('14:25') + }) + + test('should return the timeperiod stamp and the day for timeperiod less than 24 hours but not on the same day', () => { + const date = new Date(2016, 7, 2, 14, 25) + const now = new Date(2016, 7, 3, 10, 55) + expect(niceTime(date, { now })).toBe('Yesterday 14:25') + }) + + test('should return the day and timestamp for timeperiod less than 3 days', () => { + const date = new Date(2016, 7, 2, 14, 25) + const now = new Date(2016, 7, 4, 18, 55) + expect(niceTime(date, { now })).toBe('Tue 14:25') + }) + + test('should return the date and the month for timeperiod in the same year', () => { + const date = new Date(2016, 7, 2, 14, 25) + const now = new Date(2016, 9, 2, 18, 55) + expect(niceTime(date, { now })).toBe('2 Aug') + }) + + test('should return the date, month and year for timeperiod not in the same year', () => { + const date = new Date(2016, 7, 2, 14, 25) + const now = new Date(2017, 7, 2, 18, 55) + expect(niceTime(date, { now })).toBe('2 Aug 2016') + }) + + test('should return the placeholder for invalid date', () => { + const date = new Date(2016, 7, 3, 14, 25) + const now = new Date(2016, 7, 2, 18, 55) + expect(niceTime(date, { now })).toBe('soon?!') + }) +}) From cf2241ddd902845df55e85dc36f70c356b33c876 Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 12:31:12 +0530 Subject: [PATCH 05/12] Adding tests for pouchdb-update-doc and the pouchdb mock --- __mocks__/src/pouchdb.js | 18 ++++++++++ src/util/pouchdb-update-doc.test.js | 51 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 __mocks__/src/pouchdb.js create mode 100644 src/util/pouchdb-update-doc.test.js diff --git a/__mocks__/src/pouchdb.js b/__mocks__/src/pouchdb.js new file mode 100644 index 0000000000..2104e68125 --- /dev/null +++ b/__mocks__/src/pouchdb.js @@ -0,0 +1,18 @@ +import PouchDB from 'pouchdb-core' + +import PouchDBMemory from 'pouchdb-adapter-memory' +import mapreduce from 'pouchdb-mapreduce' +import replication from 'pouchdb-replication' + +PouchDB.plugin(PouchDBMemory) + .plugin(mapreduce) + .plugin(replication) + +const pouchdbOptions = { + name: 'testdb', + auto_compaction: true, + adapter: 'memory', +} + +const db = PouchDB(pouchdbOptions) +export default db diff --git a/src/util/pouchdb-update-doc.test.js b/src/util/pouchdb-update-doc.test.js new file mode 100644 index 0000000000..c396382e71 --- /dev/null +++ b/src/util/pouchdb-update-doc.test.js @@ -0,0 +1,51 @@ +import updateDoc from './pouchdb-update-doc' + +describe('updateDoc', () => { + const db = { + get: jest.fn(), + put: jest.fn(), + } + + beforeEach(() => { + db.get.mockReset() + db.put.mockReset() + }) + + test('should update the doc with the updateFunc', async () => { + const docBeforeChange = { test: 1 } + const docAfterChange = { test: 2 } + const docId = 'mydoc' + db.get.mockReturnValueOnce(docBeforeChange) + const updateFunc = jest.fn().mockReturnValueOnce(docAfterChange) + + await updateDoc(db, docId, updateFunc) + + expect(db.get).toHaveBeenCalledWith(docId) + expect(updateFunc).toHaveBeenCalledWith(docBeforeChange) + expect(db.put).toHaveBeenCalledWith(docAfterChange) + }) + + test('should survive a conflict', async () => { + const docId = 'mydoc' + const updateFunc = jest.fn() + const conflictError = { name: 'conflict', status: 409, error: true } + db.put.mockImplementationOnce(doc => { + throw conflictError + }) + + await expect(updateDoc(db, docId, updateFunc)).resolves.toBeUndefined() + }) + + test('should give up after persistent conflict', async () => { + const docId = 'mydoc' + const updateFunc = jest.fn() + const conflictError = { name: 'conflict', status: 409, error: true } + db.put.mockImplementation(doc => { + throw conflictError + }) + + await expect(updateDoc(db, docId, updateFunc)).rejects.toMatchObject( + conflictError, + ) + }) +}) From 1a26e58ec94e1005cc5b39c2d777fed34491633b Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 12:31:36 +0530 Subject: [PATCH 06/12] Adding test for random-string --- src/util/random-string.test.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/util/random-string.test.js diff --git a/src/util/random-string.test.js b/src/util/random-string.test.js new file mode 100644 index 0000000000..57eaef7c42 --- /dev/null +++ b/src/util/random-string.test.js @@ -0,0 +1,8 @@ +import randomString from './random-string' + +describe('randomString', () => { + test('should return a 10 digit random number', () => { + let expected = expect.stringMatching('^[0-9]{10}$') + expect(randomString()).toEqual(expected) + }) +}) From ec7d87033981f76de921ae89f94fa2634a49314f Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 12:31:54 +0530 Subject: [PATCH 07/12] Adding test for short-url --- src/util/short-url.test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/util/short-url.test.js diff --git a/src/util/short-url.test.js b/src/util/short-url.test.js new file mode 100644 index 0000000000..eb48d53ebc --- /dev/null +++ b/src/util/short-url.test.js @@ -0,0 +1,20 @@ +import shortUrl from './short-url' + +describe('shortUrl', () => { + test('should shorten the url to given number of characters', () => { + const url = shortUrl('https://example.com/page', 5) + expect(url).toBe('ex...') + }) + + test('should remove "http" prefix if url is within maxLength', () => { + const url = shortUrl('https://example.com/page') + expect(url).toBe('example.com/page') + }) + + test('should slice the data uri', () => { + const dataUri = + 'data:text/plain;charset=utf-8;base64,aHR0cHM6Ly9leGFtcGxlLmNvbS9wdWJsaWMvaW1hZ2UvYmFja2dyb3VuZC5qcGVn' + const url = shortUrl(dataUri) + expect(url).toBe('data:text/plain;charset=utf-8;base64,aHR0cHM6Ly...') + }) +}) From 88d66a3f6dfaa4f515d1798dc45806bd7cc11bbc Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 12:32:19 +0530 Subject: [PATCH 08/12] Adding test for sync-location-hashes --- src/util/sync-location-hashes.js | 2 +- src/util/sync-location-hashes.test.js | 128 ++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/util/sync-location-hashes.test.js diff --git a/src/util/sync-location-hashes.js b/src/util/sync-location-hashes.js index 7733b7498d..9ae200a0f1 100644 --- a/src/util/sync-location-hashes.js +++ b/src/util/sync-location-hashes.js @@ -5,7 +5,7 @@ // - windows: an Array of Windows. // - initial (optional): the window whose hash is used to sync them initially. // Returns a function that disables synchronisation again. -export default function syncLocationHashes(windows, { initial }) { +export default function syncLocationHashes(windows, { initial } = {}) { const listeners = windows.map( win => function syncHashListener() { diff --git a/src/util/sync-location-hashes.test.js b/src/util/sync-location-hashes.test.js new file mode 100644 index 0000000000..ddc4604eee --- /dev/null +++ b/src/util/sync-location-hashes.test.js @@ -0,0 +1,128 @@ +import syncLocationHashes from './sync-location-hashes' + +const createWindowMock = () => { + const win = { + location: { _hash: '' }, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + } + Object.defineProperty(win.location, 'hash', { + // Store the hash without '#' in the _hash attribute, and prefix the '#' when getting it. + get: jest.fn(function() { + return this._hash ? `#${this._hash}` : '' + }), + set: jest.fn(function(value) { + this._hash = value.replace(/^#/, '') + }), + }) + return win +} + +describe('windowMock', () => { + test('should prepend the # to the hash value', () => { + const win = createWindowMock() + win.location.hash = 'abc' + expect(win.location.hash).toEqual('#abc') + }) + + test('should not end up with a double ## before the hash value', () => { + const win = createWindowMock() + win.location.hash = '#abc' + expect(win.location.hash).toEqual('#abc') + }) + + test('should not add a # when the hash is the empty string', () => { + const win = createWindowMock() + win.location.hash = '' + expect(win.location.hash).toEqual('') + }) +}) + +describe('syncLocationHashes', () => { + const win1 = createWindowMock() + const win2 = createWindowMock() + const win3 = createWindowMock() + const windows = [win1, win2, win3] + + beforeEach(() => { + win1.location.hash = '#win1hash' + win2.location.hash = '#win2hash' + win3.location.hash = '#win3hash' + windows.forEach(win => { + win.addEventListener.mockReset() + win.removeEventListener.mockReset() + // Do not reset getter/setter implementations, but clear their call log. + // (note that the _hash value is left intact, to the value we just gave it) + const { get, set } = Object.getOwnPropertyDescriptor( + win.location, + 'hash', + ) + get.mockClear() + set.mockClear() + }) + }) + + test('should create a listener on the windows', () => { + syncLocationHashes(windows) + windows.forEach(win => { + expect(win.addEventListener).toHaveBeenCalledTimes(1) + }) + }) + + test('should disable the listeners when returned function is called', () => { + const disableListener = syncLocationHashes(windows) + windows.forEach(win => { + expect(win.removeEventListener).not.toHaveBeenCalled() + }) + + disableListener() + + windows.forEach(win => { + expect(win.removeEventListener.mock.calls).toEqual( + win.addEventListener.mock.calls, + ) + }) + }) + + test('should directly perform an initial sync if specified', () => { + win2.location.hash = '#somehash' + syncLocationHashes(windows, { initial: win2 }) + + windows.forEach(win => { + expect(win.location.hash).toEqual('#somehash') + }) + }) + + test('should sync to other windows when one emits a hashchange event', () => { + syncLocationHashes(windows) + const win2HashChangeEventListener = + win2.addEventListener.mock.calls[0][1] + + win2.location.hash = '#newhash' + win2HashChangeEventListener() + + windows.forEach(win => { + expect(win.location.hash).toEqual('#newhash') + }) + }) + + test('should avoid creating an infinite updating loop', () => { + // We simply verify that a hash will not be set if it already has the right value. + syncLocationHashes(windows) + const win2HashChangeEventListener = + win2.addEventListener.mock.calls[0][1] + + win1.location.hash = '#samehash' + win2.location.hash = '#samehash' + win3.location.hash = '#samehash' + win2HashChangeEventListener() + + windows.forEach(win => { + const hashSetter = Object.getOwnPropertyDescriptor( + win.location, + 'hash', + ).set + expect(hashSetter).toHaveBeenCalledTimes(1) // just our manual assignment above. + }) + }) +}) From 7d948f9b300b1be91b33c25c72ddf13d540fc27b Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 12:32:52 +0530 Subject: [PATCH 09/12] Adding tests for tab-events --- src/util/tab-events.test.js | 152 ++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/util/tab-events.test.js diff --git a/src/util/tab-events.test.js b/src/util/tab-events.test.js new file mode 100644 index 0000000000..8711a2fc18 --- /dev/null +++ b/src/util/tab-events.test.js @@ -0,0 +1,152 @@ +import { + whenPageDOMLoaded, + whenPageLoadComplete, + whenTabActive, +} from './tab-events' +import * as eventToPromise from './event-to-promise' + +describe('whenPageDOMLoaded', () => { + const tabId = 1 + + beforeEach(() => { + browser.tabs = {} + browser.webNavigation = { + onCommitted: jest.fn(), + } + eventToPromise.default = jest.fn().mockReturnValue(Promise.resolve()) + }) + + test('should execute script and resolve promise if script executes', async () => { + browser.tabs = { + executeScript: jest.fn().mockReturnValue(Promise.resolve()), + } + await whenPageDOMLoaded({ tabId }) + expect(browser.tabs.executeScript).toHaveBeenCalledWith(tabId, { + code: 'undefined', + runAt: 'document_end', + }) + }) + + test('should reject the promise if the script is not executed', async () => { + expect.assertions(1) + browser.tabs = { + executeScript: jest + .fn() + .mockReturnValue( + Promise.reject(new Error('Script unable to execute')), + ), + } + try { + await whenPageDOMLoaded({ tabId }) + } catch (err) { + expect(err.message).toBe('Script unable to execute') + } + }) + + test.skip('should reject the promise if tab is changed', async () => { + expect.assertions(1) + browser.tabs = { + executeScript: jest + .fn() + .mockReturnValue(new Promise((resolve, reject) => {})), + } + eventToPromise.default = jest + .fn() + .mockReturnValue(Promise.reject(new Error('Tab was changed'))) + await expect(whenPageDOMLoaded({ tabId })).rejects.toMatchObject({ + message: 'Tab was changed', + }) + }) +}) + +describe('whenPageLoadComplete', () => { + const tabId = 1 + + beforeEach(() => { + eventToPromise.default = jest.fn().mockReturnValue(Promise.resolve()) + browser.webNavigation = { + onCommitted: jest.fn(), + } + }) + + test('should return directly if the tab status is complete', async () => { + browser.tabs = { + get: jest.fn().mockReturnValueOnce({ + status: 'complete', + }), + } + await whenPageLoadComplete({ tabId }) + expect(browser.tabs.get).toHaveBeenCalledWith(tabId) + expect(eventToPromise.default).not.toHaveBeenCalled() + }) + + test('should run eventToPromise and resolve if its Promise resolves', async () => { + browser.tabs = { + get: jest.fn().mockReturnValueOnce({ + status: 'loading', + }), + } + // Add a 'shibboleth' to be able to check that *this* Promise was returned & resolved. + eventToPromise.default.mockReturnValueOnce( + Promise.resolve('shibboleth'), + ) + await expect(whenPageLoadComplete({ tabId })).resolves.toBe( + 'shibboleth', + ) + }) + + test.skip('should run eventToPromise and reject if its Promise rejects', async () => { + browser.tabs = { + get: jest.fn().mockReturnValueOnce({ + status: 'loading', + }), + } + eventToPromise.default = jest + .fn() + .mockReturnValue(Promise.reject(new Error('Tab was changed'))) + await expect(whenPageLoadComplete({ tabId })).rejects.toMatchObject({ + message: 'Tab was changed', + }) + }) +}) + +describe('whenTabActive', () => { + beforeEach(() => { + eventToPromise.default = jest.fn().mockReturnValue(Promise.resolve()) + browser.webNavigation = { + onCommitted: jest.fn(), + } + }) + + test('should return directly if the tab is already active', async () => { + browser.tabs = { + query: jest.fn().mockReturnValueOnce([{ id: 1 }]), + } + await whenTabActive({ tabId: 1 }) + expect(browser.tabs.query).toHaveBeenCalledWith({ active: true }) + expect(eventToPromise.default).not.toHaveBeenCalled() + }) + + test('should run eventToPromise and resolve if its Promise resolves', async () => { + browser.tabs = { + query: jest.fn().mockReturnValueOnce([{ id: 2 }]), + } + // Add a 'shibboleth' to be able to check that *this* Promise was returned & resolved. + eventToPromise.default.mockReturnValueOnce( + Promise.resolve('shibboleth'), + ) + await expect(whenTabActive({ tabId: 1 })).resolves.toBe('shibboleth') + }) + + test.skip('should run eventToPromise and reject if its Promise rejects', async () => { + browser.tabs = { + query: jest.fn().mockReturnValueOnce([{ id: 2 }]), + } + eventToPromise.default = jest + .fn() + .mockReturnValue(Promise.reject(new Error('Tab was changed'))) + await expect(whenTabActive({ tabId: 1 })).rejects.toMatchObject({ + message: 'Tab was changed', + }) + }) +}) From 4d4ccf160a6bf877fbc278d08ffea15b87470462 Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 12:33:13 +0530 Subject: [PATCH 10/12] Adding tests for tree-walker --- src/util/tree-walker.test.js | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/util/tree-walker.test.js diff --git a/src/util/tree-walker.test.js b/src/util/tree-walker.test.js new file mode 100644 index 0000000000..3ba7be1798 --- /dev/null +++ b/src/util/tree-walker.test.js @@ -0,0 +1,77 @@ +import { getAllNodes, getRoot } from './tree-walker' + +const testTrees = (() => { + const _edges = { + // Tree one. + a: { parent: undefined, children: ['b', 'c'] }, + b: { parent: 'a', children: [] }, + c: { parent: 'a', children: ['d', 'e'] }, + d: { parent: 'c', children: [] }, + e: { parent: 'c', children: [] }, + // Tree two. + f: { parent: undefined, children: ['g'] }, + g: { parent: 'f', children: ['h'] }, + h: { parent: 'g', children: [] }, + } + return { + _edges, + getParent: async node => _edges[node].parent, + getChildren: async node => _edges[node].children, + } +})() + +describe('getAllNodes', () => { + const getAllNodesOfTestTree = getAllNodes(testTrees) + + test('gets all nodes given a leaf node', async () => { + const nodes = [ + (await getAllNodesOfTestTree('e')).sort(), + (await getAllNodesOfTestTree('h')).sort(), + ] + expect(nodes).toEqual([['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h']]) + }) + + test('gets all nodes given a non-leaf node', async () => { + const nodes = [ + (await getAllNodesOfTestTree('c')).sort(), + (await getAllNodesOfTestTree('g')).sort(), + ] + expect(nodes).toEqual([['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h']]) + }) + + test('gets all nodes given the root node', async () => { + const nodes = [ + (await getAllNodesOfTestTree('a')).sort(), + (await getAllNodesOfTestTree('f')).sort(), + ] + expect(nodes).toEqual([['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h']]) + }) +}) + +describe('getRoot', () => { + const getRootOfTestTree = getRoot(testTrees) + + test('gets the root given a leaf node', async () => { + const roots = [ + await getRootOfTestTree('e'), + await getRootOfTestTree('h'), + ] + expect(roots).toEqual(['a', 'f']) + }) + + test('gets the root given a non-leaf node', async () => { + const roots = [ + await getRootOfTestTree('c'), + await getRootOfTestTree('g'), + ] + expect(roots).toEqual(['a', 'f']) + }) + + test('gets the root given the root node itself', async () => { + const roots = [ + await getRootOfTestTree('a'), + await getRootOfTestTree('f'), + ] + expect(roots).toEqual(['a', 'f']) + }) +}) From 17dd5a40f2a1a26b954fa8e1afd61ef568e5fca9 Mon Sep 17 00:00:00 2001 From: reficul31 Date: Fri, 22 Dec 2017 12:33:29 +0530 Subject: [PATCH 11/12] Adding test for webextensionRPC --- src/util/webextensionRPC.test.js | 73 ++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/util/webextensionRPC.test.js diff --git a/src/util/webextensionRPC.test.js b/src/util/webextensionRPC.test.js new file mode 100644 index 0000000000..3de5f84a8d --- /dev/null +++ b/src/util/webextensionRPC.test.js @@ -0,0 +1,73 @@ +import { remoteFunction } from './webextensionRPC' + +describe('remoteFunction', () => { + beforeEach(() => { + browser.runtime = { + sendMessage: jest.fn(() => Promise.resolve()), + } + browser.tabs = { + sendMessage: jest.fn(() => Promise.resolve()), + } + }) + + test('should create a function', () => { + const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) + expect(remoteFunc.name).toBe('remoteFunc_RPC') + expect(typeof remoteFunc).toBe('function') + }) + + test.skip('should throw an error when unable to sendMessage', async () => { + const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) + browser.tabs.sendMessage.mockImplementationOnce(() => { + throw new Error() + }) + await expect(remoteFunc()).rejects.toMatchObject({ + message: `Got no response when trying to call 'remoteFunc'. Did you enable RPC in the tab's content script?`, + }) + }) + + test('should call the browser.tabs function when tabId is given', async () => { + const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) + try { + await remoteFunc() + } catch (e) {} + expect(browser.tabs.sendMessage).toHaveBeenCalledTimes(1) + expect(browser.runtime.sendMessage).toHaveBeenCalledTimes(0) + }) + + test('should call the browser.runtime function when tabId is undefined', async () => { + const remoteFunc = remoteFunction('remoteFunc') + try { + await remoteFunc() + } catch (e) {} + expect(browser.tabs.sendMessage).toHaveBeenCalledTimes(0) + expect(browser.runtime.sendMessage).toHaveBeenCalledTimes(1) + }) + + test.skip('should throw an error if response does not contain RPC token', async () => { + const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) + await expect(remoteFunc()).rejects.toMatchObject({ + message: 'RPC got a response from an interfering listener.', + }) + }) + + test.skip('should throw an error if the response contains an error message', async () => { + browser.tabs.sendMessage.mockReturnValueOnce({ + __RPC_RESPONSE__: '__RPC_RESPONSE__', + errorMessage: 'Remote function error', + }) + const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) + await expect(remoteFunc()).rejects.toMatchObject({ + message: 'Remote function error', + }) + }) + + test('should return the value contained in the response', async () => { + browser.tabs.sendMessage.mockReturnValueOnce({ + __RPC_RESPONSE__: '__RPC_RESPONSE__', + returnValue: 'Remote function return value', + }) + const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) + await expect(remoteFunc()).resolves.toBe('Remote function return value') + }) +}) From 5696ef71c3c5665c56671d84ead9416790c41535 Mon Sep 17 00:00:00 2001 From: reficul31 Date: Sun, 24 Dec 2017 20:01:33 +0530 Subject: [PATCH 12/12] Adding eslint for jest and linting the test files --- src/util/delay.test.js | 2 ++ src/util/event-to-promise.test.js | 10 ++++++++-- src/util/make-range-transform.test.js | 2 ++ src/util/nice-time.test.js | 2 ++ src/util/pouchdb-update-doc.test.js | 2 ++ src/util/random-string.test.js | 4 +++- src/util/short-url.test.js | 2 ++ src/util/sync-location-hashes.test.js | 2 ++ src/util/tab-events.test.js | 2 ++ src/util/tree-walker.test.js | 2 ++ src/util/webextensionRPC.test.js | 2 ++ 11 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/util/delay.test.js b/src/util/delay.test.js index 1fde16478d..9c7baefcf6 100644 --- a/src/util/delay.test.js +++ b/src/util/delay.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import delay from './delay' jest.useFakeTimers() diff --git a/src/util/event-to-promise.test.js b/src/util/event-to-promise.test.js index 5846d74a7f..bf0142a643 100644 --- a/src/util/event-to-promise.test.js +++ b/src/util/event-to-promise.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import 'core-js/fn/object/entries' // shim Object.entries import pull from 'lodash/pull' @@ -66,7 +68,9 @@ describe('eventToPromise', () => { const resolveHandler = jest.fn() eventToPromise({ resolve: resolveOpts, - }).then(resolveHandler) + }) + .then(resolveHandler) + .catch() await null expect(resolveHandler).not.toBeCalled() @@ -118,7 +122,9 @@ describe('eventToPromise', () => { const resolveHandler = jest.fn() eventToPromise({ resolve: resolveOpts, - }).then(resolveHandler) + }) + .then(resolveHandler) + .catch() await null expect(resolveHandler).not.toBeCalled() diff --git a/src/util/make-range-transform.test.js b/src/util/make-range-transform.test.js index d5f4a486e3..b6f91af436 100644 --- a/src/util/make-range-transform.test.js +++ b/src/util/make-range-transform.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import { makeRangeTransform, makeNonlinearTransform, diff --git a/src/util/nice-time.test.js b/src/util/nice-time.test.js index a3d50f5478..3dc0a7df26 100644 --- a/src/util/nice-time.test.js +++ b/src/util/nice-time.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import niceTime from './nice-time' describe('niceTime', () => { diff --git a/src/util/pouchdb-update-doc.test.js b/src/util/pouchdb-update-doc.test.js index c396382e71..9ba842cb9f 100644 --- a/src/util/pouchdb-update-doc.test.js +++ b/src/util/pouchdb-update-doc.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import updateDoc from './pouchdb-update-doc' describe('updateDoc', () => { diff --git a/src/util/random-string.test.js b/src/util/random-string.test.js index 57eaef7c42..1d34f43e9f 100644 --- a/src/util/random-string.test.js +++ b/src/util/random-string.test.js @@ -1,8 +1,10 @@ +/* eslint-env jest */ + import randomString from './random-string' describe('randomString', () => { test('should return a 10 digit random number', () => { - let expected = expect.stringMatching('^[0-9]{10}$') + const expected = expect.stringMatching('^[0-9]{10}$') expect(randomString()).toEqual(expected) }) }) diff --git a/src/util/short-url.test.js b/src/util/short-url.test.js index eb48d53ebc..eb9ba584cc 100644 --- a/src/util/short-url.test.js +++ b/src/util/short-url.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import shortUrl from './short-url' describe('shortUrl', () => { diff --git a/src/util/sync-location-hashes.test.js b/src/util/sync-location-hashes.test.js index ddc4604eee..75a6fa5d83 100644 --- a/src/util/sync-location-hashes.test.js +++ b/src/util/sync-location-hashes.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import syncLocationHashes from './sync-location-hashes' const createWindowMock = () => { diff --git a/src/util/tab-events.test.js b/src/util/tab-events.test.js index 8711a2fc18..776ed5448d 100644 --- a/src/util/tab-events.test.js +++ b/src/util/tab-events.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import { whenPageDOMLoaded, whenPageLoadComplete, diff --git a/src/util/tree-walker.test.js b/src/util/tree-walker.test.js index 3ba7be1798..9933eee8e5 100644 --- a/src/util/tree-walker.test.js +++ b/src/util/tree-walker.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import { getAllNodes, getRoot } from './tree-walker' const testTrees = (() => { diff --git a/src/util/webextensionRPC.test.js b/src/util/webextensionRPC.test.js index 3de5f84a8d..c9300acb9f 100644 --- a/src/util/webextensionRPC.test.js +++ b/src/util/webextensionRPC.test.js @@ -1,3 +1,5 @@ +/* eslint-env jest */ + import { remoteFunction } from './webextensionRPC' describe('remoteFunction', () => {