/* eslint-env jest */ 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('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('should throw an "interfering listener" error if response is unrecognised', async () => { browser.tabs.sendMessage.mockReturnValueOnce('some unexpected return value') const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) await expect(remoteFunc()).rejects.toMatchObject({ message: expect.stringContaining('RPC got a response from an interfering listener'), }) }) test('should throw a "no response" error if sending the message fails', async () => { browser.tabs.sendMessage.mockReturnValueOnce(Promise.reject(new Error())) const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) await expect(remoteFunc()).rejects.toMatchObject({ message: expect.stringContaining('Got no response'), }) }) test('should throw a "no response" error if response is undefined', async () => { // It seems we can get back undefined when the tab is closed before the response is sent. // In such cases 'no response' seems a better error message than 'interfering listener'. browser.tabs.sendMessage.mockReturnValueOnce(undefined) const remoteFunc = remoteFunction('remoteFunc', { tabId: 1 }) await expect(remoteFunc()).rejects.toMatchObject({ message: expect.stringContaining('Got no response'), }) }) test('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') }) }) // TODO Test behaviour of executing side.