@saooti/octopus-sdk 41.11.0 → 41.12.0-beta

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,20 +1,32 @@
1
1
  export function deepEqual(obj1: any, obj2: any) {
2
+ // it's just the same object. No need to compare.
3
+ if(obj1 === obj2) {
4
+ return true;
5
+ }
2
6
 
3
- if(obj1 === obj2) // it's just the same object. No need to compare.
4
- {return true;}
7
+ // compare primitives
8
+ if(isPrimitive(obj1) && isPrimitive(obj2)) {
9
+ return obj1 === obj2;
10
+ }
5
11
 
6
- if(isPrimitive(obj1) && isPrimitive(obj2)) // compare primitives
7
- {return obj1 === obj2;}
12
+ // one is a primitive but not the other (for example undefined vs array)
13
+ if (isPrimitive(obj1) || isPrimitive(obj2)) {
14
+ return false;
15
+ }
8
16
 
9
17
  if(Object.keys(obj1).length !== Object.keys(obj2).length) {
10
18
  return false;
11
19
  }
12
20
 
13
21
  // compare objects with same number of keys
14
- for(const key in obj1)
15
- {
16
- if(!(key in obj2)) {return false;} //other object doesn't have this prop
17
- if(!deepEqual(obj1[key], obj2[key])) {return false;}
22
+ for(const key in obj1) {
23
+ //other object doesn't have this prop
24
+ if(!(key in obj2)) {
25
+ return false;
26
+ }
27
+ if(!deepEqual(obj1[key], obj2[key])) {
28
+ return false;
29
+ }
18
30
  }
19
31
 
20
32
  return true;
@@ -23,4 +35,4 @@ export function deepEqual(obj1: any, obj2: any) {
23
35
  //check if value is primitive
24
36
  function isPrimitive(obj: unknown) {
25
37
  return (obj !== Object(obj));
26
- }
38
+ }
@@ -129,6 +129,14 @@ export function simplifiedToFull(simplified: SimplifiedPodcast, organisation: Or
129
129
  };
130
130
  }
131
131
 
132
+ export function podcastToSimplified(full: Podcast): SimplifiedPodcast {
133
+ return {
134
+ ...full,
135
+ emissionId: full.emission.emissionId,
136
+ organisationId: full.organisation.id
137
+ };
138
+ }
139
+
132
140
  export function emptyPodcastData(): Podcast {
133
141
  return {
134
142
  podcastId: 0,
@@ -297,6 +297,7 @@ $utilities: map.merge(
297
297
  class: fw,
298
298
  values: (
299
299
  normal: 400,
300
+ 600: 600,
300
301
  bold: 700,
301
302
  )
302
303
  ),
@@ -219,6 +219,13 @@ input:not([class^="vs__"]), button:not([class^="vs__"]), select:not([class^="vs_
219
219
  border: 0;
220
220
  }
221
221
 
222
+ .btn-icon {
223
+ background: transparent;
224
+ border: 0;
225
+ border-radius: 100%;
226
+ height: fit-content;
227
+ }
228
+
222
229
  .share-btn {
223
230
  display: inline-flex;
224
231
  align-items: center;
@@ -11,14 +11,14 @@ const options = [
11
11
  describe('ClassicButtonGroup', () => {
12
12
  it('renders one button per option', async () => {
13
13
  const wrapper = await mount(ClassicButtonGroup, {
14
- props: { options, modelValue: ['PODCAST'] },
14
+ props: { options, value: ['PODCAST'] },
15
15
  });
16
16
  expect(wrapper.findAll('button')).toHaveLength(3);
17
17
  });
18
18
 
19
19
  it('applies active class to selected options only', async () => {
20
20
  const wrapper = await mount(ClassicButtonGroup, {
21
- props: { options, modelValue: ['PODCAST', 'LIVE'] },
21
+ props: { options, value: ['PODCAST', 'LIVE'] },
22
22
  });
23
23
  const buttons = wrapper.findAll('button');
24
24
  expect(buttons[0].classes()).toContain('active');
@@ -26,27 +26,27 @@ describe('ClassicButtonGroup', () => {
26
26
  expect(buttons[2].classes()).toContain('active');
27
27
  });
28
28
 
29
- it('emits update:modelValue with added value when clicking inactive button', async () => {
29
+ it('emits update:value with added value when clicking inactive button', async () => {
30
30
  const wrapper = await mount(ClassicButtonGroup, {
31
- props: { options, modelValue: ['PODCAST'] },
31
+ props: { options, value: ['PODCAST'] },
32
32
  });
33
33
  await wrapper.findAll('button')[1].trigger('click');
34
- expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([['PODCAST', 'VIDEO']]);
34
+ expect(wrapper.emitted('update:value')?.[0]).toEqual([['PODCAST', 'VIDEO']]);
35
35
  });
36
36
 
37
- it('emits update:modelValue without removed value when clicking active button (not last)', async () => {
37
+ it('emits update:value without removed value when clicking active button (not last)', async () => {
38
38
  const wrapper = await mount(ClassicButtonGroup, {
39
- props: { options, modelValue: ['PODCAST', 'VIDEO'] },
39
+ props: { options, value: ['PODCAST', 'VIDEO'] },
40
40
  });
41
41
  await wrapper.findAll('button')[0].trigger('click');
42
- expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([['VIDEO']]);
42
+ expect(wrapper.emitted('update:value')?.[0]).toEqual([['VIDEO']]);
43
43
  });
44
44
 
45
45
  it('does not emit when clicking the only active button', async () => {
46
46
  const wrapper = await mount(ClassicButtonGroup, {
47
- props: { options, modelValue: ['PODCAST'] },
47
+ props: { options, value: ['PODCAST'] },
48
48
  });
49
49
  await wrapper.findAll('button')[0].trigger('click');
50
- expect(wrapper.emitted('update:modelValue')).toBeUndefined();
50
+ expect(wrapper.emitted('update:value')).toBeUndefined();
51
51
  });
52
52
  });
@@ -120,20 +120,19 @@ describe('OctopusMultiselect', () => {
120
120
  expect(wrapper.emitted('update:selected')?.[0]).toEqual([[]]);
121
121
  });
122
122
 
123
- it('shows selected items summary in placeholder', async () => {
123
+ it('shows selected items in the selection display', async () => {
124
124
  const wrapper = await mount(OctopusMultiselect, {
125
125
  props: { options, optionLabel: 'name', selected: [options[0], options[1]] },
126
126
  });
127
- const input = wrapper.find('input');
128
- expect(input.attributes('placeholder')).toBe('Alpha, Beta');
127
+ expect(wrapper.find('.octopus-multiselect-selection-text').text()).toBe('Alpha, Beta');
128
+ expect(wrapper.find('.octopus-multiselect-selection-count').exists()).toBe(false);
129
129
  });
130
130
 
131
- it('shows count in placeholder when more than 2 items selected', async () => {
131
+ it('shows overflow count badge when more than visible items are selected', async () => {
132
132
  const wrapper = await mount(OctopusMultiselect, {
133
133
  props: { options, optionLabel: 'name', selected: [...options] },
134
134
  });
135
- const input = wrapper.find('input');
136
- expect(input.attributes('placeholder')).toBe('Alpha, Beta (+1)');
135
+ expect(wrapper.find('.octopus-multiselect-selection-count').exists()).toBe(true);
137
136
  });
138
137
 
139
138
  it('disables input when isDisabled is true', async () => {
@@ -159,4 +158,26 @@ describe('OctopusMultiselect', () => {
159
158
  const allCheckboxLabel = wrapper.find('.octopus-multiselect-dropdown > .octopus-form-item label');
160
159
  expect(allCheckboxLabel.text()).toBe('Tout');
161
160
  });
161
+
162
+ it('deselects by optionKey even when selected objects are different references', async () => {
163
+ const selected = [{ id: 1, name: 'Alpha' }];
164
+ const wrapper = await mount(OctopusMultiselect, {
165
+ props: { options, optionLabel: 'name', optionKey: 'id', selected },
166
+ });
167
+ await wrapper.find('input').trigger('focus');
168
+ const optionCheckboxes = wrapper.findAll('.octopus-multiselect-options input[type="checkbox"]');
169
+ await optionCheckboxes[0].trigger('input');
170
+ expect(wrapper.emitted('update:selected')?.[0]).toEqual([[]]);
171
+ });
172
+
173
+ it('toggleAll deselects by optionKey even when selected objects are different references', async () => {
174
+ const selected = options.map((o) => ({ ...o }));
175
+ const wrapper = await mount(OctopusMultiselect, {
176
+ props: { options, optionLabel: 'name', optionKey: 'id', selected },
177
+ });
178
+ await wrapper.find('input').trigger('focus');
179
+ const allCheckbox = wrapper.find('.octopus-multiselect-dropdown > .octopus-form-item input[type="checkbox"]');
180
+ await allCheckbox.trigger('input');
181
+ expect(wrapper.emitted('update:selected')?.[0]).toEqual([[]]);
182
+ });
162
183
  });
@@ -0,0 +1,143 @@
1
+ import '@tests/mocks/i18n';
2
+
3
+ import OctopusSelect from '@/components/form/OctopusSelect.vue';
4
+ import { mount } from '@tests/utils';
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ const options = [
8
+ { id: 1, name: 'Alpha' },
9
+ { id: 2, name: 'Beta' },
10
+ { id: 3, name: 'Gamma' },
11
+ ];
12
+
13
+ describe('OctopusSelect', () => {
14
+ it('renders label when provided', async () => {
15
+ const wrapper = await mount(OctopusSelect, {
16
+ props: { options, optionLabel: 'name', label: 'My label' },
17
+ });
18
+ expect(wrapper.find('label.form-label').text()).toBe('My label');
19
+ });
20
+
21
+ it('does not render label when not provided', async () => {
22
+ const wrapper = await mount(OctopusSelect, {
23
+ props: { options, optionLabel: 'name' },
24
+ });
25
+ expect(wrapper.find('label.form-label').exists()).toBe(false);
26
+ });
27
+
28
+ it('opens dropdown on input focus', async () => {
29
+ const wrapper = await mount(OctopusSelect, {
30
+ props: { options, optionLabel: 'name' },
31
+ });
32
+ expect(wrapper.find('.octopus-select-dropdown').exists()).toBe(false);
33
+ await wrapper.find('input').trigger('focus');
34
+ expect(wrapper.find('.octopus-select-dropdown').exists()).toBe(true);
35
+ });
36
+
37
+ it('shows one button per option when open', async () => {
38
+ const wrapper = await mount(OctopusSelect, {
39
+ props: { options, optionLabel: 'name' },
40
+ });
41
+ await wrapper.find('input').trigger('focus');
42
+ const optionButtons = wrapper.findAll('.octopus-select-option');
43
+ expect(optionButtons).toHaveLength(3);
44
+ expect(optionButtons[0].text()).toBe('Alpha');
45
+ });
46
+
47
+ it('filters options locally by search query', async () => {
48
+ const wrapper = await mount(OctopusSelect, {
49
+ props: { options, optionLabel: 'name' },
50
+ });
51
+ await wrapper.find('input').trigger('focus');
52
+ await wrapper.find('input').setValue('al');
53
+ await wrapper.find('input').trigger('input');
54
+ expect(wrapper.findAll('.octopus-select-option')).toHaveLength(1);
55
+ });
56
+
57
+ it('shows no-results message when filter matches nothing', async () => {
58
+ const wrapper = await mount(OctopusSelect, {
59
+ props: { options, optionLabel: 'name' },
60
+ });
61
+ await wrapper.find('input').trigger('focus');
62
+ await wrapper.find('input').setValue('zzz');
63
+ await wrapper.find('input').trigger('input');
64
+ expect(wrapper.find('.text-indic').exists()).toBe(true);
65
+ });
66
+
67
+ it('emits update:value with the clicked option', async () => {
68
+ const wrapper = await mount(OctopusSelect, {
69
+ props: { options, optionLabel: 'name' },
70
+ });
71
+ await wrapper.find('input').trigger('focus');
72
+ await wrapper.findAll('.octopus-select-option')[1].trigger('click');
73
+ expect(wrapper.emitted('update:value')?.[0]).toEqual([options[1]]);
74
+ });
75
+
76
+ it('closes dropdown after selecting an option', async () => {
77
+ const wrapper = await mount(OctopusSelect, {
78
+ props: { options, optionLabel: 'name' },
79
+ });
80
+ await wrapper.find('input').trigger('focus');
81
+ await wrapper.findAll('.octopus-select-option')[0].trigger('click');
82
+ expect(wrapper.find('.octopus-select-dropdown').exists()).toBe(false);
83
+ });
84
+
85
+ it('shows selected item label in the field when closed', async () => {
86
+ const wrapper = await mount(OctopusSelect, {
87
+ props: { options, optionLabel: 'name', value: options[0] },
88
+ });
89
+ expect(wrapper.find('.octopus-select-value').text()).toBe('Alpha');
90
+ });
91
+
92
+ it('marks the selected option with the selected class', async () => {
93
+ const wrapper = await mount(OctopusSelect, {
94
+ props: { options, optionLabel: 'name', value: options[0] },
95
+ });
96
+ await wrapper.find('.octopus-select-field').trigger('click');
97
+ const optionButtons = wrapper.findAll('.octopus-select-option');
98
+ expect(optionButtons[0].classes()).toContain('selected');
99
+ expect(optionButtons[1].classes()).not.toContain('selected');
100
+ });
101
+
102
+ it('deselects the current option by default (allowDeselect defaults to true)', async () => {
103
+ const wrapper = await mount(OctopusSelect, {
104
+ props: { options, optionLabel: 'name', optionKey: 'id', value: options[0] },
105
+ });
106
+ await wrapper.find('.octopus-select-field').trigger('click');
107
+ await wrapper.findAll('.octopus-select-option')[0].trigger('click');
108
+ expect(wrapper.emitted('update:value')?.[0]).toEqual([undefined]);
109
+ });
110
+
111
+ it('does not deselect when allowDeselect is false', async () => {
112
+ const wrapper = await mount(OctopusSelect, {
113
+ props: { options, optionLabel: 'name', optionKey: 'id', value: options[0], allowDeselect: false as boolean },
114
+ });
115
+ await wrapper.find('.octopus-select-field').trigger('click');
116
+ await wrapper.findAll('.octopus-select-option')[0].trigger('click');
117
+ expect(wrapper.emitted('update:value')?.[0]).toEqual([options[0]]);
118
+ });
119
+
120
+ it('does not open dropdown when disabled', async () => {
121
+ const wrapper = await mount(OctopusSelect, {
122
+ props: { options, optionLabel: 'name', isDisabled: true },
123
+ });
124
+ await wrapper.find('input').trigger('focus');
125
+ expect(wrapper.find('.octopus-select-dropdown').exists()).toBe(false);
126
+ });
127
+
128
+ it('disables input when isDisabled is true', async () => {
129
+ const wrapper = await mount(OctopusSelect, {
130
+ props: { options, optionLabel: 'name', isDisabled: true },
131
+ });
132
+ expect(wrapper.find('input').attributes('disabled')).toBeDefined();
133
+ });
134
+
135
+ it('matches by optionKey when checking selection', async () => {
136
+ const value = { id: 1, name: 'Alpha' };
137
+ const wrapper = await mount(OctopusSelect, {
138
+ props: { options, optionLabel: 'name', optionKey: 'id', value },
139
+ });
140
+ await wrapper.find('.octopus-select-field').trigger('click');
141
+ expect(wrapper.findAll('.octopus-select-option')[0].classes()).toContain('selected');
142
+ });
143
+ });