1
- import React from 'react' ;
2
- import { render , waitFor } from '@testing-library/react-native' ;
3
1
import { KnownCaipNamespace , stringToBytes } from '@metamask/utils' ;
2
+ import { render , waitFor } from '@testing-library/react-native' ;
3
+ import React from 'react' ;
4
4
5
5
import { Maskicon } from './Maskicon' ;
6
6
import * as MaskiconUtilities from './Maskicon.utilities' ;
7
7
8
8
jest . mock ( 'bitcoin-address-validation' , ( ) => ( {
9
- validate : ( address : string , network : any ) => {
10
- if ( address === '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa' ) return true ;
9
+ validate : ( address : string , _network : unknown ) => {
10
+ if ( address === '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa' ) {
11
+ return true ;
12
+ }
11
13
return false ;
12
14
} ,
13
15
Network : {
@@ -20,107 +22,125 @@ jest.mock('@solana/addresses', () => ({
20
22
isAddress : ( address : string ) => address === 'ValidSolanaAddress' ,
21
23
} ) ) ;
22
24
25
+ // Polyfill TextEncoder for JSDOM (Node < 18)
26
+ if ( typeof TextEncoder === 'undefined' ) {
27
+ // eslint-disable-next-line import-x/no-nodejs-modules, @typescript-eslint/no-require-imports
28
+ global . TextEncoder = require ( 'util' ) . TextEncoder ;
29
+ }
30
+
23
31
// Stub for react-native-svg so the component renders without error.
24
32
jest . mock ( 'react-native-svg' , ( ) => {
25
- const React = require ( 'react' ) ;
26
33
return {
27
- SvgXml : ( props : any ) => React . createElement ( 'SvgXml' , props , props . xml ) ,
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ SvgXml : ( props : any ) => (
36
+ < div
37
+ data-testid = { props . testID }
38
+ style = { { width : props . width , height : props . height } }
39
+ width = { props . width }
40
+ height = { props . height }
41
+ xml = { props . xml }
42
+ { ...props }
43
+ />
44
+ ) ,
28
45
} ;
29
46
} ) ;
30
47
31
48
// A simple deferred promise helper to control when a Promise resolves.
32
49
const createDeferred = < T , > ( ) => {
33
- let resolve : ( value : T ) => void ;
34
- let reject : ( error : any ) => void ;
35
- const promise = new Promise < T > ( ( res , rej ) => {
36
- resolve = res ;
37
- reject = rej ;
50
+ let resolver : ( value : T ) => void ;
51
+ let rejector : ( error : unknown ) => void ;
52
+ const promise = new Promise < T > ( ( resolve , reject ) => {
53
+ resolver = resolve ;
54
+ rejector = reject ;
38
55
} ) ;
39
- return { promise, resolve : resolve ! , reject : reject ! } ;
56
+
57
+ // Using non-null assertion is safe here because we know resolver and rejector are assigned in the Promise constructor
58
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
59
+ return { promise, resolve : resolver ! , reject : rejector ! } ;
40
60
} ;
41
61
42
62
describe ( 'Maskicon Utilities' , ( ) => {
43
- test ( 'generateSeedEthereum returns numeric seed based on address slice' , ( ) => {
63
+ it ( 'generateSeedEthereum returns numeric seed based on address slice' , ( ) => {
44
64
const address = '0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8' ;
45
65
const expectedSeed = parseInt ( address . slice ( 2 , 10 ) , 16 ) ;
46
66
expect ( MaskiconUtilities . generateSeedEthereum ( address ) ) . toBe ( expectedSeed ) ;
47
67
} ) ;
48
68
49
- test ( 'generateSeedNonEthereum returns byte-array seed from normalized lowercased address' , ( ) => {
69
+ it ( 'generateSeedNonEthereum returns byte-array seed from normalized lowercased address' , ( ) => {
50
70
const address = 'TestAddress' ;
51
71
const normalized = address . normalize ( 'NFKC' ) . toLowerCase ( ) ;
52
72
const expectedSeed = Array . from ( stringToBytes ( normalized ) ) ;
53
- expect ( MaskiconUtilities . generateSeedNonEthereum ( address ) ) . toEqual (
73
+ expect ( MaskiconUtilities . generateSeedNonEthereum ( address ) ) . toStrictEqual (
54
74
expectedSeed ,
55
75
) ;
56
76
} ) ;
57
77
58
78
describe ( 'seedToString helper' , ( ) => {
59
- test ( 'pads a numeric seed if hex is less than 6 characters' , ( ) => {
79
+ it ( 'pads a numeric seed if hex is less than 6 characters' , ( ) => {
60
80
// For example, 1 in hex is "1", so it should be padded to "100000".
61
81
const result = MaskiconUtilities . seedToString ( 1 ) ;
62
82
expect ( result ) . toBe ( '100000' ) ;
63
83
} ) ;
64
84
65
- test ( 'converts a byte array seed to hex and pads if necessary' , ( ) => {
85
+ it ( 'converts a byte array seed to hex and pads if necessary' , ( ) => {
66
86
// For an array like [1] which converts to "01", it is padded to "010000".
67
87
const result = MaskiconUtilities . seedToString ( [ 1 ] ) ;
68
88
expect ( result ) . toBe ( '010000' ) ;
69
89
} ) ;
70
90
71
- test ( 'returns "seed000" for unsupported seed types' , ( ) => {
72
- // When provided seed is not a number or an array.
91
+ it ( 'returns "seed000" for unsupported seed types' , ( ) => {
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
93
const result = MaskiconUtilities . seedToString ( { } as any ) ;
74
94
expect ( result ) . toBe ( 'seed000' ) ;
75
95
} ) ;
76
96
} ) ;
77
97
78
98
describe ( 'getCaipNamespaceFromAddress' , ( ) => {
79
- test ( 'returns Eip155 when address starts with "0x"' , async ( ) => {
99
+ it ( 'returns Eip155 when address starts with "0x"' , async ( ) => {
80
100
const address = '0xabcdef1234567890abcdef1234567890abcdef12' ;
81
101
const ns = await MaskiconUtilities . getCaipNamespaceFromAddress ( address ) ;
82
102
expect ( ns ) . toBe ( KnownCaipNamespace . Eip155 ) ;
83
103
} ) ;
84
104
85
- test ( 'returns Bip122 for CAIP-10 formatted address "bip122:..."' , async ( ) => {
105
+ it ( 'returns Bip122 for CAIP-10 formatted address "bip122:..."' , async ( ) => {
86
106
const address = 'bip122:someAddress' ;
87
107
const ns = await MaskiconUtilities . getCaipNamespaceFromAddress ( address ) ;
88
108
expect ( ns ) . toBe ( KnownCaipNamespace . Bip122 ) ;
89
109
} ) ;
90
110
91
- test ( 'returns Solana for CAIP-10 formatted address "solana:..."' , async ( ) => {
111
+ it ( 'returns Solana for CAIP-10 formatted address "solana:..."' , async ( ) => {
92
112
const address = 'solana:someAddress' ;
93
113
const ns = await MaskiconUtilities . getCaipNamespaceFromAddress ( address ) ;
94
114
expect ( ns ) . toBe ( KnownCaipNamespace . Solana ) ;
95
115
} ) ;
96
116
97
- test ( 'returns Bip122 for valid Bitcoin address (dynamic import branch)' , async ( ) => {
117
+ it ( 'returns Bip122 for valid Bitcoin address (dynamic import branch)' , async ( ) => {
98
118
const address = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa' ;
99
119
const ns = await MaskiconUtilities . getCaipNamespaceFromAddress ( address ) ;
100
120
expect ( ns ) . toBe ( KnownCaipNamespace . Bip122 ) ;
101
121
} ) ;
102
122
103
- test ( 'returns Solana for valid Solana address (fallback branch)' , async ( ) => {
123
+ it ( 'returns Solana for valid Solana address (fallback branch)' , async ( ) => {
104
124
const address = 'ValidSolanaAddress' ;
105
125
const ns = await MaskiconUtilities . getCaipNamespaceFromAddress ( address ) ;
106
126
expect ( ns ) . toBe ( KnownCaipNamespace . Solana ) ;
107
127
} ) ;
108
128
109
- test ( 'returns Eip155 for CAIP-10 formatted address with mixed-case namespace "Eip155:someAddress"' , async ( ) => {
129
+ it ( 'returns Eip155 for CAIP-10 formatted address with mixed-case namespace "Eip155:someAddress"' , async ( ) => {
110
130
const address = 'Eip155:someAddress' ;
111
131
const ns = await MaskiconUtilities . getCaipNamespaceFromAddress ( address ) ;
112
132
expect ( ns ) . toBe ( KnownCaipNamespace . Eip155 ) ;
113
133
} ) ;
114
134
115
- test ( 'returns Eip155 when none of the conditions match (fallback)' , async ( ) => {
135
+ it ( 'returns Eip155 when none of the conditions match (fallback)' , async ( ) => {
116
136
const address = 'nonEthereumNonSolanaAddress' ;
117
137
const ns = await MaskiconUtilities . getCaipNamespaceFromAddress ( address ) ;
118
138
expect ( ns ) . toBe ( KnownCaipNamespace . Eip155 ) ;
119
139
} ) ;
120
140
} ) ;
121
141
122
142
describe ( 'createMaskiconSVG' , ( ) => {
123
- test ( 'generates an SVG string using numeric seed' , ( ) => {
143
+ it ( 'generates an SVG string using numeric seed' , ( ) => {
124
144
const seed = 123456 ;
125
145
const size = 100 ;
126
146
const svg = MaskiconUtilities . createMaskiconSVG ( seed , size ) ;
@@ -131,7 +151,7 @@ describe('Maskicon Utilities', () => {
131
151
expect ( svg ) . toContain ( '<path' ) ;
132
152
} ) ;
133
153
134
- test ( 'generates an SVG string using array seed' , ( ) => {
154
+ it ( 'generates an SVG string using array seed' , ( ) => {
135
155
const seed = [ 1 , 2 , 3 , 4 , 5 ] ;
136
156
const size = 50 ;
137
157
const svg = MaskiconUtilities . createMaskiconSVG ( seed , size ) ;
@@ -142,14 +162,14 @@ describe('Maskicon Utilities', () => {
142
162
expect ( svg ) . toContain ( '<path' ) ;
143
163
} ) ;
144
164
145
- test ( 'uses default size 100 if size is not provided' , ( ) => {
165
+ it ( 'uses default size 100 if size is not provided' , ( ) => {
146
166
const seed = 123456 ;
147
167
const svg = MaskiconUtilities . createMaskiconSVG ( seed ) ;
148
168
expect ( svg ) . toContain ( 'width="100"' ) ;
149
169
expect ( svg ) . toContain ( 'height="100"' ) ;
150
170
} ) ;
151
171
152
- test ( 'triangle branch (rotation 270) produces expected path segment' , ( ) => {
172
+ it ( 'triangle branch (rotation 270) produces expected path segment' , ( ) => {
153
173
const hashSpy = jest
154
174
. spyOn ( MaskiconUtilities , 'sdbmHash' )
155
175
. mockReturnValue ( 768 ) ;
@@ -167,36 +187,34 @@ describe('Maskicon Utilities', () => {
167
187
} ) ;
168
188
169
189
describe ( 'getMaskiconSVG caching and non-Ethereum branch' , ( ) => {
170
- test ( 'getMaskiconSVG returns consistent SVG and uses caching' , async ( ) => {
190
+ it ( 'getMaskiconSVG returns consistent SVG and uses caching' , async ( ) => {
171
191
const address = '0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8' ;
172
192
const size = 100 ;
173
193
const svg1 = await MaskiconUtilities . getMaskiconSVG ( address , size ) ;
174
194
const svg2 = await MaskiconUtilities . getMaskiconSVG ( address , size ) ;
175
- expect ( svg1 ) . toEqual ( svg2 ) ;
195
+ expect ( svg1 ) . toStrictEqual ( svg2 ) ;
176
196
} ) ;
177
197
178
- test ( 'uses generateSeedNonEthereum when namespace is not Eip155' , async ( ) => {
198
+ it ( 'uses generateSeedNonEthereum when namespace is not Eip155' , async ( ) => {
179
199
// Use a CAIP-formatted address that forces a non-Ethereum (e.g. Solana) branch.
180
200
const addressNonEth = 'solana:someAddress' ;
181
201
const size = 100 ;
182
202
const svgNonEth = await MaskiconUtilities . getMaskiconSVG (
183
203
addressNonEth ,
184
204
size ,
185
205
) ;
186
-
187
206
// For comparison, generate an Ethereum version.
188
207
const ethAddress = '0xABCDEF1234567890ABCDEF1234567890ABCDEF12' ;
189
208
const svgEth = await MaskiconUtilities . getMaskiconSVG ( ethAddress , size ) ;
190
-
191
209
// They should be different, indicating the non-Ethereum branch (using generateSeedNonEthereum) was taken.
192
- expect ( svgNonEth ) . not . toEqual ( svgEth ) ;
193
- expect ( svgNonEth ) . toContain ( '<svg' ) ;
210
+ expect ( svgNonEth ) . not . toStrictEqual ( svgEth ) ;
211
+ expect ( svgNonEth ) . toStrictEqual ( expect . stringContaining ( '<svg' ) ) ;
194
212
} ) ;
195
213
} ) ;
196
214
} ) ;
197
215
198
216
describe ( 'Maskicon Component' , ( ) => {
199
- test ( 'defaults size prop to 32 if size is not provided' , async ( ) => {
217
+ it ( 'defaults size prop to 32 if size is not provided' , async ( ) => {
200
218
const { getByTestId } = render (
201
219
< Maskicon
202
220
address = "0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8"
@@ -211,7 +229,7 @@ describe('Maskicon Component', () => {
211
229
expect ( svgElement . props . height ) . toBe ( 32 ) ;
212
230
} ) ;
213
231
214
- test ( 'renders SvgXml with correct properties once SVG is ready' , async ( ) => {
232
+ it ( 'renders SvgXml with correct properties once SVG is ready' , async ( ) => {
215
233
const { getByTestId } = render (
216
234
< Maskicon
217
235
address = "0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8"
@@ -225,7 +243,7 @@ describe('Maskicon Component', () => {
225
243
expect ( svgElement . props . xml ) . toContain ( '<svg' ) ;
226
244
} ) ;
227
245
228
- test ( 'forwards additional props to the SvgXml component' , async ( ) => {
246
+ it ( 'forwards additional props to the SvgXml component' , async ( ) => {
229
247
const { getByTestId } = render (
230
248
< Maskicon
231
249
address = "0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8"
@@ -239,7 +257,7 @@ describe('Maskicon Component', () => {
239
257
expect ( forwardedElement ) . toBeDefined ( ) ;
240
258
} ) ;
241
259
242
- test ( 'does not update state if component unmounts before the async effect resolves' , async ( ) => {
260
+ it ( 'does not update state if component unmounts before the async effect resolves' , async ( ) => {
243
261
const deferred = createDeferred < string > ( ) ;
244
262
245
263
const spy = jest
0 commit comments