@eturnity/eturnity_reusable_components 9.28.0 → 9.28.2

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.
@@ -1,6 +1,18 @@
1
1
  import { ref } from 'vue'
2
2
  import InputNumberQuestion from './index.vue'
3
- import theme from '@/assets/theme'
3
+
4
+ // import InputNumberQuestion from "@eturnity/eturnity_reusable_components/src/components/inputs/inputNumberQuestion"
5
+ // How to use:
6
+ // <input-number-question
7
+ // :question="question" // required — { number_format_precision, number_min_allowed, number_max_allowed, unit_short_name }
8
+ // :value="inputValue" // required
9
+ // placeholder="Enter distance"
10
+ // inputWidth="150px" // default 100%
11
+ // :isError="false"
12
+ // errorMessage="Enter a number between 1 and 10"
13
+ // @input-change="onInputChange($event)"
14
+ // @on-enter-click="onInputSubmit()"
15
+ // />
4
16
 
5
17
  const questionWithUnit = {
6
18
  number_format_precision: 2,
@@ -23,6 +35,22 @@ export default {
23
35
  parameters: {
24
36
  layout: 'centered',
25
37
  },
38
+ argTypes: {
39
+ question: {
40
+ description:
41
+ 'Required. Drives formatting: number_format_precision, ' +
42
+ 'number_min_allowed, number_max_allowed, unit_short_name.',
43
+ },
44
+ value: { description: 'Current value (required). String or number.' },
45
+ placeholder: { description: 'Placeholder shown when empty' },
46
+ inputWidth: { description: 'Width of the input, defaults to 100%' },
47
+ isError: { description: 'Error state styling', control: 'boolean' },
48
+ errorMessage: { description: 'Message shown while isError is true' },
49
+ clearInput: {
50
+ description: 'When flipped to true, clears the displayed text',
51
+ control: 'boolean',
52
+ },
53
+ },
26
54
  }
27
55
 
28
56
  const Template = (args) => ({
@@ -32,21 +60,14 @@ const Template = (args) => ({
32
60
  return { args, val }
33
61
  },
34
62
  template: `
35
- <div data-test-id="input-number-question-story" style="min-width: 280px; padding: 16px;">
63
+ <div style="min-width: 280px; padding: 16px;">
36
64
  <InputNumberQuestion
37
- :question="args.question"
65
+ v-bind="args"
38
66
  :value="val"
39
- :placeholder="args.placeholder"
40
- :is-error="args.isError"
41
- :input-width="args.inputWidth"
42
- :error-message="args.errorMessage"
43
67
  @input-change="val = $event"
44
68
  />
45
69
  </div>
46
70
  `,
47
- provide: {
48
- theme,
49
- },
50
71
  })
51
72
 
52
73
  export const WithUnit = Template.bind({})
@@ -1,77 +1,116 @@
1
+ import { ref } from 'vue'
1
2
  import Toggle from './index.vue'
3
+ import theme from '@/assets/theme'
4
+
5
+ // import Toggle from "@eturnity/eturnity_reusable_components/src/components/inputs/toggle"
6
+ // To use:
7
+ // <toggle
8
+ // @on-toggle-change="onInputChange($event)"
9
+ // :isChecked="toggleValue" // Boolean
10
+ // label="My Label Text"
11
+ // toggleColor="red"
12
+ // size="small" // small, medium
13
+ // backgroundColor="blue"
14
+ // labelAlign="right"
15
+ // fontColor="black"
16
+ // :disabled="true"
17
+ // infoTextMessage="My info message"
18
+ // />
2
19
 
3
20
  export default {
4
21
  title: 'Components/Inputs/Toggle',
5
22
  component: Toggle,
6
- // argTypes: {},
23
+ tags: ['autodocs'],
24
+ parameters: {
25
+ layout: 'centered',
26
+ },
27
+ argTypes: {
28
+ isChecked: { control: 'boolean' },
29
+ label: { control: 'text' },
30
+ size: { control: 'select', options: ['small', 'medium'] },
31
+ labelAlign: { control: 'select', options: ['left', 'right'] },
32
+ toggleColor: { control: 'color' },
33
+ backgroundColor: { control: 'color' },
34
+ fontColor: { control: 'color' },
35
+ disabled: { control: 'boolean' },
36
+ isRequiredLabel: { control: 'boolean' },
37
+ infoTextMessage: { control: 'text' },
38
+ },
7
39
  }
8
40
 
9
- const Template = (args, { argTypes }) => ({
10
- // Components used in your story `template` are defined in the `components` object
41
+ const Template = (args) => ({
11
42
  components: { Toggle },
12
- // The story's `args` need to be mapped into the template through the `setup()` method
13
- props: Object.keys(argTypes),
14
- template: '<toggle v-bind="$props" />',
15
-
16
- // import Toggle from "@eturnity/eturnity_reusable_components/src/components/inputs/toggle"
17
- //To use:
18
- // <toggle
19
- // @on-toggle-change="onInputChange($event)"
20
- // :isChecked="toggleValue" // Boolean
21
- // label="My Label Text"
22
- // toggleColor="red"
23
- // size="small" // small, medium
24
- // backgroundColor="blue"
25
- // labelAlign="right"
26
- // fontColor="black"
27
- // :disabled="true"
28
- // />
43
+ setup() {
44
+ const isChecked = ref(args.isChecked)
45
+ return { args, isChecked }
46
+ },
47
+ template: `
48
+ <div data-test-id="toggle-story" style="padding: 16px; min-width: 240px;">
49
+ <Toggle
50
+ v-bind="args"
51
+ :isChecked="isChecked"
52
+ @on-toggle-change="isChecked = $event"
53
+ />
54
+ <p style="margin-top: 12px; font-size: 12px; color: #666;">isChecked: {{ isChecked }}</p>
55
+ </div>
56
+ `,
57
+ provide: {
58
+ theme,
59
+ },
29
60
  })
30
61
 
31
62
  export const Default = Template.bind({})
32
63
  Default.args = {
33
64
  isChecked: true,
34
- size: 'small', // "small" or "medium"
65
+ size: 'small',
35
66
  disabled: false,
36
67
  }
37
68
 
38
69
  export const Medium = Template.bind({})
39
70
  Medium.args = {
40
- isChecked: true,
41
- size: 'medium', // "small" or "medium"
42
- disabled: false,
71
+ ...Default.args,
72
+ size: 'medium',
43
73
  }
44
74
 
45
75
  export const CustomColor = Template.bind({})
46
76
  CustomColor.args = {
47
- isChecked: true,
48
- size: 'small', // "small" or "medium"
49
- disabled: false,
77
+ ...Default.args,
50
78
  toggleColor: 'red',
51
79
  backgroundColor: 'blue',
52
80
  }
53
81
 
54
82
  export const LabelLeft = Template.bind({})
55
83
  LabelLeft.args = {
56
- isChecked: true,
57
- size: 'small', // "small" or "medium"
84
+ ...Default.args,
58
85
  label: 'My Label Text',
59
- disabled: false,
60
86
  labelAlign: 'left',
61
87
  }
62
88
 
63
89
  export const LabelRight = Template.bind({})
64
90
  LabelRight.args = {
65
- isChecked: true,
66
- size: 'small', // "small" or "medium"
91
+ ...Default.args,
67
92
  label: 'My Label Text',
68
- disabled: false,
69
93
  labelAlign: 'right',
70
94
  }
71
95
 
96
+ export const WithInfoText = Template.bind({})
97
+ WithInfoText.args = {
98
+ ...Default.args,
99
+ label: 'My Label Text',
100
+ infoTextMessage: 'Applies to this location only.',
101
+ }
102
+
103
+ export const Required = Template.bind({})
104
+ Required.args = {
105
+ ...Default.args,
106
+ label: 'My Label Text',
107
+ labelAlign: 'left',
108
+ isRequiredLabel: true,
109
+ }
110
+
72
111
  export const Disabled = Template.bind({})
73
112
  Disabled.args = {
113
+ ...Default.args,
74
114
  isChecked: false,
75
- size: 'small', // "small" or "medium"
76
115
  disabled: true,
77
116
  }
@@ -1,102 +1,141 @@
1
1
  /* eslint-disable */
2
2
  import { mount } from '@vue/test-utils'
3
3
  import RCToggle from '@/components/inputs/toggle'
4
- import theme from '@/assets/theme'
4
+
5
+ // `theme` and `$gettext` are provided globally by jest.setup.js, so specs do
6
+ // not need to re-provide them. See the canonical example at
7
+ // src/components/buttons/closeButton/closeButton.spec.js.
5
8
 
6
9
  jest.mock('@/components/icon/iconCache.mjs', () => ({
7
10
  // need to mock this due to how jest handles import.meta
8
11
  fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
9
12
  }))
10
13
 
11
- describe('RCToggle.vue', () => {
12
- it('toggle is rendered and emits correct payload on change', async () => {
13
- const wrapper = mount(RCToggle, {
14
- props: { isChecked: true, label: 'Test label', disabled: false },
15
- global: {
16
- provide: {
17
- theme,
18
- },
19
- },
20
- })
21
- const toggleWrapper = wrapper.find('[data-test-id="toggle_wrapper"]')
14
+ describe('RCToggle', () => {
15
+ const mountToggle = (props = {}) =>
16
+ mount(RCToggle, { props: { isChecked: false, ...props } })
22
17
 
23
- // check that the element exists
18
+ it('renders the toggle and reflects the checked state', () => {
19
+ const wrapper = mountToggle({ isChecked: true })
20
+ const toggleWrapper = wrapper.find('[data-test-id="toggle_wrapper"]')
24
21
  expect(toggleWrapper.exists()).toBe(true)
25
22
  expect(toggleWrapper.attributes('checked')).toBe('true')
23
+ })
24
+
25
+ it('reflects the unchecked state', () => {
26
+ const wrapper = mountToggle({ isChecked: false })
27
+ const toggleWrapper = wrapper.find('[data-test-id="toggle_wrapper"]')
28
+ expect(toggleWrapper.attributes('checked')).toBe('false')
29
+ })
26
30
 
27
- // Log attributes to see what is rendered (commented out just for reference)
28
- // console.log('attributes', toggleWrapper.attributes())
29
- // console.log('All of the Toggles' attributes', wrapper.vm)
31
+ it('emits on-toggle-change with the negated value when clicked', async () => {
32
+ const wrapper = mountToggle({ isChecked: true })
33
+ await wrapper.find('[data-test-id="page_wrapper"]').trigger('click')
30
34
 
31
- // Test the label
32
- expect(wrapper.text()).toContain('Test label')
35
+ const emitted = wrapper.emitted('on-toggle-change')
36
+ expect(emitted).toHaveLength(1)
37
+ expect(emitted[0]).toEqual([false])
38
+ })
33
39
 
34
- // Test the click
40
+ it('emits on-toggle-change when the space key is pressed', async () => {
41
+ const wrapper = mountToggle({ isChecked: false })
42
+ await wrapper
43
+ .find('[data-test-id="toggle_wrapper"]')
44
+ .trigger('keydown.space')
45
+
46
+ const emitted = wrapper.emitted('on-toggle-change')
47
+ expect(emitted).toHaveLength(1)
48
+ expect(emitted[0]).toEqual([true])
49
+ })
50
+
51
+ it('does not emit when disabled and clicked', async () => {
52
+ const wrapper = mountToggle({ isChecked: false, disabled: true })
53
+ await wrapper.find('[data-test-id="page_wrapper"]').trigger('click')
54
+
55
+ expect(wrapper.emitted('on-toggle-change')).toBeUndefined()
56
+ })
57
+
58
+ it('toggles back and forth across successive clicks', async () => {
59
+ const wrapper = mountToggle({ isChecked: false })
35
60
  const pageWrapper = wrapper.find('[data-test-id="page_wrapper"]')
61
+
36
62
  await pageWrapper.trigger('click')
37
- expect(wrapper.emitted('on-toggle-change')).toBeTruthy()
38
- const emittedEvent = wrapper.emitted('on-toggle-change')
39
- // To inspect emitted events:
40
- // console.log('Emitted events', wrapper.emitted())
41
- expect(emittedEvent).toHaveLength(1) // Check if the event was emitted exactly once
42
- // Check the payload of the event
43
- expect(emittedEvent[0]).toEqual([false])
44
- }),
45
- it('toggle disabled does not emit anything', async () => {
46
- const wrapper = mount(RCToggle, {
47
- props: { isChecked: false, disabled: true },
48
- global: {
49
- provide: {
50
- theme,
51
- },
52
- },
53
- })
54
- const toggleWrapper = wrapper.find('[data-test-id="toggle_wrapper"]')
55
- expect(toggleWrapper.attributes('checked')).toBe('false')
56
-
57
- // Test the click
58
- const pageWrapper = wrapper.find('[data-test-id="page_wrapper"]')
59
- await pageWrapper.trigger('click')
60
-
61
- // Inspect emitted events
62
- const emittedEvents = wrapper.emitted('on-toggle-change')
63
-
64
- // Verify that no events were emitted
65
- expect(emittedEvents).toBeUndefined() // No event should be emitted
66
- }),
67
- it('should toggle back and forth on multiple clicks', async () => {
68
- const wrapper = mount(RCToggle, {
69
- props: { isChecked: false, disabled: false },
70
- global: {
71
- provide: {
72
- theme,
73
- },
74
- },
63
+ await wrapper.setProps({ isChecked: true })
64
+ await pageWrapper.trigger('click')
65
+ await wrapper.setProps({ isChecked: false })
66
+ await pageWrapper.trigger('click')
67
+
68
+ const emitted = wrapper.emitted('on-toggle-change')
69
+ expect(emitted).toHaveLength(3)
70
+ expect(emitted[0]).toEqual([true])
71
+ expect(emitted[1]).toEqual([false])
72
+ expect(emitted[2]).toEqual([true])
73
+ })
74
+
75
+ it('renders no label container when no label is provided', () => {
76
+ const wrapper = mountToggle()
77
+ expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
78
+ false
79
+ )
80
+ expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
81
+ false
82
+ )
83
+ })
84
+
85
+ it('renders the label on the right by default', () => {
86
+ const wrapper = mountToggle({ label: 'My Label' })
87
+ expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
88
+ true
89
+ )
90
+ expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
91
+ false
92
+ )
93
+ expect(wrapper.text()).toContain('My Label')
94
+ })
95
+
96
+ it('renders the label on the left when labelAlign is "left"', () => {
97
+ const wrapper = mountToggle({ label: 'My Label', labelAlign: 'left' })
98
+ expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
99
+ true
100
+ )
101
+ expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
102
+ false
103
+ )
104
+ })
105
+
106
+ it('renders the info text only when infoTextMessage is provided', () => {
107
+ expect(
108
+ mountToggle({ label: 'My Label', labelAlign: 'left' })
109
+ .find('[data-test-id="infoText_container"]')
110
+ .exists()
111
+ ).toBe(false)
112
+
113
+ expect(
114
+ mountToggle({
115
+ label: 'My Label',
116
+ labelAlign: 'left',
117
+ infoTextMessage: 'Some info',
75
118
  })
76
- const toggleWrapper = wrapper.find('[data-test-id="toggle_wrapper"]')
77
- // Initial state
78
- expect(toggleWrapper.attributes('checked')).toBe('false')
79
-
80
- // Trigger click and check events
81
- const pageWrapper = wrapper.find('[data-test-id="page_wrapper"]')
82
- await pageWrapper.trigger('click')
83
- let emittedEvent = wrapper.emitted('on-toggle-change')
84
- expect(emittedEvent).toBeTruthy() // Ensure that the event was emitted
85
- expect(emittedEvent).toHaveLength(1) // Ensure that only one event was emitted
86
- expect(emittedEvent[0]).toEqual([true]) // Check the payload of the first click
87
- await wrapper.setProps({ isChecked: true }) // manually update the props
88
-
89
- // Trigger click again and check events
90
- await pageWrapper.trigger('click')
91
- emittedEvent = wrapper.emitted('on-toggle-change')
92
- expect(emittedEvent).toHaveLength(2) // Ensure that the event count has increased
93
- expect(emittedEvent[1]).toEqual([false]) // Check the payload of the second click
94
- await wrapper.setProps({ isChecked: false }) // manually update the props
95
-
96
- // Trigger click again and check events
97
- await pageWrapper.trigger('click')
98
- emittedEvent = wrapper.emitted('on-toggle-change')
99
- expect(emittedEvent).toHaveLength(3) // Ensure that the event count has increased
100
- expect(emittedEvent[2]).toEqual([true]) // Check the payload of the third click
119
+ .find('[data-test-id="infoText_container"]')
120
+ .exists()
121
+ ).toBe(true)
122
+ })
123
+
124
+ it('renders the required star only when isRequiredLabel is true', () => {
125
+ const withStar = mountToggle({
126
+ label: 'My Label',
127
+ labelAlign: 'left',
128
+ isRequiredLabel: true,
101
129
  })
130
+ expect(withStar.text()).toContain('*')
131
+
132
+ const withoutStar = mountToggle({ label: 'My Label', labelAlign: 'left' })
133
+ expect(withoutStar.text()).not.toContain('*')
134
+ })
135
+
136
+ it('exposes dataId and dataQaId as data attributes', () => {
137
+ const wrapper = mountToggle({ dataId: 'my_toggle', dataQaId: 'qa_toggle' })
138
+ expect(wrapper.find('[data-id="my_toggle"]').exists()).toBe(true)
139
+ expect(wrapper.find('[data-qa-id="qa_toggle"]').exists()).toBe(true)
140
+ })
102
141
  })
@@ -0,0 +1,55 @@
1
+ /* eslint-disable */
2
+ import { mount } from '@vue/test-utils'
3
+ import Label from '@/components/label'
4
+
5
+ // `theme` and `$gettext` are provided globally by jest.setup.js, so specs do
6
+ // not need to re-provide them. See the canonical example at
7
+ // src/components/buttons/closeButton/closeButton.spec.js.
8
+
9
+ jest.mock('@/components/icon/iconCache.mjs', () => ({
10
+ // need to mock this due to how jest handles import.meta
11
+ fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
12
+ }))
13
+
14
+ describe('Label', () => {
15
+ const mountLabel = (props = {}, slot = 'My label') =>
16
+ mount(Label, { props, slots: { default: slot } })
17
+
18
+ it('renders the default slot content', () => {
19
+ const wrapper = mountLabel({}, 'Project name')
20
+ expect(wrapper.text()).toContain('Project name')
21
+ })
22
+
23
+ it('renders the required star only when isRequiredLabel is true', () => {
24
+ expect(mountLabel({ isRequiredLabel: true }).text()).toContain('*')
25
+ expect(mountLabel({ isRequiredLabel: false }).text()).not.toContain('*')
26
+ })
27
+
28
+ it('renders the optional label only when labelOptional is true', () => {
29
+ expect(mountLabel({ labelOptional: true }).text()).toContain('(optional)')
30
+ expect(mountLabel({ labelOptional: false }).text()).not.toContain(
31
+ '(optional)'
32
+ )
33
+ })
34
+
35
+ it('renders the info text only when infoTextMessage is provided', () => {
36
+ expect(
37
+ mountLabel().find('[data-test-id="infoText_container"]').exists()
38
+ ).toBe(false)
39
+
40
+ expect(
41
+ mountLabel({ infoTextMessage: 'Some info' })
42
+ .find('[data-test-id="infoText_container"]')
43
+ .exists()
44
+ ).toBe(true)
45
+ })
46
+
47
+ it('renders both the required star and the optional label together', () => {
48
+ const text = mountLabel({
49
+ isRequiredLabel: true,
50
+ labelOptional: true,
51
+ }).text()
52
+ expect(text).toContain('*')
53
+ expect(text).toContain('(optional)')
54
+ })
55
+ })
@@ -16,6 +16,7 @@ export default {
16
16
  labelOptional: { control: 'boolean' },
17
17
  infoTextMessage: { control: 'text' },
18
18
  infoTextAlign: { control: 'select', options: ['left', 'right'] },
19
+ appTheme: { control: 'select', options: ['light', 'dark'] },
19
20
  },
20
21
  }
21
22
 
@@ -67,3 +68,38 @@ HorizontalLayout.args = {
67
68
  slotLabel: 'Budget (CHF)',
68
69
  labelAlign: 'horizontal',
69
70
  }
71
+
72
+ export const CustomFontStyle = Template.bind({})
73
+ CustomFontStyle.args = {
74
+ ...Default.args,
75
+ slotLabel: 'Section heading',
76
+ fontSize: '20px',
77
+ labelFontColor: 'green',
78
+ }
79
+
80
+ const DarkTemplate = (args) => ({
81
+ components: { LabelText },
82
+ setup() {
83
+ return { args }
84
+ },
85
+ template: `
86
+ <div
87
+ data-test-id="label-story"
88
+ style="padding: 16px; max-width: 420px; background: #1f1f1f;"
89
+ >
90
+ <LabelText v-bind="args">
91
+ {{ args.slotLabel }}
92
+ </LabelText>
93
+ </div>
94
+ `,
95
+ provide: {
96
+ theme,
97
+ },
98
+ })
99
+
100
+ export const DarkTheme = DarkTemplate.bind({})
101
+ DarkTheme.args = {
102
+ ...Default.args,
103
+ slotLabel: 'Project name',
104
+ appTheme: 'dark',
105
+ }
@@ -0,0 +1,46 @@
1
+ /* eslint-disable */
2
+ import { mount } from '@vue/test-utils'
3
+ import MarkerItem from '@/components/markerItem'
4
+
5
+ // `theme` and `$gettext` are provided globally by jest.setup.js, so specs do
6
+ // not need to re-provide them. See the canonical example at
7
+ // src/components/buttons/closeButton/closeButton.spec.js.
8
+
9
+ jest.mock('@/components/icon/iconCache.mjs', () => ({
10
+ // need to mock this due to how jest handles import.meta
11
+ fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
12
+ }))
13
+
14
+ describe('MarkerItem', () => {
15
+ const mountMarker = (props = {}) =>
16
+ mount(MarkerItem, { props: { label: 'Marker', ...props } })
17
+
18
+ it('renders the label text', () => {
19
+ const wrapper = mountMarker({ label: 'Highlights' })
20
+ expect(wrapper.text()).toContain('Highlights')
21
+ })
22
+
23
+ it('exposes the label as the span title attribute', () => {
24
+ const wrapper = mountMarker({ label: 'Highlights' })
25
+ expect(wrapper.find('span').attributes('title')).toBe('Highlights')
26
+ })
27
+
28
+ it('renders an icon only when iconName is provided', () => {
29
+ expect(
30
+ mountMarker({ iconName: 'House' })
31
+ .find('[data-test-id="icon_wrapper"]')
32
+ .exists()
33
+ ).toBe(true)
34
+
35
+ expect(
36
+ mountMarker().find('[data-test-id="icon_wrapper"]').exists()
37
+ ).toBe(false)
38
+ })
39
+
40
+ it('passes the iconName through to the Icon component', () => {
41
+ const wrapper = mountMarker({ iconName: 'House' })
42
+ expect(wrapper.findComponent({ name: 'EturnityIcon' }).exists()).toBe(true)
43
+ // EturnityIcon forwards `name` to the inner Icon, which declares it as a prop
44
+ expect(wrapper.findComponent({ name: 'Icon' }).props('name')).toBe('House')
45
+ })
46
+ })
@@ -44,3 +44,10 @@ TextOnly.args = {
44
44
  label: 'No icon variant',
45
45
  cursor: 'default',
46
46
  }
47
+
48
+ export const CustomColor = Template.bind({})
49
+ CustomColor.args = {
50
+ label: 'On hold',
51
+ backgroundColor: '#ab5348',
52
+ cursor: 'default',
53
+ }