@getmicdrop/svelte-components 5.4.2 → 5.5.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/dist/calendar/AboutShow/AboutShow.svelte +172 -172
- package/dist/calendar/Calendar/MiniMonthCalendar.svelte +782 -782
- package/dist/calendar/FAQs/FAQs.svelte +75 -75
- package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte +126 -126
- package/dist/calendar/OrderSummary/OrderSummary.svelte +367 -367
- package/dist/calendar/PublicCard/PublicCard.svelte +134 -134
- package/dist/calendar/ShowCard/ShowCard.svelte +157 -157
- package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte +61 -61
- package/dist/components/Layout/Grid.svelte +4 -4
- package/dist/components/Layout/Section.svelte +80 -80
- package/dist/components/Layout/Sidebar.svelte +108 -108
- package/dist/components/Layout/Stack.svelte +6 -6
- package/dist/constants/validation.js +91 -91
- package/dist/constants/validation.spec.js +64 -64
- package/dist/index.d.ts +113 -4
- package/dist/index.js +226 -47
- package/dist/patterns/data/DataGrid.svelte +45 -45
- package/dist/patterns/data/DataList.svelte +24 -24
- package/dist/patterns/data/DataTable.svelte +36 -36
- package/dist/patterns/forms/FormActions.spec.js +88 -88
- package/dist/patterns/forms/FormActions.stories.svelte +97 -97
- package/dist/patterns/forms/FormActions.svelte +46 -46
- package/dist/patterns/forms/FormGrid.svelte +33 -33
- package/dist/patterns/forms/FormSection.svelte +32 -32
- package/dist/patterns/forms/FormValidationSummary.stories.svelte +83 -83
- package/dist/patterns/forms/FormValidationSummary.svelte +32 -32
- package/dist/patterns/layout/Sidebar.svelte +39 -39
- package/dist/patterns/navigation/BottomNav.stories.svelte +117 -117
- package/dist/patterns/navigation/BottomNav.svelte +20 -20
- package/dist/patterns/navigation/Header.stories.svelte +77 -77
- package/dist/patterns/navigation/Header.svelte +193 -193
- package/dist/patterns/page/PageHeader.svelte +18 -18
- package/dist/patterns/page/PageLayout.svelte +40 -40
- package/dist/patterns/page/PageLoader.spec.js +54 -54
- package/dist/patterns/page/PageLoader.stories.svelte +137 -137
- package/dist/patterns/page/PageLoader.svelte +24 -24
- package/dist/patterns/page/SectionHeader.svelte +29 -29
- package/dist/presets/badges.js +112 -112
- package/dist/presets/buttons.js +76 -76
- package/dist/presets/index.js +9 -9
- package/dist/primitives/Accordion/Accordion.stories.svelte +75 -75
- package/dist/primitives/Accordion/Accordion.svelte +42 -42
- package/dist/primitives/Accordion/AccordionItem.svelte +95 -95
- package/dist/primitives/Alert/Alert.spec.js +170 -170
- package/dist/primitives/Alert/Alert.stories.svelte +88 -88
- package/dist/primitives/Alert/Alert.svelte +27 -27
- package/dist/primitives/Avatar/Avatar.stories.svelte +94 -94
- package/dist/primitives/Avatar/Avatar.svelte +66 -66
- package/dist/primitives/Badges/Badge.spec.js +103 -103
- package/dist/primitives/Badges/Badge.stories.svelte +86 -86
- package/dist/primitives/Badges/Badge.svelte +79 -79
- package/dist/primitives/BottomSheet/BottomSheet.spec.js +127 -127
- package/dist/primitives/BottomSheet/BottomSheet.stories.svelte +83 -83
- package/dist/primitives/BottomSheet/BottomSheet.svelte +100 -100
- package/dist/primitives/Breadcrumb/Breadcrumb.spec.js +120 -120
- package/dist/primitives/Breadcrumb/Breadcrumb.stories.svelte +23 -23
- package/dist/primitives/Breadcrumb/Breadcrumb.svelte +89 -89
- package/dist/primitives/Button/Button.spec.js +211 -211
- package/dist/primitives/Button/Button.stories.svelte +76 -76
- package/dist/primitives/Button/Button.svelte +270 -269
- package/dist/primitives/Button/Button.svelte.d.ts.map +1 -1
- package/dist/primitives/Button/ButtonSaveDemo.spec.js +48 -48
- package/dist/primitives/Button/ButtonSaveDemo.svelte +25 -25
- package/dist/primitives/Button/ButtonVariantShowcase.svelte +129 -129
- package/dist/primitives/Card.spec.js +49 -49
- package/dist/primitives/Card.stories.svelte +22 -22
- package/dist/primitives/Card.svelte +28 -28
- package/dist/primitives/Checkbox/Checkbox.stories.svelte +84 -84
- package/dist/primitives/Checkbox/Checkbox.svelte +88 -88
- package/dist/primitives/DarkModeToggle.spec.js +357 -357
- package/dist/primitives/DarkModeToggle.stories.svelte +57 -57
- package/dist/primitives/DarkModeToggle.svelte +136 -136
- package/dist/primitives/Drawer/Drawer.stories.svelte +80 -80
- package/dist/primitives/Drawer/Drawer.svelte +120 -120
- package/dist/primitives/Dropdown/Dropdown.stories.svelte +137 -137
- package/dist/primitives/Dropdown/Dropdown.svelte +14 -14
- package/dist/primitives/Dropdown/DropdownItem.svelte +80 -80
- package/dist/primitives/Icons/ArrowLeft.svelte +8 -8
- package/dist/primitives/Icons/ArrowRight.svelte +8 -8
- package/dist/primitives/Icons/Availability.svelte +14 -14
- package/dist/primitives/Icons/Back.svelte +14 -14
- package/dist/primitives/Icons/CheckCircle.svelte +6 -6
- package/dist/primitives/Icons/CheckCircleOutline.svelte +15 -15
- package/dist/primitives/Icons/ChevronLeft.svelte +4 -4
- package/dist/primitives/Icons/ChevronRight.svelte +4 -4
- package/dist/primitives/Icons/Copy.svelte +15 -15
- package/dist/primitives/Icons/Cross.svelte +5 -5
- package/dist/primitives/Icons/DownArrow.svelte +8 -8
- package/dist/primitives/Icons/ErrorCircle.svelte +6 -6
- package/dist/primitives/Icons/FacebookIcon.svelte +2 -2
- package/dist/primitives/Icons/Home.svelte +15 -15
- package/dist/primitives/Icons/Icon.spec.js +169 -169
- package/dist/primitives/Icons/Icon.stories.svelte +100 -100
- package/dist/primitives/Icons/Icon.svelte +52 -52
- package/dist/primitives/Icons/IconGallery.stories.svelte +235 -235
- package/dist/primitives/Icons/Info.svelte +7 -7
- package/dist/primitives/Icons/InstagramIcon.svelte +4 -4
- package/dist/primitives/Icons/LogoInstagram.svelte +2 -2
- package/dist/primitives/Icons/Message.svelte +15 -15
- package/dist/primitives/Icons/MoonIcon.svelte +5 -5
- package/dist/primitives/Icons/More.svelte +21 -21
- package/dist/primitives/Icons/MoreHori.spec.js +61 -61
- package/dist/primitives/Icons/MoreHori.svelte +22 -22
- package/dist/primitives/Icons/Notification.svelte +14 -14
- package/dist/primitives/Icons/Payment.svelte +14 -14
- package/dist/primitives/Icons/Profile.svelte +21 -21
- package/dist/primitives/Icons/Reload.svelte +29 -29
- package/dist/primitives/Icons/Shows.svelte +21 -21
- package/dist/primitives/Icons/Signout.svelte +21 -21
- package/dist/primitives/Icons/SunIcon.svelte +8 -8
- package/dist/primitives/Icons/TiktokIcon.svelte +2 -2
- package/dist/primitives/Icons/TwitterIcon.svelte +2 -2
- package/dist/primitives/Icons/WarningIcon.spec.js +18 -18
- package/dist/primitives/Icons/WarningIcon.svelte +5 -5
- package/dist/primitives/Input/Input.spec.js +573 -573
- package/dist/primitives/Input/Input.stories.svelte +139 -139
- package/dist/primitives/Input/Input.svelte +391 -391
- package/dist/primitives/Input/Select.spec.js +218 -218
- package/dist/primitives/Input/Select.stories.svelte +112 -112
- package/dist/primitives/Input/Select.svelte +128 -128
- package/dist/primitives/Input/Textarea.stories.svelte +137 -137
- package/dist/primitives/Input/Textarea.svelte +35 -35
- package/dist/primitives/Label/Label.svelte +37 -37
- package/dist/primitives/Modal/Modal.spec.js +95 -95
- package/dist/primitives/Modal/Modal.stories.svelte +86 -86
- package/dist/primitives/Modal/Modal.svelte +158 -158
- package/dist/primitives/NumberInput/NumberInput.svelte +106 -0
- package/dist/primitives/NumberInput/NumberInput.svelte.d.ts +23 -0
- package/dist/primitives/NumberInput/NumberInput.svelte.d.ts.map +1 -0
- package/dist/primitives/NumberInput/index.d.ts +2 -0
- package/dist/primitives/NumberInput/index.d.ts.map +1 -0
- package/dist/primitives/NumberInput/index.js +1 -0
- package/dist/primitives/Pagination/Pagination.stories.svelte +76 -76
- package/dist/primitives/Pagination/Pagination.svelte +261 -261
- package/dist/primitives/Radio/Radio.stories.svelte +80 -80
- package/dist/primitives/Radio/Radio.svelte +67 -67
- package/dist/primitives/Skeleton/CardPlaceholder.svelte +87 -87
- package/dist/primitives/Skeleton/ImagePlaceholder.svelte +59 -59
- package/dist/primitives/Skeleton/ListPlaceholder.svelte +76 -76
- package/dist/primitives/Skeleton/Skeleton.stories.svelte +151 -151
- package/dist/primitives/Skeleton/Skeleton.svelte +26 -26
- package/dist/primitives/Spinner/Spinner.spec.js +75 -75
- package/dist/primitives/Spinner/Spinner.stories.svelte +29 -29
- package/dist/primitives/Spinner/Spinner.svelte +20 -20
- package/dist/primitives/Tabs/TabItem.svelte +49 -49
- package/dist/primitives/Tabs/Tabs.stories.svelte +112 -112
- package/dist/primitives/Tabs/Tabs.svelte +123 -123
- package/dist/primitives/Toggle.spec.js +127 -127
- package/dist/primitives/Toggle.stories.svelte +92 -92
- package/dist/primitives/Toggle.svelte +71 -71
- package/dist/primitives/Typography/Typography.svelte +53 -53
- package/dist/primitives/ValidationError.spec.js +103 -103
- package/dist/primitives/ValidationError.stories.svelte +69 -69
- package/dist/primitives/ValidationError.svelte +29 -29
- package/dist/recipes/CropImage/CropImage.spec.js +216 -216
- package/dist/recipes/CropImage/CropImage.stories.svelte +104 -104
- package/dist/recipes/CropImage/CropImage.svelte +238 -238
- package/dist/recipes/ImageUploader/ImageUploader.stories.svelte +125 -125
- package/dist/recipes/ImageUploader/ImageUploader.svelte +804 -804
- package/dist/recipes/Toaster/Toaster.stories.svelte +62 -62
- package/dist/recipes/feedback/EmptyState/EmptyState.svelte +1 -1
- package/dist/recipes/feedback/ErrorDisplay.spec.js +69 -69
- package/dist/recipes/feedback/ErrorDisplay.stories.svelte +101 -101
- package/dist/recipes/feedback/ErrorDisplay.svelte +1 -1
- package/dist/recipes/feedback/StatusIndicator/StatusIndicator.spec.js +129 -129
- package/dist/recipes/feedback/StatusIndicator/StatusIndicator.svelte +157 -157
- package/dist/recipes/fields/CheckboxField.svelte +85 -85
- package/dist/recipes/fields/FormField.svelte +58 -58
- package/dist/recipes/fields/RadioGroup.svelte +95 -95
- package/dist/recipes/fields/SelectField.svelte +80 -80
- package/dist/recipes/fields/TextareaField.svelte +97 -97
- package/dist/recipes/fields/ToggleField.svelte +60 -60
- package/dist/recipes/fields/index.js +7 -7
- package/dist/recipes/inputs/MultiSelect.spec.js +257 -257
- package/dist/recipes/inputs/MultiSelect.stories.svelte +133 -133
- package/dist/recipes/inputs/MultiSelect.svelte +249 -276
- package/dist/recipes/inputs/MultiSelect.svelte.d.ts +28 -17
- package/dist/recipes/inputs/MultiSelect.svelte.d.ts.map +1 -1
- package/dist/recipes/inputs/OTPInput.spec.js +238 -238
- package/dist/recipes/inputs/OTPInput.stories.svelte +162 -162
- package/dist/recipes/inputs/OTPInput.svelte +29 -29
- package/dist/recipes/inputs/PasswordInput.svelte +22 -22
- package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.spec.js +173 -173
- package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.svelte +42 -42
- package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.spec.js +300 -300
- package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.stories.svelte +123 -123
- package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.svelte +326 -326
- package/dist/recipes/inputs/Search.svelte +37 -37
- package/dist/recipes/inputs/SelectDropdown.svelte +57 -57
- package/dist/recipes/modals/AlertModal.svelte +130 -130
- package/dist/recipes/modals/ConfirmationModal.spec.js +191 -191
- package/dist/recipes/modals/ConfirmationModal.stories.svelte +119 -119
- package/dist/recipes/modals/ConfirmationModal.svelte +152 -152
- package/dist/recipes/modals/InputModal.svelte +182 -182
- package/dist/recipes/modals/ModalStateManager.spec.js +100 -100
- package/dist/recipes/modals/ModalStateManager.svelte +77 -77
- package/dist/recipes/modals/ModalTestWrapper.svelte +65 -65
- package/dist/recipes/modals/StatusModal.svelte +206 -206
- package/dist/services/EventService.js +75 -75
- package/dist/services/EventService.spec.js +217 -217
- package/dist/services/ShowService.spec.js +342 -342
- package/dist/stores/auth.js +36 -36
- package/dist/stores/auth.spec.js +139 -139
- package/dist/stores/toaster.js +13 -13
- package/dist/stories/ButtonAuditReview.stories.svelte +14 -14
- package/dist/stories/ButtonAuditReview.svelte +427 -427
- package/dist/stories/PatternsGallery.stories.svelte +19 -19
- package/dist/stories/PatternsGallery.svelte +206 -206
- package/dist/stories/PrimitivesGallery.stories.svelte +19 -19
- package/dist/stories/PrimitivesGallery.svelte +725 -725
- package/dist/stories/RecipesGallery.stories.svelte +19 -19
- package/dist/stories/RecipesGallery.svelte +271 -271
- package/dist/stories/button-audit-manifest.json +11186 -11186
- package/dist/tailwind/preset.cjs +82 -82
- package/dist/tokens/tokens.css +87 -87
- package/dist/utils/utils.js +354 -354
- package/package.json +283 -283
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
import { render, screen, waitFor } from "@testing-library/svelte";
|
|
2
|
-
import { expect, describe, test, vi } from "vitest";
|
|
3
|
-
import PasswordStrengthIndicator from "./PasswordStrengthIndicator.svelte";
|
|
4
|
-
|
|
5
|
-
// Debounce delay + buffer
|
|
6
|
-
const DEBOUNCE_WAIT = 500;
|
|
7
|
-
|
|
8
|
-
function setupTest(args = {}) {
|
|
9
|
-
const { component, container } = render(PasswordStrengthIndicator, {
|
|
10
|
-
props: {
|
|
11
|
-
password: "",
|
|
12
|
-
...args,
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
return { component, container };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe("PasswordStrengthIndicator Component Tests", () => {
|
|
19
|
-
test("Does not render when password is empty", () => {
|
|
20
|
-
const { container } = setupTest({ password: "" });
|
|
21
|
-
const indicator = container.querySelector(".pt-2");
|
|
22
|
-
expect(indicator).not.toBeInTheDocument();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("Renders after debounce when password has value", async () => {
|
|
26
|
-
const { container } = setupTest({ password: "test" });
|
|
27
|
-
await waitFor(
|
|
28
|
-
() => {
|
|
29
|
-
const indicator = container.querySelector(".pt-2");
|
|
30
|
-
expect(indicator).toBeInTheDocument();
|
|
31
|
-
},
|
|
32
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("Has three strength bars", async () => {
|
|
37
|
-
const { container } = setupTest({ password: "test" });
|
|
38
|
-
await waitFor(
|
|
39
|
-
() => {
|
|
40
|
-
const bars = container.querySelectorAll(".h-1.flex-1.rounded-full");
|
|
41
|
-
expect(bars.length).toBe(3);
|
|
42
|
-
},
|
|
43
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("Shows filled bars for weak password", async () => {
|
|
48
|
-
const { container } = setupTest({ password: "ab" });
|
|
49
|
-
await waitFor(
|
|
50
|
-
() => {
|
|
51
|
-
// At least one bar should be filled with a color
|
|
52
|
-
const coloredBars = container.querySelectorAll(
|
|
53
|
-
".bg-red-600, .bg-green-600"
|
|
54
|
-
);
|
|
55
|
-
expect(coloredBars.length).toBeGreaterThan(0);
|
|
56
|
-
},
|
|
57
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("Shows filled bars for good password", async () => {
|
|
62
|
-
const { container } = setupTest({ password: "Abc12345" });
|
|
63
|
-
await waitFor(
|
|
64
|
-
() => {
|
|
65
|
-
// All 3 bars should be filled for a good password
|
|
66
|
-
const successBars = container.querySelectorAll(".bg-green-600");
|
|
67
|
-
expect(successBars.length).toBe(3);
|
|
68
|
-
},
|
|
69
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("Exports score prop", () => {
|
|
74
|
-
const { component } = setupTest({ password: "test123" });
|
|
75
|
-
// Score should be accessible
|
|
76
|
-
expect(component).toBeDefined();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("Exports strengthText prop", () => {
|
|
80
|
-
const { component } = setupTest({ password: "test123" });
|
|
81
|
-
expect(component).toBeDefined();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("Exports textColor prop", () => {
|
|
85
|
-
const { component } = setupTest({ password: "test123" });
|
|
86
|
-
expect(component).toBeDefined();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("Uses danger color for weak passwords", async () => {
|
|
90
|
-
const { container } = setupTest({ password: "weak" });
|
|
91
|
-
await waitFor(
|
|
92
|
-
() => {
|
|
93
|
-
const dangerBars = container.querySelectorAll(".bg-red-600");
|
|
94
|
-
expect(dangerBars.length).toBeGreaterThan(0);
|
|
95
|
-
},
|
|
96
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("Uses success color for good/strong passwords", async () => {
|
|
101
|
-
const { container } = setupTest({ password: "StrongP@ss123" });
|
|
102
|
-
await waitFor(
|
|
103
|
-
() => {
|
|
104
|
-
const successBars = container.querySelectorAll(".bg-green-600");
|
|
105
|
-
expect(successBars.length).toBe(3);
|
|
106
|
-
},
|
|
107
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("Has transition animation classes on bars", async () => {
|
|
112
|
-
const { container } = setupTest({ password: "test" });
|
|
113
|
-
await waitFor(
|
|
114
|
-
() => {
|
|
115
|
-
const bars = container.querySelectorAll(".h-1.flex-1.rounded-full");
|
|
116
|
-
expect(bars.length).toBe(3);
|
|
117
|
-
bars.forEach((bar) => {
|
|
118
|
-
expect(bar).toHaveClass("transition-colors");
|
|
119
|
-
expect(bar).toHaveClass("duration-300");
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("Very long password shows strong indicator", async () => {
|
|
127
|
-
const { container } = setupTest({ password: "1234567890123" }); // 13 chars
|
|
128
|
-
await waitFor(
|
|
129
|
-
() => {
|
|
130
|
-
const successBars = container.querySelectorAll(".bg-green-600");
|
|
131
|
-
expect(successBars.length).toBe(3);
|
|
132
|
-
},
|
|
133
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe("Password Strength Scoring", () => {
|
|
139
|
-
test("Score 2+ (Good) shows 3 success bars - 8+ chars with 2 diversity", async () => {
|
|
140
|
-
const { container } = setupTest({ password: "Abcdefgh" }); // Upper + lower
|
|
141
|
-
await waitFor(
|
|
142
|
-
() => {
|
|
143
|
-
const successBars = container.querySelectorAll(".bg-green-600");
|
|
144
|
-
expect(successBars.length).toBe(3);
|
|
145
|
-
},
|
|
146
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
147
|
-
);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("Score 3 (Strong) - 10+ chars, multiple diversity", async () => {
|
|
151
|
-
const { container } = setupTest({ password: "Abcdefgh1!" }); // Upper, lower, number, symbol
|
|
152
|
-
await waitFor(
|
|
153
|
-
() => {
|
|
154
|
-
const successBars = container.querySelectorAll(".bg-green-600");
|
|
155
|
-
expect(successBars.length).toBe(3);
|
|
156
|
-
},
|
|
157
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
158
|
-
);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("Weak password shows danger color bars", async () => {
|
|
162
|
-
const { container } = setupTest({ password: "abcdef" });
|
|
163
|
-
await waitFor(
|
|
164
|
-
() => {
|
|
165
|
-
const coloredBars = container.querySelectorAll(
|
|
166
|
-
".bg-red-600, .bg-green-600"
|
|
167
|
-
);
|
|
168
|
-
expect(coloredBars.length).toBeGreaterThan(0);
|
|
169
|
-
},
|
|
170
|
-
{ timeout: DEBOUNCE_WAIT }
|
|
171
|
-
);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
1
|
+
import { render, screen, waitFor } from "@testing-library/svelte";
|
|
2
|
+
import { expect, describe, test, vi } from "vitest";
|
|
3
|
+
import PasswordStrengthIndicator from "./PasswordStrengthIndicator.svelte";
|
|
4
|
+
|
|
5
|
+
// Debounce delay + buffer
|
|
6
|
+
const DEBOUNCE_WAIT = 500;
|
|
7
|
+
|
|
8
|
+
function setupTest(args = {}) {
|
|
9
|
+
const { component, container } = render(PasswordStrengthIndicator, {
|
|
10
|
+
props: {
|
|
11
|
+
password: "",
|
|
12
|
+
...args,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
return { component, container };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("PasswordStrengthIndicator Component Tests", () => {
|
|
19
|
+
test("Does not render when password is empty", () => {
|
|
20
|
+
const { container } = setupTest({ password: "" });
|
|
21
|
+
const indicator = container.querySelector(".pt-2");
|
|
22
|
+
expect(indicator).not.toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("Renders after debounce when password has value", async () => {
|
|
26
|
+
const { container } = setupTest({ password: "test" });
|
|
27
|
+
await waitFor(
|
|
28
|
+
() => {
|
|
29
|
+
const indicator = container.querySelector(".pt-2");
|
|
30
|
+
expect(indicator).toBeInTheDocument();
|
|
31
|
+
},
|
|
32
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("Has three strength bars", async () => {
|
|
37
|
+
const { container } = setupTest({ password: "test" });
|
|
38
|
+
await waitFor(
|
|
39
|
+
() => {
|
|
40
|
+
const bars = container.querySelectorAll(".h-1.flex-1.rounded-full");
|
|
41
|
+
expect(bars.length).toBe(3);
|
|
42
|
+
},
|
|
43
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("Shows filled bars for weak password", async () => {
|
|
48
|
+
const { container } = setupTest({ password: "ab" });
|
|
49
|
+
await waitFor(
|
|
50
|
+
() => {
|
|
51
|
+
// At least one bar should be filled with a color
|
|
52
|
+
const coloredBars = container.querySelectorAll(
|
|
53
|
+
".bg-red-600, .bg-green-600"
|
|
54
|
+
);
|
|
55
|
+
expect(coloredBars.length).toBeGreaterThan(0);
|
|
56
|
+
},
|
|
57
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("Shows filled bars for good password", async () => {
|
|
62
|
+
const { container } = setupTest({ password: "Abc12345" });
|
|
63
|
+
await waitFor(
|
|
64
|
+
() => {
|
|
65
|
+
// All 3 bars should be filled for a good password
|
|
66
|
+
const successBars = container.querySelectorAll(".bg-green-600");
|
|
67
|
+
expect(successBars.length).toBe(3);
|
|
68
|
+
},
|
|
69
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("Exports score prop", () => {
|
|
74
|
+
const { component } = setupTest({ password: "test123" });
|
|
75
|
+
// Score should be accessible
|
|
76
|
+
expect(component).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("Exports strengthText prop", () => {
|
|
80
|
+
const { component } = setupTest({ password: "test123" });
|
|
81
|
+
expect(component).toBeDefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("Exports textColor prop", () => {
|
|
85
|
+
const { component } = setupTest({ password: "test123" });
|
|
86
|
+
expect(component).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("Uses danger color for weak passwords", async () => {
|
|
90
|
+
const { container } = setupTest({ password: "weak" });
|
|
91
|
+
await waitFor(
|
|
92
|
+
() => {
|
|
93
|
+
const dangerBars = container.querySelectorAll(".bg-red-600");
|
|
94
|
+
expect(dangerBars.length).toBeGreaterThan(0);
|
|
95
|
+
},
|
|
96
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("Uses success color for good/strong passwords", async () => {
|
|
101
|
+
const { container } = setupTest({ password: "StrongP@ss123" });
|
|
102
|
+
await waitFor(
|
|
103
|
+
() => {
|
|
104
|
+
const successBars = container.querySelectorAll(".bg-green-600");
|
|
105
|
+
expect(successBars.length).toBe(3);
|
|
106
|
+
},
|
|
107
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("Has transition animation classes on bars", async () => {
|
|
112
|
+
const { container } = setupTest({ password: "test" });
|
|
113
|
+
await waitFor(
|
|
114
|
+
() => {
|
|
115
|
+
const bars = container.querySelectorAll(".h-1.flex-1.rounded-full");
|
|
116
|
+
expect(bars.length).toBe(3);
|
|
117
|
+
bars.forEach((bar) => {
|
|
118
|
+
expect(bar).toHaveClass("transition-colors");
|
|
119
|
+
expect(bar).toHaveClass("duration-300");
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("Very long password shows strong indicator", async () => {
|
|
127
|
+
const { container } = setupTest({ password: "1234567890123" }); // 13 chars
|
|
128
|
+
await waitFor(
|
|
129
|
+
() => {
|
|
130
|
+
const successBars = container.querySelectorAll(".bg-green-600");
|
|
131
|
+
expect(successBars.length).toBe(3);
|
|
132
|
+
},
|
|
133
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("Password Strength Scoring", () => {
|
|
139
|
+
test("Score 2+ (Good) shows 3 success bars - 8+ chars with 2 diversity", async () => {
|
|
140
|
+
const { container } = setupTest({ password: "Abcdefgh" }); // Upper + lower
|
|
141
|
+
await waitFor(
|
|
142
|
+
() => {
|
|
143
|
+
const successBars = container.querySelectorAll(".bg-green-600");
|
|
144
|
+
expect(successBars.length).toBe(3);
|
|
145
|
+
},
|
|
146
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("Score 3 (Strong) - 10+ chars, multiple diversity", async () => {
|
|
151
|
+
const { container } = setupTest({ password: "Abcdefgh1!" }); // Upper, lower, number, symbol
|
|
152
|
+
await waitFor(
|
|
153
|
+
() => {
|
|
154
|
+
const successBars = container.querySelectorAll(".bg-green-600");
|
|
155
|
+
expect(successBars.length).toBe(3);
|
|
156
|
+
},
|
|
157
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("Weak password shows danger color bars", async () => {
|
|
162
|
+
const { container } = setupTest({ password: "abcdef" });
|
|
163
|
+
await waitFor(
|
|
164
|
+
() => {
|
|
165
|
+
const coloredBars = container.querySelectorAll(
|
|
166
|
+
".bg-red-600, .bg-green-600"
|
|
167
|
+
);
|
|
168
|
+
expect(coloredBars.length).toBeGreaterThan(0);
|
|
169
|
+
},
|
|
170
|
+
{ timeout: DEBOUNCE_WAIT }
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
textColor = $bindable(""),
|
|
20
20
|
children,
|
|
21
21
|
}: Props = $props();
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
let debouncedPassword = $state("");
|
|
24
24
|
let timer = $state<ReturnType<typeof setTimeout> | undefined>();
|
|
25
25
|
|
|
@@ -66,43 +66,43 @@
|
|
|
66
66
|
let strength = $derived(debouncedPassword
|
|
67
67
|
? passwordStrength(debouncedPassword, customOptions as any)
|
|
68
68
|
: null);
|
|
69
|
-
|
|
70
|
-
// Compute score based on password length and strength
|
|
71
|
-
$effect(() => {
|
|
72
|
-
score = debouncedPassword.length > 12 ? 3 : (strength?.id ?? -1);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Map score to display text and color
|
|
76
|
-
$effect(() => {
|
|
77
|
-
strengthText =
|
|
78
|
-
score === 0
|
|
79
|
-
? "Too weak"
|
|
80
|
-
: score === 1
|
|
81
|
-
? "Weak"
|
|
82
|
-
: score === 2
|
|
83
|
-
? "Good"
|
|
84
|
-
: score === 3
|
|
85
|
-
? "Strong"
|
|
86
|
-
: "";
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
let strengthColor = $derived(score <= 1 ? "bg-red-600" : "bg-green-600");
|
|
90
|
-
|
|
91
|
-
$effect(() => {
|
|
92
|
-
textColor = score <= 1 ? "text-red-600" : "text-green-600";
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// Calculate how many bars to fill (1-3)
|
|
96
|
-
let filledBars = $derived(score === 0 ? 1 : score === 1 ? 2 : score >= 2 ? 3 : 0);
|
|
97
|
-
</script>
|
|
98
|
-
|
|
99
|
-
{#if debouncedPassword.length > 0}
|
|
100
|
-
<div
|
|
101
|
-
transition:slide={{ duration: 600, easing: cubicOut }}
|
|
102
|
-
class="pt-2 space-y-2"
|
|
103
|
-
>
|
|
104
|
-
<!-- 3 segment bars -->
|
|
105
|
-
<div class="flex gap-1.5">
|
|
69
|
+
|
|
70
|
+
// Compute score based on password length and strength
|
|
71
|
+
$effect(() => {
|
|
72
|
+
score = debouncedPassword.length > 12 ? 3 : (strength?.id ?? -1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Map score to display text and color
|
|
76
|
+
$effect(() => {
|
|
77
|
+
strengthText =
|
|
78
|
+
score === 0
|
|
79
|
+
? "Too weak"
|
|
80
|
+
: score === 1
|
|
81
|
+
? "Weak"
|
|
82
|
+
: score === 2
|
|
83
|
+
? "Good"
|
|
84
|
+
: score === 3
|
|
85
|
+
? "Strong"
|
|
86
|
+
: "";
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
let strengthColor = $derived(score <= 1 ? "bg-red-600" : "bg-green-600");
|
|
90
|
+
|
|
91
|
+
$effect(() => {
|
|
92
|
+
textColor = score <= 1 ? "text-red-600" : "text-green-600";
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Calculate how many bars to fill (1-3)
|
|
96
|
+
let filledBars = $derived(score === 0 ? 1 : score === 1 ? 2 : score >= 2 ? 3 : 0);
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
{#if debouncedPassword.length > 0}
|
|
100
|
+
<div
|
|
101
|
+
transition:slide={{ duration: 600, easing: cubicOut }}
|
|
102
|
+
class="pt-2 space-y-2"
|
|
103
|
+
>
|
|
104
|
+
<!-- 3 segment bars -->
|
|
105
|
+
<div class="flex gap-1.5">
|
|
106
106
|
{#each [0, 1, 2] as barIndex}
|
|
107
107
|
<div
|
|
108
108
|
class="h-1 flex-1 rounded-full transition-colors duration-300 {barIndex <
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
: 'bg-gray-200 dark:bg-gray-700'}"
|
|
112
112
|
></div>
|
|
113
113
|
{/each}
|
|
114
|
-
</div>
|
|
115
|
-
{@render children?.()}
|
|
116
|
-
</div>
|
|
117
|
-
{/if}
|
|
114
|
+
</div>
|
|
115
|
+
{@render children?.()}
|
|
116
|
+
</div>
|
|
117
|
+
{/if}
|