ftmocks-utils 1.0.2 → 1.0.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ftmocks-utils",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -34,7 +34,7 @@ const areJsonEqual = (jsonObj1, jsonObj2) => {
34
34
  }
35
35
 
36
36
  const getDefaultMockDataFromConfig = (testConfig) => {
37
- const defaultPath = path.join(testConfig.MOCK_DIR, testConfig.MOCK_DEFAULT_FILE);
37
+ const defaultPath = path.join(testConfig.MOCK_DIR, 'default.json');
38
38
 
39
39
  try {
40
40
  const defaultData = fs.readFileSync(defaultPath, 'utf8');
@@ -42,7 +42,7 @@ try {
42
42
 
43
43
  // Read and attach mock data for each entry in parsedData
44
44
  parsedData.forEach(entry => {
45
- const mockFilePath = path.join(testConfig.MOCK_DIR, testConfig.MOCK_DEFAULT_DIR, `mock_${entry.id}.json`);;
45
+ const mockFilePath = path.join(testConfig.MOCK_DIR, 'defaultMocks', `mock_${entry.id}.json`);;
46
46
  try {
47
47
  const mockData = fs.readFileSync(mockFilePath, 'utf8');
48
48
  entry.fileContent = JSON.parse(mockData);
@@ -53,7 +53,7 @@ try {
53
53
  });
54
54
  return parsedData;
55
55
  } catch (error) {
56
- console.error(`Error reading or parsing ${testConfig.MOCK_DEFAULT_FILE}:`, error);
56
+ console.error(`Error reading or parsing default.json:`, error);
57
57
  return [];
58
58
  }
59
59
  }
@@ -69,7 +69,7 @@ const loadMockDataFromConfig = (testConfig, _testName) => {
69
69
  const config = JSON.parse(configData);
70
70
  testName = config.testName;
71
71
  }
72
- // Read the tests from testConfig.MOCK_TEST_FILE
72
+ // Read the tests from testConfig
73
73
  const mocksPath = path.join(testConfig.MOCK_DIR, `test_${nameToFolder(testName)}`, '_mock_list.json');
74
74
  const mocksData = fs.readFileSync(mocksPath, 'utf8');
75
75
  const mocks = JSON.parse(mocksData);
@@ -87,7 +87,17 @@ const loadMockDataFromConfig = (testConfig, _testName) => {
87
87
  }
88
88
  };
89
89
 
90
+ const clearNulls = postData => {
91
+ Object.keys(postData || {}).forEach(key => {
92
+ if(postData[key] === null) {
93
+ delete postData[key];
94
+ }
95
+ });
96
+ };
97
+
90
98
  const isSameRequest = (req1, req2) => {
99
+ clearNulls(req1.postData);
100
+ clearNulls(req2.postData);
91
101
  let matched = true;
92
102
  if(req1.url !== req2.url) {
93
103
  matched = false;
@@ -147,7 +157,7 @@ function getMatchingMockData({testMockData, defaultMockData, url, options, testC
147
157
  served = mock.fileContent.served;
148
158
  return compareMockToFetchRequest(mock, { url, options });
149
159
  }) || [];
150
- let foundMock = matchedMocks.find(mock => !mock.fileContent.served) ? matchedMocks.find(mock => !mock.fileContent.served) : matchedMocks[0];
160
+ let foundMock = matchedMocks.find(mock => !mock.fileContent.served) ? matchedMocks.find(mock => !mock.fileContent.served) : matchedMocks[matchedMocks.length - 1];
151
161
  // updating stats to mock file
152
162
  if(foundMock) {
153
163
  const mockFilePath = path.join(testConfig.MOCK_DIR, `test_${nameToFolder(testName)}`, `mock_${foundMock.id}.json`);
@@ -0,0 +1,280 @@
1
+ (function () {
2
+ // Intercept Fetch API
3
+ const originalFetch = window.fetch;
4
+ const recordedTracks = [];
5
+
6
+ const addTrack = track => {
7
+ track.id = recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1;
8
+ track.time = new Date();
9
+ fetch(window.FTMOCKS_CONFIG.record_events_url, {
10
+ method: 'POST',
11
+ headers: {
12
+ 'Content-Type': 'application/json',
13
+ },
14
+ body: JSON.stringify(track),
15
+ }).then(response => response.json());
16
+ };
17
+
18
+
19
+ window.fetch = async function (url, options = {}) {
20
+ const method = options.method || 'GET';
21
+ const body = options.body;
22
+ const headers = options.headers || {};
23
+ const queryString = url.includes('?') ? url.split('?')[1] : null;
24
+ const response = await originalFetch(url, options);
25
+ const ftMocksURL = new URL(window.FTMOCKS_CONFIG.record_mocks_url);
26
+ const currentURL = new URL(url.startsWith('http') ? url : `http://something/${url}`);
27
+ const clonedResponse = response.clone();
28
+ clonedResponse.text().then((text) => {
29
+ if (ftMocksURL.hostname !== currentURL.hostname) {
30
+ const mockResponse = {
31
+ url: url,
32
+ time: new Date().toString(),
33
+ method: method,
34
+ request: {
35
+ headers: headers,
36
+ queryString: queryString,
37
+ postData: {
38
+ mimeType: headers['Content-Type'] || null,
39
+ text: body
40
+ }
41
+ },
42
+ response: {
43
+ status: response.status,
44
+ headers: Array.from(clonedResponse.headers.entries()),
45
+ content: text
46
+ },
47
+ };
48
+ fetch(window.FTMOCKS_CONFIG.record_mocks_url, {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: JSON.stringify(mockResponse),
54
+ }).then(response => response.json());
55
+ addTrack({
56
+ type: mockResponse.method,
57
+ target: mockResponse.url,
58
+ });
59
+ }
60
+ });
61
+ return response;
62
+ };
63
+
64
+ // Intercept XMLHttpRequest
65
+ const originalXHR = window.XMLHttpRequest;
66
+
67
+ function MockXHR() {
68
+ const xhr = new originalXHR();
69
+ const originalOpen = xhr.open;
70
+ const originalSend = xhr.send;
71
+ const originalSetRequestHeader = xhr.setRequestHeader;
72
+ let requestDetails = {
73
+ headers: {},
74
+ };
75
+
76
+ // Override 'open' method
77
+ xhr.open = function (method, url, async, user, password) {
78
+ requestDetails.method = method;
79
+ requestDetails.url = url;
80
+ requestDetails.async = async;
81
+ requestDetails.user = user;
82
+ requestDetails.password = password;
83
+ requestDetails.queryString = url.includes('?') ? url.split('?')[1] : null;
84
+ originalOpen.apply(xhr, arguments);
85
+ };
86
+
87
+ // Override 'setRequestHeader' to log headers
88
+ xhr.setRequestHeader = function (header, value) {
89
+ requestDetails.headers[header] = value;
90
+ originalSetRequestHeader.apply(xhr, arguments);
91
+ };
92
+
93
+ // Override 'send' method
94
+ xhr.send = function (body) {
95
+ requestDetails.body = body;
96
+ const originalOnReadyStateChange = xhr.onreadystatechange;
97
+ xhr.onreadystatechange = function () {
98
+ if (xhr.readyState === 4) { // Complete
99
+ const ftMocksURL = new URL(window.FTMOCKS_CONFIG.record_mocks_url);
100
+ const currentURL = new URL(requestDetails.url.startsWith('http') ? requestDetails.url : `http://something/${requestDetails.url}`);
101
+ if (ftMocksURL.hostname !== currentURL.hostname) {
102
+ const mockResponse = {
103
+ url: requestDetails.url,
104
+ time: new Date().toString(),
105
+ method: requestDetails.method,
106
+ request: {
107
+ headers: requestDetails.headers,
108
+ queryString: requestDetails.queryString,
109
+ postData: {
110
+ mimeType: requestDetails.headers['Content-Type'] || null,
111
+ text: requestDetails.body
112
+ }
113
+ },
114
+ response: {
115
+ status: xhr.status,
116
+ headers: xhr.getAllResponseHeaders(),
117
+ content: xhr.responseText
118
+ },
119
+ };
120
+ fetch(window.FTMOCKS_CONFIG.record_mocks_url, {
121
+ method: 'POST',
122
+ headers: {
123
+ 'Content-Type': 'application/json',
124
+ },
125
+ body: JSON.stringify(mockResponse),
126
+ }).then(response => response.json());
127
+
128
+ }
129
+ }
130
+ if (originalOnReadyStateChange) originalOnReadyStateChange.apply(xhr, arguments);
131
+ };
132
+ originalSend.apply(xhr, arguments);
133
+ };
134
+
135
+ return xhr;
136
+ }
137
+
138
+ window.XMLHttpRequest = MockXHR;
139
+
140
+
141
+ const generateXPathWithNearestParentId = (element) => {
142
+ let path = '';
143
+ let nearestParentId = null;
144
+
145
+ // Check if the current element's has an ID
146
+ if (element.id) {
147
+ nearestParentId = element.id;
148
+ }
149
+
150
+ while (!nearestParentId && element !== document.body && element) {
151
+ const tagName = element.tagName.toLowerCase();
152
+ let index = 1;
153
+ let sibling = element.previousElementSibling;
154
+
155
+ while (sibling) {
156
+ if (sibling.tagName.toLowerCase() === tagName) {
157
+ index += 1;
158
+ }
159
+ sibling = sibling.previousElementSibling;
160
+ }
161
+
162
+ if (index === 1) {
163
+ path = `/${tagName}${path}`;
164
+ } else {
165
+ path = `/${tagName}[${index}]${path}`;
166
+ }
167
+
168
+ // Check if the current element's parent has an ID
169
+ if (element.parentElement && element.parentElement.id) {
170
+ nearestParentId = element.parentElement.id;
171
+ break; // Stop searching when we find the nearest parent with an ID
172
+ }
173
+
174
+ element = element.parentElement;
175
+ }
176
+
177
+ if (nearestParentId) {
178
+ path = `//*[@id='${nearestParentId}']${path}`;
179
+ return path;
180
+ }
181
+ return null; // No parent with an ID found
182
+ };
183
+
184
+ const handleMouseEvent = (type, limit) => event => {
185
+ const target = generateXPathWithNearestParentId(event.target);
186
+ const track = {
187
+ id: recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1,
188
+ type,
189
+ target,
190
+ time: new Date(),
191
+ };
192
+ if(recordedTracks.length > limit + 1) {
193
+ recordedTracks.shift();
194
+ }
195
+ recordedTracks.push(track);
196
+ addTrack(track);
197
+ };
198
+
199
+ const handleChange = limit => event => {
200
+ const prevCommand =
201
+ recordedTracks && recordedTracks.length ? recordedTracks[recordedTracks.length - 1] : null;
202
+ const target = generateXPathWithNearestParentId(event.target);
203
+ const track = {
204
+ id: recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1,
205
+ type: 'change',
206
+ target,
207
+ value: event.target.value,
208
+ time: new Date(),
209
+ };
210
+ if(recordedTracks.length > limit + 1) {
211
+ recordedTracks.shift();
212
+ }
213
+ if (
214
+ prevCommand &&
215
+ prevCommand.type === 'change' &&
216
+ prevCommand.target === target
217
+ ) {
218
+ recordedTracks.pop();
219
+ }
220
+ recordedTracks.push(track);
221
+ addTrack(track);
222
+ };
223
+
224
+ const handleDocumentLoad = limit => () => {
225
+ let oldHref = document.location.href;
226
+ const body = document.querySelector('body');
227
+ const observer = new MutationObserver(mutations => {
228
+ if (oldHref !== document.location.href) {
229
+ oldHref = document.location.href;
230
+ const track = {
231
+ id: recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1,
232
+ type: 'url',
233
+ value: oldHref,
234
+ time: new Date(),
235
+ };
236
+ if(recordedTracks.length > limit + 1) {
237
+ recordedTracks.shift();
238
+ }
239
+ recordedTracks.push(track);
240
+ addTrack(track);
241
+ }
242
+ });
243
+ observer.observe(body, { childList: true, subtree: true });
244
+ };
245
+
246
+ const clearTracks = () => {
247
+ recordedTracks = [];
248
+ };
249
+
250
+ const getAllTracks = () => {
251
+ return recordedTracks;
252
+ };
253
+
254
+ const initTracks = (initInfo = {events: ['click', 'change', 'url', 'dblclick', 'contextmenu'], limit: 100}) => {
255
+ const {events, limit} = initInfo;
256
+ const mouseEvents = {
257
+ click: handleMouseEvent('click', limit),
258
+ contextmenu: handleMouseEvent('contextmenu', limit),
259
+ dblclick: handleMouseEvent('dblclick', limit),
260
+ mousedown: handleMouseEvent('mousedown', limit),
261
+ mouseenter: handleMouseEvent('mouseenter', limit),
262
+ mouseleave: handleMouseEvent('mouseleave', limit),
263
+ mousemove: handleMouseEvent('mousemove', limit),
264
+ mouseout: handleMouseEvent('mouseout', limit),
265
+ mouseover: handleMouseEvent('mouseover', limit),
266
+ mouseup: handleMouseEvent('mouseup', limit),
267
+ };
268
+ events.forEach(e => {
269
+ if(e === 'url') {
270
+ window.onload = handleDocumentLoad(limit);
271
+ } else if (e === 'change') {
272
+ document.addEventListener('input', handleChange(limit));
273
+ } else {
274
+ document.addEventListener(e, mouseEvents[e]);
275
+ }
276
+ });
277
+ };
278
+ initTracks();
279
+
280
+ })();