ftmocks-utils 1.0.4 → 1.0.6
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/package.json +1 -1
- package/src/index.js +142 -1
- package/src/recorder.js +30 -28
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -183,6 +183,144 @@ async function resetAllMockStats({testMockData, testConfig, testName}) {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
async function initiateJestFetch (jest, ftmocksConifg, testName) {
|
|
187
|
+
const testMockData = testName ? loadMockDataFromConfig(ftmocksConifg, testName) : [];
|
|
188
|
+
resetAllMockStats({testMockData, testConfig: ftmocksConifg, testName});
|
|
189
|
+
const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
|
|
190
|
+
global.fetch = jest.fn((url, options = {}) => {
|
|
191
|
+
let mockData = getMatchingMockData({testMockData, defaultMockData, url, options, testConfig: ftmocksConifg, testName});
|
|
192
|
+
if (mockData) {
|
|
193
|
+
console.debug('mocked', url, options);
|
|
194
|
+
} else {
|
|
195
|
+
console.debug('missing mock data', url, options);
|
|
196
|
+
return Promise.resolve({
|
|
197
|
+
status: 404,
|
|
198
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
199
|
+
json: () => Promise.resolve({ error: 'Mock data not found' }),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const { content, headers, status } = mockData.response;
|
|
204
|
+
|
|
205
|
+
return Promise.resolve({
|
|
206
|
+
status,
|
|
207
|
+
headers,
|
|
208
|
+
json: () => Promise.resolve(JSON.parse(content)),
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
global.XMLHttpRequest = jest.fn(function () {
|
|
213
|
+
const xhrMock = {
|
|
214
|
+
open: jest.fn(),
|
|
215
|
+
send: jest.fn(),
|
|
216
|
+
setRequestHeader: jest.fn(),
|
|
217
|
+
getAllResponseHeaders: jest.fn(() => {
|
|
218
|
+
return '';
|
|
219
|
+
}),
|
|
220
|
+
getResponseHeader: jest.fn((header) => {
|
|
221
|
+
return null;
|
|
222
|
+
}),
|
|
223
|
+
readyState: 4,
|
|
224
|
+
status: 0,
|
|
225
|
+
response: null,
|
|
226
|
+
responseText: '',
|
|
227
|
+
onreadystatechange: null,
|
|
228
|
+
onload: null,
|
|
229
|
+
onerror: null,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
xhrMock.send.mockImplementation(function () {
|
|
233
|
+
const mockData = getMatchingMockData({
|
|
234
|
+
testMockData,
|
|
235
|
+
defaultMockData,
|
|
236
|
+
url: xhrMock._url,
|
|
237
|
+
options: xhrMock._options,
|
|
238
|
+
testConfig: ftmocksConifg,
|
|
239
|
+
testName,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (mockData) {
|
|
243
|
+
console.debug('mocked', xhrMock._url, xhrMock._options);
|
|
244
|
+
const { content, headers, status } = mockData.response;
|
|
245
|
+
|
|
246
|
+
xhrMock.status = status;
|
|
247
|
+
xhrMock.responseText = content;
|
|
248
|
+
xhrMock.response = content;
|
|
249
|
+
xhrMock.headers = headers;
|
|
250
|
+
|
|
251
|
+
if (xhrMock.onreadystatechange) {
|
|
252
|
+
xhrMock.onreadystatechange();
|
|
253
|
+
}
|
|
254
|
+
if (xhrMock.onload) {
|
|
255
|
+
xhrMock.onload();
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
console.debug('missing mock data', xhrMock._url, xhrMock._options);
|
|
259
|
+
|
|
260
|
+
xhrMock.status = 404;
|
|
261
|
+
xhrMock.responseText = JSON.stringify({ error: 'Mock data not found' });
|
|
262
|
+
xhrMock.response = xhrMock.responseText;
|
|
263
|
+
|
|
264
|
+
if (xhrMock.onreadystatechange) {
|
|
265
|
+
xhrMock.onreadystatechange();
|
|
266
|
+
}
|
|
267
|
+
if (xhrMock.onerror) {
|
|
268
|
+
xhrMock.onerror();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
xhrMock.open.mockImplementation(function (method, url) {
|
|
274
|
+
xhrMock._options = { method };
|
|
275
|
+
xhrMock._url = url;
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return xhrMock;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
function countFilesInDirectory(directoryPath) {
|
|
286
|
+
return new Promise((resolve, reject) => {
|
|
287
|
+
fs.readdir(directoryPath, (err, files) => {
|
|
288
|
+
if (err) {
|
|
289
|
+
return reject(err); // Handle error
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Filter out directories and only count files
|
|
293
|
+
const fileCount = files.filter(file => {
|
|
294
|
+
const filePath = path.join(directoryPath, file);
|
|
295
|
+
return fs.statSync(filePath).isFile();
|
|
296
|
+
}).length;
|
|
297
|
+
|
|
298
|
+
resolve(fileCount);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const saveSnap = async (html, ftmocksConifg, testName) => {
|
|
304
|
+
const snapFolder = path.join(ftmocksConifg.MOCK_DIR, `test_${nameToFolder(testName)}`, '_snaps');
|
|
305
|
+
const snapTemplate = path.join(ftmocksConifg.MOCK_DIR, 'snap_template.html');
|
|
306
|
+
|
|
307
|
+
if (!fs.existsSync(snapFolder)) {
|
|
308
|
+
fs.mkdirSync(snapFolder);
|
|
309
|
+
}
|
|
310
|
+
const fileCount = await (countFilesInDirectory(snapFolder));
|
|
311
|
+
const snapFilePath = path.join(snapFolder, `snap_${fileCount + 1}.html`);
|
|
312
|
+
let resHtml = html;
|
|
313
|
+
if (fs.existsSync(snapFolder)) {
|
|
314
|
+
const templateHtml = fs.readFileSync(snapTemplate, 'utf8');;
|
|
315
|
+
resHtml = templateHtml.replace('<!--FtMocks-Snap-Template-To-Be-Replaced-->', html)
|
|
316
|
+
}
|
|
317
|
+
fs.writeFileSync(snapFilePath, resHtml)
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const deleteAllSnaps = async (ftmocksConifg, testName) => {
|
|
321
|
+
const snapFolder = path.join(ftmocksConifg.MOCK_DIR, `test_${nameToFolder(testName)}`, '_snaps');
|
|
322
|
+
fs.rmSync(snapFolder, { recursive: true, force: true });
|
|
323
|
+
};
|
|
186
324
|
|
|
187
325
|
// Export functions as a module
|
|
188
326
|
module.exports = {
|
|
@@ -194,5 +332,8 @@ module.exports = {
|
|
|
194
332
|
nameToFolder,
|
|
195
333
|
compareMockToFetchRequest,
|
|
196
334
|
getMatchingMockData,
|
|
197
|
-
resetAllMockStats
|
|
335
|
+
resetAllMockStats,
|
|
336
|
+
initiateJestFetch,
|
|
337
|
+
saveSnap,
|
|
338
|
+
deleteAllSnaps
|
|
198
339
|
};
|
package/src/recorder.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
// Intercept Fetch API
|
|
3
3
|
const originalFetch = window.fetch;
|
|
4
4
|
const recordedTracks = [];
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
const addTrack = track => {
|
|
7
7
|
track.id = recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1;
|
|
8
8
|
track.time = new Date();
|
|
9
|
+
track.bodyHtml = document.documentElement.outerHTML.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');;
|
|
10
|
+
|
|
9
11
|
fetch(window.FTMOCKS_CONFIG.record_events_url, {
|
|
10
12
|
method: 'POST',
|
|
11
13
|
headers: {
|
|
@@ -14,8 +16,8 @@
|
|
|
14
16
|
body: JSON.stringify(track),
|
|
15
17
|
}).then(response => response.json());
|
|
16
18
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
|
|
19
21
|
window.fetch = async function (url, options = {}) {
|
|
20
22
|
const method = options.method || 'GET';
|
|
21
23
|
const body = options.body;
|
|
@@ -60,10 +62,10 @@
|
|
|
60
62
|
});
|
|
61
63
|
return response;
|
|
62
64
|
};
|
|
63
|
-
|
|
65
|
+
|
|
64
66
|
// Intercept XMLHttpRequest
|
|
65
67
|
const originalXHR = window.XMLHttpRequest;
|
|
66
|
-
|
|
68
|
+
|
|
67
69
|
function MockXHR() {
|
|
68
70
|
const xhr = new originalXHR();
|
|
69
71
|
const originalOpen = xhr.open;
|
|
@@ -72,7 +74,7 @@
|
|
|
72
74
|
let requestDetails = {
|
|
73
75
|
headers: {},
|
|
74
76
|
};
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
// Override 'open' method
|
|
77
79
|
xhr.open = function (method, url, async, user, password) {
|
|
78
80
|
requestDetails.method = method;
|
|
@@ -83,13 +85,13 @@
|
|
|
83
85
|
requestDetails.queryString = url.includes('?') ? url.split('?')[1] : null;
|
|
84
86
|
originalOpen.apply(xhr, arguments);
|
|
85
87
|
};
|
|
86
|
-
|
|
88
|
+
|
|
87
89
|
// Override 'setRequestHeader' to log headers
|
|
88
90
|
xhr.setRequestHeader = function (header, value) {
|
|
89
91
|
requestDetails.headers[header] = value;
|
|
90
92
|
originalSetRequestHeader.apply(xhr, arguments);
|
|
91
93
|
};
|
|
92
|
-
|
|
94
|
+
|
|
93
95
|
// Override 'send' method
|
|
94
96
|
xhr.send = function (body) {
|
|
95
97
|
requestDetails.body = body;
|
|
@@ -124,63 +126,63 @@
|
|
|
124
126
|
},
|
|
125
127
|
body: JSON.stringify(mockResponse),
|
|
126
128
|
}).then(response => response.json());
|
|
127
|
-
|
|
129
|
+
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
if (originalOnReadyStateChange) originalOnReadyStateChange.apply(xhr, arguments);
|
|
131
133
|
};
|
|
132
134
|
originalSend.apply(xhr, arguments);
|
|
133
135
|
};
|
|
134
|
-
|
|
136
|
+
|
|
135
137
|
return xhr;
|
|
136
138
|
}
|
|
137
|
-
|
|
139
|
+
|
|
138
140
|
window.XMLHttpRequest = MockXHR;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
|
|
142
|
+
|
|
141
143
|
const generateXPathWithNearestParentId = (element) => {
|
|
142
144
|
let path = '';
|
|
143
145
|
let nearestParentId = null;
|
|
144
|
-
|
|
146
|
+
|
|
145
147
|
// Check if the current element's has an ID
|
|
146
148
|
if (element.id) {
|
|
147
149
|
nearestParentId = element.id;
|
|
148
150
|
}
|
|
149
|
-
|
|
151
|
+
|
|
150
152
|
while (!nearestParentId && element !== document.body && element) {
|
|
151
153
|
const tagName = element.tagName.toLowerCase();
|
|
152
154
|
let index = 1;
|
|
153
155
|
let sibling = element.previousElementSibling;
|
|
154
|
-
|
|
156
|
+
|
|
155
157
|
while (sibling) {
|
|
156
158
|
if (sibling.tagName.toLowerCase() === tagName) {
|
|
157
159
|
index += 1;
|
|
158
160
|
}
|
|
159
161
|
sibling = sibling.previousElementSibling;
|
|
160
162
|
}
|
|
161
|
-
|
|
163
|
+
|
|
162
164
|
if (index === 1) {
|
|
163
165
|
path = `/${tagName}${path}`;
|
|
164
166
|
} else {
|
|
165
167
|
path = `/${tagName}[${index}]${path}`;
|
|
166
168
|
}
|
|
167
|
-
|
|
169
|
+
|
|
168
170
|
// Check if the current element's parent has an ID
|
|
169
171
|
if (element.parentElement && element.parentElement.id) {
|
|
170
172
|
nearestParentId = element.parentElement.id;
|
|
171
173
|
break; // Stop searching when we find the nearest parent with an ID
|
|
172
174
|
}
|
|
173
|
-
|
|
175
|
+
|
|
174
176
|
element = element.parentElement;
|
|
175
177
|
}
|
|
176
|
-
|
|
178
|
+
|
|
177
179
|
if (nearestParentId) {
|
|
178
180
|
path = `//*[@id='${nearestParentId}']${path}`;
|
|
179
181
|
return path;
|
|
180
182
|
}
|
|
181
183
|
return null; // No parent with an ID found
|
|
182
184
|
};
|
|
183
|
-
|
|
185
|
+
|
|
184
186
|
const handleMouseEvent = (type, limit) => event => {
|
|
185
187
|
const target = generateXPathWithNearestParentId(event.target);
|
|
186
188
|
const track = {
|
|
@@ -195,7 +197,7 @@
|
|
|
195
197
|
recordedTracks.push(track);
|
|
196
198
|
addTrack(track);
|
|
197
199
|
};
|
|
198
|
-
|
|
200
|
+
|
|
199
201
|
const handleChange = limit => event => {
|
|
200
202
|
const prevCommand =
|
|
201
203
|
recordedTracks && recordedTracks.length ? recordedTracks[recordedTracks.length - 1] : null;
|
|
@@ -220,7 +222,7 @@
|
|
|
220
222
|
recordedTracks.push(track);
|
|
221
223
|
addTrack(track);
|
|
222
224
|
};
|
|
223
|
-
|
|
225
|
+
|
|
224
226
|
const handleDocumentLoad = limit => () => {
|
|
225
227
|
let oldHref = document.location.href;
|
|
226
228
|
const body = document.querySelector('body');
|
|
@@ -242,15 +244,15 @@
|
|
|
242
244
|
});
|
|
243
245
|
observer.observe(body, { childList: true, subtree: true });
|
|
244
246
|
};
|
|
245
|
-
|
|
247
|
+
|
|
246
248
|
const clearTracks = () => {
|
|
247
249
|
recordedTracks = [];
|
|
248
250
|
};
|
|
249
|
-
|
|
251
|
+
|
|
250
252
|
const getAllTracks = () => {
|
|
251
253
|
return recordedTracks;
|
|
252
254
|
};
|
|
253
|
-
|
|
255
|
+
|
|
254
256
|
const initTracks = (initInfo = {events: ['click', 'change', 'url', 'dblclick', 'contextmenu'], limit: 100}) => {
|
|
255
257
|
const {events, limit} = initInfo;
|
|
256
258
|
const mouseEvents = {
|
|
@@ -276,5 +278,5 @@
|
|
|
276
278
|
});
|
|
277
279
|
};
|
|
278
280
|
initTracks();
|
|
279
|
-
|
|
280
|
-
})();
|
|
281
|
+
|
|
282
|
+
})();
|