Revert "don’t count unread_count or dashcard indicators against newRelic load time"
This reverts commit aa977ecd2a
.
Reverting based on belief that this is surfacing errors in the build:
"something went wrong updating unread count" TypeError: Failed to fetch
Change-Id: I842ad732d75c6ca83f31e140ddf5edb6f10e45fe
Reviewed-on: https://gerrit.instructure.com/201108
Reviewed-by: Ryan Shaw <ryan@instructure.com>
QA-Review: Ryan Shaw <ryan@instructure.com>
Product-Review: Ryan Shaw <ryan@instructure.com>
Tested-by: Jenkins
This commit is contained in:
parent
bee1d53a77
commit
d754ff5ed1
|
@ -16,32 +16,34 @@
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import _ from 'underscore'
|
||||||
import createStore from '../shared/helpers/createStore'
|
import createStore from '../shared/helpers/createStore'
|
||||||
|
import $ from 'jquery'
|
||||||
|
|
||||||
const CourseActivitySummaryStore = createStore({streams: {}})
|
const CourseActivitySummaryStore = createStore({streams: {}})
|
||||||
|
|
||||||
CourseActivitySummaryStore.getStateForCourse = function(courseId) {
|
CourseActivitySummaryStore.getStateForCourse = function(courseId) {
|
||||||
if (typeof courseId === 'undefined') return CourseActivitySummaryStore.getState()
|
if (_.isUndefined(courseId)) return CourseActivitySummaryStore.getState()
|
||||||
|
|
||||||
const {streams} = CourseActivitySummaryStore.getState()
|
if (_.has(CourseActivitySummaryStore.getState().streams, courseId)) {
|
||||||
if (!(courseId in streams)) {
|
return CourseActivitySummaryStore.getState().streams[courseId]
|
||||||
streams[courseId] = {}
|
} else {
|
||||||
|
CourseActivitySummaryStore.getState().streams[courseId] = {}
|
||||||
CourseActivitySummaryStore._fetchForCourse(courseId)
|
CourseActivitySummaryStore._fetchForCourse(courseId)
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
return streams[courseId]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CourseActivitySummaryStore._fetchForCourse = function(courseId) {
|
CourseActivitySummaryStore._fetchForCourse = function(courseId) {
|
||||||
const fetch = window.fetchIgnoredByNewRelic || window.fetch // don't let this count against us in newRelic's SPA load time stats
|
let state
|
||||||
fetch(`/api/v1/courses/${courseId}/activity_stream/summary`, {
|
|
||||||
headers: {Accept: 'application/json'}
|
$.getJSON(`/api/v1/courses/${courseId}/activity_stream/summary`, stream => {
|
||||||
|
state = CourseActivitySummaryStore.getState()
|
||||||
|
state.streams[courseId] = {
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
CourseActivitySummaryStore.setState(state)
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
|
||||||
.then(stream => {
|
|
||||||
const state = CourseActivitySummaryStore.getState()
|
|
||||||
state.streams[courseId] = {stream}
|
|
||||||
CourseActivitySummaryStore.setState(state)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CourseActivitySummaryStore
|
export default CourseActivitySummaryStore
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2019 - present Instructure, Inc.
|
|
||||||
*
|
|
||||||
* This file is part of Canvas.
|
|
||||||
*
|
|
||||||
* Canvas is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU Affero General Public License as published by the Free
|
|
||||||
* Software Foundation, version 3 of the License.
|
|
||||||
*
|
|
||||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import CourseActivitySummaryStore from '../CourseActivitySummaryStore'
|
|
||||||
import wait from 'waait'
|
|
||||||
|
|
||||||
describe('CourseActivitySummaryStore', () => {
|
|
||||||
const stream = [
|
|
||||||
{
|
|
||||||
type: 'DiscussionTopic',
|
|
||||||
unread_count: 2,
|
|
||||||
count: 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'Conversation',
|
|
||||||
unread_count: 0,
|
|
||||||
count: 3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
beforeEach(() => {
|
|
||||||
CourseActivitySummaryStore.setState({streams: {}})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getStateForCourse', () => {
|
|
||||||
it('should return root state object when no courseId is provided', () => {
|
|
||||||
expect(CourseActivitySummaryStore.getStateForCourse().streams).toEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return empty object for course id not already in state', () => {
|
|
||||||
const spy = jest
|
|
||||||
.spyOn(CourseActivitySummaryStore, '_fetchForCourse')
|
|
||||||
.mockImplementation(() => {})
|
|
||||||
expect(CourseActivitySummaryStore.getStateForCourse(1)).toEqual({})
|
|
||||||
expect(spy).toHaveBeenCalled()
|
|
||||||
CourseActivitySummaryStore.setState({streams: {1: {stream}}})
|
|
||||||
expect(CourseActivitySummaryStore.getStateForCourse(1)).toEqual({stream})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('_fetchForCourse', () => {
|
|
||||||
it('populates state based on API response', async () => {
|
|
||||||
expect(CourseActivitySummaryStore.getState().streams[1]).toBeUndefined() // precondition
|
|
||||||
|
|
||||||
const spy = jest.spyOn(window, 'fetch').mockImplementation(() =>
|
|
||||||
Promise.resolve().then(() => ({
|
|
||||||
json: () => Promise.resolve().then(() => stream)
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
CourseActivitySummaryStore._fetchForCourse(1)
|
|
||||||
await wait(1)
|
|
||||||
expect(spy).toHaveBeenCalled()
|
|
||||||
expect(CourseActivitySummaryStore.getState()).toEqual({streams: {1: {stream}}})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -37,8 +37,6 @@ const ACTIVE_ROUTE_REGEX = /^\/(courses|groups|accounts|grades|calendar|conversa
|
||||||
const ACTIVE_CLASS = 'ic-app-header__menu-list-item--active'
|
const ACTIVE_CLASS = 'ic-app-header__menu-list-item--active'
|
||||||
|
|
||||||
const UNREAD_COUNT_POLL_INTERVAL = 60000 // 60 seconds
|
const UNREAD_COUNT_POLL_INTERVAL = 60000 // 60 seconds
|
||||||
const UNREAD_COUNT_ALLOWED_AGE = UNREAD_COUNT_POLL_INTERVAL / 2
|
|
||||||
const UNREAD_COUNT_SESSION_STORAGE_KEY = `unread_count_for_${window.ENV.current_user_id}`
|
|
||||||
|
|
||||||
const TYPE_URL_MAP = {
|
const TYPE_URL_MAP = {
|
||||||
courses: '/api/v1/users/self/favorites/courses?include[]=term&exclude[]=enrollments',
|
courses: '/api/v1/users/self/favorites/courses?include[]=term&exclude[]=enrollments',
|
||||||
|
@ -60,6 +58,7 @@ export default class Navigation extends React.Component {
|
||||||
courses: [],
|
courses: [],
|
||||||
help: [],
|
help: [],
|
||||||
unread_count: 0,
|
unread_count: 0,
|
||||||
|
unread_count_attempts: 0,
|
||||||
isTrayOpen: false,
|
isTrayOpen: false,
|
||||||
type: null,
|
type: null,
|
||||||
coursesLoading: false,
|
coursesLoading: false,
|
||||||
|
@ -114,23 +113,13 @@ export default class Navigation extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (
|
if (
|
||||||
!this.unread_count_attempts &&
|
!this.state.unread_count_attempts &&
|
||||||
window.ENV.current_user_id &&
|
window.ENV.current_user_id &&
|
||||||
!window.ENV.current_user_disabled_inbox &&
|
!window.ENV.current_user_disabled_inbox &&
|
||||||
this.unreadCountElement() &&
|
this.unreadCountElement().length &&
|
||||||
!(window.ENV.current_user && window.ENV.current_user.fake_student)
|
!(window.ENV.current_user && window.ENV.current_user.fake_student)
|
||||||
) {
|
) {
|
||||||
let msUntilIShouldStartPolling = 0
|
this.pollUnreadCount()
|
||||||
const saved = sessionStorage.getItem(UNREAD_COUNT_SESSION_STORAGE_KEY)
|
|
||||||
if (saved) {
|
|
||||||
const {updatedAt, unread_count} = JSON.parse(saved)
|
|
||||||
const millisecondsSinceLastUpdate = new Date() - updatedAt
|
|
||||||
if (millisecondsSinceLastUpdate < UNREAD_COUNT_ALLOWED_AGE) {
|
|
||||||
this.updateUnreadCount(unread_count)
|
|
||||||
msUntilIShouldStartPolling = UNREAD_COUNT_ALLOWED_AGE - millisecondsSinceLastUpdate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTimeout(this.pollUnreadCount.bind(this), msUntilIShouldStartPolling)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,36 +170,26 @@ export default class Navigation extends React.Component {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async pollUnreadCount() {
|
pollUnreadCount() {
|
||||||
this.unread_count_attempts = (this.unread_count_attempts || 0) + 1
|
this.setState({unread_count_attempts: this.state.unread_count_attempts + 1}, function() {
|
||||||
if (this.unread_count_attempts > 5) return
|
if (this.state.unread_count_attempts <= 5) {
|
||||||
|
$.ajax('/api/v1/conversations/unread_count')
|
||||||
// don't let this count against us in newRelic's SPA load time stats
|
.then(data => this.updateUnreadCount(data.unread_count))
|
||||||
const fetch = window.fetchIgnoredByNewRelic || window.fetch
|
.then(null, console.log.bind(console, 'something went wrong updating unread count'))
|
||||||
|
.always(() =>
|
||||||
try {
|
setTimeout(
|
||||||
const {unread_count} = await (await fetch('/api/v1/conversations/unread_count', {
|
() => this.pollUnreadCount(),
|
||||||
headers: {Accept: 'application/json'}
|
this.state.unread_count_attempts * UNREAD_COUNT_POLL_INTERVAL
|
||||||
})).json()
|
)
|
||||||
|
)
|
||||||
sessionStorage.setItem(
|
}
|
||||||
UNREAD_COUNT_SESSION_STORAGE_KEY,
|
})
|
||||||
JSON.stringify({
|
|
||||||
updatedAt: +new Date(),
|
|
||||||
unread_count
|
|
||||||
})
|
|
||||||
)
|
|
||||||
this.updateUnreadCount(unread_count)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('something went wrong updating unread count', error)
|
|
||||||
}
|
|
||||||
setTimeout(this.pollUnreadCount.bind(this), this.unread_count_attempts * UNREAD_COUNT_POLL_INTERVAL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unreadCountElement() {
|
unreadCountElement() {
|
||||||
return (
|
return (
|
||||||
this._unreadCountElement ||
|
this.$unreadCount ||
|
||||||
(this._unreadCountElement = $('#global_nav_conversations_link').find('.menu-item__badge')[0])
|
(this.$unreadCount = $('#global_nav_conversations_link').find('.menu-item__badge'))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,11 +208,9 @@ export default class Navigation extends React.Component {
|
||||||
</ScreenReaderContent>
|
</ScreenReaderContent>
|
||||||
<PresentationContent>{count}</PresentationContent>
|
<PresentationContent>{count}</PresentationContent>
|
||||||
</React.Fragment>,
|
</React.Fragment>,
|
||||||
this.unreadCountElement()
|
this.unreadCountElement()[0]
|
||||||
)
|
)
|
||||||
if (this.unreadCountElement()) {
|
this.unreadCountElement().toggle(count > 0)
|
||||||
this.unreadCountElement().style.display = count > 0 ? '' : 'none'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
determineActiveLink() {
|
determineActiveLink() {
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
// Copyright (C) 2015 - present Instructure, Inc.
|
|
||||||
//
|
|
||||||
// This file is part of Canvas.
|
|
||||||
//
|
|
||||||
// Canvas is free software: you can redistribute it and/or modify it under
|
|
||||||
// the terms of the GNU Affero General Public License as published by the Free
|
|
||||||
// Software Foundation, version 3 of the License.
|
|
||||||
//
|
|
||||||
// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
||||||
// details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import $ from 'jquery'
|
|
||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import Navigation from 'jsx/navigation_header/Navigation'
|
|
||||||
|
|
||||||
$(document.body).append('<div id="holder">')
|
|
||||||
const componentHolder = document.getElementById('holder')
|
|
||||||
|
|
||||||
const renderComponent = () => ReactDOM.render(<Navigation />, componentHolder)
|
|
||||||
|
|
||||||
let $inbox_data
|
|
||||||
describe('GlobalNavigation', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
fetch.resetMocks()
|
|
||||||
// Need to setup the global nav stuff we are testing
|
|
||||||
$inbox_data = $(`
|
|
||||||
<a
|
|
||||||
id="global_nav_conversations_link"
|
|
||||||
href="/conversations"
|
|
||||||
class="ic-app-header__menu-list-link"
|
|
||||||
>
|
|
||||||
<div class="menu-item-icon-container">
|
|
||||||
<span class="menu-item__badge" style="display: none">0</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
`).appendTo(document.body)
|
|
||||||
window.ENV.current_user_id = 10
|
|
||||||
ENV.current_user_disabled_inbox = false
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
ReactDOM.unmountComponentAtNode(componentHolder)
|
|
||||||
$('#holder').remove()
|
|
||||||
$inbox_data.remove()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders', () => {
|
|
||||||
expect(() => renderComponent()).not.toThrow()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows the inbox badge when necessary', async () => {
|
|
||||||
fetch.mockResponse(JSON.stringify({unread_count: 12}))
|
|
||||||
renderComponent()
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100))
|
|
||||||
const $badge = $('#global_nav_conversations_link').find('.menu-item__badge')
|
|
||||||
expect($badge.text()).toBe('12 unread messages12')
|
|
||||||
expect($badge.css('display')).toBe('')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not show the inbox badge when the user has opted out of notifications', async () => {
|
|
||||||
ENV.current_user_disabled_inbox = true
|
|
||||||
renderComponent()
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100))
|
|
||||||
const $badge = $('#global_nav_conversations_link').find('.menu-item__badge')
|
|
||||||
expect($badge.text()).toBe('0')
|
|
||||||
expect($badge.css('display')).toBe('none')
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -28,7 +28,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,400i,700&subset=latin-ext&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,400i,700&subset=latin-ext&display=swap" rel="stylesheet">
|
||||||
<!--[if lte IE 9]> <meta http-equiv=refresh content="0; URL=/ie-9-is-not-supported.html" /> <![endif]-->
|
<!--[if lte IE 9]> <meta http-equiv=refresh content="0; URL=/ie-9-is-not-supported.html" /> <![endif]-->
|
||||||
<script>window.fetchIgnoredByNewRelic = window.fetch</script><%= browser_performance_monitor_embed %>
|
<%= browser_performance_monitor_embed %>
|
||||||
<%= favicon_link_tag(favicon) %>
|
<%= favicon_link_tag(favicon) %>
|
||||||
<%= favicon_link_tag(brand_variable('ic-brand-apple-touch-icon'), rel: 'apple-touch-icon', type: nil) %>
|
<%= favicon_link_tag(brand_variable('ic-brand-apple-touch-icon'), rel: 'apple-touch-icon', type: nil) %>
|
||||||
<%= yield :auto_discovery %>
|
<%= yield :auto_discovery %>
|
||||||
|
|
|
@ -26,7 +26,6 @@ module.exports = {
|
||||||
'^jsx/(.*)$': '<rootDir>/app/jsx/$1',
|
'^jsx/(.*)$': '<rootDir>/app/jsx/$1',
|
||||||
'^jst/(.*)$': '<rootDir>/app/views/jst/$1',
|
'^jst/(.*)$': '<rootDir>/app/views/jst/$1',
|
||||||
"^timezone$": "<rootDir>/public/javascripts/timezone_core.js",
|
"^timezone$": "<rootDir>/public/javascripts/timezone_core.js",
|
||||||
'node_modules-version-of-backbone': require.resolve('backbone'),
|
|
||||||
"\\.svg$": "<rootDir>/jest/imageMock.js"
|
"\\.svg$": "<rootDir>/jest/imageMock.js"
|
||||||
},
|
},
|
||||||
roots: ['app/jsx', 'app/coffeescripts'],
|
roots: ['app/jsx', 'app/coffeescripts'],
|
||||||
|
@ -56,7 +55,6 @@ module.exports = {
|
||||||
coverageDirectory: '<rootDir>/coverage-jest/',
|
coverageDirectory: '<rootDir>/coverage-jest/',
|
||||||
|
|
||||||
moduleFileExtensions: [...defaults.moduleFileExtensions, 'coffee', 'handlebars'],
|
moduleFileExtensions: [...defaults.moduleFileExtensions, 'coffee', 'handlebars'],
|
||||||
restoreMocks: true,
|
|
||||||
|
|
||||||
transform: {
|
transform: {
|
||||||
'^i18n': '<rootDir>/jest/i18nTransformer.js',
|
'^i18n': '<rootDir>/jest/i18nTransformer.js',
|
||||||
|
|
|
@ -21,7 +21,7 @@ import Adapter from 'enzyme-adapter-react-16'
|
||||||
|
|
||||||
const errorsToIgnore = ["Warning: [Focusable] Exactly one tabbable child is required (0 found)."];
|
const errorsToIgnore = ["Warning: [Focusable] Exactly one tabbable child is required (0 found)."];
|
||||||
|
|
||||||
window.fetch = require('jest-fetch-mock')
|
window.fetch = require('unfetch')
|
||||||
|
|
||||||
/* eslint-disable-next-line */
|
/* eslint-disable-next-line */
|
||||||
const _consoleDotError = console.error
|
const _consoleDotError = console.error
|
||||||
|
|
|
@ -177,7 +177,6 @@
|
||||||
"jest-canvas-mock": "^1",
|
"jest-canvas-mock": "^1",
|
||||||
"jest-config": "^24",
|
"jest-config": "^24",
|
||||||
"jest-dom": "^3",
|
"jest-dom": "^3",
|
||||||
"jest-fetch-mock": "^2.1.2",
|
|
||||||
"jest-junit": "^6",
|
"jest-junit": "^6",
|
||||||
"jest-localstorage-mock": "^2",
|
"jest-localstorage-mock": "^2",
|
||||||
"jest-moxios-utils": "^1",
|
"jest-moxios-utils": "^1",
|
||||||
|
@ -213,6 +212,7 @@
|
||||||
"style-loader": "^0.23",
|
"style-loader": "^0.23",
|
||||||
"stylelint": "^9",
|
"stylelint": "^9",
|
||||||
"through2": "^2",
|
"through2": "^2",
|
||||||
|
"unfetch": "^4.0.1",
|
||||||
"waait": "^1",
|
"waait": "^1",
|
||||||
"webpack": "^4",
|
"webpack": "^4",
|
||||||
"webpack-cleanup-plugin": "^0.5",
|
"webpack-cleanup-plugin": "^0.5",
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 - present Instructure, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of Canvas.
|
||||||
|
*
|
||||||
|
* Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {has, isEmpty} from 'lodash'
|
||||||
|
import CourseActivitySummaryStore from 'jsx/dashboard_card/CourseActivitySummaryStore'
|
||||||
|
|
||||||
|
QUnit.module('CourseActivitySummaryStore', {
|
||||||
|
setup() {
|
||||||
|
CourseActivitySummaryStore.setState({streams: {}})
|
||||||
|
this.server = sinon.fakeServer.create()
|
||||||
|
this.stream = [
|
||||||
|
{
|
||||||
|
type: 'DiscussionTopic',
|
||||||
|
unread_count: 2,
|
||||||
|
count: 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Conversation',
|
||||||
|
unread_count: 0,
|
||||||
|
count: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
teardown() {
|
||||||
|
return this.server.restore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('getStateForCourse', function() {
|
||||||
|
ok(
|
||||||
|
has(CourseActivitySummaryStore.getStateForCourse(), 'streams'),
|
||||||
|
'should return root state object when no courseId is provided'
|
||||||
|
)
|
||||||
|
const spy = sandbox.stub(CourseActivitySummaryStore, '_fetchForCourse').returns(true)
|
||||||
|
ok(
|
||||||
|
isEmpty(CourseActivitySummaryStore.getStateForCourse(1)),
|
||||||
|
'should return empty object for course id not already in state'
|
||||||
|
)
|
||||||
|
ok(spy.called, 'should call _fetchForCourse to fetch stream info for course')
|
||||||
|
CourseActivitySummaryStore.setState({streams: {1: {stream: this.stream}}})
|
||||||
|
deepEqual(
|
||||||
|
CourseActivitySummaryStore.getStateForCourse(1),
|
||||||
|
{stream: this.stream},
|
||||||
|
'should return stream if present'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('_fetchForCourse', function() {
|
||||||
|
ok(isEmpty(CourseActivitySummaryStore.getState().streams[1]), 'precondition')
|
||||||
|
this.server.respondWith('GET', '/api/v1/courses/1/activity_stream/summary', [
|
||||||
|
200,
|
||||||
|
{'Content-Type': 'application/json'},
|
||||||
|
JSON.stringify(this.stream)
|
||||||
|
])
|
||||||
|
CourseActivitySummaryStore._fetchForCourse(1)
|
||||||
|
this.server.respond()
|
||||||
|
deepEqual(
|
||||||
|
CourseActivitySummaryStore.getState().streams[1].stream,
|
||||||
|
this.stream,
|
||||||
|
'should populate state based on API response'
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (C) 2015 - present Instructure, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of Canvas.
|
||||||
|
//
|
||||||
|
// Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
// the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
// Software Foundation, version 3 of the License.
|
||||||
|
//
|
||||||
|
// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
// details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License along
|
||||||
|
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import $ from 'jquery'
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import Navigation from 'jsx/navigation_header/Navigation'
|
||||||
|
|
||||||
|
const wrapper = document.getElementById('fixtures')
|
||||||
|
$(wrapper).append('<div id="holder">')
|
||||||
|
const componentHolder = document.getElementById('holder')
|
||||||
|
|
||||||
|
const renderComponent = function() {
|
||||||
|
return ReactDOM.render(<Navigation />, componentHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
QUnit.module('GlobalNavigation', {
|
||||||
|
setup() {
|
||||||
|
// Need to setup the global nav stuff we are testing
|
||||||
|
this.$inbox_data = $(`
|
||||||
|
<a
|
||||||
|
id="global_nav_conversations_link"
|
||||||
|
href="/conversations"
|
||||||
|
class="ic-app-header__menu-list-link"
|
||||||
|
>
|
||||||
|
<div class="menu-item-icon-container">
|
||||||
|
<span class="menu-item__badge" style="display: none">0</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
`).appendTo(wrapper)
|
||||||
|
|
||||||
|
this.server = sinon.fakeServer.create()
|
||||||
|
window.ENV.current_user_id = 10
|
||||||
|
ENV.current_user_disabled_inbox = false
|
||||||
|
const response = {unread_count: 10}
|
||||||
|
this.server.respondWith('GET', /unread/, [
|
||||||
|
200,
|
||||||
|
{'Content-Type': 'application/json'},
|
||||||
|
JSON.stringify(response)
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
this.server.restore()
|
||||||
|
ReactDOM.unmountComponentAtNode(componentHolder)
|
||||||
|
$('#holder').remove()
|
||||||
|
this.$inbox_data.remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it renders', function() {
|
||||||
|
this.component = renderComponent()
|
||||||
|
ok(this.component)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shows the inbox badge when necessary', function() {
|
||||||
|
this.component = renderComponent()
|
||||||
|
this.server.respond()
|
||||||
|
const $badge = $('#global_nav_conversations_link').find('.menu-item__badge')
|
||||||
|
ok($badge.is(':visible'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not show the inbox badge when the user has opted out of notifications', function() {
|
||||||
|
ENV.current_user_disabled_inbox = true
|
||||||
|
this.component = renderComponent()
|
||||||
|
this.server.respond()
|
||||||
|
const $badge = $('#global_nav_conversations_link').find('.menu-item__badge')
|
||||||
|
notOk($badge.is(':visible'))
|
||||||
|
})
|
26
yarn.lock
26
yarn.lock
|
@ -6116,14 +6116,6 @@ cross-fetch@2.2.2:
|
||||||
node-fetch "2.1.2"
|
node-fetch "2.1.2"
|
||||||
whatwg-fetch "2.0.4"
|
whatwg-fetch "2.0.4"
|
||||||
|
|
||||||
cross-fetch@^2.2.2:
|
|
||||||
version "2.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.3.tgz#e8a0b3c54598136e037f8650f8e823ccdfac198e"
|
|
||||||
integrity sha512-PrWWNH3yL2NYIb/7WF/5vFG3DCQiXDOVf8k3ijatbrtnwNuhMWLC7YF7uqf53tbTFDzHIUD8oITw4Bxt8ST3Nw==
|
|
||||||
dependencies:
|
|
||||||
node-fetch "2.1.2"
|
|
||||||
whatwg-fetch "2.0.4"
|
|
||||||
|
|
||||||
cross-spawn@^3.0.0:
|
cross-spawn@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
|
||||||
|
@ -11516,14 +11508,6 @@ jest-environment-node@^24.8.0:
|
||||||
jest-mock "^24.8.0"
|
jest-mock "^24.8.0"
|
||||||
jest-util "^24.8.0"
|
jest-util "^24.8.0"
|
||||||
|
|
||||||
jest-fetch-mock@^2.1.2:
|
|
||||||
version "2.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-2.1.2.tgz#1260b347918e3931c4ec743ceaf60433da661bd0"
|
|
||||||
integrity sha512-tcSR4Lh2bWLe1+0w/IwvNxeDocMI/6yIA2bijZ0fyWxC4kQ18lckQ1n7Yd40NKuisGmcGBRFPandRXrW/ti/Bw==
|
|
||||||
dependencies:
|
|
||||||
cross-fetch "^2.2.2"
|
|
||||||
promise-polyfill "^7.1.1"
|
|
||||||
|
|
||||||
jest-get-type@^24.8.0:
|
jest-get-type@^24.8.0:
|
||||||
version "24.8.0"
|
version "24.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc"
|
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc"
|
||||||
|
@ -15687,11 +15671,6 @@ promise-inflight@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
|
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
|
||||||
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
|
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
|
||||||
|
|
||||||
promise-polyfill@^7.1.1:
|
|
||||||
version "7.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-7.1.2.tgz#ab05301d8c28536301622d69227632269a70ca3b"
|
|
||||||
integrity sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==
|
|
||||||
|
|
||||||
promise@^7.0.3, promise@^7.1.1:
|
promise@^7.0.3, promise@^7.1.1:
|
||||||
version "7.3.1"
|
version "7.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
||||||
|
@ -19207,6 +19186,11 @@ underscore@~1.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
|
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
|
||||||
integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=
|
integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=
|
||||||
|
|
||||||
|
unfetch@^4.0.1:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db"
|
||||||
|
integrity sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==
|
||||||
|
|
||||||
unherit@^1.0.4:
|
unherit@^1.0.4:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"
|
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"
|
||||||
|
|
Loading…
Reference in New Issue