diff --git a/packages/app/obojobo-repository/client/css/_defaults.scss b/packages/app/obojobo-repository/client/css/_defaults.scss index a8326fc902..370797069b 100644 --- a/packages/app/obojobo-repository/client/css/_defaults.scss +++ b/packages/app/obojobo-repository/client/css/_defaults.scss @@ -32,6 +32,8 @@ $color-reward: #ffe65d; $color-reward-text: #947d00; $color-obojobo-blue: #0d4fa7; $color-preview: #af1b5c; +$color-notification: #af1b5c; +$color-notification-focus: #fbdae6; $size-spacing-vertical-big: 40px; $size-spacing-vertical-half: $size-spacing-vertical-big / 2; diff --git a/packages/app/obojobo-repository/shared/components/__snapshots__/dashboard.test.js.snap b/packages/app/obojobo-repository/shared/components/__snapshots__/dashboard.test.js.snap index be4328c58f..e00b16e294 100644 --- a/packages/app/obojobo-repository/shared/components/__snapshots__/dashboard.test.js.snap +++ b/packages/app/obojobo-repository/shared/components/__snapshots__/dashboard.test.js.snap @@ -48,7 +48,6 @@ exports[`Dashboard renders in MODE_RECENT with fewer modules than moduleCount, n >
firstName lastName
@@ -286,7 +285,6 @@ exports[`Dashboard renders with default props 1`] = ` >
firstName lastName
@@ -506,7 +504,6 @@ exports[`Dashboard renders with mode = MODE_ALL 1`] = ` >
firstName lastName
@@ -759,7 +756,6 @@ exports[`Dashboard renders with mode = MODE_COLLECTION 1`] = ` >
firstName lastName
@@ -952,7 +948,6 @@ exports[`Dashboard renders with mode = MODE_RECENT 1`] = ` >
firstName lastName
diff --git a/packages/app/obojobo-repository/shared/components/__snapshots__/notification.test.js.snap b/packages/app/obojobo-repository/shared/components/__snapshots__/notification.test.js.snap index bb0f308d29..6f6b5859be 100644 --- a/packages/app/obojobo-repository/shared/components/__snapshots__/notification.test.js.snap +++ b/packages/app/obojobo-repository/shared/components/__snapshots__/notification.test.js.snap @@ -1,8 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Notification component does not update cookie when there are no notifications 1`] = `null`; +exports[`Notification component does not update cookie when there are no notifications 1`] = ` +
+

+ That's all for now +

+
+`; -exports[`Notification component handles click on exit button and updates state and cookie 1`] = `null`; +exports[`Notification component handles click on exit button and updates state and cookie 1`] = ` +
+

+ That's all for now +

+
+`; exports[`Notification component hides the notification on exit button click 1`] = `
`; -exports[`Notification component hides the notification on exit button click 2`] = `null`; +exports[`Notification component hides the notification on exit button click 2`] = ` +
+

+ That's all for now +

+
+`; -exports[`Notification component loads notifications from cookies on mount 1`] = `null`; +exports[`Notification component loads notifications from cookies on mount 1`] = ` +
+

+ That's all for now +

+
+`; -exports[`Notification component renders null when document.cookie is null 1`] = `null`; +exports[`Notification component renders null when document.cookie is null 1`] = ` +
+

+ That's all for now +

+
+`; -exports[`Notification component renders null when there are no notifications but document.cookie is not null 1`] = `null`; +exports[`Notification component renders null when there are no notifications but document.cookie is not null 1`] = ` +
+

+ That's all for now +

+
+`; -exports[`Notification component renders without crashing 1`] = `null`; +exports[`Notification component renders without crashing 1`] = ` +
+

+ That's all for now +

+
+`; diff --git a/packages/app/obojobo-repository/shared/components/__snapshots__/repository-nav.test.js.snap b/packages/app/obojobo-repository/shared/components/__snapshots__/repository-nav.test.js.snap index b5b193dcea..635b0def39 100644 --- a/packages/app/obojobo-repository/shared/components/__snapshots__/repository-nav.test.js.snap +++ b/packages/app/obojobo-repository/shared/components/__snapshots__/repository-nav.test.js.snap @@ -45,7 +45,6 @@ exports[`RepositoryNav does not render stats section with just canViewSystemStat >
Display Name
@@ -124,7 +123,6 @@ exports[`RepositoryNav loads notifications from cookies on mount 1`] = ` >
Display Name
@@ -203,7 +201,6 @@ exports[`RepositoryNav renders correctly with standard expected props 1`] = ` >
Display Name
@@ -323,12 +320,86 @@ exports[`RepositoryNav renders null when document.cookie is null 1`] = ` >
Display Name +
+
+ className="avatar--image" + > + +
+
+ +
+
+ + Log Out + +
+
+
+ + +`; + +exports[`RepositoryNav renders null when there are no notifications but document.cookie is not null 1`] = ` +
+ {isNotificationsOpen && ( -
- +
+
+ + +
+
)}
diff --git a/packages/app/obojobo-repository/shared/components/repository-nav.scss b/packages/app/obojobo-repository/shared/components/repository-nav.scss index bb128079e0..3ed0d2865d 100644 --- a/packages/app/obojobo-repository/shared/components/repository-nav.scss +++ b/packages/app/obojobo-repository/shared/components/repository-nav.scss @@ -1,7 +1,5 @@ @import '../../client/css/defaults'; -$poppin-pink: #af1b5c; - .repository--section-wrapper { max-width: $dimension-width; margin: 0 auto; @@ -10,7 +8,7 @@ $poppin-pink: #af1b5c; .repository--stick-to-top { position: sticky; top: 0; - background: #ffffff; + background: $color-bg; min-width: 100%; z-index: 100; border-bottom: 1px solid $border-color; @@ -78,7 +76,7 @@ $poppin-pink: #af1b5c; position: absolute; left: -1em; top: 3.7em; - background: #ffffff; + background: $color-bg; padding: 20px; border-radius: 3px; width: 100%; @@ -123,19 +121,73 @@ $poppin-pink: #af1b5c; } .notification-indicator { - background: $poppin-pink; - border-radius: 50%; - width: 15px; - height: 15px; - margin-left: 5px; + color: $color-notification; + cursor: pointer; } .repository--nav--current-user--name { display: flex; align-items: center; + flex-direction: column; + } +} + +.popup { + position: fixed; + text-align: right; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 50%; + height: 50%; + max-width: 600px; + border: 1px solid $color-shadow; + border-radius: 10px; + background: $color-bg; + padding: 20px; + z-index: 1000; + box-shadow: 0 0 20px 0 $color-shadow; + display: none; + overflow-y: auto; /* Enable vertical scrolling */ + + .exit-button { + border: none; + text-align: right; + background: $color-bg; + border-radius: 6em; + font-size: 0.8cm; + padding-left: 0.5em; + padding-right: 0.5em; + padding-top: 0.3em; + padding-bottom: 0.3em; + margin-bottom: 0.5em; + cursor: pointer; + } + + .exit-button:hover { + color: $color-notification; } } +.popup.active { + display: block; +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: $color-shadow; + z-index: 999; + display: none; +} + +.overlay.active { + display: block; +} + // if the last child is a link (login link instead of logged in user avatar) // space it from the right side of the window .repository--nav--links--link:last-child { diff --git a/packages/app/obojobo-repository/shared/components/repository-nav.test.js b/packages/app/obojobo-repository/shared/components/repository-nav.test.js index 46f61cd20f..a15eff0840 100644 --- a/packages/app/obojobo-repository/shared/components/repository-nav.test.js +++ b/packages/app/obojobo-repository/shared/components/repository-nav.test.js @@ -1,6 +1,11 @@ +jest.mock('./notification', () => props => { + return {props.children} +}) + import React from 'react' import RepositoryNav from './repository-nav' import { create, act } from 'react-test-renderer' +import Notification from './notification' describe('RepositoryNav', () => { let navProps @@ -14,6 +19,13 @@ describe('RepositoryNav', () => { displayName: 'Display Name', userPerms: [] } + Object.defineProperty(document, 'cookie', { + value: '', + writable: true + }) + }) + afterEach(() => { + jest.resetAllMocks() }) const expectMenuToBeOpen = component => { @@ -29,6 +41,12 @@ describe('RepositoryNav', () => { }).length ).toBe(1) } + const expectNotificationsPopupToBeOpen = component => { + expect(component.root.findAllByProps({ className: 'popup active' }).length).toBe(1) + } + const expectNotificationsPopupToBeClosed = component => { + expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(0) + } // default props.userId = 0 means there is no user logged in test('renders correctly with standard expected props but no logged in user', () => { @@ -169,6 +187,7 @@ describe('RepositoryNav', () => { }) expectMenuToBeClosed(component) }) + test('loads notifications from cookies on mount', () => { document.cookie = 'notifications=' + @@ -182,6 +201,7 @@ describe('RepositoryNav', () => { 'notifications=[{"key":1,"text":"Test Notification","title":"Test Title"}]' ) }) + test('renders null when document.cookie is null', () => { const originalDocument = document.cookie document.cookie = null @@ -201,48 +221,94 @@ describe('RepositoryNav', () => { } document.cookie = originalDocument }) + test('toggles notifications popup on button click', () => { + document.cookie = + 'notifications=' + + JSON.stringify([ + { key: 1, text: 'Notification1', title: 'Title1' }, + { key: 2, text: 'Notification2', title: 'Title2' } + ]) + const component = create() - const notificationsButton = component.root.findByProps({ - className: 'repository--nav--current-user--name' + act(() => { + component.update() }) - expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(0) + const mockClickEvent = { preventDefault: jest.fn() } act(() => { - notificationsButton.props.onClick() + component.root + .findByProps({ className: 'repository--nav--current-user--name' }) + .children[1].props.onClick(mockClickEvent) component.update() }) - expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(1) + expectNotificationsPopupToBeOpen(component) act(() => { - notificationsButton.props.onClick() + component.root.findByProps({ className: 'exit-button' }).props.onClick() component.update() }) - expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(0) + expectNotificationsPopupToBeClosed(component) }) - test('renders notifications indicator when notificationsExist is true', () => { - const navProps = { - userId: 99, - displayName: 'Display Name', - userPerms: [], - notificationsExist: true - } + test('handles notification data from Notification component', () => { + document.cookie = + 'notifications=' + + JSON.stringify([ + { key: 1, text: 'Notification1', title: 'Title1' }, + { key: 2, text: 'Notification2', title: 'Title2' } + ]) + const component = create() - const notificationsIndicator = component.root.findAllByProps({ - className: 'notification-indicator' + act(() => { + component.update() }) - expect(navProps.notificationsExist).toBe(true) - expect(notificationsIndicator).toBeTruthy() - }) - test('does not render notifications indicator when notificationsExist is false', () => { - const component = create() + const mockClickEvent = { preventDefault: jest.fn() } - const notificationsIndicators = component.root.findAllByProps({ - className: 'notification-indicator' + act(() => { + component.root + .findByProps({ className: 'repository--nav--current-user--name' }) + .children[1].props.onClick(mockClickEvent) + component.update() + }) + + const notificationComponentInstance = component.root.findByType(Notification) + + // Manually call the onDataFromNotification prop with some test data, this normally happens notificationIsOpen is true and Notifications are rendered + act(() => { + notificationComponentInstance.props.onDataFromNotification(5) + }) + + expect( + component.root.findByProps({ className: 'repository--nav--current-user--name' }).children[1] + .props.children[1] + ).toBe(5) + }) + test('renders null when there are no notifications but document.cookie is not null', () => { + const reusableComponent = + let component + act(() => { + component = create(reusableComponent) }) - expect(notificationsIndicators).not.toBe() + const tree = component.toJSON() + expect(tree).toMatchSnapshot() + + if (document && document.cookie) { + const cookiePropsRaw = decodeURIComponent(document.cookie).split(';') + + cookiePropsRaw.forEach(c => { + const parts = c.trim().split('=') + + if (parts[0] === 'notifications') { + //don't get here + } else { + expect(parts[0]).not.toBe('notifications') + } + }) + } else { + expect(document.cookie).toBe('') + } }) })