Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,9 @@ This repo is a Yarn 4 monorepo of MetaMask Snaps. The two products live in `pack

Standard commands are documented above (see "Running tests", "Linting and formatting", "Building packages"). Non-obvious caveats for running things here:

- **Build before testing.** A Snap's Jest suite (`@metamask/snaps-jest`) expects the built bundle. CI always runs `yarn workspace <pkg> build` before `yarn workspace <pkg> run test`. If tests behave unexpectedly, run `yarn build` (or the per-package build) first.
- **`yarn lint` deletes `dist/`.** `lint:eslint` runs `build:only-clean` (`rimraf -g 'packages/*/dist'`) before linting. After running `yarn lint`, re-run `yarn build` before serving a snap or running snap tests.
- **Install builds libraries.** `yarn` / `yarn allow-scripts` runs `yarn build:libs` (`ts-bridge` packages only). Snap bundles are not built on install; run `yarn build` or `yarn build:snaps` when you need `dist/bundle.js`, `installSnap` tests, or `serve`. Solana unit tests use `snaps-jest` and will lazy-build the Snap via `jest.globalSetup` if the bundle is missing.
- **Unit tests vs integration tests.** Most Snap unit Jest configs use the Node environment and do not require a Snap bundle. Tests that call `installSnap` live under `integration-test/` or `*.integration.test.*` and need a prior snap build (`yarn build` / `yarn build:snaps`).
- **`yarn lint` cleans then restores library `dist/`.** `lint:eslint` still deletes `packages/*/dist` before eslint, then runs `yarn build:libs`. Snap bundles still need `yarn build` / `yarn build:snaps` afterward.
- **Running a snap:** `yarn workspace <pkg> run serve` serves the pre-built bundle at `http://localhost:8080` (`/snap.manifest.json` and `/dist/bundle.js`); `yarn workspace <pkg> run start` (`mm-snap watch`) rebuilds on change. Both snaps use port 8080, so only run one at a time.
- **No headless end-to-end.** Fully exercising a snap normally requires the MetaMask extension in a browser, which isn't available headless. Use the `snaps-jest` test suites (they install the snap and invoke its JSON-RPC methods, e.g. sample-snap's `hello`) to exercise core functionality without a browser.
- **`.env` is optional** for `bitcoin-wallet-snap`; `snap.config.ts` reads it via dotenv but all values have sane defaults (see `.env.example`), so the snap builds and serves without one.
- **No headless end-to-end.** Fully exercising a snap normally requires the MetaMask extension in a browser, which isn't available headless. Use the `snaps-jest` integration suites (they install the snap and invoke its JSON-RPC methods, e.g. sample-snap's `hello`) to exercise core functionality without a browser.
- **Configure `.env` before building or testing wallet snaps.** Copy each package's `.env.example` / `.env.sample` and set values locally; `snap.config.ts` loads them via dotenv but does not provide runtime fallbacks.
1 change: 0 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Hi! Welcome to the contributor documentation for the `internal-snaps` monorepo.
- [Testing changes to packages in other projects](./processes/testing-changes-in-other-projects.md)
- [Building packages](./processes/building.md)
- [Adding new packages to the monorepo](./processes/adding-new-packages.md)
- [Migrating external snaps to the monorepo](./processes/snap-migration-process-guide.md)

## Code guidelines

Expand Down
11 changes: 10 additions & 1 deletion docs/getting-started/setting-up-your-environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,13 @@
- If you are using [NVM](https://github.com/creationix/nvm#installation) (recommended), running `nvm install` will install the latest version, and running `nvm use` will automatically choose the right Node version for you.
2. Run `corepack enable` to install [Yarn](https://yarnpkg.com) via [Corepack](https://github.com/nodejs/corepack?tab=readme-ov-file#how-to-install).
- If you have Yarn installed globally via Homebrew or NPM, you'll need to uninstall it before running this command.
3. Run `yarn install` to install dependencies and run any required post-install scripts.
3. Run `yarn install` to install dependencies and run post-install hooks.
- After install, library packages are built automatically via `yarn build:libs` (chained from `allow-scripts`).
- Snap bundles are **not** built during install. Run `yarn build` / `yarn build:snaps` when you need `dist/bundle.js`, `serve`, or `installSnap` integration tests.
4. Copy each wallet snap's `.env.example` / `.env.sample` to `.env` and configure it before building or testing snaps.
5. Verify the tree:

```bash
yarn typecheck
yarn test
```
4 changes: 3 additions & 1 deletion docs/processes/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@

Built files show up in the `dist/` directory in each package. These are the files which will ultimately be published to NPM.

- Run `yarn build` to build all packages in the monorepo.
- Run `yarn build` to build all packages in the monorepo (libraries and snaps, topological).
- Run `yarn build:libs` to build only non-snap (library) packages. This also runs automatically after `yarn install`.
- Run `yarn build:snaps` to build only Snap packages (`mm-snap`).
- Run `yarn workspace <workspaceName> run build` to build a single package.
9 changes: 6 additions & 3 deletions docs/processes/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

Please follow the [MetaMask unit testing guidelines](https://github.com/MetaMask/contributor-docs/blob/main/docs/testing/unit-testing.md) when writing tests.

If you need to customize the behavior of Jest for a package, see `jest.config.js` within that package.
If you need to customize the behavior of Jest for a package, see `jest.config.js` / `jest.config.mjs` within that package.

- Run `yarn workspace <workspaceName> run test` to run all tests for a package.
- Run `yarn workspace <workspaceName> run test` to run **unit** tests for a package.
- Run `yarn workspace <workspaceName> run jest --no-coverage <file>` to run a test file within the context of a package.
- Run `yarn test` to run tests for all packages.
- Run `yarn test` to run unit tests for packages in the monorepo.
- For Snaps that define them, run `yarn workspace <workspaceName> run test:integration` for `installSnap` / SES suites. Those require a prior Snap build (`yarn build` or `yarn build:snaps`).

> **Note**
>
> `workspaceName` in these commands is the `name` field within a package's `package.json`, e.g., `@metamask/bitcoin-wallet-snap`.
>
> Snap unit configs use the Node environment (or, for Solana, `snaps-jest` with a lazy build via `jest.globalSetup`). Do not put `installSnap` tests in the default unit suite — use `integration-test/` or `*.integration.test.*` instead.
9 changes: 3 additions & 6 deletions eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,7 @@
"count": 2
}
},
"packages/solana-wallet-snap/src/features/confirmation/views/ConfirmSignIn/render.test.tsx": {
"packages/solana-wallet-snap/src/features/confirmation/views/ConfirmSignIn/render.integration.test.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
},
Expand All @@ -1204,7 +1204,7 @@
"count": 2
}
},
"packages/solana-wallet-snap/src/features/confirmation/views/ConfirmSignMessage/render.test.tsx": {
"packages/solana-wallet-snap/src/features/confirmation/views/ConfirmSignMessage/render.integration.test.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
},
Expand All @@ -1227,7 +1227,7 @@
"count": 3
}
},
"packages/solana-wallet-snap/src/features/confirmation/views/ConfirmTransactionRequest/render.test.tsx": {
"packages/solana-wallet-snap/src/features/confirmation/views/ConfirmTransactionRequest/render.integration.test.tsx": {
"@typescript-eslint/explicit-function-return-type": {
"count": 2
},
Expand Down Expand Up @@ -1258,9 +1258,6 @@
},
"@typescript-eslint/no-shadow": {
"count": 1
},
"jest/unbound-method": {
"count": 2
}
},
"packages/solana-wallet-snap/src/index.ts": {
Expand Down
3 changes: 2 additions & 1 deletion jest.config.packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ module.exports = {

// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// Here we ensure that Jest resolves `@metamask/*` imports to the uncompiled source code for packages that live in this repo.
// NOTE: This must be synchronized with the `paths` option in `tsconfig.base.json`.
// NOTE: This must be synchronized with the `paths` option in `tsconfig.packages.json`
// (`@metamask/*` → `packages/*/src` from the monorepo root).
moduleNameMapper: {
'^@metamask/json-rpc-engine/v2$': [
'<rootDir>/../json-rpc-engine/src/v2/index.ts',
Expand Down
40 changes: 40 additions & 0 deletions jest.config.snaps.unit.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const path = require('path');

/**
* Shared Jest config for Snap unit tests.
*
* Uses the Node environment (not @metamask/snaps-jest) so suites run without
* a pre-built dist/bundle.js. SES / installSnap coverage belongs in each
* package's integration config instead.
*
* Workspace @metamask/* mapping must stay synchronized with
* tsconfig.packages.json paths (./packages/<name>/src).
*/
module.exports = {
testEnvironment: 'node',
preset: 'ts-jest',
transform: {
'^.+\\.(t|j)sx?$': 'ts-jest',
},
collectCoverage: true,
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: ['.*/index\\.ts'],
coverageProvider: 'babel',
coverageReporters: ['text', 'html', 'json-summary', 'lcov'],
resetMocks: true,
testMatch: ['**/src/**/?(*.)+(spec|test).[tj]s?(x)'],
testPathIgnorePatterns: [
'/node_modules/',
'/integration-test/',
'\\.integration\\.test\\.[tj]sx?$',
],
moduleNameMapper: {
'^@metamask/utils/node$': require.resolve('@metamask/utils/node'),
// Only rewrite bare workspace package names. Subpath imports such as
// `@metamask/snaps-controllers/node` must fall through to node_modules.
'^@metamask/([^/]+)$': [
path.join(__dirname, 'packages/$1/src'),
path.join(__dirname, 'node_modules/@metamask/$1'),
],
},
};
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
"url": "https://github.com/MetaMask/internal-snaps.git"
},
"workspaces": [
"examples/*",
"packages/*"
],
"files": [],
"scripts": {
"allow-scripts": "yarn exec allow-scripts && yarn build:libs",
"build": "yarn workspaces foreach --all --no-private --topological-dev --parallel --interlaced --verbose run build",
"build:clean": "yarn build:only-clean && yarn build",
"build:docs": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run build:docs",
"build:libs": "node ./scripts/build-workspace-kind.mjs",
"build:only-clean": "rimraf -g 'packages/*/dist'",
"build:snaps": "node ./scripts/build-workspace-kind.mjs --snaps",
"build:types": "tsc --build tsconfig.build.json --verbose",
"changelog:update": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run changelog:update",
"changelog:validate": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run changelog:validate",
Expand All @@ -25,7 +27,7 @@
"lint": "yarn lint:eslint && echo && yarn lint:misc --check && yarn constraints && yarn lint:dependencies && yarn readme-content:check",
"lint:dependencies": "depcheck && yarn dedupe --check",
"lint:dependencies:fix": "depcheck && yarn dedupe",
"lint:eslint": "yarn build:only-clean && NODE_OPTIONS='--max-old-space-size=6144' yarn eslint",
"lint:eslint": "yarn build:only-clean && NODE_OPTIONS='--max-old-space-size=6144' yarn eslint && yarn build:libs",
"lint:fix": "yarn lint:eslint --fix --prune-suppressions && echo && yarn lint:misc --write && yarn constraints --fix && yarn lint:dependencies:fix && yarn readme-content:update",
"lint:misc": "oxfmt --ignore-path .gitignore",
"lint:misc:check": "yarn lint:misc --check",
Expand Down
32 changes: 6 additions & 26 deletions packages/bitcoin-wallet-snap/jest.config.mjs
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
// @ts-check
import { createRequire } from 'node:module';

const requireWithCreateRequire = createRequire(import.meta.url);
const shared = requireWithCreateRequire('../../jest.config.snaps.unit.cjs');

/**
* @type {import('ts-jest').JestConfigWithTsJest}
*/
const config = {
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,

// An array of glob patterns indicating a set of files for which coverage information should be collected
collectCoverageFrom: ['./src/**/*.ts', './src/**/*.tsx'],

// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',

// An array of regexp pattern strings used to skip coverage collection
coveragePathIgnorePatterns: ['.*/index\\.ts'],

// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'babel',

// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: ['text', 'html', 'json-summary', 'lcov'],

// An object that configures minimum threshold enforcement for coverage results
...shared,
coverageThreshold: {
global: {
branches: 65.5,
Expand All @@ -30,13 +17,6 @@ const config = {
statements: 74.57,
},
},

preset: '@metamask/snaps-jest',
transform: {
'^.+\\.(t|j)sx?$': 'ts-jest',
},
resetMocks: true,
testMatch: ['**/src/**/?(*.)+(spec|test).[tj]s?(x)'],
};

export default config;
12 changes: 2 additions & 10 deletions packages/bitcoin-wallet-snap/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
{
"extends": "../../tsconfig.packages.json",
"extends": "../../tsconfig.snaps.json",
"compilerOptions": {
"baseUrl": "./",
"lib": ["ES2021", "DOM"],
"resolveJsonModule": true /* lets us import JSON modules from within TypeScript modules. */,
"jsx": "react-jsx",
"jsxImportSource": "@metamask/snaps-sdk",
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"noErrorTruncation": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["jest"]
"noUncheckedIndexedAccess": true
},
"include": ["**/*.ts", "**/*.tsx", "locales/*.json"]
}
9 changes: 5 additions & 4 deletions packages/sample-snap/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const shared = require('../../jest.config.snaps.unit.cjs');

module.exports = {
preset: '@metamask/snaps-jest',
transform: {
'^.+\\.(t|j)sx?$': 'ts-jest',
},
...shared,
// Sample snap coverage is exercised via integration tests (`installSnap`).
passWithNoTests: true,
};
7 changes: 7 additions & 0 deletions packages/sample-snap/jest.integration.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
preset: '@metamask/snaps-jest',
transform: {
'^.+\\.(t|j)sx?$': 'ts-jest',
},
testMatch: ['**/integration-test/**/*.[jt]s?(x)'],
};
1 change: 1 addition & 0 deletions packages/sample-snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"start": "mm-snap watch",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
"test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.integration.config.js --reporters=jest-silent-reporter",
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
"since-latest-release": "../../scripts/since-latest-release.sh"
Expand Down
9 changes: 1 addition & 8 deletions packages/sample-snap/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
{
"extends": "../../tsconfig.packages.json",
"compilerOptions": {
"baseUrl": "./",
"jsx": "react-jsx",
"skipLibCheck": true,
"jsxImportSource": "@metamask/snaps-sdk",
"types": ["jest"]
},
"extends": "../../tsconfig.snaps.json",
"include": ["**/*.ts", "**/*.tsx", "locales/*.json"]
}
7 changes: 7 additions & 0 deletions packages/solana-wallet-snap/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ module.exports = {
collectCoverage: true,
setupFilesAfterEnv: ['./jest.setup.ts'],
coverageReporters: ['html', 'json-summary', 'text', 'lcov'],
// Lazy-build the Snap bundle when missing so `yarn test` works after install.
globalSetup: '<rootDir>/jest.globalSetup.cjs',
testPathIgnorePatterns: [
'/node_modules/',
'/integration-test/',
'\\.integration\\.test\\.[tj]sx?$',
],
};
42 changes: 42 additions & 0 deletions packages/solana-wallet-snap/jest.globalSetup.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { access } = require('node:fs/promises');
const { spawn } = require('node:child_process');
const { join } = require('node:path');

/**
* Ensure `dist/bundle.js` exists before snaps-jest starts its HTTP server.
* Library packages are already built by `yarn build:libs` during install.
*
* @returns {Promise<void>}
*/
module.exports = async function globalSetup() {
const bundlePath = join(__dirname, 'dist', 'bundle.js');
try {
await access(bundlePath);
return;
} catch {
// Bundle missing — build below.
}

console.log(
'[solana-wallet-snap] dist/bundle.js missing; running yarn build before tests…',
);

const code = await new Promise((resolve, reject) => {
const child = spawn('yarn', ['build'], {
cwd: __dirname,
stdio: 'inherit',
shell: true,
env: process.env,

Check failure on line 29 in packages/solana-wallet-snap/jest.globalSetup.cjs

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (lint:eslint)

Unexpected use of process.env
});
child.on('error', reject);
child.on('close', (exitCode) => {
resolve(exitCode ?? 1);
});
});

if (code !== 0) {
throw new Error(
'[solana-wallet-snap] Failed to build Snap bundle required by snaps-jest',
);
}
};
13 changes: 13 additions & 0 deletions packages/solana-wallet-snap/jest.integration.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
preset: '@metamask/snaps-jest',
transform: {
'^.+\\.(t|j)sx?$': 'ts-jest',
'^.+\\.svg$': '<rootDir>/svg-transformer.js',
},
testMatch: [
'**/integration-test/**/*.[jt]s?(x)',
'**/src/**/*.integration.test.[jt]s?(x)',
],
maxWorkers: 1,
setupFilesAfterEnv: ['./jest.setup.ts'],
};
1 change: 1 addition & 0 deletions packages/solana-wallet-snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"test:core:watch": "yarn test:core --watch",
"test:features": "jest src/features --passWithNoTests --runInBand",
"test:features:watch": "yarn test:features --watch",
"test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.integration.config.js --reporters=jest-silent-reporter",
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose"
Expand Down
Loading
Loading