@financial-times/cp-content-pipeline-ui 6.11.5 → 6.13.0-beta.0
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 +7 -0
- package/lib/client.d.ts +1 -0
- package/lib/client.js +3 -1
- package/lib/client.js.map +1 -1
- package/lib/components/Body/index.test.js +1 -0
- package/lib/components/Body/index.test.js.map +1 -1
- package/lib/components/Flourish/index.js +1 -1
- package/lib/components/Flourish/index.js.map +1 -1
- package/lib/components/ImageSet/index.d.ts +1 -1
- package/lib/components/ImageSet/index.js +10 -2
- package/lib/components/ImageSet/index.js.map +1 -1
- package/lib/components/LiveBlogWrapper/index.js +1 -1
- package/lib/components/LiveBlogWrapper/index.js.map +1 -1
- package/lib/components/PartnerContentHeader/index.d.ts +1 -1
- package/lib/components/PartnerContentHeader/index.js +2 -2
- package/lib/components/PartnerContentHeader/index.js.map +1 -1
- package/lib/components/Topper/Picture.js +2 -2
- package/lib/components/Topper/Picture.js.map +1 -1
- package/lib/components/Topper/client/flourish-tracking.d.ts +14 -0
- package/lib/components/Topper/client/flourish-tracking.js +69 -0
- package/lib/components/Topper/client/flourish-tracking.js.map +1 -0
- package/lib/components/Topper/client/index.d.ts +2 -0
- package/lib/components/Topper/client/index.js +6 -0
- package/lib/components/Topper/client/index.js.map +1 -0
- package/lib/components/Topper/test/client/flourish-tracking.spec.d.ts +1 -0
- package/lib/components/Topper/test/client/flourish-tracking.spec.js +117 -0
- package/lib/components/Topper/test/client/flourish-tracking.spec.js.map +1 -0
- package/package.json +1 -1
- package/src/client.ts +1 -0
- package/src/components/Body/index.test.tsx +1 -0
- package/src/components/Flourish/index.tsx +5 -0
- package/src/components/Flourish/test/__snapshots__/snapshot.spec.tsx.snap +18 -0
- package/src/components/ImageSet/index.tsx +21 -11
- package/src/components/LiveBlogWrapper/index.tsx +1 -1
- package/src/components/PartnerContentHeader/index.tsx +2 -6
- package/src/components/Topper/Picture.tsx +2 -2
- package/src/components/Topper/client/flourish-tracking.ts +83 -0
- package/src/components/Topper/client/index.ts +3 -0
- package/src/components/Topper/test/client/flourish-tracking.spec.ts +135 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -43,7 +43,7 @@ export type AliasedBreakpoints = {
|
|
|
43
43
|
|
|
44
44
|
const defaultGetBreakpoints = (
|
|
45
45
|
image: ImageFragment
|
|
46
|
-
): Breakpoint | AliasedBreakpoints | undefined => {
|
|
46
|
+
): Breakpoint | Array<Breakpoint> | AliasedBreakpoints | undefined => {
|
|
47
47
|
switch (image.format) {
|
|
48
48
|
case 'mobile':
|
|
49
49
|
return { maxWidth: 490 }
|
|
@@ -73,7 +73,11 @@ function hasSourceSet(image: ImageFragment): image is ImageWithSourceSet {
|
|
|
73
73
|
function isAliasedBreakpoints(
|
|
74
74
|
breakpoints: ReturnType<BreakpointGetter>
|
|
75
75
|
): breakpoints is AliasedBreakpoints {
|
|
76
|
-
return Boolean(
|
|
76
|
+
return Boolean(
|
|
77
|
+
breakpoints &&
|
|
78
|
+
Object.values(breakpoints)[0] instanceof Object &&
|
|
79
|
+
!Array.isArray(breakpoints)
|
|
80
|
+
)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
type SourceProps = {
|
|
@@ -113,16 +117,22 @@ function Source({
|
|
|
113
117
|
{Object.entries(breakpointsDict)
|
|
114
118
|
.map(([key, breakpoint]) => {
|
|
115
119
|
const sourceSet = (image as ImageWithAliases)[key]
|
|
120
|
+
const sourceSetMap = Array.isArray(breakpoint)
|
|
121
|
+
? breakpoint
|
|
122
|
+
: [breakpoint]
|
|
116
123
|
return (
|
|
117
|
-
sourceSet &&
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
sourceSet &&
|
|
125
|
+
sourceSetMap.map((breakpoint) => {
|
|
126
|
+
return (
|
|
127
|
+
<source
|
|
128
|
+
key={key}
|
|
129
|
+
media={breakpointToMedia(breakpoint)}
|
|
130
|
+
srcSet={formatSourceSet(sourceSet)}
|
|
131
|
+
width={image.width ?? undefined}
|
|
132
|
+
height={image.height ?? undefined}
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
})
|
|
126
136
|
)
|
|
127
137
|
})
|
|
128
138
|
.filter((elem) => elem)}
|
|
@@ -111,7 +111,7 @@ class BaseLiveBlogWrapper extends Component<
|
|
|
111
111
|
*/
|
|
112
112
|
const config = {
|
|
113
113
|
query:
|
|
114
|
-
'article[data-trackable="live-post"],article[data-trackable="pinned-post"]',
|
|
114
|
+
'article[data-trackable="live-post"],article[data-trackable="pinned-post"],[data-component-type="flourish-topper"]',
|
|
115
115
|
minMillisecondsToReport: 5000,
|
|
116
116
|
returnVisibleElement: true,
|
|
117
117
|
observerUpdateEventString: 'LiveBlogWrapper.INSERT_POST',
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { TooltipToggle } from '@financial-times/o3-tooltip'
|
|
3
3
|
|
|
4
|
-
const PartnerContentHeader = ({
|
|
5
|
-
name = 'Default Partner',
|
|
6
|
-
}: {
|
|
7
|
-
name: string
|
|
8
|
-
}) => {
|
|
4
|
+
const PartnerContentHeader = ({ name = 'Partner' }: { name: string }) => {
|
|
9
5
|
return (
|
|
10
6
|
<div className="partner-content-header">
|
|
11
7
|
<div className="partner-content-header__content">
|
|
@@ -15,7 +11,7 @@ const PartnerContentHeader = ({
|
|
|
15
11
|
<div data-o3-brand="core">
|
|
16
12
|
<TooltipToggle
|
|
17
13
|
placement="bottom"
|
|
18
|
-
content={`This
|
|
14
|
+
content={`This content was paid for by ${name} and produced in partnership with the Financial Times Commercial department`}
|
|
19
15
|
infoLabel="More information on Partner Content"
|
|
20
16
|
/>
|
|
21
17
|
</div>
|
|
@@ -15,9 +15,9 @@ const topperGetBreakpointsMap: Partial<
|
|
|
15
15
|
SplitTextTopper(image) {
|
|
16
16
|
switch (image.format) {
|
|
17
17
|
case 'square':
|
|
18
|
-
return { maxWidth: 490 }
|
|
18
|
+
return [{ maxWidth: 490 }, { minWidth: 740, maxWidth: 1220 }]
|
|
19
19
|
case 'standard':
|
|
20
|
-
return { minWidth: 491, maxWidth:
|
|
20
|
+
return { minWidth: 491, maxWidth: 739 }
|
|
21
21
|
case 'wide':
|
|
22
22
|
return {
|
|
23
23
|
normal: { minWidth: 1221, maxWidth: 1440 },
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
class FlourishTopperTracker {
|
|
2
|
+
private startTime: number
|
|
3
|
+
private totalVisibleTime: number
|
|
4
|
+
private timeElapsedSeconds: number | null
|
|
5
|
+
private component: Element | null
|
|
6
|
+
private id: string | null
|
|
7
|
+
private observer: IntersectionObserver | null
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.startTime = 0
|
|
11
|
+
this.totalVisibleTime = 0
|
|
12
|
+
this.timeElapsedSeconds = null
|
|
13
|
+
this.component = document.querySelector(
|
|
14
|
+
'[data-component-type="flourish-topper"]'
|
|
15
|
+
)
|
|
16
|
+
this.id = this.component?.getAttribute('data-component-id') || null
|
|
17
|
+
this.observer = null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
init(): void {
|
|
21
|
+
if (!window.IntersectionObserver || !this.component) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (this.component) {
|
|
26
|
+
this.dispatchEvent('mount')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.observer = new IntersectionObserver(this.onChange.bind(this), {
|
|
30
|
+
threshold: [1.0],
|
|
31
|
+
})
|
|
32
|
+
this.observer.observe(this.component)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private dispatchEvent(action: string): void {
|
|
36
|
+
let component: object = {
|
|
37
|
+
name: 'flourish-topper',
|
|
38
|
+
id: this.id,
|
|
39
|
+
}
|
|
40
|
+
if (this.timeElapsedSeconds) {
|
|
41
|
+
component = { ...component, timeElapsedSeconds: this.timeElapsedSeconds }
|
|
42
|
+
}
|
|
43
|
+
const event = new CustomEvent('oTracking.event', {
|
|
44
|
+
detail: {
|
|
45
|
+
category: 'component',
|
|
46
|
+
action: action,
|
|
47
|
+
component,
|
|
48
|
+
},
|
|
49
|
+
bubbles: true,
|
|
50
|
+
})
|
|
51
|
+
document.body.dispatchEvent(event)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private onChange(changes: IntersectionObserverEntry[]): void {
|
|
55
|
+
changes.forEach((change) => {
|
|
56
|
+
if (change.target !== this.component) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
if (change.isIntersecting || change.intersectionRatio >= 1) {
|
|
60
|
+
this.dispatchEvent('view')
|
|
61
|
+
this.startTime = performance.now()
|
|
62
|
+
}
|
|
63
|
+
if (!change.isIntersecting || change.intersectionRatio === 0) {
|
|
64
|
+
this.totalVisibleTime = performance.now() - this.startTime
|
|
65
|
+
this.timeElapsedSeconds = parseFloat(
|
|
66
|
+
(this.totalVisibleTime / 1000).toFixed(2)
|
|
67
|
+
)
|
|
68
|
+
this.dispatchEvent('stop-view')
|
|
69
|
+
this.totalVisibleTime = 0
|
|
70
|
+
this.timeElapsedSeconds = null
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
disconnect(): void {
|
|
76
|
+
if (this.observer && this.component) {
|
|
77
|
+
this.observer.unobserve(this.component)
|
|
78
|
+
this.observer.disconnect()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { FlourishTopperTracker }
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { FlourishTopperTracker } from '../../client'
|
|
2
|
+
|
|
3
|
+
// const CustomEventMock = jest.fn()
|
|
4
|
+
// global.CustomEvent = CustomEventMock
|
|
5
|
+
|
|
6
|
+
describe('FlourishTopperTracker', () => {
|
|
7
|
+
let mockIntersectionObserver: jest.Mock
|
|
8
|
+
let mockObserve: jest.Mock
|
|
9
|
+
let mockUnobserve: jest.Mock
|
|
10
|
+
let mockDisconnect: jest.Mock
|
|
11
|
+
let mockComponent: HTMLElement
|
|
12
|
+
let dispatchEventSpy: jest.SpyInstance
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockObserve = jest.fn()
|
|
16
|
+
mockUnobserve = jest.fn()
|
|
17
|
+
mockDisconnect = jest.fn()
|
|
18
|
+
|
|
19
|
+
mockIntersectionObserver = jest.fn(() => {
|
|
20
|
+
return {
|
|
21
|
+
observe: mockObserve,
|
|
22
|
+
unobserve: mockUnobserve,
|
|
23
|
+
disconnect: mockDisconnect,
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
window.IntersectionObserver = mockIntersectionObserver
|
|
28
|
+
|
|
29
|
+
mockComponent = document.createElement('div')
|
|
30
|
+
mockComponent.setAttribute('data-component-type', 'flourish-topper')
|
|
31
|
+
mockComponent.setAttribute('data-component-id', 'test-id')
|
|
32
|
+
document.body.appendChild(mockComponent)
|
|
33
|
+
|
|
34
|
+
jest.spyOn(document, 'querySelector').mockReturnValue(mockComponent)
|
|
35
|
+
jest
|
|
36
|
+
.spyOn(performance, 'now')
|
|
37
|
+
.mockImplementationOnce(() => 1)
|
|
38
|
+
.mockImplementationOnce(() => 12344)
|
|
39
|
+
dispatchEventSpy = jest.spyOn(document.body, 'dispatchEvent')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
jest.clearAllMocks()
|
|
44
|
+
document.body.removeChild(mockComponent)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should initialise and observe the component', () => {
|
|
48
|
+
const tracker = new FlourishTopperTracker()
|
|
49
|
+
tracker.init()
|
|
50
|
+
|
|
51
|
+
expect(mockIntersectionObserver).toHaveBeenCalled()
|
|
52
|
+
expect(mockObserve).toHaveBeenCalledWith(mockComponent)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should dispatch a mount event on intialisation', () => {
|
|
56
|
+
const tracker = new FlourishTopperTracker()
|
|
57
|
+
tracker.init()
|
|
58
|
+
|
|
59
|
+
const mountEvent = dispatchEventSpy.mock.calls[0][0] as CustomEvent
|
|
60
|
+
expect(mountEvent.type).toBe('oTracking.event')
|
|
61
|
+
expect(mountEvent.detail).toEqual({
|
|
62
|
+
category: 'component',
|
|
63
|
+
action: 'mount',
|
|
64
|
+
component: {
|
|
65
|
+
name: 'flourish-topper',
|
|
66
|
+
id: 'test-id',
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should handle component visibility changes', () => {
|
|
72
|
+
const tracker = new FlourishTopperTracker()
|
|
73
|
+
tracker.init()
|
|
74
|
+
|
|
75
|
+
const mockChanges: IntersectionObserverEntry[] = [
|
|
76
|
+
{
|
|
77
|
+
target: mockComponent,
|
|
78
|
+
isIntersecting: true,
|
|
79
|
+
intersectionRatio: 1.0,
|
|
80
|
+
boundingClientRect: {} as DOMRectReadOnly,
|
|
81
|
+
intersectionRect: {} as DOMRectReadOnly,
|
|
82
|
+
rootBounds: null,
|
|
83
|
+
time: 1,
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
tracker['onChange'](mockChanges)
|
|
88
|
+
|
|
89
|
+
const viewEvent = dispatchEventSpy.mock.calls[1][0] as CustomEvent
|
|
90
|
+
expect(viewEvent.type).toBe('oTracking.event')
|
|
91
|
+
expect(viewEvent.detail).toEqual({
|
|
92
|
+
category: 'component',
|
|
93
|
+
action: 'view',
|
|
94
|
+
component: {
|
|
95
|
+
name: 'flourish-topper',
|
|
96
|
+
id: 'test-id',
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const mockChangesAfter: IntersectionObserverEntry[] = [
|
|
101
|
+
{
|
|
102
|
+
target: mockComponent,
|
|
103
|
+
isIntersecting: false,
|
|
104
|
+
intersectionRatio: 0.0,
|
|
105
|
+
boundingClientRect: {} as DOMRectReadOnly,
|
|
106
|
+
intersectionRect: {} as DOMRectReadOnly,
|
|
107
|
+
rootBounds: null,
|
|
108
|
+
time: 12.3444,
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
tracker['onChange'](mockChangesAfter)
|
|
113
|
+
|
|
114
|
+
const stopViewEvent = dispatchEventSpy.mock.calls[2][0] as CustomEvent
|
|
115
|
+
expect(stopViewEvent.type).toBe('oTracking.event')
|
|
116
|
+
expect(stopViewEvent.detail).toEqual({
|
|
117
|
+
category: 'component',
|
|
118
|
+
action: 'stop-view',
|
|
119
|
+
component: {
|
|
120
|
+
name: 'flourish-topper',
|
|
121
|
+
id: 'test-id',
|
|
122
|
+
timeElapsedSeconds: 12.34,
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should disconnect the observer', () => {
|
|
128
|
+
const tracker = new FlourishTopperTracker()
|
|
129
|
+
tracker.init()
|
|
130
|
+
tracker.disconnect()
|
|
131
|
+
|
|
132
|
+
expect(mockUnobserve).toHaveBeenCalledWith(mockComponent)
|
|
133
|
+
expect(mockDisconnect).toHaveBeenCalled()
|
|
134
|
+
})
|
|
135
|
+
})
|