@times-components/ts-components 1.41.0 → 1.43.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 (23) 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 +9 -3
  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 +18 -15
  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 +13 -2
  20. package/src/components/recommended-articles/__tests__/__snapshots__/RecommendedArticles.test.tsx.snap +57 -5
  21. package/src/components/recommended-articles/__tests__/__snapshots__/RecommendedFetch.test.tsx.snap +3 -0
  22. package/src/components/recommended-articles/helpers.ts +5 -0
  23. package/src/types/externs.d.ts +3 -2
@@ -65,12 +65,13 @@ exports[`<LatestFromSection> renders 1`] = `
65
65
  class="css-view-1dbjc4n"
66
66
  >
67
67
  <div
68
- class="css-view-1dbjc4n"
68
+ class="tc-view__TcView-nuazoi-0 fPjBcr"
69
+ data-adam-test="item2"
69
70
  style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
70
71
  >
71
72
  <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%;"
73
+ class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
74
+ data-adam-test="item1"
74
75
  >
75
76
  <div
76
77
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -112,8 +113,8 @@ exports[`<LatestFromSection> renders 1`] = `
112
113
  </div>
113
114
  </div>
114
115
  <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%;"
116
+ class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
117
+ data-adam-test="item3"
117
118
  >
118
119
  <div
119
120
  class="css-view-1dbjc4n"
@@ -212,12 +213,13 @@ exports[`<LatestFromSection> renders 1`] = `
212
213
  class="css-view-1dbjc4n"
213
214
  >
214
215
  <div
215
- class="css-view-1dbjc4n"
216
+ class="tc-view__TcView-nuazoi-0 fPjBcr"
217
+ data-adam-test="item2"
216
218
  style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
217
219
  >
218
220
  <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%;"
221
+ class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
222
+ data-adam-test="item1"
221
223
  >
222
224
  <div
223
225
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -259,8 +261,8 @@ exports[`<LatestFromSection> renders 1`] = `
259
261
  </div>
260
262
  </div>
261
263
  <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%;"
264
+ class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
265
+ data-adam-test="item3"
264
266
  >
265
267
  <div
266
268
  class="css-view-1dbjc4n"
@@ -359,12 +361,13 @@ exports[`<LatestFromSection> renders 1`] = `
359
361
  class="css-view-1dbjc4n"
360
362
  >
361
363
  <div
362
- class="css-view-1dbjc4n"
364
+ class="tc-view__TcView-nuazoi-0 fPjBcr"
365
+ data-adam-test="item2"
363
366
  style="align-items: flex-start; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end;"
364
367
  >
365
368
  <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%;"
369
+ class="imageContainerClass tc-view__TcView-nuazoi-0 fPjBcr"
370
+ data-adam-test="item1"
368
371
  >
369
372
  <div
370
373
  class="tc-view__TcView-nuazoi-0 fPjBcr"
@@ -406,8 +409,8 @@ exports[`<LatestFromSection> renders 1`] = `
406
409
  </div>
407
410
  </div>
408
411
  <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%;"
412
+ class="tc-view__TcView-nuazoi-0 contentContainerClass card-content__TcCardContainer-sc-1w0wvq8-0 hzpWKJ"
413
+ data-adam-test="item3"
411
414
  >
412
415
  <div
413
416
  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
  });
@@ -3,8 +3,17 @@ import { render } from '@testing-library/react';
3
3
 
4
4
  import { RecommendedFetch } from '../RecommendedFetch';
5
5
 
6
+ jest.mock('../RecommendedArticles', () => ({
7
+ RecommendedArticles: () => <div>RecommendedArticles</div>
8
+ }));
9
+
6
10
  jest.mock('../../../helpers/fetch/FetchProvider', () => ({
7
- FetchProvider: () => <div>FetchProvider</div>
11
+ FetchProvider: (props: any) => (
12
+ <div>
13
+ FetchProvider
14
+ {props.children}
15
+ </div>
16
+ )
8
17
  }));
9
18
 
10
19
  describe('<RecommendedFetch>', () => {
@@ -12,12 +21,14 @@ describe('<RecommendedFetch>', () => {
12
21
  const { asFragment, getByText } = render(
13
22
  <RecommendedFetch
14
23
  articleId="1234"
15
- section="News"
24
+ articleHeadline="Some headline"
25
+ articleSection="News"
16
26
  analyticsStream={() => ({})}
17
27
  />
18
28
  );
19
29
 
20
30
  expect(getByText('FetchProvider'));
31
+ expect(getByText('RecommendedArticles'));
21
32
  expect(asFragment()).toMatchSnapshot();
22
33
  });
23
34
  });
@@ -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
  `;
@@ -4,6 +4,9 @@ exports[`<RecommendedFetch> should render correctly 1`] = `
4
4
  <DocumentFragment>
5
5
  <div>
6
6
  FetchProvider
7
+ <div>
8
+ RecommendedArticles
9
+ </div>
7
10
  </div>
8
11
  </DocumentFragment>
9
12
  `;
@@ -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;