UNPKG

@yqg/multiple-click

Version:

Monitor user's multiple click behavior and report

317 lines (276 loc) 9.77 kB
/** * @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, }, } ], }); }); });