@times-components/ts-components 1.41.1 → 1.43.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.
Files changed (22) hide show
  1. package/CHANGELOG.md +33 -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 +15 -18
  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
@@ -65,12 +65,12 @@ exports[`<LatestFromSection> renders 1`] = `
65
65
  class="css-view-1dbjc4n"
66
66
  >
67
67
  <div
68
- class="css-view-1dbjc4n"
69
- style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
68
+ class="tc-view__TcView-nuazoi-0 fPjBcr"
69
+ style="align-items: flex-start; display: block; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
70
70
  >
71
71
  <div
72
- class="imageContainerClass imageContainerClass css-view-1dbjc4n"
73
- style="flex-grow: 1; flex-shrink: 1; flex-basis: 0%; margin-bottom: 10px; min-width: 100%;"
72
+ class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
73
+ style="flex: 1; margin-bottom: 10px; min-width: 100%;"
74
74
  >
75
75
  <div
76
76
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -112,8 +112,7 @@ exports[`<LatestFromSection> renders 1`] = `
112
112
  </div>
113
113
  </div>
114
114
  <div
115
- class="contentContainerClass contentContainerClass css-view-1dbjc4n"
116
- style="flex-grow: 1; flex-shrink: 1; flex-basis: 0%; margin-bottom: 0px; min-width: 100%;"
115
+ class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
117
116
  >
118
117
  <div
119
118
  class="css-view-1dbjc4n"
@@ -212,12 +211,12 @@ exports[`<LatestFromSection> renders 1`] = `
212
211
  class="css-view-1dbjc4n"
213
212
  >
214
213
  <div
215
- class="css-view-1dbjc4n"
216
- style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
214
+ class="tc-view__TcView-nuazoi-0 fPjBcr"
215
+ style="align-items: flex-start; display: block; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
217
216
  >
218
217
  <div
219
- class="imageContainerClass imageContainerClass css-view-1dbjc4n"
220
- style="flex-grow: 1; flex-shrink: 1; flex-basis: 0%; margin-bottom: 10px; min-width: 100%;"
218
+ class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
219
+ style="flex: 1; margin-bottom: 10px; min-width: 100%;"
221
220
  >
222
221
  <div
223
222
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -259,8 +258,7 @@ exports[`<LatestFromSection> renders 1`] = `
259
258
  </div>
260
259
  </div>
261
260
  <div
262
- class="contentContainerClass contentContainerClass css-view-1dbjc4n"
263
- style="flex-grow: 1; flex-shrink: 1; flex-basis: 0%; margin-bottom: 0px; min-width: 100%;"
261
+ class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
264
262
  >
265
263
  <div
266
264
  class="css-view-1dbjc4n"
@@ -359,12 +357,12 @@ exports[`<LatestFromSection> renders 1`] = `
359
357
  class="css-view-1dbjc4n"
360
358
  >
361
359
  <div
362
- class="css-view-1dbjc4n"
363
- style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
360
+ class="tc-view__TcView-nuazoi-0 fPjBcr"
361
+ style="align-items: flex-start; display: block; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
364
362
  >
365
363
  <div
366
- class="imageContainerClass imageContainerClass css-view-1dbjc4n"
367
- style="flex-grow: 1; flex-shrink: 1; flex-basis: 0%; margin-bottom: 10px; min-width: 100%;"
364
+ class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
365
+ style="flex: 1; margin-bottom: 10px; min-width: 100%;"
368
366
  >
369
367
  <div
370
368
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -406,8 +404,7 @@ exports[`<LatestFromSection> renders 1`] = `
406
404
  </div>
407
405
  </div>
408
406
  <div
409
- class="contentContainerClass contentContainerClass css-view-1dbjc4n"
410
- style="flex-grow: 1; flex-shrink: 1; flex-basis: 0%; margin-bottom: 0px; min-width: 100%;"
407
+ class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
411
408
  >
412
409
  <div
413
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;