@times-components/ts-components 1.42.0 → 1.43.2

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 (22) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/components/recommended-articles/RecommendedArticles.d.ts +1 -1
  3. package/dist/components/recommended-articles/RecommendedArticles.js +16 -3
  4. package/dist/components/recommended-articles/RecommendedArticles.stories.d.ts +1 -5
  5. package/dist/components/recommended-articles/RecommendedArticles.stories.js +5 -9
  6. package/dist/components/recommended-articles/RecommendedFetch.d.ts +2 -1
  7. package/dist/components/recommended-articles/RecommendedFetch.js +18 -3
  8. package/dist/components/recommended-articles/__tests__/RecommendedArticles.test.js +80 -9
  9. package/dist/components/recommended-articles/__tests__/RecommendedFetch.test.js +2 -2
  10. package/dist/components/recommended-articles/helpers.d.ts +5 -0
  11. package/dist/components/recommended-articles/helpers.js +6 -0
  12. package/package.json +3 -3
  13. package/rnw.js +1 -1
  14. package/src/components/latest-from-section/__tests__/__snapshots__/LatestFromSection.test.tsx.snap +6 -12
  15. package/src/components/recommended-articles/RecommendedArticles.stories.tsx +4 -9
  16. package/src/components/recommended-articles/RecommendedArticles.tsx +24 -4
  17. package/src/components/recommended-articles/RecommendedFetch.tsx +33 -7
  18. package/src/components/recommended-articles/__tests__/RecommendedArticles.test.tsx +124 -11
  19. package/src/components/recommended-articles/__tests__/RecommendedFetch.test.tsx +2 -1
  20. package/src/components/recommended-articles/__tests__/__snapshots__/RecommendedArticles.test.tsx.snap +57 -5
  21. package/src/components/recommended-articles/helpers.ts +5 -0
  22. package/src/types/externs.d.ts +3 -2
@@ -66,12 +66,11 @@ exports[`<LatestFromSection> renders 1`] = `
66
66
  >
67
67
  <div
68
68
  class="tc-view__TcView-nuazoi-0 fPjBcr"
69
- data-adam-test="item2"
70
- style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
69
+ style="align-items: flex-start; display: block; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
71
70
  >
72
71
  <div
73
72
  class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
74
- data-adam-test="item1"
73
+ style="flex: 1; margin-bottom: 10px; min-width: 100%;"
75
74
  >
76
75
  <div
77
76
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -114,7 +113,6 @@ exports[`<LatestFromSection> renders 1`] = `
114
113
  </div>
115
114
  <div
116
115
  class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
117
- data-adam-test="item3"
118
116
  >
119
117
  <div
120
118
  class="css-view-1dbjc4n"
@@ -214,12 +212,11 @@ exports[`<LatestFromSection> renders 1`] = `
214
212
  >
215
213
  <div
216
214
  class="tc-view__TcView-nuazoi-0 fPjBcr"
217
- data-adam-test="item2"
218
- style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
215
+ style="align-items: flex-start; display: block; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
219
216
  >
220
217
  <div
221
218
  class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
222
- data-adam-test="item1"
219
+ style="flex: 1; margin-bottom: 10px; min-width: 100%;"
223
220
  >
224
221
  <div
225
222
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -262,7 +259,6 @@ exports[`<LatestFromSection> renders 1`] = `
262
259
  </div>
263
260
  <div
264
261
  class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
265
- data-adam-test="item3"
266
262
  >
267
263
  <div
268
264
  class="css-view-1dbjc4n"
@@ -362,12 +358,11 @@ exports[`<LatestFromSection> renders 1`] = `
362
358
  >
363
359
  <div
364
360
  class="tc-view__TcView-nuazoi-0 fPjBcr"
365
- data-adam-test="item2"
366
- style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
361
+ style="align-items: flex-start; display: block; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
367
362
  >
368
363
  <div
369
364
  class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
370
- data-adam-test="item1"
365
+ style="flex: 1; margin-bottom: 10px; min-width: 100%;"
371
366
  >
372
367
  <div
373
368
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -410,7 +405,6 @@ exports[`<LatestFromSection> renders 1`] = `
410
405
  </div>
411
406
  <div
412
407
  class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
413
- data-adam-test="item3"
414
408
  >
415
409
  <div
416
410
  class="css-view-1dbjc4n"
@@ -2,25 +2,20 @@ import React from 'react';
2
2
 
3
3
  import { showcaseConverter } from '@times-components/storybook';
4
4
 
5
+ import { getArticles } from './helpers';
5
6
  import { FetchProvider } from '../../helpers/fetch/FetchProvider';
6
7
  import previewData from '../../fixtures/preview-data/recommended-articles';
7
8
  import analyticsStream from '../../fixtures/analytics-actions/analytics-actions';
8
9
 
9
10
  import { RecommendedArticles } from './RecommendedArticles';
10
11
 
11
- export const getArticles = (data: any, numOfArticles: number) => ({
12
- recommendations: {
13
- articles: data.recommendations.articles.slice(0, numOfArticles)
14
- }
15
- });
16
-
17
12
  const recommArticles = {
18
13
  children: [
19
14
  {
20
15
  component: () => (
21
16
  <FetchProvider previewData={getArticles(previewData, 1)}>
22
17
  <RecommendedArticles
23
- section="News"
18
+ heading="Today's news"
24
19
  isVisible
25
20
  analyticsStream={analyticsStream}
26
21
  />
@@ -34,7 +29,7 @@ const recommArticles = {
34
29
  component: () => (
35
30
  <FetchProvider previewData={getArticles(previewData, 2)}>
36
31
  <RecommendedArticles
37
- section="Business"
32
+ heading="Today's business"
38
33
  isVisible
39
34
  analyticsStream={analyticsStream}
40
35
  />
@@ -48,7 +43,7 @@ const recommArticles = {
48
43
  component: () => (
49
44
  <FetchProvider previewData={previewData}>
50
45
  <RecommendedArticles
51
- section="Sport"
46
+ heading="Today's sport"
52
47
  isVisible
53
48
  analyticsStream={analyticsStream}
54
49
  />
@@ -3,28 +3,48 @@ import React from 'react';
3
3
  import RelatedArticles from '@times-components/related-articles';
4
4
 
5
5
  import { useFetch } from '../../helpers/fetch/FetchProvider';
6
+ import { useTrackingContext } from '../../helpers/tracking/TrackingContextProvider';
6
7
  import { getRelatedArticlesSlice } from './formatters';
7
8
 
8
9
  export const RecommendedArticles: React.FC<{
9
- section: string;
10
+ heading: string;
10
11
  isVisible?: boolean;
11
12
  analyticsStream?: (evt: any) => void;
12
- }> = ({ section, isVisible, analyticsStream }) => {
13
+ }> = ({ heading, isVisible, analyticsStream }) => {
13
14
  const { loading, error, data } = useFetch<any>();
14
15
 
15
16
  if (loading || error || data === undefined) {
16
17
  return null;
17
18
  }
18
19
 
20
+ const { fireAnalyticsEvent } = useTrackingContext();
21
+
22
+ const slice = getRelatedArticlesSlice(data.recommendations);
23
+
24
+ const onClickHandler = (__: MouseEvent, article: { url: string }) => {
25
+ const found = slice.items.find(
26
+ item => item.article.shortIdentifier === article.url.slice(-9)
27
+ );
28
+
29
+ if (fireAnalyticsEvent) {
30
+ fireAnalyticsEvent({
31
+ action: 'Clicked',
32
+ object: 'RecommendedArticles',
33
+ attrs: { article_parent_name: found ? found.article.headline : '' }
34
+ });
35
+ }
36
+ };
37
+
19
38
  return (
20
39
  <div
21
40
  id="recommended-articles"
22
41
  style={{ display: isVisible ? 'block' : 'none' }}
23
42
  >
24
43
  <RelatedArticles
25
- heading={`Today's ${section}`}
26
- slice={getRelatedArticlesSlice(data.recommendations)}
44
+ heading={heading}
45
+ slice={slice}
27
46
  isVisible
47
+ onPress={onClickHandler}
28
48
  analyticsStream={analyticsStream}
29
49
  />
30
50
  </div>
@@ -1,27 +1,53 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
 
3
3
  import { FetchProvider } from '../../helpers/fetch/FetchProvider';
4
+ import { TrackingContextProvider } from '../../helpers/tracking/TrackingContextProvider';
4
5
  import { RecommendedArticles } from './RecommendedArticles';
5
6
 
6
7
  export const RecommendedFetch: React.FC<{
7
8
  articleId: string;
8
- section: string;
9
+ articleHeadline: string;
10
+ articleSection: string;
9
11
  isVisible?: boolean;
10
12
  analyticsStream?: (evt: any) => void;
11
- }> = ({ articleId, section, isVisible, analyticsStream }) => {
13
+ }> = ({
14
+ articleId,
15
+ articleHeadline,
16
+ articleSection,
17
+ isVisible,
18
+ analyticsStream
19
+ }) => {
12
20
  const [isClientSide, setIsClientSide] = useState<boolean>(false);
13
21
 
14
22
  useEffect(() => {
15
23
  setIsClientSide(true);
16
24
  }, []);
17
25
 
26
+ const heading = `Today's ${articleSection}`;
27
+
18
28
  return isClientSide ? (
19
29
  <FetchProvider url={`/api/recommended-articles/${articleId}`}>
20
- <RecommendedArticles
21
- section={section}
22
- isVisible={isVisible}
23
- analyticsStream={analyticsStream}
24
- />
30
+ <TrackingContextProvider
31
+ context={{
32
+ object: 'RecommendedArticles',
33
+ attrs: {
34
+ event_navigation_action: 'navigation',
35
+ event_navigation_name: 'widget : relevant article',
36
+ event_navigation_browsing_method: 'click',
37
+ section_details: `section : ${articleSection}`,
38
+ article_name: articleHeadline,
39
+ widget_headline: heading.toLowerCase(),
40
+ widget_section: articleSection,
41
+ widget_type: "today's section"
42
+ }
43
+ }}
44
+ >
45
+ <RecommendedArticles
46
+ heading={heading}
47
+ isVisible={isVisible}
48
+ analyticsStream={analyticsStream}
49
+ />
50
+ </TrackingContextProvider>
25
51
  </FetchProvider>
26
52
  ) : null;
27
53
  };
@@ -1,23 +1,70 @@
1
1
  import React from 'react';
2
- import { render } from '@testing-library/react';
2
+ import mockDate from 'mockdate';
3
+ import { render, cleanup, fireEvent } from '@testing-library/react';
3
4
 
5
+ import { getArticles } from '../helpers';
4
6
  import { useFetch } from '../../../helpers/fetch/FetchProvider';
7
+ import { TrackingContextProvider } from '../../../helpers/tracking/TrackingContextProvider';
5
8
  import previewData from '../../../fixtures/preview-data/recommended-articles';
6
9
 
7
10
  import { RecommendedArticles } from '../RecommendedArticles';
8
11
 
9
- jest.mock('@times-components/related-articles', () => 'RelatedArticles');
10
-
11
12
  jest.mock('../../../helpers/fetch/FetchProvider', () => ({
12
13
  useFetch: jest.fn()
13
14
  }));
14
15
 
16
+ jest.mock('@times-components/related-articles', () => ({
17
+ __esModule: true,
18
+ default: (props: any) => (
19
+ <div>
20
+ RelatedArticles
21
+ <div>{props.heading}</div>
22
+ {props.slice.items.map(({ article }: any) => (
23
+ <div
24
+ onClick={() => props.onPress(null, { url: article.shortIdentifier })}
25
+ >
26
+ {article.headline}
27
+ </div>
28
+ ))}
29
+ </div>
30
+ )
31
+ }));
32
+
33
+ const articles = previewData.recommendations.articles;
34
+
35
+ const section = 'news';
36
+ const heading = `Today's ${section}`;
37
+
38
+ const initialContext = {
39
+ object: 'RecommendedArticles',
40
+ attrs: {
41
+ event_navigation_action: 'navigation',
42
+ event_navigation_name: 'widget : relevant article',
43
+ event_navigation_browsing_method: 'click',
44
+ section_details: `section : ${section}`,
45
+ article_name: 'Headline',
46
+ widget_headline: heading.toLowerCase(),
47
+ widget_section: section,
48
+ widget_type: "today's section"
49
+ }
50
+ };
51
+
15
52
  describe('<RecommendedArticles>', () => {
53
+ beforeEach(() => {
54
+ mockDate.set(1620000000000);
55
+ });
56
+
57
+ afterEach(() => {
58
+ mockDate.reset();
59
+ jest.clearAllMocks();
60
+ cleanup();
61
+ });
62
+
16
63
  it('should render the initial loading state correctly', () => {
17
64
  (useFetch as jest.Mock).mockReturnValue({ loading: true });
18
65
 
19
66
  const { asFragment } = render(
20
- <RecommendedArticles section="News" analyticsStream={() => ({})} />
67
+ <RecommendedArticles heading={heading} analyticsStream={() => ({})} />
21
68
  );
22
69
 
23
70
  expect(asFragment().firstChild).toBeNull();
@@ -27,26 +74,92 @@ describe('<RecommendedArticles>', () => {
27
74
  (useFetch as jest.Mock).mockReturnValue({ error: 'Some error occurred' });
28
75
 
29
76
  const { asFragment } = render(
30
- <RecommendedArticles section="News" analyticsStream={() => ({})} />
77
+ <RecommendedArticles heading={heading} analyticsStream={() => ({})} />
31
78
  );
32
79
 
33
80
  expect(asFragment().firstChild).toBeNull();
34
81
  });
35
82
 
36
- it('should render RelatedArticles correctly', () => {
37
- (useFetch as jest.Mock).mockReturnValue({ data: previewData });
83
+ it('should render RelatedArticles correctly with 1 article', () => {
84
+ (useFetch as jest.Mock).mockReturnValue({
85
+ data: getArticles(previewData, 1)
86
+ });
38
87
 
39
- const { container, asFragment } = render(
88
+ const { asFragment, getByText } = render(
40
89
  <RecommendedArticles
41
- section="News"
90
+ heading={heading}
42
91
  isVisible
43
92
  analyticsStream={() => ({})}
44
93
  />
45
94
  );
46
95
 
47
- const related = container.querySelector('relatedarticles') as HTMLElement;
48
- expect(related.getAttribute('heading')).toEqual("Today's News");
96
+ expect(getByText(heading));
97
+ expect(getByText(articles[0].headline));
98
+ expect(asFragment()).toMatchSnapshot();
99
+ });
100
+
101
+ it('should render RelatedArticles correctly with 2 articles', () => {
102
+ (useFetch as jest.Mock).mockReturnValue({
103
+ data: getArticles(previewData, 2)
104
+ });
49
105
 
106
+ const { asFragment, getByText } = render(
107
+ <RecommendedArticles heading={heading} analyticsStream={() => ({})} />
108
+ );
109
+
110
+ expect(getByText(heading));
111
+ expect(getByText(articles[0].headline));
112
+ expect(getByText(articles[1].headline));
50
113
  expect(asFragment()).toMatchSnapshot();
51
114
  });
115
+
116
+ it('should render RelatedArticles correctly with 3 articles', () => {
117
+ (useFetch as jest.Mock).mockReturnValue({ data: previewData });
118
+
119
+ const { asFragment, getByText } = render(
120
+ <RecommendedArticles
121
+ heading={heading}
122
+ isVisible
123
+ analyticsStream={() => ({})}
124
+ />
125
+ );
126
+
127
+ expect(getByText(heading));
128
+ expect(getByText(articles[0].headline));
129
+ expect(getByText(articles[1].headline));
130
+ expect(getByText(articles[2].headline));
131
+ expect(asFragment()).toMatchSnapshot();
132
+ });
133
+
134
+ it('should fire analytics event when an article is clicked', () => {
135
+ const analyticsStream = jest.fn();
136
+
137
+ (useFetch as jest.Mock).mockReturnValue({ data: previewData });
138
+
139
+ const { getByText } = render(
140
+ <TrackingContextProvider
141
+ context={initialContext}
142
+ analyticsStream={analyticsStream}
143
+ >
144
+ <RecommendedArticles
145
+ heading={heading}
146
+ isVisible
147
+ analyticsStream={() => ({})}
148
+ />
149
+ </TrackingContextProvider>
150
+ );
151
+
152
+ fireEvent.click(getByText(articles[0].headline));
153
+
154
+ expect(analyticsStream).toHaveBeenCalledTimes(1);
155
+ expect(analyticsStream).toHaveBeenCalledWith({
156
+ action: 'Clicked',
157
+ object: 'RecommendedArticles',
158
+ attrs: {
159
+ ...initialContext.attrs,
160
+ eventTime: '2021-05-03T00:00:00.000Z',
161
+ article_parent_name: articles[0].headline
162
+ }
163
+ });
164
+ });
52
165
  });
@@ -21,7 +21,8 @@ describe('<RecommendedFetch>', () => {
21
21
  const { asFragment, getByText } = render(
22
22
  <RecommendedFetch
23
23
  articleId="1234"
24
- section="News"
24
+ articleHeadline="Some headline"
25
+ articleSection="News"
25
26
  analyticsStream={() => ({})}
26
27
  />
27
28
  );
@@ -1,15 +1,67 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`<RecommendedArticles> should render RelatedArticles correctly 1`] = `
3
+ exports[`<RecommendedArticles> should render RelatedArticles correctly with 1 article 1`] = `
4
4
  <DocumentFragment>
5
5
  <div
6
6
  id="recommended-articles"
7
7
  style="display: block;"
8
8
  >
9
- <relatedarticles
10
- heading="Today's News"
11
- slice="[object Object]"
12
- />
9
+ <div>
10
+ RelatedArticles
11
+ <div>
12
+ Today's news
13
+ </div>
14
+ <div>
15
+ Save or splurge: what experts spend their own money on
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </DocumentFragment>
20
+ `;
21
+
22
+ exports[`<RecommendedArticles> should render RelatedArticles correctly with 2 articles 1`] = `
23
+ <DocumentFragment>
24
+ <div
25
+ id="recommended-articles"
26
+ style="display: none;"
27
+ >
28
+ <div>
29
+ RelatedArticles
30
+ <div>
31
+ Today's news
32
+ </div>
33
+ <div>
34
+ Save or splurge: what experts spend their own money on
35
+ </div>
36
+ <div>
37
+ Lieutenant Colonel Ian Crooke
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </DocumentFragment>
42
+ `;
43
+
44
+ exports[`<RecommendedArticles> should render RelatedArticles correctly with 3 articles 1`] = `
45
+ <DocumentFragment>
46
+ <div
47
+ id="recommended-articles"
48
+ style="display: block;"
49
+ >
50
+ <div>
51
+ RelatedArticles
52
+ <div>
53
+ Today's news
54
+ </div>
55
+ <div>
56
+ Save or splurge: what experts spend their own money on
57
+ </div>
58
+ <div>
59
+ Lieutenant Colonel Ian Crooke
60
+ </div>
61
+ <div>
62
+ Is the party over for Johnson?
63
+ </div>
64
+ </div>
13
65
  </div>
14
66
  </DocumentFragment>
15
67
  `;
@@ -0,0 +1,5 @@
1
+ export const getArticles = (data: any, numOfArticles: number) => ({
2
+ recommendations: {
3
+ articles: data.recommendations.articles.slice(0, numOfArticles)
4
+ }
5
+ });
@@ -207,9 +207,10 @@ declare module '@times-components/related-articles' {
207
207
  import { FC } from 'react';
208
208
  type RelatedArticles = {
209
209
  heading?: string;
210
- analyticsStream: AnalyticsStreamType;
211
- isVisible: boolean;
212
210
  slice: any;
211
+ isVisible: boolean;
212
+ onPress?: any;
213
+ analyticsStream: AnalyticsStreamType;
213
214
  };
214
215
  const RelatedArticles: FC<RelatedArticles>;
215
216
  export default RelatedArticles;