diff --git a/src/js/components/AdvancedSearchForm/AdvancedSearchForm.stories.js b/src/js/components/AdvancedSearchForm/AdvancedSearchForm.stories.js index 17326d7..2c88d45 100644 --- a/src/js/components/AdvancedSearchForm/AdvancedSearchForm.stories.js +++ b/src/js/components/AdvancedSearchForm/AdvancedSearchForm.stories.js @@ -1,11 +1,16 @@ import AdvancedSearchForm from './index.svelte'; -import AdvancedSearchDecorator from '../../decorators/AdvancedSearchDecorator.svelte' +import AdvancedSearchDecorator from '../../decorators/AdvancedSearchDecorator.svelte'; import { fn, expect, waitFor } from 'storybook/test'; export default { title: 'AdvancedSearchForm', component: AdvancedSearchForm, - decorators: [() => AdvancedSearchDecorator], + decorators: [ + () => ({ + Component: AdvancedSearchDecorator, + props: {}, + }), + ], args: { mockSubmit: fn(), }, @@ -28,17 +33,15 @@ export const DefaultDesktop = { }, }, play: async ({ args, canvas, userEvent }) => { - await canvas.getByLabelText('Search Term 1').focus(); await userEvent.type(canvas.getByLabelText('Search Term 1'), 'elephant'); await userEvent.click(canvas.getByTestId('advanced-search-submit')); - await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()); const calledUrl = args.mockSubmit.mock.calls[0][0]; - expect(calledUrl).toContain('q1=elephant') - - } + expect(calledUrl).toContain('q1=elephant'); + }, }; export const TwoFieldsWithAnd = { @@ -49,7 +52,6 @@ export const TwoFieldsWithAnd = { }, }, play: async ({ args, canvas, userEvent }) => { - await canvas.getByLabelText('Search Term 1').focus(); await userEvent.type(canvas.getByLabelText('Search Term 1'), 'elephant'); const searchField2 = canvas.getByLabelText('Selected field 2'); @@ -58,13 +60,14 @@ export const TwoFieldsWithAnd = { await userEvent.type(canvas.getByLabelText('Search Term 2'), 'conservation'); await userEvent.click(canvas.getByTestId('advanced-search-submit')); - await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()); const calledUrl = args.mockSubmit.mock.calls[0][0]; - expect(calledUrl).toContain('lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=AND') - - } -} + expect(calledUrl).toContain( + 'lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=AND' + ); + }, +}; export const FullTextFields1And4WithLastOr = { globals: { viewport: { @@ -73,7 +76,6 @@ export const FullTextFields1And4WithLastOr = { }, }, play: async ({ args, canvas, userEvent }) => { - await canvas.getByLabelText('Search Term 1').focus(); await userEvent.type(canvas.getByLabelText('Search Term 1'), 'elephant'); const searchField4 = canvas.getByLabelText('Selected field 4'); @@ -84,13 +86,14 @@ export const FullTextFields1And4WithLastOr = { await userEvent.click(thirdRadioGroupOr); await userEvent.click(canvas.getByTestId('advanced-search-submit')); - await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()); const calledUrl = args.mockSubmit.mock.calls[0][0]; - expect(calledUrl).toContain('lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=OR') - - } -} + expect(calledUrl).toContain( + 'lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=OR' + ); + }, +}; export const FullTextFields1And4WithFirstOr = { globals: { @@ -100,7 +103,6 @@ export const FullTextFields1And4WithFirstOr = { }, }, play: async ({ args, canvas, userEvent }) => { - await canvas.getByLabelText('Search Term 1').focus(); await userEvent.type(canvas.getByLabelText('Search Term 1'), 'elephant'); const firstRadioGroupOr = canvas.getAllByRole('radio', { name: 'OR' })[0]; @@ -111,13 +113,14 @@ export const FullTextFields1And4WithFirstOr = { await userEvent.type(canvas.getByLabelText('Search Term 4'), 'conservation'); await userEvent.click(canvas.getByTestId('advanced-search-submit')); - await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()); const calledUrl = args.mockSubmit.mock.calls[0][0]; - expect(calledUrl).toContain('lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=OR') - - } -} + expect(calledUrl).toContain( + 'lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=OR' + ); + }, +}; export const CatalogFields1And4WithLastOr = { globals: { viewport: { @@ -126,7 +129,6 @@ export const CatalogFields1And4WithLastOr = { }, }, play: async ({ args, canvas, userEvent }) => { - const searchField1 = canvas.getByLabelText('Selected field 1'); await userEvent.selectOptions(searchField1, 'Title'); await canvas.getByLabelText('Search Term 1').focus(); @@ -139,13 +141,14 @@ export const CatalogFields1And4WithLastOr = { await userEvent.click(thirdRadioGroupOr); await userEvent.click(canvas.getByTestId('advanced-search-submit')); - await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()); const calledUrl = args.mockSubmit.mock.calls[0][0]; - expect(calledUrl).toContain('adv=1&setft=true&ft=ft&lookfor%5B%5D=apple&lookfor%5B%5D=orange&type%5B%5D=title&type%5B%5D=author&bool%5B%5D=OR') - - } -} + expect(calledUrl).toContain( + 'adv=1&setft=true&ft=ft&lookfor%5B%5D=apple&lookfor%5B%5D=orange&type%5B%5D=title&type%5B%5D=author&bool%5B%5D=OR' + ); + }, +}; export const InvalidDate = { globals: { viewport: { @@ -162,10 +165,10 @@ export const InvalidDate = { await waitFor(() => { expect(args.mockSubmit).not.toHaveBeenCalled(); - expect( canvas.getByText( /Publication Year must/)).toBeVisible(); + expect(canvas.getByText(/Publication Year must/)).toBeVisible(); }); - } -} + }, +}; export const MissingSearch = { globals: { @@ -179,7 +182,7 @@ export const MissingSearch = { await waitFor(() => { expect(args.mockSubmit).not.toHaveBeenCalled(); - expect( canvas.getByText( /A search term is required/)).toBeVisible(); + expect(canvas.getByText(/A search term is required/)).toBeVisible(); }); - } -} + }, +}; diff --git a/src/js/components/Modal/index.svelte b/src/js/components/Modal/index.svelte index 7c5b56f..6202c5c 100644 --- a/src/js/components/Modal/index.svelte +++ b/src/js/components/Modal/index.svelte @@ -2,6 +2,7 @@ import { onMount } from 'svelte'; import dialogPolyfill from 'dialog-polyfill'; + import docCookies from '../../lib/cookies.svelte'; /** * @typedef {Object} Props @@ -12,6 +13,7 @@ * @property {boolean} [scrollable] * @property {string} [mode] * @property {boolean} [modalLarge] + * @property {boolean} [setPromptCookie] * @property {boolean} [fullscreenOnMobile] * @property {boolean} [focusHelpOnClose] * @property {boolean} [focusMyAccountOnClose] @@ -36,6 +38,8 @@ focusMyAccountOnClose = false, focusButtonOnClose = false, focusDownloadOnClose = false, + setPromptCookie = false, + checkForNotifications, title, body, footer, @@ -78,6 +82,9 @@ window.removeEventListener('keydown', logKeys); console.log('-- dialog is closed'); + if (setPromptCookie) { + docCookies.setItem('HT-role-prompt', 'true', 4, 'hours'); + } if (focusHelpOnClose) { document.getElementById('get-help').focus(); } @@ -97,10 +104,6 @@ console.log('-- polyfilling dialog'); dialogPolyfill.registerDialog(dialog); } - - if (isOpen) { - openModal(); - } }); $effect(() => { @@ -131,6 +134,9 @@ data-bs-dismiss="modal" onclick={() => { hide(); + if (setPromptCookie) { + checkForNotifications() + } }} > { const canvas = within(canvasElement); //sanity check - expect(await canvas.getByTitle('HathiTrust Home')).toBeInTheDocument(); + expect(canvas.getByTitle('HathiTrust Home')).toBeInTheDocument(); }, globals: { viewport: { @@ -44,7 +44,7 @@ export const DesktopDropdownMenuSelected = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const mainMenu = await canvas.getByText(/member libraries/i); + const mainMenu = canvas.getByText(/member libraries/i); await userEvent.click(mainMenu); }, }; @@ -99,6 +99,25 @@ export const DesktopLoggedInResourceSharingRole = { await userEvent.click(accountButton); }, }; +export const DesktopLoggedInResourceSharingRolePromptDismissed = { + parameters: { ...Default.parameters }, + args: { + loggedIn: true, + }, + decorators: [ + () => ({ + Component: PingCallbackDecorator, + props: { loggedIn: true, role: 'resourceSharing', hasActivatedRole: false }, + }), + ], + play: async ({ canvas }) => { + const closeModal = canvas.getByRole('button', { name: 'Cancel' }); + await userEvent.click(closeModal); + + const accountButton = canvas.getByLabelText(/My Account/); + await userEvent.click(accountButton); + }, +}; export const DesktopLoggedInResourceSharingRoleActivated = { parameters: { ...Default.parameters }, args: { @@ -108,7 +127,14 @@ export const DesktopLoggedInResourceSharingRoleActivated = { decorators: [ () => ({ Component: PingCallbackDecorator, - props: { loggedIn: true, role: 'resourceSharing', hasActivatedRole: true }, + props: { + loggedIn: true, + role: 'resourceSharing', + hasActivatedRole: true, + cookieData: { + 'HT-role-prompt': 'true', + }, + }, }), ], play: async ({ canvasElement }) => { @@ -140,6 +166,61 @@ export const DesktopLoggedInWithNotifications = { }), ], }; +export const DesktopLoggedInResourceSharingRoleAndNotification = { + parameters: { ...Default.parameters }, + args: { + loggedIn: true, + hasNotification: true, + }, + decorators: [ + () => ({ + Component: PingCallbackDecorator, + props: { + loggedIn: true, + role: 'resourceSharing', + hasActivatedRole: false, + cookieData: {}, + notificationData: [{ title: 'What happens with two modals?', message: 'Hopefully these are not overlapping!' }], + }, + }), + ], + play: async ({ canvas, userEvent }) => { + const rolePromptCancelButton = canvas.getByRole('button', { name: 'Cancel' }); + await userEvent.click(rolePromptCancelButton); + }, +}; +export const DesktopLoggedInRoleSwitchClosed = { + parameters: { ...Default.parameters }, + args: { + loggedIn: true, + hasNotification: true, + }, + decorators: [ + () => ({ + Component: PingCallbackDecorator, + props: { + loggedIn: true, + role: 'resourceSharing', + hasActivatedRole: false, + cookieData: {}, + notificationData: [ + { + title: 'Closed role switch modal', + message: + 'User closed role switch modal using the X icon. Cookie was set, notifications should still appear.', + }, + ], + }, + }), + ], + play: async ({ canvas, userEvent }) => { + await waitFor(() => { + expect(canvas.getByRole('heading', { name: 'Choose a role' })).toBeVisible(); + }); + const rolePromptCloseButton = canvas.getByRole('button', { name: 'Close modal' }); + await userEvent.click(rolePromptCloseButton); + }, +}; export const Mobile = { decorators: [ () => ({ diff --git a/src/js/components/Navbar/index.svelte b/src/js/components/Navbar/index.svelte index 70f0b7d..6714d4c 100644 --- a/src/js/components/Navbar/index.svelte +++ b/src/js/components/Navbar/index.svelte @@ -59,7 +59,9 @@ roleSwitchModal.show(); } function openNotificationsModal(event) { - event.preventDefault(); + if (event) { + event.preventDefault(); + } notificationsModal.show(); } function openFeedback(formLocation, event) { @@ -93,22 +95,61 @@ return { status: false }; } + let roleSwitchOpen = $state(false); + let notificationsOpen = $state(false); + let canAutoOpenNotifications = $state(false); + + function shouldShowRoleSwitch() { + if (!loggedIn || !hasSwitchableRoles) { + return false; + } + if (HT.cookieJar.hasItem('HT-role-prompt')) { + return false; + } + return true; + } + // $: loginStatus = HT.loginStatus; let loggedIn = $derived(HT.loginStatus.logged_in); let hasSwitchableRoles = $derived(checkSwitchableRoles(loggedIn).status); let hasActivatedRole = $derived(checkSwitchableRoles(loggedIn).activated); let role = $derived(checkSwitchableRoles(loggedIn).label); + $effect(() => { if (HT.loginStatus && HT.loginStatus.notificationData) { notificationsManager.update(HT.loginStatus.notificationData); hasNotification = notificationsManager.hasNotifications(); } }); + + $effect(() => { + if (shouldShowRoleSwitch() && !roleSwitchOpen) { + roleSwitchOpen = true; + } + }); + + $effect(() => { + if (shouldShowRoleSwitch() && roleSwitchOpen) { + canAutoOpenNotifications = false; + return; + } + + canAutoOpenNotifications = true; + }); + {#if hasSwitchableRoles} - + { + if (hasNotification && notificationsManager.hasNewNotifications()) { + openNotificationsModal(); + } + }} + /> {/if} {#if hasNotification} - + {/if}