@avalix/chroma 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  End-to-end testing library for Polkadot wallet interactions using Playwright.
4
4
 
5
+ ## Documentation
6
+
7
+ We highly recommend you take a look at the [Chroma documentation](https://chroma-docs.up.railway.app/docs) to level up. It's a great resource for learning more about the library. It covers everything from getting started to advanced topics like CI/CD integration and Docker setup.
8
+
5
9
  ## Installation
6
10
 
7
11
  ```bash
@@ -82,14 +86,9 @@ test('multi-wallet test', async ({ page, wallets }) => {
82
86
  })
83
87
  ```
84
88
 
85
- ## Features
86
-
87
- - **Easy Extension Setup** - Download wallet extensions with a single command
88
- - **Multi-Wallet Support** - Test with multiple wallet extensions simultaneously
89
- - **TypeScript Support** - Full type safety and autocomplete
90
- - **VS Code Integration** - Works with [Playwright Test for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
89
+ ## Supported Wallets & Chains
91
90
 
92
- ## Supported Chains
91
+ ### Supported Chains
93
92
 
94
93
  | Chain | Status |
95
94
  |-------|--------|
@@ -97,7 +96,7 @@ test('multi-wallet test', async ({ page, wallets }) => {
97
96
  | Ethereum | ✅ Supported |
98
97
  | Solana | ⏳ Planned |
99
98
 
100
- ## Supported Wallets
99
+ ### Supported Wallets
101
100
 
102
101
  | Wallet | Status | Version |
103
102
  |--------|--------|---------|
@@ -106,6 +105,13 @@ test('multi-wallet test', async ({ page, wallets }) => {
106
105
  | SubWallet | ⏳ Planned | - |
107
106
  | MetaMask | ⏳ Planned | - |
108
107
 
108
+ ## Features
109
+
110
+ - **Easy Extension Setup** - Download wallet extensions with a single command
111
+ - **Multi-Wallet Support** - Test with multiple wallet extensions simultaneously
112
+ - **TypeScript Support** - Full type safety and autocomplete
113
+ - **VS Code Integration** - Works with [Playwright Test for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
114
+
109
115
  ## Requirements
110
116
 
111
117
  - Node.js 24+
package/dist/index.mjs CHANGED
@@ -9,6 +9,7 @@ const POLKADOT_JS_CONFIG = {
9
9
  downloadUrl: `https://github.com/polkadot-js/extension/releases/download/v${VERSION$1}/master-chrome-build.zip`,
10
10
  extensionName: `polkadot-extension-${VERSION$1}`
11
11
  };
12
+ /* c8 ignore start */
12
13
  async function findExtensionPopup$1(context, extensionId) {
13
14
  const maxAttempts = 10;
14
15
  const retryDelay = 500;
@@ -22,12 +23,14 @@ async function findExtensionPopup$1(context, extensionId) {
22
23
  }
23
24
  throw new Error(`Extension popup not found for ID: ${extensionId}`);
24
25
  }
26
+ /* c8 ignore stop */
25
27
  async function getPolkadotJSExtensionPath() {
26
28
  const extensionsDir = path.resolve(process.cwd(), ".chroma");
27
29
  const extensionDir = path.join(extensionsDir, POLKADOT_JS_CONFIG.extensionName);
28
30
  if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Polkadot-JS extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n`);
29
31
  return extensionDir;
30
32
  }
33
+ /* c8 ignore start */
31
34
  async function importPolkadotJSAccount(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
32
35
  const context = page.__extensionContext;
33
36
  const extensionPopupUrl = `chrome-extension://${page.__extensionId}/index.html`;
@@ -72,6 +75,7 @@ async function rejectPolkadotJSTx(page) {
72
75
  const extensionId = page.__extensionId;
73
76
  await (await findExtensionPopup$1(context, extensionId)).getByRole("link", { name: "Cancel" }).click();
74
77
  }
78
+ /* c8 ignore stop */
75
79
 
76
80
  //#endregion
77
81
  //#region src/wallets/talisman.ts
@@ -80,6 +84,7 @@ const TALISMAN_CONFIG = {
80
84
  downloadUrl: `https://github.com/avalix-labs/polkadot-wallets/raw/refs/heads/main/talisman/talisman-${VERSION}.zip`,
81
85
  extensionName: `talisman-extension-${VERSION}`
82
86
  };
87
+ /* c8 ignore start */
83
88
  async function findExtensionPopup(context, extensionId) {
84
89
  const maxAttempts = 10;
85
90
  const retryDelay = 500;
@@ -97,12 +102,14 @@ async function findExtensionPopup(context, extensionId) {
97
102
  }
98
103
  throw new Error(`Extension popup not found for ID: ${extensionId}`);
99
104
  }
105
+ /* c8 ignore stop */
100
106
  async function getTalismanExtensionPath() {
101
107
  const extensionsDir = path.resolve(process.cwd(), ".chroma");
102
108
  const extensionDir = path.join(extensionsDir, TALISMAN_CONFIG.extensionName);
103
109
  if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Talisman extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n`);
104
110
  return extensionDir;
105
111
  }
112
+ /* c8 ignore start */
106
113
  async function findOnboardingPage(context, extensionId) {
107
114
  const popupUrl = `chrome-extension://${extensionId}/dashboard.html`;
108
115
  const newPage = await context.newPage();
@@ -199,17 +206,21 @@ async function rejectTalismanTx(page) {
199
206
  const context = page.__extensionContext;
200
207
  const extensionId = page.__extensionId;
201
208
  const extensionPopup = await findExtensionPopup(context, extensionId);
202
- await extensionPopup.getByTestId("connection-reject-button").or(extensionPopup.getByRole("button", { name: "Cancel" })).click();
209
+ await extensionPopup.getByTestId("connection-reject-button").or(extensionPopup.getByRole("button", { name: "Cancel" })).or(extensionPopup.getByRole("button", { name: "Reject" })).click();
203
210
  }
211
+ /* c8 ignore stop */
204
212
 
205
213
  //#endregion
206
214
  //#region src/context-playwright/wallet-factory.ts
215
+ /* c8 ignore start */
207
216
  function createExtendedPage(page, context, extensionId) {
208
217
  const extPage = page;
209
218
  extPage.__extensionContext = context;
210
219
  extPage.__extensionId = extensionId;
211
220
  return extPage;
212
221
  }
222
+ /* c8 ignore stop */
223
+ /* c8 ignore start */
213
224
  function createPolkadotJsWallet(extensionId, context) {
214
225
  return {
215
226
  extensionId,
@@ -228,6 +239,8 @@ function createPolkadotJsWallet(extensionId, context) {
228
239
  }
229
240
  };
230
241
  }
242
+ /* c8 ignore stop */
243
+ /* c8 ignore start */
231
244
  function createTalismanWallet(extensionId, context) {
232
245
  let importedAccountName;
233
246
  return {
@@ -258,6 +271,7 @@ function createTalismanWallet(extensionId, context) {
258
271
  }
259
272
  };
260
273
  }
274
+ /* c8 ignore stop */
261
275
  const walletFactories = {
262
276
  "polkadot-js": createPolkadotJsWallet,
263
277
  "talisman": createTalismanWallet
@@ -277,6 +291,7 @@ function createWalletTest(options = {}) {
277
291
  const { headless = false, slowMo = 150 } = options;
278
292
  const walletConfigs = options.wallets && options.wallets.length > 0 ? options.wallets : [{ type: "polkadot-js" }];
279
293
  const isMultiWallet = walletConfigs.length > 1;
294
+ /* c8 ignore start */
280
295
  return test$1.extend({
281
296
  walletContext: [async ({}, use) => {
282
297
  const extensionPathsString = (await Promise.all(walletConfigs.map((config) => getExtensionPathForWallet(config)))).join(",");
@@ -316,6 +331,7 @@ function createWalletTest(options = {}) {
316
331
  await use(walletMap);
317
332
  }
318
333
  });
334
+ /* c8 ignore stop */
319
335
  }
320
336
  const test = createWalletTest();
321
337
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@avalix/chroma",
3
3
  "type": "module",
4
- "version": "0.0.12",
4
+ "version": "0.0.14",
5
5
  "description": "End-to-end testing library for Polkadot wallet interactions",
6
6
  "author": "Avalix Labs",
7
7
  "license": "MIT",
8
- "homepage": "https://github.com/avalix-labs/chroma#readme",
8
+ "homepage": "https://chroma-docs.up.railway.app/docs",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/avalix-labs/chroma.git",
@@ -61,6 +61,7 @@
61
61
  "@playwright/test": "^1.57.0",
62
62
  "@types/node": "^24.10.2",
63
63
  "@types/unzipper": "^0.10.11",
64
+ "@vitest/coverage-v8": "4.0.18",
64
65
  "eslint": "^9.39.1",
65
66
  "tsdown": "^0.20.1",
66
67
  "tsx": "^4.21.0",
@@ -0,0 +1,148 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { getPolkadotJSExtensionPath } from '../wallets/polkadot-js.js'
3
+ import { getTalismanExtensionPath } from '../wallets/talisman.js'
4
+ import { createWalletTest, getExtensionPathForWallet } from './index.js'
5
+
6
+ // Mock @playwright/test
7
+ vi.mock('@playwright/test', () => {
8
+ const mockExtend = vi.fn().mockReturnValue({
9
+ extend: vi.fn(),
10
+ })
11
+
12
+ return {
13
+ test: {
14
+ extend: mockExtend,
15
+ },
16
+ chromium: {
17
+ launchPersistentContext: vi.fn(),
18
+ },
19
+ expect: vi.fn(),
20
+ }
21
+ })
22
+
23
+ // Mock wallet extension paths
24
+ vi.mock('../wallets/polkadot-js.js', () => ({
25
+ getPolkadotJSExtensionPath: vi.fn().mockResolvedValue('/mock/path/polkadot-extension'),
26
+ }))
27
+
28
+ vi.mock('../wallets/talisman.js', () => ({
29
+ getTalismanExtensionPath: vi.fn().mockResolvedValue('/mock/path/talisman-extension'),
30
+ }))
31
+
32
+ // Mock wallet factories
33
+ vi.mock('./wallet-factory.js', () => ({
34
+ walletFactories: {
35
+ 'polkadot-js': vi.fn().mockReturnValue({ type: 'polkadot-js' }),
36
+ 'talisman': vi.fn().mockReturnValue({ type: 'talisman' }),
37
+ },
38
+ }))
39
+
40
+ describe('context-playwright/index', () => {
41
+ beforeEach(() => {
42
+ vi.clearAllMocks()
43
+ })
44
+
45
+ describe('createWalletTest', () => {
46
+ it('should create a test function', () => {
47
+ const result = createWalletTest()
48
+
49
+ expect(result).toBeDefined()
50
+ })
51
+
52
+ it('should accept empty options', () => {
53
+ const result = createWalletTest({})
54
+
55
+ expect(result).toBeDefined()
56
+ })
57
+
58
+ it('should accept headless option', () => {
59
+ const result = createWalletTest({ headless: true })
60
+
61
+ expect(result).toBeDefined()
62
+ })
63
+
64
+ it('should accept slowMo option', () => {
65
+ const result = createWalletTest({ slowMo: 200 })
66
+
67
+ expect(result).toBeDefined()
68
+ })
69
+
70
+ it('should accept single wallet configuration', () => {
71
+ const result = createWalletTest({
72
+ wallets: [{ type: 'polkadot-js' }],
73
+ })
74
+
75
+ expect(result).toBeDefined()
76
+ })
77
+
78
+ it('should accept talisman wallet configuration', () => {
79
+ const result = createWalletTest({
80
+ wallets: [{ type: 'talisman' }],
81
+ })
82
+
83
+ expect(result).toBeDefined()
84
+ })
85
+
86
+ it('should accept multi-wallet configuration', () => {
87
+ const result = createWalletTest({
88
+ wallets: [
89
+ { type: 'polkadot-js' },
90
+ { type: 'talisman' },
91
+ ],
92
+ })
93
+
94
+ expect(result).toBeDefined()
95
+ })
96
+
97
+ it('should accept all options combined', () => {
98
+ const result = createWalletTest({
99
+ headless: true,
100
+ slowMo: 100,
101
+ wallets: [{ type: 'polkadot-js' }],
102
+ })
103
+
104
+ expect(result).toBeDefined()
105
+ })
106
+
107
+ it('should default to polkadot-js when wallets array is empty', () => {
108
+ const result = createWalletTest({
109
+ wallets: [],
110
+ })
111
+
112
+ expect(result).toBeDefined()
113
+ })
114
+
115
+ it('should handle wallets with downloadUrl option', () => {
116
+ const result = createWalletTest({
117
+ wallets: [{
118
+ type: 'polkadot-js',
119
+ downloadUrl: 'https://custom-url.com/extension.zip',
120
+ }],
121
+ })
122
+
123
+ expect(result).toBeDefined()
124
+ })
125
+ })
126
+
127
+ describe('getExtensionPathForWallet', () => {
128
+ it('should return polkadot-js extension path', async () => {
129
+ const result = await getExtensionPathForWallet({ type: 'polkadot-js' })
130
+
131
+ expect(result).toBe('/mock/path/polkadot-extension')
132
+ expect(getPolkadotJSExtensionPath).toHaveBeenCalled()
133
+ })
134
+
135
+ it('should return talisman extension path', async () => {
136
+ const result = await getExtensionPathForWallet({ type: 'talisman' })
137
+
138
+ expect(result).toBe('/mock/path/talisman-extension')
139
+ expect(getTalismanExtensionPath).toHaveBeenCalled()
140
+ })
141
+
142
+ it('should throw error for unsupported wallet type', async () => {
143
+ await expect(
144
+ getExtensionPathForWallet({ type: 'unsupported' as any }),
145
+ ).rejects.toThrow('Unsupported wallet type: unsupported')
146
+ })
147
+ })
148
+ })
@@ -13,7 +13,7 @@ import { getTalismanExtensionPath } from '../wallets/talisman.js'
13
13
  import { walletFactories } from './wallet-factory.js'
14
14
 
15
15
  // Helper function to get extension path for a wallet config
16
- async function getExtensionPathForWallet(config: WalletConfig): Promise<string> {
16
+ export async function getExtensionPathForWallet(config: WalletConfig): Promise<string> {
17
17
  const { type } = config
18
18
 
19
19
  switch (type) {
@@ -43,6 +43,15 @@ export function createWalletTest<const T extends readonly WalletConfig[]>(
43
43
  // Compute the expected wallets type
44
44
  type ExpectedWallets = T extends readonly WalletConfig[] ? ConfiguredWallets<T> : Wallets
45
45
 
46
+ /*
47
+ * Playwright Fixtures - Coverage Exclusion
48
+ *
49
+ * The fixture implementations below are excluded from unit test coverage because:
50
+ * 1. They require a real Chromium browser with extension support
51
+ * 2. They interact with Chrome's extension APIs (service workers, extension IDs)
52
+ * 3. They are thoroughly tested via E2E tests in the tests/ directory
53
+ */
54
+ /* c8 ignore start */
46
55
  return base.extend<WalletFixtures<ExpectedWallets>, WalletWorkerFixtures>({
47
56
  // Worker-scoped: Browser context with extension(s) (persists across all tests in worker)
48
57
  // eslint-disable-next-line no-empty-pattern
@@ -127,6 +136,7 @@ export function createWalletTest<const T extends readonly WalletConfig[]>(
127
136
  await use(walletMap)
128
137
  },
129
138
  })
139
+ /* c8 ignore stop */
130
140
  }
131
141
 
132
142
  // Default test with Polkadot JS wallet (with persistent wallet support via worker-scoped fixtures)
@@ -0,0 +1,106 @@
1
+ import type { BrowserContext, Page } from '@playwright/test'
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import {
4
+ createPolkadotJsWallet,
5
+ createTalismanWallet,
6
+ walletFactories,
7
+ } from './wallet-factory.js'
8
+
9
+ // Mock wallet implementations
10
+ vi.mock('../wallets/polkadot-js.js', () => ({
11
+ importPolkadotJSAccount: vi.fn(),
12
+ authorizePolkadotJS: vi.fn(),
13
+ approvePolkadotJSTx: vi.fn(),
14
+ rejectPolkadotJSTx: vi.fn(),
15
+ }))
16
+
17
+ vi.mock('../wallets/talisman.js', () => ({
18
+ importPolkadotMnemonic: vi.fn(),
19
+ importEthPrivateKey: vi.fn(),
20
+ authorizeTalisman: vi.fn(),
21
+ approveTalismanTx: vi.fn(),
22
+ rejectTalismanTx: vi.fn(),
23
+ }))
24
+
25
+ // Create mock browser context
26
+ function createMockContext(): BrowserContext {
27
+ const mockPage = {
28
+ url: () => 'https://example.com',
29
+ __extensionContext: null,
30
+ __extensionId: '',
31
+ } as unknown as Page
32
+
33
+ return {
34
+ pages: vi.fn().mockReturnValue([mockPage]),
35
+ newPage: vi.fn().mockResolvedValue(mockPage),
36
+ } as unknown as BrowserContext
37
+ }
38
+
39
+ describe('wallet-factory', () => {
40
+ beforeEach(() => {
41
+ vi.clearAllMocks()
42
+ })
43
+
44
+ describe('walletFactories', () => {
45
+ it('should have polkadot-js factory', () => {
46
+ expect(walletFactories['polkadot-js']).toBeDefined()
47
+ expect(typeof walletFactories['polkadot-js']).toBe('function')
48
+ })
49
+
50
+ it('should have talisman factory', () => {
51
+ expect(walletFactories.talisman).toBeDefined()
52
+ expect(typeof walletFactories.talisman).toBe('function')
53
+ })
54
+ })
55
+
56
+ describe('createPolkadotJsWallet', () => {
57
+ const extensionId = 'test-extension-id'
58
+ let mockContext: BrowserContext
59
+
60
+ beforeEach(() => {
61
+ mockContext = createMockContext()
62
+ })
63
+
64
+ it('should create wallet with correct type and extensionId', () => {
65
+ const wallet = createPolkadotJsWallet(extensionId, mockContext)
66
+
67
+ expect(wallet.type).toBe('polkadot-js')
68
+ expect(wallet.extensionId).toBe(extensionId)
69
+ })
70
+
71
+ it('should have all required methods', () => {
72
+ const wallet = createPolkadotJsWallet(extensionId, mockContext)
73
+
74
+ expect(typeof wallet.importMnemonic).toBe('function')
75
+ expect(typeof wallet.authorize).toBe('function')
76
+ expect(typeof wallet.approveTx).toBe('function')
77
+ expect(typeof wallet.rejectTx).toBe('function')
78
+ })
79
+ })
80
+
81
+ describe('createTalismanWallet', () => {
82
+ const extensionId = 'test-extension-id'
83
+ let mockContext: BrowserContext
84
+
85
+ beforeEach(() => {
86
+ mockContext = createMockContext()
87
+ })
88
+
89
+ it('should create wallet with correct type and extensionId', () => {
90
+ const wallet = createTalismanWallet(extensionId, mockContext)
91
+
92
+ expect(wallet.type).toBe('talisman')
93
+ expect(wallet.extensionId).toBe(extensionId)
94
+ })
95
+
96
+ it('should have all required methods', () => {
97
+ const wallet = createTalismanWallet(extensionId, mockContext)
98
+
99
+ expect(typeof wallet.importPolkadotMnemonic).toBe('function')
100
+ expect(typeof wallet.importEthPrivateKey).toBe('function')
101
+ expect(typeof wallet.authorize).toBe('function')
102
+ expect(typeof wallet.approveTx).toBe('function')
103
+ expect(typeof wallet.rejectTx).toBe('function')
104
+ })
105
+ })
106
+ })
@@ -15,14 +15,20 @@ import {
15
15
  } from '../wallets/talisman.js'
16
16
 
17
17
  // Helper to create extended page with wallet context
18
+ /* c8 ignore start */
18
19
  function createExtendedPage(page: Page, context: BrowserContext, extensionId: string) {
19
20
  const extPage = page as Page & { __extensionContext: BrowserContext, __extensionId: string }
20
21
  extPage.__extensionContext = context
21
22
  extPage.__extensionId = extensionId
22
23
  return extPage
23
24
  }
25
+ /* c8 ignore stop */
24
26
 
25
- // Factory function for Polkadot-JS wallet
27
+ /*
28
+ * Factory function for Polkadot-JS wallet
29
+ * Coverage excluded: methods interact with Chrome extension APIs via browser context.
30
+ */
31
+ /* c8 ignore start */
26
32
  export function createPolkadotJsWallet(extensionId: string, context: BrowserContext) {
27
33
  return {
28
34
  extensionId,
@@ -49,8 +55,13 @@ export function createPolkadotJsWallet(extensionId: string, context: BrowserCont
49
55
  },
50
56
  }
51
57
  }
58
+ /* c8 ignore stop */
52
59
 
53
- // Factory function for Talisman wallet
60
+ /*
61
+ * Factory function for Talisman wallet
62
+ * Coverage excluded: methods interact with Chrome extension APIs via browser context.
63
+ */
64
+ /* c8 ignore start */
54
65
  export function createTalismanWallet(extensionId: string, context: BrowserContext) {
55
66
  let importedAccountName: string | undefined
56
67
 
@@ -91,6 +102,7 @@ export function createTalismanWallet(extensionId: string, context: BrowserContex
91
102
  },
92
103
  }
93
104
  }
105
+ /* c8 ignore stop */
94
106
 
95
107
  // Wallet factories map - auto-inferred types
96
108
  export const walletFactories = {
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ // Mock playwright before importing
4
+ vi.mock('@playwright/test', () => ({
5
+ test: {
6
+ extend: vi.fn().mockReturnValue({}),
7
+ },
8
+ chromium: {
9
+ launchPersistentContext: vi.fn(),
10
+ },
11
+ expect: vi.fn(),
12
+ }))
13
+
14
+ vi.mock('./wallets/polkadot-js.js', () => ({
15
+ getPolkadotJSExtensionPath: vi.fn().mockResolvedValue('/mock/path'),
16
+ }))
17
+
18
+ vi.mock('./wallets/talisman.js', () => ({
19
+ getTalismanExtensionPath: vi.fn().mockResolvedValue('/mock/path'),
20
+ }))
21
+
22
+ vi.mock('./context-playwright/wallet-factory.js', () => ({
23
+ walletFactories: {
24
+ 'polkadot-js': vi.fn(),
25
+ 'talisman': vi.fn(),
26
+ },
27
+ }))
28
+
29
+ describe('index exports', () => {
30
+ it('should export createWalletTest function', async () => {
31
+ const { createWalletTest } = await import('./index.js')
32
+ expect(createWalletTest).toBeDefined()
33
+ expect(typeof createWalletTest).toBe('function')
34
+ })
35
+
36
+ it('should export test function', async () => {
37
+ const { test } = await import('./index.js')
38
+ expect(test).toBeDefined()
39
+ })
40
+
41
+ it('should export expect function', async () => {
42
+ const { expect: playwrightExpect } = await import('./index.js')
43
+ expect(playwrightExpect).toBeDefined()
44
+ })
45
+
46
+ it('should be able to call createWalletTest', async () => {
47
+ const { createWalletTest } = await import('./index.js')
48
+ const result = createWalletTest()
49
+ expect(result).toBeDefined()
50
+ })
51
+
52
+ it('should be able to call createWalletTest with options', async () => {
53
+ const { createWalletTest } = await import('./index.js')
54
+ const result = createWalletTest({ headless: true })
55
+ expect(result).toBeDefined()
56
+ })
57
+ })
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
4
+ import {
5
+ getPolkadotJSExtensionPath,
6
+ POLKADOT_JS_CONFIG,
7
+ } from './polkadot-js.js'
8
+
9
+ // Mock node:fs module
10
+ vi.mock('node:fs', async () => {
11
+ const actual = await vi.importActual<typeof import('node:fs')>('node:fs')
12
+ return {
13
+ ...actual,
14
+ default: {
15
+ ...actual,
16
+ existsSync: vi.fn(),
17
+ readdirSync: vi.fn(),
18
+ },
19
+ existsSync: vi.fn(),
20
+ readdirSync: vi.fn(),
21
+ }
22
+ })
23
+
24
+ describe('polkadot-js wallet', () => {
25
+ beforeEach(() => {
26
+ vi.clearAllMocks()
27
+ })
28
+
29
+ afterEach(() => {
30
+ vi.restoreAllMocks()
31
+ })
32
+
33
+ describe('polkadot_js_config', () => {
34
+ it('should have correct extension name format', () => {
35
+ expect(POLKADOT_JS_CONFIG.extensionName).toMatch(/^polkadot-extension-\d+\.\d+\.\d+$/)
36
+ })
37
+
38
+ it('should have valid download URL', () => {
39
+ expect(POLKADOT_JS_CONFIG.downloadUrl).toContain('github.com')
40
+ expect(POLKADOT_JS_CONFIG.downloadUrl).toContain('polkadot-js/extension')
41
+ expect(POLKADOT_JS_CONFIG.downloadUrl.endsWith('.zip')).toBe(true)
42
+ })
43
+ })
44
+
45
+ describe('getPolkadotJSExtensionPath', () => {
46
+ it('should return extension path when extension exists', async () => {
47
+ const mockedFs = vi.mocked(fs)
48
+ mockedFs.existsSync.mockReturnValue(true)
49
+ mockedFs.readdirSync.mockReturnValue(['manifest.json'] as any)
50
+
51
+ const result = await getPolkadotJSExtensionPath()
52
+
53
+ expect(result).toContain('.chroma')
54
+ expect(result).toContain(POLKADOT_JS_CONFIG.extensionName)
55
+ expect(mockedFs.existsSync).toHaveBeenCalled()
56
+ })
57
+
58
+ it('should throw error when extension directory does not exist', async () => {
59
+ const mockedFs = vi.mocked(fs)
60
+ mockedFs.existsSync.mockReturnValue(false)
61
+
62
+ await expect(getPolkadotJSExtensionPath()).rejects.toThrow(
63
+ 'Polkadot-JS extension not found',
64
+ )
65
+ })
66
+
67
+ it('should throw error when extension directory is empty', async () => {
68
+ const mockedFs = vi.mocked(fs)
69
+ mockedFs.existsSync.mockReturnValue(true)
70
+ mockedFs.readdirSync.mockReturnValue([])
71
+
72
+ await expect(getPolkadotJSExtensionPath()).rejects.toThrow(
73
+ 'Polkadot-JS extension not found',
74
+ )
75
+ })
76
+
77
+ it('should include download instructions in error message', async () => {
78
+ const mockedFs = vi.mocked(fs)
79
+ mockedFs.existsSync.mockReturnValue(false)
80
+
81
+ await expect(getPolkadotJSExtensionPath()).rejects.toThrow(
82
+ 'npx @avalix/chroma download-extensions',
83
+ )
84
+ })
85
+
86
+ it('should use correct path structure', async () => {
87
+ const mockedFs = vi.mocked(fs)
88
+ mockedFs.existsSync.mockReturnValue(true)
89
+ mockedFs.readdirSync.mockReturnValue(['manifest.json'] as any)
90
+
91
+ const result = await getPolkadotJSExtensionPath()
92
+
93
+ const expectedPath = path.join(process.cwd(), '.chroma', POLKADOT_JS_CONFIG.extensionName)
94
+ expect(result).toBe(expectedPath)
95
+ })
96
+ })
97
+ })
@@ -12,7 +12,11 @@ export const POLKADOT_JS_CONFIG = {
12
12
  extensionName: `polkadot-extension-${VERSION}`,
13
13
  } as const
14
14
 
15
- // Helper function to find extension popup
15
+ /*
16
+ * Helper function to find extension popup
17
+ * Coverage excluded: requires real browser context with Chrome extension APIs.
18
+ */
19
+ /* c8 ignore start */
16
20
  async function findExtensionPopup(context: BrowserContext, extensionId: string): Promise<Page> {
17
21
  // Wait for extension popup to appear with retry logic
18
22
  const maxAttempts = 10
@@ -35,6 +39,7 @@ async function findExtensionPopup(context: BrowserContext, extensionId: string):
35
39
 
36
40
  throw new Error(`Extension popup not found for ID: ${extensionId}`)
37
41
  }
42
+ /* c8 ignore stop */
38
43
 
39
44
  // Get Polkadot-JS extension path
40
45
  export async function getPolkadotJSExtensionPath(): Promise<string> {
@@ -53,6 +58,13 @@ export async function getPolkadotJSExtensionPath(): Promise<string> {
53
58
  return extensionDir
54
59
  }
55
60
 
61
+ /*
62
+ * Wallet interaction functions below are excluded from coverage because:
63
+ * - They require a real Chromium browser with extension support
64
+ * - They interact with Chrome extension popup pages
65
+ */
66
+ /* c8 ignore start */
67
+
56
68
  // Polkadot-JS specific account import implementation
57
69
  export async function importPolkadotJSAccount(
58
70
  page: Page & { __extensionContext: BrowserContext, __extensionId: string },
@@ -142,3 +154,5 @@ export async function rejectPolkadotJSTx(
142
154
  const extensionPopup = await findExtensionPopup(context, extensionId)
143
155
  await extensionPopup.getByRole('link', { name: 'Cancel' }).click()
144
156
  }
157
+
158
+ /* c8 ignore stop */
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
4
+ import {
5
+ getTalismanExtensionPath,
6
+ TALISMAN_CONFIG,
7
+ } from './talisman.js'
8
+
9
+ // Mock node:fs module
10
+ vi.mock('node:fs', async () => {
11
+ const actual = await vi.importActual<typeof import('node:fs')>('node:fs')
12
+ return {
13
+ ...actual,
14
+ default: {
15
+ ...actual,
16
+ existsSync: vi.fn(),
17
+ readdirSync: vi.fn(),
18
+ },
19
+ existsSync: vi.fn(),
20
+ readdirSync: vi.fn(),
21
+ }
22
+ })
23
+
24
+ describe('talisman wallet', () => {
25
+ beforeEach(() => {
26
+ vi.clearAllMocks()
27
+ })
28
+
29
+ afterEach(() => {
30
+ vi.restoreAllMocks()
31
+ })
32
+
33
+ describe('talisman_config', () => {
34
+ it('should have correct extension name format', () => {
35
+ expect(TALISMAN_CONFIG.extensionName).toMatch(/^talisman-extension-\d+\.\d+\.\d+$/)
36
+ })
37
+
38
+ it('should have valid download URL', () => {
39
+ expect(TALISMAN_CONFIG.downloadUrl).toContain('github.com')
40
+ expect(TALISMAN_CONFIG.downloadUrl).toContain('talisman')
41
+ expect(TALISMAN_CONFIG.downloadUrl.endsWith('.zip')).toBe(true)
42
+ })
43
+ })
44
+
45
+ describe('getTalismanExtensionPath', () => {
46
+ it('should return extension path when extension exists', async () => {
47
+ const mockedFs = vi.mocked(fs)
48
+ mockedFs.existsSync.mockReturnValue(true)
49
+ mockedFs.readdirSync.mockReturnValue(['manifest.json'] as any)
50
+
51
+ const result = await getTalismanExtensionPath()
52
+
53
+ expect(result).toContain('.chroma')
54
+ expect(result).toContain(TALISMAN_CONFIG.extensionName)
55
+ expect(mockedFs.existsSync).toHaveBeenCalled()
56
+ })
57
+
58
+ it('should throw error when extension directory does not exist', async () => {
59
+ const mockedFs = vi.mocked(fs)
60
+ mockedFs.existsSync.mockReturnValue(false)
61
+
62
+ await expect(getTalismanExtensionPath()).rejects.toThrow(
63
+ 'Talisman extension not found',
64
+ )
65
+ })
66
+
67
+ it('should throw error when extension directory is empty', async () => {
68
+ const mockedFs = vi.mocked(fs)
69
+ mockedFs.existsSync.mockReturnValue(true)
70
+ mockedFs.readdirSync.mockReturnValue([])
71
+
72
+ await expect(getTalismanExtensionPath()).rejects.toThrow(
73
+ 'Talisman extension not found',
74
+ )
75
+ })
76
+
77
+ it('should include download instructions in error message', async () => {
78
+ const mockedFs = vi.mocked(fs)
79
+ mockedFs.existsSync.mockReturnValue(false)
80
+
81
+ await expect(getTalismanExtensionPath()).rejects.toThrow(
82
+ 'npx @avalix/chroma download-extensions',
83
+ )
84
+ })
85
+
86
+ it('should use correct path structure', async () => {
87
+ const mockedFs = vi.mocked(fs)
88
+ mockedFs.existsSync.mockReturnValue(true)
89
+ mockedFs.readdirSync.mockReturnValue(['manifest.json'] as any)
90
+
91
+ const result = await getTalismanExtensionPath()
92
+
93
+ const expectedPath = path.join(process.cwd(), '.chroma', TALISMAN_CONFIG.extensionName)
94
+ expect(result).toBe(expectedPath)
95
+ })
96
+ })
97
+ })
@@ -12,7 +12,11 @@ export const TALISMAN_CONFIG = {
12
12
  extensionName: `talisman-extension-${VERSION}`,
13
13
  } as const
14
14
 
15
- // Helper function to find extension popup
15
+ /*
16
+ * Helper function to find extension popup
17
+ * Coverage excluded: requires real browser context with Chrome extension APIs.
18
+ */
19
+ /* c8 ignore start */
16
20
  async function findExtensionPopup(context: BrowserContext, extensionId: string): Promise<Page> {
17
21
  // Wait for extension popup to appear with retry logic
18
22
  const maxAttempts = 10
@@ -36,6 +40,7 @@ async function findExtensionPopup(context: BrowserContext, extensionId: string):
36
40
 
37
41
  throw new Error(`Extension popup not found for ID: ${extensionId}`)
38
42
  }
43
+ /* c8 ignore stop */
39
44
 
40
45
  // Get Talisman extension path
41
46
  export async function getTalismanExtensionPath(): Promise<string> {
@@ -54,6 +59,13 @@ export async function getTalismanExtensionPath(): Promise<string> {
54
59
  return extensionDir
55
60
  }
56
61
 
62
+ /*
63
+ * Wallet interaction functions below are excluded from coverage because:
64
+ * - They require a real Chromium browser with extension support
65
+ * - They interact with Chrome extension popup pages
66
+ */
67
+ /* c8 ignore start */
68
+
57
69
  // Helper function to find Talisman onboarding page
58
70
  async function findOnboardingPage(
59
71
  context: BrowserContext,
@@ -232,6 +244,9 @@ export async function rejectTalismanTx(
232
244
 
233
245
  const rejectButton = extensionPopup.getByTestId('connection-reject-button')
234
246
  .or(extensionPopup.getByRole('button', { name: 'Cancel' }))
247
+ .or(extensionPopup.getByRole('button', { name: 'Reject' }))
235
248
 
236
249
  await rejectButton.click()
237
250
  }
251
+
252
+ /* c8 ignore stop */