@eturnity/eturnity_reusable_components 7.39.5-qa-elisee-7.42.1 → 7.45.0-EPDM-4900.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ const defaultRadioButtonProps = {
2
+ dataId: 'radio_buttons',
3
+ name: 'options',
4
+ options: [
5
+ {
6
+ label: 'Option 1',
7
+ value: 'option_1',
8
+ img: 'https://staging-01-backend.eturnity.dev/static/e_templates/base/styles/solar_img_i.png',
9
+ },
10
+ {
11
+ label: 'Option 2',
12
+ value: 'option_2',
13
+ img: 'https://staging-01-backend.eturnity.dev/static/e_templates/base/styles/switcher_img_i.png',
14
+ infoText: 'Option 2 info text',
15
+ },
16
+ {
17
+ label: 'Option 3',
18
+ value: 'option_3',
19
+ img: 'https://staging-01-backend.eturnity.dev/static/e_templates/base/styles/varta_img_i.png',
20
+ infoText: 'Option 3 info text',
21
+ disabled: true,
22
+ },
23
+ {
24
+ label: 'Option 4',
25
+ value: 'option_4',
26
+ disabled: true,
27
+ infoText: 'Option 4 info text',
28
+ },
29
+ ],
30
+ selectedOption: 'option_2',
31
+ }
32
+
33
+ export default defaultRadioButtonProps
@@ -1,6 +1,10 @@
1
1
  <template>
2
2
  <ComponentWrapper :layout="layout">
3
- <RadioWrapper v-for="(item, index) in options" :key="item.value">
3
+ <RadioWrapper
4
+ v-for="(item, index) in options"
5
+ :key="item.value"
6
+ :data-test-id="'radioWrapper_' + item.value"
7
+ >
4
8
  <LabelContainer
5
9
  :checkmark-color="checkmarkColor"
6
10
  :has-label="!!item.label"
@@ -11,6 +15,7 @@
11
15
  <Radio
12
16
  :checked="selectedOption === item.value"
13
17
  :data-id="`radio_button_${dataId}_option_${item.value}`"
18
+ :data-test-id="'radioInput_' + item.value"
14
19
  :disabled="item.disabled"
15
20
  :name="'radioButtons_' + radioName"
16
21
  type="radio"
@@ -18,15 +23,32 @@
18
23
  @click="onInputHandler(item.value)"
19
24
  />
20
25
  <span class="checkmark"></span>
21
- <LabelText v-if="item.label" :is-disabled="item.disabled">
26
+ <LabelText
27
+ v-if="item.label"
28
+ :data-test-id="'radioLabel_' + item.value"
29
+ :is-disabled="item.disabled"
30
+ >
22
31
  {{ item.label }}
23
32
  </LabelText>
24
- <InfoText v-if="item.infoText" size="16px" :text="item.infoText" />
33
+ <InfoText
34
+ v-if="item.infoText"
35
+ :data-test-id="'radioInfo_' + item.value"
36
+ size="16px"
37
+ :text="item.infoText"
38
+ />
25
39
  </LabelContainer>
26
40
  <ImageContainer v-if="item.img">
27
- <RadioImage :src="item.img" />
28
- <div class="search-icn-container" @click="toggleImageModal(index)">
41
+ <RadioImage
42
+ :data-test-id="'radioImage_' + item.value"
43
+ :src="item.img"
44
+ />
45
+ <div
46
+ class="search-icn-container"
47
+ :data-test-id="'radioOpenImage_' + item.value"
48
+ @click="toggleImageModal(index)"
49
+ >
29
50
  <img
51
+ alt=""
30
52
  class="search-icn"
31
53
  :src="require('../../../assets/icons/search_icon.png')"
32
54
  />
@@ -34,6 +56,7 @@
34
56
  </ImageContainer>
35
57
  <Modal
36
58
  v-if="item.img"
59
+ :data-test-id="'radioModal_' + item.value"
37
60
  :is-open="isImageOpen(index)"
38
61
  @on-close="toggleImageModal(null)"
39
62
  >
@@ -49,18 +72,21 @@
49
72
  // import RadioButton from "@eturnity/eturnity_reusable_components/src/components/inputs/radioButton"
50
73
  // To use:
51
74
  // <radio-button
75
+ // checkmarkColor="red"
76
+ // dataId="radio_button"
77
+ // layout="horizontal"
78
+ // name="Name for Radio buttons"
52
79
  // :options="radioOptions"
53
80
  // :selectedOption="checkedOption"
54
- // @on-radio-change="onInputChange($event)"
55
- // layout="vertical"
56
81
  // size="large"
82
+ // @on-radio-change="onInputChange($event)"
57
83
  // />
58
84
  // Data being passed should look like:
59
85
  // radioOptions: [
60
- // { label: 'Button 1', value: 'button_1', img: 'www.imagesrc.com', infoText: 'my info text' },
61
- // { label: 'Button 2', value: 'button_2', img: 'www.imagesrc.com', infoText: 'my info text 2' },
62
- // { label: 'Button 3', value: 'button_3', img: 'www.imagesrc.com', disabled: true },
63
- // { label: 'Button 4', value: 'button_4', disabled: true }
86
+ // { label: 'Option 1', value: 'option_1', img: 'www.imagesrc.com', infoText: 'my info text' },
87
+ // { label: 'Option 2', value: 'option_2', img: 'www.imagesrc.com', infoText: 'my info text 2' },
88
+ // { label: 'Option 3', value: 'option_3', img: 'www.imagesrc.com', disabled: true },
89
+ // { label: 'Option 4', value: 'option_4', disabled: true }
64
90
  // ]
65
91
 
66
92
  import styled from 'vue3-styled-components'
@@ -82,7 +108,6 @@
82
108
  cursor: pointer;
83
109
  position: absolute;
84
110
  opacity: 0;
85
- cursor: pointer;
86
111
  height: 0;
87
112
  width: 0;
88
113
  `
@@ -221,33 +246,42 @@
221
246
  props: {
222
247
  selectedOption: {
223
248
  required: true,
224
- default: false,
249
+ type: [String, Number],
225
250
  },
226
251
  options: {
252
+ default() {
253
+ return []
254
+ },
227
255
  required: true,
228
- default: [],
256
+ type: Array,
229
257
  },
230
258
  layout: {
231
- required: false,
232
259
  default: 'horizontal', //2 options: 'vertical' (only 1 per row) & 'horizontal' (many per row)
260
+ required: false,
261
+ type: String,
233
262
  },
234
263
  size: {
235
- required: false,
236
264
  default: 'medium', // small, medium, large
265
+ required: false,
266
+ type: String,
237
267
  },
238
268
  name: {
239
- required: false,
240
269
  default: '',
270
+ required: false,
271
+ type: String,
241
272
  },
242
273
  checkmarkColor: {
243
- required: false,
244
274
  default: '',
275
+ required: false,
276
+ type: String,
245
277
  },
246
278
  dataId: {
247
- type: String,
248
279
  default: 'key',
280
+ required: false,
281
+ type: String,
249
282
  },
250
283
  },
284
+ emits: ['on-radio-change'],
251
285
  data() {
252
286
  return {
253
287
  selectedImage: null,
@@ -260,7 +294,9 @@
260
294
  },
261
295
  methods: {
262
296
  onInputHandler(value) {
263
- this.$emit('on-radio-change', value)
297
+ if (value !== this.selectedOption) {
298
+ this.$emit('on-radio-change', value)
299
+ }
264
300
  },
265
301
  isImageOpen(index) {
266
302
  return this.selectedImage === index
@@ -0,0 +1,306 @@
1
+ import { mount, DOMWrapper } from '@vue/test-utils'
2
+ import RadioButton from '@/components/inputs/radioButton'
3
+ import defaultRadioButtonProps from './defaultProps'
4
+ import theme from '@/assets/theme'
5
+
6
+ jest.mock('@/components/icon/iconCache.mjs', () => ({
7
+ // need to mock this due to how jest handles import.meta
8
+ fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
9
+ }))
10
+ jest.mock('../../../assets/icons/search_icon.png', () => 'search_icon.png')
11
+
12
+ describe('RadioButton.vue', () => {
13
+ const radioButtons = mount(RadioButton, {
14
+ props: defaultRadioButtonProps,
15
+ global: {
16
+ provide: {
17
+ theme,
18
+ },
19
+ },
20
+ })
21
+ function findRadioWrappersByCriteria(criteria) {
22
+ // criteria === ['active', ..., 'hasImage'], can include any number of criteria
23
+ // This function is used for getting a list of
24
+ // RadioWrapper components which matches
25
+ // all the requested criteria (conjunctive filtering).
26
+ // Make sure that criteria don't contradict each other.
27
+ // List of possible criteria =
28
+ // - active || disabled
29
+ // - checked || unchecked
30
+ // - hasInfoIcon
31
+ // - hasImage
32
+
33
+ function getParentRadioWrapper(element) {
34
+ // function is used for getting a parent RadioWrapper component
35
+ const radioWrapper = element.element.closest(
36
+ '[data-test-id^="radioWrapper_"]'
37
+ )
38
+ return radioWrapper ? new DOMWrapper(radioWrapper) : null
39
+ }
40
+
41
+ const elementsWithTestIds = radioButtons.findAll('[data-test-id]')
42
+ let resultsObject = {}
43
+
44
+ criteria.forEach((criterion) => {
45
+ resultsObject[criterion] = []
46
+ if (criterion === 'active' || criterion === 'disabled') {
47
+ const criteriaRelatedElements = elementsWithTestIds.filter((element) =>
48
+ element.attributes('data-test-id').startsWith('radioInput_')
49
+ )
50
+ criteriaRelatedElements.forEach((el) => {
51
+ const element = el.element
52
+ if (criterion === 'active' ? !element.disabled : element.disabled) {
53
+ resultsObject[criterion].push(getParentRadioWrapper(el))
54
+ }
55
+ })
56
+ }
57
+ if (criterion === 'checked' || criterion === 'unchecked') {
58
+ const criteriaRelatedElements = elementsWithTestIds.filter((element) =>
59
+ element.attributes('data-test-id').startsWith('radioInput_')
60
+ )
61
+ criteriaRelatedElements.forEach((el) => {
62
+ const element = el.element
63
+ if (criterion === 'checked' ? element.checked : !element.checked) {
64
+ resultsObject[criterion].push(getParentRadioWrapper(el))
65
+ }
66
+ })
67
+ }
68
+ if (criterion === 'hasInfoIcon') {
69
+ const criteriaRelatedElements = elementsWithTestIds.filter((element) =>
70
+ element.attributes('data-test-id').startsWith('radioInfo_')
71
+ )
72
+ criteriaRelatedElements.forEach((element) => {
73
+ resultsObject[criterion].push(getParentRadioWrapper(element))
74
+ })
75
+ }
76
+ if (criterion === 'hasImage') {
77
+ const criteriaRelatedElements = elementsWithTestIds.filter((element) =>
78
+ element.attributes('data-test-id').startsWith('radioOpenImage_')
79
+ )
80
+ criteriaRelatedElements.forEach((element) => {
81
+ resultsObject[criterion].push(getParentRadioWrapper(element))
82
+ })
83
+ }
84
+ })
85
+
86
+ let resultArray = []
87
+ Object.keys(resultsObject).forEach((criterion, index) => {
88
+ if (index === 0) {
89
+ resultArray = [...resultsObject[criterion]]
90
+ } else {
91
+ resultArray = resultArray.filter((element) =>
92
+ resultsObject[criterion]
93
+ .map((item) => item.attributes('data-test-id'))
94
+ .includes(element.attributes('data-test-id'))
95
+ )
96
+ }
97
+ })
98
+
99
+ return resultArray
100
+ }
101
+
102
+ it('Radio buttons are rendered and emit correct payload on change', async () => {
103
+ //check that all the options have been rendered
104
+ let radioBtnWrappers = radioButtons.findAll('[data-test-id]')
105
+ radioBtnWrappers = radioBtnWrappers.filter((wrapper) =>
106
+ wrapper.attributes('data-test-id').startsWith('radioWrapper_')
107
+ )
108
+ const numberOfOptions = radioButtons.props('options').length
109
+ expect(radioBtnWrappers).toHaveLength(numberOfOptions)
110
+
111
+ // check that the selected element exists and there is only a single copy of it and has correct value
112
+ const checkedWrapperArray = findRadioWrappersByCriteria(['checked'])
113
+ expect(checkedWrapperArray.length).toBe(1)
114
+ const checkedWrapper = checkedWrapperArray[0]
115
+ const checkedRadioInput = checkedWrapper.find('input[type="radio"]')
116
+ const defaultValueFromProps = radioButtons.props('selectedOption')
117
+ expect(checkedRadioInput.attributes('value')).toBe(defaultValueFromProps)
118
+
119
+ // Log attributes to see what is rendered (commented out just for reference)
120
+ // console.log('checkedRadioInput attributes', checkedRadioInput.attributes())
121
+
122
+ // Test the label
123
+ const labelOfDefaultValue = radioButtons
124
+ .props('options')
125
+ .find((option) => option.value === defaultValueFromProps).label
126
+ expect(checkedWrapper.text()).toContain(labelOfDefaultValue)
127
+
128
+ // Test the click on unselected active element
129
+ const uncheckedWrapperArray = findRadioWrappersByCriteria([
130
+ 'unchecked',
131
+ 'active',
132
+ ])
133
+ const expectedValueOfEmittedEvent = uncheckedWrapperArray[0]
134
+ .attributes('data-test-id')
135
+ .replace('radioWrapper_', '')
136
+ const uncheckedLabelWrapper = uncheckedWrapperArray[0].find('label')
137
+ await uncheckedLabelWrapper.trigger('click')
138
+ expect(radioButtons.emitted('on-radio-change')).toBeTruthy()
139
+ const emittedEvents = radioButtons.emitted('on-radio-change')
140
+ expect(emittedEvents).toHaveLength(1) // Check if the event was emitted exactly once
141
+ // Check the payload of the event
142
+ expect(emittedEvents[emittedEvents.length - 1]).toEqual([
143
+ expectedValueOfEmittedEvent,
144
+ ])
145
+ await radioButtons.setProps({ selectedOption: expectedValueOfEmittedEvent })
146
+ }),
147
+ it('click on disabled element (|| selected element || info icon || image) does not emit anything', async () => {
148
+ // Remember the number of emitted events before triggering clicks
149
+ const initialNumberOfEvents =
150
+ radioButtons.emitted('on-radio-change')?.length || 0
151
+
152
+ // Test RadioWrapper with disabled element
153
+ const disabledWrapperArray = findRadioWrappersByCriteria(['disabled'])
154
+ if (disabledWrapperArray.length > 0) {
155
+ const disabledLabelWrapper = disabledWrapperArray[0].find('label')
156
+ if (disabledLabelWrapper.exists()) {
157
+ await disabledLabelWrapper.trigger('click')
158
+ // Check if we still have the same number of emitted events as at the beginning
159
+ expect(radioButtons.emitted('on-radio-change')?.length || 0).toBe(
160
+ initialNumberOfEvents
161
+ )
162
+ }
163
+ }
164
+
165
+ // Get RadioWrapper with selected element
166
+ const checkedWrapperArray = findRadioWrappersByCriteria(['checked'])
167
+ if (checkedWrapperArray.length > 0) {
168
+ const checkedLabelWrapper = checkedWrapperArray[0].find('label')
169
+ if (checkedLabelWrapper.exists()) {
170
+ await checkedLabelWrapper.trigger('click')
171
+ // Check if we still have the same number of emitted events as at the beginning
172
+ expect(radioButtons.emitted('on-radio-change')?.length || 0).toBe(
173
+ initialNumberOfEvents
174
+ )
175
+ }
176
+ }
177
+
178
+ // Get RadioWrapper with info icon
179
+ const arrayOfWrappersWithInfoIcons = findRadioWrappersByCriteria([
180
+ 'active',
181
+ 'unchecked',
182
+ 'hasInfoIcon',
183
+ ])
184
+ if (arrayOfWrappersWithInfoIcons.length > 0) {
185
+ const infoIconForClick = arrayOfWrappersWithInfoIcons[0].find(
186
+ '[data-test-id="infoText_trigger"]'
187
+ )
188
+ if (infoIconForClick.exists()) {
189
+ await infoIconForClick.trigger('click')
190
+ // Check if we still have the same number of emitted events as at the beginning
191
+ expect(radioButtons.emitted('on-radio-change')?.length || 0).toBe(
192
+ initialNumberOfEvents
193
+ )
194
+ }
195
+ }
196
+
197
+ // Get RadioWrapper with image
198
+ const arrayOfWrappersWithImage = findRadioWrappersByCriteria([
199
+ 'active',
200
+ 'unchecked',
201
+ 'hasImage',
202
+ ])
203
+ if (arrayOfWrappersWithImage.length > 0) {
204
+ const testedRadioWrapperWithImage = arrayOfWrappersWithImage[0]
205
+ const openImageWrapperTestId = testedRadioWrapperWithImage
206
+ .attributes('data-test-id')
207
+ .replace('radioWrapper_', 'radioOpenImage_')
208
+ const openImageWrapper = testedRadioWrapperWithImage.find(
209
+ `[data-test-id="${openImageWrapperTestId}"]`
210
+ )
211
+ if (openImageWrapper.exists()) {
212
+ await openImageWrapper.trigger('click')
213
+ // Check if we still have the same number of emitted events as at the beginning
214
+ expect(radioButtons.emitted('on-radio-change')?.length || 0).toBe(
215
+ initialNumberOfEvents
216
+ )
217
+
218
+ // Since we just clicked on image miniature
219
+ // lets check has the corresponding modal been opened
220
+ // and is the correct image displayed
221
+ const imageModalWrapperTestId = testedRadioWrapperWithImage
222
+ .attributes('data-test-id')
223
+ .replace('radioWrapper_', 'radioModal_')
224
+ const imageModalWrapper = testedRadioWrapperWithImage.find(
225
+ `[data-test-id="${imageModalWrapperTestId}"]`
226
+ )
227
+ if (imageModalWrapper.exists()) {
228
+ expect(imageModalWrapper.attributes('class')).toContain('visible')
229
+ const imageWrapperTestId = testedRadioWrapperWithImage
230
+ .attributes('data-test-id')
231
+ .replace('radioWrapper_', 'radioImage_')
232
+ const imageWrapper = testedRadioWrapperWithImage.find(
233
+ `[data-test-id="${imageWrapperTestId}"]`
234
+ )
235
+ if (imageWrapper.exists()) {
236
+ const expectedImageSrc = imageWrapper.attributes('src')
237
+ const modalImage = imageModalWrapper.find('img')
238
+ if (modalImage.exists()) {
239
+ const modalImageSrc = modalImage.attributes('src')
240
+ expect(modalImageSrc).toBe(expectedImageSrc)
241
+ }
242
+ }
243
+ //Close the modal
244
+ const closeButton = radioButtons.find('.visible .close')
245
+ if (closeButton.exists()) {
246
+ await closeButton.trigger('click')
247
+ await radioButtons.vm.$nextTick()
248
+ expect(imageModalWrapper.attributes('class')).toContain('hidden')
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }),
254
+ it('test hover on Info Icon', async () => {
255
+ // Get RadioWrapper with Info Icon
256
+ const arrayOfWrappersWithInfoIcon = findRadioWrappersByCriteria([
257
+ 'hasInfoIcon',
258
+ ])
259
+
260
+ // Ensure we have at least one wrapper with Info Icon
261
+ expect(arrayOfWrappersWithInfoIcon.length).toBeGreaterThan(0)
262
+
263
+ //Select tested item and get expected text within the info badge
264
+ const testedRadioWrapper =
265
+ arrayOfWrappersWithInfoIcon[arrayOfWrappersWithInfoIcon.length - 1]
266
+ const valueOfTestedRadioWrapper = testedRadioWrapper
267
+ .attributes('data-test-id')
268
+ .replace('radioWrapper_', '')
269
+ const expectedText = defaultRadioButtonProps.options.find(
270
+ (el) => el.value === valueOfTestedRadioWrapper
271
+ ).infoText
272
+ const iconForHover = testedRadioWrapper.find(
273
+ '[data-test-id="infoText_trigger"]'
274
+ )
275
+
276
+ // Check if the icon exists before triggering the event
277
+ // expect(iconForHover.exists()).toBe(true)
278
+
279
+ if (iconForHover.exists()) {
280
+ await iconForHover.trigger('mouseenter')
281
+ await radioButtons.vm.$nextTick() // Wait for the next tick to ensure reactivity
282
+ expect(testedRadioWrapper.text()).toContain(expectedText)
283
+ }
284
+ }),
285
+ it('Test the click again after all the manipulations', async () => {
286
+ const uncheckedWrapperArray = findRadioWrappersByCriteria([
287
+ 'unchecked',
288
+ 'active',
289
+ ])
290
+ const expectedValueOfEmittedEvent = uncheckedWrapperArray[0]
291
+ .attributes('data-test-id')
292
+ .replace('radioWrapper_', '')
293
+ const uncheckedLabelWrapper = uncheckedWrapperArray[0].find('label')
294
+ await uncheckedLabelWrapper.trigger('click')
295
+ expect(radioButtons.emitted('on-radio-change')).toBeTruthy()
296
+ const emittedEvents = radioButtons.emitted('on-radio-change')
297
+ expect(emittedEvents).toHaveLength(2) // Check that this is just 2nd emitted event
298
+ // Check the payload of the event
299
+ expect(emittedEvents[emittedEvents.length - 1]).toEqual([
300
+ expectedValueOfEmittedEvent,
301
+ ])
302
+ await radioButtons.setProps({
303
+ selectedOption: expectedValueOfEmittedEvent,
304
+ })
305
+ })
306
+ })
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="29" height="29" fill="#333" viewBox="0 0 29 29"><path d="m10.5 14 4-8 4 8z"/><path class='fix' fill="#ccc" d="m10.5 16 4 8 4-8z"/></svg>
@@ -1,6 +0,0 @@
1
- <svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
2
- viewBox="0 0 512 512" xml:space="preserve">
3
- <path d="M256,0C114.617,0,0,114.615,0,256s114.617,256,256,256s256-114.615,256-256S397.383,0,256,0z M224,320
4
- c0,8.836-7.164,16-16,16h-32c-8.836,0-16-7.164-16-16V192c0-8.836,7.164-16,16-16h32c8.836,0,16,7.164,16,16V320z M352,320
5
- c0,8.836-7.164,16-16,16h-32c-8.836,0-16-7.164-16-16V192c0-8.836,7.164-16,16-16h32c8.836,0,16,7.164,16,16V320z"/>
6
- </svg>