diff --git a/jest.config.js b/jest.config.js index 7726b093..921a64c0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,4 +14,5 @@ module.exports = { }, transformIgnorePatterns: [`/node_modules/(?!crypto-random-string)`], setupFilesAfterEnv: ['./jest.setup.js'], + testEnvironment: 'jsdom' }; diff --git a/src/api/messaging.js b/src/api/messaging.js index f8d6e8b4..51f96b44 100644 --- a/src/api/messaging.js +++ b/src/api/messaging.js @@ -171,7 +171,7 @@ export const Messaging = { // protect background by not allowing not whitelisted if (!whitelisted || whitelisted.error) return; - const event = new CustomEvent(TARGET + response.event, { + const event = new CustomEvent(`${TARGET}${response.event}`, { detail: response.data, }); diff --git a/src/api/webpage/eventRegistring.js b/src/api/webpage/eventRegistring.js new file mode 100644 index 00000000..35c912cf --- /dev/null +++ b/src/api/webpage/eventRegistring.js @@ -0,0 +1,45 @@ +import { TARGET } from '../../config/config'; + +/** + * @param {string} eventName + * @param {Function} callback + */ +export const on = (eventName, callback) => { + const handler = (event) => callback(event.detail); + + const events = window.cardano._events[eventName] || []; + + window.cardano._events[eventName] = [ + ...events, + [ callback, handler ], + ]; + + window.addEventListener(`${TARGET}${eventName}`, handler); + + return { remove: () => off(eventName, callback) }; +}; + +/** + * @param {string} eventName + * @param {Function} callback + */ +export const off = (eventName, callback) => { + const filterHandlersBy = (predicate) => (handlers) => handlers.filter( + ([ savedCallback ]) => predicate(savedCallback) + ); + + const filterByMatchingHandlers = filterHandlersBy((cb) => cb === callback); + const filterByNonMatchingHandlers = filterHandlersBy((cb) => cb !== callback); + + const eventHandlers = window.cardano._events[eventName]; + + if (typeof eventHandlers !== 'undefined') { + const matchingHandlers = filterByMatchingHandlers(eventHandlers); + + for (const [, handler] of matchingHandlers) { + window.removeEventListener(`${TARGET}${eventName}`, handler); + } + + window.cardano._events[eventName] = filterByNonMatchingHandlers(eventHandlers); + } +}; diff --git a/src/api/webpage/index.js b/src/api/webpage/index.js index 49c08390..29ff1940 100644 --- a/src/api/webpage/index.js +++ b/src/api/webpage/index.js @@ -1,4 +1,4 @@ -import { EVENT, METHOD, SENDER, TARGET } from '../../config/config'; +import { METHOD } from '../../config/config'; import { Messaging } from '../messaging'; export const getBalance = async () => { @@ -76,32 +76,4 @@ export const submitTx = async (tx) => { return result.data; }; -export const on = (eventName, callback) => { - const fn = (e) => callback(e.detail); - Object.defineProperty(fn, 'name', { value: callback.name }); - window.cardano._events[eventName] = [ - ...(window.cardano._events[eventName] || []), - fn, - ]; - window.addEventListener( - TARGET + eventName, - window.cardano._events[eventName][ - window.cardano._events[eventName].length - 1 - ] - ); -}; - -export const removeListener = (eventName, callback) => { - const fn = window.cardano._events[eventName].find( - (f) => f.name == callback.name - ); - if (!fn) return; - const index = window.cardano._events[eventName].indexOf(fn); - window.removeEventListener( - TARGET + eventName, - window.cardano._events[eventName][index] - ); - window.cardano._events[eventName].splice(index, 1); - if (window.cardano._events[eventName].length <= 0) - delete window.cardano._events[eventName]; -}; +export { on, off } from './eventRegistring'; diff --git a/src/pages/Content/injected.js b/src/pages/Content/injected.js index ba7caffb..d887b1a2 100644 --- a/src/pages/Content/injected.js +++ b/src/pages/Content/injected.js @@ -8,7 +8,6 @@ import { getUtxos, isEnabled, on, - removeListener, signData, signTx, submitTx, diff --git a/src/test/unit/api/webpage/eventRegistring.test.js b/src/test/unit/api/webpage/eventRegistring.test.js new file mode 100644 index 00000000..1317a6e7 --- /dev/null +++ b/src/test/unit/api/webpage/eventRegistring.test.js @@ -0,0 +1,90 @@ +import { on, off } from '../../../../api/webpage/eventRegistring'; +import { TARGET } from '../../../../config/config'; + +describe('webpage/eventRegistring', () => { + const makeEvent = (eventType, detail) => (new CustomEvent(`${TARGET}${eventType}`, { detail })); + + describe('on', () => { + + beforeEach(() => { + window.cardano = { + _events: {}, + }; + }); + + test('invokes the callback when a the target event is triggered', () => { + const eventType = 'mock-event'; + const mockPayload = 'mock-payload'; + const event = makeEvent(eventType, mockPayload); + const mockFn = jest.fn(); + + on(eventType, mockFn); + + window.dispatchEvent(event); + + expect(mockFn).toHaveBeenCalledTimes(1); + expect(mockFn).toHaveBeenCalledWith(mockPayload); + }); + + test('does not invoke the callback when a different event is triggered', () => { + const mockFn = jest.fn(); + + on('event-A', mockFn); + + const mockPayload = 'mock-payload'; + const event = makeEvent('event-B', mockPayload); + + window.dispatchEvent(event); + + expect(mockFn).not.toHaveBeenCalled(); + }); + }); + + describe('of', () => { + const mockEventType = 'mock-event'; + const mockCallback = jest.fn(); + const mockHandler = jest.fn().mockImplementation(() => mockCallback()); + + beforeEach(() => { + jest.resetAllMocks(); + + window.cardano = { + _events: { + [mockEventType]: [[mockCallback, mockHandler]] + }, + }; + }); + + test('clean out matching callbacks fom the given event', () => { + off(mockEventType, mockCallback); + + expect(window.cardano._events).toEqual({ + [mockEventType]: [] + }) + }); + + test('stops the given callback from being invoked when cleaned out', () => { + off(mockEventType, mockCallback); + + const event = makeEvent(mockEventType); + + window.dispatchEvent(event); + + expect(mockCallback).not.toHaveBeenCalled() + }); + + test('does not stop other callbacks from being invoked after cleaned one out', () => { + const mockFn = jest.fn(); + + on('event-A', mockFn); + + off('mockEventType', mockCallback); + + const event = makeEvent('event-A'); + + window.dispatchEvent(event); + + expect(mockFn).toHaveBeenCalledTimes(1) + }); + }); +}); diff --git a/src/ui/app/components/transactionBuilder.jsx b/src/ui/app/components/transactionBuilder.jsx index e25cb331..55357aac 100644 --- a/src/ui/app/components/transactionBuilder.jsx +++ b/src/ui/app/components/transactionBuilder.jsx @@ -26,7 +26,7 @@ import { UnorderedList, ListItem, } from '@chakra-ui/react'; -import { GoStop } from 'react-icons/Go'; +import { GoStop } from 'react-icons/go'; // Assets import Berry from '../../../assets/img/berry.svg'; import { ERROR } from '../../../config/config';