@ouestfrance/sipa-bms-ui 8.9.3 → 8.10.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouestfrance/sipa-bms-ui",
3
- "version": "8.9.3",
3
+ "version": "8.10.0",
4
4
  "author": "Ouest-France BMS",
5
5
  "license": "ISC",
6
6
  "scripts": {
@@ -62,6 +62,7 @@ const Template = (args) => ({
62
62
  </template>
63
63
  <template #default>
64
64
  <h3>This is content</h3>
65
+ <p v-html="args.content"/>
65
66
  <BmsProblem
66
67
  v-if="args.openNotification"
67
68
  :problem="args.openNotification"
@@ -80,6 +81,14 @@ Default.args = {
80
81
  title: 'Titre',
81
82
  };
82
83
 
84
+ export const WithScroll = Template.bind({});
85
+ WithScroll.args = {
86
+ modelValue: true,
87
+ title: 'Titre',
88
+ content:
89
+ 'This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>This is a big content for scrolling<br/>',
90
+ };
91
+
83
92
  export const Success = Template.bind({});
84
93
  Success.args = {
85
94
  modelValue: true,
@@ -267,6 +267,7 @@ onUnmounted(() => {
267
267
  align-items: center;
268
268
  justify-content: center;
269
269
  background: var(--bms-white);
270
+ z-index: calc(var(--bms-z-index-fixed) + 5);
270
271
 
271
272
  p {
272
273
  margin: 0;
@@ -308,6 +309,7 @@ onUnmounted(() => {
308
309
  margin: -1em;
309
310
  position: sticky;
310
311
  background: var(--bms-white);
312
+ z-index: var(--bms-z-index-fixed);
311
313
  }
312
314
 
313
315
  &__header {
@@ -1,26 +1,74 @@
1
- import BmsPagination from '@/components/table/BmsPagination.vue';
2
- import { field } from '@/plugins/field';
3
1
  import { mount, shallowMount } from '@vue/test-utils';
2
+ import { describe, test, expect, vi, beforeEach } from 'vitest';
3
+ import BmsPagination from './BmsPagination.vue';
4
4
 
5
+ // Mock field plugin
6
+ const mockField = {
7
+ install: vi.fn(),
8
+ };
9
+
10
+ // Mock IntersectionObserver
5
11
  const intersectionObserverMock = () => ({
6
12
  observe: () => null,
7
13
  });
8
14
 
15
+ // Mock child components
16
+ vi.mock('@/components/button/BmsIconButton.vue', () => ({
17
+ default: {
18
+ name: 'BmsIconButton',
19
+ template: '<button><slot /></button>',
20
+ props: ['color', 'disabled'],
21
+ },
22
+ }));
23
+
24
+ vi.mock('@/index', () => ({
25
+ BmsSelect: {
26
+ name: 'BmsSelect',
27
+ template:
28
+ '<select><option v-for="option in options" :key="option.value" :value="option.value">{{ option.label }}</option></select>',
29
+ props: ['modelValue', 'options'],
30
+ emits: ['update:modelValue'],
31
+ },
32
+ }));
33
+
34
+ vi.mock('lucide-vue-next', () => ({
35
+ ChevronFirst: { name: 'ChevronFirst', template: '<span>First</span>' },
36
+ ChevronLast: { name: 'ChevronLast', template: '<span>Last</span>' },
37
+ ChevronLeft: { name: 'ChevronLeft', template: '<span>Left</span>' },
38
+ ChevronRight: { name: 'ChevronRight', template: '<span>Right</span>' },
39
+ }));
40
+
9
41
  const shallowFactory = (props: any) => {
10
42
  return shallowMount(BmsPagination, {
11
43
  global: {
12
- plugins: [field],
44
+ plugins: [mockField],
13
45
  },
14
- propsData: { ...props },
46
+ props: { ...props },
15
47
  });
16
48
  };
17
49
 
18
50
  const factory = (props: any) => {
19
51
  return mount(BmsPagination, {
20
52
  global: {
21
- plugins: [field],
53
+ plugins: [mockField],
54
+ stubs: {
55
+ BmsSelect: {
56
+ name: 'BmsSelect',
57
+ template:
58
+ '<select v-model="modelValue"><option v-for="option in options" :key="option.value" :value="option.value">{{ option.label }}</option></select>',
59
+ props: ['modelValue', 'options'],
60
+ emits: ['update:modelValue'],
61
+ },
62
+ BmsIconButton: {
63
+ name: 'BmsIconButton',
64
+ template:
65
+ '<button @click="$emit(\'click\')" :disabled="disabled"><slot /></button>',
66
+ props: ['color', 'disabled'],
67
+ emits: ['click'],
68
+ },
69
+ },
22
70
  },
23
- propsData: { ...props },
71
+ props: { ...props },
24
72
  });
25
73
  };
26
74
 
@@ -31,7 +79,8 @@ describe('BmsPagination', () => {
31
79
  .fn()
32
80
  .mockImplementation(intersectionObserverMock);
33
81
  });
34
- it('should display correctly', () => {
82
+
83
+ test('should display correctly', () => {
35
84
  const wrapper = shallowFactory({
36
85
  sizes: [25, 50],
37
86
  total: 120,
@@ -40,10 +89,10 @@ describe('BmsPagination', () => {
40
89
  pages: Math.ceil(120 / 25),
41
90
  });
42
91
 
43
- expect(wrapper.text()).toContain('Éléments par page : 1 à 25 / 120');
92
+ expect(wrapper.text()).toContain('1 à 25');
44
93
  });
45
94
 
46
- it('should emit correct navigation events', async () => {
95
+ test('should emit correct navigation events', async () => {
47
96
  const wrapper = factory({
48
97
  sizes: [25, 50],
49
98
  total: 120,
@@ -52,22 +101,69 @@ describe('BmsPagination', () => {
52
101
  pages: Math.ceil(120 / 25),
53
102
  });
54
103
 
55
- const actions = wrapper.find('.bms-pagination__actions').findAll('button');
56
- const goToBeginning = actions[0];
57
- const goBack = actions[1];
58
- const goForward = actions[2];
59
- const goToEnding = actions[3];
104
+ const buttons = wrapper.findAllComponents({ name: 'BmsIconButton' });
105
+ const goToBeginning = buttons[0];
106
+ const goBack = buttons[1];
107
+ const goForward = buttons[2];
108
+ const goToEnding = buttons[3];
60
109
 
61
110
  await goToBeginning.trigger('click');
62
- expect(wrapper.emitted().go[0]).toEqual([0]);
111
+ expect(wrapper.emitted('go')?.[0]).toEqual([0]);
63
112
 
64
113
  await goBack.trigger('click');
65
- expect(wrapper.emitted().prev[0]).toEqual([]);
114
+ expect(wrapper.emitted('prev')?.[0]).toEqual([]);
66
115
 
67
116
  await goForward.trigger('click');
68
- expect(wrapper.emitted().next[0]).toEqual([]);
117
+ expect(wrapper.emitted('next')?.[0]).toEqual([]);
69
118
 
70
119
  await goToEnding.trigger('click');
71
- expect(wrapper.emitted().go[1]).toEqual([4]);
120
+ expect(wrapper.emitted('go')?.[1]).toEqual([4]);
121
+ });
122
+
123
+ test('should handle size change', async () => {
124
+ const wrapper = factory({
125
+ sizes: [25, 50],
126
+ total: 120,
127
+ currentPage: 0,
128
+ currentSize: 25,
129
+ pages: Math.ceil(120 / 25),
130
+ });
131
+
132
+ const select = wrapper.findComponent({ name: 'BmsSelect' });
133
+ await select.vm.$emit('update:modelValue', 50);
134
+
135
+ expect(wrapper.emitted('update:currentSize')?.[0]).toEqual([50]);
136
+ });
137
+
138
+ test('should disable navigation buttons correctly', () => {
139
+ const wrapper = factory({
140
+ sizes: [25, 50],
141
+ total: 120,
142
+ currentPage: 0,
143
+ currentSize: 25,
144
+ pages: Math.ceil(120 / 25),
145
+ });
146
+
147
+ const buttons = wrapper.findAllComponents({ name: 'BmsIconButton' });
148
+
149
+ // First and previous buttons should be disabled on first page
150
+ expect(buttons[0].props('disabled')).toBe(true);
151
+ expect(buttons[1].props('disabled')).toBe(true);
152
+
153
+ // Next and last buttons should be enabled
154
+ expect(buttons[2].props('disabled')).toBe(false);
155
+ expect(buttons[3].props('disabled')).toBe(false);
156
+ });
157
+
158
+ test('should calculate last item number correctly', () => {
159
+ const wrapper = shallowFactory({
160
+ sizes: [25, 50],
161
+ total: 120,
162
+ currentPage: 1,
163
+ currentSize: 25,
164
+ pages: Math.ceil(120 / 25),
165
+ });
166
+
167
+ expect(wrapper.text()).toContain('26 à 50');
72
168
  });
73
169
  });
@@ -112,7 +112,6 @@ const props = withDefaults(defineProps<UiTableProps>(), {
112
112
  signal: controller.signal,
113
113
  },
114
114
  );
115
-
116
115
  const totalItems = response.headers.get('X-Total');
117
116
  const data = await response.json();
118
117
  return { data, total: totalItems ? parseInt(totalItems) : Infinity };
@@ -211,13 +210,21 @@ watch(
211
210
  { deep: true },
212
211
  );
213
212
 
213
+ // Watch vuejs cannot call directly the debounce function
214
+ const debouncedHandler = _debounce((callBack) => {
215
+ callBack();
216
+ }, props.debounceTime);
217
+
214
218
  watch(
215
219
  [() => filters.value, () => sort.value, size, search],
216
220
  () => {
217
221
  if (!isMounting.value) {
218
- currentPage.value = props.initialPage;
219
- emits('update:selectMode', SelectMode.DEFAULT);
220
- fetchData();
222
+ const callBack = () => {
223
+ currentPage.value = props.initialPage;
224
+ emits('update:selectMode', SelectMode.DEFAULT);
225
+ fetchData();
226
+ };
227
+ debouncedHandler(callBack);
221
228
  }
222
229
  },
223
230
  { deep: true },
@@ -268,10 +275,6 @@ async function fetchData() {
268
275
  }
269
276
  }
270
277
 
271
- const debouncedSearch = _debounce((value: string) => {
272
- search.value = value;
273
- }, props.debounceTime);
274
-
275
278
  const onPrevClick = () => {
276
279
  goToPage(currentPage.value - 1, total.value);
277
280
  };
@@ -375,7 +378,7 @@ const onSelectAll = () => emits('update:selectMode', SelectMode.ALL);
375
378
  <BmsSearch
376
379
  :modelValue="search"
377
380
  class="table-search"
378
- @update:modelValue="debouncedSearch"
381
+ @update:modelValue="search = $event"
379
382
  />
380
383
  </slot>
381
384
  </template>