Skip to content

Commit dc33554

Browse files
authored
Merge pull request #28 from gabrielbarth/sdx
feat: implements event label formatting string options; sets test and lint workflow
2 parents 043494d + fcd7eab commit dc33554

File tree

10 files changed

+409
-24
lines changed

10 files changed

+409
-24
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Test and Lint
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- "src/**"
9+
pull_request:
10+
paths:
11+
- "src/**"
12+
workflow_dispatch:
13+
14+
jobs:
15+
test-and-lint:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- name: Setup Node.js
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: 18
26+
27+
- name: Install dependencies
28+
run: npm install
29+
30+
- name: Run lint
31+
run: npm run lint
32+
33+
- name: Run unit tests
34+
run: npm test -- --coverage

eslint.config.mjs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// @ts-check
2+
3+
import eslint from "@eslint/js";
4+
import tseslint from "typescript-eslint";
5+
6+
export default tseslint.config(
7+
eslint.configs.recommended,
8+
tseslint.configs.recommended,
9+
{
10+
ignores: ["**/dist/**"],
11+
rules: {
12+
"no-console": "off",
13+
eqeqeq: ["error", "always"],
14+
semi: ["error", "always"],
15+
},
16+
}
17+
);

jest.config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export default {
2+
preset: "ts-jest",
3+
testEnvironment: "jsdom",
4+
testMatch: ["**/__tests__/**/*.ts?(x)", "**/?(*.)+(test).ts?(x)"],
5+
transform: {
6+
"^.+\\.(js|ts)$": "ts-jest",
7+
},
8+
transformIgnorePatterns: [
9+
"/node_modules/(?![@autofiy/autofiyable|@autofiy/property]).+\\.js$",
10+
"/node_modules/(?![@autofiy/autofiyable|@autofiy/property]).+\\.ts$",
11+
"/node_modules/(?![@autofiy/autofiyable|@autofiy/property]).+\\.tsx$",
12+
],
13+
};

package.json

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
{
22
"name": "analytics-event-factory",
33
"description": "Analytics Event Factory",
4-
"version": "1.1.0",
4+
"version": "2.0.0",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"files": [
88
"dist"
99
],
10+
"type": "module",
1011
"scripts": {
11-
"test": "echo \"Error: no test specified\" && exit 1",
12-
"build": "tsc"
12+
"test": "jest",
13+
"test:coverage": "jest --coverage",
14+
"build": "tsc",
15+
"lint": "eslint . --ext .ts,.js --ignore-pattern dist/"
1316
},
1417
"keywords": [
1518
"event",
@@ -30,8 +33,18 @@
3033
"@babel/cli": "^7.25.6",
3134
"@babel/core": "^7.25.2",
3235
"@babel/preset-env": "^7.25.4",
36+
"@eslint/js": "^9.25.1",
37+
"@types/jest": "^29.5.14",
3338
"@types/node": "^22.5.5",
34-
"typescript": "^5.6.2"
39+
"@typescript-eslint/eslint-plugin": "^8.31.0",
40+
"@typescript-eslint/parser": "^8.31.0",
41+
"eslint": "^9.25.1",
42+
"globals": "^16.0.0",
43+
"jest": "^29.7.0",
44+
"jest-environment-jsdom": "^29.7.0",
45+
"ts-jest": "^29.3.2",
46+
"typescript": "^5.8.3",
47+
"typescript-eslint": "^8.31.0"
3548
},
3649
"repository": {
3750
"type": "git",

src/examples/basic.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ function logEvent(eventMetadata: unknown): unknown {
1111
}
1212

1313
// usage example
14-
const eventHandler = eventCreator(logEvent);
14+
const eventHandler = eventCreator({
15+
callback: logEvent,
16+
});
1517

1618
eventHandler.button.click({
1719
id: "btn-1",
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { eventCreator } from "./index";
2+
import { EventHandler } from "../../types/event/EventHandlers";
3+
import { EventCreatorParams } from "../../types/event/EventCreatorParams";
4+
5+
describe("eventCreator", () => {
6+
const mockCallback = jest.fn();
7+
8+
const defaultParams: EventCreatorParams = {
9+
callback: mockCallback,
10+
options: {
11+
page: {
12+
currentPage: "Home",
13+
showOnLabel: true,
14+
showOnMetadata: true,
15+
},
16+
action: {
17+
possibleActions: ["click", "scroll"],
18+
showOnMetadata: true,
19+
},
20+
element: {
21+
possibleElements: ["button", "link"],
22+
showOnMetadata: true,
23+
showElementIdOnLabel: true,
24+
},
25+
labelOptions: {
26+
stringCase: "capitalize",
27+
stringFormat: "snake_case",
28+
},
29+
},
30+
};
31+
32+
beforeEach(() => {
33+
jest.clearAllMocks();
34+
});
35+
36+
it("should create handlers for all elements and actions", () => {
37+
const handlers: EventHandler = eventCreator(defaultParams);
38+
39+
expect(handlers).toHaveProperty("button");
40+
expect(handlers).toHaveProperty("link");
41+
expect(handlers.button).toHaveProperty("click");
42+
expect(handlers.button).toHaveProperty("scroll");
43+
expect(handlers.link).toHaveProperty("click");
44+
expect(handlers.link).toHaveProperty("scroll");
45+
});
46+
47+
it("should call the callback with the correct label and metadata", () => {
48+
const handlers: EventHandler = eventCreator(defaultParams);
49+
50+
const eventMetadata = { elementId: "123", customData: "test" };
51+
handlers.button.click(eventMetadata);
52+
53+
expect(mockCallback).toHaveBeenCalledWith({
54+
label: "Home_Button_Click_123",
55+
metadata: {
56+
...eventMetadata,
57+
page: "Home",
58+
element: "button",
59+
action: "click",
60+
},
61+
});
62+
});
63+
64+
it("should exclude page from metadata if showOnMetadata is false", () => {
65+
const params = {
66+
...defaultParams,
67+
options: {
68+
...defaultParams.options,
69+
page: {
70+
...defaultParams.options?.page,
71+
showOnMetadata: false,
72+
},
73+
},
74+
};
75+
76+
const handlers: EventHandler = eventCreator(params);
77+
78+
const eventMetadata = { elementId: "789" };
79+
handlers.button.click(eventMetadata);
80+
81+
expect(mockCallback).toHaveBeenCalledWith({
82+
label: "Home_Button_Click_789",
83+
metadata: {
84+
...eventMetadata,
85+
element: "button",
86+
action: "click",
87+
},
88+
});
89+
});
90+
91+
it("should handle default actions and elements if none are provided", () => {
92+
const params = {
93+
callback: mockCallback,
94+
options: {},
95+
};
96+
97+
const handlers: EventHandler = eventCreator(params);
98+
99+
expect(Object.keys(handlers).length).toBeGreaterThan(0);
100+
expect(
101+
Object.keys(handlers[Object.keys(handlers)[0]]).length
102+
).toBeGreaterThan(0);
103+
});
104+
105+
it("should format the label correctly with stringFormat set to 'noCase'", () => {
106+
const params: EventCreatorParams = {
107+
...defaultParams,
108+
options: {
109+
...defaultParams.options,
110+
labelOptions: {
111+
stringCase: "lowercase",
112+
stringFormat: "noCase",
113+
},
114+
},
115+
};
116+
117+
const handlers: EventHandler = eventCreator(params);
118+
119+
const eventMetadata = { elementId: "456" };
120+
handlers.link.click(eventMetadata);
121+
122+
expect(mockCallback).toHaveBeenCalledWith({
123+
label: "home link click 456",
124+
metadata: {
125+
...eventMetadata,
126+
page: "Home",
127+
element: "link",
128+
action: "click",
129+
},
130+
});
131+
});
132+
});

src/features/eventCreator/index.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { EventAction } from "../../types/event/EventAction";
12
import { EventCreatorParams } from "../../types/event/EventCreatorParams";
23
import { EventElement } from "../../types/event/EventElement";
34
import { EventHandler } from "../../types/event/EventHandlers";
45
import { EventMetadata } from "../../types/event/EventMetadata";
5-
import { eventActions } from "../../utils/constants/eventActions";
6-
import { eventElements } from "../../utils/constants/eventElements";
6+
import { eventActions as defaultEventActions } from "../../utils/constants/eventActions";
7+
import { eventElements as defaultEventElements } from "../../utils/constants/eventElements";
8+
import { formatString } from "../../utils/helpers/formatString";
79

810
// function to create an object structure [element][action] and returns callback
911
export function eventCreator({
@@ -13,29 +15,41 @@ export function eventCreator({
1315
const handlers: EventHandler = {} as EventHandler;
1416

1517
const page = options?.page?.showOnLabel
16-
? `${options?.page?.currentPage?.trim()}.`
18+
? `${options?.page?.currentPage?.trim()}`
1719
: "";
1820
const showPageOnMetadata = options?.page?.showOnMetadata;
1921
const showElementIdOnLabel = options?.element?.showElementIdOnLabel;
22+
const labelOptions = options?.labelOptions;
23+
24+
const eventElements =
25+
options?.element?.possibleElements || defaultEventElements;
26+
const eventActions = options?.action?.possibleActions || defaultEventActions;
2027

2128
// iterate over each element
22-
eventElements.forEach((element) => {
29+
eventElements.forEach((element: EventElement) => {
2330
handlers[element] = {} as EventHandler[EventElement];
2431

2532
// iterate over each action
26-
eventActions.forEach((action) => {
33+
eventActions.forEach((action: EventAction) => {
2734
// define an action handler that receives event metadata
2835
handlers[element][action] = (eventMetadata: EventMetadata) => {
2936
const isShowElementId =
3037
eventMetadata?.elementId && showElementIdOnLabel;
3138
const elementId = isShowElementId
32-
? `.${eventMetadata?.elementId?.trim()}`
39+
? ` ${eventMetadata?.elementId?.trim()}`
3340
: "";
41+
3442
callback({
35-
label: `${page}${element}.${action}${elementId}`,
43+
label: formatString({
44+
str: `${page} ${element.trim()} ${action.trim()}${elementId}`,
45+
stringCase: labelOptions?.stringCase,
46+
stringFormat: labelOptions?.stringFormat,
47+
}),
3648
metadata: {
3749
...eventMetadata,
38-
...(showPageOnMetadata ? { page: page.slice(0, -1) } : {}),
50+
...(showPageOnMetadata ? { page: page } : {}),
51+
...(options?.element?.showOnMetadata ? { element: element } : {}),
52+
...(options?.action?.showOnMetadata ? { action: action } : {}),
3953
},
4054
});
4155
};

src/types/event/EventCreatorParams.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
1-
type StringFormat =
1+
export type StringFormat =
22
| "camelCase"
3-
| "pascalCase"
4-
| "snakeCase"
3+
| "PascalCase"
4+
| "snake_case"
5+
| "kebab-case"
56
| "dotCase"
6-
| "kebabCase"
77
| "noCase";
88

9-
type StringCase = "lowercase" | "uppercase";
9+
export type StringCase = "lowercase" | "uppercase" | "capitalize";
1010

1111
type EventCreatorParamsOptions = {
12-
page?: {
13-
currentPage?: string;
12+
labelOptions?: {
1413
stringCase?: StringCase;
1514
stringFormat?: StringFormat;
15+
};
16+
page?: {
17+
currentPage?: string;
1618
showOnLabel?: boolean;
1719
showOnMetadata?: boolean;
1820
};
1921
action?: {
2022
possibleActions?: string[];
21-
stringCase?: StringCase;
22-
stringFormat?: StringFormat;
2323
showOnMetadata?: boolean;
2424
};
2525
element?: {
2626
possibleElements?: string[];
27-
stringCase?: StringCase;
28-
stringFormat?: StringFormat;
2927
showOnMetadata?: boolean;
3028
showElementIdOnLabel?: boolean;
3129
};

0 commit comments

Comments
 (0)