@financial-times/n-myft-ui 26.0.0 → 27.2.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 (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
@@ -1,69 +0,0 @@
1
- import React, { Fragment } from 'react';
2
- import FollowButton from '../follow-button/follow-button';
3
-
4
- export default function ConceptList ({ flags, concepts, contentType, conceptListTitle, trackable, csrfToken, cacheablePersonalisedUrl }) {
5
-
6
- const {
7
- myFtApi,
8
- myFtApiWrite
9
- } = flags;
10
-
11
- const generateTrackableProps = (primary, secondary) => {
12
- return {
13
- 'data-trackable': primary ? primary : secondary
14
- }
15
- }
16
-
17
- const shouldDisplay = () => {
18
- if(myFtApi && myFtApiWrite && Array.isArray(concepts) && concepts.length) {
19
- return true
20
- }
21
-
22
- return false;
23
- }
24
-
25
-
26
- return (
27
-
28
- <Fragment>
29
- {shouldDisplay() &&
30
- <div
31
- className='concept-list'
32
- {...generateTrackableProps(trackable, 'concept-list')}>
33
- {
34
- (contentType || conceptListTitle) &&
35
- <h2 className='concept-list__title'>
36
- {conceptListTitle ? conceptListTitle : `Follow the topics in this ${contentType}`}
37
- </h2>
38
- }
39
- <ul className='concept-list__list'>
40
- {concepts.map((concept, index) => {
41
- const {
42
- relativeUrl,
43
- url,
44
- conceptTrackable,
45
- prefLabel,
46
- id
47
- } = concept;
48
- return (
49
- <li key={index} className='concept-list__list-item'>
50
- {/* The relativeUrl and url point to the same resource. The url is the base path + the relative url.
51
- Example: browser_path = https://ft.com, relativeUrl = /capital-markets then url = https://www.ft.com/capital-markets.
52
-
53
- Note: we don't need to compute these urls in the business logic of these components as they're passed in as props.
54
-
55
- This note is just an explanation for why relativeUrl has preference over url.*/}
56
- <a
57
- href={relativeUrl || url}
58
- {...generateTrackableProps(conceptTrackable, 'concept')}
59
- className='concept-list__concept'>
60
- {prefLabel}
61
- </a>
62
- <FollowButton csrfToken={csrfToken} cacheablePersonalisedUrl={cacheablePersonalisedUrl} conceptId={id} name={prefLabel} flags={flags} />
63
- </li>
64
- )
65
- })}
66
- </ul>
67
- </div>}
68
- </Fragment>)
69
- }
@@ -1,116 +0,0 @@
1
- import React from 'react';
2
- import ConceptList from './concept-list';
3
- import { render, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom';
5
-
6
- const fixtures = [
7
- {
8
- 'conceptListTitle': 'Follow european union things',
9
- 'concepts': [
10
- {
11
- 'id': '00000000-0000-0000-0000-000000000161',
12
- 'prefLabel': 'EU immigration',
13
- 'directType': 'http://www.ft.com/ontology/Topic',
14
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000161',
15
- 'name': 'EU immigration'
16
- },
17
- {
18
- 'id': '00000000-0000-0000-0000-000000000162',
19
- 'prefLabel': 'Europe Quantitative Easing',
20
- 'directType': 'http://www.ft.com/ontology/Topic',
21
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000162',
22
- 'name': 'Europe Quantitative Easing'
23
- },
24
- {
25
- 'id': '00000000-0000-0000-0000-000000000163',
26
- 'prefLabel': 'EU financial regulation',
27
- 'directType': 'http://www.ft.com/ontology/Topic',
28
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000163',
29
- 'name': 'EU financial regulation'
30
- },
31
- {
32
- 'id': '00000000-0000-0000-0000-000000000164',
33
- 'prefLabel': 'EU nothing',
34
- 'directType': 'http://www.ft.com/ontology/Topic',
35
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000164',
36
- 'name': 'EU nothing'
37
- },
38
- {
39
- 'id': '00000000-0000-0000-0000-000000000165',
40
- 'prefLabel': 'EU trade',
41
- 'directType': 'http://www.ft.com/ontology/Topic',
42
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000165',
43
- 'name': 'EU trade'
44
- }
45
- ]
46
- },
47
- {
48
- 'contentType': 'search',
49
- 'concepts': [
50
- {
51
- 'id': '00000000-0000-0000-0000-000000000166',
52
- 'prefLabel': 'Noodle',
53
- 'directType': 'http://www.ft.com/ontology/Topic',
54
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000166',
55
- 'name': 'Noodle'
56
- },
57
- {
58
- 'id': '00000000-0000-0000-0000-000000000167',
59
- 'prefLabel': 'Green apples',
60
- 'directType': 'http://www.ft.com/ontology/Topic',
61
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000167',
62
- 'name': 'Green apples'
63
- },
64
- {
65
- 'id': '00000000-0000-0000-0000-000000000168',
66
- 'prefLabel': 'Fox blood',
67
- 'directType': 'http://www.ft.com/ontology/Topic',
68
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000168',
69
- 'name': 'Fox blood'
70
- },
71
- {
72
- 'id': '00000000-0000-0000-0000-000000000169',
73
- 'prefLabel': 'Dog party',
74
- 'directType': 'http://www.ft.com/ontology/Topic',
75
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000169',
76
- 'name': 'Dog party'
77
- },
78
- {
79
- 'id': '00000000-0000-0000-0000-000000000170',
80
- 'prefLabel': 'Fifth thing',
81
- 'directType': 'http://www.ft.com/ontology/Topic',
82
- 'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000170',
83
- 'name': 'Fifth thing'
84
- }
85
- ]
86
- },
87
- ];
88
-
89
-
90
- const flags = {
91
- myFtApi: true,
92
- myFtApiWrite: true
93
- };
94
-
95
- describe('Concept List', () => {
96
-
97
- test('It renders conceptListTitle value as title when conceptListTitle is provided', async () => {
98
- render(<ConceptList {...fixtures[0]} flags={flags} />);
99
- expect(await screen.findByText('Follow european union things')).toBeTruthy();
100
- });
101
-
102
- test('It renders "Follow the topics in this {conceptType}" value as title when conceptType is provided', async () => {
103
- render(<ConceptList {...fixtures[1]} flags={flags} />);
104
- expect(await screen.findByText('Follow the topics in this search')).toBeTruthy();
105
- });
106
-
107
- test('It renders label for the concept button', async () => {
108
- render(<ConceptList {...fixtures[0]} flags={flags} />);
109
- expect(await screen.findByText('EU immigration')).toBeTruthy();
110
- expect(await screen.findByText('Europe Quantitative Easing')).toBeTruthy();
111
- expect(await screen.findByText('EU financial regulation')).toBeTruthy();
112
- expect(await screen.findByText('EU nothing')).toBeTruthy();
113
- expect(await screen.findByText('EU trade')).toBeTruthy();
114
- });
115
-
116
- });
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
-
3
- export default function CsrfToken ({ cacheablePersonalisedUrl, csrfToken }) {
4
-
5
- let inputProps = {};
6
-
7
- if (cacheablePersonalisedUrl) {
8
- inputProps.value = csrfToken;
9
- }
10
-
11
- return (
12
- <input
13
- data-myft-csrf-token
14
- {...inputProps}
15
- type="hidden"
16
- name="token"
17
- />
18
- );
19
-
20
- }
@@ -1,23 +0,0 @@
1
- import React from 'react';
2
- import CsrfToken from './input';
3
- import { render } from '@testing-library/react';
4
- import '@testing-library/jest-dom';
5
-
6
- const props = {
7
- cacheablePersonalisedUrl: false
8
- };
9
-
10
- describe('Csrf Token Input', () => {
11
-
12
- test('It renders default button', async () => {
13
- let { container } = render(<CsrfToken {...props} />);
14
- expect(container.querySelector('[name=\'token\']')).toBeTruthy();
15
- });
16
-
17
- test('It renders csrf token attribute', async () => {
18
- let { container } = render(<CsrfToken cacheablePersonalisedUrl={true} csrfToken={'test-token'} />);
19
- expect(container.querySelector('[data-myft-csrf-token]')).toBeTruthy();
20
- });
21
-
22
-
23
- });
@@ -1,176 +0,0 @@
1
- import React, {Fragment} from 'react';
2
- import CsrfToken from '../csrf-token/input';
3
-
4
- function generateFormProps (props) {
5
- let generatedProps = {};
6
-
7
- const {
8
- collectionName,
9
- followPlusDigestEmail,
10
- conceptId,
11
- setFollowButtonStateToSelected,
12
- cacheablePersonalisedUrl
13
- } = props;
14
-
15
- if (collectionName) {
16
- generatedProps['data-myft-tracking'] = `collectionName=${collectionName}`;
17
- }
18
-
19
- if(followPlusDigestEmail) {
20
- generatedProps['action'] = `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put`;
21
- generatedProps['data-myft-ui-variant'] = 'followPlusDigestEmail';
22
- } else {
23
- if(setFollowButtonStateToSelected && cacheablePersonalisedUrl) {
24
- generatedProps['action'] = `/myft/remove/${conceptId}`;
25
- generatedProps['data-js-action'] = `/__myft/api/core/followed/concept/${conceptId}?method=delete`;
26
- } else {
27
- generatedProps['action'] = `/myft/add/${conceptId}`;
28
- generatedProps['data-js-action'] = `/__myft/api/core/followed/concept/${conceptId}?method=put`;
29
- }
30
- }
31
-
32
- return generatedProps;
33
-
34
- }
35
-
36
- function generateButtonProps (props) {
37
-
38
- const {
39
- cacheablePersonalisedUrl,
40
- setFollowButtonStateToSelected,
41
- name,
42
- buttonText,
43
- variant,
44
- conceptId,
45
- alternateText,
46
- followPlusDigestEmail
47
- } = props;
48
-
49
- let generatedProps = {
50
- 'data-concept-id': conceptId,
51
- 'n-myft-follow-button': 'true',
52
- 'data-trackable': 'follow',
53
- type: 'submit'
54
- };
55
-
56
- if (cacheablePersonalisedUrl && setFollowButtonStateToSelected) {
57
- generatedProps['aria-label'] = `Remove ${name} from myFT`;
58
- generatedProps['title'] = `Remove ${name} from myFT`
59
- generatedProps['data-alternate-label'] = `Add ${name} to myFT`;
60
- generatedProps['aria-pressed'] = true;
61
-
62
- if(alternateText) {
63
- generatedProps['data-alternate-text'] = alternateText;
64
- } else {
65
- if(buttonText) {
66
- generatedProps['data-alternate-text'] = buttonText;
67
- } else {
68
- generatedProps['data-alternate-text'] = 'Add to myFT';
69
- }
70
- }
71
- } else {
72
- generatedProps['aria-pressed'] = false;
73
- generatedProps['aria-label'] = `Add ${name} to myFT`;
74
- generatedProps['title'] = `Add ${name} to myFT`;
75
- generatedProps['data-alternate-label'] = `Remove ${name} from myFT`;
76
- if (alternateText) {
77
- generatedProps['data-alternate-text'] = alternateText;
78
- } else {
79
- if (buttonText) {
80
- generatedProps['data-alternate-text'] = buttonText;
81
- } else {
82
- generatedProps['data-alternate-text'] = 'Added';
83
- }
84
- }
85
- }
86
-
87
- if(variant) {
88
- generatedProps[`n-myft-follow-button--${variant}`] = 'true';
89
- }
90
-
91
- if(followPlusDigestEmail) {
92
- generatedProps['data-trackable-context-messaging'] = 'add-to-myft-plus-digest-button';
93
- }
94
-
95
- return generatedProps;
96
- }
97
-
98
- function getButtonText (props) {
99
-
100
- const {
101
- buttonText,
102
- setFollowButtonStateToSelected,
103
- cacheablePersonalisedUrl
104
- } = props;
105
- let outputText;
106
-
107
- if(buttonText) {
108
- outputText = buttonText;
109
- } else {
110
- if(setFollowButtonStateToSelected && cacheablePersonalisedUrl) {
111
- outputText = 'Added';
112
- } else {
113
- outputText = 'Add to myFT';
114
- }
115
- }
116
-
117
- return outputText;
118
- }
119
-
120
- /**
121
- *
122
- * @param {Object} props
123
- * @param {string} props.name
124
- * @param {Object} props.flags
125
- * @param {string} props.extraClasses
126
- * @param {string} props.conceptId
127
- * @param {string} props.variant
128
- * @param {string} props.buttonText
129
- * @param {*} props.setFollowButtonStateToSelected
130
- * @param {string} props.cacheablePersonalisedUrl
131
- * @param {string} props.alternateText
132
- * @param {*} props.followPlusDigestEmail
133
- * @param {string} props.collectionName
134
- */
135
- export default function FollowButton (props) {
136
-
137
- const {
138
- name,
139
- flags,
140
- extraClasses,
141
- conceptId,
142
- variant,
143
- csrfToken,
144
- cacheablePersonalisedUrl
145
- } = props;
146
-
147
- const formProps = generateFormProps(props);
148
- const buttonProps = generateButtonProps(props);
149
-
150
- const getVariantClass = (variant) => variant ? `n-myft-follow-button--${variant}` : '';
151
-
152
- return (
153
- <Fragment>
154
- {flags.myFtApiWrite && <form
155
- className={`n-myft-ui n-myft-ui--follow ${extraClasses || ''}`}
156
- method="GET"
157
- data-myft-ui="follow"
158
- data-concept-id={conceptId}
159
- {...formProps}>
160
- <CsrfToken cacheablePersonalisedUrl={cacheablePersonalisedUrl} csrfToken={csrfToken} />
161
- <div
162
- className="n-myft-ui__announcement o-normalise-visually-hidden"
163
- aria-live="assertive"
164
- data-pressed-text={`Now following ${name}.`}
165
- data-unpressed-text={`No longer following ${name}.`}
166
- ></div>
167
- <button
168
- {...buttonProps}
169
- className={[`n-myft-follow-button ${getVariantClass(variant)}`]}>
170
- {getButtonText(props)}
171
- </button>
172
- </form>}
173
- </Fragment>
174
- );
175
-
176
- }
@@ -1,40 +0,0 @@
1
- import React from 'react';
2
- import FollowButton from './follow-button';
3
- import { render, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom';
5
-
6
- const props = {
7
- flags: {
8
- myFtApi: true,
9
- myFtApiWrite: true
10
- },
11
- conceptId: '0000-000000-00000-0000',
12
- name: 'Follow button'
13
- };
14
-
15
- describe('Follow button', () => {
16
-
17
- test('It renders default button', async () => {
18
- render(<FollowButton {...props} />);
19
- expect(await screen.findByText('Add to myFT')).toBeTruthy();
20
- });
21
-
22
- test('It renders a variant', async () => {
23
- const { container } = render(<FollowButton {...props} variant={'standard'} />);
24
- expect(container.getElementsByClassName('n-myft-follow-button--standard')).toHaveLength(1);
25
- });
26
-
27
- test('It renders follow button form', async () => {
28
- const { container } = render(<FollowButton {...props} variant={'standard'} />);
29
- expect(container.querySelector(`form[action='/myft/add/${props.conceptId}']`)).toBeTruthy();
30
- });
31
-
32
- test('Button state changes when attributes change', async () => {
33
- render(<FollowButton {...props}
34
- variant={'standard'}
35
- setFollowButtonStateToSelected={true}
36
- cacheablePersonalisedUrl={true} />);
37
- expect(await screen.findByText('Added')).toBeTruthy();
38
- });
39
-
40
- });
@@ -1,17 +0,0 @@
1
- import CsrfToken from './csrf-token/input';
2
- import FollowButton from './follow-button/follow-button';
3
- import ConceptList from './concept-list/concept-list';
4
- import Collections from './collections/collections';
5
- import SaveForLater from './save-for-later/save-for-later';
6
- import PinButton from './pin-button/pin-button';
7
- import InstantAlert from './instant-alert/instant-alert';
8
-
9
- export {
10
- CsrfToken,
11
- FollowButton,
12
- ConceptList,
13
- Collections,
14
- SaveForLater,
15
- PinButton,
16
- InstantAlert
17
- };
@@ -1,73 +0,0 @@
1
- import React, { Fragment } from 'react';
2
- import CsrfToken from '../csrf-token/input';
3
-
4
- /**
5
- *
6
- * @param {Object} props
7
- * @param {string} props.name
8
- * @param {Object} props.flags
9
- * @param {booelan} props.hideButtonText
10
- * @param {string} props.conceptId
11
- * @param {string} props.name
12
- * @param {string} props.extraClasses
13
- * @param {boolean} props.directType
14
- * @param {string} props.cacheablePersonalisedUrl
15
- * @param {string} props.hasInstantAlert
16
- * @param {string} props.buttonText
17
- * @param {string} props.alternateText
18
- * @param {string} props.variant
19
- * @param {string} props.size
20
- */
21
- export default function InstantAlert (props) {
22
-
23
- const {
24
- hasInstantAlert,
25
- cacheablePersonalisedUrl,
26
- name,
27
- alternateText,
28
- buttonText,
29
- conceptId,
30
- variant,
31
- size,
32
- flags,
33
- hideButtonText,
34
- directType,
35
- extraClasses
36
- } = props;
37
-
38
- const generateButtonProps = () => {
39
-
40
- let buttonProps = {
41
- 'aria-pressed': `${Boolean(hasInstantAlert) && Boolean(cacheablePersonalisedUrl)}`,
42
- 'aria-label': `Get instant alerts for ${name}`,
43
- 'data-alternate-label': `Stop instant alerts for ${name}`,
44
- 'data-alternate-text': alternateText? alternateText: (buttonText ? buttonText : 'Instant alerts'),
45
- 'data-concept-id': conceptId, // duplicated here for tracking
46
- 'data-trackable': 'instant',
47
- title: `Get instant alerts for ${name}`,
48
- value: hasInstantAlert ? false : true,
49
- type: 'submit',
50
- name: '_rel.instant',
51
- className: `n-myft-ui__button n-myft-ui__button--instant n-myft-ui__button--instant-light${variant ? ` n-myft-ui__button--${variant}` : ''}${size ? ` n-myft-ui__button--${size}` : ''}`
52
- };
53
- return buttonProps;
54
- }
55
-
56
- return (
57
- <Fragment>
58
- {flags.myFtApiWrite &&
59
- <form className={`n-myft-ui n-myft-ui--instant${hideButtonText ? ' n-myft-ui--instant--hide-text' : ''}${extraClasses ? ` ${extraClasses}` : ''}`}
60
- method="GET"
61
- data-myft-ui="instant"
62
- data-concept-id={conceptId}
63
- action={`/myft/add/${conceptId}?instant=true`}
64
- data-js-action={`/__myft/api/core/followed/concept/${conceptId}?method=put`}>
65
- <CsrfToken />
66
- <input type="hidden" value={name} name="name" />
67
- <input type="hidden" value={directType || 'http://www.ft.com/ontology/concept/Concept'} name="directType" />
68
- <button {...generateButtonProps()}>{buttonText ? buttonText : 'Instant alerts'}</button>
69
- </form>}
70
- </Fragment>
71
- );
72
-
73
- }
@@ -1,86 +0,0 @@
1
- import React from 'react';
2
- import InstantAlert from './instant-alert';
3
- import { render, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom';
5
-
6
- const props = {
7
- flags: {
8
- myFtApi: true,
9
- myFtApiWrite: true
10
- },
11
- conceptId: '0000-000000-00000-0000',
12
- name: 'Instant Alert',
13
- buttonText: 'Instant Alert'
14
- };
15
-
16
- describe('InstantAlert', () => {
17
-
18
- test('It renders', async () => {
19
- render(<InstantAlert {...props} />);
20
- expect(await screen.findByText('Instant Alert')).toBeInTheDocument();
21
- });
22
-
23
- test('It renders form attributes', () => {
24
- const { container } = render(<InstantAlert {...props} />);
25
- expect(container.querySelector('form[method="GET"]')).toBeInTheDocument();
26
- expect(container.querySelector(`form[action='/myft/add/${props.conceptId}?instant=true']`)).toBeInTheDocument();
27
- expect(container.querySelector('form[data-myft-ui="instant"]')).toBeInTheDocument();
28
- expect(container.querySelector(`form[data-concept-id="${props.conceptId}"]`)).toBeInTheDocument();
29
- expect(container.querySelector(`form[data-js-action="/__myft/api/core/followed/concept/${props.conceptId}?method=put"]`)).toBeInTheDocument();
30
- });
31
-
32
- test('It renders extraClasses in form', () => {
33
- const { container } = render(<InstantAlert {...props} extraClasses={'extra'} />);
34
- expect(container.querySelector('form[class="n-myft-ui n-myft-ui--instant extra"]')).toBeInTheDocument();
35
- });
36
-
37
- test('It renders hide button class in form', () => {
38
- const { container } = render(<InstantAlert {...props} hideButtonText={true} />);
39
- expect(container.querySelector('form[class="n-myft-ui n-myft-ui--instant n-myft-ui--instant--hide-text"]')).toBeInTheDocument();
40
- });
41
-
42
- test('It renders csrftoken input', () => {
43
- const { container } = render(<InstantAlert {...props} />);
44
- expect(container.querySelector('input[data-myft-csrf-token]')).toBeInTheDocument();
45
- });
46
-
47
- test('It renders input name as value attribute', () => {
48
- const { container } = render(<InstantAlert {...props} />);
49
- expect(container.querySelector('input[value="Instant Alert"]')).toBeInTheDocument();
50
- });
51
-
52
- test('It renders input directType as value attribute', () => {
53
- const { container } = render(<InstantAlert {...props} directType={'http://www.ft.com/ontology/test/Test'} />);
54
- expect(container.querySelector('input[value="http://www.ft.com/ontology/test/Test"]')).toBeInTheDocument();
55
- });
56
-
57
- test('It renders button props', () => {
58
- const { container } = render(<InstantAlert {...props} alternateText={'Sample alternate text'} variant={'blue'} size={'small'} />);
59
- expect(container.querySelector(`button[aria-label="Get instant alerts for ${props.name}"]`)).toBeInTheDocument();
60
- expect(container.querySelector('button[data-alternate-text="Sample alternate text"]')).toBeInTheDocument();
61
- expect(container.querySelector(`button[data-concept-id="${props.conceptId}"]`)).toBeInTheDocument();
62
- expect(container.querySelector('button[data-trackable="instant"]')).toBeInTheDocument();
63
- expect(container.querySelector(`button[title="Get instant alerts for ${props.name}"]`)).toBeInTheDocument();
64
- expect(container.querySelector('button[value="true"]')).toBeInTheDocument();
65
- expect(container.querySelector('button[type="submit"]')).toBeInTheDocument();
66
- expect(container.querySelector('button[name="_rel.instant"]')).toBeInTheDocument();
67
- expect(container.getElementsByClassName('n-myft-ui__button--blue')).toHaveLength(1);
68
- expect(container.getElementsByClassName('n-myft-ui__button--small')).toHaveLength(1);
69
- });
70
-
71
- test('It renders buttonText as data-alternate-text attribute when alternateText prop is not provided', () => {
72
- const { container } = render(<InstantAlert {...props} buttonText={'Sample button text'} variant={'blue'} size={'small'} />);
73
- expect(container.querySelector('button[data-alternate-text="Sample button text"]')).toBeInTheDocument();
74
- });
75
-
76
- test('It renders button aria-pressed=false attribute when hasInstantAlert=false or cacheablePersonalisedUrl props is not provided', () => {
77
- render(<InstantAlert {...props} buttonText={'Sample button text'} hasInstantAlert={false} cacheablePersonalisedUrl={'https://ft.com'} />);
78
- expect(screen.getByRole('button', {pressed: false})).toBeInTheDocument();
79
- });
80
-
81
- test('It renders button aria-pressed=true attribute when hasInstantAlert=true and cacheablePersonalisedUrl provided', () => {
82
- render(<InstantAlert {...props} buttonText={'Sample button text'} hasInstantAlert={true} cacheablePersonalisedUrl={'https://ft.com'} />);
83
- expect(screen.getByRole('button', {pressed: true})).toBeInTheDocument();
84
- });
85
-
86
- });
@@ -1,40 +0,0 @@
1
- import React, { Fragment } from 'react';
2
- import CsrfToken from '../csrf-token/input';
3
- export default function PinButton ({ showPrioritiseButton, id, name, directType, prioritised, csrfToken, cacheablePersonalisedUrl }) {
4
-
5
- const getAction = () => `/__myft/api/core/prioritised/concept/${id}?method=${prioritised ? 'delete' : 'put'}`;
6
-
7
- if (!showPrioritiseButton) {
8
- return null;
9
- }
10
-
11
- return (
12
- <Fragment>
13
- <span className="myft-pin-divider"></span>
14
- <div className="myft-pin-button-wrapper">
15
- <form method="post" action={getAction()} data-myft-prioritise>
16
- <CsrfToken csrfToken={csrfToken} cacheablePersonalisedUrl={cacheablePersonalisedUrl} />
17
- <input type="hidden" value={name} name="name" />
18
- <input type="hidden" value={directType || 'http://www.ft.com/ontology/concept/Concept'} name="directType" />
19
- <div
20
- className="n-myft-ui__announcement o-normalise-visually-hidden"
21
- aria-live="assertive"
22
- data-pressed-text={`${name} pinned in myFT.`}
23
- data-unpressed-text={`Unpinned ${name} from myFT.`}
24
- ></div>
25
- <button id={`myft-pin-button__${id}`}
26
- className="myft-pin-button"
27
- data-prioritise-button
28
- data-trackable="prioritised"
29
- data-concept-id={id}
30
- data-prioritised={prioritised ? true : false}
31
- aria-label={`${prioritised ? 'Unpin' : 'Pin'} ${name} ${prioritised ? 'from' : 'in'} my F T`}
32
- aria-pressed={prioritised ? true : false}
33
- title={`${prioritised ? 'Unpin' : 'Pin'} ${name}`}>
34
- </button>
35
- </form>
36
- </div>
37
- </Fragment>
38
- )
39
-
40
- }