@webex/cc-ui-logging 1.28.0-ccwidgets.108

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/dist/index.js ADDED
@@ -0,0 +1,98 @@
1
+ /*
2
+ * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
3
+ * This devtool is neither made for production nor for readable output files.
4
+ * It uses "eval()" calls to create a separate source file in the browser devtools.
5
+ * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
6
+ * or disable the default devtool with "devtool: false".
7
+ * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
8
+ */
9
+ /******/ (() => { // webpackBootstrap
10
+ /******/ "use strict";
11
+ /******/ var __webpack_modules__ = ({
12
+
13
+ /***/ "./src/index.ts":
14
+ /*!**********************!*\
15
+ !*** ./src/index.ts ***!
16
+ \**********************/
17
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
18
+
19
+ eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.withMetrics = void 0;\nconst withMetrics_1 = __importDefault(__webpack_require__(/*! ./withMetrics */ \"./src/withMetrics.tsx\"));\nexports.withMetrics = withMetrics_1.default;\n\n\n//# sourceURL=webpack://@webex/cc-ui-logging/./src/index.ts?");
20
+
21
+ /***/ }),
22
+
23
+ /***/ "./src/metricsLogger.ts":
24
+ /*!******************************!*\
25
+ !*** ./src/metricsLogger.ts ***!
26
+ \******************************/
27
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
28
+
29
+ eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.logMetrics = void 0;\nexports.havePropsChanged = havePropsChanged;\nconst cc_store_1 = __importDefault(__webpack_require__(/*! @webex/cc-store */ \"@webex/cc-store\"));\n/**\n * Logs UI metrics for contact center widgets.\n *\n * This function logs widget lifecycle events and errors to help monitor\n * widget performance and user interactions. If no logger is available,\n * it will emit a warning and skip logging.\n *\n * @param metric - The metrics data to be logged\n * @param metric.widgetName - Name of the widget generating the metric\n * @param metric.event - Type of event being logged\n * @param metric.props - Optional properties associated with the widget\n * @param metric.timestamp - Unix timestamp when the event occurred\n * @param metric.additionalContext - Optional additional context data\n *\n * @example\n * ```typescript\n * logMetrics({\n * widgetName: 'CallControl',\n * event: 'WIDGET_MOUNTED',\n * props: { callId: '123' },\n * timestamp: Date.now(),\n * additionalContext: { userId: 'user123' }\n * });\n * ```\n */\nconst logMetrics = (metric) => {\n if (!cc_store_1.default.logger) {\n console.warn('CC-Widgets: UI Metrics: No logger found');\n return;\n }\n cc_store_1.default.logger.log(`CC-Widgets: UI Metrics: ${JSON.stringify(metric, null, 2)}`, {\n module: 'metricsLogger.tsx',\n method: 'logMetrics',\n });\n};\nexports.logMetrics = logMetrics;\n/**\n * Determines if props have changed between two objects using shallow comparison.\n *\n * This function performs a shallow comparison between two objects to detect changes.\n * It compares object keys and primitive values, but does not recursively compare\n * nested objects. This is useful for determining when to log metrics based on prop changes.\n *\n * @param prev - The previous props object\n * @param next - The next props object to compare against\n * @returns `true` if the props have changed, `false` otherwise\n *\n * @example\n * ```typescript\n * const oldProps = { name: 'John', age: 30 };\n * const newProps = { name: 'John', age: 31 };\n *\n * if (havePropsChanged(oldProps, newProps)) {\n * // Props have changed, log metrics\n * logMetrics({\n * widgetName: 'UserProfile',\n * event: 'WIDGET_MOUNTED',\n * props: newProps,\n * timestamp: Date.now()\n * });\n * } * ```\n *\n * @remarks\n * The function is important as we dont sanitize our props right now.\n * Once we start sanitizing we can do a deep comparison. This is used to only re-render\n * the HOC if the props have changed.\n */\nfunction havePropsChanged(prev, next) {\n if (prev === next)\n return false;\n // Do shallow comparison\n if (typeof prev !== typeof next)\n return true;\n if (!prev || !next)\n return prev !== next;\n const prevKeys = Object.keys(prev);\n const nextKeys = Object.keys(next);\n if (prevKeys.length !== nextKeys.length)\n return true;\n // Check if any primitive values changed\n for (const key of prevKeys) {\n const prevVal = prev[key];\n const nextVal = next[key];\n if (prevVal === nextVal)\n continue;\n if (typeof prevVal !== 'object' || prevVal === null)\n return true;\n if (typeof nextVal !== 'object' || nextVal === null)\n return true;\n }\n // All shallow comparisons passed, consider props unchanged\n return false;\n}\n\n\n//# sourceURL=webpack://@webex/cc-ui-logging/./src/metricsLogger.ts?");
30
+
31
+ /***/ }),
32
+
33
+ /***/ "./src/withMetrics.tsx":
34
+ /*!*****************************!*\
35
+ !*** ./src/withMetrics.tsx ***!
36
+ \*****************************/
37
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
38
+
39
+ eval("\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n}));\nvar __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n});\nvar __importStar = (this && this.__importStar) || (function () {\n var ownKeys = function(o) {\n ownKeys = Object.getOwnPropertyNames || function (o) {\n var ar = [];\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\n return ar;\n };\n return ownKeys(o);\n };\n return function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\n __setModuleDefault(result, mod);\n return result;\n };\n})();\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports[\"default\"] = withMetrics;\nconst react_1 = __importStar(__webpack_require__(/*! react */ \"react\"));\nconst metricsLogger_1 = __webpack_require__(/*! ./metricsLogger */ \"./src/metricsLogger.ts\");\nfunction withMetrics(Component, widgetName) {\n return react_1.default.memo((props) => {\n (0, react_1.useEffect)(() => {\n (0, metricsLogger_1.logMetrics)({\n widgetName,\n event: 'WIDGET_MOUNTED',\n timestamp: Date.now(),\n });\n return () => {\n (0, metricsLogger_1.logMetrics)({\n widgetName,\n event: 'WIDGET_UNMOUNTED',\n timestamp: Date.now(),\n });\n };\n }, []);\n // TODO: https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6890 PROPS_UPDATED event\n return react_1.default.createElement(Component, Object.assign({}, props));\n }, (prevProps, nextProps) => !(0, metricsLogger_1.havePropsChanged)(prevProps, nextProps));\n}\n\n\n//# sourceURL=webpack://@webex/cc-ui-logging/./src/withMetrics.tsx?");
40
+
41
+ /***/ }),
42
+
43
+ /***/ "@webex/cc-store":
44
+ /*!**********************************!*\
45
+ !*** external "@webex/cc-store" ***!
46
+ \**********************************/
47
+ /***/ ((module) => {
48
+
49
+ module.exports = require("@webex/cc-store");
50
+
51
+ /***/ }),
52
+
53
+ /***/ "react":
54
+ /*!************************!*\
55
+ !*** external "react" ***!
56
+ \************************/
57
+ /***/ ((module) => {
58
+
59
+ module.exports = require("react");
60
+
61
+ /***/ })
62
+
63
+ /******/ });
64
+ /************************************************************************/
65
+ /******/ // The module cache
66
+ /******/ var __webpack_module_cache__ = {};
67
+ /******/
68
+ /******/ // The require function
69
+ /******/ function __webpack_require__(moduleId) {
70
+ /******/ // Check if module is in cache
71
+ /******/ var cachedModule = __webpack_module_cache__[moduleId];
72
+ /******/ if (cachedModule !== undefined) {
73
+ /******/ return cachedModule.exports;
74
+ /******/ }
75
+ /******/ // Create a new module (and put it into the cache)
76
+ /******/ var module = __webpack_module_cache__[moduleId] = {
77
+ /******/ // no module.id needed
78
+ /******/ // no module.loaded needed
79
+ /******/ exports: {}
80
+ /******/ };
81
+ /******/
82
+ /******/ // Execute the module function
83
+ /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
84
+ /******/
85
+ /******/ // Return the exports of the module
86
+ /******/ return module.exports;
87
+ /******/ }
88
+ /******/
89
+ /************************************************************************/
90
+ /******/
91
+ /******/ // startup
92
+ /******/ // Load entry module and return exports
93
+ /******/ // This entry module is referenced by other modules so it can't be inlined
94
+ /******/ var __webpack_exports__ = __webpack_require__("./src/index.ts");
95
+ /******/ module.exports = __webpack_exports__;
96
+ /******/
97
+ /******/ })()
98
+ ;
@@ -0,0 +1,4 @@
1
+ import withMetrics from './withMetrics';
2
+ import { WidgetMetrics } from './metricsLogger';
3
+ export { withMetrics };
4
+ export type { WidgetMetrics };
@@ -0,0 +1,65 @@
1
+ export type WidgetMetrics = {
2
+ widgetName: string;
3
+ event: 'WIDGET_MOUNTED' | 'ERROR' | 'WIDGET_UNMOUNTED' | 'PROPS_UPDATED';
4
+ props?: Record<string, any>;
5
+ timestamp: number;
6
+ additionalContext?: Record<string, any>;
7
+ };
8
+ /**
9
+ * Logs UI metrics for contact center widgets.
10
+ *
11
+ * This function logs widget lifecycle events and errors to help monitor
12
+ * widget performance and user interactions. If no logger is available,
13
+ * it will emit a warning and skip logging.
14
+ *
15
+ * @param metric - The metrics data to be logged
16
+ * @param metric.widgetName - Name of the widget generating the metric
17
+ * @param metric.event - Type of event being logged
18
+ * @param metric.props - Optional properties associated with the widget
19
+ * @param metric.timestamp - Unix timestamp when the event occurred
20
+ * @param metric.additionalContext - Optional additional context data
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * logMetrics({
25
+ * widgetName: 'CallControl',
26
+ * event: 'WIDGET_MOUNTED',
27
+ * props: { callId: '123' },
28
+ * timestamp: Date.now(),
29
+ * additionalContext: { userId: 'user123' }
30
+ * });
31
+ * ```
32
+ */
33
+ export declare const logMetrics: (metric: WidgetMetrics) => void;
34
+ /**
35
+ * Determines if props have changed between two objects using shallow comparison.
36
+ *
37
+ * This function performs a shallow comparison between two objects to detect changes.
38
+ * It compares object keys and primitive values, but does not recursively compare
39
+ * nested objects. This is useful for determining when to log metrics based on prop changes.
40
+ *
41
+ * @param prev - The previous props object
42
+ * @param next - The next props object to compare against
43
+ * @returns `true` if the props have changed, `false` otherwise
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const oldProps = { name: 'John', age: 30 };
48
+ * const newProps = { name: 'John', age: 31 };
49
+ *
50
+ * if (havePropsChanged(oldProps, newProps)) {
51
+ * // Props have changed, log metrics
52
+ * logMetrics({
53
+ * widgetName: 'UserProfile',
54
+ * event: 'WIDGET_MOUNTED',
55
+ * props: newProps,
56
+ * timestamp: Date.now()
57
+ * });
58
+ * } * ```
59
+ *
60
+ * @remarks
61
+ * The function is important as we dont sanitize our props right now.
62
+ * Once we start sanitizing we can do a deep comparison. This is used to only re-render
63
+ * the HOC if the props have changed.
64
+ */
65
+ export declare function havePropsChanged(prev: any, next: any): boolean;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export default function withMetrics<P extends object>(Component: any, widgetName: string): React.MemoExoticComponent<(props: P) => React.JSX.Element>;
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ const jestConfig = require('../../../jest.config.js');
2
+
3
+ jestConfig.rootDir = '../../../';
4
+ jestConfig.testMatch = ['**/ui-logging/tests/**/*.ts', '**/ui-logging/tests/**/*.tsx'];
5
+
6
+ module.exports = jestConfig;
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@webex/cc-ui-logging",
3
+ "version": "1.28.0-ccwidgets.108",
4
+ "description": "UI metrics tracking for Webex widgets",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "scripts": {
11
+ "build:src": "webpack --mode=development",
12
+ "clean": "rm -rf dist",
13
+ "clean:dist": "rm -rf dist",
14
+ "test:unit": "jest --coverage",
15
+ "test:styles": "echo 'No styles to test'"
16
+ },
17
+ "dependencies": {
18
+ "@webex/cc-store": "1.28.0-ccwidgets.108"
19
+ },
20
+ "devDependencies": {
21
+ "@testing-library/dom": "10.4.0",
22
+ "@testing-library/jest-dom": "6.6.2",
23
+ "@testing-library/react": "16.0.1",
24
+ "@types/jest": "29.5.14",
25
+ "@types/react-test-renderer": "18",
26
+ "@webex/test-fixtures": "0.0.0",
27
+ "babel-jest": "29.7.0",
28
+ "jest": "29.7.0",
29
+ "jest-environment-jsdom": "29.7.0",
30
+ "typescript": "^5.6.3",
31
+ "uuid": "^9.0.0",
32
+ "webpack": "^5.96.1",
33
+ "webpack-cli": "^5.1.4"
34
+ },
35
+ "peerDependencies": {
36
+ "react": ">=18.3.1",
37
+ "react-dom": ">=18.3.1"
38
+ }
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import withMetrics from './withMetrics';
2
+ import {WidgetMetrics} from './metricsLogger';
3
+
4
+ export {withMetrics};
5
+ export type {WidgetMetrics};
@@ -0,0 +1,102 @@
1
+ import store from '@webex/cc-store';
2
+
3
+ export type WidgetMetrics = {
4
+ widgetName: string;
5
+ event: 'WIDGET_MOUNTED' | 'ERROR' | 'WIDGET_UNMOUNTED' | 'PROPS_UPDATED';
6
+ props?: Record<string, any>;
7
+ timestamp: number;
8
+ additionalContext?: Record<string, any>;
9
+ };
10
+
11
+ /**
12
+ * Logs UI metrics for contact center widgets.
13
+ *
14
+ * This function logs widget lifecycle events and errors to help monitor
15
+ * widget performance and user interactions. If no logger is available,
16
+ * it will emit a warning and skip logging.
17
+ *
18
+ * @param metric - The metrics data to be logged
19
+ * @param metric.widgetName - Name of the widget generating the metric
20
+ * @param metric.event - Type of event being logged
21
+ * @param metric.props - Optional properties associated with the widget
22
+ * @param metric.timestamp - Unix timestamp when the event occurred
23
+ * @param metric.additionalContext - Optional additional context data
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * logMetrics({
28
+ * widgetName: 'CallControl',
29
+ * event: 'WIDGET_MOUNTED',
30
+ * props: { callId: '123' },
31
+ * timestamp: Date.now(),
32
+ * additionalContext: { userId: 'user123' }
33
+ * });
34
+ * ```
35
+ */
36
+ export const logMetrics = (metric: WidgetMetrics) => {
37
+ if (!store.logger) {
38
+ console.warn('CC-Widgets: UI Metrics: No logger found');
39
+ return;
40
+ }
41
+ store.logger.log(`CC-Widgets: UI Metrics: ${JSON.stringify(metric, null, 2)}`, {
42
+ module: 'metricsLogger.tsx',
43
+ method: 'logMetrics',
44
+ });
45
+ };
46
+
47
+ /**
48
+ * Determines if props have changed between two objects using shallow comparison.
49
+ *
50
+ * This function performs a shallow comparison between two objects to detect changes.
51
+ * It compares object keys and primitive values, but does not recursively compare
52
+ * nested objects. This is useful for determining when to log metrics based on prop changes.
53
+ *
54
+ * @param prev - The previous props object
55
+ * @param next - The next props object to compare against
56
+ * @returns `true` if the props have changed, `false` otherwise
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const oldProps = { name: 'John', age: 30 };
61
+ * const newProps = { name: 'John', age: 31 };
62
+ *
63
+ * if (havePropsChanged(oldProps, newProps)) {
64
+ * // Props have changed, log metrics
65
+ * logMetrics({
66
+ * widgetName: 'UserProfile',
67
+ * event: 'WIDGET_MOUNTED',
68
+ * props: newProps,
69
+ * timestamp: Date.now()
70
+ * });
71
+ * } * ```
72
+ *
73
+ * @remarks
74
+ * The function is important as we dont sanitize our props right now.
75
+ * Once we start sanitizing we can do a deep comparison. This is used to only re-render
76
+ * the HOC if the props have changed.
77
+ */
78
+ export function havePropsChanged(prev: any, next: any): boolean {
79
+ if (prev === next) return false;
80
+
81
+ // Do shallow comparison
82
+ if (typeof prev !== typeof next) return true;
83
+ if (!prev || !next) return prev !== next;
84
+
85
+ const prevKeys = Object.keys(prev);
86
+ const nextKeys = Object.keys(next);
87
+
88
+ if (prevKeys.length !== nextKeys.length) return true;
89
+
90
+ // Check if any primitive values changed
91
+ for (const key of prevKeys) {
92
+ const prevVal = prev[key];
93
+ const nextVal = next[key];
94
+
95
+ if (prevVal === nextVal) continue;
96
+ if (typeof prevVal !== 'object' || prevVal === null) return true;
97
+ if (typeof nextVal !== 'object' || nextVal === null) return true;
98
+ }
99
+
100
+ // All shallow comparisons passed, consider props unchanged
101
+ return false;
102
+ }
@@ -0,0 +1,29 @@
1
+ import React, {useEffect, useRef} from 'react';
2
+ import {havePropsChanged, logMetrics} from './metricsLogger';
3
+
4
+ export default function withMetrics<P extends object>(Component: any, widgetName: string) {
5
+ return React.memo(
6
+ (props: P) => {
7
+ useEffect(() => {
8
+ logMetrics({
9
+ widgetName,
10
+ event: 'WIDGET_MOUNTED',
11
+ timestamp: Date.now(),
12
+ });
13
+
14
+ return () => {
15
+ logMetrics({
16
+ widgetName,
17
+ event: 'WIDGET_UNMOUNTED',
18
+ timestamp: Date.now(),
19
+ });
20
+ };
21
+ }, []);
22
+
23
+ // TODO: https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6890 PROPS_UPDATED event
24
+
25
+ return <Component {...props} />;
26
+ },
27
+ (prevProps, nextProps) => !havePropsChanged(prevProps, nextProps)
28
+ );
29
+ }
@@ -0,0 +1,86 @@
1
+ import store from '@webex/cc-store';
2
+ import {logMetrics, havePropsChanged, WidgetMetrics} from '../src/metricsLogger';
3
+
4
+ describe('metricsLogger', () => {
5
+ store.store.logger = {
6
+ log: jest.fn(),
7
+ info: jest.fn(),
8
+ warn: jest.fn(),
9
+ error: jest.fn(),
10
+ trace: jest.fn(),
11
+ };
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ describe('logMetrics', () => {
17
+ it('should log metrics when logger is available', () => {
18
+ const metric: WidgetMetrics = {
19
+ widgetName: 'TestWidget',
20
+ event: 'WIDGET_MOUNTED',
21
+ timestamp: 1234567890,
22
+ props: {test: 'prop'},
23
+ additionalContext: {context: 'test'},
24
+ };
25
+
26
+ logMetrics(metric);
27
+
28
+ expect(store.logger.log).toHaveBeenCalledWith(`CC-Widgets: UI Metrics: ${JSON.stringify(metric, null, 2)}`, {
29
+ module: 'metricsLogger.tsx',
30
+ method: 'logMetrics',
31
+ });
32
+ });
33
+
34
+ it('should handle case when logger is not available', () => {
35
+ const consoleSpy = jest.spyOn(console, 'warn');
36
+ store.store.logger = undefined;
37
+
38
+ const metric: WidgetMetrics = {
39
+ widgetName: 'TestWidget',
40
+ event: 'WIDGET_MOUNTED',
41
+ timestamp: 1234567890,
42
+ };
43
+
44
+ logMetrics(metric);
45
+
46
+ expect(consoleSpy).toHaveBeenCalledWith('CC-Widgets: UI Metrics: No logger found');
47
+ consoleSpy.mockRestore();
48
+ });
49
+ });
50
+
51
+ describe('havePropsChanged', () => {
52
+ it('should return false for identical primitives', () => {
53
+ expect(havePropsChanged(1, 1)).toBe(false);
54
+ expect(havePropsChanged('test', 'test')).toBe(false);
55
+ expect(havePropsChanged(true, true)).toBe(false);
56
+ });
57
+
58
+ it('should return true for different primitives', () => {
59
+ expect(havePropsChanged('test', 'test2')).toBe(true);
60
+ expect(havePropsChanged(true, false)).toBe(true);
61
+ });
62
+
63
+ it('should return true for different types', () => {
64
+ expect(havePropsChanged(1, '1')).toBe(true);
65
+ expect(havePropsChanged(null, undefined)).toBe(true);
66
+ });
67
+
68
+ it('should return true when object keys differ', () => {
69
+ const obj1 = {a: 1, b: 2};
70
+ const obj2 = {a: 1};
71
+ expect(havePropsChanged(obj1, obj2)).toBe(true);
72
+ });
73
+
74
+ it('should return false when nested values differ', () => {
75
+ const obj1 = {a: {b: 1}};
76
+ const obj2 = {a: {b: 2}};
77
+ expect(havePropsChanged(obj1, obj2)).toBe(false);
78
+ });
79
+
80
+ it('should handle null and undefined', () => {
81
+ expect(havePropsChanged(null, null)).toBe(false);
82
+ expect(havePropsChanged(undefined, undefined)).toBe(false);
83
+ expect(havePropsChanged(null, undefined)).toBe(true);
84
+ });
85
+ });
86
+ });
@@ -0,0 +1,105 @@
1
+ import React from 'react';
2
+ import {render} from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import withMetrics from '../src/withMetrics';
5
+ import store from '@webex/cc-store';
6
+ import * as metricsLogger from '../src/metricsLogger';
7
+
8
+ interface TestComponentProps {
9
+ name?: string;
10
+ [key: string]: any;
11
+ }
12
+
13
+ describe('withMetrics HOC', () => {
14
+ store.store.logger = {
15
+ log: jest.fn(),
16
+ info: jest.fn(),
17
+ warn: jest.fn(),
18
+ error: jest.fn(),
19
+ trace: jest.fn(),
20
+ };
21
+ const logMetricsSpy = jest.spyOn(metricsLogger, 'logMetrics');
22
+
23
+ const TestComponent: React.FC<TestComponentProps> = (props) => <div>Test Component {props.name}</div>;
24
+ const WrappedComponent = withMetrics<TestComponentProps>(TestComponent, 'TestWidget');
25
+
26
+ beforeEach(() => {
27
+ jest.clearAllMocks();
28
+ jest.useFakeTimers();
29
+ });
30
+
31
+ afterEach(() => {
32
+ jest.useRealTimers();
33
+ });
34
+
35
+ it('should log metrics on mount', () => {
36
+ const mockTime = 1234567890;
37
+ jest.setSystemTime(mockTime);
38
+
39
+ render(<WrappedComponent name="test" />);
40
+
41
+ expect(logMetricsSpy).toHaveBeenCalledWith({
42
+ widgetName: 'TestWidget',
43
+ event: 'WIDGET_MOUNTED',
44
+ timestamp: mockTime,
45
+ });
46
+ });
47
+
48
+ it('should log metrics on unmount', () => {
49
+ const mockTime = 1234567890;
50
+ jest.setSystemTime(mockTime);
51
+
52
+ const {unmount} = render(<WrappedComponent name="test" />);
53
+
54
+ // Clear the mount log
55
+ logMetricsSpy.mockClear();
56
+
57
+ // Unmount the component
58
+ unmount();
59
+
60
+ expect(logMetricsSpy).toHaveBeenCalledWith({
61
+ widgetName: 'TestWidget',
62
+ event: 'WIDGET_UNMOUNTED',
63
+ timestamp: mockTime,
64
+ });
65
+ });
66
+
67
+ it('should pass through props to wrapped component', () => {
68
+ const {getByText} = render(<WrappedComponent name="test-name" />);
69
+ expect(getByText('Test Component test-name')).toBeInTheDocument();
70
+ });
71
+
72
+ it('should not re-render when props have not changed', () => {
73
+ const renderSpy = jest.fn();
74
+ const SpyComponent: React.FC<TestComponentProps> = (props) => {
75
+ renderSpy();
76
+ return <div>Test Component {props.name}</div>;
77
+ };
78
+
79
+ const WrappedSpy = withMetrics<TestComponentProps>(SpyComponent, 'TestWidget');
80
+
81
+ const {rerender} = render(<WrappedSpy name="test" />);
82
+ expect(renderSpy).toHaveBeenCalledTimes(1);
83
+
84
+ // Re-render with same props
85
+ rerender(<WrappedSpy name="test" />);
86
+ expect(renderSpy).toHaveBeenCalledTimes(1);
87
+ });
88
+
89
+ it('should re-render when props have changed', () => {
90
+ const renderSpy = jest.fn();
91
+ const SpyComponent: React.FC<TestComponentProps> = (props) => {
92
+ renderSpy();
93
+ return <div>Test Component {props.name}</div>;
94
+ };
95
+
96
+ const WrappedSpy = withMetrics<TestComponentProps>(SpyComponent, 'TestWidget');
97
+
98
+ const {rerender} = render(<WrappedSpy name="test" />);
99
+ expect(renderSpy).toHaveBeenCalledTimes(1);
100
+
101
+ // Re-render with different props
102
+ rerender(<WrappedSpy name="different" />);
103
+ expect(renderSpy).toHaveBeenCalledTimes(2);
104
+ });
105
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "include": [
4
+ "./src"
5
+ ],
6
+ "compilerOptions": {
7
+ "outDir": "./dist",
8
+ "declaration": true,
9
+ "declarationDir": "./dist/types"
10
+ },
11
+ }
@@ -0,0 +1,9 @@
1
+ // This config is to do type checking in our files while running tests.
2
+ {
3
+ "extends": "./tsconfig.json",
4
+ "include": ["./tests"],
5
+ "exclude": ["node_modules"],
6
+ "compilerOptions": {
7
+ "noEmit": true // Don't output any files
8
+ }
9
+ }
@@ -0,0 +1,17 @@
1
+ const {merge} = require('webpack-merge');
2
+ const path = require('path');
3
+
4
+ const baseConfig = require('../../../webpack.config');
5
+
6
+ module.exports = merge(baseConfig, {
7
+ output: {
8
+ path: path.resolve(__dirname, 'dist'),
9
+ filename: 'index.js', // Set the output filename to index.js
10
+ libraryTarget: 'commonjs2',
11
+ },
12
+ externals: {
13
+ react: 'react',
14
+ 'react-dom': 'react-dom',
15
+ '@webex/cc-store': '@webex/cc-store',
16
+ },
17
+ });