@financial-times/cp-content-pipeline-ui 6.9.2 → 6.10.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/lib/components/BackToTopButton/client/index.d.ts +1 -0
  3. package/lib/components/BackToTopButton/client/index.js +7 -1
  4. package/lib/components/BackToTopButton/client/index.js.map +1 -1
  5. package/lib/components/Body/index.test.js +9 -0
  6. package/lib/components/Body/index.test.js.map +1 -1
  7. package/lib/components/Clip/client/index.d.ts +2 -0
  8. package/lib/components/Clip/client/index.js +40 -27
  9. package/lib/components/Clip/client/index.js.map +1 -1
  10. package/lib/components/Clip/components/ClipTag.js +1 -1
  11. package/lib/components/Clip/components/ClipTag.js.map +1 -1
  12. package/lib/components/Clip/components/Container.js +2 -2
  13. package/lib/components/Clip/components/Container.js.map +1 -1
  14. package/lib/components/Clip/template/index.js +0 -1
  15. package/lib/components/Clip/template/index.js.map +1 -1
  16. package/lib/components/Clip/test/index.spec.js +21 -9
  17. package/lib/components/Clip/test/index.spec.js.map +1 -1
  18. package/lib/components/Expander/client/index.d.ts +49 -0
  19. package/lib/components/Expander/client/index.js +124 -0
  20. package/lib/components/Expander/client/index.js.map +1 -0
  21. package/lib/components/Expander/index.d.ts +15 -0
  22. package/lib/components/Expander/index.js +27 -0
  23. package/lib/components/Expander/index.js.map +1 -0
  24. package/lib/components/Expander/test/client/index.spec.d.ts +1 -0
  25. package/lib/components/Expander/test/client/index.spec.js +103 -0
  26. package/lib/components/Expander/test/client/index.spec.js.map +1 -0
  27. package/lib/components/Expander/test/index.spec.d.ts +1 -0
  28. package/lib/components/Expander/test/index.spec.js +57 -0
  29. package/lib/components/Expander/test/index.spec.js.map +1 -0
  30. package/lib/components/Expander/test/snapshot.spec.d.ts +1 -0
  31. package/lib/components/Expander/test/snapshot.spec.js +63 -0
  32. package/lib/components/Expander/test/snapshot.spec.js.map +1 -0
  33. package/lib/components/ImageSet/index.js +1 -1
  34. package/lib/components/ImageSet/index.js.map +1 -1
  35. package/lib/components/LiveBlogPost/client/index.d.ts +4 -0
  36. package/lib/components/LiveBlogPost/client/index.js +19 -0
  37. package/lib/components/LiveBlogPost/client/index.js.map +1 -0
  38. package/lib/components/LiveBlogPost/index.js +9 -21
  39. package/lib/components/LiveBlogPost/index.js.map +1 -1
  40. package/lib/components/LiveBlogWrapper/index.js +1 -1
  41. package/lib/components/LiveBlogWrapper/index.js.map +1 -1
  42. package/lib/components/Recommended/index.js +1 -1
  43. package/lib/components/Recommended/index.js.map +1 -1
  44. package/lib/components/RichText/index.d.ts +1 -1
  45. package/lib/components/Table/index.js +1 -1
  46. package/lib/components/Table/index.js.map +1 -1
  47. package/lib/components/Video/index.js +1 -1
  48. package/lib/components/Video/index.js.map +1 -1
  49. package/lib/components/YoutubeVideo/index.js +1 -1
  50. package/lib/components/YoutubeVideo/index.js.map +1 -1
  51. package/lib/extensions/scrollIntoView.d.ts +10 -0
  52. package/lib/extensions/scrollIntoView.js +32 -0
  53. package/lib/extensions/scrollIntoView.js.map +1 -0
  54. package/lib/stories/Clip.stories.d.ts +2 -1
  55. package/lib/stories/Clip.stories.js +5 -5
  56. package/lib/stories/Clip.stories.js.map +1 -1
  57. package/lib/stories/Expander.stories.d.ts +54 -0
  58. package/lib/stories/Expander.stories.js +142 -0
  59. package/lib/stories/Expander.stories.js.map +1 -0
  60. package/package.json +2 -5
  61. package/src/components/BackToTopButton/client/index.tsx +8 -1
  62. package/src/components/Body/__snapshots__/index.test.tsx.snap +55 -5
  63. package/src/components/Body/index.test.tsx +9 -0
  64. package/src/components/Clip/client/index.ts +68 -26
  65. package/src/components/Clip/client/main.scss +27 -12
  66. package/src/components/Clip/components/ClipTag.tsx +0 -1
  67. package/src/components/Clip/components/Container.tsx +10 -3
  68. package/src/components/Clip/template/index.ts +0 -1
  69. package/src/components/Clip/test/__snapshots__/snapshot.spec.tsx.snap +8 -16
  70. package/src/components/Clip/test/index.spec.ts +33 -7
  71. package/src/components/Expander/client/index.ts +201 -0
  72. package/src/components/Expander/client/main.scss +162 -0
  73. package/src/components/Expander/index.tsx +74 -0
  74. package/src/components/Expander/test/__snapshots__/snapshot.spec.tsx.snap +221 -0
  75. package/src/components/Expander/test/client/index.spec.tsx +129 -0
  76. package/src/components/Expander/test/index.spec.tsx +77 -0
  77. package/src/components/Expander/test/snapshot.spec.tsx +73 -0
  78. package/src/components/ImageSet/index.tsx +1 -0
  79. package/src/components/LiveBlogPost/client/index.ts +16 -0
  80. package/src/components/LiveBlogPost/index.tsx +29 -43
  81. package/src/components/LiveBlogWrapper/index.tsx +1 -0
  82. package/src/components/Recommended/index.tsx +1 -0
  83. package/src/components/Table/index.tsx +1 -0
  84. package/src/components/Video/index.tsx +4 -1
  85. package/src/components/YoutubeVideo/index.tsx +4 -1
  86. package/src/extensions/scrollIntoView.ts +38 -0
  87. package/src/stories/Clip.stories.tsx +3 -2
  88. package/src/stories/Expander.stories.scss +3 -0
  89. package/src/stories/Expander.stories.tsx +159 -0
  90. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,221 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Expander Snapshot component rendered on server default render 1`] = `
4
+ <div
5
+ className="cp-expander cp-expander--all-resolutions cp-expander--not-initialised"
6
+ data-component="expander"
7
+ data-state="collapsed"
8
+ id="test-id__container"
9
+ >
10
+ <div
11
+ className="cp-expander-content"
12
+ id="test-id"
13
+ >
14
+ <div
15
+ className="cp-expander__expand"
16
+ >
17
+ <a
18
+ aria-controls="test-id"
19
+ aria-expanded={false}
20
+ aria-hidden={false}
21
+ className="cp-expander__link"
22
+ data-action="expand"
23
+ data-trackable="truncated-post"
24
+ data-trackable-context-truncated-id="test-id"
25
+ data-trackable-context-truncated-post="expand"
26
+ href="#test-id"
27
+ >
28
+ Expand
29
+ </a>
30
+ </div>
31
+ <div>
32
+ Children 1
33
+ </div>
34
+ <div>
35
+ Children 2
36
+ </div>
37
+ <div
38
+ className="cp-expander__collapse"
39
+ >
40
+ <a
41
+ aria-controls="test-id"
42
+ aria-expanded={false}
43
+ aria-hidden={true}
44
+ className="cp-expander__link"
45
+ data-action="collapse"
46
+ data-trackable="truncated-post"
47
+ data-trackable-context-truncated-id="test-id"
48
+ data-trackable-context-truncated-post="collapse"
49
+ href="#test-id__container"
50
+ >
51
+ Collapse
52
+ </a>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ `;
57
+
58
+ exports[`Expander Snapshot component rendered on server only on mobile 1`] = `
59
+ <div
60
+ className="cp-expander cp-expander--mobile-only cp-expander--not-initialised"
61
+ data-component="expander"
62
+ data-state="collapsed"
63
+ id="test-id__container"
64
+ >
65
+ <div
66
+ className="cp-expander-content"
67
+ id="test-id"
68
+ >
69
+ <div
70
+ className="cp-expander__expand"
71
+ >
72
+ <a
73
+ aria-controls="test-id"
74
+ aria-expanded={false}
75
+ aria-hidden={false}
76
+ className="cp-expander__link"
77
+ data-action="expand"
78
+ data-trackable="truncated-post"
79
+ data-trackable-context-truncated-id="test-id"
80
+ data-trackable-context-truncated-post="expand"
81
+ href="#test-id"
82
+ >
83
+ Expand
84
+ </a>
85
+ </div>
86
+ <div>
87
+ Children 1
88
+ </div>
89
+ <div>
90
+ Children 2
91
+ </div>
92
+ <div
93
+ className="cp-expander__collapse"
94
+ >
95
+ <a
96
+ aria-controls="test-id"
97
+ aria-expanded={false}
98
+ aria-hidden={true}
99
+ className="cp-expander__link"
100
+ data-action="collapse"
101
+ data-trackable="truncated-post"
102
+ data-trackable-context-truncated-id="test-id"
103
+ data-trackable-context-truncated-post="collapse"
104
+ href="#test-id__container"
105
+ >
106
+ Collapse
107
+ </a>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ `;
112
+
113
+ exports[`Expander Snapshot component rendered on server with different labels 1`] = `
114
+ <div
115
+ className="cp-expander cp-expander--all-resolutions cp-expander--not-initialised"
116
+ data-component="expander"
117
+ data-state="collapsed"
118
+ id="test-id__container"
119
+ >
120
+ <div
121
+ className="cp-expander-content"
122
+ id="test-id"
123
+ >
124
+ <div
125
+ className="cp-expander__expand"
126
+ >
127
+ <a
128
+ aria-controls="test-id"
129
+ aria-expanded={false}
130
+ aria-hidden={false}
131
+ className="cp-expander__link"
132
+ data-action="expand"
133
+ data-trackable="truncated-post"
134
+ data-trackable-context-truncated-id="test-id"
135
+ data-trackable-context-truncated-post="expand"
136
+ href="#test-id"
137
+ >
138
+ Open
139
+ </a>
140
+ </div>
141
+ <div>
142
+ Children 1
143
+ </div>
144
+ <div>
145
+ Children 2
146
+ </div>
147
+ <div
148
+ className="cp-expander__collapse"
149
+ >
150
+ <a
151
+ aria-controls="test-id"
152
+ aria-expanded={false}
153
+ aria-hidden={true}
154
+ className="cp-expander__link"
155
+ data-action="collapse"
156
+ data-trackable="truncated-post"
157
+ data-trackable-context-truncated-id="test-id"
158
+ data-trackable-context-truncated-post="collapse"
159
+ href="#test-id__container"
160
+ >
161
+ Close
162
+ </a>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ `;
167
+
168
+ exports[`Expander Snapshot component rendered on server with different state 1`] = `
169
+ <div
170
+ className="cp-expander cp-expander--all-resolutions cp-expander--not-initialised"
171
+ data-component="expander"
172
+ data-state="expanded"
173
+ id="test-id__container"
174
+ >
175
+ <div
176
+ className="cp-expander-content"
177
+ id="test-id"
178
+ >
179
+ <div
180
+ className="cp-expander__expand"
181
+ >
182
+ <a
183
+ aria-controls="test-id"
184
+ aria-expanded={true}
185
+ aria-hidden={true}
186
+ className="cp-expander__link"
187
+ data-action="expand"
188
+ data-trackable="truncated-post"
189
+ data-trackable-context-truncated-id="test-id"
190
+ data-trackable-context-truncated-post="expand"
191
+ href="#test-id"
192
+ >
193
+ Expand
194
+ </a>
195
+ </div>
196
+ <div>
197
+ Children 1
198
+ </div>
199
+ <div>
200
+ Children 2
201
+ </div>
202
+ <div
203
+ className="cp-expander__collapse"
204
+ >
205
+ <a
206
+ aria-controls="test-id"
207
+ aria-expanded={true}
208
+ aria-hidden={false}
209
+ className="cp-expander__link"
210
+ data-action="collapse"
211
+ data-trackable="truncated-post"
212
+ data-trackable-context-truncated-id="test-id"
213
+ data-trackable-context-truncated-post="collapse"
214
+ href="#test-id__container"
215
+ >
216
+ Collapse
217
+ </a>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ `;
@@ -0,0 +1,129 @@
1
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
+ // @ts-nocheck
3
+ import React from 'react'
4
+
5
+ import Expander, { ExpanderProps } from '../../index'
6
+ import { ExpanderClient } from '../../client'
7
+ import { render, screen } from '@testing-library/react'
8
+
9
+ //mock IntersectionObserver
10
+ class IntersectionObserver {
11
+ //mock constructor
12
+ constructor(callback) {
13
+ this.callback = callback
14
+ }
15
+ observe() {
16
+ this.active = true
17
+ }
18
+ disconnect() {
19
+ this.active = false
20
+ }
21
+
22
+ intersects() {
23
+ if (this.active) {
24
+ this.callback([{ isIntersecting: true }])
25
+ }
26
+ }
27
+ }
28
+
29
+ //mock scrollIntoView
30
+ window.HTMLElement.prototype.scrollIntoView = jest.fn()
31
+
32
+ describe('Expander client test', () => {
33
+ beforeEach(() => {
34
+ window.IntersectionObserver = IntersectionObserver
35
+ })
36
+
37
+ it('find expanders markup and initialises it', () => {
38
+ const props: ExpanderProps = {
39
+ id: 'test-id',
40
+ }
41
+ render(
42
+ <Expander {...props}>
43
+ <div>Children 1</div>
44
+ </Expander>
45
+ )
46
+
47
+ const expanders = ExpanderClient.init()
48
+ expect(expanders.length).toBe(1)
49
+ expect(
50
+ document.querySelectorAll("[data-component='expander']").length
51
+ ).toBe(1)
52
+ expect(expanders[0].container.id).toBe('test-id__container')
53
+ expect(
54
+ expanders[0].container.classList.contains('cp-expander--initialised')
55
+ ).toBe(true)
56
+ })
57
+
58
+ it('does not initialise expanders twice', () => {
59
+ const props: ExpanderProps = {
60
+ id: 'test-id',
61
+ }
62
+ render(
63
+ <Expander {...props}>
64
+ <div>Children 1</div>
65
+ </Expander>
66
+ )
67
+ ExpanderClient.init()
68
+ const expanders = ExpanderClient.init()
69
+
70
+ expect(expanders.length).toBe(0)
71
+ expect(
72
+ document.querySelectorAll("[data-component='expander']").length
73
+ ).toBe(1)
74
+ })
75
+
76
+ it('expands and collapse', async () => {
77
+ const props: ExpanderProps = {
78
+ id: 'test-id',
79
+ }
80
+ render(
81
+ <Expander {...props}>
82
+ <div>Children 1</div>
83
+ </Expander>
84
+ )
85
+ const expanders = ExpanderClient.init()
86
+ const expanderButton = screen.getByText('Expand')
87
+ const collapserButton = screen.getByText('Collapse')
88
+ expect(expanderButton.getAttribute('aria-expanded')).toBe('false')
89
+ expect(expanderButton.getAttribute('aria-hidden')).toBe('false')
90
+ expect(collapserButton.getAttribute('aria-expanded')).toBe('false')
91
+ expect(collapserButton.getAttribute('aria-hidden')).toBe('true')
92
+
93
+ await expanderButton.click()
94
+ expect(expanderButton.getAttribute('aria-expanded')).toBe('true')
95
+ expect(expanders[0].container?.getAttribute('data-state')).toBe('expanded')
96
+ expect(expanderButton.getAttribute('aria-expanded')).toBe('true')
97
+ expect(expanderButton.getAttribute('aria-hidden')).toBe('true')
98
+ expect(collapserButton.getAttribute('aria-expanded')).toBe('true')
99
+ expect(collapserButton.getAttribute('aria-hidden')).toBe('false')
100
+
101
+ await collapserButton.click()
102
+ expect(expanders[0].container?.getAttribute('data-state')).toBe('collapsed')
103
+ expect(expanderButton.getAttribute('aria-expanded')).toBe('false')
104
+ expect(expanderButton.getAttribute('aria-hidden')).toBe('false')
105
+ expect(collapserButton.getAttribute('aria-expanded')).toBe('false')
106
+ expect(collapserButton.getAttribute('aria-hidden')).toBe('true')
107
+ })
108
+
109
+ it('track entire read only once', () => {
110
+ const props: ExpanderProps = {
111
+ id: 'test-id',
112
+ }
113
+ render(
114
+ <Expander {...props}>
115
+ <div>Children 1</div>
116
+ </Expander>
117
+ )
118
+
119
+ const expanders = ExpanderClient.init()
120
+ const spy = jest.spyOn(document.body, 'dispatchEvent')
121
+ spy.mockImplementation((event) => {
122
+ expect(event.detail.action).toBe('entire_read')
123
+ })
124
+ expanders[0].trackingEntireRead.observer.intersects()
125
+ expect(spy).toHaveBeenCalledTimes(1)
126
+ expanders[0].trackingEntireRead.observer.intersects()
127
+ expect(spy).toHaveBeenCalledTimes(1)
128
+ })
129
+ })
@@ -0,0 +1,77 @@
1
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
+ // @ts-nocheck
3
+ import React from 'react'
4
+
5
+ import Expander, { ExpanderProps } from '../index'
6
+ import { render, screen } from '@testing-library/react'
7
+
8
+ describe('Expander template', () => {
9
+ it('renders', () => {
10
+ const props: ExpanderProps = {
11
+ id: 'test-id',
12
+ }
13
+ const tree = render(
14
+ <Expander {...props}>
15
+ <div>Children 1</div>
16
+ <div>Children 2</div>
17
+ </Expander>
18
+ )
19
+ expect(
20
+ tree.container.querySelector('[data-component="expander"]')
21
+ ).toBeTruthy()
22
+ expect(
23
+ tree.container.querySelector('[data-state="collapsed"]')
24
+ ).toBeTruthy()
25
+ expect(
26
+ tree.container.querySelector('.cp-expander--not-initialised')
27
+ ).toBeTruthy()
28
+ expect(screen.getByText('Expand')).toBeTruthy()
29
+ expect(screen.getByText('Collapse')).toBeTruthy()
30
+ expect(screen.getByText('Children 1')).toBeTruthy()
31
+ expect(screen.getByText('Children 2')).toBeTruthy()
32
+ })
33
+
34
+ it('with different labels', () => {
35
+ const props: ExpanderProps = {
36
+ id: 'test-id',
37
+ collapseLabel: 'Close',
38
+ expandLabel: 'Open',
39
+ }
40
+ render(
41
+ <Expander {...props}>
42
+ <div>Children</div>
43
+ </Expander>
44
+ )
45
+
46
+ expect(screen.getByText('Open')).toBeTruthy()
47
+ expect(screen.getByText('Close')).toBeTruthy()
48
+ })
49
+
50
+ it('with different state', () => {
51
+ const props: ExpanderProps = {
52
+ id: 'test-id',
53
+ state: 'expanded',
54
+ }
55
+ const tree = render(
56
+ <Expander {...props}>
57
+ <div>Children</div>
58
+ </Expander>
59
+ )
60
+ expect(tree.container.querySelector('[data-state="expanded"]')).toBeTruthy()
61
+ })
62
+
63
+ it('add class for only mobile', () => {
64
+ const props: ExpanderProps = {
65
+ id: 'test-id',
66
+ onlyMobile: true,
67
+ }
68
+ const tree = render(
69
+ <Expander {...props}>
70
+ <div>Children</div>
71
+ </Expander>
72
+ )
73
+ expect(
74
+ tree.container.querySelector('.cp-expander--mobile-only')
75
+ ).toBeTruthy()
76
+ })
77
+ })
@@ -0,0 +1,73 @@
1
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
+ // @ts-nocheck
3
+ import React from 'react'
4
+ import renderer from 'react-test-renderer'
5
+ import Expander, { ExpanderProps } from '../index'
6
+
7
+ describe('Expander Snapshot', () => {
8
+ describe('component rendered on server', () => {
9
+ it('default render', () => {
10
+ const props: ExpanderProps = {
11
+ id: 'test-id',
12
+ }
13
+ const tree = renderer
14
+ .create(
15
+ <Expander {...props}>
16
+ <div>Children 1</div>
17
+ <div>Children 2</div>
18
+ </Expander>
19
+ )
20
+ .toJSON()
21
+ expect(tree).toMatchSnapshot()
22
+ })
23
+
24
+ it('only on mobile', () => {
25
+ const props: ExpanderProps = {
26
+ id: 'test-id',
27
+ onlyMobile: true,
28
+ }
29
+ const tree = renderer
30
+ .create(
31
+ <Expander {...props}>
32
+ <div>Children 1</div>
33
+ <div>Children 2</div>
34
+ </Expander>
35
+ )
36
+ .toJSON()
37
+ expect(tree).toMatchSnapshot()
38
+ })
39
+
40
+ it('with different labels', () => {
41
+ const props: ExpanderProps = {
42
+ id: 'test-id',
43
+ collapseLabel: 'Close',
44
+ expandLabel: 'Open',
45
+ }
46
+ const tree = renderer
47
+ .create(
48
+ <Expander {...props}>
49
+ <div>Children 1</div>
50
+ <div>Children 2</div>
51
+ </Expander>
52
+ )
53
+ .toJSON()
54
+ expect(tree).toMatchSnapshot()
55
+ })
56
+
57
+ it('with different state', () => {
58
+ const props: ExpanderProps = {
59
+ id: 'test-id',
60
+ state: 'expanded',
61
+ }
62
+ const tree = renderer
63
+ .create(
64
+ <Expander {...props}>
65
+ <div>Children 1</div>
66
+ <div>Children 2</div>
67
+ </Expander>
68
+ )
69
+ .toJSON()
70
+ expect(tree).toMatchSnapshot()
71
+ })
72
+ })
73
+ })
@@ -212,6 +212,7 @@ export default function ImageSet(
212
212
  'n-content-image'
213
213
  }
214
214
  style={figureWidth(imageSet.picture)}
215
+ data-component="image-set"
215
216
  >
216
217
  <picture>
217
218
  <Sources images={imageSet.picture.images} />
@@ -0,0 +1,16 @@
1
+ import { ExpanderClient } from '../../Expander/client'
2
+
3
+ export class LiveBlogPostClient {
4
+ static init() {
5
+ const expanders = ExpanderClient.init({
6
+ trackingData: { category: 'live-blog-post' },
7
+ })
8
+ expanders.forEach((expander) => {
9
+ expander.container?.addEventListener('expander:collapsed', () => {
10
+ expander?.container?.parentElement?.parentElement?.scrollIntoView()
11
+ })
12
+ })
13
+ }
14
+ }
15
+
16
+ export default LiveBlogPostClient
@@ -2,10 +2,13 @@ import React from 'react'
2
2
  import ShareButtons from './ShareButtons'
3
3
  import Timestamp from './Timestamp'
4
4
  import RichText from '../RichText'
5
+ import Expander from '../Expander'
5
6
  import classnames from 'classnames'
6
7
  import Headshot from '../Headshot'
7
8
  import Byline from '../Byline'
8
9
  import { StructuredTreeFragment } from '@financial-times/cp-content-pipeline-client'
10
+ import parse from 'html-react-parser'
11
+
9
12
  /* eslint-disable @typescript-eslint/no-explicit-any */
10
13
  //TODO: CI-1975 remove "any" types
11
14
  // this has been ported from x-dash, and a lot of what is expected into these arguments will take a lot of digging
@@ -14,6 +17,26 @@ type indicators = {
14
17
  isOpinion?: boolean
15
18
  }
16
19
 
20
+ const TruncatedPost = ({ id, children }: { id: string; children: any }) => {
21
+ return (
22
+ <div
23
+ data-trackable="truncated-post"
24
+ data-trackable-context-post={id}
25
+ data-trackable-category="live-blog"
26
+ className="x-live-blog-post__body n-content-body article--body"
27
+ >
28
+ <Expander
29
+ expandLabel="Expand post"
30
+ collapseLabel="Collapse post"
31
+ onlyMobile={true}
32
+ id={`truncated-${id}`}
33
+ >
34
+ {children}
35
+ </Expander>
36
+ </div>
37
+ )
38
+ }
39
+
17
40
  export type LiveBlogPostProps = {
18
41
  id: string
19
42
  postId: string // Remove once wordpress is no longer in use
@@ -39,23 +62,6 @@ export type LiveBlogPostProps = {
39
62
  authors: Array<any>
40
63
  }
41
64
 
42
- const truncatedSelector = () => {
43
- const prefix = '.x-live-blog-post__body >'
44
- const selectors = [
45
- 'p',
46
- 'blockquote',
47
- 'ul',
48
- '.o-table-container',
49
- '.n-content-recommended--single-story',
50
- "[data-layout-name='card']",
51
- '.n-content-big-number',
52
- 'hr',
53
- '.n-content-tweet',
54
- "[data-layout-name='timeline']",
55
- ]
56
- return selectors.map((item) => `${prefix} ${item}`).join(',')
57
- }
58
-
59
65
  const LiveBlogPost = ({
60
66
  id,
61
67
  postId, // Remove once wordpress is no longer in use
@@ -78,22 +84,18 @@ const LiveBlogPost = ({
78
84
  const showBreakingNewsLabel = standout.breakingNews || isBreakingNews
79
85
 
80
86
  let postBody
81
-
82
87
  if (body && 'structured' in body) {
83
88
  // Content comes from cp-content-pipeline-api
84
89
  postBody = (
85
- <div className="x-live-blog-post__body n-content-body article--body">
90
+ <TruncatedPost id={id}>
86
91
  <RichText structuredContent={body.structured} />
87
- </div>
92
+ </TruncatedPost>
88
93
  )
89
94
  } else {
95
+ const elements = bodyHTML || content ? parse(bodyHTML || content) : []
96
+
90
97
  // Content comes from next-es or wordpress
91
- postBody = (
92
- <div
93
- className="x-live-blog-post__body n-content-body article--body"
94
- dangerouslySetInnerHTML={{ __html: bodyHTML || content }}
95
- />
96
- )
98
+ postBody = <TruncatedPost id={id}>{elements}</TruncatedPost>
97
99
  }
98
100
 
99
101
  const author = authors?.[0] ?? null
@@ -155,23 +157,7 @@ const LiveBlogPost = ({
155
157
  <div className="x-live-blog-post__breaking-news">Breaking news</div>
156
158
  )}
157
159
  {title && <h2 className="x-live-blog-post__title">{title}</h2>}
158
- {isPinned ? (
159
- <div
160
- data-o-component="o-expander"
161
- className="o-expander"
162
- data-o-expander-shrink-to="1"
163
- data-o-expander-item-selector={truncatedSelector()}
164
- data-o-expander-collapsed-toggle-text={'Expand post'}
165
- data-o-expander-expanded-toggle-text={'Collapse post'}
166
- >
167
- <div className="o-expander__content">{postBody}</div>
168
- <a className="o-expander__toggle o-expander__text--custom">
169
- <span className="o-expander__visually-hidden">&nbsp;</span>
170
- </a>
171
- </div>
172
- ) : (
173
- postBody
174
- )}
160
+ {postBody}
175
161
  <div className="x-live-blog-post__controls">
176
162
  {showShareButtons && (
177
163
  <ShareButtons
@@ -194,6 +194,7 @@ class BaseLiveBlogWrapper extends Component<
194
194
  className="x-live-blog-wrapper"
195
195
  data-live-blog-wrapper-id={id}
196
196
  ref={liveBlogWrapperElementRef}
197
+ data-component="live-blog-wrapper"
197
198
  >
198
199
  {postElements}
199
200
  </div>
@@ -25,6 +25,7 @@ export default function Recommended({
25
25
  className={classnames('n-content-recommended--single-story', {
26
26
  'n-content-recommended--inset': !isInLiveBlog,
27
27
  })}
28
+ data-component="recommended"
28
29
  >
29
30
  {!isInLiveBlog && (
30
31
  <p className="n-content-recommended__title">{heading}</p>
@@ -22,6 +22,7 @@ export default function Table({
22
22
  'o-table-container--contracted': Boolean(collapseAfterHowManyRows),
23
23
  })}
24
24
  data-layout-width={layoutWidth}
25
+ data-component="table"
25
26
  >
26
27
  <div
27
28
  className={classNames('o-table-overlay-wrapper', {
@@ -8,7 +8,10 @@ export default function Video({
8
8
  title,
9
9
  }: VideoFragment & ContentTreeWorkarounds.Video) {
10
10
  return (
11
- <div className="n-content-video n-content-video--internal">
11
+ <div
12
+ className="n-content-video n-content-video--internal"
13
+ data-component="video"
14
+ >
12
15
  <div
13
16
  className="n-content-video__placeholder"
14
17
  data-o-component="o-video"
@@ -13,7 +13,10 @@ export default function YoutubeVideo({
13
13
  }
14
14
 
15
15
  return (
16
- <div className="n-content-video n-content-video--youtube">
16
+ <div
17
+ className="n-content-video n-content-video--youtube"
18
+ data-component="youtube-video"
19
+ >
17
20
  <div className="n-content-video__placeholder">
18
21
  <iframe
19
22
  className="n-content-video__embedded"