diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 4f7be51d2c4..aa47dc7dc50 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -4767,23 +4767,9 @@ "count": 2 } }, - "packages/phishing-controller/jest.config.cjs": { - "import/unambiguous": { - "count": 1 - } - }, "packages/phishing-controller/src/BulkTokenScan.test.ts": { "typescript/explicit-function-return-type": { "count": 2 - }, - "typescript/no-unsafe-call": { - "count": 1 - }, - "typescript/no-unsafe-member-access": { - "count": 1 - }, - "typescript/no-unsafe-return": { - "count": 2 } }, "packages/phishing-controller/src/CacheManager.test.ts": { @@ -4850,6 +4836,11 @@ "count": 1 } }, + "packages/phishing-controller/src/signature-address-extraction.test.ts": { + "vitest/no-conditional-in-test": { + "count": 1 + } + }, "packages/phishing-controller/src/tests/utils.ts": { "typescript/explicit-function-return-type": { "count": 2 diff --git a/packages/phishing-controller/jest.config.cjs b/packages/phishing-controller/jest.config.cjs deleted file mode 100644 index e94b4c5e85b..00000000000 --- a/packages/phishing-controller/jest.config.cjs +++ /dev/null @@ -1,26 +0,0 @@ -/* - * For a detailed explanation regarding each configuration property and type check, visit: - * https://jestjs.io/docs/configuration - */ - -const merge = require('deepmerge'); -const path = require('path'); - -const baseConfig = require('../../jest.config.packages.cjs'); - -const displayName = path.basename(__dirname); - -module.exports = merge(baseConfig, { - // The display name when running multiple projects - displayName, - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 98.95, - functions: 100, - lines: 100, - statements: 100, - }, - }, -}); diff --git a/packages/phishing-controller/package.json b/packages/phishing-controller/package.json index 7940289ae26..3673f6ed609 100644 --- a/packages/phishing-controller/package.json +++ b/packages/phishing-controller/package.json @@ -44,10 +44,10 @@ "messenger-action-types:check": "node --import ../../scripts/resolver/register.ts --experimental-transform-types ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --esm --check", "messenger-action-types:generate": "node --import ../../scripts/resolver/register.ts --experimental-transform-types ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --esm --generate", "since-latest-release": "../../scripts/since-latest-release.sh", - "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", - "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", - "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", - "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" + "test": "vitest", + "test:clean": "vitest --clearCache", + "test:verbose": "vitest --reporter=verbose", + "test:watch": "vitest --watch" }, "dependencies": { "@metamask/address-book-controller": "^8.0.0", @@ -64,16 +64,13 @@ "devDependencies": { "@metamask/auto-changelog": "^6.2.1", "@metamask/eth-sig-util": "^9.0.0", - "@types/jest": "^30.0.0", "@typescript/native": "npm:typescript@^7.0.2", - "deepmerge": "^4.3.1", - "jest": "^30.5.2", "nock": "^13.5.6", "rimraf": "^6.1.3", - "ts-jest": "^29.4.14", "typedoc": "^0.28.20", "typedoc-plugin-missing-exports": "^4.1.4", - "typescript": "npm:@typescript/typescript6@^6.0.2" + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vitest": "^5.0.0" }, "engines": { "node": "^22.14.0 || ^24" diff --git a/packages/phishing-controller/src/BulkTokenScan.test.ts b/packages/phishing-controller/src/BulkTokenScan.test.ts index 7f2ab12fc82..f063e0047d6 100644 --- a/packages/phishing-controller/src/BulkTokenScan.test.ts +++ b/packages/phishing-controller/src/BulkTokenScan.test.ts @@ -6,6 +6,8 @@ import type { MockAnyNamespace, } from '@metamask/messenger'; import nock, { cleanAll } from 'nock'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { MockInstance, MockedFunction } from 'vitest'; import { PhishingController, @@ -19,15 +21,16 @@ import type { import { TokenScanResultType } from './types.js'; import type { BulkTokenScanRequest, TokenScanApiResponse } from './types.js'; -jest.mock('@metamask/controller-utils', () => ({ - ...jest.requireActual('@metamask/controller-utils'), - safelyExecuteWithTimeout: jest.fn(), +vi.mock('@metamask/controller-utils', async () => ({ + ...(await vi.importActual( + '@metamask/controller-utils', + )), + safelyExecuteWithTimeout: vi.fn(), })); -const mockSafelyExecuteWithTimeout = - safelyExecuteWithTimeout as jest.MockedFunction< - typeof safelyExecuteWithTimeout - >; +const mockSafelyExecuteWithTimeout = safelyExecuteWithTimeout as MockedFunction< + typeof safelyExecuteWithTimeout +>; const controllerName = 'PhishingController'; @@ -98,20 +101,26 @@ function getPhishingController(options?: Partial) { describe('PhishingController - Bulk Token Scanning', () => { let controller: PhishingController; - let consoleErrorSpy: jest.SpyInstance; - let consoleWarnSpy: jest.SpyInstance; + let consoleErrorSpy: MockInstance; + let consoleWarnSpy: MockInstance; beforeEach(() => { controller = getPhishingController(); - consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); + consoleWarnSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => undefined); // Reset the mock to its default behavior (pass through to real implementation) mockSafelyExecuteWithTimeout.mockImplementation( - (fn, throwOnTimeout, timeout) => { - return jest - .requireActual('@metamask/controller-utils') - .safelyExecuteWithTimeout(fn, throwOnTimeout, timeout); + async (fn, throwOnTimeout, timeout) => { + return ( + await vi.importActual( + '@metamask/controller-utils', + ) + ).safelyExecuteWithTimeout(fn, throwOnTimeout, timeout); }, ); }); diff --git a/packages/phishing-controller/src/CacheManager.test.ts b/packages/phishing-controller/src/CacheManager.test.ts index 5bbf8c92cb5..0101e5f0c9c 100644 --- a/packages/phishing-controller/src/CacheManager.test.ts +++ b/packages/phishing-controller/src/CacheManager.test.ts @@ -1,16 +1,19 @@ +import type { Mock } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import { CacheManager } from './CacheManager.js'; import * as utils from './utils.js'; describe('CacheManager', () => { - let updateStateSpy: jest.Mock; + let updateStateSpy: Mock; let cache: CacheManager<{ value: string }>; beforeEach(() => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); - jest - .spyOn(utils, 'fetchTimeNow') - .mockImplementation(() => Math.floor(Date.now() / 1000)); - updateStateSpy = jest.fn(); + vi.useFakeTimers({ now: 0 }); + vi.spyOn(utils, 'fetchTimeNow').mockImplementation(() => + Math.floor(Date.now() / 1000), + ); + updateStateSpy = vi.fn(); cache = new CacheManager<{ value: string }>({ cacheTTL: 300, // 5 minutes maxCacheSize: 3, @@ -19,8 +22,8 @@ describe('CacheManager', () => { }); afterEach(() => { - jest.useRealTimers(); - jest.restoreAllMocks(); + vi.useRealTimers(); + vi.restoreAllMocks(); }); describe('constructor', () => { @@ -67,7 +70,7 @@ describe('CacheManager', () => { cache.set('key1', { value: 'value1' }); // Fast forward time past TTL - jest.advanceTimersByTime(301 * 1000); + vi.advanceTimersByTime(301 * 1000); expect(cache.get('key1')).toBeUndefined(); }); diff --git a/packages/phishing-controller/src/PathTrie.test.ts b/packages/phishing-controller/src/PathTrie.test.ts index 82ee3991e89..8a9e5c19167 100644 --- a/packages/phishing-controller/src/PathTrie.test.ts +++ b/packages/phishing-controller/src/PathTrie.test.ts @@ -1,3 +1,5 @@ +import { beforeEach, describe, expect, it } from 'vitest'; + import { convertListToTrie, deepCopyPathTrie, diff --git a/packages/phishing-controller/src/PhishingController.test.ts b/packages/phishing-controller/src/PhishingController.test.ts index 21d049dc353..45e0079385d 100644 --- a/packages/phishing-controller/src/PhishingController.test.ts +++ b/packages/phishing-controller/src/PhishingController.test.ts @@ -13,6 +13,8 @@ import { import type { TransactionControllerState } from '@metamask/transaction-controller'; import { strict as assert } from 'assert'; import nock, { cleanAll, isDone, pendingMocks } from 'nock'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { MockInstance } from 'vitest'; import { ListNames, @@ -193,7 +195,7 @@ function getPhishingController(options?: Partial): { describe('PhishingController', () => { afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); cleanAll(); }); @@ -320,7 +322,7 @@ describe('PhishingController', () => { }); it('should not re-request when an update is in progress', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const nockScope = nock(PHISHING_CONFIG_BASE_URL) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .delay(500) // delay promise resolution to generate "pending" state that lasts long enough to test. @@ -358,7 +360,7 @@ describe('PhishingController', () => { ], }, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); const pendingUpdate = controller.updateHotlist(); expect(controller.isHotlistOutOfDate()).toBe(true); @@ -413,11 +415,11 @@ describe('PhishingController', () => { }); it('should not have stalelist be out of date immediately after maybeUpdateState is called', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller, rootMessenger } = getPhishingController({ stalelistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isStalelistOutOfDate()).toBe(true); await rootMessenger.call('PhishingController:maybeUpdateState'); expect(controller.isStalelistOutOfDate()).toBe(false); @@ -425,24 +427,24 @@ describe('PhishingController', () => { }); it('should not be out of date after maybeUpdateStalelist is called but before refresh interval has passed', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller, rootMessenger } = getPhishingController({ stalelistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isStalelistOutOfDate()).toBe(true); await rootMessenger.call('PhishingController:maybeUpdateState'); - jest.advanceTimersByTime(1000 * 5); + vi.advanceTimersByTime(1000 * 5); expect(controller.isStalelistOutOfDate()).toBe(false); expect(nockScope.isDone()).toBe(true); }); it('should still be out of date while update is in progress', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller, rootMessenger } = getPhishingController({ stalelistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); // do not wait const maybeUpdatePhisingListPromise = rootMessenger.call( 'PhishingController:maybeUpdateState', @@ -450,13 +452,13 @@ describe('PhishingController', () => { expect(controller.isStalelistOutOfDate()).toBe(true); await maybeUpdatePhisingListPromise; expect(controller.isStalelistOutOfDate()).toBe(false); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isStalelistOutOfDate()).toBe(true); expect(nockScope.isDone()).toBe(true); }); it('should call update only if it is out of date, otherwise it should not call update', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller, rootMessenger } = getPhishingController({ stalelistRefreshInterval: 10, }); @@ -482,7 +484,7 @@ describe('PhishingController', () => { type: PhishingDetectorResultType.All, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); await rootMessenger.call('PhishingController:maybeUpdateState'); expect( @@ -526,15 +528,12 @@ describe('PhishingController', () => { }, ], }); - jest.useFakeTimers({ - doNotFake: ['nextTick', 'queueMicrotask'], - now: 50, - }); + vi.useFakeTimers({ now: 50 }); const { controller, rootMessenger } = getPhishingController({ hotlistRefreshInterval: 10, stalelistRefreshInterval: 50, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isHotlistOutOfDate()).toBe(true); await rootMessenger.call('PhishingController:maybeUpdateState'); expect(controller.isHotlistOutOfDate()).toBe(false); @@ -548,11 +547,11 @@ describe('PhishingController', () => { recentlyRemoved: [], lastFetchedAt: 1, }); - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller, rootMessenger } = getPhishingController({ c2DomainBlocklistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isC2DomainBlocklistOutOfDate()).toBe(true); await rootMessenger.call('PhishingController:maybeUpdateState'); expect(controller.isC2DomainBlocklistOutOfDate()).toBe(false); @@ -613,8 +612,8 @@ describe('PhishingController', () => { }); // Force the stalelist to be out of date and trigger update - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); - jest.advanceTimersByTime(1000 * 10); + vi.useFakeTimers({ now: 0 }); + vi.advanceTimersByTime(1000 * 10); await rootMessenger.call('PhishingController:maybeUpdateState'); @@ -636,13 +635,13 @@ describe('PhishingController', () => { }, ]); - jest.useRealTimers(); + vi.useRealTimers(); }); }); describe('isStalelistOutOfDate', () => { it('should not be out of date upon construction', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ stalelistRefreshInterval: 10, }); @@ -651,31 +650,31 @@ describe('PhishingController', () => { }); it('should not be out of date after some of the refresh interval has passed', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ stalelistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 5); + vi.advanceTimersByTime(1000 * 5); expect(controller.isStalelistOutOfDate()).toBe(false); }); it('should be out of date after the refresh interval has passed', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ stalelistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isStalelistOutOfDate()).toBe(true); }); it('should be out of date if the refresh interval has passed and an update is in progress', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ stalelistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); const pendingUpdate = controller.updateStalelist(); expect(controller.isStalelistOutOfDate()).toBe(true); @@ -685,7 +684,7 @@ describe('PhishingController', () => { }); it('should not be out of date if the phishing lists were just updated', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ stalelistRefreshInterval: 10, }); @@ -695,23 +694,23 @@ describe('PhishingController', () => { }); it('should not be out of date if the phishing lists were recently updated', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ stalelistRefreshInterval: 10, }); await controller.updateStalelist(); - jest.advanceTimersByTime(1000 * 5); + vi.advanceTimersByTime(1000 * 5); expect(controller.isStalelistOutOfDate()).toBe(false); }); it('should be out of date if the time elapsed since the last update equals the refresh interval', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ stalelistRefreshInterval: 10, }); await controller.updateStalelist(); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isStalelistOutOfDate()).toBe(true); }); @@ -719,7 +718,7 @@ describe('PhishingController', () => { describe('isHotlistOutOfDate', () => { it('should not be out of date upon construction', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ hotlistRefreshInterval: 10, }); @@ -728,27 +727,27 @@ describe('PhishingController', () => { }); it('should not be out of date after some of the refresh interval has passed', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ hotlistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 5); + vi.advanceTimersByTime(1000 * 5); expect(controller.isHotlistOutOfDate()).toBe(false); }); it('should be out of date after the refresh interval has passed', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ hotlistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isHotlistOutOfDate()).toBe(true); }); it('should be out of date if the refresh interval has passed and an update is in progress', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ hotlistRefreshInterval: 10, state: { @@ -767,7 +766,7 @@ describe('PhishingController', () => { ], }, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); const pendingUpdate = controller.updateHotlist(); expect(controller.isHotlistOutOfDate()).toBe(true); @@ -777,7 +776,7 @@ describe('PhishingController', () => { }); it('should not be out of date if the phishing lists were just updated', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ hotlistRefreshInterval: 10, }); @@ -787,23 +786,23 @@ describe('PhishingController', () => { }); it('should not be out of date if the phishing lists were recently updated', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ hotlistRefreshInterval: 10, }); await controller.updateHotlist(); - jest.advanceTimersByTime(1000 * 5); + vi.advanceTimersByTime(1000 * 5); expect(controller.isHotlistOutOfDate()).toBe(false); }); it('should be out of date if the time elapsed since the last update equals the refresh interval', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ hotlistRefreshInterval: 10, }); await controller.updateHotlist(); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isHotlistOutOfDate()).toBe(true); }); @@ -811,7 +810,7 @@ describe('PhishingController', () => { describe('isC2DomainBlocklistOutOfDate', () => { it('should not be out of date upon construction', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ c2DomainBlocklistRefreshInterval: 10, }); @@ -820,31 +819,31 @@ describe('PhishingController', () => { }); it('should not be out of date after some of the refresh interval has passed', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ c2DomainBlocklistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 5); + vi.advanceTimersByTime(1000 * 5); expect(controller.isC2DomainBlocklistOutOfDate()).toBe(false); }); it('should be out of date after the refresh interval has passed', () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ c2DomainBlocklistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isC2DomainBlocklistOutOfDate()).toBe(true); }); it('should be out of date if the refresh interval has passed and an update is in progress', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ c2DomainBlocklistRefreshInterval: 10, }); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); const pendingUpdate = controller.updateC2DomainBlocklist(); expect(controller.isC2DomainBlocklistOutOfDate()).toBe(true); @@ -854,7 +853,7 @@ describe('PhishingController', () => { }); it('should not be out of date if the C2 domain blocklist was just updated', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ c2DomainBlocklistRefreshInterval: 10, }); @@ -864,23 +863,23 @@ describe('PhishingController', () => { }); it('should not be out of date if the C2 domain blocklist was recently updated', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ c2DomainBlocklistRefreshInterval: 10, }); await controller.updateC2DomainBlocklist(); - jest.advanceTimersByTime(1000 * 5); + vi.advanceTimersByTime(1000 * 5); expect(controller.isC2DomainBlocklistOutOfDate()).toBe(false); }); it('should be out of date if the time elapsed since the last update equals the refresh interval', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); const { controller } = getPhishingController({ c2DomainBlocklistRefreshInterval: 10, }); await controller.updateC2DomainBlocklist(); - jest.advanceTimersByTime(1000 * 10); + vi.advanceTimersByTime(1000 * 10); expect(controller.isC2DomainBlocklistOutOfDate()).toBe(true); }); @@ -1602,7 +1601,7 @@ describe('PhishingController', () => { describe('updateStalelist', () => { it('should update lists with addition to hotlist', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 2 }); + vi.useFakeTimers({ now: 2 }); const exampleBlockedUrl = 'example-blocked-website.com'; const exampleRequestBlockedHash = '0415f1f12f07ddc4ef7e229da747c6c53a6a6474fbaf295a35d984ec0ece9455'; @@ -1659,7 +1658,7 @@ describe('PhishingController', () => { }); it('should update lists with removal diff from hotlist', async () => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 2 }); + vi.useFakeTimers({ now: 2 }); const exampleBlockedUrl = 'example-blocked-website.com'; const exampleRequestBlockedHash = '0415f1f12f07ddc4ef7e229da747c6c53a6a6474fbaf295a35d984ec0ece9455'; @@ -1870,10 +1869,7 @@ describe('PhishingController', () => { describe('an update is in progress', () => { it('should not fetch phishing lists again', async () => { - jest.useFakeTimers({ - doNotFake: ['nextTick', 'queueMicrotask'], - now: 0, - }); + vi.useFakeTimers({ now: 0 }); const nockScope = nock(PHISHING_CONFIG_BASE_URL) .get(METAMASK_STALELIST_FILE) .delay(100) @@ -1895,7 +1891,7 @@ describe('PhishingController', () => { const firstPromise = controller.updateStalelist(); const secondPromise = controller.updateStalelist(); - jest.advanceTimersByTime(1000 * 100); + vi.advanceTimersByTime(1000 * 100); await firstPromise; await secondPromise; @@ -1906,10 +1902,7 @@ describe('PhishingController', () => { }); it('should wait until the in-progress update has completed', async () => { - jest.useFakeTimers({ - doNotFake: ['nextTick', 'queueMicrotask'], - now: 0, - }); + vi.useFakeTimers({ now: 0 }); nock(PHISHING_CONFIG_BASE_URL) .get(METAMASK_STALELIST_FILE) .delay(100) @@ -1930,7 +1923,7 @@ describe('PhishingController', () => { const { controller } = getPhishingController(); const firstPromise = controller.updateStalelist(); const secondPromise = controller.updateStalelist(); - jest.advanceTimersByTime(1000 * 99); + vi.advanceTimersByTime(1000 * 99); await expect(secondPromise).toNeverResolve(); @@ -2834,7 +2827,7 @@ describe('PhishingController', () => { rootMessenger = createdMessenger; - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); }); it('should return the scan result', async () => { @@ -2889,7 +2882,7 @@ describe('PhishingController', () => { .reply(200, {}); const promise = rootMessenger.call('PhishingController:scanUrl', testUrl); - jest.advanceTimersByTime(8000); + vi.advanceTimersByTime(8000); const response = await promise; expect(response).toMatchObject({ hostname: '', @@ -3025,7 +3018,7 @@ describe('PhishingController', () => { recommendedAction: RecommendedAction.Block, }); - const fetchSpy = jest.spyOn(global, 'fetch'); + const fetchSpy = vi.spyOn(global, 'fetch'); const resultA1 = await rootMessenger.call( 'PhishingController:scanUrl', @@ -3191,11 +3184,11 @@ describe('PhishingController', () => { rootMessenger = createdMessenger; - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('should return the scan results for multiple URLs', async () => { @@ -3296,7 +3289,7 @@ describe('PhishingController', () => { 'PhishingController:bulkScanUrls', testUrls, ); - jest.advanceTimersByTime(15000); + vi.advanceTimersByTime(15000); const response = await promise; expect(response).toStrictEqual({ results: {}, @@ -3661,11 +3654,11 @@ describe('PhishingController', () => { rootMessenger = createdMessenger; - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('will return the scan result for a valid address', async () => { @@ -3731,7 +3724,7 @@ describe('PhishingController', () => { testChainId, testAddress, ); - jest.advanceTimersByTime(5000); + vi.advanceTimersByTime(5000); const response = await promise; expect(response).toMatchObject({ result_type: AddressScanResultType.ErrorResult, @@ -3821,7 +3814,7 @@ describe('PhishingController', () => { }); it('will cache scan results and return them on subsequent calls', async () => { - const fetchSpy = jest.spyOn(global, 'fetch'); + const fetchSpy = vi.spyOn(global, 'fetch'); const scope = nock(SECURITY_ALERTS_BASE_URL) .post(ADDRESS_SCAN_ENDPOINT, { @@ -3944,11 +3937,11 @@ describe('PhishingController', () => { beforeEach(() => { const { rootMessenger: createdMessenger } = getPhishingController(); rootMessenger = createdMessenger; - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('will return approvals for a valid address and chain', async () => { @@ -4038,7 +4031,7 @@ describe('PhishingController', () => { testChainId, testAddress, ); - jest.advanceTimersByTime(5000); + vi.advanceTimersByTime(5000); const response = await promise; expect(response).toStrictEqual({ approvals: [] }); expect(scope.isDone()).toBe(false); @@ -4084,10 +4077,10 @@ describe('PhishingController', () => { describe('URL Scan Cache', () => { beforeEach(() => { - jest.useFakeTimers({ doNotFake: ['nextTick', 'queueMicrotask'], now: 0 }); + vi.useFakeTimers({ now: 0 }); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); cleanAll(); }); @@ -4095,7 +4088,7 @@ describe('URL Scan Cache', () => { const testDomain = 'example.com'; // Spy on the fetch function to track calls - const fetchSpy = jest.spyOn(global, 'fetch'); + const fetchSpy = vi.spyOn(global, 'fetch'); nock(PHISHING_DETECTION_BASE_URL) .get( @@ -4165,7 +4158,7 @@ describe('URL Scan Cache', () => { ); // Before TTL expires, should use cache - jest.advanceTimersByTime((cacheTTL - 10) * 1000); + vi.advanceTimersByTime((cacheTTL - 10) * 1000); await rootMessenger.call( 'PhishingController:scanUrl', `https://${testDomain}`, @@ -4173,7 +4166,7 @@ describe('URL Scan Cache', () => { expect(pendingMocks()).toHaveLength(1); // One mock remaining // After TTL expires, should fetch again - jest.advanceTimersByTime(11 * 1000); + vi.advanceTimersByTime(11 * 1000); await rootMessenger.call( 'PhishingController:scanUrl', `https://${testDomain}`, @@ -4218,14 +4211,14 @@ describe('URL Scan Cache', () => { 'PhishingController:scanUrl', `https://${domains[0]}`, ); - jest.advanceTimersByTime(1000); // Ensure different timestamps + vi.advanceTimersByTime(1000); // Ensure different timestamps await rootMessenger.call( 'PhishingController:scanUrl', `https://${domains[1]}`, ); // This should evict the oldest entry (domain1) - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); await rootMessenger.call( 'PhishingController:scanUrl', `https://${domains[2]}`, @@ -4420,7 +4413,7 @@ describe('URL Scan Cache', () => { describe('Transaction Controller State Change Integration', () => { let controller: PhishingController; let globalMessenger: RootMessenger; - let bulkScanTokensSpy: jest.SpyInstance; + let bulkScanTokensSpy: MockInstance; beforeEach(() => { const { messenger, rootMessenger } = setupMessenger(); @@ -4431,7 +4424,7 @@ describe('Transaction Controller State Change Integration', () => { messenger, }); - bulkScanTokensSpy = jest + bulkScanTokensSpy = vi .spyOn(controller, 'bulkScanTokens') .mockResolvedValue({}); }); @@ -4568,7 +4561,9 @@ describe('Transaction Controller State Change Integration', () => { }); it('handles errors in transaction state change processing', async () => { - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); const stateChangePayload = createMockStateChangePayload([]); @@ -4595,7 +4590,9 @@ describe('Transaction Controller State Change Integration', () => { }); it('handles errors in bulk token scanning', async () => { - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); bulkScanTokensSpy.mockRejectedValue(new Error('Scanning failed')); @@ -4628,7 +4625,9 @@ describe('Transaction Controller State Change Integration', () => { }); it('continues bulk token scanning if known recipient updates fail', async () => { - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); const mockTransaction = createMockTransaction('test-tx-1', [ TEST_ADDRESSES.USDC, @@ -5390,7 +5389,9 @@ describe('Address poisoning detection', () => { it('logs when transaction state hydration fails', () => { const { messenger, rootMessenger } = setupMessenger(); const error = new Error('Transaction state unavailable'); - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); rootMessenger.unregisterActionHandler('TransactionController:getState'); rootMessenger.registerActionHandler( @@ -5416,7 +5417,9 @@ describe('Address poisoning detection', () => { it('logs when address book state hydration fails', () => { const { messenger, rootMessenger } = setupMessenger(); const error = new Error('Address book state unavailable'); - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); rootMessenger.unregisterActionHandler('AddressBookController:getState'); rootMessenger.registerActionHandler( diff --git a/packages/phishing-controller/src/PhishingDetector.test.ts b/packages/phishing-controller/src/PhishingDetector.test.ts index 8736b47c8fe..8fe36431f98 100644 --- a/packages/phishing-controller/src/PhishingDetector.test.ts +++ b/packages/phishing-controller/src/PhishingDetector.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it, vi } from 'vitest'; + import { PhishingDetector } from './PhishingDetector.js'; import type { PhishingDetectorOptions } from './PhishingDetector.js'; import { formatHostnameToUrl } from './tests/utils.js'; @@ -89,7 +91,7 @@ describe('PhishingDetector', () => { {}, ])('logs an error when config name is %p', async (mockInvalidName) => { // Mock console.error to track error logs without cluttering test output - const consoleErrorMock = jest.spyOn(console, 'error'); + const consoleErrorMock = vi.spyOn(console, 'error'); let detector; @@ -121,7 +123,7 @@ describe('PhishingDetector', () => { }); it('drops the invalid config and retains the valid config', async () => { - const consoleErrorMock = jest.spyOn(console, 'error'); + const consoleErrorMock = vi.spyOn(console, 'error'); let detector: PhishingDetector | undefined; @@ -169,7 +171,7 @@ describe('PhishingDetector', () => { }); it('logs an error when tolerance is provided without fuzzylist', async () => { - const consoleErrorMock = jest.spyOn(console, 'error'); + const consoleErrorMock = vi.spyOn(console, 'error'); let detector; @@ -210,7 +212,7 @@ describe('PhishingDetector', () => { 'logs an error when config version is %p', async (mockInvalidVersion) => { // Mock console.error to track error logs without cluttering test output - const consoleErrorMock = jest.spyOn(console, 'error'); + const consoleErrorMock = vi.spyOn(console, 'error'); let detector; diff --git a/packages/phishing-controller/src/address-poisoning.test.ts b/packages/phishing-controller/src/address-poisoning.test.ts index cd89b4b2b40..33466668658 100644 --- a/packages/phishing-controller/src/address-poisoning.test.ts +++ b/packages/phishing-controller/src/address-poisoning.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { findSimilarAddresses } from './address-poisoning.js'; function getNumberRange(start: number, end: number): number[] { diff --git a/packages/phishing-controller/src/request-source.test.ts b/packages/phishing-controller/src/request-source.test.ts index 58645661b2a..94e618fe91f 100644 --- a/packages/phishing-controller/src/request-source.test.ts +++ b/packages/phishing-controller/src/request-source.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { REQUEST_SOURCE_HEADER, RequestSourceFlow, diff --git a/packages/phishing-controller/src/signature-address-extraction.test.ts b/packages/phishing-controller/src/signature-address-extraction.test.ts index 7e53a162469..c742025ca18 100644 --- a/packages/phishing-controller/src/signature-address-extraction.test.ts +++ b/packages/phishing-controller/src/signature-address-extraction.test.ts @@ -1,4 +1,5 @@ import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; +import { describe, expect, it } from 'vitest'; import { DEFAULT_MAX_SIGNATURE_ADDRESSES, diff --git a/packages/phishing-controller/src/utils.test.ts b/packages/phishing-controller/src/utils.test.ts index 873968e6284..ef2585cc19d 100644 --- a/packages/phishing-controller/src/utils.test.ts +++ b/packages/phishing-controller/src/utils.test.ts @@ -1,3 +1,6 @@ +import type { MockInstance } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import { ListKeys, ListNames } from './PhishingController.js'; import type { PhishingListState } from './PhishingController.js'; import type { TokenScanResultType } from './types.js'; @@ -84,15 +87,12 @@ const exampleRemoveDiff = { describe('fetchTimeNow', () => { afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('correctly converts time from milliseconds to seconds', () => { const testTime = 1674773005000; - jest.useFakeTimers({ - doNotFake: ['nextTick', 'queueMicrotask'], - now: testTime, - }); + vi.useFakeTimers({ now: testTime }); const result = fetchTimeNow(); expect(result).toBe(1674773005); }); @@ -100,7 +100,7 @@ describe('fetchTimeNow', () => { describe('applyDiffs', () => { afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('adds a valid addition diff to the state then sets lastUpdated to be the time of the latest diff', () => { @@ -130,10 +130,7 @@ describe('applyDiffs', () => { it('does not add an addition diff to the state if it is older than the state.lastUpdated time.', () => { const testTime = 1674773005000; - jest.useFakeTimers({ - doNotFake: ['nextTick', 'queueMicrotask'], - now: testTime, - }); + vi.useFakeTimers({ now: testTime }); const testExistingState = { ...exampleListState, lastUpdated: 1674773005 }; const result = applyDiffs( testExistingState, @@ -145,10 +142,7 @@ describe('applyDiffs', () => { it('does not remove a url from the state if the removal diff is older than the state.lastUpdated time.', () => { const testTime = 1674773005000; - jest.useFakeTimers({ - doNotFake: ['nextTick', 'queueMicrotask'], - now: testTime, - }); + vi.useFakeTimers({ now: testTime }); const testExistingState = { ...exampleListState, lastUpdated: 1674773005, @@ -170,10 +164,7 @@ describe('applyDiffs', () => { it('does not add an addition diff to the state if it does not contain the same targetlist listkey.', () => { const testTime = 1674773005000; - jest.useFakeTimers({ - doNotFake: ['nextTick', 'queueMicrotask'], - now: testTime, - }); + vi.useFakeTimers({ now: testTime }); const testExistingState = { ...exampleListState, lastUpdated: 1674773005 }; const result = applyDiffs( testExistingState, @@ -188,10 +179,7 @@ describe('applyDiffs', () => { it('does not remove a url from the state if it does not contain the same targetlist listkey.', () => { const testTime = 1674773005000; - jest.useFakeTimers({ - doNotFake: ['nextTick', 'queueMicrotask'], - now: testTime, - }); + vi.useFakeTimers({ now: testTime }); const testExistingState = { ...exampleListState, lastUpdated: 1674773005, @@ -565,10 +553,10 @@ describe('domainToParts', () => { }); describe('processConfigs', () => { - let consoleErrorMock: jest.SpyInstance; + let consoleErrorMock: MockInstance; beforeEach(() => { - consoleErrorMock = jest.spyOn(console, 'error'); + consoleErrorMock = vi.spyOn(console, 'error'); }); afterEach(() => { @@ -727,10 +715,12 @@ describe('processConfigs', () => { }); describe('processDomainList', () => { - let consoleWarnMock: jest.SpyInstance; + let consoleWarnMock: MockInstance; beforeEach(() => { - consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation(); + consoleWarnMock = vi + .spyOn(console, 'warn') + .mockImplementation(() => undefined); }); afterEach(() => { @@ -1339,7 +1329,7 @@ describe('isAddressScanSupportedChainId', () => { describe('splitCacheHits', () => { const mockCache = { - get: jest.fn(), + get: vi.fn(), }; beforeEach(() => { diff --git a/packages/phishing-controller/tsconfig.json b/packages/phishing-controller/tsconfig.json index 0e1f32126e4..9a8ac800349 100644 --- a/packages/phishing-controller/tsconfig.json +++ b/packages/phishing-controller/tsconfig.json @@ -1,8 +1,5 @@ { "extends": "../../tsconfig.packages.json", - "compilerOptions": { - "types": ["jest"] - }, "references": [ { "path": "../base-controller" diff --git a/packages/phishing-controller/vitest.config.js b/packages/phishing-controller/vitest.config.js new file mode 100644 index 00000000000..6c3a55cd9e4 --- /dev/null +++ b/packages/phishing-controller/vitest.config.js @@ -0,0 +1,20 @@ +import { basename, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { mergeConfig } from 'vitest/config'; + +import baseConfig from '../../vitest.config.packages.js'; + +export default mergeConfig(baseConfig, { + test: { + name: basename(dirname(fileURLToPath(import.meta.url))), + + coverage: { + thresholds: { + branches: 98.95, + functions: 100, + lines: 100, + statements: 100, + }, + }, + }, +}); diff --git a/test-runner-package-map.cjs b/test-runner-package-map.cjs index 7f336e36a40..1d73ddf240a 100644 --- a/test-runner-package-map.cjs +++ b/test-runner-package-map.cjs @@ -74,7 +74,6 @@ const PACKAGES_ON_JEST = new Set([ '@metamask/permission-controller', '@metamask/permission-log-controller', '@metamask/perps-controller', - '@metamask/phishing-controller', '@metamask/platform-api-docs', '@metamask/preferences-controller', '@metamask/profile-controller', diff --git a/yarn.lock b/yarn.lock index 6d7f5e33200..2d352fb7be0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8490,20 +8490,17 @@ __metadata: "@metamask/messenger": "npm:^3.0.0" "@metamask/transaction-controller": "npm:^72.2.1" "@noble/hashes": "npm:^1.8.0" - "@types/jest": "npm:^30.0.0" "@types/punycode": "npm:^2.1.4" "@typescript/native": "npm:typescript@^7.0.2" - deepmerge: "npm:^4.3.1" ethereum-cryptography: "npm:^3.2.0" fastest-levenshtein: "npm:^1.0.16" - jest: "npm:^30.5.2" nock: "npm:^13.5.6" punycode: "npm:^2.3.1" rimraf: "npm:^6.1.3" - ts-jest: "npm:^29.4.14" typedoc: "npm:^0.28.20" typedoc-plugin-missing-exports: "npm:^4.1.4" typescript: "npm:@typescript/typescript6@^6.0.2" + vitest: "npm:^5.0.0" languageName: unknown linkType: soft