@farm-investimentos/front-mfe-components 15.13.2 → 15.14.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.
@@ -0,0 +1,106 @@
1
+ import FilterEmptyState from './FilterEmptyState.vue';
2
+
3
+ export default {
4
+ title: 'Display/FilterEmptyState',
5
+ component: FilterEmptyState,
6
+ parameters: {
7
+ docs: {
8
+ description: {
9
+ component: `Componente para exibir estados vazios em filtros<br />
10
+ selector: <em>farm-filter-empty-state</em><br />
11
+ <span style="color: var(--farm-primary-base);">ready for use</span>
12
+ `,
13
+ },
14
+ },
15
+ viewMode: 'docs',
16
+ },
17
+ argTypes: {
18
+ isEmpty: {
19
+ control: { type: 'boolean' },
20
+ description: 'Indica se o estado é vazio (sem dados)',
21
+ },
22
+ isNotFound: {
23
+ control: { type: 'boolean' },
24
+ description: 'Indica se o estado é não encontrado (sem resultados do filtro)',
25
+ },
26
+ isEmptyImage: {
27
+ control: { type: 'text' },
28
+ description: 'URL da imagem personalizada para estado vazio',
29
+ },
30
+ isEmptyImageAlt: {
31
+ control: { type: 'text' },
32
+ description: 'Texto alternativo da imagem para estado vazio',
33
+ },
34
+ isNotFoundImage: {
35
+ control: { type: 'text' },
36
+ description: 'URL da imagem personalizada para estado não encontrado',
37
+ },
38
+ isNotFoundImageAlt: {
39
+ control: { type: 'text' },
40
+ description: 'Texto alternativo da imagem para estado não encontrado',
41
+ },
42
+ title: {
43
+ control: { type: 'text' },
44
+ description: 'Título personalizado',
45
+ },
46
+ subtitle: {
47
+ control: { type: 'text' },
48
+ description: 'Subtítulo personalizado',
49
+ },
50
+ },
51
+ args: {
52
+ isEmpty: true,
53
+ isNotFound: false,
54
+ },
55
+ };
56
+
57
+ // Empty state image
58
+ export const EmptyStateImage = args => ({
59
+ components: { FilterEmptyState },
60
+ setup() {
61
+ return { args };
62
+ },
63
+ template: '<farm-filter-empty-state v-bind="args" />',
64
+ });
65
+ EmptyStateImage.storyName = 'Empty state image';
66
+ EmptyStateImage.args = {
67
+ isEmpty: true,
68
+ isNotFound: false,
69
+ };
70
+
71
+ // Not found state image
72
+ export const NotFoundStateImage = args => ({
73
+ components: { FilterEmptyState },
74
+ setup() {
75
+ return { args };
76
+ },
77
+ template: '<farm-filter-empty-state v-bind="args" />',
78
+ });
79
+ NotFoundStateImage.storyName = 'Not found state image';
80
+ NotFoundStateImage.args = {
81
+ isEmpty: false,
82
+ isNotFound: true,
83
+ };
84
+
85
+ // Custom titles and subtitles (slot)
86
+ export const CustomTitlesAndSubtitles = args => ({
87
+ components: { FilterEmptyState },
88
+ setup() {
89
+ return { args };
90
+ },
91
+ template: `
92
+ <farm-filter-empty-state v-bind="args">
93
+ <template #title>
94
+ <span style="color: #4f8406;">Título customizado via slot</span>
95
+ </template>
96
+ <template #subtitle>
97
+ <span>Subtítulo <b>com HTML</b> via slot</span>
98
+ </template>
99
+ </farm-filter-empty-state>
100
+ `,
101
+ });
102
+ CustomTitlesAndSubtitles.storyName = 'Custom titles and subtitles (slot)';
103
+ CustomTitlesAndSubtitles.args = {
104
+ isEmpty: true,
105
+ isNotFound: false,
106
+ };
@@ -0,0 +1,137 @@
1
+ <template>
2
+ <farm-box>
3
+ <farm-row justify="center" class="mb-4">
4
+ <img
5
+ v-if="isEmpty"
6
+ :src="isEmptyImage"
7
+ :alt="isEmptyImageAlt"
8
+ class="filter-empty-state__image"
9
+ />
10
+ <img
11
+ v-else-if="isNotFound"
12
+ :src="isNotFoundImage"
13
+ :alt="isNotFoundImageAlt"
14
+ class="filter-empty-state__image"
15
+ />
16
+ </farm-row>
17
+ <farm-row justify="center">
18
+ <farm-bodytext variation="bold" color="gray" :type="2">
19
+ <slot name="title">
20
+ {{ title || 'Nenhuma informação para exibir aqui' }}
21
+ </slot>
22
+ </farm-bodytext>
23
+ </farm-row>
24
+ <farm-row justify="center" class="mt-2">
25
+ <farm-caption variation="regular" color="gray">
26
+ <slot name="subtitle">
27
+ {{ subtitle || 'Tente filtrar novamente sua pesquisa.' }}
28
+ </slot>
29
+ </farm-caption>
30
+ </farm-row>
31
+ </farm-box>
32
+ </template>
33
+
34
+ <script lang="ts">
35
+ import { defineComponent, PropType } from 'vue';
36
+
37
+ export type FilterEmptyStateProps = {
38
+ /**
39
+ * Indicates if the state is empty (no data)
40
+ */
41
+ isEmpty?: boolean;
42
+ /**
43
+ * Indicates if the state is not found (no results from filter)
44
+ */
45
+ isNotFound?: boolean;
46
+ /**
47
+ * Custom image URL for empty state
48
+ */
49
+ isEmptyImage?: string;
50
+ /**
51
+ * Alt text for empty state image
52
+ */
53
+ isEmptyImageAlt?: string;
54
+ /**
55
+ * Custom image URL for not found state
56
+ */
57
+ isNotFoundImage?: string;
58
+ /**
59
+ * Alt text for not found state image
60
+ */
61
+ isNotFoundImageAlt?: string;
62
+ /**
63
+ * Title to be displayed
64
+ */
65
+ title?: string;
66
+ /**
67
+ * Subtitle to be displayed
68
+ */
69
+ subtitle?: string;
70
+ };
71
+
72
+ export default defineComponent({
73
+ name: 'farm-filter-empty-state',
74
+ props: {
75
+ /**
76
+ * Indicates if the state is empty (no data)
77
+ */
78
+ isEmpty: {
79
+ type: Boolean as PropType<FilterEmptyStateProps['isEmpty']>,
80
+ default: false,
81
+ },
82
+ /**
83
+ * Indicates if the state is not found (no results from filter)
84
+ */
85
+ isNotFound: {
86
+ type: Boolean as PropType<FilterEmptyStateProps['isNotFound']>,
87
+ default: false,
88
+ },
89
+ /**
90
+ * Custom image URL for empty state
91
+ */
92
+ isEmptyImage: {
93
+ type: String as PropType<FilterEmptyStateProps['isEmptyImage']>,
94
+ default: require('../../assets/imgs/empty-data.svg'),
95
+ },
96
+ /**
97
+ * Alt text for empty state image
98
+ */
99
+ isEmptyImageAlt: {
100
+ type: String as PropType<FilterEmptyStateProps['isEmptyImageAlt']>,
101
+ default: 'Imagem referente a dados vazios',
102
+ },
103
+ /**
104
+ * Custom image URL for not found state
105
+ */
106
+ isNotFoundImage: {
107
+ type: String as PropType<FilterEmptyStateProps['isNotFoundImage']>,
108
+ default: require('../../assets/imgs/empty-not-found.svg'),
109
+ },
110
+ /**
111
+ * Alt text for not found state image
112
+ */
113
+ isNotFoundImageAlt: {
114
+ type: String as PropType<FilterEmptyStateProps['isNotFoundImageAlt']>,
115
+ default: 'Imagem referente a filtro vazio',
116
+ },
117
+ /**
118
+ * Title to be displayed
119
+ */
120
+ title: {
121
+ type: String as PropType<FilterEmptyStateProps['title']>,
122
+ default: '',
123
+ },
124
+ /**
125
+ * Subtitle to be displayed
126
+ */
127
+ subtitle: {
128
+ type: String as PropType<FilterEmptyStateProps['subtitle']>,
129
+ default: '',
130
+ },
131
+ },
132
+ });
133
+ </script>
134
+
135
+ <style scoped lang="scss">
136
+ @import './FilterEmptyState.scss';
137
+ </style>
@@ -0,0 +1,108 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import FilterEmptyState from '../FilterEmptyState.vue';
3
+
4
+ const globalStubs = {
5
+ 'farm-box': { template: '<div><slot /></div>' },
6
+ 'farm-row': { template: '<div><slot /></div>' },
7
+ 'farm-bodytext': { template: '<div><slot /></div>' },
8
+ 'farm-caption': { template: '<div><slot /></div>' },
9
+ };
10
+
11
+ describe('FilterEmptyState', () => {
12
+ it('renders empty state correctly', () => {
13
+ const wrapper = mount(FilterEmptyState, {
14
+ propsData: {
15
+ isEmpty: true,
16
+ isNotFound: false,
17
+ },
18
+ stubs: globalStubs,
19
+ });
20
+
21
+ expect(wrapper.find('.filter-empty-state__image').exists()).toBe(true);
22
+ expect(wrapper.text()).toContain('Nenhuma informação para exibir aqui');
23
+ expect(wrapper.text()).toContain('Tente filtrar novamente sua pesquisa.');
24
+ });
25
+
26
+ it('renders not found state correctly', () => {
27
+ const wrapper = mount(FilterEmptyState, {
28
+ propsData: {
29
+ isEmpty: false,
30
+ isNotFound: true,
31
+ },
32
+ stubs: globalStubs,
33
+ });
34
+
35
+ expect(wrapper.find('.filter-empty-state__image').exists()).toBe(true);
36
+ expect(wrapper.text()).toContain('Nenhuma informação para exibir aqui');
37
+ expect(wrapper.text()).toContain('Tente filtrar novamente sua pesquisa.');
38
+ });
39
+
40
+ it('renders custom title and subtitle', () => {
41
+ const wrapper = mount(FilterEmptyState, {
42
+ propsData: {
43
+ isEmpty: true,
44
+ title: 'Custom Title',
45
+ subtitle: 'Custom Subtitle',
46
+ },
47
+ stubs: globalStubs,
48
+ });
49
+
50
+ expect(wrapper.text()).toContain('Custom Title');
51
+ expect(wrapper.text()).toContain('Custom Subtitle');
52
+ });
53
+
54
+ it('uses custom images when provided', () => {
55
+ const wrapper = mount(FilterEmptyState, {
56
+ propsData: {
57
+ isEmpty: true,
58
+ isEmptyImage: 'custom-empty-image.svg',
59
+ isEmptyImageAlt: 'Custom empty image',
60
+ },
61
+ stubs: globalStubs,
62
+ });
63
+
64
+ const img = wrapper.find('.filter-empty-state__image');
65
+ expect(img.exists()).toBe(true);
66
+ expect(img.attributes('src')).toBe('custom-empty-image.svg');
67
+ expect(img.attributes('alt')).toBe('Custom empty image');
68
+ });
69
+
70
+ it('uses custom not found images when provided', () => {
71
+ const wrapper = mount(FilterEmptyState, {
72
+ propsData: {
73
+ isNotFound: true,
74
+ isNotFoundImage: 'custom-not-found-image.svg',
75
+ isNotFoundImageAlt: 'Custom not found image',
76
+ },
77
+ stubs: globalStubs,
78
+ });
79
+
80
+ const img = wrapper.find('.filter-empty-state__image');
81
+ expect(img.exists()).toBe(true);
82
+ expect(img.attributes('src')).toBe('custom-not-found-image.svg');
83
+ expect(img.attributes('alt')).toBe('Custom not found image');
84
+ });
85
+
86
+ it('has correct default props', () => {
87
+ const wrapper = mount(FilterEmptyState, {
88
+ stubs: globalStubs,
89
+ });
90
+
91
+ expect(wrapper.props('isEmpty')).toBe(false);
92
+ expect(wrapper.props('isNotFound')).toBe(false);
93
+ expect(wrapper.props('title')).toBe('');
94
+ expect(wrapper.props('subtitle')).toBe('');
95
+ });
96
+
97
+ it('does not show image when neither isEmpty nor isNotFound is true', () => {
98
+ const wrapper = mount(FilterEmptyState, {
99
+ propsData: {
100
+ isEmpty: false,
101
+ isNotFound: false,
102
+ },
103
+ stubs: globalStubs,
104
+ });
105
+
106
+ expect(wrapper.find('.filter-empty-state__image').exists()).toBe(false);
107
+ });
108
+ });
@@ -0,0 +1,38 @@
1
+ import FilterEmptyState from './FilterEmptyState.vue';
2
+
3
+ export type FilterEmptyStateProps = {
4
+ /**
5
+ * Indicates if the state is empty (no data)
6
+ */
7
+ isEmpty?: boolean;
8
+ /**
9
+ * Indicates if the state is not found (no results from filter)
10
+ */
11
+ isNotFound?: boolean;
12
+ /**
13
+ * Custom image URL for empty state
14
+ */
15
+ isEmptyImage?: string;
16
+ /**
17
+ * Alt text for empty state image
18
+ */
19
+ isEmptyImageAlt?: string;
20
+ /**
21
+ * Custom image URL for not found state
22
+ */
23
+ isNotFoundImage?: string;
24
+ /**
25
+ * Alt text for not found state image
26
+ */
27
+ isNotFoundImageAlt?: string;
28
+ /**
29
+ * Title to be displayed
30
+ */
31
+ title?: string;
32
+ /**
33
+ * Subtitle to be displayed
34
+ */
35
+ subtitle?: string;
36
+ };
37
+
38
+ export default FilterEmptyState;
@@ -136,6 +136,7 @@
136
136
  margin-top: gutter('lg');
137
137
  padding-top: gutter('sm');
138
138
  flex-wrap: wrap;
139
+ width: 100%;
139
140
  }
140
141
 
141
142
  &__legend-title {
@@ -213,6 +214,10 @@
213
214
  margin-bottom: 0;
214
215
  }
215
216
  }
217
+ .tooltip-label {
218
+ margin-right: 4px;
219
+ }
220
+
216
221
 
217
222
  .tooltip-text {
218
223
  font-weight: 500;
@@ -227,9 +227,9 @@ export default defineComponent({
227
227
 
228
228
  const legendStyle = computed(() => {
229
229
  const timelineWidth = monthColumns.value.length * 80;
230
- const totalWidth = 120 + timelineWidth;
230
+ const totalWidth = 180 + timelineWidth;
231
231
  return {
232
- width: `${totalWidth}px`,
232
+ minWidth: `${totalWidth}px`,
233
233
  };
234
234
  });
235
235
 
@@ -93,15 +93,15 @@ describe('GanttChart component', () => {
93
93
  {
94
94
  id: 1,
95
95
  label: 'Early Bar',
96
- start: new Date(2025, 0, 15), // Jan 15, 2025
97
- end: new Date(2025, 2, 10), // Mar 10, 2025
96
+ start: new Date(2025, 0, 15),
97
+ end: new Date(2025, 2, 10),
98
98
  color: '#7BC4F7',
99
99
  },
100
100
  {
101
101
  id: 2,
102
102
  label: 'Late Bar',
103
- start: new Date(2025, 4, 5), // May 5, 2025
104
- end: new Date(2025, 6, 20), // Jul 20, 2025
103
+ start: new Date(2025, 4, 5),
104
+ end: new Date(2025, 6, 20),
105
105
  color: '#8BB455',
106
106
  },
107
107
  ],
@@ -113,11 +113,10 @@ describe('GanttChart component', () => {
113
113
  propsData: { data: testData },
114
114
  });
115
115
 
116
- // Should calculate from January 1st to July 31st (full months)
117
116
  const monthColumns = testWrapper.vm.monthColumns;
118
- expect(monthColumns.length).toBe(7); // Jan to Jul = 7 months
117
+ expect(monthColumns.length).toBe(8);
119
118
  expect(monthColumns[0].label).toContain('Jan');
120
- expect(monthColumns[monthColumns.length - 1].label).toContain('Jul');
119
+ expect(monthColumns[monthColumns.length - 1].label).toContain('Ago');
121
120
  });
122
121
 
123
122
  it('should handle single month range', () => {
@@ -129,8 +128,8 @@ describe('GanttChart component', () => {
129
128
  {
130
129
  id: 1,
131
130
  label: 'Single Month Bar',
132
- start: new Date(2025, 3, 5), // Apr 5, 2025
133
- end: new Date(2025, 3, 25), // Apr 25, 2025
131
+ start: new Date(2025, 3, 5),
132
+ end: new Date(2025, 3, 25),
134
133
  color: '#7BC4F7',
135
134
  },
136
135
  ],
@@ -143,8 +142,9 @@ describe('GanttChart component', () => {
143
142
  });
144
143
 
145
144
  const monthColumns = testWrapper.vm.monthColumns;
146
- expect(monthColumns.length).toBe(1);
145
+ expect(monthColumns.length).toBe(2);
147
146
  expect(monthColumns[0].label).toContain('Abr');
147
+ expect(monthColumns[1].label).toContain('Mai');
148
148
  });
149
149
  });
150
150
 
@@ -171,7 +171,7 @@ describe('GanttChart component', () => {
171
171
  },
172
172
  {
173
173
  id: 3,
174
- label: 'Design', // Same label, same color - should not duplicate
174
+ label: 'Design',
175
175
  start: new Date(2025, 2, 1),
176
176
  end: new Date(2025, 3, 1),
177
177
  color: '#8E44AD',
@@ -186,7 +186,7 @@ describe('GanttChart component', () => {
186
186
  });
187
187
 
188
188
  const legend = testWrapper.vm.autoGeneratedLegend;
189
- expect(legend).toHaveLength(2); // Only unique combinations
189
+ expect(legend).toHaveLength(2);
190
190
  expect(legend.some(item => item.label === 'Design' && item.color === '#8E44AD')).toBe(true);
191
191
  expect(legend.some(item => item.label === 'Development' && item.color === '#16A085')).toBe(true);
192
192
  });
@@ -219,7 +219,6 @@ describe('GanttChart component', () => {
219
219
  ];
220
220
  const positionedBars = component.getPositionedBars(bars);
221
221
  expect(positionedBars).toHaveLength(2);
222
- // Each bar should have its own unique row position
223
222
  expect(positionedBars[0].rowPosition).toBe(0);
224
223
  expect(positionedBars[1].rowPosition).toBe(1);
225
224
  });
@@ -228,21 +227,21 @@ describe('GanttChart component', () => {
228
227
  const bars = [
229
228
  {
230
229
  id: 3,
231
- start: new Date(2025, 2, 1), // Latest date but first in array
230
+ start: new Date(2025, 2, 1),
232
231
  end: new Date(2025, 2, 15),
233
232
  label: 'Latest Bar',
234
233
  color: '#FFB84D',
235
234
  },
236
235
  {
237
236
  id: 1,
238
- start: new Date(2025, 0, 1), // Earliest date but second in array
237
+ start: new Date(2025, 0, 1),
239
238
  end: new Date(2025, 0, 15),
240
239
  label: 'Early Bar',
241
240
  color: '#7BC4F7',
242
241
  },
243
242
  {
244
243
  id: 2,
245
- start: new Date(2025, 1, 1), // Middle date but third in array
244
+ start: new Date(2025, 1, 1),
246
245
  end: new Date(2025, 1, 15),
247
246
  label: 'Middle Bar',
248
247
  color: '#8BB455',
@@ -250,10 +249,9 @@ describe('GanttChart component', () => {
250
249
  ];
251
250
  const positionedBars = component.getPositionedBars(bars);
252
251
  expect(positionedBars).toHaveLength(3);
253
- // Bars should maintain original order, not be sorted by date
254
- expect(positionedBars[0].id).toBe(3); // Latest date stays first
255
- expect(positionedBars[1].id).toBe(1); // Earliest date stays second
256
- expect(positionedBars[2].id).toBe(2); // Middle date stays third
252
+ expect(positionedBars[0].id).toBe(3);
253
+ expect(positionedBars[1].id).toBe(1);
254
+ expect(positionedBars[2].id).toBe(2);
257
255
  expect(positionedBars[0].rowPosition).toBe(0);
258
256
  expect(positionedBars[1].rowPosition).toBe(1);
259
257
  expect(positionedBars[2].rowPosition).toBe(2);
@@ -310,7 +308,6 @@ describe('GanttChart component', () => {
310
308
  label: 'Bar Without Color',
311
309
  start: new Date(2025, 0, 1),
312
310
  end: new Date(2025, 1, 1),
313
- // color não definido - deve usar fallback
314
311
  }]
315
312
  }]
316
313
  };
@@ -350,16 +347,13 @@ describe('GanttChart component', () => {
350
347
  it('should validate invalid data prop structure', () => {
351
348
  const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
352
349
 
353
- // Teste com data null
354
350
  const validator = wrapper.vm.$options.props.data.validator;
355
351
  expect(validator(null)).toBe(false);
356
352
  expect(consoleSpy).toHaveBeenCalledWith('GanttChart: prop "data" deve ser um objeto.');
357
353
 
358
- // Teste com data sem groups
359
354
  expect(validator({})).toBe(false);
360
355
  expect(consoleSpy).toHaveBeenCalledWith('GanttChart: prop "data.groups" deve ser um array.');
361
356
 
362
- // Teste com groups inválido
363
357
  expect(validator({ groups: 'invalid' })).toBe(false);
364
358
  expect(consoleSpy).toHaveBeenCalledWith('GanttChart: prop "data.groups" deve ser um array.');
365
359
 
@@ -370,7 +364,6 @@ describe('GanttChart component', () => {
370
364
  const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
371
365
  const validator = wrapper.vm.$options.props.data.validator;
372
366
 
373
- // Teste com grupo sem title
374
367
  const invalidData = {
375
368
  groups: [{
376
369
  bars: []
@@ -379,7 +372,6 @@ describe('GanttChart component', () => {
379
372
  expect(validator(invalidData)).toBe(false);
380
373
  expect(consoleSpy).toHaveBeenCalledWith('GanttChart: cada grupo deve ter título (string) e barras (array).');
381
374
 
382
- // Teste com grupo sem bars
383
375
  const invalidData2 = {
384
376
  groups: [{
385
377
  title: 'Valid Title'
@@ -408,18 +400,15 @@ describe('GanttChart component', () => {
408
400
  const bar = {
409
401
  id: 1,
410
402
  label: 'Inverted Date Bar',
411
- // Datas invertidas (end antes de start)
412
- start: new Date(2025, 3, 15), // 15 de abril
413
- end: new Date(2025, 2, 15), // 15 de março (antes do início)
403
+ start: new Date(2025, 3, 15),
404
+ end: new Date(2025, 2, 15),
414
405
  color: '#7BC4F7',
415
406
  };
416
407
 
417
- // Normalizar barra via função de posicionamento
418
408
  const dates = component.normalizeBarDates(bar);
419
409
 
420
- // Verificar se as datas foram invertidas corretamente
421
- expect(dates.startDate.getTime()).toBe(new Date(2025, 2, 15).getTime()); // Agora é 15 de março
422
- expect(dates.endDate.getTime()).toBe(new Date(2025, 3, 15).getTime()); // Agora é 15 de abril
410
+ expect(dates.startDate.getTime()).toBe(new Date(2025, 2, 15).getTime());
411
+ expect(dates.endDate.getTime()).toBe(new Date(2025, 3, 15).getTime());
423
412
  });
424
413
 
425
414
  it('should handle invalid dates', () => {
@@ -438,17 +427,14 @@ describe('GanttChart component', () => {
438
427
 
439
428
  describe('Tooltip System', () => {
440
429
  it('should show tooltip on bar mouseenter', async () => {
441
- // Encontrar uma barra
442
430
  const bar = wrapper.find('.farm-gantt-chart__bar');
443
431
 
444
432
  if (bar.exists()) {
445
- // Disparar evento mouseenter
446
433
  await bar.trigger('mouseenter', {
447
434
  clientX: 100,
448
435
  clientY: 100,
449
436
  });
450
437
 
451
- // Verificar se tooltipState foi atualizado
452
438
  expect(component.tooltipState.visible).toBe(true);
453
439
  expect(component.tooltipState.title).toBe('Test Bar');
454
440
  expect(component.tooltipState.barData).not.toBeNull();
@@ -456,24 +442,20 @@ describe('GanttChart component', () => {
456
442
  });
457
443
 
458
444
  it('should hide tooltip on bar mouseleave', async () => {
459
- // Preparar estado (mostrar tooltip)
460
445
  component.tooltipState.visible = true;
461
446
  component.tooltipState.title = 'Teste';
462
447
  component.tooltipState.barData = { label: 'Teste' };
463
448
 
464
- // Encontrar e disparar mouseleave
465
449
  const bar = wrapper.find('.farm-gantt-chart__bar');
466
450
  if (bar.exists()) {
467
451
  await bar.trigger('mouseleave');
468
452
 
469
- // Verificar se tooltipState foi atualizado
470
453
  expect(component.tooltipState.visible).toBe(false);
471
454
  expect(component.tooltipState.barData).toBeNull();
472
455
  }
473
456
  });
474
457
 
475
458
  it('should show structured tooltip when tooltipData is available', () => {
476
- // Criar wrapper com dados para tooltip
477
459
  const testData = {
478
460
  groups: [{
479
461
  title: 'Group with Tooltips',
@@ -495,7 +477,6 @@ describe('GanttChart component', () => {
495
477
  propsData: { data: testData },
496
478
  });
497
479
 
498
- // Simular mouseenter
499
480
  const barWithTooltip = tooltipWrapper.find('.farm-gantt-chart__bar');
500
481
  if (barWithTooltip.exists()) {
501
482
  barWithTooltip.trigger('mouseenter', {
@@ -503,8 +484,6 @@ describe('GanttChart component', () => {
503
484
  clientY: 100
504
485
  });
505
486
 
506
- // Verificar que tooltip com dados estruturados será exibido
507
- // quando tooltipState.barData.tooltipData existir
508
487
  expect(tooltipWrapper.vm.tooltipState.barData.tooltipData).toBeDefined();
509
488
  expect(tooltipWrapper.vm.tooltipState.barData.tooltipData.Taxa).toBe('1,75%');
510
489
  }
@@ -518,11 +497,8 @@ describe('GanttChart component', () => {
518
497
  propsData: { data: emptyData },
519
498
  });
520
499
 
521
- // Componente deve renderizar sem erros mesmo com dados vazios
522
- expect(emptyWrapper.exists()).toBe(true);
523
500
  expect(emptyWrapper.find('.farm-gantt-chart').exists()).toBe(true);
524
501
 
525
- // Não deve haver grupos renderizados
526
502
  const groups = emptyWrapper.findAll('.farm-gantt-chart__group');
527
503
  expect(groups.length).toBe(0);
528
504
  });
@@ -533,7 +509,6 @@ describe('GanttChart component', () => {
533
509
  propsData: { data: emptyData },
534
510
  });
535
511
 
536
- // Timeline headers devem estar presentes (baseado em data range padrão ou calculado)
537
512
  const header = emptyWrapper.find('.farm-gantt-chart__header');
538
513
  expect(header.exists()).toBe(true);
539
514
  });
@@ -1,8 +1,12 @@
1
1
  import { isValid } from 'date-fns';
2
- import type { GanttBar } from '../types';
2
+ import { Ref } from 'vue';
3
+ import type { GanttBar, DateRange, MonthColumn } from '../types';
3
4
  import { getColumnForDate, getDaysInMonth } from '../utils/dateUtils';
4
5
 
5
- export default function buildBarPositioning(dateRange, monthColumns) {
6
+ export default function buildBarPositioning(
7
+ dateRange: Ref<DateRange>,
8
+ monthColumns: Ref<MonthColumn[]>
9
+ ) {
6
10
  const normalizeBarDates = (bar: GanttBar) => {
7
11
  let startDate = bar.start instanceof Date ? bar.start : new Date(bar.start);
8
12
  let endDate = bar.end instanceof Date ? bar.end : new Date(bar.end);