@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
@@ -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
- }