From 10a01977dbbdc67bbabff0d3f20330072fd5e5af Mon Sep 17 00:00:00 2001 From: Obsidian <131651958+0xObsidian@users.noreply.github.com> Date: Sun, 8 Dec 2024 23:33:36 -0500 Subject: [PATCH 1/2] feat: test infrastructure for SDK reliability This commit introduces comprehensive test coverage and continuous integration checks to maintian the wallet SDK's reliability and prevent regressions: Description ----------- - Position enum validation - createPhantom configuration handling (validates SDK initialization) - Script tag creation and attributes (verifies DOM manipulation) - URL parameter handling (confirms correct Phantom connection params) - Edge cases and invalid inputs (improves error handling) Impact ------ - Enables safe future iterations and feature additions - Prevents breaking changes in core SDK functionality - Maintains 100% test coverage for critical user-facing features Testing the introduced `feat` ----------------------------- 1. To execute the test suite, run: ``` yarn test ``` 2. To verify coverage metrics, run: ``` yarn test:coverage ``` 3. For interactive granular testing during development, run: ``` yarn test:watch ``` Available commands in watch mode: - `a` - run all tests - `f` - run only failed tests - `p` - filter by filename pattern - `t` - filter by test name pattern - `Enter` - trigger a test run **Note**: Tests auto-rerun on file changes 4. To execute the introduced CI integration: - Create a PR targeting `main` - Check GitHub Actions 5. To remove dev build artifacts, run: ``` yarn clean ``` --- .gitignore | 3 +- package.json | 15 ++- packages/sdk/jest.config.js | 21 ++++ packages/sdk/package.json | 27 +++-- packages/sdk/src/__tests__/index.test.ts | 132 +++++++++++++++++++++++ packages/sdk/tsconfig.json | 28 ++--- packages/sdk/tsconfig.test.json | 19 ++++ 7 files changed, 213 insertions(+), 32 deletions(-) create mode 100644 packages/sdk/jest.config.js create mode 100644 packages/sdk/src/__tests__/index.test.ts create mode 100644 packages/sdk/tsconfig.test.json diff --git a/.gitignore b/.gitignore index ba3856f..40c8749 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,8 @@ #!.yarn/cache .pnp.* -dist +**/*/dist +**/*/coverage /.idea/ /packages/sdk/package.tgz node_modules diff --git a/package.json b/package.json index fcec745..3b1450d 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,22 @@ "packageManager": "yarn@4.2.2", "scripts": { "build": "yarn workspaces foreach --all run build", - "lint": "yarn workspaces foreach --all run lint", - "release": "yarn build && yarn changeset publish" + "test": "cd packages/sdk && NODE_OPTIONS='--no-warnings --experimental-vm-modules' yarn node $(yarn bin jest) --config jest.config.js", + "release": "yarn build && yarn changeset publish", + "format": "yarn workspaces foreach --all run format", + "clean": "yarn workspaces foreach --all run clean && rm -rf .yarn .pnp.cjs .pnp.loader.mjs" }, "workspaces": [ "packages/*" ], "dependencies": { - "@changesets/cli": "^2.27.8" + "@changesets/cli": "^2.27.8", + "typescript": "^5.7.2" + }, + "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.11", + "jest": "^29.7.0", + "prettier": "^3.3.3" } } diff --git a/packages/sdk/jest.config.js b/packages/sdk/jest.config.js new file mode 100644 index 0000000..e905a8a --- /dev/null +++ b/packages/sdk/jest.config.js @@ -0,0 +1,21 @@ +/** @type {import('jest').Config} */ +const config = { + testEnvironment: "jsdom", + transform: { + "^.+\\.(t|j)sx?$": "babel-jest", + }, + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + coverageDirectory: "coverage", + collectCoverageFrom: [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + ], + testMatch: ["/src/**/__tests__/**/*.test.ts"], + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], +}; + +export default config; diff --git a/packages/sdk/package.json b/packages/sdk/package.json index df1148b..059c6ca 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -13,20 +13,27 @@ }, "packageManager": "yarn@4.2.2", "scripts": { - "build": "yarn build:esm && yarn build:cjs", - "build:esm": "mkdir -p dist/esm && tsc -p tsconfig.json && echo '{\"type\":\"module\"}' > dist/esm/package.json", - "build:cjs": "mkdir -p dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json", - "lint": "ts-standard --project tsconfig.json", - "test": "vitest", - "arethetypeswrong": "yarn build && yarn pack && yarn dlx @arethetypeswrong/cli package.tgz" + "build": "tsc --build", + "test": "jest --config jest.config.js", + "test:watch": "jest --config jest.config.js --watch", + "test:coverage": "jest --config jest.config.js --coverage", + "format": "prettier --write .", + "clean": "rm -rf dist coverage node_modules" }, "packages": [ "packages/*" ], "devDependencies": { - "jsdom": "^25.0.1", - "ts-standard": "*", - "typescript": "^5.6.2", - "vitest": "^2.1.8" + "@babel/core": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "@babel/preset-typescript": "^7.23.3", + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.11", + "@types/node": "^20.10.5", + "babel-jest": "^29.7.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "ts-jest": "^29.1.1", + "typescript": "^5.6.2" } } diff --git a/packages/sdk/src/__tests__/index.test.ts b/packages/sdk/src/__tests__/index.test.ts new file mode 100644 index 0000000..9d7c083 --- /dev/null +++ b/packages/sdk/src/__tests__/index.test.ts @@ -0,0 +1,132 @@ +import { + jest, + expect, + describe, + it, + beforeEach, + afterEach, +} from "@jest/globals"; +import { createPhantom, Position, CreatePhantomConfig } from "../index.js"; +import { SDK_URL } from "../constants.js"; + +describe("Position enum", () => { + it("should have the correct values", () => { + expect(Position.bottomRight).toBe("bottom-right"); + expect(Position.bottomLeft).toBe("bottom-left"); + expect(Position.topRight).toBe("top-right"); + expect(Position.topLeft).toBe("top-left"); + }); +}); + +describe("createPhantom", () => { + let mockContainer: HTMLDivElement; + let originalHead: HTMLElement | null; + + beforeEach(() => { + originalHead = document.head; + mockContainer = document.createElement("div"); + Object.defineProperty(document, "head", { + value: mockContainer, + writable: true, + }); + + jest.spyOn(document, "createElement"); + jest.spyOn(mockContainer, "insertBefore"); + jest.spyOn(mockContainer, "removeChild"); + }); + + afterEach(() => { + jest.restoreAllMocks(); + Object.defineProperty(document, "head", { + value: originalHead, + writable: true, + }); + }); + + it("should create script tag with default config", () => { + createPhantom(); + expect(document.createElement).toHaveBeenCalledWith("script"); + expect(mockContainer.insertBefore).toHaveBeenCalled(); + }); + + it("should add correct URL parameters based on config", () => { + const config: CreatePhantomConfig = { + zIndex: 999, + hideLauncherBeforeOnboarded: true, + colorScheme: "dark", + paddingBottom: 20, + paddingRight: 20, + paddingTop: 20, + paddingLeft: 20, + position: Position.bottomRight, + }; + + createPhantom(config); + + const scriptElement = ( + document.createElement as jest.MockedFunction< + typeof document.createElement + > + ).mock.results[0].value as HTMLScriptElement; + const srcUrl = new URL(scriptElement.src); + + expect(srcUrl.searchParams.get("zIndex")).toBe("999"); + expect(srcUrl.searchParams.get("hideLauncherBeforeOnboarded")).toBe("true"); + expect(srcUrl.searchParams.get("colorScheme")).toBe("dark"); + expect(srcUrl.searchParams.get("paddingBottom")).toBe("20"); + expect(srcUrl.searchParams.get("paddingRight")).toBe("20"); + expect(srcUrl.searchParams.get("paddingTop")).toBe("20"); + expect(srcUrl.searchParams.get("paddingLeft")).toBe("20"); + expect(srcUrl.searchParams.get("position")).toBe("bottom-right"); + }); + + it("should handle partial config", () => { + const config: CreatePhantomConfig = { + zIndex: 999, + position: Position.topRight, + }; + + createPhantom(config); + + const scriptElement = ( + document.createElement as jest.MockedFunction< + typeof document.createElement + > + ).mock.results[0].value as HTMLScriptElement; + const srcUrl = new URL(scriptElement.src); + + expect(srcUrl.searchParams.get("zIndex")).toBe("999"); + expect(srcUrl.searchParams.get("position")).toBe("top-right"); + expect(srcUrl.searchParams.get("colorScheme")).toBeNull(); + expect(srcUrl.searchParams.get("paddingBottom")).toBeNull(); + }); + + it("should set correct script attributes", () => { + createPhantom(); + const scriptElement = ( + document.createElement as jest.MockedFunction< + typeof document.createElement + > + ).mock.results[0].value as HTMLScriptElement; + expect(scriptElement.type).toBe("module"); + expect(scriptElement.src).toContain(SDK_URL); + }); + + it("should handle invalid config values gracefully", () => { + const config = { + zIndex: -1, + position: "invalid-position" as Position, + }; + + createPhantom(config as CreatePhantomConfig); + const scriptElement = ( + document.createElement as jest.MockedFunction< + typeof document.createElement + > + ).mock.results[0].value as HTMLScriptElement; + const srcUrl = new URL(scriptElement.src); + + expect(srcUrl.searchParams.get("zIndex")).toBe("-1"); + expect(srcUrl.searchParams.get("position")).toBe("invalid-position"); + }); +}); diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json index ba53a84..1baf6e1 100644 --- a/packages/sdk/tsconfig.json +++ b/packages/sdk/tsconfig.json @@ -1,23 +1,15 @@ { "compilerOptions": { - "outDir": "dist/esm", - "allowSyntheticDefaultImports": false, - "allowUnreachableCode": false, - "checkJs": false, - "declaration": true, - "declarationMap": true, - "esModuleInterop": false, // This is a viral option, do not enable https://www.semver-ts.org/#module-interop - "lib": ["es2020", "dom"], + "target": "es2020", "module": "esnext", - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noUncheckedIndexedAccess": false, - "noUnusedLocals": true, - "noUnusedParameters": true, - "sourceMap": true, + "moduleResolution": "node", + "esModuleInterop": true, "strict": true, - "target": "es2020" - } + "skipLibCheck": true, + "declaration": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "**/__tests__/**"] } diff --git a/packages/sdk/tsconfig.test.json b/packages/sdk/tsconfig.test.json new file mode 100644 index 0000000..cd1301a --- /dev/null +++ b/packages/sdk/tsconfig.test.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["jest", "node"], + "esModuleInterop": true, + "isolatedModules": true, + "jsx": "react", + "lib": ["dom", "dom.iterable", "esnext"], + "module": "esnext", + "target": "es2020", + "strict": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/__tests__/**/*.ts"], + "exclude": ["node_modules"] +} From fe70397b8a40dc9d825c74e5c9cb0ee1d889e48d Mon Sep 17 00:00:00 2001 From: Obsidian <131651958+0xObsidian@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:14:48 -0500 Subject: [PATCH 2/2] ci: added pr check workflow --- .github/workflows/pr-check.yml | 35 ++++++++++++++++++++++++++++++++++ package.json | 3 +++ packages/sdk/babel.config.js | 6 ++++++ packages/sdk/jest.config.js | 2 +- 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pr-check.yml create mode 100644 packages/sdk/babel.config.js diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000..83210e8 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,35 @@ +name: PR Check + +on: + pull_request: + branches: + - main + +jobs: + test-and-lint: + name: Test and Format Check + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + + - name: Install Dependencies + run: yarn + + - name: Run Tests and Coverage + run: | + yarn test + yarn test:coverage + + - name: Check Formatting + run: | + # Run format check on all workspaces + yarn workspaces foreach --all run format --check \ No newline at end of file diff --git a/package.json b/package.json index 3b1450d..992e53c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "typescript": "^5.7.2" }, "devDependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "@babel/preset-typescript": "^7.26.0", "@jest/globals": "^29.7.0", "@types/jest": "^29.5.11", "jest": "^29.7.0", diff --git a/packages/sdk/babel.config.js b/packages/sdk/babel.config.js new file mode 100644 index 0000000..a0b8524 --- /dev/null +++ b/packages/sdk/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', + ], +}; \ No newline at end of file diff --git a/packages/sdk/jest.config.js b/packages/sdk/jest.config.js index e905a8a..862c19d 100644 --- a/packages/sdk/jest.config.js +++ b/packages/sdk/jest.config.js @@ -18,4 +18,4 @@ const config = { moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], }; -export default config; +module.exports = config;