@getmicdrop/svelte-components 5.5.4 → 5.6.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.spec.d.ts +2 -0
- package/dist/calendar/AboutShow/AboutShow.spec.d.ts.map +1 -0
- package/dist/calendar/AboutShow/AboutShow.spec.js +791 -0
- package/dist/calendar/AboutShow/AboutShow.svelte +172 -172
- package/dist/calendar/Calendar/MiniMonthCalendar.spec.d.ts +2 -0
- package/dist/calendar/Calendar/MiniMonthCalendar.spec.d.ts.map +1 -0
- package/dist/calendar/Calendar/MiniMonthCalendar.spec.js +1191 -0
- package/dist/calendar/Calendar/MiniMonthCalendar.svelte +782 -782
- package/dist/calendar/FAQs/FAQs.spec.d.ts +2 -0
- package/dist/calendar/FAQs/FAQs.spec.d.ts.map +1 -0
- package/dist/calendar/FAQs/FAQs.spec.js +238 -0
- package/dist/calendar/FAQs/FAQs.svelte +75 -75
- package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.d.ts +2 -0
- package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.d.ts.map +1 -0
- package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.js +420 -0
- package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte +126 -126
- package/dist/calendar/OrderSummary/OrderSummary.spec.d.ts +2 -0
- package/dist/calendar/OrderSummary/OrderSummary.spec.d.ts.map +1 -0
- package/dist/calendar/OrderSummary/OrderSummary.spec.js +808 -0
- package/dist/calendar/OrderSummary/OrderSummary.svelte +367 -367
- package/dist/calendar/PublicCard/PublicCard.spec.d.ts +2 -0
- package/dist/calendar/PublicCard/PublicCard.spec.d.ts.map +1 -0
- package/dist/calendar/PublicCard/PublicCard.spec.js +301 -0
- package/dist/calendar/PublicCard/PublicCard.svelte +134 -134
- package/dist/calendar/ShowCard/ShowCard.spec.d.ts +2 -0
- package/dist/calendar/ShowCard/ShowCard.spec.d.ts.map +1 -0
- package/dist/calendar/ShowCard/ShowCard.spec.js +714 -0
- package/dist/calendar/ShowCard/ShowCard.svelte +157 -157
- package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.d.ts +2 -0
- package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.d.ts.map +1 -0
- package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.js +241 -0
- package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte +61 -61
- package/dist/components/Layout/AppShell.svelte +104 -0
- package/dist/components/Layout/AppShell.svelte.d.ts +26 -0
- package/dist/components/Layout/AppShell.svelte.d.ts.map +1 -0
- package/dist/components/Layout/ContentSection.svelte +80 -0
- package/dist/components/Layout/ContentSection.svelte.d.ts +23 -0
- package/dist/components/Layout/ContentSection.svelte.d.ts.map +1 -0
- package/dist/components/Layout/Grid.svelte +4 -4
- package/dist/components/Layout/Heading.svelte +81 -0
- package/dist/components/Layout/Heading.svelte.d.ts +24 -0
- package/dist/components/Layout/Heading.svelte.d.ts.map +1 -0
- package/dist/components/Layout/PageContainer.svelte +69 -0
- package/dist/components/Layout/PageContainer.svelte.d.ts +23 -0
- package/dist/components/Layout/PageContainer.svelte.d.ts.map +1 -0
- package/dist/components/Layout/Responsive.svelte +75 -0
- package/dist/components/Layout/Responsive.svelte.d.ts +19 -0
- package/dist/components/Layout/Responsive.svelte.d.ts.map +1 -0
- package/dist/components/Layout/Section.spec.d.ts +2 -0
- package/dist/components/Layout/Section.spec.d.ts.map +1 -0
- package/dist/components/Layout/Section.spec.js +149 -0
- package/dist/components/Layout/Section.svelte +80 -80
- package/dist/components/Layout/ShowOnDesktop.svelte +37 -0
- package/dist/components/Layout/ShowOnDesktop.svelte.d.ts +16 -0
- package/dist/components/Layout/ShowOnDesktop.svelte.d.ts.map +1 -0
- package/dist/components/Layout/ShowOnMobile.svelte +37 -0
- package/dist/components/Layout/ShowOnMobile.svelte.d.ts +16 -0
- package/dist/components/Layout/ShowOnMobile.svelte.d.ts.map +1 -0
- package/dist/components/Layout/Sidebar.spec.d.ts +2 -0
- package/dist/components/Layout/Sidebar.spec.d.ts.map +1 -0
- package/dist/components/Layout/Sidebar.spec.js +186 -0
- package/dist/components/Layout/Sidebar.svelte +108 -108
- package/dist/components/Layout/Stack.spec.js +2 -2
- package/dist/components/Layout/Stack.svelte +6 -6
- package/dist/components/Layout/Text.svelte +87 -0
- package/dist/components/Layout/Text.svelte.d.ts +28 -0
- package/dist/components/Layout/Text.svelte.d.ts.map +1 -0
- package/dist/components/Layout/TwoColumn.svelte +108 -0
- package/dist/components/Layout/TwoColumn.svelte.d.ts +28 -0
- package/dist/components/Layout/TwoColumn.svelte.d.ts.map +1 -0
- package/dist/components/Layout/__tests__/Heading.test.d.ts +2 -0
- package/dist/components/Layout/__tests__/Heading.test.d.ts.map +1 -0
- package/dist/components/Layout/__tests__/Heading.test.js +123 -0
- package/dist/components/Layout/__tests__/ShowOnDesktop.test.d.ts +2 -0
- package/dist/components/Layout/__tests__/ShowOnDesktop.test.d.ts.map +1 -0
- package/dist/components/Layout/__tests__/ShowOnDesktop.test.js +84 -0
- package/dist/components/Layout/__tests__/ShowOnMobile.test.d.ts +2 -0
- package/dist/components/Layout/__tests__/ShowOnMobile.test.d.ts.map +1 -0
- package/dist/components/Layout/__tests__/ShowOnMobile.test.js +80 -0
- package/dist/components/Layout/__tests__/Text.test.d.ts +2 -0
- package/dist/components/Layout/__tests__/Text.test.d.ts.map +1 -0
- package/dist/components/Layout/__tests__/Text.test.js +146 -0
- package/dist/components/Layout/__tests__/TwoColumn.test.d.ts +2 -0
- package/dist/components/Layout/__tests__/TwoColumn.test.d.ts.map +1 -0
- package/dist/components/Layout/__tests__/TwoColumn.test.js +129 -0
- package/dist/constants/formOptions.spec.js +9 -5
- package/dist/constants/validation.js +91 -91
- package/dist/constants/validation.spec.js +64 -64
- package/dist/datetime/__tests__/timezone.test.js +123 -1
- package/dist/forms/createFieldTracker.spec.d.ts +2 -0
- package/dist/forms/createFieldTracker.spec.d.ts.map +1 -0
- package/dist/forms/createFieldTracker.spec.js +343 -0
- package/dist/forms/createFormStore.spec.d.ts +2 -0
- package/dist/forms/createFormStore.spec.d.ts.map +1 -0
- package/dist/forms/createFormStore.spec.js +689 -0
- package/dist/index.d.ts +4 -112
- package/dist/index.js +40 -226
- package/dist/patterns/data/DataGrid.spec.d.ts +2 -0
- package/dist/patterns/data/DataGrid.spec.d.ts.map +1 -0
- package/dist/patterns/data/DataGrid.spec.js +159 -0
- package/dist/patterns/data/DataGrid.svelte +45 -45
- package/dist/patterns/data/DataList.spec.d.ts +2 -0
- package/dist/patterns/data/DataList.spec.d.ts.map +1 -0
- package/dist/patterns/data/DataList.spec.js +158 -0
- package/dist/patterns/data/DataList.svelte +24 -24
- package/dist/patterns/data/DataTable.spec.d.ts +2 -0
- package/dist/patterns/data/DataTable.spec.d.ts.map +1 -0
- package/dist/patterns/data/DataTable.spec.js +196 -0
- package/dist/patterns/data/DataTable.svelte +36 -36
- package/dist/patterns/forms/FormActions.spec.js +95 -88
- package/dist/patterns/forms/FormActions.stories.svelte +97 -97
- package/dist/patterns/forms/FormActions.svelte +46 -46
- package/dist/patterns/forms/FormGrid.spec.d.ts +2 -0
- package/dist/patterns/forms/FormGrid.spec.d.ts.map +1 -0
- package/dist/patterns/forms/FormGrid.spec.js +125 -0
- package/dist/patterns/forms/FormGrid.svelte +33 -33
- package/dist/patterns/forms/FormSection.spec.d.ts +2 -0
- package/dist/patterns/forms/FormSection.spec.d.ts.map +1 -0
- package/dist/patterns/forms/FormSection.spec.js +153 -0
- package/dist/patterns/forms/FormSection.svelte +32 -32
- package/dist/patterns/forms/FormValidationSummary.stories.svelte +83 -83
- package/dist/patterns/forms/FormValidationSummary.svelte +74 -74
- package/dist/patterns/layout/Sidebar.spec.d.ts +2 -0
- package/dist/patterns/layout/Sidebar.spec.d.ts.map +1 -0
- package/dist/patterns/layout/Sidebar.spec.js +159 -0
- package/dist/patterns/layout/Sidebar.svelte +39 -39
- package/dist/patterns/layout/index.d.ts +9 -0
- package/dist/patterns/layout/index.js +22 -0
- package/dist/patterns/navigation/BottomNav.stories.svelte +117 -117
- package/dist/patterns/navigation/BottomNav.svelte +64 -64
- package/dist/patterns/navigation/Header.spec.js +33 -24
- package/dist/patterns/navigation/Header.stories.svelte +77 -77
- package/dist/patterns/navigation/Header.svelte +193 -193
- package/dist/patterns/page/PageHeader.spec.d.ts +2 -0
- package/dist/patterns/page/PageHeader.spec.d.ts.map +1 -0
- package/dist/patterns/page/PageHeader.spec.js +167 -0
- package/dist/patterns/page/PageHeader.svelte +18 -18
- package/dist/patterns/page/PageLayout.spec.d.ts +2 -0
- package/dist/patterns/page/PageLayout.spec.d.ts.map +1 -0
- package/dist/patterns/page/PageLayout.spec.js +145 -0
- package/dist/patterns/page/PageLayout.svelte +40 -40
- package/dist/patterns/page/PageLoader.spec.js +57 -54
- package/dist/patterns/page/PageLoader.stories.svelte +137 -137
- package/dist/patterns/page/PageLoader.svelte +24 -24
- package/dist/patterns/page/SectionHeader.spec.d.ts +2 -0
- package/dist/patterns/page/SectionHeader.spec.d.ts.map +1 -0
- package/dist/patterns/page/SectionHeader.spec.js +197 -0
- package/dist/patterns/page/SectionHeader.svelte +29 -29
- package/dist/presets/badges.js +112 -112
- package/dist/presets/badges.spec.d.ts +2 -0
- package/dist/presets/badges.spec.d.ts.map +1 -0
- package/dist/presets/badges.spec.js +172 -0
- package/dist/presets/buttons.js +76 -76
- package/dist/presets/buttons.spec.d.ts +2 -0
- package/dist/presets/buttons.spec.d.ts.map +1 -0
- package/dist/presets/buttons.spec.js +135 -0
- package/dist/presets/index.js +9 -9
- package/dist/primitives/Accordion/Accordion.spec.d.ts +2 -0
- package/dist/primitives/Accordion/Accordion.spec.d.ts.map +1 -0
- package/dist/primitives/Accordion/Accordion.spec.js +83 -0
- package/dist/primitives/Accordion/Accordion.stories.svelte +75 -75
- package/dist/primitives/Accordion/Accordion.svelte +42 -42
- package/dist/primitives/Accordion/AccordionItem.spec.d.ts +2 -0
- package/dist/primitives/Accordion/AccordionItem.spec.d.ts.map +1 -0
- package/dist/primitives/Accordion/AccordionItem.spec.js +661 -0
- package/dist/primitives/Accordion/AccordionItem.svelte +95 -95
- package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte +107 -0
- package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte.d.ts +35 -0
- package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte.d.ts.map +1 -0
- package/dist/primitives/Alert/Alert.spec.js +173 -170
- package/dist/primitives/Alert/Alert.stories.svelte +88 -88
- package/dist/primitives/Alert/Alert.svelte +27 -27
- package/dist/primitives/Avatar/Avatar.spec.d.ts +2 -0
- package/dist/primitives/Avatar/Avatar.spec.d.ts.map +1 -0
- package/dist/primitives/Avatar/Avatar.spec.js +211 -0
- package/dist/primitives/Avatar/Avatar.stories.svelte +94 -94
- package/dist/primitives/Avatar/Avatar.svelte +66 -66
- package/dist/primitives/Badges/Badge.spec.js +144 -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 +136 -127
- package/dist/primitives/BottomSheet/BottomSheet.stories.svelte +83 -83
- package/dist/primitives/BottomSheet/BottomSheet.svelte +100 -100
- package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte +13 -0
- package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts +7 -0
- package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts.map +1 -0
- package/dist/primitives/Breadcrumb/Breadcrumb.spec.js +122 -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 +223 -211
- package/dist/primitives/Button/Button.stories.svelte +76 -76
- package/dist/primitives/Button/Button.svelte +270 -270
- package/dist/primitives/Button/ButtonSaveDemo.spec.js +146 -48
- package/dist/primitives/Button/ButtonSaveDemo.svelte +25 -25
- package/dist/primitives/Button/ButtonVariantShowcase.spec.d.ts +2 -0
- package/dist/primitives/Button/ButtonVariantShowcase.spec.d.ts.map +1 -0
- package/dist/primitives/Button/ButtonVariantShowcase.spec.js +202 -0
- 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.spec.d.ts +2 -0
- package/dist/primitives/Checkbox/Checkbox.spec.d.ts.map +1 -0
- package/dist/primitives/Checkbox/Checkbox.spec.js +252 -0
- package/dist/primitives/Checkbox/Checkbox.stories.svelte +84 -84
- package/dist/primitives/Checkbox/Checkbox.svelte +88 -88
- package/dist/primitives/DarkModeToggle.spec.js +390 -357
- package/dist/primitives/DarkModeToggle.stories.svelte +57 -57
- package/dist/primitives/DarkModeToggle.svelte +136 -136
- package/dist/primitives/Drawer/Drawer.spec.d.ts +2 -0
- package/dist/primitives/Drawer/Drawer.spec.d.ts.map +1 -0
- package/dist/primitives/Drawer/Drawer.spec.js +212 -0
- package/dist/primitives/Drawer/Drawer.stories.svelte +80 -80
- package/dist/primitives/Drawer/Drawer.svelte +120 -120
- package/dist/primitives/Dropdown/Dropdown.spec.d.ts +2 -0
- package/dist/primitives/Dropdown/Dropdown.spec.d.ts.map +1 -0
- package/dist/primitives/Dropdown/Dropdown.spec.js +366 -0
- package/dist/primitives/Dropdown/Dropdown.stories.svelte +137 -137
- package/dist/primitives/Dropdown/Dropdown.svelte +14 -14
- package/dist/primitives/Dropdown/DropdownItem.spec.d.ts +2 -0
- package/dist/primitives/Dropdown/DropdownItem.spec.d.ts.map +1 -0
- package/dist/primitives/Dropdown/DropdownItem.spec.js +182 -0
- 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/Icons/iconTestUtils.spec.d.ts +2 -0
- package/dist/primitives/Icons/iconTestUtils.spec.d.ts.map +1 -0
- package/dist/primitives/Icons/iconTestUtils.spec.js +235 -0
- package/dist/primitives/Input/Input.spec.js +573 -573
- package/dist/primitives/Input/Input.stories.svelte +139 -139
- package/dist/primitives/Input/Input.svelte +418 -431
- package/dist/primitives/Input/Input.svelte.d.ts.map +1 -1
- package/dist/primitives/Input/Select.spec.js +212 -218
- package/dist/primitives/Input/Select.stories.svelte +112 -112
- package/dist/primitives/Input/Select.svelte +128 -128
- package/dist/primitives/Input/Textarea.spec.d.ts +2 -0
- package/dist/primitives/Input/Textarea.spec.d.ts.map +1 -0
- package/dist/primitives/Input/Textarea.spec.js +255 -0
- package/dist/primitives/Input/Textarea.stories.svelte +137 -137
- package/dist/primitives/Input/Textarea.svelte +35 -35
- package/dist/primitives/Label/Label.spec.d.ts +2 -0
- package/dist/primitives/Label/Label.spec.d.ts.map +1 -0
- package/dist/primitives/Label/Label.spec.js +157 -0
- package/dist/primitives/Label/Label.svelte +37 -37
- package/dist/primitives/Modal/Modal.spec.js +99 -95
- package/dist/primitives/Modal/Modal.stories.svelte +86 -86
- package/dist/primitives/Modal/Modal.svelte +158 -158
- package/dist/primitives/Modal/ModalTestWrapper.svelte +65 -0
- package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts +23 -0
- package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts.map +1 -0
- package/dist/primitives/NumberInput/NumberInput.spec.d.ts +2 -0
- package/dist/primitives/NumberInput/NumberInput.spec.d.ts.map +1 -0
- package/dist/primitives/NumberInput/NumberInput.spec.js +235 -0
- package/dist/primitives/NumberInput/NumberInput.svelte +106 -106
- package/dist/primitives/Pagination/Pagination.spec.d.ts +2 -0
- package/dist/primitives/Pagination/Pagination.spec.d.ts.map +1 -0
- package/dist/primitives/Pagination/Pagination.spec.js +266 -0
- package/dist/primitives/Pagination/Pagination.stories.svelte +76 -76
- package/dist/primitives/Pagination/Pagination.svelte +261 -261
- package/dist/primitives/Radio/Radio.spec.d.ts +2 -0
- package/dist/primitives/Radio/Radio.spec.d.ts.map +1 -0
- package/dist/primitives/Radio/Radio.spec.js +206 -0
- package/dist/primitives/Radio/Radio.stories.svelte +80 -80
- package/dist/primitives/Radio/Radio.svelte +67 -67
- package/dist/primitives/Skeleton/CardPlaceholder.spec.d.ts +2 -0
- package/dist/primitives/Skeleton/CardPlaceholder.spec.d.ts.map +1 -0
- package/dist/primitives/Skeleton/CardPlaceholder.spec.js +156 -0
- package/dist/primitives/Skeleton/CardPlaceholder.svelte +87 -87
- package/dist/primitives/Skeleton/ImagePlaceholder.spec.d.ts +2 -0
- package/dist/primitives/Skeleton/ImagePlaceholder.spec.d.ts.map +1 -0
- package/dist/primitives/Skeleton/ImagePlaceholder.spec.js +120 -0
- package/dist/primitives/Skeleton/ImagePlaceholder.svelte +59 -59
- package/dist/primitives/Skeleton/ListPlaceholder.spec.d.ts +2 -0
- package/dist/primitives/Skeleton/ListPlaceholder.spec.d.ts.map +1 -0
- package/dist/primitives/Skeleton/ListPlaceholder.spec.js +220 -0
- package/dist/primitives/Skeleton/ListPlaceholder.svelte +76 -76
- package/dist/primitives/Skeleton/Skeleton.spec.d.ts +2 -0
- package/dist/primitives/Skeleton/Skeleton.spec.d.ts.map +1 -0
- package/dist/primitives/Skeleton/Skeleton.spec.js +173 -0
- package/dist/primitives/Skeleton/Skeleton.stories.svelte +151 -151
- package/dist/primitives/Skeleton/Skeleton.svelte +26 -26
- package/dist/primitives/Spinner/Spinner.spec.js +71 -75
- package/dist/primitives/Spinner/Spinner.stories.svelte +29 -29
- package/dist/primitives/Spinner/Spinner.svelte +20 -20
- package/dist/primitives/Tabs/TabItem.spec.d.ts +2 -0
- package/dist/primitives/Tabs/TabItem.spec.d.ts.map +1 -0
- package/dist/primitives/Tabs/TabItem.spec.js +130 -0
- package/dist/primitives/Tabs/TabItem.svelte +49 -49
- package/dist/primitives/Tabs/Tabs.spec.d.ts +2 -0
- package/dist/primitives/Tabs/Tabs.spec.d.ts.map +1 -0
- package/dist/primitives/Tabs/Tabs.spec.js +295 -0
- package/dist/primitives/Tabs/Tabs.stories.svelte +112 -112
- package/dist/primitives/Tabs/Tabs.svelte +123 -123
- package/dist/primitives/Tabs/TabsWithItems.test.svelte +18 -0
- package/dist/primitives/Tabs/TabsWithItems.test.svelte.d.ts +16 -0
- package/dist/primitives/Tabs/TabsWithItems.test.svelte.d.ts.map +1 -0
- package/dist/primitives/Toggle.spec.js +143 -127
- package/dist/primitives/Toggle.stories.svelte +92 -92
- package/dist/primitives/Toggle.svelte +71 -71
- package/dist/primitives/Typography/Typography.spec.d.ts +2 -0
- package/dist/primitives/Typography/Typography.spec.d.ts.map +1 -0
- package/dist/primitives/Typography/Typography.spec.js +183 -0
- 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/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +84 -81
- package/dist/recipes/CropImage/CropImage.spec.js +208 -216
- package/dist/recipes/CropImage/CropImage.stories.svelte +104 -104
- package/dist/recipes/CropImage/CropImage.svelte +238 -238
- package/dist/recipes/ImageUploader/ImageUploader.spec.d.ts +2 -0
- package/dist/recipes/ImageUploader/ImageUploader.spec.d.ts.map +1 -0
- package/dist/recipes/ImageUploader/ImageUploader.spec.js +1351 -0
- package/dist/recipes/ImageUploader/ImageUploader.stories.svelte +125 -125
- package/dist/recipes/ImageUploader/ImageUploader.svelte +804 -804
- package/dist/recipes/SuperLogin/SuperLogin.spec.d.ts +2 -0
- package/dist/recipes/SuperLogin/SuperLogin.spec.d.ts.map +1 -0
- package/dist/recipes/SuperLogin/SuperLogin.spec.js +1436 -0
- package/dist/recipes/Toaster/Toaster.stories.svelte +62 -62
- package/dist/recipes/feedback/EmptyState/EmptyState.spec.d.ts +2 -0
- package/dist/recipes/feedback/EmptyState/EmptyState.spec.d.ts.map +1 -0
- package/dist/recipes/feedback/EmptyState/EmptyState.spec.js +202 -0
- 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 +133 -129
- package/dist/recipes/feedback/StatusIndicator/StatusIndicator.svelte +157 -157
- package/dist/recipes/fields/CheckboxField.spec.d.ts +2 -0
- package/dist/recipes/fields/CheckboxField.spec.d.ts.map +1 -0
- package/dist/recipes/fields/CheckboxField.spec.js +135 -0
- package/dist/recipes/fields/CheckboxField.svelte +85 -85
- package/dist/recipes/fields/FormField.spec.d.ts +2 -0
- package/dist/recipes/fields/FormField.spec.d.ts.map +1 -0
- package/dist/recipes/fields/FormField.spec.js +159 -0
- package/dist/recipes/fields/FormField.svelte +58 -58
- package/dist/recipes/fields/RadioGroup.spec.d.ts +2 -0
- package/dist/recipes/fields/RadioGroup.spec.d.ts.map +1 -0
- package/dist/recipes/fields/RadioGroup.spec.js +199 -0
- package/dist/recipes/fields/RadioGroup.svelte +95 -95
- package/dist/recipes/fields/SelectField.spec.d.ts +2 -0
- package/dist/recipes/fields/SelectField.spec.d.ts.map +1 -0
- package/dist/recipes/fields/SelectField.spec.js +188 -0
- package/dist/recipes/fields/SelectField.svelte +80 -80
- package/dist/recipes/fields/TextareaField.spec.d.ts +2 -0
- package/dist/recipes/fields/TextareaField.spec.d.ts.map +1 -0
- package/dist/recipes/fields/TextareaField.spec.js +205 -0
- package/dist/recipes/fields/TextareaField.svelte +97 -97
- package/dist/recipes/fields/ToggleField.spec.d.ts +2 -0
- package/dist/recipes/fields/ToggleField.spec.d.ts.map +1 -0
- package/dist/recipes/fields/ToggleField.spec.js +153 -0
- package/dist/recipes/fields/ToggleField.svelte +60 -60
- package/dist/recipes/fields/index.js +7 -7
- package/dist/recipes/inputs/MultiSelect.spec.js +258 -257
- package/dist/recipes/inputs/MultiSelect.stories.svelte +133 -133
- package/dist/recipes/inputs/MultiSelect.svelte +256 -249
- package/dist/recipes/inputs/MultiSelect.svelte.d.ts +2 -0
- package/dist/recipes/inputs/MultiSelect.svelte.d.ts.map +1 -1
- package/dist/recipes/inputs/OTPInput.spec.js +251 -238
- package/dist/recipes/inputs/OTPInput.stories.svelte +162 -162
- package/dist/recipes/inputs/OTPInput.svelte +29 -29
- package/dist/recipes/inputs/PasswordInput.spec.d.ts +2 -0
- package/dist/recipes/inputs/PasswordInput.spec.d.ts.map +1 -0
- package/dist/recipes/inputs/PasswordInput.spec.js +410 -0
- package/dist/recipes/inputs/PasswordInput.svelte +22 -22
- package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.spec.js +253 -173
- package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.svelte +117 -117
- package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte +71 -0
- package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte.d.ts +9 -0
- package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte.d.ts.map +1 -0
- package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.spec.js +1246 -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.spec.d.ts +2 -0
- package/dist/recipes/inputs/Search.spec.d.ts.map +1 -0
- package/dist/recipes/inputs/Search.spec.js +177 -0
- package/dist/recipes/inputs/Search.svelte +37 -37
- package/dist/recipes/inputs/SelectDropdown.spec.d.ts +2 -0
- package/dist/recipes/inputs/SelectDropdown.spec.d.ts.map +1 -0
- package/dist/recipes/inputs/SelectDropdown.spec.js +512 -0
- package/dist/recipes/inputs/SelectDropdown.svelte +57 -57
- package/dist/recipes/modals/AlertModal.spec.d.ts +2 -0
- package/dist/recipes/modals/AlertModal.spec.d.ts.map +1 -0
- package/dist/recipes/modals/AlertModal.spec.js +432 -0
- package/dist/recipes/modals/AlertModal.svelte +130 -130
- package/dist/recipes/modals/ConfirmationModal.spec.js +206 -191
- package/dist/recipes/modals/ConfirmationModal.stories.svelte +119 -119
- package/dist/recipes/modals/ConfirmationModal.svelte +152 -152
- package/dist/recipes/modals/InputModal.spec.d.ts +2 -0
- package/dist/recipes/modals/InputModal.spec.d.ts.map +1 -0
- package/dist/recipes/modals/InputModal.spec.js +872 -0
- 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.spec.d.ts +2 -0
- package/dist/recipes/modals/ModalTestWrapper.spec.d.ts.map +1 -0
- package/dist/recipes/modals/ModalTestWrapper.spec.js +502 -0
- package/dist/recipes/modals/ModalTestWrapper.svelte +65 -65
- package/dist/recipes/modals/StatusModal.spec.d.ts +2 -0
- package/dist/recipes/modals/StatusModal.spec.d.ts.map +1 -0
- package/dist/recipes/modals/StatusModal.spec.js +599 -0
- 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 +345 -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/ButtonAuditDashboard.spec.d.ts +2 -0
- package/dist/stories/ButtonAuditDashboard.spec.d.ts.map +1 -0
- package/dist/stories/ButtonAuditDashboard.spec.js +913 -0
- package/dist/stories/ButtonAuditReview.spec.d.ts +2 -0
- package/dist/stories/ButtonAuditReview.spec.d.ts.map +1 -0
- package/dist/stories/ButtonAuditReview.spec.js +422 -0
- package/dist/stories/ButtonAuditReview.stories.svelte +14 -14
- package/dist/stories/ButtonAuditReview.svelte +427 -427
- package/dist/stories/ButtonGridView.spec.d.ts +2 -0
- package/dist/stories/ButtonGridView.spec.d.ts.map +1 -0
- package/dist/stories/ButtonGridView.spec.js +667 -0
- package/dist/stories/ButtonShowcase.spec.d.ts +2 -0
- package/dist/stories/ButtonShowcase.spec.d.ts.map +1 -0
- package/dist/stories/ButtonShowcase.spec.js +499 -0
- package/dist/stories/PatternsGallery.spec.d.ts +2 -0
- package/dist/stories/PatternsGallery.spec.d.ts.map +1 -0
- package/dist/stories/PatternsGallery.spec.js +514 -0
- package/dist/stories/PatternsGallery.stories.svelte +19 -19
- package/dist/stories/PatternsGallery.svelte +206 -206
- package/dist/stories/PrimitivesGallery.spec.d.ts +2 -0
- package/dist/stories/PrimitivesGallery.spec.d.ts.map +1 -0
- package/dist/stories/PrimitivesGallery.spec.js +813 -0
- package/dist/stories/PrimitivesGallery.stories.svelte +19 -19
- package/dist/stories/PrimitivesGallery.svelte +725 -725
- package/dist/stories/RecipesGallery.spec.d.ts +2 -0
- package/dist/stories/RecipesGallery.spec.d.ts.map +1 -0
- package/dist/stories/RecipesGallery.spec.js +299 -0
- 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/stripe/useStripeTheme.spec.d.ts +2 -0
- package/dist/stripe/useStripeTheme.spec.d.ts.map +1 -0
- package/dist/stripe/useStripeTheme.spec.js +793 -0
- package/dist/tailwind/preset.cjs +82 -82
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +405 -404
- package/dist/telemetry.spec.js +1144 -661
- package/dist/tokens/__tests__/colors.test.d.ts +2 -0
- package/dist/tokens/__tests__/colors.test.d.ts.map +1 -0
- package/dist/tokens/__tests__/colors.test.js +152 -0
- package/dist/tokens/__tests__/radius.test.d.ts +2 -0
- package/dist/tokens/__tests__/radius.test.d.ts.map +1 -0
- package/dist/tokens/__tests__/radius.test.js +118 -0
- package/dist/tokens/__tests__/shadows.test.d.ts +2 -0
- package/dist/tokens/__tests__/shadows.test.d.ts.map +1 -0
- package/dist/tokens/__tests__/shadows.test.js +105 -0
- package/dist/tokens/__tests__/spacing.test.js +11 -8
- package/dist/tokens/__tests__/typography-base.test.d.ts +2 -0
- package/dist/tokens/__tests__/typography-base.test.d.ts.map +1 -0
- package/dist/tokens/__tests__/typography-base.test.js +138 -0
- package/dist/tokens/__tests__/typography.test.d.ts +2 -0
- package/dist/tokens/__tests__/typography.test.d.ts.map +1 -0
- package/dist/tokens/__tests__/typography.test.js +156 -0
- package/dist/tokens/__tests__/z-index.test.d.ts +2 -0
- package/dist/tokens/__tests__/z-index.test.d.ts.map +1 -0
- package/dist/tokens/__tests__/z-index.test.js +121 -0
- package/dist/tokens/tokens.css +87 -87
- package/dist/tokens/typography-base.css +163 -0
- package/dist/utils/apiConfig.spec.js +219 -118
- package/dist/utils/formatters.spec.d.ts +2 -0
- package/dist/utils/formatters.spec.d.ts.map +1 -0
- package/dist/utils/formatters.spec.js +82 -0
- package/dist/utils/transitions.js +62 -62
- package/dist/utils/transitions.spec.d.ts +2 -0
- package/dist/utils/transitions.spec.d.ts.map +1 -0
- package/dist/utils/transitions.spec.js +130 -0
- package/dist/utils/utils.js +354 -354
- package/package.json +292 -286
|
@@ -0,0 +1,1436 @@
|
|
|
1
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/svelte";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { expect, describe, test, vi, beforeEach, afterEach } from "vitest";
|
|
4
|
+
import SuperLogin from "./SuperLogin.svelte";
|
|
5
|
+
|
|
6
|
+
// Mock ResizeObserver
|
|
7
|
+
global.ResizeObserver = class ResizeObserver {
|
|
8
|
+
constructor(callback) {
|
|
9
|
+
this.callback = callback;
|
|
10
|
+
}
|
|
11
|
+
observe() {}
|
|
12
|
+
unobserve() {}
|
|
13
|
+
disconnect() {}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Mock localStorage
|
|
17
|
+
const localStorageMock = (() => {
|
|
18
|
+
let store = {};
|
|
19
|
+
return {
|
|
20
|
+
getItem: (key) => store[key] || null,
|
|
21
|
+
setItem: (key, value) => {
|
|
22
|
+
store[key] = value.toString();
|
|
23
|
+
},
|
|
24
|
+
removeItem: (key) => {
|
|
25
|
+
delete store[key];
|
|
26
|
+
},
|
|
27
|
+
clear: () => {
|
|
28
|
+
store = {};
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
})();
|
|
32
|
+
|
|
33
|
+
Object.defineProperty(window, "localStorage", {
|
|
34
|
+
value: localStorageMock,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Mock fetch
|
|
38
|
+
global.fetch = vi.fn();
|
|
39
|
+
|
|
40
|
+
function setupTest(props = {}) {
|
|
41
|
+
const user = userEvent.setup();
|
|
42
|
+
const defaultProps = {
|
|
43
|
+
apiBaseUrl: "",
|
|
44
|
+
logoSrc: "",
|
|
45
|
+
logoAlt: "Logo",
|
|
46
|
+
initialView: "login",
|
|
47
|
+
isFirstTime: false,
|
|
48
|
+
userEmail: "",
|
|
49
|
+
firstName: "",
|
|
50
|
+
portalType: "dashboard",
|
|
51
|
+
defaultRedirectPath: "/",
|
|
52
|
+
setupRedirectPath: "/profile",
|
|
53
|
+
tosUrl: "https://get-micdrop.com/tos",
|
|
54
|
+
onLoginSuccess: vi.fn(),
|
|
55
|
+
onAccountSelect: vi.fn(),
|
|
56
|
+
onNavigate: vi.fn(),
|
|
57
|
+
onExternalNavigate: vi.fn(),
|
|
58
|
+
showDarkModeToggle: true,
|
|
59
|
+
searchParams: "",
|
|
60
|
+
...props,
|
|
61
|
+
};
|
|
62
|
+
const result = render(SuperLogin, { props: defaultProps });
|
|
63
|
+
return {
|
|
64
|
+
user,
|
|
65
|
+
component: result.component,
|
|
66
|
+
container: result.container,
|
|
67
|
+
...defaultProps,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("SuperLogin Component - Basic Rendering", () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
localStorageMock.clear();
|
|
74
|
+
vi.clearAllMocks();
|
|
75
|
+
global.fetch.mockClear();
|
|
76
|
+
// Mock fetch to avoid eligibility check errors
|
|
77
|
+
global.fetch.mockResolvedValue({
|
|
78
|
+
ok: true,
|
|
79
|
+
json: async () => ({ eligible: false }),
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("renders login view by default", () => {
|
|
84
|
+
setupTest();
|
|
85
|
+
expect(screen.getByRole("heading", { name: /log in/i })).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("renders logo when logoSrc is provided", () => {
|
|
89
|
+
setupTest({ logoSrc: "/logo.png", logoAlt: "Test Logo" });
|
|
90
|
+
const logo = screen.getByAltText("Test Logo");
|
|
91
|
+
expect(logo).toBeInTheDocument();
|
|
92
|
+
expect(logo).toHaveAttribute("src", "/logo.png");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("does not render logo when logoSrc is not provided", () => {
|
|
96
|
+
setupTest({ logoSrc: "" });
|
|
97
|
+
expect(screen.queryByRole("img", { name: /logo/i })).not.toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("shows dark mode toggle by default", () => {
|
|
101
|
+
setupTest();
|
|
102
|
+
expect(screen.getByRole("button", { name: /toggle theme/i })).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("hides dark mode toggle when showDarkModeToggle is false", () => {
|
|
106
|
+
setupTest({ showDarkModeToggle: false });
|
|
107
|
+
expect(screen.queryByRole("button", { name: /toggle theme/i })).not.toBeInTheDocument();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("renders first-time user welcome message", () => {
|
|
111
|
+
setupTest({ isFirstTime: true, firstName: "John" });
|
|
112
|
+
expect(screen.getByText(/welcome, john/i)).toBeInTheDocument();
|
|
113
|
+
expect(screen.getByText(/sign in to access your dashboard/i)).toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("renders remembered user welcome back message", async () => {
|
|
117
|
+
localStorageMock.setItem(
|
|
118
|
+
"rememberedUser",
|
|
119
|
+
JSON.stringify({ email: "john@example.com", firstName: "John" })
|
|
120
|
+
);
|
|
121
|
+
setupTest();
|
|
122
|
+
await waitFor(() => {
|
|
123
|
+
expect(screen.getByText(/welcome back, john/i)).toBeInTheDocument();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("SuperLogin Component - Login Form", () => {
|
|
129
|
+
beforeEach(() => {
|
|
130
|
+
localStorageMock.clear();
|
|
131
|
+
vi.clearAllMocks();
|
|
132
|
+
global.fetch.mockClear();
|
|
133
|
+
global.fetch.mockResolvedValue({
|
|
134
|
+
ok: true,
|
|
135
|
+
json: async () => ({ eligible: false }),
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("renders email and password inputs", () => {
|
|
140
|
+
setupTest();
|
|
141
|
+
expect(screen.getByLabelText("Email")).toBeInTheDocument();
|
|
142
|
+
expect(screen.getByLabelText("Password")).toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("renders remember me checkbox", () => {
|
|
146
|
+
setupTest();
|
|
147
|
+
expect(screen.getByRole("checkbox", { name: /remember me on this device/i })).toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("remember me checkbox is checked by default", () => {
|
|
151
|
+
setupTest();
|
|
152
|
+
const checkbox = screen.getByRole("checkbox", { name: /remember me on this device/i });
|
|
153
|
+
expect(checkbox).toBeChecked();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("renders forgot password link", () => {
|
|
157
|
+
setupTest();
|
|
158
|
+
expect(screen.getByRole("button", { name: /forgot your password/i })).toBeInTheDocument();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("renders login button", () => {
|
|
162
|
+
setupTest();
|
|
163
|
+
expect(screen.getByRole("button", { name: /^log in$/i })).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("renders send login link button", () => {
|
|
167
|
+
setupTest();
|
|
168
|
+
expect(screen.getByRole("button", { name: /send me a login link/i })).toBeInTheDocument();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("login button is disabled when form is invalid", () => {
|
|
172
|
+
setupTest();
|
|
173
|
+
const loginButton = screen.getByRole("button", { name: /^log in$/i });
|
|
174
|
+
expect(loginButton).toBeDisabled();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("login button is enabled when email and password are filled", async () => {
|
|
178
|
+
const { user } = setupTest();
|
|
179
|
+
const emailInput = screen.getByLabelText("Email");
|
|
180
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
181
|
+
|
|
182
|
+
await user.type(emailInput, "test@example.com");
|
|
183
|
+
await user.type(passwordInput, "password123");
|
|
184
|
+
|
|
185
|
+
const loginButton = screen.getByRole("button", { name: /^log in$/i });
|
|
186
|
+
expect(loginButton).toBeEnabled();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("pre-fills email when userEmail prop is provided", () => {
|
|
190
|
+
setupTest({ userEmail: "prefilled@example.com" });
|
|
191
|
+
expect(screen.getByText("prefilled@example.com")).toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("hides email input when userEmail is provided", () => {
|
|
195
|
+
setupTest({ userEmail: "prefilled@example.com" });
|
|
196
|
+
expect(screen.queryByLabelText(/email/i)).not.toBeInTheDocument();
|
|
197
|
+
expect(screen.getByText("prefilled@example.com")).toBeInTheDocument();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("shows email input when 'Not me?' is clicked", async () => {
|
|
201
|
+
localStorageMock.setItem(
|
|
202
|
+
"rememberedUser",
|
|
203
|
+
JSON.stringify({ email: "john@example.com", firstName: "John" })
|
|
204
|
+
);
|
|
205
|
+
const { user } = setupTest();
|
|
206
|
+
|
|
207
|
+
await waitFor(() => {
|
|
208
|
+
expect(screen.getByText(/welcome back, john/i)).toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const notMeButton = screen.getByRole("button", { name: /not john/i });
|
|
212
|
+
await user.click(notMeButton);
|
|
213
|
+
|
|
214
|
+
expect(screen.getByLabelText("Email")).toBeInTheDocument();
|
|
215
|
+
expect(screen.queryByText(/welcome back, john/i)).not.toBeInTheDocument();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("SuperLogin Component - Login Functionality", () => {
|
|
220
|
+
beforeEach(() => {
|
|
221
|
+
localStorageMock.clear();
|
|
222
|
+
vi.clearAllMocks();
|
|
223
|
+
global.fetch.mockClear();
|
|
224
|
+
global.fetch.mockResolvedValue({
|
|
225
|
+
ok: true,
|
|
226
|
+
json: async () => ({ eligible: false }),
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("validates email format on submit", async () => {
|
|
231
|
+
const { user } = setupTest();
|
|
232
|
+
|
|
233
|
+
const emailInput = screen.getByLabelText("Email");
|
|
234
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
235
|
+
|
|
236
|
+
await user.type(emailInput, "invalidemail");
|
|
237
|
+
await user.type(passwordInput, "password123");
|
|
238
|
+
|
|
239
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
240
|
+
await fireEvent.submit(form);
|
|
241
|
+
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(screen.getByText(/invalid email address/i)).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("submits login request with correct credentials", async () => {
|
|
250
|
+
global.fetch.mockResolvedValueOnce({
|
|
251
|
+
ok: true,
|
|
252
|
+
json: async () => ({
|
|
253
|
+
token: "test-token",
|
|
254
|
+
refreshToken: "refresh-token",
|
|
255
|
+
firstName: "John",
|
|
256
|
+
}),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const onLoginSuccess = vi.fn();
|
|
260
|
+
const onNavigate = vi.fn();
|
|
261
|
+
const { user } = setupTest({ onLoginSuccess, onNavigate });
|
|
262
|
+
|
|
263
|
+
const emailInput = screen.getByLabelText("Email");
|
|
264
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
265
|
+
|
|
266
|
+
await user.type(emailInput, "test@example.com");
|
|
267
|
+
await user.type(passwordInput, "password123");
|
|
268
|
+
|
|
269
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
270
|
+
await fireEvent.submit(form);
|
|
271
|
+
|
|
272
|
+
await waitFor(() => {
|
|
273
|
+
expect(global.fetch).toHaveBeenCalledWith("/api/public/login", {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: { "Content-Type": "application/json" },
|
|
276
|
+
body: JSON.stringify({
|
|
277
|
+
email: "test@example.com",
|
|
278
|
+
password: "password123",
|
|
279
|
+
}),
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(onLoginSuccess).toHaveBeenCalledWith(
|
|
284
|
+
{
|
|
285
|
+
token: "test-token",
|
|
286
|
+
refreshToken: "refresh-token",
|
|
287
|
+
firstName: "John",
|
|
288
|
+
email: "test@example.com",
|
|
289
|
+
},
|
|
290
|
+
true
|
|
291
|
+
);
|
|
292
|
+
expect(onNavigate).toHaveBeenCalledWith("/");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("handles login error for incorrect credentials", async () => {
|
|
296
|
+
global.fetch.mockResolvedValueOnce({
|
|
297
|
+
ok: false,
|
|
298
|
+
status: 401,
|
|
299
|
+
json: async () => ({ message: "Invalid credentials" }),
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const { user } = setupTest();
|
|
303
|
+
|
|
304
|
+
const emailInput = screen.getByLabelText("Email");
|
|
305
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
306
|
+
|
|
307
|
+
await user.type(emailInput, "test@example.com");
|
|
308
|
+
await user.type(passwordInput, "wrongpassword");
|
|
309
|
+
|
|
310
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
311
|
+
await fireEvent.submit(form);
|
|
312
|
+
|
|
313
|
+
await waitFor(() => {
|
|
314
|
+
expect(screen.getByText(/incorrect email or password/i)).toBeInTheDocument();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("handles network error during login", async () => {
|
|
319
|
+
global.fetch.mockRejectedValueOnce(new TypeError("Network Error"));
|
|
320
|
+
|
|
321
|
+
const { user } = setupTest();
|
|
322
|
+
|
|
323
|
+
const emailInput = screen.getByLabelText("Email");
|
|
324
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
325
|
+
|
|
326
|
+
await user.type(emailInput, "test@example.com");
|
|
327
|
+
await user.type(passwordInput, "password123");
|
|
328
|
+
|
|
329
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
330
|
+
await fireEvent.submit(form);
|
|
331
|
+
|
|
332
|
+
await waitFor(() => {
|
|
333
|
+
expect(screen.getByText(/unable to connect/i)).toBeInTheDocument();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("saves remembered user when rememberMe is checked", async () => {
|
|
338
|
+
global.fetch.mockResolvedValueOnce({
|
|
339
|
+
ok: true,
|
|
340
|
+
json: async () => ({
|
|
341
|
+
token: "test-token",
|
|
342
|
+
firstName: "John",
|
|
343
|
+
}),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const { user } = setupTest();
|
|
347
|
+
|
|
348
|
+
const emailInput = screen.getByLabelText("Email");
|
|
349
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
350
|
+
|
|
351
|
+
await user.type(emailInput, "test@example.com");
|
|
352
|
+
await user.type(passwordInput, "password123");
|
|
353
|
+
|
|
354
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
355
|
+
await fireEvent.submit(form);
|
|
356
|
+
|
|
357
|
+
await waitFor(() => {
|
|
358
|
+
const stored = localStorageMock.getItem("rememberedUser");
|
|
359
|
+
expect(stored).toBeTruthy();
|
|
360
|
+
const parsed = JSON.parse(stored);
|
|
361
|
+
expect(parsed.email).toBe("test@example.com");
|
|
362
|
+
expect(parsed.firstName).toBe("John");
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("does not save remembered user when rememberMe is unchecked", async () => {
|
|
367
|
+
global.fetch.mockResolvedValueOnce({
|
|
368
|
+
ok: true,
|
|
369
|
+
json: async () => ({
|
|
370
|
+
token: "test-token",
|
|
371
|
+
firstName: "John",
|
|
372
|
+
}),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const { user } = setupTest();
|
|
376
|
+
|
|
377
|
+
const emailInput = screen.getByLabelText("Email");
|
|
378
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
379
|
+
const rememberMeCheckbox = screen.getByRole("checkbox", { name: /remember me/i });
|
|
380
|
+
|
|
381
|
+
await user.type(emailInput, "test@example.com");
|
|
382
|
+
await user.type(passwordInput, "password123");
|
|
383
|
+
await user.click(rememberMeCheckbox);
|
|
384
|
+
|
|
385
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
386
|
+
await fireEvent.submit(form);
|
|
387
|
+
|
|
388
|
+
await waitFor(() => {
|
|
389
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const stored = localStorageMock.getItem("rememberedUser");
|
|
393
|
+
expect(stored).toBeNull();
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe("SuperLogin Component - Account Selection", () => {
|
|
398
|
+
beforeEach(() => {
|
|
399
|
+
localStorageMock.clear();
|
|
400
|
+
vi.clearAllMocks();
|
|
401
|
+
global.fetch.mockClear();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("shows account selection when multiple accounts are returned", async () => {
|
|
405
|
+
global.fetch.mockResolvedValueOnce({
|
|
406
|
+
ok: true,
|
|
407
|
+
json: async () => ({
|
|
408
|
+
accounts: [
|
|
409
|
+
{
|
|
410
|
+
type: "owner",
|
|
411
|
+
organizationName: "Test Org",
|
|
412
|
+
token: "token1",
|
|
413
|
+
firstName: "John",
|
|
414
|
+
lastName: "Doe",
|
|
415
|
+
email: "test@example.com",
|
|
416
|
+
dashboardUrl: "/org-dashboard",
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
type: "performer",
|
|
420
|
+
token: "token2",
|
|
421
|
+
firstName: "John",
|
|
422
|
+
lastName: "Doe",
|
|
423
|
+
email: "test@example.com",
|
|
424
|
+
dashboardUrl: "/performer-dashboard",
|
|
425
|
+
performerProfile: {
|
|
426
|
+
stageName: "DJ Johnny",
|
|
427
|
+
useStageName: true,
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
}),
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const { user } = setupTest();
|
|
435
|
+
|
|
436
|
+
const emailInput = screen.getByLabelText("Email");
|
|
437
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
438
|
+
|
|
439
|
+
await user.type(emailInput, "test@example.com");
|
|
440
|
+
await user.type(passwordInput, "password123");
|
|
441
|
+
|
|
442
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
443
|
+
await fireEvent.submit(form);
|
|
444
|
+
|
|
445
|
+
await waitFor(() => {
|
|
446
|
+
expect(screen.getByRole("heading", { name: /select account/i })).toBeInTheDocument();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
expect(screen.getByText("Test Org")).toBeInTheDocument();
|
|
450
|
+
expect(screen.getByText("DJ Johnny")).toBeInTheDocument();
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test.skip("calls onAccountSelect when account is selected", async () => {
|
|
454
|
+
global.fetch.mockResolvedValueOnce({
|
|
455
|
+
ok: true,
|
|
456
|
+
json: async () => ({
|
|
457
|
+
accounts: [
|
|
458
|
+
{
|
|
459
|
+
type: "owner",
|
|
460
|
+
organizationName: "Test Org",
|
|
461
|
+
token: "token1",
|
|
462
|
+
firstName: "John",
|
|
463
|
+
lastName: "Doe",
|
|
464
|
+
email: "test@example.com",
|
|
465
|
+
dashboardUrl: "/org-dashboard",
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
}),
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const onAccountSelect = vi.fn();
|
|
472
|
+
const onNavigate = vi.fn();
|
|
473
|
+
const { user } = setupTest({ onAccountSelect, onNavigate });
|
|
474
|
+
|
|
475
|
+
const emailInput = screen.getByLabelText("Email");
|
|
476
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
477
|
+
|
|
478
|
+
await user.type(emailInput, "test@example.com");
|
|
479
|
+
await user.type(passwordInput, "password123");
|
|
480
|
+
|
|
481
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
482
|
+
await fireEvent.submit(form);
|
|
483
|
+
|
|
484
|
+
await waitFor(() => {
|
|
485
|
+
expect(screen.getByText("Test Org")).toBeInTheDocument();
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const accountButton = screen.getByText("Test Org").closest("button");
|
|
489
|
+
await user.click(accountButton);
|
|
490
|
+
|
|
491
|
+
expect(onAccountSelect).toHaveBeenCalledWith(
|
|
492
|
+
expect.objectContaining({
|
|
493
|
+
type: "owner",
|
|
494
|
+
organizationName: "Test Org",
|
|
495
|
+
}),
|
|
496
|
+
true
|
|
497
|
+
);
|
|
498
|
+
expect(onNavigate).toHaveBeenCalledWith("/org-dashboard");
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test.skip("handles external dashboard URLs correctly", async () => {
|
|
502
|
+
global.fetch.mockResolvedValueOnce({
|
|
503
|
+
ok: true,
|
|
504
|
+
json: async () => ({
|
|
505
|
+
accounts: [
|
|
506
|
+
{
|
|
507
|
+
type: "owner",
|
|
508
|
+
organizationName: "Test Org",
|
|
509
|
+
token: "token1",
|
|
510
|
+
firstName: "John",
|
|
511
|
+
lastName: "Doe",
|
|
512
|
+
email: "test@example.com",
|
|
513
|
+
dashboardUrl: "https://external.example.com/dashboard",
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
}),
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const onAccountSelect = vi.fn();
|
|
520
|
+
const onExternalNavigate = vi.fn();
|
|
521
|
+
const { user } = setupTest({ onAccountSelect, onExternalNavigate });
|
|
522
|
+
|
|
523
|
+
const emailInput = screen.getByLabelText("Email");
|
|
524
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
525
|
+
|
|
526
|
+
await user.type(emailInput, "test@example.com");
|
|
527
|
+
await user.type(passwordInput, "password123");
|
|
528
|
+
|
|
529
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
530
|
+
await fireEvent.submit(form);
|
|
531
|
+
|
|
532
|
+
await waitFor(() => {
|
|
533
|
+
expect(screen.getByText("Test Org")).toBeInTheDocument();
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const accountButton = screen.getByText("Test Org").closest("button");
|
|
537
|
+
await user.click(accountButton);
|
|
538
|
+
|
|
539
|
+
expect(onExternalNavigate).toHaveBeenCalledWith("https://external.example.com/dashboard");
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test.skip("allows returning to login from account selection", async () => {
|
|
543
|
+
global.fetch.mockResolvedValueOnce({
|
|
544
|
+
ok: true,
|
|
545
|
+
json: async () => ({
|
|
546
|
+
accounts: [
|
|
547
|
+
{
|
|
548
|
+
type: "owner",
|
|
549
|
+
organizationName: "Test Org",
|
|
550
|
+
token: "token1",
|
|
551
|
+
firstName: "John",
|
|
552
|
+
lastName: "Doe",
|
|
553
|
+
email: "test@example.com",
|
|
554
|
+
dashboardUrl: "/org-dashboard",
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
}),
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const { user } = setupTest();
|
|
561
|
+
|
|
562
|
+
const emailInput = screen.getByLabelText("Email");
|
|
563
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
564
|
+
|
|
565
|
+
await user.type(emailInput, "test@example.com");
|
|
566
|
+
await user.type(passwordInput, "password123");
|
|
567
|
+
|
|
568
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
569
|
+
await fireEvent.submit(form);
|
|
570
|
+
|
|
571
|
+
await waitFor(() => {
|
|
572
|
+
expect(screen.getByText(/select account/i)).toBeInTheDocument();
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
const returnButton = screen.getByRole("button", { name: /log in with a different email/i });
|
|
576
|
+
await user.click(returnButton);
|
|
577
|
+
|
|
578
|
+
await waitFor(() => {
|
|
579
|
+
expect(screen.getByRole("heading", { name: /log in/i })).toBeInTheDocument();
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
describe("SuperLogin Component - Password Reset", () => {
|
|
585
|
+
beforeEach(() => {
|
|
586
|
+
localStorageMock.clear();
|
|
587
|
+
vi.clearAllMocks();
|
|
588
|
+
global.fetch.mockClear();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test("navigates to reset view when forgot password is clicked", async () => {
|
|
592
|
+
const { user } = setupTest();
|
|
593
|
+
|
|
594
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
595
|
+
await user.click(forgotPasswordButton);
|
|
596
|
+
|
|
597
|
+
await waitFor(() => {
|
|
598
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("validates email format in reset view", async () => {
|
|
603
|
+
const { user } = setupTest();
|
|
604
|
+
|
|
605
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
606
|
+
await user.click(forgotPasswordButton);
|
|
607
|
+
|
|
608
|
+
await waitFor(() => {
|
|
609
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
const emailInput = screen.getByLabelText("Email");
|
|
613
|
+
await user.type(emailInput, "invalidemail");
|
|
614
|
+
|
|
615
|
+
const submitButton = screen.getByRole("button", { name: /send reset link/i });
|
|
616
|
+
await user.click(submitButton);
|
|
617
|
+
|
|
618
|
+
await waitFor(() => {
|
|
619
|
+
expect(screen.getByText(/invalid email address/i)).toBeInTheDocument();
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("submits reset password request", async () => {
|
|
624
|
+
global.fetch.mockResolvedValueOnce({
|
|
625
|
+
ok: true,
|
|
626
|
+
json: async () => ({}),
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const { user } = setupTest();
|
|
630
|
+
|
|
631
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
632
|
+
await user.click(forgotPasswordButton);
|
|
633
|
+
|
|
634
|
+
await waitFor(() => {
|
|
635
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const emailInput = screen.getByLabelText("Email");
|
|
639
|
+
await user.type(emailInput, "test@example.com");
|
|
640
|
+
|
|
641
|
+
const submitButton = screen.getByRole("button", { name: /send reset link/i });
|
|
642
|
+
await user.click(submitButton);
|
|
643
|
+
|
|
644
|
+
await waitFor(() => {
|
|
645
|
+
expect(global.fetch).toHaveBeenCalledWith("/api/public/resetPassword", {
|
|
646
|
+
method: "POST",
|
|
647
|
+
headers: { "Content-Type": "application/json" },
|
|
648
|
+
body: JSON.stringify({ email: "test@example.com" }),
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
await waitFor(() => {
|
|
653
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("shows success message even on reset password error (prevents enumeration)", async () => {
|
|
658
|
+
global.fetch.mockRejectedValueOnce(new Error("Network error"));
|
|
659
|
+
|
|
660
|
+
const { user } = setupTest();
|
|
661
|
+
|
|
662
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
663
|
+
await user.click(forgotPasswordButton);
|
|
664
|
+
|
|
665
|
+
await waitFor(() => {
|
|
666
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
const emailInput = screen.getByLabelText("Email");
|
|
670
|
+
await user.type(emailInput, "test@example.com");
|
|
671
|
+
|
|
672
|
+
const submitButton = screen.getByRole("button", { name: /send reset link/i });
|
|
673
|
+
await user.click(submitButton);
|
|
674
|
+
|
|
675
|
+
await waitFor(() => {
|
|
676
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
test("allows returning to login from reset view", async () => {
|
|
681
|
+
const { user } = setupTest();
|
|
682
|
+
|
|
683
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
684
|
+
await user.click(forgotPasswordButton);
|
|
685
|
+
|
|
686
|
+
await waitFor(() => {
|
|
687
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
const returnButton = screen.getByRole("button", { name: /return to log in/i });
|
|
691
|
+
await user.click(returnButton);
|
|
692
|
+
|
|
693
|
+
await waitFor(() => {
|
|
694
|
+
expect(screen.getByRole("heading", { name: /log in/i })).toBeInTheDocument();
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
describe("SuperLogin Component - Login Link", () => {
|
|
700
|
+
beforeEach(() => {
|
|
701
|
+
localStorageMock.clear();
|
|
702
|
+
vi.clearAllMocks();
|
|
703
|
+
global.fetch.mockClear();
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test("navigates to login link view", async () => {
|
|
707
|
+
const { user } = setupTest();
|
|
708
|
+
|
|
709
|
+
const loginLinkButton = screen.getByRole("button", { name: /send me a login link/i });
|
|
710
|
+
await user.click(loginLinkButton);
|
|
711
|
+
|
|
712
|
+
await waitFor(() => {
|
|
713
|
+
expect(screen.getByRole("heading", { name: /send a login link/i })).toBeInTheDocument();
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test("validates email format in login link view", async () => {
|
|
718
|
+
const { user } = setupTest();
|
|
719
|
+
|
|
720
|
+
const loginLinkButton = screen.getByRole("button", { name: /send me a login link/i });
|
|
721
|
+
await user.click(loginLinkButton);
|
|
722
|
+
|
|
723
|
+
await waitFor(() => {
|
|
724
|
+
expect(screen.getByRole("heading", { name: /send a login link/i })).toBeInTheDocument();
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
const emailInput = screen.getByLabelText("Email");
|
|
728
|
+
await user.type(emailInput, "invalidemail");
|
|
729
|
+
|
|
730
|
+
const submitButton = screen.getByRole("button", { name: /^send login link$/i });
|
|
731
|
+
await user.click(submitButton);
|
|
732
|
+
|
|
733
|
+
await waitFor(() => {
|
|
734
|
+
expect(screen.getByText(/invalid email address/i)).toBeInTheDocument();
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("submits login link request", async () => {
|
|
739
|
+
global.fetch.mockResolvedValueOnce({
|
|
740
|
+
ok: true,
|
|
741
|
+
json: async () => ({}),
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
const { user } = setupTest();
|
|
745
|
+
|
|
746
|
+
const loginLinkButton = screen.getByRole("button", { name: /send me a login link/i });
|
|
747
|
+
await user.click(loginLinkButton);
|
|
748
|
+
|
|
749
|
+
await waitFor(() => {
|
|
750
|
+
expect(screen.getByRole("heading", { name: /send a login link/i })).toBeInTheDocument();
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
const emailInput = screen.getByLabelText("Email");
|
|
754
|
+
await user.type(emailInput, "test@example.com");
|
|
755
|
+
|
|
756
|
+
const submitButton = screen.getByRole("button", { name: /^send login link$/i });
|
|
757
|
+
await user.click(submitButton);
|
|
758
|
+
|
|
759
|
+
await waitFor(() => {
|
|
760
|
+
expect(global.fetch).toHaveBeenCalledWith("/api/public/passwordlessLogin", {
|
|
761
|
+
method: "POST",
|
|
762
|
+
headers: { "Content-Type": "application/json" },
|
|
763
|
+
body: JSON.stringify({ email: "test@example.com", destination: "dashboard" }),
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
await waitFor(() => {
|
|
768
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
test("shows success message even on login link error (prevents enumeration)", async () => {
|
|
773
|
+
global.fetch.mockRejectedValueOnce(new Error("Network error"));
|
|
774
|
+
|
|
775
|
+
const { user } = setupTest();
|
|
776
|
+
|
|
777
|
+
const loginLinkButton = screen.getByRole("button", { name: /send me a login link/i });
|
|
778
|
+
await user.click(loginLinkButton);
|
|
779
|
+
|
|
780
|
+
await waitFor(() => {
|
|
781
|
+
expect(screen.getByRole("heading", { name: /send a login link/i })).toBeInTheDocument();
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
const emailInput = screen.getByLabelText("Email");
|
|
785
|
+
await user.type(emailInput, "test@example.com");
|
|
786
|
+
|
|
787
|
+
const submitButton = screen.getByRole("button", { name: /^send login link$/i });
|
|
788
|
+
await user.click(submitButton);
|
|
789
|
+
|
|
790
|
+
await waitFor(() => {
|
|
791
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
describe("SuperLogin Component - Setup/First Use", () => {
|
|
797
|
+
beforeEach(() => {
|
|
798
|
+
localStorageMock.clear();
|
|
799
|
+
vi.clearAllMocks();
|
|
800
|
+
global.fetch.mockClear();
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
test("checks eligibility on mount with setup param", async () => {
|
|
804
|
+
global.fetch.mockResolvedValueOnce({
|
|
805
|
+
ok: true,
|
|
806
|
+
json: async () => ({
|
|
807
|
+
eligible: true,
|
|
808
|
+
firstName: "John",
|
|
809
|
+
}),
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
// Mock window.location.search
|
|
813
|
+
delete window.location;
|
|
814
|
+
window.location = { search: "?setup=true&email=test@example.com" };
|
|
815
|
+
|
|
816
|
+
setupTest({ searchParams: "?setup=true&email=test@example.com" });
|
|
817
|
+
|
|
818
|
+
await waitFor(() => {
|
|
819
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
820
|
+
"/api/public/checkIfFirstUseEligible/test%40example.com"
|
|
821
|
+
);
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
await waitFor(() => {
|
|
825
|
+
expect(screen.getByRole("heading", { name: /welcome, john/i })).toBeInTheDocument();
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("shows error when eligibility check fails", async () => {
|
|
830
|
+
global.fetch.mockResolvedValueOnce({
|
|
831
|
+
ok: true,
|
|
832
|
+
json: async () => ({
|
|
833
|
+
eligible: false,
|
|
834
|
+
}),
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
delete window.location;
|
|
838
|
+
window.location = { search: "?setup=true&email=test@example.com" };
|
|
839
|
+
|
|
840
|
+
setupTest({ searchParams: "?setup=true&email=test@example.com" });
|
|
841
|
+
|
|
842
|
+
await waitFor(() => {
|
|
843
|
+
expect(screen.getByText(/this link is no longer valid/i)).toBeInTheDocument();
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
test.skip("renders setup view with password strength indicator", async () => {
|
|
848
|
+
setupTest({ initialView: "setup", email: "test@example.com" });
|
|
849
|
+
|
|
850
|
+
expect(screen.getByRole("heading", { name: /set up your account/i })).toBeInTheDocument();
|
|
851
|
+
expect(screen.getByLabelText(/create password/i)).toBeInTheDocument();
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
test.skip("complete setup button is disabled when password is weak", async () => {
|
|
855
|
+
setupTest({ initialView: "setup" });
|
|
856
|
+
|
|
857
|
+
const submitButton = screen.getByRole("button", { name: /complete setup/i });
|
|
858
|
+
expect(submitButton).toBeDisabled();
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
test.skip("submits setup request with valid password", async () => {
|
|
862
|
+
global.fetch.mockResolvedValueOnce({
|
|
863
|
+
ok: true,
|
|
864
|
+
json: async () => ({
|
|
865
|
+
token: "test-token",
|
|
866
|
+
refreshToken: "refresh-token",
|
|
867
|
+
}),
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
const onLoginSuccess = vi.fn();
|
|
871
|
+
const onNavigate = vi.fn();
|
|
872
|
+
const { user } = setupTest({
|
|
873
|
+
initialView: "setup",
|
|
874
|
+
email: "test@example.com",
|
|
875
|
+
onLoginSuccess,
|
|
876
|
+
onNavigate,
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
const passwordInput = screen.getByLabelText(/create password/i);
|
|
880
|
+
await user.type(passwordInput, "StrongPass123!");
|
|
881
|
+
|
|
882
|
+
// Wait for password strength to update
|
|
883
|
+
await waitFor(() => {
|
|
884
|
+
const submitButton = screen.getByRole("button", { name: /complete setup/i });
|
|
885
|
+
expect(submitButton).toBeEnabled();
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
const form = screen.getByRole("button", { name: /complete setup/i }).closest("form");
|
|
889
|
+
await fireEvent.submit(form);
|
|
890
|
+
|
|
891
|
+
await waitFor(() => {
|
|
892
|
+
expect(global.fetch).toHaveBeenCalledWith("/api/public/setFirstUsePassword", {
|
|
893
|
+
method: "POST",
|
|
894
|
+
headers: { "Content-Type": "application/json" },
|
|
895
|
+
credentials: "include",
|
|
896
|
+
body: JSON.stringify({
|
|
897
|
+
email: "test@example.com",
|
|
898
|
+
password: "StrongPass123!",
|
|
899
|
+
}),
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
expect(onLoginSuccess).toHaveBeenCalled();
|
|
904
|
+
expect(onNavigate).toHaveBeenCalledWith("/profile");
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
test.skip("shows terms of service link in setup view", () => {
|
|
908
|
+
setupTest({ initialView: "setup", tosUrl: "https://example.com/tos" });
|
|
909
|
+
|
|
910
|
+
const tosLink = screen.getByRole("link", { name: /terms of service/i });
|
|
911
|
+
expect(tosLink).toBeInTheDocument();
|
|
912
|
+
expect(tosLink).toHaveAttribute("href", "https://example.com/tos");
|
|
913
|
+
expect(tosLink).toHaveAttribute("target", "_blank");
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
test.skip("handles setup password error", async () => {
|
|
917
|
+
global.fetch.mockResolvedValueOnce({
|
|
918
|
+
ok: false,
|
|
919
|
+
json: async () => ({
|
|
920
|
+
error: "Password does not meet requirements",
|
|
921
|
+
}),
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
const { user } = setupTest({ initialView: "setup", email: "test@example.com" });
|
|
925
|
+
|
|
926
|
+
const passwordInput = screen.getByLabelText(/create password/i);
|
|
927
|
+
await user.type(passwordInput, "StrongPass123!");
|
|
928
|
+
|
|
929
|
+
await waitFor(() => {
|
|
930
|
+
const submitButton = screen.getByRole("button", { name: /complete setup/i });
|
|
931
|
+
expect(submitButton).toBeEnabled();
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
const form = screen.getByRole("button", { name: /complete setup/i }).closest("form");
|
|
935
|
+
await fireEvent.submit(form);
|
|
936
|
+
|
|
937
|
+
await waitFor(() => {
|
|
938
|
+
expect(screen.getByText(/password does not meet requirements/i)).toBeInTheDocument();
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
describe("SuperLogin Component - Success View", () => {
|
|
944
|
+
beforeEach(() => {
|
|
945
|
+
localStorageMock.clear();
|
|
946
|
+
vi.clearAllMocks();
|
|
947
|
+
global.fetch.mockClear();
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test.skip("shows success message after password reset", async () => {
|
|
951
|
+
global.fetch.mockResolvedValueOnce({
|
|
952
|
+
ok: true,
|
|
953
|
+
json: async () => ({}),
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
const { user } = setupTest();
|
|
957
|
+
|
|
958
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
959
|
+
await user.click(forgotPasswordButton);
|
|
960
|
+
|
|
961
|
+
await waitFor(() => {
|
|
962
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
const emailInput = screen.getByLabelText("Email");
|
|
966
|
+
await user.type(emailInput, "test@example.com");
|
|
967
|
+
|
|
968
|
+
const submitButton = screen.getByRole("button", { name: /send reset link/i });
|
|
969
|
+
await user.click(submitButton);
|
|
970
|
+
|
|
971
|
+
await waitFor(() => {
|
|
972
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
973
|
+
expect(screen.getByText(/resetting your password/i)).toBeInTheDocument();
|
|
974
|
+
});
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
test.skip("shows success message after login link", async () => {
|
|
978
|
+
global.fetch.mockResolvedValueOnce({
|
|
979
|
+
ok: true,
|
|
980
|
+
json: async () => ({}),
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
const { user } = setupTest();
|
|
984
|
+
|
|
985
|
+
const loginLinkButton = screen.getByRole("button", { name: /send me a login link/i });
|
|
986
|
+
await user.click(loginLinkButton);
|
|
987
|
+
|
|
988
|
+
await waitFor(() => {
|
|
989
|
+
expect(screen.getByRole("heading", { name: /send a login link/i })).toBeInTheDocument();
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
const emailInput = screen.getByLabelText("Email");
|
|
993
|
+
await user.type(emailInput, "test@example.com");
|
|
994
|
+
|
|
995
|
+
const submitButton = screen.getByRole("button", { name: /^send login link$/i });
|
|
996
|
+
await user.click(submitButton);
|
|
997
|
+
|
|
998
|
+
await waitFor(() => {
|
|
999
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
1000
|
+
expect(screen.getByText(/signing in/i)).toBeInTheDocument();
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
test.skip("allows resending reset email", async () => {
|
|
1005
|
+
global.fetch
|
|
1006
|
+
.mockResolvedValueOnce({
|
|
1007
|
+
ok: true,
|
|
1008
|
+
json: async () => ({}),
|
|
1009
|
+
})
|
|
1010
|
+
.mockResolvedValueOnce({
|
|
1011
|
+
ok: true,
|
|
1012
|
+
json: async () => ({}),
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
const { user } = setupTest();
|
|
1016
|
+
|
|
1017
|
+
// Navigate to reset view
|
|
1018
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
1019
|
+
await user.click(forgotPasswordButton);
|
|
1020
|
+
|
|
1021
|
+
await waitFor(() => {
|
|
1022
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1026
|
+
await user.type(emailInput, "test@example.com");
|
|
1027
|
+
|
|
1028
|
+
const submitButton = screen.getByRole("button", { name: /send reset link/i });
|
|
1029
|
+
await user.click(submitButton);
|
|
1030
|
+
|
|
1031
|
+
await waitFor(() => {
|
|
1032
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
// Click resend
|
|
1036
|
+
const resendButton = screen.getByRole("button", { name: /resend/i });
|
|
1037
|
+
await user.click(resendButton);
|
|
1038
|
+
|
|
1039
|
+
await waitFor(() => {
|
|
1040
|
+
expect(screen.getByText(/we've resent/i)).toBeInTheDocument();
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
expect(global.fetch).toHaveBeenCalledTimes(2);
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
test.skip("allows trying different email from success view", async () => {
|
|
1047
|
+
global.fetch.mockResolvedValueOnce({
|
|
1048
|
+
ok: true,
|
|
1049
|
+
json: async () => ({}),
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
const { user } = setupTest();
|
|
1053
|
+
|
|
1054
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
1055
|
+
await user.click(forgotPasswordButton);
|
|
1056
|
+
|
|
1057
|
+
await waitFor(() => {
|
|
1058
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1062
|
+
await user.type(emailInput, "test@example.com");
|
|
1063
|
+
|
|
1064
|
+
const submitButton = screen.getByRole("button", { name: /send reset link/i });
|
|
1065
|
+
await user.click(submitButton);
|
|
1066
|
+
|
|
1067
|
+
await waitFor(() => {
|
|
1068
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
const tryDifferentButton = screen.getByRole("button", { name: /try a different email/i });
|
|
1072
|
+
await user.click(tryDifferentButton);
|
|
1073
|
+
|
|
1074
|
+
await waitFor(() => {
|
|
1075
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
test.skip("allows returning to sign in from success view", async () => {
|
|
1080
|
+
global.fetch.mockResolvedValueOnce({
|
|
1081
|
+
ok: true,
|
|
1082
|
+
json: async () => ({}),
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
const { user } = setupTest();
|
|
1086
|
+
|
|
1087
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
1088
|
+
await user.click(forgotPasswordButton);
|
|
1089
|
+
|
|
1090
|
+
await waitFor(() => {
|
|
1091
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1095
|
+
await user.type(emailInput, "test@example.com");
|
|
1096
|
+
|
|
1097
|
+
const submitButton = screen.getByRole("button", { name: /send reset link/i });
|
|
1098
|
+
await user.click(submitButton);
|
|
1099
|
+
|
|
1100
|
+
await waitFor(() => {
|
|
1101
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
const returnButton = screen.getByRole("button", { name: /return to sign in/i });
|
|
1105
|
+
await user.click(returnButton);
|
|
1106
|
+
|
|
1107
|
+
await waitFor(() => {
|
|
1108
|
+
expect(screen.getByRole("heading", { name: /log in/i })).toBeInTheDocument();
|
|
1109
|
+
});
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
describe("SuperLogin Component - Error Handling", () => {
|
|
1114
|
+
beforeEach(() => {
|
|
1115
|
+
localStorageMock.clear();
|
|
1116
|
+
vi.clearAllMocks();
|
|
1117
|
+
global.fetch.mockClear();
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
test.skip("shows shake animation on validation error", async () => {
|
|
1121
|
+
const { user, container } = setupTest();
|
|
1122
|
+
|
|
1123
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1124
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
1125
|
+
|
|
1126
|
+
await user.type(emailInput, "invalidemail");
|
|
1127
|
+
await user.type(passwordInput, "password123");
|
|
1128
|
+
|
|
1129
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
1130
|
+
await fireEvent.submit(form);
|
|
1131
|
+
|
|
1132
|
+
await waitFor(() => {
|
|
1133
|
+
const card = container.querySelector(".login-card");
|
|
1134
|
+
expect(card).toHaveClass("shake");
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
test.skip("clears errors when returning to login", async () => {
|
|
1139
|
+
global.fetch.mockResolvedValueOnce({
|
|
1140
|
+
ok: false,
|
|
1141
|
+
status: 401,
|
|
1142
|
+
json: async () => ({ message: "Invalid credentials" }),
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
const { user } = setupTest();
|
|
1146
|
+
|
|
1147
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1148
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
1149
|
+
|
|
1150
|
+
await user.type(emailInput, "test@example.com");
|
|
1151
|
+
await user.type(passwordInput, "wrongpassword");
|
|
1152
|
+
|
|
1153
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
1154
|
+
await fireEvent.submit(form);
|
|
1155
|
+
|
|
1156
|
+
await waitFor(() => {
|
|
1157
|
+
expect(screen.getByText(/incorrect email or password/i)).toBeInTheDocument();
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
// Navigate away and back
|
|
1161
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
1162
|
+
await user.click(forgotPasswordButton);
|
|
1163
|
+
|
|
1164
|
+
await waitFor(() => {
|
|
1165
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
const returnButton = screen.getByRole("button", { name: /return to log in/i });
|
|
1169
|
+
await user.click(returnButton);
|
|
1170
|
+
|
|
1171
|
+
await waitFor(() => {
|
|
1172
|
+
expect(screen.queryByText(/incorrect email or password/i)).not.toBeInTheDocument();
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
test.skip("handles needsSetup response from login", async () => {
|
|
1177
|
+
global.fetch.mockResolvedValueOnce({
|
|
1178
|
+
ok: false,
|
|
1179
|
+
json: async () => ({
|
|
1180
|
+
needsSetup: true,
|
|
1181
|
+
email: "test@example.com",
|
|
1182
|
+
}),
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
const { user } = setupTest();
|
|
1186
|
+
|
|
1187
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1188
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
1189
|
+
|
|
1190
|
+
await user.type(emailInput, "test@example.com");
|
|
1191
|
+
await user.type(passwordInput, "password123");
|
|
1192
|
+
|
|
1193
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
1194
|
+
await fireEvent.submit(form);
|
|
1195
|
+
|
|
1196
|
+
await waitFor(() => {
|
|
1197
|
+
expect(screen.getByRole("heading", { name: /set up your account/i })).toBeInTheDocument();
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
describe("SuperLogin Component - Accessibility", () => {
|
|
1203
|
+
beforeEach(() => {
|
|
1204
|
+
localStorageMock.clear();
|
|
1205
|
+
vi.clearAllMocks();
|
|
1206
|
+
global.fetch.mockClear();
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
test.skip("email input has correct autocomplete attribute", () => {
|
|
1210
|
+
setupTest();
|
|
1211
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1212
|
+
expect(emailInput).toHaveAttribute("autocomplete", "username");
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
test.skip("password input has correct autocomplete attribute", () => {
|
|
1216
|
+
setupTest();
|
|
1217
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
1218
|
+
expect(passwordInput).toHaveAttribute("autocomplete", "current-password");
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
test.skip("setup password input has correct autocomplete attribute", () => {
|
|
1222
|
+
setupTest({ initialView: "setup" });
|
|
1223
|
+
const passwordInput = screen.getByLabelText(/create password/i);
|
|
1224
|
+
expect(passwordInput).toHaveAttribute("autocomplete", "new-password");
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
test.skip("error messages have role=alert", async () => {
|
|
1228
|
+
const { user } = setupTest();
|
|
1229
|
+
|
|
1230
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1231
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
1232
|
+
|
|
1233
|
+
await user.type(emailInput, "invalidemail");
|
|
1234
|
+
await user.type(passwordInput, "password123");
|
|
1235
|
+
|
|
1236
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
1237
|
+
await fireEvent.submit(form);
|
|
1238
|
+
|
|
1239
|
+
await waitFor(() => {
|
|
1240
|
+
const errorAlert = screen.getByRole("alert");
|
|
1241
|
+
expect(errorAlert).toBeInTheDocument();
|
|
1242
|
+
});
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
test.skip("form inputs are properly labeled", () => {
|
|
1246
|
+
setupTest();
|
|
1247
|
+
expect(screen.getByLabelText("Email")).toBeInTheDocument();
|
|
1248
|
+
expect(screen.getByLabelText("Password")).toBeInTheDocument();
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
test.skip("buttons have descriptive text", () => {
|
|
1252
|
+
setupTest();
|
|
1253
|
+
expect(screen.getByRole("button", { name: /^log in$/i })).toBeInTheDocument();
|
|
1254
|
+
expect(screen.getByRole("button", { name: /send me a login link/i })).toBeInTheDocument();
|
|
1255
|
+
expect(screen.getByRole("button", { name: /forgot your password/i })).toBeInTheDocument();
|
|
1256
|
+
});
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
describe("SuperLogin Component - Theme Management", () => {
|
|
1260
|
+
beforeEach(() => {
|
|
1261
|
+
localStorageMock.clear();
|
|
1262
|
+
vi.clearAllMocks();
|
|
1263
|
+
global.fetch.mockClear();
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
test("applies dark mode class based on localStorage", () => {
|
|
1267
|
+
localStorageMock.setItem("theme", "dark");
|
|
1268
|
+
const { container } = setupTest();
|
|
1269
|
+
|
|
1270
|
+
// Component applies theme to .micdrop container
|
|
1271
|
+
const micdropContainer = container.querySelector(".micdrop");
|
|
1272
|
+
if (micdropContainer) {
|
|
1273
|
+
expect(micdropContainer).toHaveClass("dark");
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
test("applies dark mode class based on system preference when no saved theme", () => {
|
|
1278
|
+
// matchMedia is mocked in vitest-setup.js to return matches: false
|
|
1279
|
+
const { container } = setupTest();
|
|
1280
|
+
|
|
1281
|
+
const micdropContainer = container.querySelector(".micdrop");
|
|
1282
|
+
if (micdropContainer) {
|
|
1283
|
+
expect(micdropContainer).not.toHaveClass("dark");
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
describe("SuperLogin Component - Email Normalization", () => {
|
|
1289
|
+
beforeEach(() => {
|
|
1290
|
+
localStorageMock.clear();
|
|
1291
|
+
vi.clearAllMocks();
|
|
1292
|
+
global.fetch.mockClear();
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
test.skip("converts email to lowercase on login", async () => {
|
|
1296
|
+
global.fetch.mockResolvedValueOnce({
|
|
1297
|
+
ok: true,
|
|
1298
|
+
json: async () => ({
|
|
1299
|
+
token: "test-token",
|
|
1300
|
+
}),
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
const { user } = setupTest();
|
|
1304
|
+
|
|
1305
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1306
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
1307
|
+
|
|
1308
|
+
await user.type(emailInput, "Test@EXAMPLE.COM");
|
|
1309
|
+
await user.type(passwordInput, "password123");
|
|
1310
|
+
|
|
1311
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
1312
|
+
await fireEvent.submit(form);
|
|
1313
|
+
|
|
1314
|
+
await waitFor(() => {
|
|
1315
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
1316
|
+
"/api/public/login",
|
|
1317
|
+
expect.objectContaining({
|
|
1318
|
+
body: JSON.stringify({
|
|
1319
|
+
email: "test@example.com",
|
|
1320
|
+
password: "password123",
|
|
1321
|
+
}),
|
|
1322
|
+
})
|
|
1323
|
+
);
|
|
1324
|
+
});
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
test("handles email with + sign in URL params", () => {
|
|
1328
|
+
delete window.location;
|
|
1329
|
+
window.location = { search: "?email=test%2Bplus@example.com" };
|
|
1330
|
+
|
|
1331
|
+
setupTest({ searchParams: "?email=test%2Bplus@example.com" });
|
|
1332
|
+
|
|
1333
|
+
// Component should handle + sign correctly
|
|
1334
|
+
expect(screen.getByRole("heading")).toBeInTheDocument();
|
|
1335
|
+
});
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
describe("SuperLogin Component - Loading States", () => {
|
|
1339
|
+
beforeEach(() => {
|
|
1340
|
+
localStorageMock.clear();
|
|
1341
|
+
vi.clearAllMocks();
|
|
1342
|
+
global.fetch.mockClear();
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
test("shows loading state during login", async () => {
|
|
1346
|
+
global.fetch.mockImplementation(
|
|
1347
|
+
() =>
|
|
1348
|
+
new Promise((resolve) =>
|
|
1349
|
+
setTimeout(
|
|
1350
|
+
() =>
|
|
1351
|
+
resolve({
|
|
1352
|
+
ok: true,
|
|
1353
|
+
json: async () => ({ token: "test-token" }),
|
|
1354
|
+
}),
|
|
1355
|
+
100
|
|
1356
|
+
)
|
|
1357
|
+
)
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
const { user } = setupTest();
|
|
1361
|
+
|
|
1362
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1363
|
+
const passwordInput = screen.getByLabelText("Password");
|
|
1364
|
+
|
|
1365
|
+
await user.type(emailInput, "test@example.com");
|
|
1366
|
+
await user.type(passwordInput, "password123");
|
|
1367
|
+
|
|
1368
|
+
const form = screen.getByRole("button", { name: /^log in$/i }).closest("form");
|
|
1369
|
+
fireEvent.submit(form);
|
|
1370
|
+
|
|
1371
|
+
// Button should show loading state
|
|
1372
|
+
await waitFor(() => {
|
|
1373
|
+
const loginButton = screen.getByRole("button", { name: /^log in$/i });
|
|
1374
|
+
expect(loginButton).toBeDisabled();
|
|
1375
|
+
});
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
test("shows loading spinner during eligibility check", () => {
|
|
1379
|
+
global.fetch.mockImplementation(
|
|
1380
|
+
() =>
|
|
1381
|
+
new Promise((resolve) =>
|
|
1382
|
+
setTimeout(
|
|
1383
|
+
() =>
|
|
1384
|
+
resolve({
|
|
1385
|
+
ok: true,
|
|
1386
|
+
json: async () => ({ eligible: true }),
|
|
1387
|
+
}),
|
|
1388
|
+
100
|
|
1389
|
+
)
|
|
1390
|
+
)
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
delete window.location;
|
|
1394
|
+
window.location = { search: "?setup=true&email=test@example.com" };
|
|
1395
|
+
|
|
1396
|
+
const { container } = setupTest({ searchParams: "?setup=true&email=test@example.com" });
|
|
1397
|
+
|
|
1398
|
+
// Should show loading spinner
|
|
1399
|
+
const spinner = container.querySelector(".animate-spin");
|
|
1400
|
+
expect(spinner).toBeInTheDocument();
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
test.skip("enforces minimum delay for password reset (prevents timing attacks)", async () => {
|
|
1404
|
+
const startTime = Date.now();
|
|
1405
|
+
|
|
1406
|
+
global.fetch.mockResolvedValueOnce({
|
|
1407
|
+
ok: true,
|
|
1408
|
+
json: async () => ({}),
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
const { user } = setupTest();
|
|
1412
|
+
|
|
1413
|
+
const forgotPasswordButton = screen.getByRole("button", { name: /forgot your password/i });
|
|
1414
|
+
await user.click(forgotPasswordButton);
|
|
1415
|
+
|
|
1416
|
+
await waitFor(() => {
|
|
1417
|
+
expect(screen.getByRole("heading", { name: /reset your password/i })).toBeInTheDocument();
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
const emailInput = screen.getByLabelText("Email");
|
|
1421
|
+
await user.type(emailInput, "test@example.com");
|
|
1422
|
+
|
|
1423
|
+
const submitButton = screen.getByRole("button", { name: /send reset link/i });
|
|
1424
|
+
await user.click(submitButton);
|
|
1425
|
+
|
|
1426
|
+
await waitFor(() => {
|
|
1427
|
+
expect(screen.getByText(/check your email/i)).toBeInTheDocument();
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
const endTime = Date.now();
|
|
1431
|
+
const elapsed = endTime - startTime;
|
|
1432
|
+
|
|
1433
|
+
// Should take at least 1000ms due to minimum delay
|
|
1434
|
+
expect(elapsed).toBeGreaterThanOrEqual(900); // Small buffer for timing
|
|
1435
|
+
});
|
|
1436
|
+
});
|