@financial-times/n-myft-ui 26.0.0 → 27.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. package/.circleci/config.yml +42 -16
  2. package/.nvmrc +1 -1
  3. package/Makefile +0 -1
  4. package/README.md +2 -48
  5. package/build-state/npm-shrinkwrap.json +10147 -20187
  6. package/components/collections/collections.html +85 -0
  7. package/components/concept-list/concept-list.html +31 -0
  8. package/components/csrf-token/input.html +5 -0
  9. package/components/follow-button/follow-button.html +79 -0
  10. package/components/instant-alert/instant-alert.html +47 -0
  11. package/components/pin-button/pin-button.html +20 -0
  12. package/components/save-for-later/save-for-later.html +68 -0
  13. package/components/unread-articles-indicator/date-fns.js +6 -6
  14. package/demos/app.js +3 -26
  15. package/demos/templates/demo.html +11 -10
  16. package/myft/main.scss +145 -0
  17. package/myft/ui/lists.js +22 -0
  18. package/myft/ui/save-article-to-list-variant.js +376 -0
  19. package/package.json +16 -30
  20. package/components/collections/collections.jsx +0 -68
  21. package/components/collections/collections.test.js +0 -83
  22. package/components/concept-list/concept-list.jsx +0 -69
  23. package/components/concept-list/concept-list.test.js +0 -116
  24. package/components/csrf-token/input.jsx +0 -20
  25. package/components/csrf-token/input.test.js +0 -23
  26. package/components/follow-button/follow-button.jsx +0 -176
  27. package/components/follow-button/follow-button.test.js +0 -40
  28. package/components/index.js +0 -17
  29. package/components/instant-alert/instant-alert.jsx +0 -73
  30. package/components/instant-alert/instant-alert.test.js +0 -86
  31. package/components/pin-button/pin-button.jsx +0 -40
  32. package/components/pin-button/pin-button.test.js +0 -57
  33. package/components/save-for-later/save-for-later.jsx +0 -101
  34. package/components/save-for-later/save-for-later.test.js +0 -59
  35. package/demos/templates/demo-layout.html +0 -25
  36. package/demos/templates/demo.jsx +0 -125
  37. package/dist/bundles/bundle.js +0 -3232
  38. package/jest.config.js +0 -8
  39. package/jsx-migration.md +0 -16
  40. package/webpack.config.js +0 -34
package/myft/ui/lists.js CHANGED
@@ -6,6 +6,7 @@ import nNotification from 'n-notification';
6
6
  import { uuid } from 'n-ui-foundations';
7
7
  import getToken from './lib/get-csrf-token';
8
8
  import oForms from 'o-forms';
9
+ import openSaveArticleToListVariant from './save-article-to-list-variant';
9
10
 
10
11
  const delegate = new Delegate(document.body);
11
12
  const csrfToken = getToken();
@@ -161,6 +162,10 @@ function showArticleSavedOverlay (contentId) {
161
162
  showListsOverlay('Article saved', `/myft/list?fragment=true&fromArticleSaved=true&contentId=${contentId}`, contentId);
162
163
  }
163
164
 
165
+ function showCreateListAndAddArticleOverlay (contentId, name = 'myft-ui-create-list-variant') {
166
+ return openSaveArticleToListVariant(name, contentId);
167
+ }
168
+
164
169
  function handleArticleSaved (contentId) {
165
170
  return myFtClient.getAll('created', 'list')
166
171
  .then(createdLists => createdLists.filter(list => !list.isRedirect))
@@ -171,10 +176,27 @@ function handleArticleSaved (contentId) {
171
176
  });
172
177
  }
173
178
 
179
+ function openCreateListAndAddArticleOverlay (contentId) {
180
+ return myFtClient.getAll('created', 'list')
181
+ .then(createdLists => createdLists.filter(list => !list.isRedirect))
182
+ .then(createdLists => {
183
+ return !createdLists.length ? showCreateListAndAddArticleOverlay(contentId) : showArticleSavedOverlay(contentId);
184
+ });
185
+ }
186
+
174
187
  function initialEventListeners () {
175
188
 
176
189
  document.body.addEventListener('myft.user.saved.content.add', event => {
177
190
  const contentId = event.detail.subject;
191
+
192
+ // Checks if the createListAndSaveArticle variant is active
193
+ // and will show the variant overlay if the user has no lists,
194
+ // otherwise it will show the classic overlay
195
+ const createListVariant = event.currentTarget.querySelector('[data-myft-ui-variant="createListAndSaveArticleVariant"]');
196
+ if (createListVariant) {
197
+ return openCreateListAndAddArticleOverlay(contentId);
198
+ }
199
+
178
200
  handleArticleSaved(contentId);
179
201
  });
180
202
 
@@ -0,0 +1,376 @@
1
+ import Overlay from 'o-overlay';
2
+ import myFtClient from 'next-myft-client';
3
+ import { uuid } from 'n-ui-foundations';
4
+ import getToken from './lib/get-csrf-token';
5
+ import oTooltip from 'o-tooltip';
6
+
7
+ const csrfToken = getToken();
8
+
9
+ let lists;
10
+
11
+ export default async function showSaveArticleToListVariant (name, contentId) {
12
+ try {
13
+ await openSaveArticleToListVariant (name, contentId);
14
+ } catch(error) {
15
+ handleError(error);
16
+ }
17
+ }
18
+
19
+ async function openSaveArticleToListVariant (name, contentId) {
20
+ function createList (list) {
21
+ if(!list) {
22
+ return;
23
+ }
24
+
25
+ myFtClient.add('user', null, 'created', 'list', uuid(), { name: list, token: csrfToken })
26
+ .then(detail => {
27
+ myFtClient.add('list', detail.subject, 'contained', 'content', contentId, { token: csrfToken }).then((createdList) => {
28
+ lists.push({ name: list, uuid: createdList.actorId, checked: true });
29
+ const listElement = ListsElement(lists, addToList, removeFromList);
30
+ const overlayContent = document.querySelector('.o-overlay__content');
31
+ overlayContent.insertAdjacentElement('afterbegin', listElement);
32
+ const announceListContainer = document.querySelector('.myft-ui-create-list-variant-announcement');
33
+ announceListContainer.textContent = `${list} created`;
34
+ triggerCreateListEvent(contentId);
35
+ });
36
+ });
37
+ }
38
+
39
+ function addToList (list) {
40
+ if(!list) {
41
+ return;
42
+ }
43
+
44
+ myFtClient.add('list', list.uuid, 'contained', 'content', contentId, { token: csrfToken }).then(() => {
45
+ const indexToUpdate = lists.indexOf(list);
46
+ lists[indexToUpdate] = { ...lists[indexToUpdate], checked: true };
47
+ const listElement = ListsElement(lists, addToList, removeFromList);
48
+ const overlayContent = document.querySelector('.o-overlay__content');
49
+ overlayContent.insertAdjacentElement('afterbegin', listElement);
50
+ triggerAddToListEvent(contentId);
51
+ });
52
+ }
53
+
54
+ function removeFromList (list) {
55
+ if(!list) {
56
+ return;
57
+ }
58
+
59
+ myFtClient.remove('list', list.uuid, 'contained', 'content', contentId, { token: csrfToken }).then(() => {
60
+ const indexToUpdate = lists.indexOf(list);
61
+ lists[indexToUpdate] = { ...lists[indexToUpdate], checked: false };
62
+ const listElement = ListsElement(lists, addToList, removeFromList);
63
+ const overlayContent = document.querySelector('.o-overlay__content');
64
+ overlayContent.insertAdjacentElement('afterbegin', listElement);
65
+ triggerRemoveFromListEvent(contentId);
66
+ });
67
+ }
68
+
69
+ if (!lists) {
70
+ lists = await getLists(contentId);
71
+ }
72
+
73
+ const overlays = Overlay.getOverlays();
74
+ const existingOverlay = overlays[name];
75
+ if (existingOverlay) {
76
+ existingOverlay.destroy();
77
+ }
78
+
79
+ const contentElement = ContentElement();
80
+ const headingElement = HeadingElement();
81
+
82
+ const createListOverlay = new Overlay(name, {
83
+ html: contentElement,
84
+ heading: { title: headingElement.outerHTML },
85
+ modal: false,
86
+ parentnode: isMobile() ? '.o-share--horizontal' : '.o-share--vertical',
87
+ class: 'myft-ui-create-list-variant',
88
+ });
89
+
90
+ const realignListener = realignOverlay(window.scrollY);
91
+
92
+ function outsideClickHandler (e) {
93
+ try {
94
+ const overlayContent = document.querySelector('.o-overlay__content');
95
+ if(!overlayContent || !overlayContent.contains(e.target)) {
96
+ createListOverlay.close();
97
+ }
98
+ } catch(error) {
99
+ handleError(error);
100
+ }
101
+ }
102
+
103
+ function openFormHandler () {
104
+ try {
105
+ const formElement = FormElement(createList);
106
+ const overlayContent = document.querySelector('.o-overlay__content');
107
+ overlayContent.insertAdjacentElement('beforeend', formElement);
108
+ formElement.elements[0].focus();
109
+ } catch(error) {
110
+ handleError(error);
111
+ }
112
+ }
113
+
114
+ createListOverlay.open();
115
+ createListOverlay.wrapper.addEventListener('oOverlay.ready', (data) => {
116
+ realignListener(data.currentTarget);
117
+
118
+ if (lists && lists.length) {
119
+ const listElement = ListsElement(lists, addToList, removeFromList);
120
+ const overlayContent = document.querySelector('.o-overlay__content');
121
+ overlayContent.insertAdjacentElement('afterbegin', listElement);
122
+ }
123
+
124
+ contentElement.addEventListener('click', openFormHandler);
125
+
126
+ document.querySelector('.article-content').addEventListener('click', outsideClickHandler);
127
+ });
128
+
129
+ createListOverlay.wrapper.addEventListener('oOverlay.destroy', () => {
130
+ const tooltipTemplate = document.createElement('div');
131
+ const opts = {
132
+ target: 'o-header-top-link-myft',
133
+ content: 'Go to saved articles in myFT to find your lists',
134
+ showOnConstruction: true,
135
+ closeAfter: 5,
136
+ position: 'below'
137
+ };
138
+
139
+ new oTooltip(tooltipTemplate, opts);
140
+
141
+ contentElement.removeEventListener('click', openFormHandler);
142
+
143
+ document.querySelector('.article-content').removeEventListener('click', outsideClickHandler);
144
+ });
145
+
146
+ window.addEventListener('scroll', function () {
147
+ realignListener(createListOverlay.wrapper, window.scrollY);
148
+ });
149
+
150
+ window.addEventListener('oViewport.resize', () => {
151
+ realignListener(createListOverlay.wrapper);
152
+ });
153
+ }
154
+
155
+ function stringToHTMLElement (string) {
156
+ const template = document.createElement('template');
157
+ template.innerHTML = string.trim();
158
+ return template.content.firstChild;
159
+ }
160
+
161
+ function FormElement (createList) {
162
+ const formString = `
163
+ <form class="myft-ui-create-list-variant-form">
164
+ <label class="o-forms-field">
165
+ <span class="o-forms-input o-forms-input--text">
166
+ <input type="text" name="list-name" aria-label="List name">
167
+ </span>
168
+ </label>
169
+ <button class="o-buttons o-buttons--secondary" type="submit">
170
+ Save
171
+ </button>
172
+ </form>
173
+ `;
174
+
175
+ const formElement = stringToHTMLElement(formString);
176
+
177
+ function handleSubmit (event) {
178
+ try {
179
+ event.preventDefault();
180
+ event.stopPropagation();
181
+ const inputListName = formElement.querySelector('input[name="list-name"]');
182
+ createList(inputListName.value);
183
+ inputListName.value = '';
184
+ formElement.remove();
185
+ } catch(error) {
186
+ handleError(error);
187
+ }
188
+ }
189
+
190
+ formElement.querySelector('button[type="submit"]').addEventListener('click', handleSubmit);
191
+
192
+ return formElement;
193
+ }
194
+
195
+ function ContentElement () {
196
+ let content = `
197
+ <div class="myft-ui-create-list-variant-footer">
198
+ <button class="myft-ui-create-list-variant-add">Add to a new list</button>
199
+ ${!lists.length ? '<p class="myft-ui-create-list-variant-add-description">Lists are a simple way to curate your content</p>' : ''}
200
+ </div>
201
+ `;
202
+
203
+ return stringToHTMLElement(content);
204
+ }
205
+
206
+ function HeadingElement () {
207
+ const heading = `
208
+ <span class="myft-ui-create-list-variant-heading">Added to <a href="https://www.ft.com/myft/saved-articles">saved articles</a> in <span class="myft-ui-create-list-variant-icon"><span class="myft-ui-create-list-variant-icon-visually-hidden">my FT</span></span></span>
209
+ `;
210
+
211
+ return stringToHTMLElement(heading);
212
+ }
213
+
214
+ function ListsElement (lists, addToList, removeFromList) {
215
+ const currentList = document.querySelector('.myft-ui-create-list-variant-lists');
216
+ if (currentList) {
217
+ currentList.remove();
218
+ }
219
+
220
+ const listCheckboxElement = ListCheckboxElement(addToList, removeFromList);
221
+
222
+ const listsTemplate = `
223
+ <div class="myft-ui-create-list-variant-lists o-forms-field o-forms-field--optional" role="group">
224
+ <span class="myft-ui-create-list-variant-lists-text">Add to a list</span>
225
+ <span class="myft-ui-create-list-variant-lists-container o-forms-input o-forms-input--checkbox">
226
+ </span>
227
+ </div>
228
+ `;
229
+ const listsElement = stringToHTMLElement(listsTemplate);
230
+
231
+ const listsElementContainer = listsElement.querySelector('.myft-ui-create-list-variant-lists-container');
232
+
233
+ lists.map(list => listsElementContainer.insertAdjacentElement('beforeend', listCheckboxElement(list)));
234
+
235
+ return listsElement;
236
+ }
237
+
238
+ function ListCheckboxElement (addToList, removeFromList) {
239
+ return function (list) {
240
+ const listCheckbox = `<label>
241
+ <input type="checkbox" name="default" value="${list.name}" ${list.checked ? 'checked' : ''}>
242
+ <span class="o-forms-input__label">
243
+ <span class="o-normalise-visually-hidden">
244
+ ${list.checked ? 'Remove article from ' : 'Add article to ' }
245
+ </span>
246
+ ${list.name}
247
+ </span>
248
+ </label>
249
+ `;
250
+
251
+ const listCheckboxElement = stringToHTMLElement(listCheckbox);
252
+
253
+ const [ input ] = listCheckboxElement.children;
254
+
255
+ function handleCheck (event) {
256
+ event.preventDefault();
257
+ return event.target.checked ? addToList(list) : removeFromList(list);
258
+ }
259
+
260
+ input.addEventListener('click', handleCheck);
261
+
262
+ return listCheckboxElement;
263
+ };
264
+ }
265
+
266
+ function realignOverlay (originalScrollPosition) {
267
+ return function (target, currentScrollPosition) {
268
+ try {
269
+ if(currentScrollPosition && Math.abs(currentScrollPosition - originalScrollPosition) < 120) {
270
+ return;
271
+ }
272
+
273
+ originalScrollPosition = currentScrollPosition;
274
+
275
+ target.style['min-width'] = '340px';
276
+ target.style['width'] = '100%';
277
+ target.style['margin-top'] = '-50px';
278
+ target.style['left'] = 0;
279
+
280
+ if (isMobile()) {
281
+ target.style['position'] = 'absolute';
282
+ target.style['margin-left'] = 0;
283
+ target.style['margin-top'] = 0;
284
+ target.style['top'] = calculateLargerScreenHalf(target) === 'ABOVE' ? '-120px' : '50px';
285
+ } else {
286
+ target.style['position'] = 'absolute';
287
+ target.style['margin-left'] = '45px';
288
+ target.style['top'] = '220px';
289
+ }
290
+ } catch (error) {
291
+ handleError(error);
292
+ }
293
+ };
294
+ }
295
+
296
+ function isMobile () {
297
+ const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
298
+
299
+ return vw <= 980;
300
+ }
301
+
302
+ function calculateLargerScreenHalf (target) {
303
+ const vh = Math.min(document.documentElement.clientHeight || 0, window.innerHeight || 0);
304
+
305
+ const targetBox = target.getBoundingClientRect();
306
+ const spaceAbove = targetBox.top;
307
+ const spaceBelow = vh - targetBox.bottom;
308
+
309
+ return spaceBelow < spaceAbove ? 'ABOVE' : 'BELOW';
310
+ }
311
+
312
+ async function getLists () {
313
+ return myFtClient.getAll('created', 'list')
314
+ .then(lists => lists.filter(list => !list.isRedirect))
315
+ .then(lists => {
316
+ return lists.map(list => ({ name: list.name, uuid: list.uuid, checked: false }));
317
+ });
318
+ }
319
+
320
+ function triggerAddToListEvent (contentId) {
321
+ return document.body.dispatchEvent(new CustomEvent('oTracking.event', {
322
+ detail: {
323
+ category: 'professorLists',
324
+ action: 'add-to-list',
325
+ article_id: contentId,
326
+ teamName: 'customer-products-us-growth',
327
+ amplitudeExploratory: true
328
+ },
329
+ bubbles: true
330
+ }));
331
+ }
332
+
333
+ function triggerRemoveFromListEvent (contentId) {
334
+ return document.body.dispatchEvent(new CustomEvent('oTracking.event', {
335
+ detail: {
336
+ category: 'professorLists',
337
+ action: 'remove-from-list',
338
+ article_id: contentId,
339
+ teamName: 'customer-products-us-growth',
340
+ amplitudeExploratory: true
341
+ },
342
+ bubbles: true
343
+ }));
344
+ }
345
+
346
+ function triggerCreateListEvent (contentId) {
347
+ document.body.dispatchEvent(new CustomEvent('oTracking.event', {
348
+ detail: {
349
+ category: 'professorLists',
350
+ action: 'create-list',
351
+ article_id: contentId,
352
+ teamName: 'customer-products-us-growth',
353
+ amplitudeExploratory: true
354
+ },
355
+ bubbles: true
356
+ }));
357
+
358
+ return document.body.dispatchEvent(new CustomEvent('oTracking.event', {
359
+ detail: {
360
+ category: 'myFT',
361
+ action: 'create-list-success',
362
+ article_id: contentId
363
+ },
364
+ bubbles: true
365
+ }));
366
+ }
367
+
368
+ function handleError (error) {
369
+ document.body.dispatchEvent(new CustomEvent('oErrors.log', {
370
+ bubbles: true,
371
+ detail: {
372
+ error,
373
+ info: { component: 'professorLists' },
374
+ }
375
+ }));
376
+ }
package/package.json CHANGED
@@ -1,15 +1,13 @@
1
1
  {
2
2
  "name": "@financial-times/n-myft-ui",
3
- "version": "26.0.0",
3
+ "version": "27.2.0",
4
4
  "description": "Client side component for interaction with myft",
5
- "main": "dist/bundles/bundle.js",
6
- "module": "dist/bundles/bundle.js",
5
+ "main": "server.js",
7
6
  "scripts": {
8
7
  "test": "echo \"Error: no test specified\" && exit 1",
9
8
  "commit": "commit-wizard",
10
9
  "prepare": "npx snyk protect || npx snyk protect -d || true",
11
- "preinstall": "[ \"$INIT_CWD\" != \"$PWD\" ] || npm_config_yes=true npx check-engine",
12
- "build-package": "webpack"
10
+ "preinstall": "npm_config_yes=true npx check-engine"
13
11
  },
14
12
  "repository": {
15
13
  "type": "git",
@@ -22,18 +20,14 @@
22
20
  },
23
21
  "homepage": "https://github.com/Financial-Times/n-myft-ui#readme",
24
22
  "devDependencies": {
25
- "@financial-times/dotcom-build-bower-resolve": "0.4.1",
26
- "@financial-times/dotcom-build-code-splitting": "0.4.1",
27
- "@financial-times/dotcom-build-js": "0.4.1",
28
- "@financial-times/dotcom-build-sass": "0.4.1",
29
- "@financial-times/dotcom-page-kit-cli": "0.4.1",
30
- "@financial-times/dotcom-server-handlebars": "^3.0.0",
31
- "@financial-times/dotcom-server-react-jsx": "^2.6.2",
32
- "@financial-times/n-express": "^22.4.1",
23
+ "@financial-times/dotcom-build-bower-resolve": "^2.6.2",
24
+ "@financial-times/dotcom-build-code-splitting": "^5.0.0",
25
+ "@financial-times/dotcom-build-js": "^5.0.0",
26
+ "@financial-times/dotcom-build-sass": "^5.0.0",
27
+ "@financial-times/dotcom-page-kit-cli": "^0.6.5",
28
+ "@financial-times/dotcom-server-handlebars": "^5.0.0",
29
+ "@financial-times/n-express": "^23.0.1",
33
30
  "@financial-times/n-gage": "^8.3.2",
34
- "@sucrase/jest-plugin": "^2.2.0",
35
- "@testing-library/jest-dom": "^5.16.1",
36
- "@testing-library/react": "^12.1.2",
37
31
  "ascii-table": "0.0.9",
38
32
  "autoprefixer": "9.7.0",
39
33
  "aws-sdk-mock": "4.5.0",
@@ -46,8 +40,6 @@
46
40
  "babel-plugin-transform-runtime": "^6.9.0",
47
41
  "babel-preset-env": "^1.7.0",
48
42
  "babel-preset-es2015": "^6.6.0",
49
- "babel-preset-react": "^6.24.1",
50
- "babel-preset-stage-2": "^6.24.1",
51
43
  "babel-runtime": "^6.9.2",
52
44
  "bower": "^1.8.8",
53
45
  "bower-resolve-webpack-plugin": "^1.0.5",
@@ -59,18 +51,14 @@
59
51
  "css-loader": "^0.23.1",
60
52
  "denodeify": "^1.2.1",
61
53
  "eslint": "6.5.1",
62
- "eslint-plugin-react": "^7.27.1",
63
54
  "extract-css-block-webpack-plugin": "^1.3.0",
64
- "extract-text-webpack-plugin": "3.0.2",
65
55
  "fetch-mock": "^5.0.3",
66
56
  "handlebars": "^4.0.6",
67
57
  "handlebars-loader": "^1.4.0",
68
58
  "http-server": "^0.11.1",
69
59
  "hyperons": "^0.4.1",
70
60
  "imports-loader": "0.8.0",
71
- "inject-loader": "^3.0.0",
72
- "jest": "^27.4.5",
73
- "jsdom": "^19.0.0",
61
+ "inject-loader": "^4.0.1",
74
62
  "karma": "4.4.1",
75
63
  "karma-browserstack-launcher": "1.5.1",
76
64
  "karma-chai": "^0.1.0",
@@ -81,31 +69,29 @@
81
69
  "karma-sinon": "^1.0.5",
82
70
  "karma-sinon-chai": "2.0.2",
83
71
  "karma-sourcemap-loader": "^0.3.7",
84
- "karma-webpack": "^3.0.0",
72
+ "karma-webpack": "^4.0.2",
85
73
  "lintspaces-cli": "^0.7.0",
86
74
  "lolex": "5.1.1",
87
75
  "mocha": "6.2.2",
88
76
  "mockery": "2.1.0",
89
77
  "node-fetch": "2.6.0",
90
- "node-sass": "^4.14.1",
78
+ "node-sass": "^6.0.1",
91
79
  "nodemon": "^1.9.2",
92
80
  "npm-prepublish": "^1.2.1",
93
81
  "pa11y-ci": "^2.1.1",
94
82
  "postcss-loader": "^0.9.1",
95
- "react": "^17.0.2",
96
83
  "regenerator-runtime": "^0.13.3",
97
84
  "semver": "6.3.0",
98
85
  "sinon": "^7.1.0",
99
86
  "sinon-chai": "^3.2.0",
100
- "snyk": "^1.216.5",
101
- "sucrase": "^3.10.1"
87
+ "snyk": "^1.216.5"
102
88
  },
103
89
  "volta": {
104
- "node": "12.22.5",
90
+ "node": "16.14.2",
105
91
  "npm": "7.20.2"
106
92
  },
107
93
  "engines": {
108
- "node": "12.x",
94
+ "node": "14.x || 16.x",
109
95
  "npm": "7.x || 8.x"
110
96
  },
111
97
  "x-dash": {
@@ -1,68 +0,0 @@
1
- import React from 'react';
2
- import CsrfToken from '../csrf-token/input';
3
- import FollowButton from '../follow-button/follow-button';
4
-
5
- export default function Collections ({ title, liteStyle, flags, collectionName, trackable, concepts = [], csrfToken, cacheablePersonalisedUrl }) {
6
- const getLiteStyleModifier = () => liteStyle ? 'lite' : 'regular';
7
- let formProps = {
8
- method: 'POST',
9
- action: '#',
10
- 'data-myft-ui': 'follow',
11
- 'data-concept-id': concepts.map(concept => concept.id).join(',')
12
- };
13
-
14
- if (collectionName) {
15
- formProps['data-myft-tracking'] = collectionName;
16
- }
17
-
18
- return (
19
- <section
20
- className={`collection collection--${getLiteStyleModifier()}`}
21
- data-trackable={trackable ? trackable : 'collection'}>
22
- <header className={`collection__header collection__header--${getLiteStyleModifier()}`}>
23
- <h2 className={`collection__title collection__title--${getLiteStyleModifier()}`}>
24
- {title}
25
- </h2>
26
- </header>
27
- <ul className="collection__concepts">
28
- {concepts && concepts.map((concept, index) =>
29
- <li className="collection__concept" key={index}>
30
- <FollowButton cacheablePersonalisedUrl={cacheablePersonalisedUrl} csrfToken={csrfToken} variant={liteStyle ? 'primary' : 'inverse'} buttonText={concept.name} flags={flags} collectionName={collectionName} />
31
- </li>)
32
- }
33
- </ul>
34
-
35
- <div className="collection__meta">
36
- <form
37
- {...formProps}
38
- className="n-myft-ui n-myft-ui--follow n-ui-hide-core collection-follow-all">
39
- <input
40
- type="hidden"
41
- name="directType"
42
- value={concepts.map(concept => concept.directType).join(',')}
43
- />
44
- <CsrfToken csrfToken={csrfToken} cacheablePersonalisedUrl={cacheablePersonalisedUrl} />
45
- <input
46
- type="hidden"
47
- name="name"
48
- value={concepts.map(concept => concept.name).join(',')}
49
- />
50
- <button
51
- type="submit"
52
- aria-pressed="false"
53
- className={`collection-follow-all__button collection-follow-all__button--${getLiteStyleModifier()}`}
54
- data-trackable="follow all"
55
- data-concept-id={concepts.map(concept => concept.id).join(',')}
56
- aria-label={`Add all topics in the ${title} collection to my F T`}
57
- data-alternate-label={`Remove all topics in the ${title} collection from my F T`}
58
- data-alternate-text="Added"
59
- title={`Add all topics in the ${title} collection to my F T`}>
60
- Add all to myFT
61
- </button>
62
- </form>
63
- </div>
64
- </section>
65
-
66
- )
67
-
68
- }
@@ -1,83 +0,0 @@
1
- import React from 'react';
2
- import Collections from './collections';
3
- import { render, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom';
5
-
6
- const fixtures = {
7
- 'title': 'European Union',
8
- 'concepts': [
9
- {
10
- 'id': '00000000-0000-0000-0000-000000000000',
11
- 'prefLabel': 'EU immigration',
12
- 'directType': 'http://www.ft.com/ontology/Topic',
13
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
14
- 'name': 'EU immigration'
15
- },
16
- {
17
- 'id': '00000000-0000-0000-0000-000000000001',
18
- 'prefLabel': 'Europe Quantitative Easing',
19
- 'directType': 'http://www.ft.com/ontology/Topic',
20
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
21
- 'name': 'Europe Quantitative Easing'
22
- },
23
- {
24
- 'id': '00000000-0000-0000-0000-000000000002',
25
- 'prefLabel': 'EU financial regulation',
26
- 'directType': 'http://www.ft.com/ontology/Topic',
27
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
28
- 'name': 'EU financial regulation'
29
- },
30
- {
31
- 'id': '00000000-0000-0000-0000-000000000003',
32
- 'prefLabel': 'EU nothing',
33
- 'directType': 'http://www.ft.com/ontology/Topic',
34
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
35
- 'name': 'EU nothing'
36
- },
37
- {
38
- 'id': '00000000-0000-0000-0000-000000000004',
39
- 'prefLabel': 'EU trade',
40
- 'directType': 'http://www.ft.com/ontology/Topic',
41
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
42
- 'name': 'EU trade'
43
- }
44
- ]
45
- };
46
- const joinedDirectTypes = 'http://www.ft.com/ontology/Topic,http://www.ft.com/ontology/Topic,http://www.ft.com/ontology/Topic,http://www.ft.com/ontology/Topic,http://www.ft.com/ontology/Topic';
47
-
48
- const flags = {
49
- myFtApi: true,
50
- myFtApiWrite: true
51
- };
52
-
53
- describe('Concept List', () => {
54
-
55
- test('It renders the title of the collection', async () => {
56
- render(<Collections {...fixtures} flags={flags} />);
57
- expect(await screen.findByText('European Union')).toBeTruthy();
58
- });
59
-
60
- test('It renders label for the concept button', async () => {
61
- render(<Collections {...fixtures} flags={flags} />);
62
- expect(await screen.findByText('EU immigration')).toBeTruthy();
63
- expect(await screen.findByText('Europe Quantitative Easing')).toBeTruthy();
64
- expect(await screen.findByText('EU financial regulation')).toBeTruthy();
65
- expect(await screen.findByText('EU nothing')).toBeTruthy();
66
- expect(await screen.findByText('EU trade')).toBeTruthy();
67
- });
68
-
69
- test('It renders form "Add all to my FT" from', () => {
70
- const { container} = render(<Collections {...fixtures} flags={flags} />);
71
- const formElement = container.querySelector('form[action="#"]');
72
- expect(formElement).toBeTruthy();
73
- expect(formElement.method).toEqual('post');
74
- });
75
-
76
- test('It renders directType input with value of types joined', () => {
77
- const { container} = render(<Collections {...fixtures} flags={flags} />);
78
- const directTypeElement = container.querySelector('input[name="directType"]');
79
- expect(directTypeElement).toBeTruthy();
80
- expect(directTypeElement.value).toEqual(joinedDirectTypes);
81
- });
82
-
83
- });