From 139bfe60a70815260673abf37e2e283f9cee7727 Mon Sep 17 00:00:00 2001 From: Isaac Moore Date: Mon, 28 Feb 2022 15:52:52 -0600 Subject: [PATCH] Convert DashboardCard to TypeScript refs DE-1083 Change-Id: I2df4456fd58878dbed4366f52e32acdccd141aa5 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/286065 Tested-by: Service Cloud Jenkins Reviewed-by: Aaron Ogata QA-Review: Isaac Moore Product-Review: Isaac Moore --- package.json | 1 + .../jsx/dashboard_card/DashboardCardSpec.js | 128 +++--- .../DashboardCardReorderingSpec.js | 9 +- .../dashboard-card/react/DashboardCard.js | 403 ------------------ .../dashboard-card/react/DashboardCard.tsx | 355 +++++++++++++++ yarn.lock | 13 +- 6 files changed, 435 insertions(+), 474 deletions(-) delete mode 100644 ui/shared/dashboard-card/react/DashboardCard.js create mode 100644 ui/shared/dashboard-card/react/DashboardCard.tsx diff --git a/package.json b/package.json index 36cb5b7c7cd..f353e34038a 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@sentry/fullstory": "^1.1.7", "@sentry/react": "^6.16.1", "@sentry/tracing": "^6.17.2", + "@types/react-dnd": "2.0.36", "apollo-cache": "^1.3.2", "apollo-cache-inmemory": "^1.6.3", "apollo-cache-persist": "^0.1.1", diff --git a/spec/coffeescripts/jsx/dashboard_card/DashboardCardSpec.js b/spec/coffeescripts/jsx/dashboard_card/DashboardCardSpec.js index eb7573b9202..94c0727781a 100644 --- a/spec/coffeescripts/jsx/dashboard_card/DashboardCardSpec.js +++ b/spec/coffeescripts/jsx/dashboard_card/DashboardCardSpec.js @@ -18,12 +18,10 @@ import $ from 'jquery' import React from 'react' -import ReactDOM from 'react-dom' -import TestUtils from 'react-dom/test-utils' import moxios from 'moxios' import sinon from 'sinon' import {moxiosWait} from 'jest-moxios-utils' -import {waitFor} from '@testing-library/react' +import {act, cleanup, render, waitFor} from '@testing-library/react' import DashboardCard from '@canvas/dashboard-card/react/DashboardCard' import CourseActivitySummaryStore from '@canvas/dashboard-card/react/CourseActivitySummaryStore' @@ -50,6 +48,15 @@ QUnit.module('DashboardCard', { href: '/courses/1', courseCode: '101', id: '1', + links: [ + { + css_class: 'discussions', + hidden: false, + icon: 'icon-discussion', + label: 'Discussions', + path: '/courses/1/discussion_topics' + } + ], backgroundColor: '#EF4437', image: null, isFavorited: true, @@ -57,12 +64,14 @@ QUnit.module('DashboardCard', { connectDropTarget: c => c } moxios.install() - return sandbox.stub(CourseActivitySummaryStore, 'getStateForCourse').returns({}) + return (this.getStateForCourseStub = sandbox + .stub(CourseActivitySummaryStore, 'getStateForCourse') + .returns({})) }, teardown() { moxios.uninstall() localStorage.clear() - ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this.component).parentNode) + cleanup() if (this.wrapper) { return this.wrapper.remove() } @@ -75,61 +84,41 @@ function errorRendered() { } } -test('render', function () { - const DashCard = - this.component = TestUtils.renderIntoDocument(DashCard) - const $html = $(ReactDOM.findDOMNode(this.component)) - ok($html.attr('class').match(/DashboardCard/)) - const renderSpy = sandbox.spy(this.component, 'render') - ok(!renderSpy.called, 'precondition') - CourseActivitySummaryStore.setState({streams: {1: {stream: this.stream}}}) - ok(renderSpy.called, 'should re-render on state update') +test('obtains new course activity when course activity is updated', function (assert) { + const {getByText} = render() + + assert.notEqual(getByText(`${this.props.links[0].label} - ${this.props.shortName}`), undefined) + assert.ok(this.getStateForCourseStub.calledOnce) + + act(() => CourseActivitySummaryStore.setState({streams: {1: {stream: this.stream}}})) + + assert.ok(this.getStateForCourseStub.calledTwice) }) // eslint-disable-next-line qunit/resolve-async -test('it should be accessible', function (assert) { - const DashCard = +test('is accessible', function (assert) { this.wrapper = $('
').appendTo('body')[0] - this.component = ReactDOM.render(DashCard, this.wrapper) - const $html = $(ReactDOM.findDOMNode(this.component)) + const {container} = render(, this.wrapper) + const $html = $(container.firstChild) const done = assert.async() assertions.isAccessible($html, done) }) -test('unreadCount', function () { - const DashCard = - this.component = TestUtils.renderIntoDocument(DashCard) - ok(!this.component.unreadCount('icon-discussion', []), 'should not blow up without a stream') - equal( - this.component.unreadCount('icon-discussion', this.stream), - 2, - 'should pass down unread count if stream item corresponding to icon has unread count' - ) +test('does not have an image when a url is not provided', function (assert) { + const {getByText, queryByText} = render() + + assert.equal(queryByText(`Course image for ${this.props.shortName}`), undefined) + assert.notEqual(getByText(`Course card color region for ${this.props.shortName}`), undefined) }) -test('does not have image attribute when a url is not provided', function () { - const DashCard = - this.component = TestUtils.renderIntoDocument(DashCard) - strictEqual( - TestUtils.scryRenderedDOMComponentsWithClass(this.component, 'ic-DashboardCard__header_image') - .length, - 0, - 'image attribute should not be present' - ) -}) - -test('has image attribute when url is provided', function () { +test('has an image when a url is provided', function (assert) { this.props.image = 'http://coolUrl' - const DashCard = - this.component = TestUtils.renderIntoDocument(DashCard) - const $html = TestUtils.findRenderedDOMComponentWithClass( - this.component, - 'ic-DashboardCard__header_image' - ) - ok($html, 'image showing') + const {getByText} = render() + + assert.notEqual(getByText(`Course image for ${this.props.shortName}`), undefined) }) -test('#removeCourseFromFavorites succeeds', function () { +test('handles success removing course from favorites', async function (assert) { const handleRerenderSpy = sinon.spy() this.props.onConfirmUnfavorite = handleRerenderSpy @@ -139,25 +128,30 @@ test('#removeCourseFromFavorites succeeds', function () { } } - const DashCard = - this.component = TestUtils.renderIntoDocument(DashCard) - this.component.removeCourseFromFavorites() + const {getByText} = render() + act(() => + getByText( + `Choose a color or course nickname or move course card for ${this.props.shortName}` + ).click() + ) + act(() => getByText('Move').click()) + act(() => getByText('Unfavorite').click()) + act(() => getByText('Submit').click()) - return moxiosWait(function () { + await moxiosWait(() => { const request = moxios.requests.mostRecent() request.respondWith({ status: 200, response: [] }) - }).then(async function () { - await waitFor(() => waitForResponse()) - ok(handleRerenderSpy.calledOnce) }) + + await waitFor(() => waitForResponse()) + assert.ok(handleRerenderSpy.calledOnce) }) -test('#removeCourseFromFavorites fails', function () { - const handleRerenderSpy = sinon.spy() - this.props.onConfirmUnfavorite = handleRerenderSpy +test('handles failure removing course from favorites', async function (assert) { + this.props.onConfirmUnfavorite = sinon.spy() function waitForAlert() { if (errorRendered) { @@ -165,18 +159,24 @@ test('#removeCourseFromFavorites fails', function () { } } - const DashCard = - this.component = TestUtils.renderIntoDocument(DashCard) - this.component.removeCourseFromFavorites() + const {getByText} = render() + act(() => + getByText( + `Choose a color or course nickname or move course card for ${this.props.shortName}` + ).click() + ) + act(() => getByText('Move').click()) + act(() => getByText('Unfavorite').click()) + act(() => getByText('Submit').click()) - return moxiosWait(function () { + await moxiosWait(() => { const request = moxios.requests.mostRecent() request.respondWith({ status: 403, response: [] }) - }).then(async function () { - await waitFor(() => waitForAlert()) - ok(errorRendered) }) + + await waitFor(() => waitForAlert()) + assert.ok(errorRendered) }) diff --git a/spec/javascripts/jsx/dashboard_card/DashboardCardReorderingSpec.js b/spec/javascripts/jsx/dashboard_card/DashboardCardReorderingSpec.js index 24d8cbfe6ba..30677290b96 100644 --- a/spec/javascripts/jsx/dashboard_card/DashboardCardReorderingSpec.js +++ b/spec/javascripts/jsx/dashboard_card/DashboardCardReorderingSpec.js @@ -24,6 +24,7 @@ import DashboardCard from '@canvas/dashboard-card/react/DashboardCard' import DraggableDashboardCard from '@canvas/dashboard-card/react/DraggableDashboardCard' import getDroppableDashboardCardBox from '@canvas/dashboard-card/react/getDroppableDashboardCardBox' import fakeENV from 'helpers/fakeENV' +import {render} from '@testing-library/react' let cards let fakeServer @@ -84,8 +85,8 @@ test('it renders', () => { ok(root) }) -test('cards have opacity of 0 while moving', () => { - const card = TestUtils.renderIntoDocument( +test('cards have opacity of 0 while moving', assert => { + const {container} = render( { isDragging /> ) - const div = TestUtils.findRenderedDOMComponentWithClass(card, 'ic-DashboardCard') - equal(div.style.opacity, 0) + + assert.equal(container.firstChild.style.opacity, 0) }) test('moving a card adjusts the position property', () => { diff --git a/ui/shared/dashboard-card/react/DashboardCard.js b/ui/shared/dashboard-card/react/DashboardCard.js deleted file mode 100644 index 58ec7ee992a..00000000000 --- a/ui/shared/dashboard-card/react/DashboardCard.js +++ /dev/null @@ -1,403 +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 . - */ - -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import { useScope as useI18nScope } from '@canvas/i18n'; -import axios from '@canvas/axios' - -import DashboardCardAction from './DashboardCardAction' -import CourseActivitySummaryStore from './CourseActivitySummaryStore' -import DashboardCardMenu from './DashboardCardMenu' -import PublishButton from './PublishButton' -import {showConfirmUnfavorite} from './ConfirmUnfavoriteCourseModal' -import {showFlashError} from '@canvas/alerts/react/FlashAlert' -import instFSOptimizedImageUrl from '../util/instFSOptimizedImageUrl' - -const I18n = useI18nScope('dashcards'); - -export function DashboardCardHeaderHero({image, backgroundColor, hideColorOverlays, onClick}) { - if (image) { - return ( -
- - ) - } - - return ( -