gatsby-core-theme 44.20.0 → 44.21.1
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/CHANGELOG.md +30 -0
- package/gatsby-browser.js +35 -10
- package/package.json +1 -1
- package/src/components/atoms/button/operator-cta.js +14 -37
- package/src/components/atoms/button/operator-cta.test.js +82 -44
- package/src/components/atoms/pretty-link/index.js +12 -41
- package/src/components/atoms/pretty-link/pretty-link.test.js +54 -0
- package/src/helpers/validateData.mjs +27 -9
- package/src/hooks/useCtaClickHandler/index.js +82 -0
- package/src/hooks/useCtaClickHandler/useCtaClickHandler.test.js +145 -0
- package/src/resolver/index.mjs +2 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## [44.21.1](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.21.0...v44.21.1) (2026-04-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* null event on gatsby browser ([6378dfe](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/6378dfe56a3d3bcd737c125d9af6d53dbbabf37d))
|
|
7
|
+
* null event on gatsby browser ([2dd27b3](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/2dd27b3069acd122feeba0338922b87e3d92648d))
|
|
8
|
+
* null event on gatsby browser ([3ea9a1e](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/3ea9a1ea0ef5d338dcc7833cb84aec57df044867))
|
|
9
|
+
|
|
10
|
+
# [44.21.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.20.0...v44.21.0) (2026-04-07)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* apply INP fix to core theme level ([76bc451](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/76bc4516d27376203f6eafa71a2ca04456c10a20))
|
|
16
|
+
* fix test by mocking the useCtaClickHandler ([882f602](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/882f602797a4b0605cb0d16f71604906f6303552))
|
|
17
|
+
* fix test by mocking the useCtaClickHandler ([99b6f73](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/99b6f7324d09f053182ffb019ce2cb79d9fb4b22))
|
|
18
|
+
* improve code ([7d5ec29](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/7d5ec29d6db3105082de5b1f2267b434b8f2ff06))
|
|
19
|
+
* remove unused param ([aaa3e22](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/aaa3e22478d5cff7c5dfdbae8da7736d3e5a3ddc))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
* Merge branch 'improve-code' into 'master' ([1d4be07](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/1d4be073aa4727c500452713fb88d46216e2c60c))
|
|
23
|
+
* Merge branch 'feat/gatsby-browser-yield-thread' into 'master' ([d107be8](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/d107be8a617e1c24ee4598310be7f160247329c6))
|
|
24
|
+
* Merge branch 'inp-fix' into 'master' ([c892c47](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/c892c47ee11e8b4635c043764ec47b210b9ba192))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* yield before script loading ([ad0e7de](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/ad0e7de84050d8cdf7a5c3dc503f7dd11d67f63b))
|
|
30
|
+
|
|
1
31
|
# [44.20.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.19.2...v44.20.0) (2026-04-03)
|
|
2
32
|
|
|
3
33
|
|
package/gatsby-browser.js
CHANGED
|
@@ -16,6 +16,18 @@ require("./src/styles/base/_reset.scss");
|
|
|
16
16
|
require("./src/styles/base/_spacing.scss");
|
|
17
17
|
require("./src/styles/layouts/_grid.scss");
|
|
18
18
|
|
|
19
|
+
async function yieldToMain() {
|
|
20
|
+
if (globalThis.scheduler?.yield) {
|
|
21
|
+
return scheduler.yield();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
(globalThis.requestIdleCallback || setTimeout)(resolve);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let didInit = false;
|
|
30
|
+
|
|
19
31
|
function initGTM() {
|
|
20
32
|
if (
|
|
21
33
|
window.loadGTM === false ||
|
|
@@ -129,7 +141,10 @@ const piguard = () => {
|
|
|
129
141
|
document.head.appendChild(script);
|
|
130
142
|
};
|
|
131
143
|
|
|
132
|
-
function scrollEvent(
|
|
144
|
+
async function scrollEvent() {
|
|
145
|
+
if (didInit) return;
|
|
146
|
+
didInit = true;
|
|
147
|
+
await yieldToMain();
|
|
133
148
|
initGTM();
|
|
134
149
|
|
|
135
150
|
if (
|
|
@@ -166,19 +181,29 @@ function scrollEvent(event) {
|
|
|
166
181
|
fbq("track", "PageView");
|
|
167
182
|
}
|
|
168
183
|
}
|
|
169
|
-
|
|
170
|
-
if (event) {
|
|
171
|
-
event.currentTarget.removeEventListener(event.type, scrollEvent); // remove the event listener that got triggered
|
|
172
|
-
}
|
|
173
184
|
}
|
|
174
185
|
|
|
175
186
|
exports.onClientEntry = () => {
|
|
176
|
-
if (
|
|
177
|
-
|
|
187
|
+
if (
|
|
188
|
+
process.env.PPC === "true" ||
|
|
189
|
+
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
190
|
+
navigator.userAgent
|
|
191
|
+
)
|
|
192
|
+
) {
|
|
193
|
+
scrollEvent();
|
|
178
194
|
} else {
|
|
179
|
-
document.addEventListener("scroll", scrollEvent, {
|
|
180
|
-
|
|
181
|
-
|
|
195
|
+
document.addEventListener("scroll", scrollEvent, {
|
|
196
|
+
passive: true,
|
|
197
|
+
once: true,
|
|
198
|
+
});
|
|
199
|
+
document.addEventListener("mousemove", scrollEvent, {
|
|
200
|
+
passive: true,
|
|
201
|
+
once: true,
|
|
202
|
+
});
|
|
203
|
+
document.addEventListener("touchstart", scrollEvent, {
|
|
204
|
+
passive: true,
|
|
205
|
+
once: true,
|
|
206
|
+
});
|
|
182
207
|
}
|
|
183
208
|
// Capture external referrer on first entry
|
|
184
209
|
if (
|
package/package.json
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
/* eslint-disable no-unused-expressions */
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
|
-
import { globalHistory } from '@reach/router';
|
|
6
5
|
import useTranslate from '~hooks/useTranslate/useTranslate';
|
|
7
6
|
import { prettyTracker } from '~helpers/getters';
|
|
8
|
-
import {
|
|
9
|
-
import { getTrackingAPIParams, trackerLinkActive } from '~helpers/tracker';
|
|
10
|
-
import keygen from '~helpers/keygen';
|
|
7
|
+
import { trackerLinkActive } from '~helpers/tracker';
|
|
11
8
|
import styles from './button.module.scss';
|
|
9
|
+
import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index';
|
|
12
10
|
|
|
13
11
|
const OperatorCtaButton = ({
|
|
14
12
|
operator,
|
|
@@ -43,10 +41,6 @@ const OperatorCtaButton = ({
|
|
|
43
41
|
}) => {
|
|
44
42
|
const status = operator?.status || ''
|
|
45
43
|
const trackerType = tracker?.toLowerCase()?.replace(' ', '_')
|
|
46
|
-
const referer =
|
|
47
|
-
typeof document !== 'undefined' && document?.referrer
|
|
48
|
-
? document.referrer
|
|
49
|
-
: '';
|
|
50
44
|
|
|
51
45
|
const prettyLink = `${prettyTracker(
|
|
52
46
|
operator,
|
|
@@ -54,6 +48,16 @@ const OperatorCtaButton = ({
|
|
|
54
48
|
false
|
|
55
49
|
)}`
|
|
56
50
|
|
|
51
|
+
const { ctaRef, handleCtaClick } = useCtaClickHandler({
|
|
52
|
+
pageTemplate,
|
|
53
|
+
moduleName,
|
|
54
|
+
tracker,
|
|
55
|
+
clickedElement,
|
|
56
|
+
modulePosition,
|
|
57
|
+
itemPosition,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
|
|
57
61
|
const translateBtn =
|
|
58
62
|
status && translationsObj[status]
|
|
59
63
|
? // eslint-disable-next-line react-hooks/rules-of-hooks
|
|
@@ -63,40 +67,13 @@ const OperatorCtaButton = ({
|
|
|
63
67
|
)
|
|
64
68
|
: 'Claim'
|
|
65
69
|
|
|
66
|
-
const affObject = JSON.stringify(
|
|
67
|
-
getTrackingAPIParams(
|
|
68
|
-
pageTemplate,
|
|
69
|
-
moduleName,
|
|
70
|
-
tracker,
|
|
71
|
-
clickedElement,
|
|
72
|
-
globalHistory.location.href,
|
|
73
|
-
referer,
|
|
74
|
-
modulePosition,
|
|
75
|
-
itemPosition
|
|
76
|
-
)
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
const onCTAClick = () => {
|
|
80
|
-
process.env.IS_TRACKING_SSR &&
|
|
81
|
-
setCookie(
|
|
82
|
-
'affObject',
|
|
83
|
-
affObject,
|
|
84
|
-
1,
|
|
85
|
-
'/'
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
if (process.env.ENABLE_PIXEL === 'true' && typeof window.fbq !== 'undefined') {
|
|
89
|
-
// Call fbq track event
|
|
90
|
-
window.fbq('track', 'InitiateCheckout', { keyID: keygen() })
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
70
|
const classes = `${styles[buttonType] || ''} ${status && styles[status] ? styles[status] : ''} ${buttonSize ? styles[`${buttonSize}_size`] : ''
|
|
95
71
|
} `;
|
|
96
72
|
|
|
97
73
|
return (
|
|
98
74
|
trackerLinkActive(operator, tracker) && status === "active" ? (
|
|
99
75
|
<a
|
|
76
|
+
ref={ctaRef}
|
|
100
77
|
href={prettyLink || "#"}
|
|
101
78
|
title={`${typeof translateBtn === "string" ? translateBtn : ""} ${titleSuffix || ""
|
|
102
79
|
}`.trimEnd()}
|
|
@@ -105,7 +82,7 @@ const OperatorCtaButton = ({
|
|
|
105
82
|
className={`${classes} ${gtmClass}`}
|
|
106
83
|
target="_blank"
|
|
107
84
|
rel={rel}
|
|
108
|
-
onClick={
|
|
85
|
+
onClick={handleCtaClick}
|
|
109
86
|
>
|
|
110
87
|
{translateBtn}
|
|
111
88
|
{icon && icon}
|
|
@@ -4,6 +4,9 @@ import React from 'react';
|
|
|
4
4
|
import { render, cleanup, fireEvent } from '@testing-library/react';
|
|
5
5
|
import '@testing-library/jest-dom/extend-expect';
|
|
6
6
|
import OperatorCtaButton from './operator-cta';
|
|
7
|
+
import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index';
|
|
8
|
+
|
|
9
|
+
jest.mock('../../../hooks/useCtaClickHandler/index', () => jest.fn());
|
|
7
10
|
|
|
8
11
|
const data = (activeStatus = 'active', links = true) => ({
|
|
9
12
|
operator: {
|
|
@@ -24,62 +27,35 @@ const data = (activeStatus = 'active', links = true) => ({
|
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
describe('OperatorCtaButton Component', () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} else {
|
|
33
|
-
expect(button).toBeNull();
|
|
34
|
-
}
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
useCtaClickHandler.mockImplementation(() => ({
|
|
32
|
+
ctaRef: { current: null },
|
|
33
|
+
handleCtaClick: jest.fn(),
|
|
34
|
+
}));
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const button = queryByRole('link');
|
|
40
|
-
if (button) {
|
|
41
|
-
expect(getByText('Not Accepting New Players')).toBeInTheDocument();
|
|
42
|
-
} else {
|
|
43
|
-
expect(button).toBeNull();
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('OperatorCtaButton with status not_recommended', () => {
|
|
48
|
-
const { getByText, queryByRole } = render(<OperatorCtaButton {...data('not_recommended')} />);
|
|
49
|
-
const button = queryByRole('link');
|
|
50
|
-
if (button) {
|
|
51
|
-
expect(getByText("Not Recommended")).toBeInTheDocument();
|
|
52
|
-
} else {
|
|
53
|
-
expect(button).toBeNull();
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('OperatorCtaButton with status coming_soon', () => {
|
|
58
|
-
const { getByText, queryByRole } = render(<OperatorCtaButton {...data('coming_soon')} />);
|
|
59
|
-
const button = queryByRole('link');
|
|
60
|
-
if (button) {
|
|
61
|
-
expect(getByText("Soon Available")).toBeInTheDocument();
|
|
62
|
-
} else {
|
|
63
|
-
expect(button).toBeNull();
|
|
64
|
-
}
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
jest.clearAllMocks();
|
|
65
39
|
});
|
|
66
40
|
|
|
67
41
|
test('Affiliate object in cookie', () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
value: {
|
|
73
|
-
href: url,
|
|
74
|
-
},
|
|
75
|
-
writable: true, // possibility to override
|
|
42
|
+
const mockClickHandler = jest.fn((e) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
document.cookie =
|
|
45
|
+
'affObject={"page_type":"test","module":"test2","request_url":"http://localhost/","tracker_name":"testing"}';
|
|
76
46
|
});
|
|
47
|
+
useCtaClickHandler.mockImplementation(() => ({
|
|
48
|
+
ctaRef: { current: null },
|
|
49
|
+
handleCtaClick: mockClickHandler,
|
|
50
|
+
}));
|
|
51
|
+
|
|
77
52
|
const { container } = render(
|
|
78
53
|
<OperatorCtaButton {...data()} pageTemplate="test" moduleName="test2" tracker="testing" />
|
|
79
54
|
);
|
|
80
55
|
const button = container.querySelector('a');
|
|
81
56
|
if (button) {
|
|
82
57
|
fireEvent.click(button);
|
|
58
|
+
expect(mockClickHandler).toHaveBeenCalled();
|
|
83
59
|
const affObject = JSON.parse(document.cookie.split('=')[1]);
|
|
84
60
|
expect(affObject.page_type).toEqual('test');
|
|
85
61
|
expect(affObject.module).toEqual('test2');
|
|
@@ -91,10 +67,20 @@ describe('OperatorCtaButton Component', () => {
|
|
|
91
67
|
});
|
|
92
68
|
|
|
93
69
|
test('Default Affiliate object in cookie', () => {
|
|
70
|
+
const mockClickHandler = jest.fn((e) => {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
document.cookie =
|
|
73
|
+
'affObject={"page_type":"default","request_url":"http://localhost/"}';
|
|
74
|
+
});
|
|
75
|
+
useCtaClickHandler.mockImplementation(() => ({
|
|
76
|
+
ctaRef: { current: null },
|
|
77
|
+
handleCtaClick: mockClickHandler,
|
|
78
|
+
}));
|
|
94
79
|
const { container } = render(<OperatorCtaButton {...data()} />);
|
|
95
80
|
const button = container.querySelector('a');
|
|
96
81
|
if (button) {
|
|
97
82
|
fireEvent.click(button);
|
|
83
|
+
expect(mockClickHandler).toHaveBeenCalled();
|
|
98
84
|
const affObject = JSON.parse(document.cookie.split('=')[1]);
|
|
99
85
|
expect(affObject.page_type).toEqual('default');
|
|
100
86
|
expect(affObject.request_url).toEqual('http://localhost/');
|
|
@@ -108,6 +94,58 @@ describe('OperatorCtaButton Component', () => {
|
|
|
108
94
|
const button = queryByRole('link');
|
|
109
95
|
expect(button).toBeNull();
|
|
110
96
|
});
|
|
97
|
+
|
|
98
|
+
test('Passes tracking props to cta click hook', () => {
|
|
99
|
+
render(<OperatorCtaButton {...data()} moduleName="test-module" tracker="testing" />);
|
|
100
|
+
expect(useCtaClickHandler).toHaveBeenCalledWith({
|
|
101
|
+
pageTemplate: 'default',
|
|
102
|
+
moduleName: 'test-module',
|
|
103
|
+
tracker: 'testing',
|
|
104
|
+
clickedElement: 'cta',
|
|
105
|
+
modulePosition: undefined,
|
|
106
|
+
itemPosition: undefined,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('OperatorCtaButton with status active', () => {
|
|
111
|
+
const { getByText, queryByRole } = render(<OperatorCtaButton {...data()} />);
|
|
112
|
+
const button = queryByRole('link');
|
|
113
|
+
if (button) {
|
|
114
|
+
expect(getByText("Visit")).toBeInTheDocument();
|
|
115
|
+
} else {
|
|
116
|
+
expect(button).toBeNull();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('OperatorCtaButton with status inactive', () => {
|
|
121
|
+
const { getByText, queryByRole } = render(<OperatorCtaButton {...data('inactive')} />);
|
|
122
|
+
const button = queryByRole('link');
|
|
123
|
+
if (button) {
|
|
124
|
+
expect(getByText('Not Accepting New Players')).toBeInTheDocument();
|
|
125
|
+
} else {
|
|
126
|
+
expect(button).toBeNull();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('OperatorCtaButton with status not_recommended', () => {
|
|
131
|
+
const { getByText, queryByRole } = render(<OperatorCtaButton {...data('not_recommended')} />);
|
|
132
|
+
const button = queryByRole('link');
|
|
133
|
+
if (button) {
|
|
134
|
+
expect(getByText("Not Recommended")).toBeInTheDocument();
|
|
135
|
+
} else {
|
|
136
|
+
expect(button).toBeNull();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('OperatorCtaButton with status coming_soon', () => {
|
|
141
|
+
const { getByText, queryByRole } = render(<OperatorCtaButton {...data('coming_soon')} />);
|
|
142
|
+
const button = queryByRole('link');
|
|
143
|
+
if (button) {
|
|
144
|
+
expect(getByText("Soon Available")).toBeInTheDocument();
|
|
145
|
+
} else {
|
|
146
|
+
expect(button).toBeNull();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
111
149
|
});
|
|
112
150
|
|
|
113
151
|
afterEach(() => {
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/* eslint-disable react/jsx-no-target-blank */
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import PropTypes from 'prop-types'
|
|
4
|
-
import { globalHistory } from '@reach/router'
|
|
5
4
|
import { prettyTracker } from '~helpers/getters'
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import { getTrackingAPIParams, trackerLinkActive } from '~helpers/tracker'
|
|
5
|
+
import { trackerLinkActive } from '~helpers/tracker'
|
|
6
|
+
import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index'
|
|
9
7
|
|
|
10
8
|
const PrettyLink = ({
|
|
11
9
|
operator,
|
|
@@ -22,57 +20,30 @@ const PrettyLink = ({
|
|
|
22
20
|
itemPosition
|
|
23
21
|
}) => {
|
|
24
22
|
const prettyLink = prettyTracker(operator, tracker, false, pageTemplate)
|
|
25
|
-
const referer =
|
|
26
|
-
typeof document !== 'undefined' && document?.referrer
|
|
27
|
-
? document.referrer
|
|
28
|
-
: ''
|
|
29
|
-
|
|
30
|
-
const affObject = JSON.stringify(
|
|
31
|
-
getTrackingAPIParams(
|
|
32
|
-
pageTemplate,
|
|
33
|
-
moduleName,
|
|
34
|
-
tracker,
|
|
35
|
-
clickedElement,
|
|
36
|
-
globalHistory.location.href,
|
|
37
|
-
referer,
|
|
38
|
-
modulePosition,
|
|
39
|
-
itemPosition
|
|
40
|
-
)
|
|
41
|
-
)
|
|
42
23
|
|
|
24
|
+
const { ctaRef, handleCtaClick } = useCtaClickHandler({
|
|
25
|
+
pageTemplate,
|
|
26
|
+
moduleName,
|
|
27
|
+
tracker,
|
|
28
|
+
clickedElement,
|
|
29
|
+
modulePosition,
|
|
30
|
+
itemPosition,
|
|
31
|
+
});
|
|
43
32
|
|
|
44
|
-
const onCTAClick = () => {
|
|
45
|
-
// eslint-disable-next-line no-unused-expressions
|
|
46
|
-
process.env.IS_TRACKING_SSR &&
|
|
47
|
-
setCookie(
|
|
48
|
-
'affObject',
|
|
49
|
-
affObject,
|
|
50
|
-
1,
|
|
51
|
-
'/'
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
process.env.ENABLE_PIXEL === 'true' &&
|
|
56
|
-
typeof window.fbq !== 'undefined'
|
|
57
|
-
) {
|
|
58
|
-
// Call fbq track event
|
|
59
|
-
window.fbq('track', 'InitiateCheckout', { keyID: keygen() })
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
33
|
const isLinkActive =
|
|
64
34
|
(prettyLink && trackerLinkActive(operator, tracker)) || directPrettyLink
|
|
65
35
|
const hrefLink = prettyLink || directPrettyLink || '#'
|
|
66
36
|
|
|
67
37
|
return isLinkActive ? (
|
|
68
38
|
<a
|
|
39
|
+
ref={ctaRef}
|
|
69
40
|
href={hrefLink}
|
|
70
41
|
title={`${operator?.name || ''} ${titleSuffix || ''}`.trimEnd() || ''}
|
|
71
42
|
className={className}
|
|
72
43
|
target="_blank"
|
|
73
44
|
rel={rel}
|
|
74
45
|
aria-label={`${operator?.name} ${titleSuffix || 'Link'}`}
|
|
75
|
-
onClick={
|
|
46
|
+
onClick={handleCtaClick}
|
|
76
47
|
>
|
|
77
48
|
{children}
|
|
78
49
|
</a>
|
|
@@ -5,6 +5,9 @@ import { render, cleanup, fireEvent } from '@testing-library/react';
|
|
|
5
5
|
import '@testing-library/jest-dom/extend-expect';
|
|
6
6
|
|
|
7
7
|
import PrettyLink from '.';
|
|
8
|
+
import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index';
|
|
9
|
+
|
|
10
|
+
jest.mock('../../../hooks/useCtaClickHandler/index', () => jest.fn());
|
|
8
11
|
|
|
9
12
|
const data = (activeStatus = 'active') => ({
|
|
10
13
|
operator: {
|
|
@@ -21,6 +24,17 @@ const data = (activeStatus = 'active') => ({
|
|
|
21
24
|
});
|
|
22
25
|
|
|
23
26
|
describe('PrettyLink Component', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
useCtaClickHandler.mockImplementation(() => ({
|
|
29
|
+
ctaRef: { current: null },
|
|
30
|
+
handleCtaClick: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
24
38
|
test('PrettyLink children and link', () => {
|
|
25
39
|
const { getByText, container } = render(<PrettyLink {...data()} />);
|
|
26
40
|
expect(getByText('Test children').tagName.toLowerCase()).toBe('h1');
|
|
@@ -28,6 +42,15 @@ describe('PrettyLink Component', () => {
|
|
|
28
42
|
});
|
|
29
43
|
|
|
30
44
|
test('Affiliate object in cookie', () => {
|
|
45
|
+
const mockClickHandler = jest.fn((e) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
document.cookie =
|
|
48
|
+
'affObject={"page_type":"test","module":"test2","request_url":"http://localhost/","tracker_name":"testing","clicked_element":"cta"}';
|
|
49
|
+
});
|
|
50
|
+
useCtaClickHandler.mockImplementation(() => ({
|
|
51
|
+
ctaRef: { current: null },
|
|
52
|
+
handleCtaClick: mockClickHandler,
|
|
53
|
+
}));
|
|
31
54
|
const { container } = render(
|
|
32
55
|
<PrettyLink
|
|
33
56
|
{...data()}
|
|
@@ -39,6 +62,7 @@ describe('PrettyLink Component', () => {
|
|
|
39
62
|
);
|
|
40
63
|
const button = container.querySelector('a');
|
|
41
64
|
fireEvent.click(button);
|
|
65
|
+
expect(mockClickHandler).toHaveBeenCalled();
|
|
42
66
|
const affObject = JSON.parse(document.cookie.split('=')[1]);
|
|
43
67
|
expect(affObject.page_type).toEqual('test');
|
|
44
68
|
expect(affObject.module).toEqual('test2');
|
|
@@ -62,13 +86,43 @@ describe('PrettyLink Component', () => {
|
|
|
62
86
|
});
|
|
63
87
|
|
|
64
88
|
test('Default Affiliate object in cookie', () => {
|
|
89
|
+
const mockClickHandler = jest.fn((e) => {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
document.cookie =
|
|
92
|
+
'affObject={"page_type":"default","request_url":"http://localhost/"}';
|
|
93
|
+
});
|
|
94
|
+
useCtaClickHandler.mockImplementation(() => ({
|
|
95
|
+
ctaRef: { current: null },
|
|
96
|
+
handleCtaClick: mockClickHandler,
|
|
97
|
+
}));
|
|
65
98
|
const { container } = render(<PrettyLink {...data()} />);
|
|
66
99
|
const button = container.querySelector('a');
|
|
67
100
|
fireEvent.click(button);
|
|
101
|
+
expect(mockClickHandler).toHaveBeenCalled();
|
|
68
102
|
const affObject = JSON.parse(document.cookie.split('=')[1]);
|
|
69
103
|
expect(affObject.page_type).toEqual('default');
|
|
70
104
|
expect(affObject.request_url).toEqual('http://localhost/');
|
|
71
105
|
});
|
|
106
|
+
|
|
107
|
+
test('Passes tracking props to cta click hook', () => {
|
|
108
|
+
render(
|
|
109
|
+
<PrettyLink
|
|
110
|
+
{...data()}
|
|
111
|
+
pageTemplate="test"
|
|
112
|
+
moduleName="test2"
|
|
113
|
+
tracker="testing"
|
|
114
|
+
clickedElement="cta"
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
expect(useCtaClickHandler).toHaveBeenCalledWith({
|
|
118
|
+
pageTemplate: 'test',
|
|
119
|
+
moduleName: 'test2',
|
|
120
|
+
tracker: 'testing',
|
|
121
|
+
clickedElement: 'cta',
|
|
122
|
+
modulePosition: undefined,
|
|
123
|
+
itemPosition: undefined,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
72
126
|
});
|
|
73
127
|
|
|
74
128
|
afterEach(() => {
|
|
@@ -1,17 +1,35 @@
|
|
|
1
|
-
/* eslint-disable no-useless-return */
|
|
2
1
|
// eslint-disable-next-line import/prefer-default-export
|
|
3
2
|
import { excludeStatusInactiveToplist } from '../constants/excluded.mjs';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const INACTIVE_SHORTCODES = new Set([
|
|
5
|
+
'inactive_operator_toplist',
|
|
6
|
+
'inactive_sportsbook_toplist',
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export default function checkForInactiveOperatorToplist(item, status, res, pageType) {
|
|
10
|
+
const shortCode = item?.items?.[0]?.value;
|
|
7
11
|
const siteName = process.env.GATSBY_SITE_NAME;
|
|
8
|
-
const excludeStatus =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return
|
|
12
|
+
const excludeStatus =
|
|
13
|
+
excludeStatusInactiveToplist[siteName] || excludeStatusInactiveToplist.default;
|
|
14
|
+
|
|
15
|
+
if (item.name === 'top_list') {
|
|
16
|
+
|
|
17
|
+
if (!INACTIVE_SHORTCODES.has(shortCode)) return false;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if (excludeStatus.includes(status)) return true;
|
|
21
|
+
|
|
22
|
+
const expectedShortcode =
|
|
23
|
+
pageType === 'sportsbook'
|
|
24
|
+
? 'inactive_sportsbook_toplist'
|
|
25
|
+
: 'inactive_operator_toplist';
|
|
26
|
+
|
|
27
|
+
return shortCode !== expectedShortcode;
|
|
14
28
|
}
|
|
15
29
|
|
|
30
|
+
if (item.name === 'cards_v2') {
|
|
31
|
+
return status === 'active' && res === 'pre_main_operators';
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
return false;
|
|
17
35
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
import { globalHistory } from '@reach/router';
|
|
3
|
+
import { setCookie } from '~helpers/cookies';
|
|
4
|
+
import keygen from '~helpers/keygen';
|
|
5
|
+
import { getTrackingAPIParams } from '~helpers/tracker';
|
|
6
|
+
|
|
7
|
+
const useCtaClickHandler = ({
|
|
8
|
+
pageTemplate,
|
|
9
|
+
moduleName,
|
|
10
|
+
tracker,
|
|
11
|
+
clickedElement,
|
|
12
|
+
modulePosition,
|
|
13
|
+
itemPosition,
|
|
14
|
+
}) => {
|
|
15
|
+
const referer =
|
|
16
|
+
typeof document !== 'undefined' && document?.referrer
|
|
17
|
+
? document.referrer
|
|
18
|
+
: '';
|
|
19
|
+
|
|
20
|
+
const affObject = JSON.stringify(
|
|
21
|
+
getTrackingAPIParams(
|
|
22
|
+
pageTemplate,
|
|
23
|
+
moduleName,
|
|
24
|
+
tracker,
|
|
25
|
+
clickedElement,
|
|
26
|
+
globalHistory.location.href,
|
|
27
|
+
referer,
|
|
28
|
+
modulePosition,
|
|
29
|
+
itemPosition
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const ctaRef = useRef(null);
|
|
34
|
+
|
|
35
|
+
const fireTracking = useCallback(() => {
|
|
36
|
+
if (process.env.IS_TRACKING_SSR) {
|
|
37
|
+
setCookie('affObject', affObject, 1, '/');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
process.env.ENABLE_PIXEL === 'true' &&
|
|
42
|
+
typeof window.fbq !== 'undefined'
|
|
43
|
+
) {
|
|
44
|
+
window.fbq('track', 'InitiateCheckout', { keyID: keygen() });
|
|
45
|
+
}
|
|
46
|
+
}, [affObject]);
|
|
47
|
+
|
|
48
|
+
const handleCtaClick = useCallback(
|
|
49
|
+
(e) => {
|
|
50
|
+
// Ignore synthetic events dispatched for GTM
|
|
51
|
+
if (!e.isTrusted) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Block GTM's click listener from running synchronously (it listens on document)
|
|
57
|
+
e.stopPropagation();
|
|
58
|
+
|
|
59
|
+
const anchor = ctaRef.current;
|
|
60
|
+
if (!anchor) return;
|
|
61
|
+
|
|
62
|
+
requestAnimationFrame(() => {
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
fireTracking();
|
|
65
|
+
|
|
66
|
+
// Re-notify GTM with a synthetic click so it can still track the event
|
|
67
|
+
const gtmEvent = new MouseEvent('click', {
|
|
68
|
+
bubbles: true,
|
|
69
|
+
cancelable: true,
|
|
70
|
+
view: window,
|
|
71
|
+
});
|
|
72
|
+
anchor.dispatchEvent(gtmEvent);
|
|
73
|
+
}, 0);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
[fireTracking]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return { ctaRef, handleCtaClick };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default useCtaClickHandler;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import useCtaClickHandler from './index';
|
|
3
|
+
import { setCookie } from '~helpers/cookies';
|
|
4
|
+
import keygen from '~helpers/keygen';
|
|
5
|
+
import { getTrackingAPIParams } from '~helpers/tracker';
|
|
6
|
+
|
|
7
|
+
jest.mock('@reach/router', () => ({
|
|
8
|
+
globalHistory: {
|
|
9
|
+
location: {
|
|
10
|
+
href: 'http://localhost/',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock('~helpers/cookies', () => ({
|
|
16
|
+
setCookie: jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
jest.mock('~helpers/keygen', () => jest.fn());
|
|
20
|
+
|
|
21
|
+
jest.mock('~helpers/tracker', () => ({
|
|
22
|
+
getTrackingAPIParams: jest.fn(),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('useCtaClickHandler', () => {
|
|
26
|
+
const originalTrackingSsr = process.env.IS_TRACKING_SSR;
|
|
27
|
+
const originalEnablePixel = process.env.ENABLE_PIXEL;
|
|
28
|
+
const originalFbq = window.fbq;
|
|
29
|
+
const originalRequestAnimationFrame = global.requestAnimationFrame;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.clearAllMocks();
|
|
33
|
+
process.env.IS_TRACKING_SSR = 'true';
|
|
34
|
+
process.env.ENABLE_PIXEL = 'false';
|
|
35
|
+
getTrackingAPIParams.mockReturnValue({
|
|
36
|
+
page_type: 'default',
|
|
37
|
+
request_url: 'http://localhost/',
|
|
38
|
+
});
|
|
39
|
+
keygen.mockReturnValue('generated-key');
|
|
40
|
+
global.requestAnimationFrame = (cb) => cb();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
process.env.IS_TRACKING_SSR = originalTrackingSsr;
|
|
45
|
+
process.env.ENABLE_PIXEL = originalEnablePixel;
|
|
46
|
+
window.fbq = originalFbq;
|
|
47
|
+
global.requestAnimationFrame = originalRequestAnimationFrame;
|
|
48
|
+
jest.useRealTimers();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('builds tracking params from hook arguments', () => {
|
|
52
|
+
renderHook(() =>
|
|
53
|
+
useCtaClickHandler({
|
|
54
|
+
pageTemplate: 'review',
|
|
55
|
+
moduleName: 'top-list',
|
|
56
|
+
tracker: 'main',
|
|
57
|
+
clickedElement: 'cta',
|
|
58
|
+
modulePosition: 1,
|
|
59
|
+
itemPosition: 2,
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(getTrackingAPIParams).toHaveBeenCalledWith(
|
|
64
|
+
'review',
|
|
65
|
+
'top-list',
|
|
66
|
+
'main',
|
|
67
|
+
'cta',
|
|
68
|
+
'http://localhost/',
|
|
69
|
+
'',
|
|
70
|
+
1,
|
|
71
|
+
2
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('ignores non-trusted click events', () => {
|
|
76
|
+
const { result } = renderHook(() =>
|
|
77
|
+
useCtaClickHandler({
|
|
78
|
+
pageTemplate: 'default',
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
const event = {
|
|
82
|
+
isTrusted: false,
|
|
83
|
+
preventDefault: jest.fn(),
|
|
84
|
+
stopPropagation: jest.fn(),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
result.current.handleCtaClick(event);
|
|
88
|
+
|
|
89
|
+
expect(event.preventDefault).toHaveBeenCalled();
|
|
90
|
+
expect(event.stopPropagation).not.toHaveBeenCalled();
|
|
91
|
+
expect(setCookie).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('does nothing when trusted click has no anchor ref', () => {
|
|
95
|
+
const { result } = renderHook(() =>
|
|
96
|
+
useCtaClickHandler({
|
|
97
|
+
pageTemplate: 'default',
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
const event = {
|
|
101
|
+
isTrusted: true,
|
|
102
|
+
preventDefault: jest.fn(),
|
|
103
|
+
stopPropagation: jest.fn(),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
result.current.handleCtaClick(event);
|
|
107
|
+
|
|
108
|
+
expect(event.stopPropagation).toHaveBeenCalled();
|
|
109
|
+
expect(setCookie).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('fires tracking and redispatches click for trusted events', () => {
|
|
113
|
+
jest.useFakeTimers();
|
|
114
|
+
process.env.ENABLE_PIXEL = 'true';
|
|
115
|
+
window.fbq = jest.fn();
|
|
116
|
+
|
|
117
|
+
const { result } = renderHook(() =>
|
|
118
|
+
useCtaClickHandler({
|
|
119
|
+
pageTemplate: 'default',
|
|
120
|
+
tracker: 'main',
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
const dispatchEvent = jest.fn();
|
|
124
|
+
result.current.ctaRef.current = { dispatchEvent };
|
|
125
|
+
const event = {
|
|
126
|
+
isTrusted: true,
|
|
127
|
+
preventDefault: jest.fn(),
|
|
128
|
+
stopPropagation: jest.fn(),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
result.current.handleCtaClick(event);
|
|
132
|
+
jest.runAllTimers();
|
|
133
|
+
|
|
134
|
+
expect(setCookie).toHaveBeenCalledWith(
|
|
135
|
+
'affObject',
|
|
136
|
+
JSON.stringify({ page_type: 'default', request_url: 'http://localhost/' }),
|
|
137
|
+
1,
|
|
138
|
+
'/'
|
|
139
|
+
);
|
|
140
|
+
expect(window.fbq).toHaveBeenCalledWith('track', 'InitiateCheckout', {
|
|
141
|
+
keyID: 'generated-key',
|
|
142
|
+
});
|
|
143
|
+
expect(dispatchEvent).toHaveBeenCalledTimes(1);
|
|
144
|
+
});
|
|
145
|
+
});
|
package/src/resolver/index.mjs
CHANGED
|
@@ -627,24 +627,11 @@ export default {
|
|
|
627
627
|
checkForInactiveOperatorToplist(
|
|
628
628
|
item,
|
|
629
629
|
page.relation.status,
|
|
630
|
-
res
|
|
630
|
+
res,
|
|
631
|
+
page.relation.type
|
|
631
632
|
)
|
|
632
633
|
)
|
|
633
634
|
return;
|
|
634
|
-
|
|
635
|
-
if (item.name === "top_list") {
|
|
636
|
-
const shortCode = item?.items?.[0]?.value;
|
|
637
|
-
const isSportsbook =
|
|
638
|
-
page?.relation?.type === "sportsbook";
|
|
639
|
-
|
|
640
|
-
const shouldSkip =
|
|
641
|
-
(isSportsbook &&
|
|
642
|
-
shortCode === "inactive_operator_toplist") ||
|
|
643
|
-
(!isSportsbook &&
|
|
644
|
-
shortCode === "inactive_sportsbook_toplist");
|
|
645
|
-
|
|
646
|
-
if (shouldSkip) return;
|
|
647
|
-
}
|
|
648
635
|
|
|
649
636
|
//pre main games section will show only on inactive game page
|
|
650
637
|
const isActiveGamePage = page?.relation?.type === "game" || page?.relation?.status === "active";
|