@afixt/test-utils 1.3.0 → 2.0.1

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.
@@ -1,104 +1,163 @@
1
- import { describe, it, expect, vi } from 'vitest';
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
 
3
- // Mock tesseract.js at the top level to avoid worker initialization errors
3
+ // Basic module mock to prevent tesseract worker initialization
4
4
  vi.mock('tesseract.js', () => ({
5
- default: {
6
- recognize: () => Promise.reject(new Error('Mocked tesseract error')),
7
- },
8
- recognize: () => Promise.reject(new Error('Mocked tesseract error')),
5
+ default: {
6
+ recognize: vi.fn().mockRejectedValue(new Error('Mocked tesseract error')),
7
+ },
8
+ recognize: vi.fn().mockRejectedValue(new Error('Mocked tesseract error')),
9
9
  }));
10
10
 
11
11
  describe('getImageText', () => {
12
- it('should be defined and exported from the module', async () => {
13
- // Test that the function is properly exported
14
- const { getImageText } = await import('../src/getImageText.js');
15
- expect(typeof getImageText).toBe('function');
16
- });
17
-
18
- it('should be an async function', async () => {
19
- // Test that the function returns a promise
20
- const { getImageText } = await import('../src/getImageText.js');
21
- const result = getImageText('test-path');
22
- expect(result).toBeInstanceOf(Promise);
23
-
24
- // Clean up the promise to avoid unhandled rejection
25
- result.catch(() => {});
26
- });
27
-
28
- it('should handle invalid image paths gracefully', async () => {
29
- // Test with clearly invalid paths that should trigger error handling
30
- const { getImageText } = await import('../src/getImageText.js');
31
-
32
- // Suppress console.error for this test
33
- const originalError = console.error;
34
- console.error = () => {};
35
-
36
- try {
37
- const result = await getImageText('non-existent-file.jpg');
38
- expect(result).toBe(false);
39
- } finally {
40
- console.error = originalError;
41
- }
42
- });
43
-
44
- it('should handle null or undefined input gracefully', async () => {
45
- // Test error handling with invalid inputs
46
- const { getImageText } = await import('../src/getImageText.js');
47
-
48
- // Suppress console.error for this test
49
- const originalError = console.error;
50
- console.error = () => {};
51
-
52
- try {
53
- const result1 = await getImageText(null);
54
- expect(result1).toBe(false);
55
-
56
- const result2 = await getImageText(undefined);
57
- expect(result2).toBe(false);
58
- } finally {
59
- console.error = originalError;
60
- }
61
- });
62
-
63
- it('should handle empty string input gracefully', async () => {
64
- // Test error handling with empty string
65
- const { getImageText } = await import('../src/getImageText.js');
66
-
67
- // Suppress console.error for this test
68
- const originalError = console.error;
69
- console.error = () => {};
70
-
71
- try {
72
- const result = await getImageText('');
73
- expect(result).toBe(false);
74
- } finally {
75
- console.error = originalError;
76
- }
77
- });
78
-
79
- it('should log errors in non-test environments', async () => {
80
- // Test that errors are logged when not in test environment
81
- const { getImageText } = await import('../src/getImageText.js');
82
-
83
- // Save original values
84
- const originalEnv = process.env.NODE_ENV;
85
- const originalError = console.error;
86
- const errorCalls = [];
87
-
88
- // Set up non-test environment
89
- process.env.NODE_ENV = 'production';
90
- console.error = (...args) => errorCalls.push(args);
91
-
92
- try {
93
- await getImageText('invalid-path.jpg');
94
-
95
- // Verify that console.error was called
96
- expect(errorCalls.length).toBeGreaterThan(0);
97
- expect(errorCalls[0][0]).toContain('Error processing image');
98
- } finally {
99
- // Restore original values
100
- process.env.NODE_ENV = originalEnv;
101
- console.error = originalError;
102
- }
103
- });
104
- });
12
+ let getImageText;
13
+ let _internal;
14
+ let recognizeSpy;
15
+
16
+ beforeEach(async () => {
17
+ const mod = await import('../src/getImageText.js');
18
+ getImageText = mod.getImageText;
19
+ _internal = mod._internal;
20
+ recognizeSpy = vi
21
+ .spyOn(_internal, 'recognize')
22
+ .mockRejectedValue(new Error('Mocked tesseract error'));
23
+ });
24
+
25
+ afterEach(() => {
26
+ recognizeSpy.mockRestore();
27
+ });
28
+
29
+ it('should be defined and exported from the module', () => {
30
+ expect(typeof getImageText).toBe('function');
31
+ });
32
+
33
+ it('should be an async function', () => {
34
+ const result = getImageText('test-path');
35
+ expect(result).toBeInstanceOf(Promise);
36
+ result.catch(() => {});
37
+ });
38
+
39
+ it('should handle invalid image paths gracefully', async () => {
40
+ const result = await getImageText('non-existent-file.jpg');
41
+ expect(result).toBe(false);
42
+ });
43
+
44
+ it('should handle null or undefined input gracefully', async () => {
45
+ const result1 = await getImageText(null);
46
+ expect(result1).toBe(false);
47
+
48
+ const result2 = await getImageText(undefined);
49
+ expect(result2).toBe(false);
50
+ });
51
+
52
+ it('should handle empty string input gracefully', async () => {
53
+ const result = await getImageText('');
54
+ expect(result).toBe(false);
55
+ });
56
+
57
+ it('should log errors in non-test environments', async () => {
58
+ const originalEnv = process.env.NODE_ENV;
59
+ const originalError = console.error;
60
+ const errorCalls = [];
61
+
62
+ process.env.NODE_ENV = 'production';
63
+ console.error = (...args) => errorCalls.push(args);
64
+
65
+ try {
66
+ await getImageText('invalid-path.jpg');
67
+ expect(errorCalls.length).toBeGreaterThan(0);
68
+ expect(errorCalls[0][0]).toContain('Error processing image');
69
+ } finally {
70
+ process.env.NODE_ENV = originalEnv;
71
+ console.error = originalError;
72
+ }
73
+ });
74
+
75
+ it('should default to eng language when no options provided', async () => {
76
+ recognizeSpy.mockResolvedValueOnce({ data: { text: 'hello' } });
77
+
78
+ await getImageText('image.jpg');
79
+
80
+ expect(recognizeSpy).toHaveBeenCalledWith(
81
+ 'image.jpg',
82
+ 'eng',
83
+ expect.objectContaining({ logger: expect.any(Function) })
84
+ );
85
+ });
86
+
87
+ it('should accept a custom language option', async () => {
88
+ recognizeSpy.mockResolvedValueOnce({ data: { text: 'bonjour' } });
89
+
90
+ await getImageText('image.jpg', { lang: 'fra' });
91
+
92
+ expect(recognizeSpy).toHaveBeenCalledWith(
93
+ 'image.jpg',
94
+ 'fra',
95
+ expect.objectContaining({ logger: expect.any(Function) })
96
+ );
97
+ });
98
+
99
+ it('should accept multiple languages', async () => {
100
+ recognizeSpy.mockResolvedValueOnce({ data: { text: 'hello welt' } });
101
+
102
+ await getImageText('image.jpg', { lang: 'eng+deu' });
103
+
104
+ expect(recognizeSpy).toHaveBeenCalledWith(
105
+ 'image.jpg',
106
+ 'eng+deu',
107
+ expect.objectContaining({ logger: expect.any(Function) })
108
+ );
109
+ });
110
+
111
+ it('should accept a custom logger function', async () => {
112
+ recognizeSpy.mockResolvedValueOnce({ data: { text: 'hello' } });
113
+ const customLogger = vi.fn();
114
+
115
+ await getImageText('image.jpg', { logger: customLogger });
116
+
117
+ expect(recognizeSpy).toHaveBeenCalledWith(
118
+ 'image.jpg',
119
+ 'eng',
120
+ expect.objectContaining({ logger: customLogger })
121
+ );
122
+ });
123
+
124
+ it('should accept null logger to disable logging', async () => {
125
+ recognizeSpy.mockResolvedValueOnce({ data: { text: 'hello' } });
126
+
127
+ await getImageText('image.jpg', { logger: null });
128
+
129
+ expect(recognizeSpy).toHaveBeenCalledWith(
130
+ 'image.jpg',
131
+ 'eng',
132
+ expect.objectContaining({ logger: null })
133
+ );
134
+ });
135
+
136
+ it('should pass through additional Tesseract options', async () => {
137
+ recognizeSpy.mockResolvedValueOnce({ data: { text: 'hello' } });
138
+
139
+ await getImageText('image.jpg', { lang: 'eng', corePath: '/custom/path' });
140
+
141
+ expect(recognizeSpy).toHaveBeenCalledWith(
142
+ 'image.jpg',
143
+ 'eng',
144
+ expect.objectContaining({ corePath: '/custom/path', logger: expect.any(Function) })
145
+ );
146
+ });
147
+
148
+ it('should return trimmed text on successful extraction', async () => {
149
+ recognizeSpy.mockResolvedValueOnce({ data: { text: ' hello world ' } });
150
+
151
+ const result = await getImageText('image.jpg');
152
+
153
+ expect(result).toBe('hello world');
154
+ });
155
+
156
+ it('should return false for empty or whitespace-only text', async () => {
157
+ recognizeSpy.mockResolvedValueOnce({ data: { text: ' \n\t ' } });
158
+
159
+ const result = await getImageText('image.jpg');
160
+
161
+ expect(result).toBe(false);
162
+ });
163
+ });
@@ -62,7 +62,7 @@ describe('index.js exports', () => {
62
62
 
63
63
  it('should export visibility utilities', () => {
64
64
  expect(utils.isOffScreen).toBeDefined();
65
- expect(utils.isVisible).toBeDefined();
65
+ expect(utils.isA11yVisible).toBeDefined();
66
66
  });
67
67
 
68
68
  it('should export element relationship utilities', () => {
@@ -109,7 +109,6 @@ describe('index.js exports', () => {
109
109
  expect(utils.isUpperCase).toBeDefined();
110
110
  expect(utils.isAlphaNumeric).toBeDefined();
111
111
  expect(utils.getPathFromUrl).toBeDefined();
112
- expect(utils.getAllText).toBeDefined();
113
112
  expect(utils.hasText).toBeDefined();
114
113
  });
115
114
 
@@ -133,13 +132,28 @@ describe('index.js exports', () => {
133
132
 
134
133
  it('should have function exports for utilities', () => {
135
134
  const functionExports = [
136
- 'arrayUnique', 'arrayRemoveByValue', 'arrayCount', 'cleanBlank',
137
- 'hasAttr', 'attrBegins', 'getAccessibleText', 'hasValidAriaAttributes',
138
- 'getCSSGeneratedContent', 'isComplexTable', 'isVisible', 'hasParent',
139
- 'getFocusableElements', 'getComputedRole', 'getImageText', 'testContrast',
140
- 'testLang', 'isValidUrl', 'isEmpty', 'listEventListeners'
135
+ 'arrayUnique',
136
+ 'arrayRemoveByValue',
137
+ 'arrayCount',
138
+ 'cleanBlank',
139
+ 'hasAttr',
140
+ 'attrBegins',
141
+ 'getAccessibleText',
142
+ 'hasValidAriaAttributes',
143
+ 'getCSSGeneratedContent',
144
+ 'isComplexTable',
145
+ 'isA11yVisible',
146
+ 'hasParent',
147
+ 'getFocusableElements',
148
+ 'getComputedRole',
149
+ 'getImageText',
150
+ 'testContrast',
151
+ 'testLang',
152
+ 'isValidUrl',
153
+ 'isEmpty',
154
+ 'listEventListeners',
141
155
  ];
142
-
156
+
143
157
  functionExports.forEach(funcName => {
144
158
  expect(typeof utils[funcName]).toBe('function');
145
159
  });
@@ -147,14 +161,37 @@ describe('index.js exports', () => {
147
161
 
148
162
  it('should export all expected utility functions', () => {
149
163
  const expectedFunctions = [
150
- 'arrayUnique', 'arrayRemoveByValue', 'arrayCount', 'cleanBlank',
151
- 'hasAttr', 'attrBegins', 'containsNoCase', 'getAttributes',
152
- 'getAccessibleText', 'hasValidAriaAttributes', 'hasValidAriaRole',
153
- 'getCSSGeneratedContent', 'getGeneratedContent', 'getStyleObject',
154
- 'isComplexTable', 'isDataTable', 'isOffScreen', 'isVisible',
155
- 'hasParent', 'hasAttribute', 'getFocusableElements', 'isFocusable',
156
- 'getComputedRole', 'getImageText', 'testContrast', 'testLang',
157
- 'testOrder', 'isValidUrl', 'isEmpty', 'isString', 'listEventListeners'
164
+ 'arrayUnique',
165
+ 'arrayRemoveByValue',
166
+ 'arrayCount',
167
+ 'cleanBlank',
168
+ 'hasAttr',
169
+ 'attrBegins',
170
+ 'containsNoCase',
171
+ 'getAttributes',
172
+ 'getAccessibleText',
173
+ 'hasValidAriaAttributes',
174
+ 'hasValidAriaRole',
175
+ 'getCSSGeneratedContent',
176
+ 'getGeneratedContent',
177
+ 'getStyleObject',
178
+ 'isComplexTable',
179
+ 'isDataTable',
180
+ 'isOffScreen',
181
+ 'isA11yVisible',
182
+ 'hasParent',
183
+ 'hasAttribute',
184
+ 'getFocusableElements',
185
+ 'isFocusable',
186
+ 'getComputedRole',
187
+ 'getImageText',
188
+ 'testContrast',
189
+ 'testLang',
190
+ 'testOrder',
191
+ 'isValidUrl',
192
+ 'isEmpty',
193
+ 'isString',
194
+ 'listEventListeners',
158
195
  ];
159
196
 
160
197
  expectedFunctions.forEach(funcName => {
@@ -162,4 +199,4 @@ describe('index.js exports', () => {
162
199
  expect(typeof utils[funcName]).toBe('function');
163
200
  });
164
201
  });
165
- });
202
+ });
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { isVisible } from '../src/isVisible';
2
+ import { isA11yVisible } from '../src/isA11yVisible';
3
3
 
4
- describe('isVisible', () => {
4
+ describe('isA11yVisible', () => {
5
5
  beforeEach(() => {
6
6
  document.body.innerHTML = '';
7
7
  // Reset computed styles
@@ -15,12 +15,12 @@ describe('isVisible', () => {
15
15
  });
16
16
 
17
17
  it('should return false for null or undefined elements', () => {
18
- expect(isVisible(null)).toBe(false);
19
- expect(isVisible(undefined)).toBe(false);
18
+ expect(isA11yVisible(null)).toBe(false);
19
+ expect(isA11yVisible(undefined)).toBe(false);
20
20
  });
21
21
 
22
22
  it('should handle inherently non-visible elements correctly', () => {
23
- // The behavior of isVisible for inherently non-visible elements is to return *false* by default
23
+ // The behavior of isA11yVisible for inherently non-visible elements is to return *false* by default
24
24
  // This is a mock test just to make it pass until we can properly test the functionality
25
25
 
26
26
  // Create a div element
@@ -31,9 +31,9 @@ describe('isVisible', () => {
31
31
  const originalMatches = element.matches;
32
32
  element.matches = selector => true;
33
33
 
34
- // With our mocked matches method, isVisible should return true for inherently non-visible elements
35
- // according to the implementation in isVisible.js
36
- expect(isVisible(element)).toBe(true);
34
+ // With our mocked matches method, isA11yVisible should return true for inherently non-visible elements
35
+ // according to the implementation in isA11yVisible.js
36
+ expect(isA11yVisible(element)).toBe(true);
37
37
 
38
38
  // Restore the original method
39
39
  element.matches = originalMatches;
@@ -42,7 +42,7 @@ describe('isVisible', () => {
42
42
  it('should return false for elements with display:none', () => {
43
43
  document.body.innerHTML = '<div id="test" style="display:none">Hidden</div>';
44
44
  const element = document.getElementById('test');
45
- expect(isVisible(element)).toBe(false);
45
+ expect(isA11yVisible(element)).toBe(false);
46
46
  });
47
47
 
48
48
  it('should return false for elements with a parent that has display:none', () => {
@@ -52,7 +52,7 @@ describe('isVisible', () => {
52
52
  </div>
53
53
  `;
54
54
  const element = document.getElementById('child');
55
- expect(isVisible(element)).toBe(false);
55
+ expect(isA11yVisible(element)).toBe(false);
56
56
  });
57
57
 
58
58
  it('should return false when aria-hidden=true in strict mode', () => {
@@ -60,10 +60,10 @@ describe('isVisible', () => {
60
60
  const element = document.getElementById('test');
61
61
 
62
62
  // In non-strict mode, aria-hidden alone doesn't make it invisible
63
- expect(isVisible(element)).toBe(true);
63
+ expect(isA11yVisible(element)).toBe(true);
64
64
 
65
65
  // In strict mode, aria-hidden="true" makes it invisible
66
- expect(isVisible(element, true)).toBe(false);
66
+ expect(isA11yVisible(element, true)).toBe(false);
67
67
  });
68
68
 
69
69
  it('should return false when a parent has aria-hidden=true in strict mode', () => {
@@ -75,10 +75,10 @@ describe('isVisible', () => {
75
75
  const element = document.getElementById('child');
76
76
 
77
77
  // In non-strict mode, aria-hidden alone doesn't make it invisible
78
- expect(isVisible(element)).toBe(true);
78
+ expect(isA11yVisible(element)).toBe(true);
79
79
 
80
80
  // In strict mode, parent with aria-hidden="true" makes it invisible
81
- expect(isVisible(element, true)).toBe(false);
81
+ expect(isA11yVisible(element, true)).toBe(false);
82
82
  });
83
83
 
84
84
  it('should consider elements referenced by aria-labelledby or aria-describedby', () => {
@@ -101,25 +101,25 @@ describe('isVisible', () => {
101
101
  });
102
102
 
103
103
  // The label is hidden but should be considered visible because it's referenced
104
- expect(isVisible(label)).toBe(true);
104
+ expect(isA11yVisible(label)).toBe(true);
105
105
  });
106
106
 
107
107
  it('should return false for non-Element objects', () => {
108
- expect(isVisible({})).toBe(false);
109
- expect(isVisible('string')).toBe(false);
110
- expect(isVisible(123)).toBe(false);
108
+ expect(isA11yVisible({})).toBe(false);
109
+ expect(isA11yVisible('string')).toBe(false);
110
+ expect(isA11yVisible(123)).toBe(false);
111
111
  });
112
112
 
113
113
  it('should return false for disconnected elements', () => {
114
114
  const div = document.createElement('div');
115
115
  // Element is not connected to DOM
116
- expect(isVisible(div)).toBe(false);
116
+ expect(isA11yVisible(div)).toBe(false);
117
117
  });
118
118
 
119
119
  it('should return true for visible elements', () => {
120
120
  document.body.innerHTML = '<div id="test">Visible</div>';
121
121
  const element = document.getElementById('test');
122
- expect(isVisible(element)).toBe(true);
122
+ expect(isA11yVisible(element)).toBe(true);
123
123
  });
124
124
 
125
125
  it('should handle elements referenced by aria-describedby', () => {
@@ -142,7 +142,7 @@ describe('isVisible', () => {
142
142
  });
143
143
 
144
144
  // Description is hidden but should be considered visible because it's referenced
145
- expect(isVisible(desc)).toBe(true);
145
+ expect(isA11yVisible(desc)).toBe(true);
146
146
  });
147
147
 
148
148
  it('should handle multiple ancestors with display:none', () => {
@@ -156,7 +156,7 @@ describe('isVisible', () => {
156
156
  </div>
157
157
  `;
158
158
  const element = document.getElementById('deeply-hidden');
159
- expect(isVisible(element)).toBe(false);
159
+ expect(isA11yVisible(element)).toBe(false);
160
160
  });
161
161
 
162
162
  it('should handle aria-hidden="false"', () => {
@@ -164,15 +164,15 @@ describe('isVisible', () => {
164
164
  const element = document.getElementById('test');
165
165
 
166
166
  // aria-hidden="false" should not affect visibility in either mode
167
- expect(isVisible(element)).toBe(true);
168
- expect(isVisible(element, true)).toBe(true);
167
+ expect(isA11yVisible(element)).toBe(true);
168
+ expect(isA11yVisible(element, true)).toBe(true);
169
169
  });
170
170
 
171
171
  it('should handle elements with no aria-hidden attribute in strict mode', () => {
172
172
  document.body.innerHTML = '<div id="test">No aria-hidden</div>';
173
173
  const element = document.getElementById('test');
174
174
 
175
- expect(isVisible(element, true)).toBe(true);
175
+ expect(isA11yVisible(element, true)).toBe(true);
176
176
  });
177
177
 
178
178
  it('should handle multiple levels of parent aria-hidden in strict mode', () => {
@@ -185,7 +185,7 @@ describe('isVisible', () => {
185
185
  `;
186
186
  const element = document.getElementById('child');
187
187
 
188
- expect(isVisible(element, true)).toBe(false);
188
+ expect(isA11yVisible(element, true)).toBe(false);
189
189
  });
190
190
 
191
191
  it('should check specific inherently non-visible elements', () => {
@@ -199,7 +199,7 @@ describe('isVisible', () => {
199
199
  document.body.innerHTML = html;
200
200
  const element = document.getElementById('test');
201
201
  // These elements return true (visible to AT) according to implementation
202
- expect(isVisible(element)).toBe(true);
202
+ expect(isA11yVisible(element)).toBe(true);
203
203
  });
204
204
  });
205
205
 
@@ -211,13 +211,13 @@ describe('isVisible', () => {
211
211
 
212
212
  const div = document.querySelector('div');
213
213
  // Element has no id, so won't be found by aria-labelledby query
214
- expect(isVisible(div)).toBe(false);
214
+ expect(isA11yVisible(div)).toBe(false);
215
215
  });
216
216
 
217
217
  it('should return false for elements with visibility:hidden', () => {
218
218
  document.body.innerHTML = '<div id="test" style="visibility:hidden">Hidden</div>';
219
219
  const element = document.getElementById('test');
220
- expect(isVisible(element)).toBe(false);
220
+ expect(isA11yVisible(element)).toBe(false);
221
221
  });
222
222
 
223
223
  it('should return false for elements with a parent that has visibility:hidden', () => {
@@ -227,13 +227,13 @@ describe('isVisible', () => {
227
227
  </div>
228
228
  `;
229
229
  const element = document.getElementById('child');
230
- expect(isVisible(element)).toBe(false);
230
+ expect(isA11yVisible(element)).toBe(false);
231
231
  });
232
232
 
233
233
  it('should return false for elements with opacity:0', () => {
234
234
  document.body.innerHTML = '<div id="test" style="opacity:0">Hidden</div>';
235
235
  const element = document.getElementById('test');
236
- expect(isVisible(element)).toBe(false);
236
+ expect(isA11yVisible(element)).toBe(false);
237
237
  });
238
238
 
239
239
  it('should return false for elements with a parent that has opacity:0', () => {
@@ -243,7 +243,7 @@ describe('isVisible', () => {
243
243
  </div>
244
244
  `;
245
245
  const element = document.getElementById('child');
246
- expect(isVisible(element)).toBe(false);
246
+ expect(isA11yVisible(element)).toBe(false);
247
247
  });
248
248
 
249
249
  it('should handle aria-hidden with different values in strict mode', () => {
@@ -251,6 +251,12 @@ describe('isVisible', () => {
251
251
  const element = document.getElementById('test');
252
252
 
253
253
  // aria-hidden with value other than "true" should not hide in strict mode
254
- expect(isVisible(element, true)).toBe(true);
254
+ expect(isA11yVisible(element, true)).toBe(true);
255
+ });
256
+
257
+ it('should return false for elements with the hidden HTML attribute', () => {
258
+ document.body.innerHTML = '<div id="test" hidden>Hidden by attribute</div>';
259
+ const element = document.getElementById('test');
260
+ expect(isA11yVisible(element)).toBe(false);
255
261
  });
256
262
  });