@dhis2-ui/file-input 10.16.2 → 10.16.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -9
- package/src/file-input/__tests__/file-input.test.js +28 -0
- package/src/file-input/features/accepts_multiple_files/index.js +32 -0
- package/src/file-input/features/accepts_multiple_files.feature +8 -0
- package/src/file-input/features/can_be_blurred/index.js +23 -0
- package/src/file-input/features/can_be_blurred.feature +6 -0
- package/src/file-input/features/can_be_changed/index.js +26 -0
- package/src/file-input/features/can_be_changed.feature +8 -0
- package/src/file-input/features/can_be_focused/index.js +22 -0
- package/src/file-input/features/can_be_focused.feature +6 -0
- package/src/file-input/features/common/index.js +21 -0
- package/src/file-input/file-input.e2e.stories.js +46 -0
- package/src/file-input/file-input.js +159 -0
- package/src/file-input/file-input.prod.stories.js +82 -0
- package/src/file-input/index.js +1 -0
- package/src/file-input-field/__tests__/file-input-field.test.js +29 -0
- package/src/file-input-field/features/can_be_required/index.js +9 -0
- package/src/file-input-field/features/can_be_required.feature +5 -0
- package/src/file-input-field/features/has_default_button_label/index.js +9 -0
- package/src/file-input-field/features/has_default_button_label.feature +5 -0
- package/src/file-input-field/features/has_default_placeholder/index.js +9 -0
- package/src/file-input-field/features/has_default_placeholder.feature +5 -0
- package/src/file-input-field/file-input-field.e2e.stories.js +15 -0
- package/src/file-input-field/file-input-field.js +139 -0
- package/src/file-input-field/file-input-field.prod.stories.js +167 -0
- package/src/file-input-field/index.js +1 -0
- package/src/file-input-field-with-list/__tests__/file-input-field-with-list.test.js +30 -0
- package/src/file-input-field-with-list/features/common/index.js +18 -0
- package/src/file-input-field-with-list/features/deduplicates_the_file_list/index.js +43 -0
- package/src/file-input-field-with-list/features/deduplicates_the_file_list.feature +8 -0
- package/src/file-input-field-with-list/features/disables_button_when_full/index.js +13 -0
- package/src/file-input-field-with-list/features/disables_button_when_full.feature +6 -0
- package/src/file-input-field-with-list/features/displays_files_in_a_list/index.js +7 -0
- package/src/file-input-field-with-list/features/displays_files_in_a_list.feature +5 -0
- package/src/file-input-field-with-list/features/files_can_be_removed/index.js +24 -0
- package/src/file-input-field-with-list/features/files_can_be_removed.feature +7 -0
- package/src/file-input-field-with-list/features/has_default_button_label/index.js +9 -0
- package/src/file-input-field-with-list/features/has_default_button_label.feature +5 -0
- package/src/file-input-field-with-list/features/has_default_placeholder/index.js +9 -0
- package/src/file-input-field-with-list/features/has_default_placeholder.feature +5 -0
- package/src/file-input-field-with-list/features/has_default_remove_text/index.js +9 -0
- package/src/file-input-field-with-list/features/has_default_remove_text.feature +5 -0
- package/src/file-input-field-with-list/file-input-field-with-list.e2e.stories.js +73 -0
- package/src/file-input-field-with-list/file-input-field-with-list.js +188 -0
- package/src/file-input-field-with-list/file-input-field-with-list.prod.stories.js +67 -0
- package/src/file-input-field-with-list/file-list-item-with-remove.js +34 -0
- package/src/file-input-field-with-list/index.js +1 -0
- package/src/file-list/features/accepts_cancel_text/index.js +14 -0
- package/src/file-list/features/accepts_cancel_text.feature +5 -0
- package/src/file-list/features/accepts_label/index.js +11 -0
- package/src/file-list/features/accepts_label.feature +5 -0
- package/src/file-list/features/accepts_remove_text/index.js +11 -0
- package/src/file-list/features/accepts_remove_text.feature +5 -0
- package/src/file-list/features/can_be_removed/index.js +15 -0
- package/src/file-list/features/can_be_removed.feature +6 -0
- package/src/file-list/features/file-list-item-accepts_children/index.js +10 -0
- package/src/file-list/features/file-list-item-accepts_children.feature +5 -0
- package/src/file-list/features/file-list-placeholder-accepts_children/index.js +12 -0
- package/src/file-list/features/file-list-placeholder-accepts_children.feature +5 -0
- package/src/file-list/features/loading_can_be_cancelled/index.js +15 -0
- package/src/file-list/features/loading_can_be_cancelled.feature +6 -0
- package/src/file-list/file-list-item.e2e.stories.js +39 -0
- package/src/file-list/file-list-item.js +131 -0
- package/src/file-list/file-list-placeholder.e2e.stories.js +7 -0
- package/src/file-list/file-list-placeholder.js +27 -0
- package/src/file-list/file-list.e2e.stories.js +5 -0
- package/src/file-list/file-list.js +28 -0
- package/src/file-list/index.js +3 -0
- package/src/index.js +9 -0
- package/src/locales/ar/translations.json +5 -0
- package/src/locales/ar_IQ/translations.json +5 -0
- package/src/locales/ckb/translations.json +5 -0
- package/src/locales/cs/translations.json +5 -0
- package/src/locales/da/translations.json +5 -0
- package/src/locales/en/translations.json +5 -0
- package/src/locales/es/translations.json +5 -0
- package/src/locales/es_419/translations.json +5 -0
- package/src/locales/fr/translations.json +5 -0
- package/src/locales/hi_IN/translations.json +5 -0
- package/src/locales/id/translations.json +5 -0
- package/src/locales/index.js +82 -0
- package/src/locales/km/translations.json +5 -0
- package/src/locales/ko_KR/translations.json +5 -0
- package/src/locales/lo/translations.json +5 -0
- package/src/locales/my/translations.json +5 -0
- package/src/locales/nb/translations.json +5 -0
- package/src/locales/nl/translations.json +5 -0
- package/src/locales/prs/translations.json +5 -0
- package/src/locales/ps/translations.json +5 -0
- package/src/locales/pt/translations.json +5 -0
- package/src/locales/pt_BR/translations.json +5 -0
- package/src/locales/ru/translations.json +5 -0
- package/src/locales/si/translations.json +5 -0
- package/src/locales/sv/translations.json +5 -0
- package/src/locales/tet/translations.json +5 -0
- package/src/locales/tg/translations.json +5 -0
- package/src/locales/uk/translations.json +5 -0
- package/src/locales/ur/translations.json +5 -0
- package/src/locales/uz_Latn/translations.json +5 -0
- package/src/locales/uz_UZ_Cyrl/translations.json +5 -0
- package/src/locales/uz_UZ_Latn/translations.json +5 -0
- package/src/locales/vi/translations.json +5 -0
- package/src/locales/zh/translations.json +5 -0
- package/src/locales/zh_CN/translations.json +5 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import { Field } from '@dhis2-ui/field'
|
|
3
|
+
import { Label } from '@dhis2-ui/label'
|
|
4
|
+
import PropTypes from 'prop-types'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import { FileInput } from '../file-input/index.js'
|
|
7
|
+
import { FileList, FileListPlaceholder } from '../file-list/index.js'
|
|
8
|
+
import i18n from '../locales/index.js'
|
|
9
|
+
|
|
10
|
+
// TODO: i18n
|
|
11
|
+
const translate = (prop, interpolationObject) => {
|
|
12
|
+
if (typeof prop === 'function') {
|
|
13
|
+
return prop(interpolationObject)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return prop
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const defaultButtonLabel = () => i18n.t('Upload a file')
|
|
20
|
+
const defaultPlaceholder = () => i18n.t('No file uploaded yet')
|
|
21
|
+
const FileInputField = ({
|
|
22
|
+
accept = '*',
|
|
23
|
+
buttonLabel = defaultButtonLabel,
|
|
24
|
+
children,
|
|
25
|
+
className,
|
|
26
|
+
dataTest = 'dhis2-uiwidgets-fileinputfield',
|
|
27
|
+
disabled,
|
|
28
|
+
error,
|
|
29
|
+
helpText,
|
|
30
|
+
initialFocus,
|
|
31
|
+
label,
|
|
32
|
+
large,
|
|
33
|
+
multiple,
|
|
34
|
+
name,
|
|
35
|
+
onBlur,
|
|
36
|
+
onChange,
|
|
37
|
+
onFocus,
|
|
38
|
+
onKeyDown,
|
|
39
|
+
placeholder = defaultPlaceholder,
|
|
40
|
+
required,
|
|
41
|
+
small,
|
|
42
|
+
tabIndex,
|
|
43
|
+
valid,
|
|
44
|
+
validationText,
|
|
45
|
+
warning,
|
|
46
|
+
}) => (
|
|
47
|
+
<Field
|
|
48
|
+
className={className}
|
|
49
|
+
dataTest={dataTest}
|
|
50
|
+
helpText={helpText}
|
|
51
|
+
validationText={validationText}
|
|
52
|
+
error={error}
|
|
53
|
+
warning={warning}
|
|
54
|
+
valid={valid}
|
|
55
|
+
>
|
|
56
|
+
{label && (
|
|
57
|
+
<Label required={required} disabled={disabled} htmlFor={name}>
|
|
58
|
+
{label}
|
|
59
|
+
</Label>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<FileInput
|
|
63
|
+
accept={accept}
|
|
64
|
+
buttonLabel={translate(buttonLabel)}
|
|
65
|
+
className={className}
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
error={error}
|
|
68
|
+
initialFocus={initialFocus}
|
|
69
|
+
large={large}
|
|
70
|
+
multiple={multiple}
|
|
71
|
+
name={name}
|
|
72
|
+
onBlur={onBlur}
|
|
73
|
+
onChange={onChange}
|
|
74
|
+
onFocus={onFocus}
|
|
75
|
+
onKeyDown={onKeyDown}
|
|
76
|
+
small={small}
|
|
77
|
+
tabIndex={tabIndex}
|
|
78
|
+
valid={valid}
|
|
79
|
+
warning={warning}
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
<FileList>
|
|
83
|
+
{children ? (
|
|
84
|
+
children
|
|
85
|
+
) : (
|
|
86
|
+
<FileListPlaceholder>
|
|
87
|
+
{translate(placeholder)}
|
|
88
|
+
</FileListPlaceholder>
|
|
89
|
+
)}
|
|
90
|
+
</FileList>
|
|
91
|
+
</Field>
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
FileInputField.propTypes = {
|
|
95
|
+
/** The `accept` attribute of the [native file input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) */
|
|
96
|
+
accept: PropTypes.string,
|
|
97
|
+
/** Text on the button */
|
|
98
|
+
buttonLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
|
99
|
+
children: PropTypes.node,
|
|
100
|
+
className: PropTypes.string,
|
|
101
|
+
dataTest: PropTypes.string,
|
|
102
|
+
/** Disables the button */
|
|
103
|
+
disabled: PropTypes.bool,
|
|
104
|
+
/** Applies 'error' styling to the validation text. Mutually exclusive with `warning` and `valid` props */
|
|
105
|
+
error: sharedPropTypes.statusPropType,
|
|
106
|
+
/** Useful guiding text for the user */
|
|
107
|
+
helpText: PropTypes.string,
|
|
108
|
+
initialFocus: PropTypes.bool,
|
|
109
|
+
/** A descriptive label above the button */
|
|
110
|
+
label: PropTypes.string,
|
|
111
|
+
/** Size of the button. Mutually exclusive with the `small` prop */
|
|
112
|
+
large: sharedPropTypes.sizePropType,
|
|
113
|
+
/** The `multiple` attribute of the [native file input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#multiple) */
|
|
114
|
+
multiple: PropTypes.bool,
|
|
115
|
+
/** Name associated with input. Passed to event handler callbacks */
|
|
116
|
+
name: PropTypes.string,
|
|
117
|
+
/** Placeholder below the button */
|
|
118
|
+
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
|
119
|
+
/** Adds an asterisk to indicate this field is required */
|
|
120
|
+
required: PropTypes.bool,
|
|
121
|
+
/** Size of the button. Mutually exclusive with the `large` prop */
|
|
122
|
+
small: sharedPropTypes.sizePropType,
|
|
123
|
+
tabIndex: PropTypes.string,
|
|
124
|
+
/** Applies 'valid' styling to the validation text. Mutually exclusive with `warning` and `error` props */
|
|
125
|
+
valid: sharedPropTypes.statusPropType,
|
|
126
|
+
/** Text below the button that provides validation feedback */
|
|
127
|
+
validationText: PropTypes.string,
|
|
128
|
+
/** Applies 'warning' styling to the validation text. Mutually exclusive with `valid` and `error` props */
|
|
129
|
+
warning: sharedPropTypes.statusPropType,
|
|
130
|
+
onBlur: PropTypes.func,
|
|
131
|
+
/** Called with signature `({ name: string, files: [] }, event)` */
|
|
132
|
+
onChange: PropTypes.func,
|
|
133
|
+
/** Called with signature `({ name: string, files: [] }, event)` */
|
|
134
|
+
onFocus: PropTypes.func,
|
|
135
|
+
/** Called with signature `({ name: string, files: [] }, event)` */
|
|
136
|
+
onKeyDown: PropTypes.func,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { FileInputField }
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { FileListItem } from '../index.js'
|
|
4
|
+
import { FileInputField } from './file-input-field.js'
|
|
5
|
+
|
|
6
|
+
const description = `
|
|
7
|
+
The \`FileInputField\` component wraps the \`FileInput\` component in a \`Field\` wrapper to add labels, help text, and validation text.
|
|
8
|
+
|
|
9
|
+
\`\`\`js
|
|
10
|
+
import { FileInputField, FileListItem } from '@dhis2/ui'
|
|
11
|
+
\`\`\`
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
const onChange = (obj) => console.log('onChange', obj)
|
|
15
|
+
const onRemove = () => console.log('onRemove')
|
|
16
|
+
const onCancel = () => console.log('onCancel')
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
title: 'File Input Field',
|
|
20
|
+
component: FileInputField,
|
|
21
|
+
parameters: { docs: { description: { component: description } } },
|
|
22
|
+
// Default args:
|
|
23
|
+
args: {
|
|
24
|
+
// Handle default values (see comment in Transfer.js)
|
|
25
|
+
accept: '*',
|
|
26
|
+
dataTest: 'dhis2-uiwidgets-fileinputfield',
|
|
27
|
+
placeholder: 'No file uploaded yet',
|
|
28
|
+
onChange: onChange,
|
|
29
|
+
name: 'uploadName',
|
|
30
|
+
label: 'Upload something',
|
|
31
|
+
buttonLabel: 'Upload a file',
|
|
32
|
+
},
|
|
33
|
+
argTypes: {
|
|
34
|
+
small: { ...sharedPropTypes.sizeArgType },
|
|
35
|
+
large: { ...sharedPropTypes.sizeArgType },
|
|
36
|
+
valid: { ...sharedPropTypes.statusArgType },
|
|
37
|
+
warning: { ...sharedPropTypes.statusArgType },
|
|
38
|
+
error: { ...sharedPropTypes.statusArgType },
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const Template = (args) => <FileInputField {...args} />
|
|
43
|
+
|
|
44
|
+
export const Default = Template.bind({})
|
|
45
|
+
Default.args = { label: null }
|
|
46
|
+
|
|
47
|
+
export const WithLabel = Template.bind({})
|
|
48
|
+
|
|
49
|
+
export const Required = Template.bind({})
|
|
50
|
+
Required.args = { required: true }
|
|
51
|
+
|
|
52
|
+
export const Multiple = Template.bind({})
|
|
53
|
+
Multiple.args = {
|
|
54
|
+
multiple: true,
|
|
55
|
+
label: 'Upload multiple things',
|
|
56
|
+
buttonLabel: 'Upload files',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const Disabled = Template.bind({})
|
|
60
|
+
Disabled.args = { disabled: true }
|
|
61
|
+
|
|
62
|
+
export const Sizes = (args) => (
|
|
63
|
+
<>
|
|
64
|
+
<FileInputField
|
|
65
|
+
{...args}
|
|
66
|
+
buttonLabel="Default size"
|
|
67
|
+
name="defaultName"
|
|
68
|
+
/>
|
|
69
|
+
<FileInputField {...args} buttonLabel="Small" small name="smallName" />
|
|
70
|
+
<FileInputField {...args} buttonLabel="Large" large name="largeName" />
|
|
71
|
+
</>
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
export const Statuses = (args) => (
|
|
75
|
+
<>
|
|
76
|
+
<FileInputField {...args} buttonLabel="Default" name="defaultName" />
|
|
77
|
+
<FileInputField {...args} buttonLabel="Valid" name="validName" valid />
|
|
78
|
+
<FileInputField
|
|
79
|
+
{...args}
|
|
80
|
+
buttonLabel="Warning"
|
|
81
|
+
name="warningName"
|
|
82
|
+
warning
|
|
83
|
+
/>
|
|
84
|
+
<FileInputField
|
|
85
|
+
{...args}
|
|
86
|
+
buttonLabel="Error"
|
|
87
|
+
name="errorName"
|
|
88
|
+
error
|
|
89
|
+
validationText="Something went wrong"
|
|
90
|
+
/>
|
|
91
|
+
</>
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
export const FileList = (args) => (
|
|
95
|
+
<div style={{ width: 250 }}>
|
|
96
|
+
<FileInputField {...args}>
|
|
97
|
+
<FileListItem
|
|
98
|
+
label="picture1.jpg"
|
|
99
|
+
onRemove={onRemove}
|
|
100
|
+
onCancel={onCancel}
|
|
101
|
+
cancelText="Cancel"
|
|
102
|
+
removeText="Remove"
|
|
103
|
+
/>
|
|
104
|
+
<FileListItem
|
|
105
|
+
label="image_that_is_uploading.jpg"
|
|
106
|
+
onRemove={onRemove}
|
|
107
|
+
onCancel={onCancel}
|
|
108
|
+
cancelText="Cancel"
|
|
109
|
+
removeText="Remove"
|
|
110
|
+
loading
|
|
111
|
+
/>
|
|
112
|
+
<FileListItem
|
|
113
|
+
label="image_file_name_is_to_long_to_display_on_one_line.jpg"
|
|
114
|
+
onRemove={onRemove}
|
|
115
|
+
onCancel={onCancel}
|
|
116
|
+
cancelText="Cancel"
|
|
117
|
+
removeText="Remove"
|
|
118
|
+
/>
|
|
119
|
+
</FileInputField>
|
|
120
|
+
<br />
|
|
121
|
+
<p style={{ color: 'grey' }}>
|
|
122
|
+
<em>Bounding box is 250px wide</em>
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
export const PlaceholderText = Template.bind({})
|
|
128
|
+
PlaceholderText.args = { placeholder: 'No file(s) selected yet' }
|
|
129
|
+
|
|
130
|
+
export const HelpText = Template.bind({})
|
|
131
|
+
HelpText.args = { helpText: 'Please select any file type' }
|
|
132
|
+
|
|
133
|
+
export const DesignSystemStackingOrder = (args) => (
|
|
134
|
+
<FileInputField {...args}>
|
|
135
|
+
<FileListItem
|
|
136
|
+
label="TestFile.txt"
|
|
137
|
+
onRemove={onRemove}
|
|
138
|
+
removeText="remove"
|
|
139
|
+
/>
|
|
140
|
+
<FileListItem
|
|
141
|
+
label="BusyFile.txt"
|
|
142
|
+
onRemove={onRemove}
|
|
143
|
+
onCancel={onCancel}
|
|
144
|
+
cancelText="cancel"
|
|
145
|
+
removeText="remove"
|
|
146
|
+
loading
|
|
147
|
+
/>
|
|
148
|
+
</FileInputField>
|
|
149
|
+
)
|
|
150
|
+
DesignSystemStackingOrder.args = {
|
|
151
|
+
error: true,
|
|
152
|
+
validationText: 'Oops!',
|
|
153
|
+
placeholder: 'Select a file',
|
|
154
|
+
helpText: 'Please upload something',
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const DesignSystemStackingOrderEmptyFileList = Template.bind({})
|
|
158
|
+
DesignSystemStackingOrderEmptyFileList.args = {
|
|
159
|
+
...DesignSystemStackingOrder.args,
|
|
160
|
+
}
|
|
161
|
+
DesignSystemStackingOrderEmptyFileList.storyName =
|
|
162
|
+
'Design system stacking order - empty file list'
|
|
163
|
+
|
|
164
|
+
export const DefaultButtonLabelAndPlaceholder = Template.bind({})
|
|
165
|
+
DefaultButtonLabelAndPlaceholder.args = { label: null }
|
|
166
|
+
DefaultButtonLabelAndPlaceholder.storyName =
|
|
167
|
+
'Default: buttonLabel and placeholder'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileInputField } from './file-input-field.js'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { render, fireEvent, screen } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { FileInputFieldWithList } from '../file-input-field-with-list.js'
|
|
4
|
+
|
|
5
|
+
describe('<FileInputFieldWithList />', () => {
|
|
6
|
+
it('should call the onKeyDown callback when provided', () => {
|
|
7
|
+
const onKeyDown = jest.fn()
|
|
8
|
+
|
|
9
|
+
render(
|
|
10
|
+
<FileInputFieldWithList
|
|
11
|
+
label="Label"
|
|
12
|
+
name="foo"
|
|
13
|
+
value="bar"
|
|
14
|
+
checked={false}
|
|
15
|
+
onKeyDown={onKeyDown}
|
|
16
|
+
onChange={jest.fn()}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
fireEvent.keyDown(screen.getByRole('button'), {})
|
|
21
|
+
|
|
22
|
+
expect(onKeyDown).toHaveBeenCalledTimes(1)
|
|
23
|
+
|
|
24
|
+
const input = screen.getByTestId('dhis2-uicore-fileinput-input')
|
|
25
|
+
expect(onKeyDown).toHaveBeenCalledWith(
|
|
26
|
+
{ name: 'foo', files: input.files },
|
|
27
|
+
expect.objectContaining({})
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileInputFieldWithList with multiple files is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInputFieldWithList', 'Multiple files - with files')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the onChange handler is called', () => {
|
|
8
|
+
cy.window().should((win) => {
|
|
9
|
+
const calls = win.onChange.getCalls()
|
|
10
|
+
expect(calls).to.have.lengthOf(1)
|
|
11
|
+
|
|
12
|
+
const callArgs = calls[0].args
|
|
13
|
+
expect(callArgs).to.have.lengthOf(2)
|
|
14
|
+
|
|
15
|
+
const payload = callArgs[0]
|
|
16
|
+
expect(Object.keys(payload)).to.include.members(['files', 'name'])
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('the list contains the file duplicate.md', () => {
|
|
4
|
+
cy.contains('duplicate.md')
|
|
5
|
+
.should('have.lengthOf', 1)
|
|
6
|
+
.should('have.class', 'label')
|
|
7
|
+
.closest('[data-test="dhis2-uicore-filelistitem"]')
|
|
8
|
+
.should('have.lengthOf', 1)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
When('the file duplicate.md is selected', () => {
|
|
12
|
+
cy.window().then((win) => {
|
|
13
|
+
cy.get('[data-test="dhis2-uicore-fileinput"] input')
|
|
14
|
+
.then(($input) => {
|
|
15
|
+
// name, lastModified, size and type are equal to the file created in the story
|
|
16
|
+
const duplicate = new File(...win.duplicateFileConstructorArgs)
|
|
17
|
+
const dataTransfer = new DataTransfer()
|
|
18
|
+
|
|
19
|
+
dataTransfer.items.add(duplicate)
|
|
20
|
+
$input[0].files = dataTransfer.files
|
|
21
|
+
})
|
|
22
|
+
.trigger('change', { force: true })
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
Then(
|
|
27
|
+
"the onChange handler's payload contains a single entry for file.md",
|
|
28
|
+
() => {
|
|
29
|
+
cy.window().should((win) => {
|
|
30
|
+
const calls = win.onChange.getCalls()
|
|
31
|
+
const callArgs = calls[0].args
|
|
32
|
+
const payload = callArgs[0]
|
|
33
|
+
const files = payload.files
|
|
34
|
+
|
|
35
|
+
expect(files).to.have.lengthOf(3)
|
|
36
|
+
|
|
37
|
+
const filesWithNameFileMd = files.filter(
|
|
38
|
+
(f) => f.name === 'duplicate.md'
|
|
39
|
+
)
|
|
40
|
+
expect(filesWithNameFileMd).to.have.lengthOf(1)
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Feature: The FileInputFieldWithList deduplicates the file list
|
|
2
|
+
|
|
3
|
+
Scenario: The file list is deduplicated
|
|
4
|
+
Given a FileInputFieldWithList with multiple files is rendered
|
|
5
|
+
And the list contains the file duplicate.md
|
|
6
|
+
When the file duplicate.md is selected
|
|
7
|
+
Then the onChange handler is called
|
|
8
|
+
Then the onChange handler's payload contains a single entry for file.md
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileInputFieldWithList without multiple is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInputFieldWithList', 'Single file - with file')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Given('the FileInputFieldWithList holds a file', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-filelistitem"]').should('have.lengthOf', 1)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the button is disabled', () => {
|
|
12
|
+
cy.get('[data-test="dhis2-uicore-button"]').should('have.attr', 'disabled')
|
|
13
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Then('the files are displayed in a list', () => {
|
|
4
|
+
cy.get('[data-test="dhis2-uicore-filelistitem"] .label')
|
|
5
|
+
.then(($labels) => $labels.map((_, el) => el.innerHTML).toArray())
|
|
6
|
+
.should('deep.equal', ['test1.md', 'duplicate.md', 'test2.md'])
|
|
7
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
When('the remove handle behind a file is clicked', () => {
|
|
4
|
+
cy.contains('test1.md')
|
|
5
|
+
.siblings('[data-test="dhis2-uicore-filelistitem-remove"]')
|
|
6
|
+
.click()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
Then(
|
|
10
|
+
"the onChange handler's payload does not contain an entry for that file",
|
|
11
|
+
() => {
|
|
12
|
+
cy.window().should((win) => {
|
|
13
|
+
const calls = win.onChange.getCalls()
|
|
14
|
+
const callArgs = calls[0].args
|
|
15
|
+
|
|
16
|
+
const payload = callArgs[0]
|
|
17
|
+
const files = payload.files
|
|
18
|
+
expect(files).to.have.lengthOf(2)
|
|
19
|
+
|
|
20
|
+
const filtered = files.filter((file) => file.name === 'test1.md')
|
|
21
|
+
expect(filtered).to.have.lengthOf(0)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Feature: Files can be removed from FileInputFieldWithList
|
|
2
|
+
|
|
3
|
+
Scenario: Files can be removed from the file list
|
|
4
|
+
Given a FileInputFieldWithList with multiple files is rendered
|
|
5
|
+
When the remove handle behind a file is clicked
|
|
6
|
+
Then the onChange handler is called
|
|
7
|
+
Then the onChange handler's payload does not contain an entry for that file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a default FileInputFieldWithList is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInputFieldWithList', 'With default texts')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the default button label is visible', () => {
|
|
8
|
+
cy.contains('Upload a file').should('be.visible')
|
|
9
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a default FileInputFieldWithList is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInputFieldWithList', 'With default texts')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the default placeholder is visible', () => {
|
|
8
|
+
cy.contains('No file uploaded yet').should('be.visible')
|
|
9
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a default FileInputFieldWithList is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInputFieldWithList', 'With file and default texts')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the default remove text is visible', () => {
|
|
8
|
+
cy.contains('Remove').should('be.visible')
|
|
9
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { FileInputFieldWithList } from './index.js'
|
|
3
|
+
|
|
4
|
+
window.onChange = window.Cypress && window.Cypress.cy.stub()
|
|
5
|
+
|
|
6
|
+
const singleFileArray = [new File([], 'dummy.txt')]
|
|
7
|
+
|
|
8
|
+
window.duplicateFileConstructorArgs = [
|
|
9
|
+
[],
|
|
10
|
+
'duplicate.md',
|
|
11
|
+
{
|
|
12
|
+
lastModified: 1581348175357,
|
|
13
|
+
type: 'text/markdown',
|
|
14
|
+
},
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
const multipleFileArray = [
|
|
18
|
+
new File([], 'test1.md'),
|
|
19
|
+
new File(...window.duplicateFileConstructorArgs),
|
|
20
|
+
new File([], 'test2.md'),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const onChange = (payload, event) => {
|
|
24
|
+
// NOTE: if files is not transformed into an array,
|
|
25
|
+
// cypress will get an empty file list!
|
|
26
|
+
window.onChange(
|
|
27
|
+
{
|
|
28
|
+
...payload,
|
|
29
|
+
files: [...payload.files],
|
|
30
|
+
},
|
|
31
|
+
event
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default { title: 'FileInputFieldWithList' }
|
|
36
|
+
export const SingleFileWithFile = () => (
|
|
37
|
+
<FileInputFieldWithList
|
|
38
|
+
buttonLabel="Upload file"
|
|
39
|
+
name="upload"
|
|
40
|
+
files={singleFileArray}
|
|
41
|
+
removeText="remove"
|
|
42
|
+
onChange={onChange}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
export const MultipleFilesEmpty = () => (
|
|
46
|
+
<FileInputFieldWithList
|
|
47
|
+
buttonLabel="Upload file"
|
|
48
|
+
multiple
|
|
49
|
+
name="upload"
|
|
50
|
+
removeText="remove"
|
|
51
|
+
onChange={onChange}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
export const MultipleFilesWithFiles = () => (
|
|
55
|
+
<FileInputFieldWithList
|
|
56
|
+
buttonLabel="Upload file"
|
|
57
|
+
multiple
|
|
58
|
+
name="upload"
|
|
59
|
+
files={multipleFileArray}
|
|
60
|
+
removeText="remove"
|
|
61
|
+
onChange={onChange}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
export const WithFileAndDefaultTexts = () => (
|
|
65
|
+
<FileInputFieldWithList
|
|
66
|
+
name="upload"
|
|
67
|
+
files={singleFileArray}
|
|
68
|
+
onChange={onChange}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
export const WithDefaultTexts = () => (
|
|
72
|
+
<FileInputFieldWithList name="upload" onChange={onChange} />
|
|
73
|
+
)
|