@yqg/multiple-click
Version:
Monitor user's multiple click behavior and report
317 lines (276 loc) • 9.77 kB
text/typescript
/**
* @jest-environment jsdom
*/
import {processTrackingConfig} from '../config';
import {trackingHandlerFactory} from '../handler';
import {baseConfig, getSubsets, mockFetchConfig, mockUploadTrackingInfo} from './common';
const getMockEvent = (x: number, y: number, timeStamp: number) => {
document.body.innerHTML =
`
<div id='app'>
<div id='target' style='position: absolute; left: ${x}px; top: ${y}px;'>测试</div>
</div>
`;
const target = document.querySelector('#target') as HTMLElement;
const mockTarget = new Proxy(target, {
get: (origin, name) => {
if (name === 'offsetLeft') {
return x;
}
if (name === 'offsetTop') {
return y;
}
if (name === 'getBoundingClientRect') {
return () => ({x, y});
}
return origin[name];
}
});
return {
timeStamp,
pageX: x,
pageY: y,
target: mockTarget,
} as any as MouseEvent;
};
const square = Math.floor(30 / Math.sqrt(2));
const point1 = [10, 40];
const point2 = [40, 10];
const point3 = [40, 70];
const point4 = [70, 40];
const point5 = [40, 40];
const point6 = [40 - square, 40 - square];
const point7 = [40 + square, 40 + square];
const point8 = [40 - square, 40 + square];
const point9 = [40 + square, 40 - square];
const runWithConfig = async (
customConfig: any,
points: any[],
_timeStamp: number,
getStep: (index: number) => number,
callback?: (index: number) => void,
appId = Date.now().toString()
) => {
const config = {...baseConfig, appId};
mockFetchConfig({
[appId]: JSON.stringify(customConfig),
});
const handlerConfig = await processTrackingConfig(config);
const trackingHandler = trackingHandlerFactory(handlerConfig);
let req: any;
mockUploadTrackingInfo(_req => {
req = _req;
});
let timeStamp = _timeStamp;
points.forEach(([x, y], index) => {
timeStamp += getStep(index);
trackingHandler(getMockEvent(x, y, timeStamp));
callback && callback(index);
});
return req;
};
describe('handler测试', () => {
beforeEach(() => {
mockFetchConfig({});
});
test('interval计算测试', async () => {
// 以下点都在左上角坐标为 (10, 10),边长为30 * 2的正方形的内切圆内。
const points = [point1, point2, point3, point4];
const config = {
disabled: false,
interval: 400,
continuousCount: 4,
range: 30,
};
const timeStamp = Date.now();
let counter = 0;
counter += Number(!!await runWithConfig(config, points, timeStamp, () => 400));
expect(counter).toBe(1);
counter += Number(!!await runWithConfig(config, points, timeStamp, index => index === points.length - 1 ? 401 : 400));
expect(counter).toBe(1);
});
test('continuousCount计算测试', async () => {
// 以下点都在左上角坐标为 (10, 10),边长为30 * 2的正方形的内切圆内。
const points = [point1, point2, point3, point4];
let counter = 0;
let timeStamp = Date.now();
counter += Number(!!await runWithConfig(
{
disabled: false,
interval: 400,
continuousCount: 4,
range: 30,
},
points,
timeStamp,
() => 400,
index => {
if (index !== points.length - 1) {
expect(counter).toBe(0);
}
}
));
expect(counter).toBe(1);
// 以下点都在左上角坐标为 (10, 10),边长为30 * 2的正方形的内切圆内。
const points2 = [point1, point2, point3, point4, point5, point6, point7, point8, point9];
timeStamp += 1000;
counter += Number(!!await runWithConfig(
{
disabled: false,
interval: 400,
continuousCount: 9,
range: 30,
},
points2,
timeStamp,
() => 400,
index => {
if (index !== points.length - 1) {
expect(counter).toBe(1);
}
}
));
expect(counter).toBe(2);
});
test('range计算测试', async () => {
// 以下点都在左上角坐标为 (10, 10),边长为30 * 2的正方形的内切圆内。
const points = [point1, point2, point3, point4, point5, point6, point7, point8, point9];
// 获取这10个点的长度为4的组合
const subsets: Array<Array<[number, number]>> = getSubsets(points, 4);
const config = {
disabled: false,
interval: 600,
continuousCount: 4,
range: 30,
};
const timeStamp = Date.now();
let counter = 0;
for (const subset of subsets) {
counter += Number(!!await runWithConfig(config, subset, timeStamp, () => 10));
}
expect(counter).toBe(subsets.length);
const dis = 40 + Math.ceil(90 / Math.sqrt(2));
const invalidSubset = getSubsets(points, 3).flatMap(subset => [...subset, [dis, dis]]);
counter += Number(!!await runWithConfig(config, invalidSubset, timeStamp, () => 10));
// 上报次数不变,说明invalidSubsets不触发上报
expect(counter).toBe(subsets.length);
});
test('excludeRules测试', async () => {
// 以下点都在左上角坐标为 (10, 10),边长为30 * 2的正方形的内切圆内。
const points = [point1, point2, point3, point4];
const timeStamp = Date.now();
const commonConfig = {
disabled: false,
interval: 600,
continuousCount: 4,
range: 30,
};
let counter = 0;
counter += Number(!!await runWithConfig({
...commonConfig,
excludeRules: [{
selector: '#app',
shallow: true,
}],
}, points, timeStamp, () => 10));
expect(counter).toBe(1);
counter += Number(!!await runWithConfig({
...commonConfig,
excludeRules: [{
selector: '#app',
shallow: false,
}],
}, points, timeStamp, () => 10));
expect(counter).toBe(1);
counter += Number(!!await runWithConfig({
...commonConfig,
excludeRules: [{
selector: '#target',
shallow: true,
}],
}, points, timeStamp, () => 10));
expect(counter).toBe(1);
counter += Number(!!await runWithConfig({
...commonConfig,
excludeRules: [{
selector: '#target',
shallow: false,
}],
}, points, timeStamp, () => 10));
expect(counter).toBe(1);
counter += Number(!!await runWithConfig({
...commonConfig,
excludeRules: [{
selector: '#target',
shallow: false,
page: ['/test/aaa']
}],
}, points, timeStamp, () => 10));
expect(counter).toBe(2);
// eslint-disable-next-line no-global-assign
window = Object.create(window);
Object.defineProperty(window, 'location', {
value: {
href: 'http://localhost/test/aaa'
},
writable: true
});
counter += Number(!!await runWithConfig({
...commonConfig,
excludeRules: [{
selector: '#target',
shallow: false,
page: ['/test/aaa']
}],
}, points, timeStamp, () => 10));
// 还原location
Object.defineProperty(window, 'location', {
value: {
href: 'http://localhost/'
},
writable: true
});
expect(counter).toBe(2);
});
test('上报信息测试', async () => {
// 以下点都在左上角坐标为 (10, 10),边长为30 * 2的正方形的内切圆内。
const points = [point1, point2, point3, point4];
const appId = Date.now().toString();
const config = {
disabled: false,
interval: 400,
continuousCount: 4,
range: 30,
};
const timeStamp = Date.now();
let counter = 0;
const req = await runWithConfig(config, points, timeStamp, () => 400, undefined, appId);
counter += Number(!!req);
expect(counter).toBe(1);
const {headers, body} = req;
expect(headers).toMatchObject({
'Country': 'CN',
'YQG-PLATFORM-SDK-TYPE': appId,
});
expect(JSON.parse(body)).toMatchObject({
level: 'INFO',
logs: [
{
appId,
appVersion: baseConfig.appVersion,
measurement: 'app_metrics_for_multiple_click',
metricsType: 'metricsType1',
parameter: {
url: 'http://localhost/',
outerHTML: '<div id="target" style="position: absolute; left: 70px; top: 40px;">测试</div>',
selector: 'body > div#app > div#target',
offsetTop: 40,
offsetLeft: 70,
clientTop: 40,
clientLeft: 70,
},
}
],
});
});
});