Skip to content

Commit

Permalink
Merge pull request #8 from interledger/feature/test-coverage-and-impr…
Browse files Browse the repository at this point in the history
…ovements

Improvements, fixes and unit test coverage
  • Loading branch information
ionutanin committed Aug 15, 2023
2 parents 73ee557 + 5dda625 commit 1c176d8
Show file tree
Hide file tree
Showing 17 changed files with 425 additions and 79 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"plugin:jsx-a11y/recommended",
"plugin:cypress/recommended",
"plugin:tailwind/recommended",
"prettier"
"prettier",
"plugin:jest/recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
Expand All @@ -33,6 +35,7 @@
"cypress",
"@typescript-eslint",
"jest",
"@typescript-eslint",
"simple-import-sort",
"@funboxteam/no-only-tests"
],
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,51 @@ To build the extension for production, use the following command:

`yarn build`

### Building the Extension for Firefox

To build the extension for Firefox, use the following command:

`yarn build:firefox`

This command transpiles the TypeScript code and generates a production-ready build of the extension in the dist
directory.

### Installing the Extension in Chrome

To install the extension in Chrome, follow these steps:

1. Extract the Files:<br/>
Extract the contents of the ZIP file to a folder on your computer.

2. Open Chrome's Extensions Page:<br/>
Open Chrome, click the three dots in the top-right corner, go to "More tools," and select "Extensions."

3. Enable Developer Mode:<br/>
Enable "Developer mode" using the toggle switch at the top-right of the Extensions page.

4. Load the Extension:<br/>
Click the "Load unpacked" button that appears after enabling Developer mode.

5. Select the Extension Folder:<br/>
Choose the folder containing the extracted extension files (with the manifest.json file).

6. Pin the Extension:<br/>
Click on the puzzle piece icon in the top-right corner of Chrome, and pin the Web Monetization extension.

### Installing the Extension in Firefox

1. Extract the Files:<br/>
Extract the contents of the ZIP file to a folder on your computer.

2. Open Firefox's Add-ons Page:<br/>
Open Firefox, click the three horizontal lines in the top-right corner, and choose "Add-ons."

3. Access Extensions Settings:<br/>
In the Add-ons Manager, click the gear icon in the top-right corner and select "Install Add-on From File..."

4. Select the Extension File:<br/>
Navigate to the folder where you extracted the extension files and select the manifest.json file or the main folder of the extension.

### Testing the Extension

To run the tests, use the following command:
Expand Down
8 changes: 7 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ export default {
collectCoverage: true,

// An array of glob patterns indicating a set of files for which coverage information should be collected
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.css', '!src/**/*.svg'],
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.css',
'!src/**/*.svg',
'!src/**/*.d.ts',
'!src/**/index.ts',
],

// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
Expand Down
12 changes: 10 additions & 2 deletions manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ const action = {
},
}

const csp = isFirefox ? { content_security_policy: `script-src 'self'; object-src 'self'` } : {}
const csp = isFirefox
? { content_security_policy: `script-src 'self'; object-src 'self'` }
: {
content_security_policy: {
extension_pages: `script-src 'self' 'wasm-unsafe-eval'; object-src 'self'`,
},
}

const resources = [
'assets/js/*.js',
Expand All @@ -23,6 +29,8 @@ const resources = [
'icon-34.png',
'icon-active-34.png',
'icon-active-128.png',
'icon-inactive-34.png',
'icon-inactive-128.png',
]

const manifest = {
Expand All @@ -41,7 +49,7 @@ const manifest = {
{
matches: ['http://*/*', 'https://*/*', '<all_urls>'],
js: ['src/pages/content/index.js'],
css: ['assets/css/contentStyle<KEY>.chunk.css'],
// css: ['assets/css/contentStyle<KEY>.chunk.css'],
},
],
...csp,
Expand Down
Binary file added public/icon-inactive-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icon-inactive-34.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 123 additions & 0 deletions src/lib/listeners.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { initListeners } from '@/lib/index'
import { addTabChangeListener } from '@/lib/listeners'
import { addMessageListener, sendRuntimeMessage } from '@/lib/messageUtils'

// Mock the addMessageListener and sendRuntimeMessage functions
jest.mock('@/lib/messageUtils', () => ({
addMessageListener: jest.fn(),
sendRuntimeMessage: jest.fn(),
}))

// Mock browser.tabs.onActivated listener
const onActivatedMock = jest.fn()
const tabsMock = {
onActivated: {
addListener: onActivatedMock,
},
}
// Mock the browser object with the tabsMock
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(global as any).browser = {
tabs: tabsMock,
}

describe('initListeners', () => {
// Set up the HTML body before each test
beforeEach(() => {
document.body.innerHTML = `
<link rel="monetization" href="https://example.com/payment" />
`
})

// Clean up the HTML body and mock function calls after each test
afterEach(() => {
document.body.innerHTML = ''
jest.clearAllMocks()
})

it('sends MONETIZATION_START message if monetization link exists', () => {
initListeners()

expect(sendRuntimeMessage).toHaveBeenCalledWith('MONETIZATION_START', true)
})

it('sends MONETIZATION_START message with false if monetization link does not exist', () => {
document.body.innerHTML = ''
initListeners()

expect(sendRuntimeMessage).toHaveBeenCalledWith('MONETIZATION_START', false)
})

it('listens for GET_MONETIZATION message and responds with monetization link status', () => {
// Mock addMessageListener function
const addMessageListenerMock = addMessageListener as jest.Mock
addMessageListenerMock.mockImplementation(listener => {
const sendResponseMock = jest.fn()
const sender = {}

listener({ action: 'GET_MONETIZATION' }, sender, sendResponseMock)

expect(sendResponseMock).toHaveBeenCalledWith(true)
})

initListeners()
})

it('sends MONETIZATION_START message based on link existence', () => {
// Monetization link exists
document.body.innerHTML = '<link rel="monetization" href="https://example.com/payment" />'
const sendResponseMock = jest.fn()
const sender = {}

const addMessageListenerMock = addMessageListener as jest.Mock
addMessageListenerMock.mockImplementation(listener => {
listener({ action: 'GET_MONETIZATION' }, sender, sendResponseMock)
})

initListeners()

expect(sendResponseMock).toHaveBeenCalledWith(true)

// Monetization link does not exist
document.body.innerHTML = ''
sendResponseMock.mockClear()

initListeners()

expect(sendResponseMock).toHaveBeenCalledWith(false)
})
})

describe('addTabChangeListener', () => {
it('registers tab change listener', () => {
const listenerMock = jest.fn()
tabsMock.onActivated.addListener.mockImplementation(callback => {
callback({ tabId: 123, windowId: 456 })

expect(listenerMock).toHaveBeenCalled()
})

addTabChangeListener(listenerMock)
})

it('registers tab change listener and handles failure', () => {
// Mock addMessageListener function
const addMessageListenerMock = addMessageListener as jest.Mock
addMessageListenerMock.mockImplementation(listener => {
const sendResponseMock = jest.fn()
const sender = {}

listener({ action: 'GET_MONETIZATION' }, sender, sendResponseMock)

expect(sendResponseMock).toHaveBeenCalledWith(false)
})

// Simulate tab change listener registration failure
tabsMock.onActivated.addListener = jest.fn(() => {
throw new Error('Listener registration failed')
})

// Ensure that no errors are thrown when initListeners is called
expect(() => initListeners()).not.toThrow()
})
})
120 changes: 120 additions & 0 deletions src/lib/messageUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { BrowserAPI } from '@/lib/index'
import {
addMessageListener,
queryActiveTab,
sendRuntimeMessage,
sendTabsMessage,
} from '@/lib/messageUtils'

describe('messageUtils', () => {
let originalBrowser

// Store the original global browser object
beforeAll(() => {
originalBrowser = global.browser
})

// Restore the original global browser object after all tests
afterAll(() => {
global.browser = originalBrowser
})

// Clear mock function calls after each test
afterEach(() => {
jest.clearAllMocks()
})

describe('sendTabsMessage', () => {
it('should send tabs message and return true', () => {
const message = { action: 'ACTION', content: 'test' }
const tabId = 123
const callback = jest.fn()

// Call the sendTabsMessage function
const result = sendTabsMessage(message, tabId, callback)

// Check if BrowserAPI.tabs.sendMessage was called with the expected arguments
expect(BrowserAPI.tabs.sendMessage).toHaveBeenCalledWith(tabId, message, callback)
// Check if the result is true
expect(result).toBe(true)
})
})

describe('sendRuntimeMessage', () => {
it('should send runtime message with action and payload', () => {
const action = 'ACTION'
const payload = { data: 'payload' }
const callback = jest.fn()

// Call the sendRuntimeMessage function
sendRuntimeMessage(action, payload, callback)

// Check if BrowserAPI.runtime.sendMessage was called with the expected arguments
expect(BrowserAPI.runtime.sendMessage).toHaveBeenCalledWith(
{ type: action, content: payload },
callback,
)
})
})

describe('addMessageListener', () => {
it('should add message listener and call the provided callback', () => {
const listener = jest.fn()
const addListenerMock = jest.fn()

// Mock the runtime.onMessage.addListener function
BrowserAPI.runtime.onMessage = {
addListener: addListenerMock,
}

// Call the addMessageListener function
addMessageListener(listener)

// Check if BrowserAPI.runtime.onMessage.addListener was called with the expected argument
expect(addListenerMock).toHaveBeenCalledWith(listener)
})

it('adds message listener and calls the provided callback', () => {
const listenerMock = jest.fn()
// Call the addMessageListener function
addMessageListener(listenerMock)

// Check if BrowserAPI.runtime.onMessage.addListener was called with an anonymous function
expect(BrowserAPI.runtime.onMessage.addListener).toHaveBeenCalledWith(expect.any(Function))
const addedListener = BrowserAPI.runtime.onMessage.addListener.mock.calls[0][0]

// Simulate the listener being called
const sendResponseMock = jest.fn()
addedListener({ action: 'GET_MONETIZATION' }, {}, sendResponseMock)

// Check if the listenerMock was called with the expected arguments
expect(listenerMock).toHaveBeenCalledWith(
{ action: 'GET_MONETIZATION' },
{},
sendResponseMock,
)
})
})

describe('queryActiveTab', () => {
it('should query active tab and call the callback with the active tab', () => {
const activeTab = { id: 123 }
const callback = jest.fn()
const tabsMock = { query: jest.fn((_, cb) => cb([activeTab])) }

// Mock BrowserAPI.tabs with tabsMock
BrowserAPI.tabs = tabsMock

// Call the queryActiveTab function
queryActiveTab(callback)

// Check if the callback was called with the activeTab
expect(callback).toHaveBeenCalledWith(activeTab)
// Check if BrowserAPI.tabs.query was called with the expected arguments
expect(tabsMock.query).toHaveBeenCalledWith(
{ active: true, currentWindow: true },
expect.any(Function),
)
})
})
})
8 changes: 4 additions & 4 deletions src/pages/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { addTabChangeListener } from '@/lib/listeners'
import { sendTabsMessage } from '@/lib/messageUtils'
import { addMessageListener, BrowserAPI } from '@/src/lib'

const icon34 = BrowserAPI.runtime.getURL('icon-34.png')
const icon128 = BrowserAPI.runtime.getURL('icon-128.png')
const iconActive34 = BrowserAPI.runtime.getURL('icon-active-34.png')
const iconActive128 = BrowserAPI.runtime.getURL('icon-active-128.png')
const iconInactive34 = BrowserAPI.runtime.getURL('icon-inactive-34.png')
const iconInactive128 = BrowserAPI.runtime.getURL('icon-inactive-128.png')

const updateIcon = (active: boolean) => {
const iconData = {
'34': active ? iconActive34 : icon34,
'128': active ? iconActive128 : icon128,
'34': active ? iconActive34 : iconInactive34,
'128': active ? iconActive128 : iconInactive128,
}

if (BrowserAPI.action) {
Expand Down
4 changes: 0 additions & 4 deletions src/pages/content/style.scss

This file was deleted.

Loading

0 comments on commit 1c176d8

Please sign in to comment.