From 8ec51cc5031467148a06328a163e2aafe22dcba9 Mon Sep 17 00:00:00 2001 From: Rachel Dauns Date: Thu, 23 Feb 2023 10:30:08 -0500 Subject: [PATCH 01/44] migration files --- ...0826-modify-users-add-last-login-column.js | 31 ++++++++++ .../20230220134045-create-notification.js | 61 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 packages/app/obojobo-express/server/migrations/20221214210826-modify-users-add-last-login-column.js create mode 100644 packages/app/obojobo-express/server/migrations/20230220134045-create-notification.js diff --git a/packages/app/obojobo-express/server/migrations/20221214210826-modify-users-add-last-login-column.js b/packages/app/obojobo-express/server/migrations/20221214210826-modify-users-add-last-login-column.js new file mode 100644 index 0000000000..f02c559fc9 --- /dev/null +++ b/packages/app/obojobo-express/server/migrations/20221214210826-modify-users-add-last-login-column.js @@ -0,0 +1,31 @@ +'use strict' + +var dbm +var type +var seed + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function(options, seedLink) { + dbm = options.dbmigrate + type = dbm.dataType + seed = seedLink +} + +exports.up = function(db) { + return db.addColumn('users', 'last_login', { + type: 'timestamp WITH TIME ZONE', + notNull: true, + defaultValue: new String('now()') + }) +} + +exports.down = function(db) { + return null +} + +exports._meta = { + version: 1 +} diff --git a/packages/app/obojobo-express/server/migrations/20230220134045-create-notification.js b/packages/app/obojobo-express/server/migrations/20230220134045-create-notification.js new file mode 100644 index 0000000000..3882408689 --- /dev/null +++ b/packages/app/obojobo-express/server/migrations/20230220134045-create-notification.js @@ -0,0 +1,61 @@ +'use strict' + +var dbm +var type +var seed + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + * yarn db:migrateup + */ +exports.setup = function(options, seedLink) { + dbm = options.dbmigrate + type = dbm.dataType + seed = seedLink +} + +exports.up = function(db) { + return db + .createTable('notification_status', { + id: { + type: 'bigserial', + primaryKey: true, + notNull: true + }, + created_at: { + type: 'timestamp WITH TIME ZONE', + notNull: true, + defaultValue: new String('now()') + }, + user_id: { type: 'bigint', notNull: true }, + draft_id: { type: 'UUID', notNull: true }, + text: { type: 'string', notNull: true }, + title: { type: 'string', notNull: true }, + status: { type: 'boolean', notNull: true } + }) + + .then(result => { + return db.addIndex('notification_status', 'note_user_id_index', ['user_id']) + }) + .then(result => { + return db.addIndex('notification_status', 'note_draft_id_index', ['draft_id']) + }) + .then(() => + db.addIndex( + 'notification_status', + 'user_draft_unique_user', + ['user_id', 'draft_id'], + + true + ) + ) +} + +exports.down = function(db) { + db.dropTable('notification_status') +} + +exports._meta = { + version: 1 +} From de8b0550383212b699ec4217afbc01f521843a36 Mon Sep 17 00:00:00 2001 From: Rachel Dauns Date: Thu, 23 Feb 2023 10:33:18 -0500 Subject: [PATCH 02/44] display notification in nav --- .../src/scripts/viewer/components/nav.js | 24 +++++++++++++++++- .../src/scripts/viewer/components/nav.scss | 6 +++++ .../scripts/viewer/components/notification.js | 25 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 packages/app/obojobo-document-engine/src/scripts/viewer/components/notification.js diff --git a/packages/app/obojobo-document-engine/src/scripts/viewer/components/nav.js b/packages/app/obojobo-document-engine/src/scripts/viewer/components/nav.js index 377d7c8016..1648d470af 100644 --- a/packages/app/obojobo-document-engine/src/scripts/viewer/components/nav.js +++ b/packages/app/obojobo-document-engine/src/scripts/viewer/components/nav.js @@ -1,4 +1,5 @@ import './nav.scss' +import './notification.scss' import Common from '../../common' import FocusUtil from '../util/focus-util' @@ -6,6 +7,7 @@ import Logo from './logo' import NavUtil from '../util/nav-util' import AssessmentUtil from '../util/assessment-util' import React from 'react' +import Notification from './notification.js' const { Button } = Common.components const { StyleableText } = Common.text @@ -26,6 +28,7 @@ export default class Nav extends React.Component { this.selfRef = React.createRef() this.hideOrShowOnResize = this.hideOrShowOnResize.bind(this) this.closeNavOnMobile = this.closeNavOnMobile.bind(this) + this.onClickExitNotification = this.onClickExitNotification.bind(this) } isMobileSize() { @@ -222,16 +225,30 @@ export default class Nav extends React.Component { } } + onClickExitNotification() { + const isNotificationEnabled = NavUtil.isNotificationEnabled(this.props.navState) + //console.log("is noteification enabled in nav b4: " + isNotificationEnabled) + NavUtil.setNotificationStatus(!isNotificationEnabled) + //console.log("is noteification enabled in nav after: " + isNotificationEnabled) + } + render() { const navState = this.props.navState const list = NavUtil.getOrderedList(navState) const lockEl = this.getLockEl(navState.locked) const isNavInaccessible = navState.disabled || !navState.open + + const isNotificationEnabled = NavUtil.isNotificationEnabled(this.props.navState) + const notificationTitle = NavUtil.getNotificationTitle(this.props.navState) + const notificationText = NavUtil.getNotificationText(this.props.navState) + //console.log("title: " + notificationTitle + " plus text: " + notificationText + " plus status: " + isNotificationEnabled) + const className = 'viewer--components--nav' + isOrNot(navState.locked, 'locked') + isOrNot(navState.open, 'open') + - isOrNot(!navState.disabled, 'enabled') + isOrNot(!navState.disabled, 'enabled') + + isOrNot(isNotificationEnabled, 'notification-enabled') return ( + +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+ +
+
`; @@ -153,6 +176,29 @@ exports[`RepositoryNav loads notifications from cookies on mount 1`] = ` + +
+ +
+
+ +
+
`; @@ -231,6 +277,29 @@ exports[`RepositoryNav renders correctly with standard expected props 1`] = ` + +
+ +
+
+ +
+
`; @@ -272,6 +341,29 @@ exports[`RepositoryNav renders correctly with standard expected props but no log + +
+ +
+
+ +
+
`; @@ -350,6 +442,29 @@ exports[`RepositoryNav renders null when document.cookie is null 1`] = ` + +
+ +
+
+ +
+
`; @@ -428,6 +543,29 @@ exports[`RepositoryNav renders null when there are no notifications but document + +
+ +
+
+ +
+
`; @@ -515,5 +653,28 @@ exports[`RepositoryNav renders stats section with canViewStatsPage 1`] = ` + +
+ +
+
+ +
+
`; diff --git a/packages/app/obojobo-repository/shared/components/__snapshots__/stats.test.js.snap b/packages/app/obojobo-repository/shared/components/__snapshots__/stats.test.js.snap index c276ed5fa3..2208c80cf3 100644 --- a/packages/app/obojobo-repository/shared/components/__snapshots__/stats.test.js.snap +++ b/packages/app/obojobo-repository/shared/components/__snapshots__/stats.test.js.snap @@ -79,6 +79,37 @@ exports[`Stats Renders "modules loaded" state correctly 1`] = ` + +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
{ } const expectDialogToBeRendered = (component, dialogComponent, title) => { - expect(ReactModal.setAppElement).toHaveBeenCalledTimes(1) - expect(component.root.findByType(ReactModal).props.contentLabel).toBe(title) + expect(ReactModal.setAppElement).toHaveBeenCalledTimes(2) + const modal = component.root.findByProps({ contentLabel: title }) + expect(modal).toBeDefined() expect(component.root.findAllByType(dialogComponent).length).toBe(1) + const modalNotifications = component.root.findByProps({ contentLabel: 'Notifications' }) + expect(modalNotifications).toBeDefined() } const expectMethodToBeCalledOnceWith = (method, calledWith = []) => { @@ -487,8 +490,9 @@ describe('Dashboard', () => { expect(moduleComponents[3].props.draftId).toBe('mockDraftId') expect(moduleComponents[4].props.draftId).toBe('mockDraftId4') - // Shouldn't be any modal dialogs open, either - expect(component.root.findAllByType(ReactModal).length).toBe(0) + // Shouldn't be any modal dialogs open other than notifications, either + expect(component.root.findAllByType(ReactModal).length).toBe(1) + expect(component.root.findAllByType(ReactModal)[0].props.contentLabel).toBe('Notifications') return component } @@ -2176,14 +2180,21 @@ describe('Dashboard', () => { expectMethodToBeCalledOnceWith(dashboardProps.closeModal) }) - test('renders no dialogs if props.dialog value is unsupported', () => { + test('renders only the notification modal if props.dialog value is unsupported', () => { dashboardProps.dialog = 'some-unsupported-value' let component act(() => { component = create() }) - expect(component.root.findAllByType(ReactModal).length).toBe(0) + const modalInstances = component.root.findAllByType(ReactModal) + + const notificationModals = modalInstances.filter(instance => + instance.findByProps({ contentLabel: 'Notifications' }) + ) + + expect(component.root.findAllByType(ReactModal).length).toBe(1) + expect(notificationModals.length).toBe(1) component.unmount() }) diff --git a/packages/app/obojobo-repository/shared/components/pages/__snapshots__/page-error.test.js.snap b/packages/app/obojobo-repository/shared/components/pages/__snapshots__/page-error.test.js.snap index 52d1b40ac6..8f0c4a949c 100644 --- a/packages/app/obojobo-repository/shared/components/pages/__snapshots__/page-error.test.js.snap +++ b/packages/app/obojobo-repository/shared/components/pages/__snapshots__/page-error.test.js.snap @@ -112,6 +112,37 @@ exports[`PageError renders when given props 1`] = `
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
+ +
+ +
+
+
+

+ That's all for now +

+
+
+
props => { + return +}) import React from 'react' import renderer from 'react-test-renderer' +import ReactModal from 'react-modal' mockStaticDate() @@ -7,6 +11,8 @@ mockStaticDate() const PageError = require('./page-error') describe('PageError', () => { + ReactModal.setAppElement = jest.fn() + test('renders when given props', () => { const mockCurrentUser = { id: 99, diff --git a/packages/app/obojobo-repository/shared/components/pages/page-homepage.test.js b/packages/app/obojobo-repository/shared/components/pages/page-homepage.test.js index a7deb0aa9d..542f0a8532 100644 --- a/packages/app/obojobo-repository/shared/components/pages/page-homepage.test.js +++ b/packages/app/obojobo-repository/shared/components/pages/page-homepage.test.js @@ -1,5 +1,9 @@ +jest.mock('react-modal', () => props => { + return +}) import React from 'react' import renderer from 'react-test-renderer' +import ReactModal from 'react-modal' mockStaticDate() @@ -7,6 +11,8 @@ mockStaticDate() const PageHomepage = require('./page-homepage') describe('PageHomepage', () => { + ReactModal.setAppElement = jest.fn() + test('renders when given props', () => { const mockCurrentUser = { id: 99, diff --git a/packages/app/obojobo-repository/shared/components/pages/page-login.test.js b/packages/app/obojobo-repository/shared/components/pages/page-login.test.js index 91a17f96f7..75e2f5c8c6 100644 --- a/packages/app/obojobo-repository/shared/components/pages/page-login.test.js +++ b/packages/app/obojobo-repository/shared/components/pages/page-login.test.js @@ -1,5 +1,9 @@ +jest.mock('react-modal', () => props => { + return +}) import React from 'react' import renderer from 'react-test-renderer' +import ReactModal from 'react-modal' mockStaticDate() @@ -7,6 +11,8 @@ mockStaticDate() const PageLogin = require('./page-login') describe('PageLogin', () => { + ReactModal.setAppElement = jest.fn() + test('renders when given props', () => { const mockCurrentUser = { id: 99, diff --git a/packages/app/obojobo-repository/shared/components/pages/page-module.test.js b/packages/app/obojobo-repository/shared/components/pages/page-module.test.js index 57c61106f5..dca534e4e5 100644 --- a/packages/app/obojobo-repository/shared/components/pages/page-module.test.js +++ b/packages/app/obojobo-repository/shared/components/pages/page-module.test.js @@ -1,9 +1,13 @@ jest.mock('../../api-util') jest.mock('dayjs') +jest.mock('react-modal', () => props => { + return +}) import React from 'react' import PageModule from './page-module' import { create, act } from 'react-test-renderer' +import ReactModal from 'react-modal' const Button = require('../button') const ButtonLink = require('../button-link') @@ -23,6 +27,7 @@ describe('PageModule', () => { fromNow: () => 'A long time ago' })) dayjs.extend = jest.fn() + ReactModal.setAppElement = jest.fn() }) beforeEach(() => { diff --git a/packages/app/obojobo-repository/shared/components/repository-nav.jsx b/packages/app/obojobo-repository/shared/components/repository-nav.jsx index 9b5b2ef21d..acb5cb2555 100644 --- a/packages/app/obojobo-repository/shared/components/repository-nav.jsx +++ b/packages/app/obojobo-repository/shared/components/repository-nav.jsx @@ -3,12 +3,14 @@ require('./repository-nav.scss') const React = require('react') const Avatar = require('./avatar') const Notification = require('./notification') +const ReactModal = require('react-modal') const RepositoryNav = props => { let timeOutId const [isMenuOpen, setMenuOpen] = React.useState(false) const [isNotificationsOpen, setNotificationsOpen] = React.useState(false) const [numberNotifications, setNumberNotifications] = React.useState(0) + ReactModal.setAppElement('#react-hydrate-root') const handleNotificationsData = numberOfNotificationsData => { // Handle the data received from the Notification component @@ -53,8 +55,6 @@ const RepositoryNav = props => { if (parsedValue && parsedValue.length >= 1) { setNumberNotifications(parsedValue.length) } - } else { - // there is nothing to update } }, []) @@ -117,21 +117,23 @@ const RepositoryNav = props => {
)} - {isNotificationsOpen && ( -
-
-
- -
-
- -
-
-
+ + +
+ +
+
+
- )} +
) } diff --git a/packages/app/obojobo-repository/shared/components/repository-nav.scss b/packages/app/obojobo-repository/shared/components/repository-nav.scss index 2cc9acdb14..5c17efd0e8 100644 --- a/packages/app/obojobo-repository/shared/components/repository-nav.scss +++ b/packages/app/obojobo-repository/shared/components/repository-nav.scss @@ -192,11 +192,6 @@ 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) 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 0fca260c47..f0813f223d 100644 --- a/packages/app/obojobo-repository/shared/components/repository-nav.test.js +++ b/packages/app/obojobo-repository/shared/components/repository-nav.test.js @@ -1,15 +1,22 @@ jest.mock('./notification', () => props => { return {props.children} }) +jest.mock('react-modal', () => props => { + return +}) import React from 'react' import RepositoryNav from './repository-nav' import { create, act } from 'react-test-renderer' import Notification from './notification' +import ReactModal from 'react-modal' describe('RepositoryNav', () => { let navProps + beforeAll(() => { + ReactModal.setAppElement = jest.fn() + }) beforeEach(() => { jest.resetAllMocks() jest.useFakeTimers() @@ -27,7 +34,6 @@ describe('RepositoryNav', () => { afterEach(() => { jest.resetAllMocks() }) - const expectMenuToBeOpen = component => { expect( component.root.findAllByProps({ className: 'repository--nav--current-user--menu is-open' }) @@ -42,10 +48,22 @@ describe('RepositoryNav', () => { ).toBe(1) } const expectNotificationsPopupToBeOpen = component => { - expect(component.root.findAllByProps({ className: 'popup active' }).length).toBe(1) + //expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(1) + const modalInstances = component.root.findAllByType(ReactModal) + + const modalInstance = modalInstances.find(instance => + instance.findByProps({ contentLabel: 'Notifications' }) + ) + expect(modalInstance.props.isOpen).toBe(true) } const expectNotificationsPopupToBeClosed = component => { - expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(0) + //expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(0) + const modalInstances = component.root.findAllByType(ReactModal) + + const modalInstance = modalInstances.find(instance => + instance.findByProps({ contentLabel: 'Notifications' }) + ) + expect(modalInstance.props.isOpen).toBe(false) } // default props.userId = 0 means there is no user logged in diff --git a/packages/app/obojobo-repository/shared/components/stats.test.js b/packages/app/obojobo-repository/shared/components/stats.test.js index 633a4a2eaa..2cdf61f73a 100644 --- a/packages/app/obojobo-repository/shared/components/stats.test.js +++ b/packages/app/obojobo-repository/shared/components/stats.test.js @@ -1,3 +1,6 @@ +jest.mock('react-modal', () => props => { + return +}) import React from 'react' import { create, act } from 'react-test-renderer' import Button from './button' @@ -5,6 +8,7 @@ import Button from './button' import Stats from './stats' import AssessmentStats from './stats/assessment-stats' import DataGridDrafts from './stats/data-grid-drafts' +import ReactModal from 'react-modal' jest.mock('react-data-table-component', () => ({ default: props => ( @@ -35,7 +39,9 @@ describe('Stats', () => { revisionCount: 1 } ] - + beforeAll(() => { + ReactModal.setAppElement = jest.fn() + }) beforeEach(() => { jest.resetAllMocks() }) From d758ecfb56dc40120a0d1b0aedb0d198c234c0b6 Mon Sep 17 00:00:00 2001 From: Rachel Dauns Date: Tue, 30 Apr 2024 12:24:49 -0400 Subject: [PATCH 40/44] remove comments --- .../shared/components/pages/page-error.test.js | 1 - .../shared/components/pages/page-homepage.test.js | 1 - .../shared/components/pages/page-login.test.js | 1 - .../obojobo-repository/shared/components/repository-nav.test.js | 2 -- 4 files changed, 5 deletions(-) diff --git a/packages/app/obojobo-repository/shared/components/pages/page-error.test.js b/packages/app/obojobo-repository/shared/components/pages/page-error.test.js index a1d06fd1b5..83b17b4143 100644 --- a/packages/app/obojobo-repository/shared/components/pages/page-error.test.js +++ b/packages/app/obojobo-repository/shared/components/pages/page-error.test.js @@ -12,7 +12,6 @@ const PageError = require('./page-error') describe('PageError', () => { ReactModal.setAppElement = jest.fn() - test('renders when given props', () => { const mockCurrentUser = { id: 99, diff --git a/packages/app/obojobo-repository/shared/components/pages/page-homepage.test.js b/packages/app/obojobo-repository/shared/components/pages/page-homepage.test.js index 542f0a8532..fe695b106f 100644 --- a/packages/app/obojobo-repository/shared/components/pages/page-homepage.test.js +++ b/packages/app/obojobo-repository/shared/components/pages/page-homepage.test.js @@ -12,7 +12,6 @@ const PageHomepage = require('./page-homepage') describe('PageHomepage', () => { ReactModal.setAppElement = jest.fn() - test('renders when given props', () => { const mockCurrentUser = { id: 99, diff --git a/packages/app/obojobo-repository/shared/components/pages/page-login.test.js b/packages/app/obojobo-repository/shared/components/pages/page-login.test.js index 75e2f5c8c6..2c6578c941 100644 --- a/packages/app/obojobo-repository/shared/components/pages/page-login.test.js +++ b/packages/app/obojobo-repository/shared/components/pages/page-login.test.js @@ -12,7 +12,6 @@ const PageLogin = require('./page-login') describe('PageLogin', () => { ReactModal.setAppElement = jest.fn() - test('renders when given props', () => { const mockCurrentUser = { id: 99, 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 f0813f223d..8b93671793 100644 --- a/packages/app/obojobo-repository/shared/components/repository-nav.test.js +++ b/packages/app/obojobo-repository/shared/components/repository-nav.test.js @@ -48,7 +48,6 @@ describe('RepositoryNav', () => { ).toBe(1) } const expectNotificationsPopupToBeOpen = component => { - //expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(1) const modalInstances = component.root.findAllByType(ReactModal) const modalInstance = modalInstances.find(instance => @@ -57,7 +56,6 @@ describe('RepositoryNav', () => { expect(modalInstance.props.isOpen).toBe(true) } const expectNotificationsPopupToBeClosed = component => { - //expect(component.root.findAllByProps({ className: 'popup' }).length).toBe(0) const modalInstances = component.root.findAllByType(ReactModal) const modalInstance = modalInstances.find(instance => From 5dc37a8f1c9d7967852b3eaaaaf9a491b9ce0d70 Mon Sep 17 00:00:00 2001 From: Rachel Dauns Date: Mon, 6 May 2024 16:00:57 -0400 Subject: [PATCH 41/44] migrations regenerated and unit tests updated --- ...0506192834-modify-users-add-last-login.js} | 2 +- ...240506193547-create-notification-table.js} | 3 +- .../shared/components/notification.test.js | 37 ++++++++----------- .../shared/components/repository-nav.test.js | 35 +++++++++--------- 4 files changed, 35 insertions(+), 42 deletions(-) rename packages/app/obojobo-express/server/migrations/{20240130162639-modify-users-add-last-login.js => 20240506192834-modify-users-add-last-login.js} (91%) rename packages/app/obojobo-express/server/migrations/{20240130163052-create-notification-table.js => 20240506193547-create-notification-table.js} (93%) diff --git a/packages/app/obojobo-express/server/migrations/20240130162639-modify-users-add-last-login.js b/packages/app/obojobo-express/server/migrations/20240506192834-modify-users-add-last-login.js similarity index 91% rename from packages/app/obojobo-express/server/migrations/20240130162639-modify-users-add-last-login.js rename to packages/app/obojobo-express/server/migrations/20240506192834-modify-users-add-last-login.js index 4536813b7c..1c1adbca61 100644 --- a/packages/app/obojobo-express/server/migrations/20240130162639-modify-users-add-last-login.js +++ b/packages/app/obojobo-express/server/migrations/20240506192834-modify-users-add-last-login.js @@ -23,7 +23,7 @@ exports.up = function(db) { } exports.down = function(db) { - return db.removeColumn('last_login') + return db.removeColumn('users', 'last_login') } exports._meta = { diff --git a/packages/app/obojobo-express/server/migrations/20240130163052-create-notification-table.js b/packages/app/obojobo-express/server/migrations/20240506193547-create-notification-table.js similarity index 93% rename from packages/app/obojobo-express/server/migrations/20240130163052-create-notification-table.js rename to packages/app/obojobo-express/server/migrations/20240506193547-create-notification-table.js index de42979476..7e3c52d261 100644 --- a/packages/app/obojobo-express/server/migrations/20240130163052-create-notification-table.js +++ b/packages/app/obojobo-express/server/migrations/20240506193547-create-notification-table.js @@ -7,7 +7,6 @@ var seed /** * We receive the dbmigrate dependency from dbmigrate initially. * This enables us to not have to rely on NODE_PATH. - * yarn db:migrateup */ exports.setup = function(options, seedLink) { dbm = options.dbmigrate @@ -33,7 +32,7 @@ exports.up = function(db) { } exports.down = function(db) { - db.dropTable('notifications') + return db.dropTable('notifications') } exports._meta = { diff --git a/packages/app/obojobo-repository/shared/components/notification.test.js b/packages/app/obojobo-repository/shared/components/notification.test.js index 121273a868..b99bdef8a5 100644 --- a/packages/app/obojobo-repository/shared/components/notification.test.js +++ b/packages/app/obojobo-repository/shared/components/notification.test.js @@ -18,27 +18,25 @@ describe('Notification component', () => { test('loads notifications from cookies on mount', () => { const onDataFromNotification = jest.fn() - document.cookie = - 'notifications=' + - JSON.stringify([{ key: 1, text: 'Test Notification', title: 'Test Title' }]) + const notificationValue = [{ key: 1, text: 'Test Notification', title: 'Test Title' }] + document.cookie = `notifications=${JSON.stringify(notificationValue)}` const component = create() const tree = component.toJSON() expect(tree).toMatchSnapshot() expect(document.cookie).toBe( - 'notifications=%5B%7B%22key%22%3A1%2C%22text%22%3A%22Test%20Notification%22%2C%22title%22%3A%22Test%20Title%22%7D%5D;' + `notifications=${encodeURIComponent(JSON.stringify(notificationValue))};` ) }) test('handles click on exit button and updates state and cookie', () => { const onDataFromNotification = jest.fn() - document.cookie = - 'notifications=' + - JSON.stringify([ - { key: 1, text: 'Notification1', title: 'Title1' }, - { key: 2, text: 'Notification2', title: 'Title2' } - ]) + const notificationValue = [ + { key: 1, text: 'Notification1', title: 'Title1' }, + { key: 2, text: 'Notification2', title: 'Title2' } + ] + document.cookie = `notifications=${JSON.stringify(notificationValue)}` const reusableComponent = let component @@ -58,11 +56,10 @@ describe('Notification component', () => { exitButtons[key].props.onClick() }) - // Update the tree after state changes tree = component.toJSON() expect(tree).toMatchSnapshot() expect(document.cookie).toBe( - 'notifications=%5B%7B%22key%22%3A2%2C%22text%22%3A%22Notification2%22%2C%22title%22%3A%22Title2%22%7D%5D;' + `notifications=${encodeURIComponent(JSON.stringify(notificationValue))};` ) } }) @@ -136,21 +133,19 @@ describe('Notification component', () => { const tree = component.toJSON() expect(tree).toMatchSnapshot() - if (document && document.cookie) { - //don't get here - } else { - expect(document.cookie).toBe(null) - } + //expect(document.cookie).toBe(null) + document.cookie = originalDocument }) test('does not update cookie when there are no notifications', () => { const onDataFromNotification = jest.fn() - // Ensure notifications state is empty + const notificationValue = [] + document.cookie = `notifications=${JSON.stringify(notificationValue)}` + const component = create() const tree = component.toJSON() - expect(tree).toMatchSnapshot() - // The useEffect should not update the cookie since notifications is empty - expect(document.cookie).toBe('notifications=%5B%5D;') + expect(tree).toMatchSnapshot() + expect(document.cookie).toBe(`notifications=${JSON.stringify(notificationValue)}`) }) }) 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 8b93671793..faa456938e 100644 --- a/packages/app/obojobo-repository/shared/components/repository-nav.test.js +++ b/packages/app/obojobo-repository/shared/components/repository-nav.test.js @@ -230,11 +230,9 @@ describe('RepositoryNav', () => { const tree = component.toJSON() expect(tree).toMatchSnapshot() - if (document && document.cookie) { - //don't get here - } else { - expect(document.cookie).toBe(null) - } + expect(document).not.toBeNull() + expect(document.cookie).toBe(null) + document.cookie = originalDocument }) @@ -305,26 +303,27 @@ describe('RepositoryNav', () => { test('renders null when there are no notifications but document.cookie is not null', () => { const reusableComponent = let component + let parsedValue + const notificationValue = 'otherrandomdata=otherrandomdata' + document.cookie = notificationValue act(() => { component = create(reusableComponent) }) const tree = component.toJSON() expect(tree).toMatchSnapshot() - if (document && document.cookie) { - const cookiePropsRaw = decodeURIComponent(document.cookie).split(';') + expect(document).not.toBeNull() + expect(document.cookie).not.toBeNull() - cookiePropsRaw.forEach(c => { - const parts = c.trim().split('=') + const cookiePropsRaw = decodeURIComponent(document.cookie).split(';') - if (parts[0] === 'notifications') { - //don't get here - } else { - expect(parts[0]).not.toBe('notifications') - } - }) - } else { - expect(document.cookie).toBe('') - } + cookiePropsRaw.forEach(c => { + const parts = c.trim().split('=') + + expect(parts[0]).not.toBe('notifications') + expect(parts[0] === 'notifications').toBe(false) + }) + expect(document.cookie).toBe(notificationValue) + expect(parsedValue).toBe(undefined) }) }) From 6ca28de9cb92f0d111d3539f21d52311cc0015fc Mon Sep 17 00:00:00 2001 From: Rachel Dauns Date: Tue, 7 May 2024 14:44:48 -0400 Subject: [PATCH 42/44] unit tests updated for notifications --- .../__snapshots__/notification.test.js.snap | 2 +- .../shared/components/notification.test.js | 58 ++++++++----------- .../shared/components/repository-nav.test.js | 16 ++--- 3 files changed, 31 insertions(+), 45 deletions(-) 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 fbedcada71..f5fe6a26aa 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 @@ -79,7 +79,7 @@ exports[`Notification component loads notifications from cookies on mount 1`] = `; -exports[`Notification component renders null when document.cookie is null 1`] = ` +exports[`Notification component renders nothing when document.cookie is null 1`] = `
diff --git a/packages/app/obojobo-repository/shared/components/notification.test.js b/packages/app/obojobo-repository/shared/components/notification.test.js index b99bdef8a5..3d048003bb 100644 --- a/packages/app/obojobo-repository/shared/components/notification.test.js +++ b/packages/app/obojobo-repository/shared/components/notification.test.js @@ -15,7 +15,21 @@ describe('Notification component', () => { const tree = component.toJSON() expect(tree).toMatchSnapshot() }) + test('renders nothing when document.cookie is null', () => { + const onDataFromNotification = jest.fn() + const originalDocument = document.cookie + Object.defineProperty(document, 'cookie', { value: null, writable: true }) + const reusableComponent = + let component + act(() => { + component = create(reusableComponent) + }) + const tree = component.toJSON() + expect(tree).toMatchSnapshot() + + document.cookie = originalDocument + }) test('loads notifications from cookies on mount', () => { const onDataFromNotification = jest.fn() const notificationValue = [{ key: 1, text: 'Test Notification', title: 'Test Title' }] @@ -25,9 +39,7 @@ describe('Notification component', () => { const tree = component.toJSON() expect(tree).toMatchSnapshot() - expect(document.cookie).toBe( - `notifications=${encodeURIComponent(JSON.stringify(notificationValue))};` - ) + expect(document.cookie).toBe(`notifications=${JSON.stringify(notificationValue)}`) }) test('handles click on exit button and updates state and cookie', () => { @@ -90,51 +102,29 @@ describe('Notification component', () => { tree = component.toJSON() expect(tree).toMatchSnapshot() - if (elementToExit) { - expect(elementToExit.style.display).toBe('undefined') - } + expect(elementToExit).toBe(undefined) }) test('renders null when there are no notifications but document.cookie is not null', () => { const onDataFromNotification = jest.fn() const reusableComponent = + const originalDocument = document.cookie let component + const cookieValue = 'otherrandomdata=otherrandomdata' + document.cookie = cookieValue act(() => { component = create(reusableComponent) }) const tree = component.toJSON() expect(tree).toMatchSnapshot() - if (document && document.cookie) { - const cookiePropsRaw = decodeURIComponent(document.cookie).split(';') + expect(document).not.toBeNull() + expect(document.cookie).not.toBeNull() - 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(undefined) - } - }) - test('renders null when document.cookie is null', () => { - const onDataFromNotification = jest.fn() - const originalDocument = document.cookie - document.cookie = null - - const reusableComponent = - let component - act(() => { - component = create(reusableComponent) - }) - const tree = component.toJSON() - expect(tree).toMatchSnapshot() + const cookiePropsRaw = decodeURIComponent(document.cookie).split(';') - //expect(document.cookie).toBe(null) + const parts = cookiePropsRaw[0].trim().split('=') + expect(parts[1]).toBe('undefined') document.cookie = originalDocument }) test('does not update cookie when there are no notifications', () => { 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 faa456938e..f6f60d3a62 100644 --- a/packages/app/obojobo-repository/shared/components/repository-nav.test.js +++ b/packages/app/obojobo-repository/shared/components/repository-nav.test.js @@ -205,17 +205,13 @@ describe('RepositoryNav', () => { }) test('loads notifications from cookies on mount', () => { - document.cookie = - 'notifications=' + - JSON.stringify([{ key: 1, text: 'Test Notification', title: 'Test Title' }]) - + const notificationValue = { key: 1, text: 'TestNotification', title: 'TestTitle' } + document.cookie = `notifications=${JSON.stringify(notificationValue)}` const component = create() const tree = component.toJSON() expect(tree).toMatchSnapshot() - expect(document.cookie).toBe( - 'notifications=[{"key":1,"text":"Test Notification","title":"Test Title"}]' - ) + expect(document.cookie).toBe(`notifications=${JSON.stringify(notificationValue)}`) }) test('renders null when document.cookie is null', () => { @@ -304,8 +300,8 @@ describe('RepositoryNav', () => { const reusableComponent = let component let parsedValue - const notificationValue = 'otherrandomdata=otherrandomdata' - document.cookie = notificationValue + const cookieValue = 'otherrandomdata=otherrandomdata' + document.cookie = cookieValue act(() => { component = create(reusableComponent) }) @@ -323,7 +319,7 @@ describe('RepositoryNav', () => { expect(parts[0]).not.toBe('notifications') expect(parts[0] === 'notifications').toBe(false) }) - expect(document.cookie).toBe(notificationValue) + expect(document.cookie).toBe(cookieValue) expect(parsedValue).toBe(undefined) }) }) From c37cc24c390f3226bebaad9436f226625a9f56f0 Mon Sep 17 00:00:00 2001 From: Rachel Dauns Date: Wed, 8 May 2024 11:36:33 -0400 Subject: [PATCH 43/44] notification unit tests for button click updated --- .../__snapshots__/notification.test.js.snap | 113 ++++++++++++++++-- .../shared/components/notification.test.js | 57 ++++----- 2 files changed, 133 insertions(+), 37 deletions(-) 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 f5fe6a26aa..405bdff0bf 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 @@ -14,13 +14,87 @@ exports[`Notification component does not update cookie when there are no notific exports[`Notification component handles click on exit button and updates state and cookie 1`] = `
-

- That's all for now -

+
+

+ Title1 +

+ +
+

+ Notification1 +

+
+
+
+

+ Title2 +

+ +
+

+ Notification2 +

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

+ Title2 +

+ +
+

+ Notification2 +

+
`; @@ -69,13 +143,32 @@ exports[`Notification component hides the notification on exit button click 2`] exports[`Notification component loads notifications from cookies on mount 1`] = `
-

- That's all for now -

+
+

+ Test Title +

+ +
+

+ Test Notification +

+
`; diff --git a/packages/app/obojobo-repository/shared/components/notification.test.js b/packages/app/obojobo-repository/shared/components/notification.test.js index 3d048003bb..2ccbd59688 100644 --- a/packages/app/obojobo-repository/shared/components/notification.test.js +++ b/packages/app/obojobo-repository/shared/components/notification.test.js @@ -35,20 +35,25 @@ describe('Notification component', () => { const notificationValue = [{ key: 1, text: 'Test Notification', title: 'Test Title' }] document.cookie = `notifications=${JSON.stringify(notificationValue)}` - const component = create() + const reusableComponent = + let component + act(() => { + component = create(reusableComponent) + }) + const tree = component.toJSON() expect(tree).toMatchSnapshot() - expect(document.cookie).toBe(`notifications=${JSON.stringify(notificationValue)}`) + expect(document.cookie).toBe( + `notifications=${encodeURIComponent(JSON.stringify(notificationValue))};` + ) }) - test('handles click on exit button and updates state and cookie', () => { + test('hides the notification on exit button click', () => { const onDataFromNotification = jest.fn() - const notificationValue = [ - { key: 1, text: 'Notification1', title: 'Title1' }, - { key: 2, text: 'Notification2', title: 'Title2' } - ] - document.cookie = `notifications=${JSON.stringify(notificationValue)}` + document.cookie = + 'notifications=' + + JSON.stringify([{ key: 1, text: 'Test Notification', title: 'Test Title' }]) const reusableComponent = let component @@ -59,28 +64,22 @@ describe('Notification component', () => { let tree = component.toJSON() expect(tree).toMatchSnapshot() - // Simulate click on exit button for the first notification - const key = 0 const exitButtons = component.root.findAllByProps({ className: 'notification-exit-button' }) - if (exitButtons[key]) { - act(() => { - exitButtons[key].props.onClick() - }) - - tree = component.toJSON() - expect(tree).toMatchSnapshot() - expect(document.cookie).toBe( - `notifications=${encodeURIComponent(JSON.stringify(notificationValue))};` - ) - } - }) + act(() => { + exitButtons[0].props.onClick() + }) - test('hides the notification on exit button click', () => { + tree = component.toJSON() + expect(tree).toMatchSnapshot() + }) + test('handles click on exit button and updates state and cookie', () => { const onDataFromNotification = jest.fn() - document.cookie = - 'notifications=' + - JSON.stringify([{ key: 1, text: 'Test Notification', title: 'Test Title' }]) + const notificationValue = [ + { key: 1, text: 'Notification1', title: 'Title1' }, + { key: 2, text: 'Notification2', title: 'Title2' } + ] + document.cookie = `notifications=${JSON.stringify(notificationValue)}` const elementToExit = document.getElementsByClassName('notification-exit-button')[0] const reusableComponent = @@ -99,11 +98,15 @@ describe('Notification component', () => { exitButtons[key].props.onClick() }) + const newNotificationValue = [{ key: 2, text: 'Notification2', title: 'Title2' }] tree = component.toJSON() expect(tree).toMatchSnapshot() - + expect(document.cookie).toBe( + `notifications=${encodeURIComponent(JSON.stringify(newNotificationValue))};` + ) expect(elementToExit).toBe(undefined) }) + test('renders null when there are no notifications but document.cookie is not null', () => { const onDataFromNotification = jest.fn() const reusableComponent = From ad3959946be0ff16f2232c099b32670e59d1aad6 Mon Sep 17 00:00:00 2001 From: Rachel Dauns Date: Thu, 9 May 2024 10:21:32 -0400 Subject: [PATCH 44/44] express-current-user test updated date mock --- .../__tests__/express_current_user.test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/app/obojobo-express/__tests__/express_current_user.test.js b/packages/app/obojobo-express/__tests__/express_current_user.test.js index 0f1fcca2ca..30caf390f2 100644 --- a/packages/app/obojobo-express/__tests__/express_current_user.test.js +++ b/packages/app/obojobo-express/__tests__/express_current_user.test.js @@ -234,6 +234,8 @@ describe('current user middleware', () => { req.currentUserId = mockUser.id req.currentUser = mockUser req.currentUser.lastLogin = mockUser.lastLogin + jest.useFakeTimers('modern') + jest.setSystemTime(new Date(2024, 3, 1)) //mock the date so that runtime does not affect the date/time const mockNotifications = [ { id: 1, title: 'Notification 1', text: 'Message 1' }, @@ -263,6 +265,8 @@ describe('current user middleware', () => { ) expect(req.currentUser.lastLogin).toStrictEqual(today) expect(viewerNotificationState.setLastLogin).toHaveBeenCalledWith(8, today) + + jest.useRealTimers() }) }) test('getNotifications returns empty when there are no notifications', async () => { @@ -274,6 +278,8 @@ describe('current user middleware', () => { req.currentUserId = mockUser.id req.currentUser = mockUser req.currentUser.lastLogin = mockUser.lastLogin + jest.useFakeTimers('modern') + jest.setSystemTime(new Date(2024, 3, 1)) //mock the date so that runtime does not affect the date/time viewerState.get.mockResolvedValueOnce(req.currentUserId) viewerNotificationState.getRecentNotifications.mockResolvedValueOnce(null) @@ -286,6 +292,8 @@ describe('current user middleware', () => { expect(res.cookie).not.toHaveBeenCalled() expect(req.currentUser.lastLogin).toStrictEqual(today) expect(viewerNotificationState.setLastLogin).toHaveBeenCalledWith(8, today) + + jest.useRealTimers() }) }) })