vueless 1.0.2-beta.0 → 1.0.2-beta.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.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M445.93-445.93H194.02v-68.14h251.91v-252.15h68.14v252.15h252.15v68.14H514.07v251.91h-68.14v-251.91Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="m326.37-249.79 153.64-91.89 153.64 92.9-41.28-173.94L727.5-540.33l-178.17-15.52L480-720.02l-69.33 163.41-178.17 15.28 135.22 117.38-41.35 174.16ZM224.15-107.56l67.39-291.29L65.41-594.78l298.43-25.67L480-895.3l116.16 274.85 298.43 25.67-226.13 195.93 67.63 291.29L480-262.3 224.15-107.56ZM480-474.52Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M354.5-860v-65.5h251v65.5h-251Zm92.87 452.02h65.26v-230.48h-65.26v230.48ZM479.94-70q-75.49 0-142.05-28.94-66.56-28.94-116.36-78.7-49.79-49.75-78.65-116.29-28.86-66.54-28.86-142.01 0-75.47 28.88-142.04 28.88-66.56 78.68-116.4 49.79-49.84 116.35-78.83Q404.5-802.2 480-802.2q67.48 0 127.08 22.5 59.59 22.5 105.83 62.74l53.16-53.15 46.3 46.07-53.15 53.15q36.24 40.48 61.62 97.96 25.38 57.47 25.38 136.95 0 75.52-28.96 142.11-28.95 66.59-78.78 116.31T622.04-98.92Q555.43-70 479.94-70Zm.02-68.37q124.32 0 211.11-86.62 86.78-86.62 86.78-210.95 0-124.32-86.74-211.1-86.75-86.79-211.07-86.79t-211.11 86.75q-86.78 86.74-86.78 211.06 0 124.32 86.74 210.99 86.75 86.66 211.07 86.66Zm.04-296.61Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.0.2-beta.0",
3
+ "version": "1.0.2-beta.2",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
6
6
  "keywords": [
@@ -8,6 +8,7 @@ import UIcon from "../../ui.image-icon/UIcon.vue";
8
8
  import type { Props } from "../types.ts";
9
9
 
10
10
  describe("UButton.vue", () => {
11
+ // Props tests
11
12
  describe("Props", () => {
12
13
  // Variant prop
13
14
  it("applies the correct variant class", async () => {
@@ -0,0 +1,365 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, it, expect } from "vitest";
3
+ import { createRouter, createWebHistory } from "vue-router";
4
+
5
+ import ULink from "../ULink.vue";
6
+
7
+ import type { Props } from "../types.ts";
8
+
9
+ // Create a mock router for testing router-link functionality
10
+ const router = createRouter({
11
+ history: createWebHistory(),
12
+ routes: [
13
+ { path: "/", name: "home", component: { template: "<div>Home</div>" } },
14
+ { path: "/about", name: "about", component: { template: "<div>About</div>" } },
15
+ ],
16
+ });
17
+
18
+ describe("ULink.vue", () => {
19
+ // Props tests
20
+ describe("Props", () => {
21
+ // Size prop
22
+ it("applies the correct size class", async () => {
23
+ const size = {
24
+ sm: "text-small",
25
+ md: "text-medium",
26
+ lg: "text-large",
27
+ };
28
+
29
+ Object.entries(size).forEach(([size, classes]) => {
30
+ const component = mount(ULink, {
31
+ props: {
32
+ size: size as Props["size"],
33
+ },
34
+ });
35
+
36
+ expect(component.attributes("class")).toContain(classes);
37
+ });
38
+ });
39
+
40
+ // Color prop
41
+ it("applies the correct color class", async () => {
42
+ const colors = [
43
+ "primary",
44
+ "secondary",
45
+ "error",
46
+ "warning",
47
+ "success",
48
+ "info",
49
+ "notice",
50
+ "neutral",
51
+ "grayscale",
52
+ "inherit",
53
+ ];
54
+
55
+ colors.forEach((color) => {
56
+ const component = mount(ULink, {
57
+ props: {
58
+ color: color as Props["color"],
59
+ },
60
+ });
61
+
62
+ expect(component.attributes("class")).toContain(color);
63
+ });
64
+ });
65
+
66
+ // Label prop
67
+ it("renders the correct label text", () => {
68
+ const label = "Link Text";
69
+
70
+ const component = mount(ULink, {
71
+ props: {
72
+ label,
73
+ },
74
+ });
75
+
76
+ expect(component.text()).toBe(label);
77
+ });
78
+
79
+ // Href prop
80
+ it("renders the correct href attribute", () => {
81
+ const href = "https://example.com";
82
+
83
+ const component = mount(ULink, {
84
+ props: {
85
+ href,
86
+ },
87
+ });
88
+
89
+ expect(component.attributes("href")).toBe(href);
90
+ });
91
+
92
+ // Type prop
93
+ it("applies the correct href prefix based on type", () => {
94
+ const types = {
95
+ phone: { href: "1234567890", expected: "tel:1234567890" },
96
+ email: { href: "test@example.com", expected: "mailto:test@example.com" },
97
+ link: { href: "https://example.com", expected: "https://example.com" },
98
+ };
99
+
100
+ Object.entries(types).forEach(([type, { href, expected }]) => {
101
+ const component = mount(ULink, {
102
+ props: {
103
+ type: type as Props["type"],
104
+ href,
105
+ },
106
+ });
107
+
108
+ expect(component.attributes("href")).toBe(expected);
109
+ });
110
+ });
111
+
112
+ // To prop
113
+ it("renders as router-link when to prop is provided", async () => {
114
+ const to = "/about";
115
+
116
+ const component = mount(ULink, {
117
+ props: {
118
+ to,
119
+ },
120
+ global: {
121
+ plugins: [router],
122
+ },
123
+ });
124
+
125
+ // Check if it's a router-link by looking for the to attribute
126
+ expect(component.findComponent({ name: "RouterLink" }).exists()).toBe(true);
127
+ });
128
+
129
+ // Target prop
130
+ it("applies the correct target attribute", () => {
131
+ const targets = ["_blank", "_self", "_parent", "_top"];
132
+
133
+ targets.forEach((target) => {
134
+ const component = mount(ULink, {
135
+ props: {
136
+ target: target as Props["target"],
137
+ href: "https://example.com",
138
+ },
139
+ });
140
+
141
+ expect(component.attributes("target")).toBe(target);
142
+ });
143
+ });
144
+
145
+ // Rel prop
146
+ it("applies the correct rel attribute", () => {
147
+ const rel = "noopener noreferrer";
148
+
149
+ const component = mount(ULink, {
150
+ props: {
151
+ rel,
152
+ href: "https://example.com",
153
+ },
154
+ });
155
+
156
+ expect(component.attributes("rel")).toBe(rel);
157
+ });
158
+
159
+ // Underlined prop
160
+ it("applies underlined class when underlined prop is true", () => {
161
+ const underlined = {
162
+ true: "underline",
163
+ false: "no-underline",
164
+ undefined: "hover:underline",
165
+ };
166
+
167
+ Object.entries(underlined).forEach(([value, expectedClass]) => {
168
+ const component = mount(ULink, {
169
+ props: {
170
+ underlined: value === "undefined" ? undefined : value === "true",
171
+ },
172
+ });
173
+
174
+ expect(component.attributes("class")).toContain(expectedClass);
175
+ });
176
+ });
177
+
178
+ // Dashed prop
179
+ it("applies dashed class when dashed prop is true", () => {
180
+ const dashed = true;
181
+ const dashedClass = "decoration-dashed";
182
+
183
+ const component = mount(ULink, {
184
+ props: {
185
+ dashed,
186
+ },
187
+ });
188
+
189
+ expect(component.attributes("class")).toContain(dashedClass);
190
+ });
191
+
192
+ // Dotted prop
193
+ it("applies dotted class when dotted prop is true", () => {
194
+ const dotted = true;
195
+ const dottedClass = "decoration-dotted";
196
+
197
+ const component = mount(ULink, {
198
+ props: {
199
+ dotted,
200
+ },
201
+ });
202
+
203
+ expect(component.attributes("class")).toContain(dottedClass);
204
+ });
205
+
206
+ // Disabled prop
207
+ it("applies disabled class when disabled prop is true", () => {
208
+ const disabled = true;
209
+
210
+ const component = mount(ULink, {
211
+ props: {
212
+ disabled,
213
+ },
214
+ });
215
+
216
+ expect(component.attributes("class")).toContain("cursor-not-allowed");
217
+ });
218
+
219
+ // Block prop
220
+ it("applies block class when block prop is true", () => {
221
+ const block = true;
222
+ const blockClass = "w-full";
223
+
224
+ const component = mount(ULink, {
225
+ props: {
226
+ block,
227
+ },
228
+ });
229
+
230
+ expect(component.attributes("class")).toContain(blockClass);
231
+ });
232
+
233
+ // DataTest prop
234
+ it("applies the correct data-test attribute", () => {
235
+ const dataTest = "test-link";
236
+
237
+ const component = mount(ULink, {
238
+ props: {
239
+ dataTest,
240
+ },
241
+ });
242
+
243
+ expect(component.attributes("data-test")).toBe(dataTest);
244
+ });
245
+ });
246
+
247
+ // Slots tests
248
+ describe("Slots", () => {
249
+ // Default slot
250
+ it("renders content from default slot", () => {
251
+ const slotContent = "Custom Content";
252
+ const label = "Link";
253
+
254
+ const component = mount(ULink, {
255
+ props: {
256
+ label,
257
+ },
258
+ slots: {
259
+ default: slotContent,
260
+ },
261
+ });
262
+
263
+ expect(component.text()).not.toContain(label);
264
+ expect(component.text()).toContain(slotContent);
265
+ });
266
+
267
+ // Default slot with router-link
268
+ it("renders content from default slot with router-link", () => {
269
+ const slotContent = "Custom Content";
270
+ const label = "Link";
271
+
272
+ const component = mount(ULink, {
273
+ props: {
274
+ label,
275
+ to: "/about",
276
+ },
277
+ slots: {
278
+ default: slotContent,
279
+ },
280
+ global: {
281
+ plugins: [router],
282
+ },
283
+ });
284
+
285
+ expect(component.text()).not.toContain(label);
286
+ expect(component.text()).toContain(slotContent);
287
+ });
288
+
289
+ // Default slot with bindings
290
+ it("provides isActive and isExactActive bindings to default slot with router-link", () => {
291
+ const component = mount(ULink, {
292
+ props: {
293
+ to: "/about",
294
+ },
295
+ slots: {
296
+ default: `
297
+ <template #default="{ isActive, isExactActive }">
298
+ <span class="active-status">{{ isActive }}</span>
299
+ <span class="exact-active-status">{{ isExactActive }}</span>
300
+ </template>
301
+ `,
302
+ },
303
+ global: {
304
+ plugins: [router],
305
+ },
306
+ });
307
+
308
+ expect(component.find(".active-status").exists()).toBe(true);
309
+ expect(component.find(".exact-active-status").exists()).toBe(true);
310
+ });
311
+ });
312
+
313
+ // Events tests
314
+ describe("Events", () => {
315
+ // Click event
316
+ it("emits click event when clicked", async () => {
317
+ const component = mount(ULink, {});
318
+
319
+ await component.trigger("click");
320
+ expect(component.emitted("click")).toBeTruthy();
321
+ });
322
+
323
+ // Mouseover event
324
+ it("emits mouseover event when hovered", async () => {
325
+ const component = mount(ULink, {});
326
+
327
+ await component.trigger("mouseover");
328
+ expect(component.emitted("mouseover")).toBeTruthy();
329
+ });
330
+
331
+ // Focus event
332
+ it("emits focus event when focused", async () => {
333
+ const component = mount(ULink, {});
334
+
335
+ await component.trigger("focus");
336
+ expect(component.emitted("focus")).toBeTruthy();
337
+ });
338
+
339
+ // Blur event
340
+ it("emits blur event when blurred", async () => {
341
+ const component = mount(ULink, {});
342
+
343
+ await component.trigger("blur");
344
+ expect(component.emitted("blur")).toBeTruthy();
345
+ });
346
+
347
+ // Keydown event
348
+ it("emits keydown event when key is pressed", async () => {
349
+ const component = mount(ULink, {});
350
+
351
+ await component.trigger("keydown");
352
+ expect(component.emitted("keydown")).toBeTruthy();
353
+ });
354
+ });
355
+
356
+ // Exposed refs tests
357
+ describe("Exposed refs", () => {
358
+ // linkRef
359
+ it("exposes linkRef", () => {
360
+ const component = mount(ULink, {});
361
+
362
+ expect(component.vm.linkRef).toBeDefined();
363
+ });
364
+ });
365
+ });
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, useTemplateRef } from "vue";
2
+ import { computed, ref, useId, useTemplateRef } from "vue";
3
3
 
4
4
  import UButton from "../ui.button/UButton.vue";
5
5
 
@@ -27,6 +27,7 @@ const emit = defineEmits([
27
27
  "update:modelValue",
28
28
  ]);
29
29
 
30
+ const elementId = props.id || useId();
30
31
  const optionsRef = useTemplateRef<HTMLDivElement>("options");
31
32
 
32
33
  const hoveredItem = ref();
@@ -106,7 +107,7 @@ const {
106
107
  </script>
107
108
 
108
109
  <template>
109
- <div ref="options" v-bind="optionsAttrs">
110
+ <div :id="elementId" ref="options" v-bind="optionsAttrs" :data-test="getDataTest()">
110
111
  <template v-for="(option, index) in options" :key="index">
111
112
  <UButton
112
113
  :label="option.label"
@@ -57,16 +57,7 @@ const DefaultTemplate: StoryFn<UToggleArgs> = (args: UToggleArgs) => ({
57
57
 
58
58
  const EnumTemplate: StoryFn<UToggleArgs> = (args: UToggleArgs, { argTypes }) => ({
59
59
  components: { UToggle, URow },
60
- setup() {
61
- const values = ref(argTypes.size?.options);
62
-
63
- return {
64
- args,
65
- values,
66
- argTypes,
67
- getArgs,
68
- };
69
- },
60
+ setup: () => ({ args, argTypes, getArgs, values: ref(argTypes.size?.options) }),
70
61
  template: `
71
62
  <URow>
72
63
  <UToggle
@@ -110,15 +101,10 @@ Square.args = {
110
101
  name: "square",
111
102
  square: true,
112
103
  options: [
113
- { value: "11", label: "star" },
114
- { value: "12", label: "add" },
115
- { value: "13", label: "timer" },
104
+ { value: "11", icon: "star" },
105
+ { value: "12", icon: "add" },
106
+ { value: "13", icon: "timer" },
116
107
  ],
117
- slotTemplate: `
118
- <template #option="{ label, index }">
119
- <UIcon :name="label" color="inherit" />
120
- </template>
121
- `,
122
108
  };
123
109
 
124
110
  export const Disabled = DefaultTemplate.bind({});