vueless 1.3.6-beta.0 → 1.3.6-beta.10

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.
Files changed (51) hide show
  1. package/components.d.ts +2 -0
  2. package/components.ts +2 -0
  3. package/constants.d.ts +2 -0
  4. package/constants.js +2 -0
  5. package/index.d.ts +10 -1
  6. package/index.ts +10 -1
  7. package/package.json +2 -2
  8. package/types.ts +2 -0
  9. package/ui.button/UButton.vue +1 -1
  10. package/ui.button/storybook/stories.ts +2 -2
  11. package/ui.container-card/storybook/stories.ts +2 -2
  12. package/ui.container-drawer/UDrawer.vue +2 -2
  13. package/ui.container-drawer/storybook/stories.ts +2 -2
  14. package/ui.container-grid/UGrid.vue +39 -0
  15. package/ui.container-grid/config.ts +123 -0
  16. package/ui.container-grid/constants.ts +5 -0
  17. package/ui.container-grid/storybook/docs.mdx +17 -0
  18. package/ui.container-grid/storybook/stories.ts +246 -0
  19. package/ui.container-grid/tests/UGrid.test.ts +297 -0
  20. package/ui.container-grid/types.ts +91 -0
  21. package/ui.container-modal/storybook/stories.ts +2 -2
  22. package/ui.container-modal-confirm/storybook/stories.ts +2 -2
  23. package/ui.container-page/storybook/stories.ts +3 -3
  24. package/ui.form-calendar/tests/UCalendar.test.ts +113 -0
  25. package/ui.form-date-picker-range/UDatePickerRangeInputs.vue +5 -1
  26. package/ui.form-date-picker-range/tests/UDatePickerRange.test.ts +114 -0
  27. package/ui.form-date-picker-range/types.ts +1 -0
  28. package/ui.form-listbox/config.ts +1 -1
  29. package/ui.image-avatar/UAvatar.vue +55 -28
  30. package/ui.image-avatar/config.ts +18 -1
  31. package/ui.image-avatar/storybook/docs.mdx +16 -1
  32. package/ui.image-avatar/storybook/stories.ts +17 -3
  33. package/ui.image-avatar/tests/UAvatar.test.ts +35 -7
  34. package/ui.image-avatar/types.ts +13 -0
  35. package/ui.image-avatar-group/UAvatarGroup.vue +87 -0
  36. package/ui.image-avatar-group/config.ts +11 -0
  37. package/ui.image-avatar-group/constants.ts +5 -0
  38. package/ui.image-avatar-group/storybook/docs.mdx +16 -0
  39. package/ui.image-avatar-group/storybook/stories.ts +147 -0
  40. package/ui.image-avatar-group/tests/UAvatarGroup.test.ts +141 -0
  41. package/ui.image-avatar-group/types.ts +51 -0
  42. package/ui.navigation-pagination/storybook/stories.ts +2 -2
  43. package/ui.navigation-tab/tests/UTab.test.ts +2 -3
  44. package/ui.navigation-tabs/UTabs.vue +44 -1
  45. package/ui.navigation-tabs/storybook/stories.ts +33 -0
  46. package/ui.navigation-tabs/tests/UTabs.test.ts +88 -0
  47. package/ui.text-block/config.ts +1 -0
  48. package/ui.text-block/storybook/stories.ts +2 -2
  49. package/ui.text-notify/UNotify.vue +31 -8
  50. package/ui.text-notify/config.ts +1 -1
  51. package/ui.text-notify/tests/UNotify.test.ts +22 -7
@@ -0,0 +1,297 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, it, expect } from "vitest";
3
+
4
+ import UGrid from "../UGrid.vue";
5
+
6
+ import type { Props } from "../types";
7
+
8
+ describe("UGrid.vue", () => {
9
+ describe("Props", () => {
10
+ it("Cols – applies the correct cols class", () => {
11
+ const colsClasses = {
12
+ "1": "grid-cols-1",
13
+ "2": "grid-cols-2",
14
+ "3": "grid-cols-3",
15
+ "4": "grid-cols-4",
16
+ "5": "grid-cols-5",
17
+ "6": "grid-cols-6",
18
+ "7": "grid-cols-7",
19
+ "8": "grid-cols-8",
20
+ "9": "grid-cols-9",
21
+ "10": "grid-cols-10",
22
+ "11": "grid-cols-11",
23
+ "12": "grid-cols-12",
24
+ };
25
+
26
+ Object.entries(colsClasses).forEach(([cols, classes]) => {
27
+ const component = mount(UGrid, {
28
+ props: {
29
+ cols: cols as Props["cols"],
30
+ },
31
+ });
32
+
33
+ expect(component.attributes("class")).toContain(classes);
34
+ });
35
+ });
36
+
37
+ it("Rows – applies the correct rows class", () => {
38
+ const rowsClasses = {
39
+ "1": "grid-rows-1",
40
+ "2": "grid-rows-2",
41
+ "3": "grid-rows-3",
42
+ "4": "grid-rows-4",
43
+ "5": "grid-rows-5",
44
+ "6": "grid-rows-6",
45
+ "7": "grid-rows-7",
46
+ "8": "grid-rows-8",
47
+ "9": "grid-rows-9",
48
+ "10": "grid-rows-10",
49
+ "11": "grid-rows-11",
50
+ "12": "grid-rows-12",
51
+ };
52
+
53
+ Object.entries(rowsClasses).forEach(([rows, classes]) => {
54
+ const component = mount(UGrid, {
55
+ props: {
56
+ rows: rows as Props["rows"],
57
+ },
58
+ });
59
+
60
+ expect(component.attributes("class")).toContain(classes);
61
+ });
62
+ });
63
+
64
+ it("Gap – applies the correct gap class", () => {
65
+ const gapClasses = {
66
+ none: "gap-0",
67
+ "2xs": "gap-1",
68
+ xs: "gap-2",
69
+ sm: "gap-3",
70
+ md: "gap-4",
71
+ lg: "gap-5",
72
+ xl: "gap-6",
73
+ "2xl": "gap-8",
74
+ };
75
+
76
+ Object.entries(gapClasses).forEach(([gap, classes]) => {
77
+ const component = mount(UGrid, {
78
+ props: {
79
+ gap: gap as Props["gap"],
80
+ },
81
+ });
82
+
83
+ expect(component.attributes("class")).toContain(classes);
84
+ });
85
+ });
86
+
87
+ it("Row Gap – applies the correct row gap class", () => {
88
+ const rowGapClasses = {
89
+ none: "gap-y-0",
90
+ "2xs": "gap-y-1",
91
+ xs: "gap-y-2",
92
+ sm: "gap-y-3",
93
+ md: "gap-y-4",
94
+ lg: "gap-y-5",
95
+ xl: "gap-y-6",
96
+ "2xl": "gap-y-8",
97
+ };
98
+
99
+ Object.entries(rowGapClasses).forEach(([rowGap, classes]) => {
100
+ const component = mount(UGrid, {
101
+ props: {
102
+ rowGap: rowGap as Props["rowGap"],
103
+ },
104
+ });
105
+
106
+ expect(component.attributes("class")).toContain(classes);
107
+ });
108
+ });
109
+
110
+ it("Col Gap – applies the correct col gap class", () => {
111
+ const colGapClasses = {
112
+ none: "gap-x-0",
113
+ "2xs": "gap-x-1",
114
+ xs: "gap-x-2",
115
+ sm: "gap-x-3",
116
+ md: "gap-x-4",
117
+ lg: "gap-x-5",
118
+ xl: "gap-x-6",
119
+ "2xl": "gap-x-8",
120
+ };
121
+
122
+ Object.entries(colGapClasses).forEach(([colGap, classes]) => {
123
+ const component = mount(UGrid, {
124
+ props: {
125
+ colGap: colGap as Props["colGap"],
126
+ },
127
+ });
128
+
129
+ expect(component.attributes("class")).toContain(classes);
130
+ });
131
+ });
132
+
133
+ it("Align – applies the correct align class", () => {
134
+ const alignClasses = {
135
+ start: "items-start",
136
+ end: "items-end",
137
+ center: "items-center",
138
+ stretch: "items-stretch",
139
+ baseline: "items-baseline",
140
+ normal: "items-normal",
141
+ };
142
+
143
+ Object.entries(alignClasses).forEach(([align, classes]) => {
144
+ const component = mount(UGrid, {
145
+ props: {
146
+ align: align as Props["align"],
147
+ },
148
+ });
149
+
150
+ expect(component.attributes("class")).toContain(classes);
151
+ });
152
+ });
153
+
154
+ it("Content – applies the correct content class", () => {
155
+ const contentClasses = {
156
+ start: "content-start",
157
+ end: "content-end",
158
+ center: "content-center",
159
+ around: "content-around",
160
+ evenly: "content-evenly",
161
+ between: "content-between",
162
+ normal: "content-normal",
163
+ stretch: "content-stretch",
164
+ baseline: "content-baseline",
165
+ };
166
+
167
+ Object.entries(contentClasses).forEach(([content, classes]) => {
168
+ const component = mount(UGrid, {
169
+ props: {
170
+ content: content as Props["content"],
171
+ },
172
+ });
173
+
174
+ expect(component.attributes("class")).toContain(classes);
175
+ });
176
+ });
177
+
178
+ it("Justify – applies the correct justify class", () => {
179
+ const justifyClasses = {
180
+ start: "justify-items-start",
181
+ end: "justify-items-end",
182
+ "end-safe": "justify-items-end-safe",
183
+ center: "justify-items-center",
184
+ "center-safe": "justify-items-center-safe",
185
+ stretch: "justify-items-stretch",
186
+ normal: "justify-items-normal",
187
+ };
188
+
189
+ Object.entries(justifyClasses).forEach(([justify, classes]) => {
190
+ const component = mount(UGrid, {
191
+ props: {
192
+ justify: justify as Props["justify"],
193
+ },
194
+ });
195
+
196
+ expect(component.attributes("class")).toContain(classes);
197
+ });
198
+ });
199
+
200
+ it("Place Content – applies the correct place content class", () => {
201
+ const placeContentClasses = {
202
+ start: "place-content-start",
203
+ end: "place-content-end",
204
+ "end-safe": "place-content-end-safe",
205
+ center: "place-content-center",
206
+ "center-safe": "place-content-center-safe",
207
+ around: "place-content-around",
208
+ evenly: "place-content-evenly",
209
+ between: "place-content-between",
210
+ stretch: "place-content-stretch",
211
+ baseline: "place-content-baseline",
212
+ };
213
+
214
+ Object.entries(placeContentClasses).forEach(([placeContent, classes]) => {
215
+ const component = mount(UGrid, {
216
+ props: {
217
+ placeContent: placeContent as Props["placeContent"],
218
+ },
219
+ });
220
+
221
+ expect(component.attributes("class")).toContain(classes);
222
+ });
223
+ });
224
+
225
+ it("Place Items – applies the correct place items class", () => {
226
+ const placeItemsClasses = {
227
+ start: "place-items-start",
228
+ end: "place-items-end",
229
+ "end-safe": "place-items-end-safe",
230
+ center: "place-items-center",
231
+ "center-safe": "place-items-center-safe",
232
+ stretch: "place-items-stretch",
233
+ baseline: "place-items-baseline",
234
+ };
235
+
236
+ Object.entries(placeItemsClasses).forEach(([placeItems, classes]) => {
237
+ const component = mount(UGrid, {
238
+ props: {
239
+ placeItems: placeItems as Props["placeItems"],
240
+ },
241
+ });
242
+
243
+ expect(component.attributes("class")).toContain(classes);
244
+ });
245
+ });
246
+
247
+ it("Tag – renders the correct HTML tag", () => {
248
+ const tags = ["div", "section", "article", "main", "aside", "nav", "span"];
249
+
250
+ tags.forEach((tag) => {
251
+ const component = mount(UGrid, {
252
+ props: {
253
+ tag,
254
+ },
255
+ });
256
+
257
+ expect(component.element.tagName.toLowerCase()).toBe(tag);
258
+ });
259
+ });
260
+
261
+ it("Data Test – applies the correct data-test attribute", () => {
262
+ const dataTest = "grid-test";
263
+
264
+ const component = mount(UGrid, {
265
+ props: {
266
+ dataTest,
267
+ },
268
+ });
269
+
270
+ expect(component.attributes("data-test")).toBe(dataTest);
271
+ });
272
+ });
273
+
274
+ describe("Slots", () => {
275
+ it("Default – renders content from default slot", () => {
276
+ const slotContent = "Custom Content";
277
+
278
+ const component = mount(UGrid, {
279
+ slots: {
280
+ default: slotContent,
281
+ },
282
+ });
283
+
284
+ expect(component.text()).toContain(slotContent);
285
+ });
286
+ });
287
+
288
+ describe("Exposed refs", () => {
289
+ it("wrapperRef – exposes wrapperRef", () => {
290
+ const component = mount(UGrid);
291
+
292
+ expect(component.vm.wrapperRef).toBeDefined();
293
+ // wrapperRef is a reference to the wrapper div element, not a boolean
294
+ expect(component.vm.wrapperRef instanceof HTMLElement).toBe(true);
295
+ });
296
+ });
297
+ });
@@ -0,0 +1,91 @@
1
+ import defaultConfig from "./config";
2
+
3
+ import type { ComponentConfig } from "../types";
4
+
5
+ export type Config = typeof defaultConfig;
6
+
7
+ export interface Props {
8
+ /**
9
+ * Number of columns.
10
+ */
11
+ cols?: string;
12
+
13
+ /**
14
+ * Number of rows.
15
+ */
16
+ rows?: string;
17
+
18
+ /**
19
+ * Gap between items.
20
+ */
21
+ gap?: "none" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
22
+
23
+ /**
24
+ * Vertical gap override.
25
+ */
26
+ rowGap?: "none" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
27
+
28
+ /**
29
+ * Horizontal gap override.
30
+ */
31
+ colGap?: "none" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
32
+
33
+ /**
34
+ * Vertical alignment (align-items).
35
+ */
36
+ align?: "start" | "end" | "center" | "stretch" | "baseline" | "normal";
37
+
38
+ /**
39
+ * Items vertical align for multi-row grid containers (align-content).
40
+ */
41
+ content?:
42
+ | "start"
43
+ | "end"
44
+ | "center"
45
+ | "around"
46
+ | "evenly"
47
+ | "between"
48
+ | "normal"
49
+ | "stretch"
50
+ | "baseline";
51
+
52
+ /**
53
+ * Control how grid items are aligned along their inline axis (justify-items).
54
+ */
55
+ justify?: "start" | "end" | "end-safe" | "center" | "center-safe" | "stretch" | "normal";
56
+
57
+ /**
58
+ * Control how content is justified and aligned within the grid (place-content).
59
+ */
60
+ placeContent?:
61
+ | "start"
62
+ | "end"
63
+ | "end-safe"
64
+ | "center"
65
+ | "center-safe"
66
+ | "around"
67
+ | "evenly"
68
+ | "between"
69
+ | "stretch"
70
+ | "baseline";
71
+
72
+ /**
73
+ * Control how items are justified and aligned within the grid (place-items).
74
+ */
75
+ placeItems?: "start" | "end" | "end-safe" | "center" | "center-safe" | "stretch" | "baseline";
76
+
77
+ /**
78
+ * Allows changing HTML tag.
79
+ */
80
+ tag?: string;
81
+
82
+ /**
83
+ * Component config object.
84
+ */
85
+ config?: ComponentConfig<Config>;
86
+
87
+ /**
88
+ * Data-test attribute for automated testing.
89
+ */
90
+ dataTest?: string | null;
91
+ }
@@ -280,8 +280,8 @@ WithoutDivider.parameters = {
280
280
  export const Sizes = EnumTemplate.bind({});
281
281
  Sizes.args = { enum: "size", modelValues: {} };
282
282
 
283
- export const Variant = EnumTemplate.bind({});
284
- Variant.args = { enum: "variant", modelValues: {} };
283
+ export const Variants = EnumTemplate.bind({});
284
+ Variants.args = { enum: "variant", modelValues: {} };
285
285
 
286
286
  export const BackLink: StoryFn<UModalArgs> = (args: UModalArgs) => ({
287
287
  components: { UModal, UButton, UCheckbox, UCol, URow, UDivider, UInput, UInputPassword },
@@ -221,8 +221,8 @@ DisableConfirmButton.args = { confirmDisabled: true };
221
221
  export const Sizes = EnumTemplate.bind({});
222
222
  Sizes.args = { enum: "size", modelValues: {} };
223
223
 
224
- export const Variant = EnumTemplate.bind({});
225
- Variant.args = { enum: "variant", modelValues: {} };
224
+ export const Variants = EnumTemplate.bind({});
225
+ Variants.args = { enum: "variant", modelValues: {} };
226
226
 
227
227
  export const Colors: StoryFn<UModalConfirmArgs> = (args: UModalConfirmArgs, { argTypes }) => ({
228
228
  components: { UModalConfirm, UButton, URow },
@@ -212,9 +212,9 @@ Sizes.parameters = {
212
212
  },
213
213
  };
214
214
 
215
- export const Variant = EnumTemplate.bind({});
216
- Variant.args = { enum: "variant", description: "{enumValue}" };
217
- Variant.parameters = {
215
+ export const Variants = EnumTemplate.bind({});
216
+ Variants.args = { enum: "variant", description: "{enumValue}" };
217
+ Variants.parameters = {
218
218
  docs: {
219
219
  description: {
220
220
  story: "Page variant.",
@@ -131,6 +131,31 @@ describe("UCalendar.vue", () => {
131
131
  expect(rangeUpdate.to).not.toBeNull();
132
132
  });
133
133
 
134
+ it("Range – allows selecting the same day for from and to", async () => {
135
+ const component = mount(UCalendar, {
136
+ props: {
137
+ range: true,
138
+ modelValue: { from: null, to: null },
139
+ dateFormat: "Y-m-d",
140
+ },
141
+ });
142
+
143
+ const dayView = component.findComponent(DayView);
144
+ const days = dayView.findAll('[vl-key="day"]');
145
+
146
+ await days[5].trigger("click");
147
+ await days[5].trigger("click");
148
+
149
+ expect(component.emitted("update:modelValue")).toBeTruthy();
150
+ expect(component.emitted("update:modelValue")).toHaveLength(2);
151
+
152
+ const rangeUpdate = component.emitted("update:modelValue")![1][0] as RangeDate;
153
+
154
+ expect(rangeUpdate.from).not.toBeNull();
155
+ expect(rangeUpdate.to).not.toBeNull();
156
+ expect(rangeUpdate.from).toBe(rangeUpdate.to);
157
+ });
158
+
134
159
  it("Timepicker – shows timepicker when enabled", () => {
135
160
  const component = mount(UCalendar, {
136
161
  props: {
@@ -518,6 +543,94 @@ describe("UCalendar.vue", () => {
518
543
 
519
544
  expect(component.emitted("userDateChange")).toBeTruthy();
520
545
  });
546
+
547
+ it("ChangeRange – emits when first date is selected in range mode", async () => {
548
+ const component = mount(UCalendar, {
549
+ props: {
550
+ range: true,
551
+ modelValue: { from: null, to: null },
552
+ dateFormat: "Y-m-d",
553
+ },
554
+ });
555
+
556
+ const dayView = component.findComponent(DayView);
557
+ const days = dayView.findAll('[vl-key="day"]');
558
+
559
+ await days[0].trigger("click");
560
+
561
+ expect(component.emitted("change-range")).toBeTruthy();
562
+ expect(component.emitted("change-range")![0][0]).toHaveProperty("from");
563
+ expect(component.emitted("change-range")![0][0]).toHaveProperty("to");
564
+ });
565
+
566
+ it("ChangeRange – emits when both dates are selected in range mode", async () => {
567
+ const component = mount(UCalendar, {
568
+ props: {
569
+ range: true,
570
+ modelValue: { from: null, to: null },
571
+ dateFormat: "Y-m-d",
572
+ },
573
+ });
574
+
575
+ const dayView = component.findComponent(DayView);
576
+ const days = dayView.findAll('[vl-key="day"]');
577
+
578
+ await days[0].trigger("click");
579
+ await days[3].trigger("click");
580
+
581
+ const changeRangeEvents = component.emitted("change-range");
582
+
583
+ expect(changeRangeEvents).toBeTruthy();
584
+ expect(changeRangeEvents!.length).toBeGreaterThan(0);
585
+
586
+ const lastEvent = changeRangeEvents![changeRangeEvents!.length - 1][0] as RangeDate;
587
+
588
+ expect(lastEvent.from).not.toBeNull();
589
+ expect(lastEvent.to).not.toBeNull();
590
+ });
591
+
592
+ it("ChangeRange – emits when same date is selected twice in range mode", async () => {
593
+ const component = mount(UCalendar, {
594
+ props: {
595
+ range: true,
596
+ modelValue: { from: null, to: null },
597
+ dateFormat: "Y-m-d",
598
+ },
599
+ });
600
+
601
+ const dayView = component.findComponent(DayView);
602
+ const days = dayView.findAll('[vl-key="day"]');
603
+
604
+ await days[5].trigger("click");
605
+ await days[5].trigger("click");
606
+
607
+ const changeRangeEvents = component.emitted("change-range");
608
+
609
+ expect(changeRangeEvents).toBeTruthy();
610
+
611
+ const lastEvent = changeRangeEvents![changeRangeEvents!.length - 1][0] as RangeDate;
612
+
613
+ expect(lastEvent.from).not.toBeNull();
614
+ expect(lastEvent.to).not.toBeNull();
615
+ expect(lastEvent.from).toBe(lastEvent.to);
616
+ });
617
+
618
+ it("ChangeRange – does not emit when range mode is disabled", async () => {
619
+ const component = mount(UCalendar, {
620
+ props: {
621
+ range: false,
622
+ modelValue: null,
623
+ dateFormat: "Y-m-d",
624
+ },
625
+ });
626
+
627
+ const dayView = component.findComponent(DayView);
628
+ const day = dayView.find('[vl-key="day"]');
629
+
630
+ await day.trigger("click");
631
+
632
+ expect(component.emitted("change-range")).toBeFalsy();
633
+ });
521
634
  });
522
635
 
523
636
  describe("Exposed Properties", () => {
@@ -16,7 +16,9 @@ type UInputRef = InstanceType<typeof UInput>;
16
16
 
17
17
  defineOptions({ internal: true });
18
18
 
19
- const props = defineProps<UDatePickerRangeInputsProps>();
19
+ const props = withDefaults(defineProps<UDatePickerRangeInputsProps>(), {
20
+ dataTest: null,
21
+ });
20
22
 
21
23
  const rangeInputStartRef = useTemplateRef<UInputRef>("range-input-start");
22
24
  const rangeInputEndRef = useTemplateRef<UInputRef>("range-input-end");
@@ -140,6 +142,7 @@ defineExpose({
140
142
  v-bind="attrs.rangeInputFirstAttrs.value"
141
143
  :name="rangeInputName"
142
144
  no-autocomplete
145
+ :data-test="`${dataTest}-from`"
143
146
  @blur="updateDateValue(rangeStart, InputRangeType.Start)"
144
147
  @keydown.enter="updateDateValue(rangeStart, InputRangeType.Start)"
145
148
  @input="validateInput($event, InputRangeType.Start)"
@@ -153,6 +156,7 @@ defineExpose({
153
156
  v-bind="attrs.rangeInputLastAttrs.value"
154
157
  :name="rangeInputName"
155
158
  no-autocomplete
159
+ :data-test="`${dataTest}-to`"
156
160
  @blur="updateDateValue(rangeEnd, InputRangeType.End)"
157
161
  @keydown.enter="updateDateValue(rangeEnd, InputRangeType.End)"
158
162
  @input="validateInput($event, InputRangeType.End)"