diff --git a/docs/decisions/0007-testing.md b/docs/decisions/0007-testing.md new file mode 100644 index 00000000..ffdb1fd9 --- /dev/null +++ b/docs/decisions/0007-testing.md @@ -0,0 +1,41 @@ +# Testing + +## Status +Draft + +Let's live with this a bit longer before deciding it's a solid approach and marking this Approved. + +## Context +We'd like to all be on the same page about how to approach testing, what is +worth testing, and how to do it. + +## React Testing Library +We'll use react-testing-library and jest as the main testing tools. + +This has some implications about how to test. You can read the React Testing Library's +[Guiding Principles](https://testing-library.com/docs/guiding-principles), but the main +takeaway is that you should be interacting with React as closely as possible to the way +the user will interact with it. + +For example, they discourage using class or element name selectors to find components +during a test. Instead, you should find them by user-oriented attributes like labels, +text, or roles. As a last resort, by a `data-testid` tag. + +## What to Test +We have not found exhaustive unit testing of frontend code to be worth the trouble. +Rather, let's focus on testing non-obvious behavior. + +In essence: `test behavior that wouldn't present itself to a developer playing around`. + +Practically speaking, this means error states, interactive components, corner cases, +or anything that wouldn't come up in a demo course. Something a developer wouldn't +notice in the normal course of working in devstack. + +## Snapshots +In practice, we've found snapshots of component trees to be too brittle to be worth it, +as refactors occur or external libraries change. + +They can still be useful for data (like redux tests) or tiny isolated components. + +But please avoid for any "interesting" component. Prefer inspecting the explicit behavior +under test, rather than just snapshotting the entire component tree. diff --git a/src/course-home/data/__factories__/courseHomeMetadata.factory.js b/src/course-home/data/__factories__/courseHomeMetadata.factory.js index 65d8215f..5acedcf7 100644 --- a/src/course-home/data/__factories__/courseHomeMetadata.factory.js +++ b/src/course-home/data/__factories__/courseHomeMetadata.factory.js @@ -2,10 +2,8 @@ import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dep Factory.define('courseHomeMetadata') .sequence( - 'course_id', - (courseId) => `course-v1:edX+DemoX+Demo_Course_${courseId}`, + 'courseId', (courseId) => `course-v1:edX+DemoX+Demo_Course_${courseId}`, ) - .option('courseTabs', []) .option('host', 'http://localhost:18000') .attrs({ is_staff: false, @@ -15,10 +13,67 @@ Factory.define('courseHomeMetadata') title: 'Demonstration Course', is_self_paced: false, }) - .attr('tabs', ['courseTabs', 'host'], (courseTabs, host) => courseTabs.map( - tab => ({ - tab_id: tab.slug, - title: tab.title, - url: `${host}${tab.url}`, - }), - )); + .attr( + 'tabs', ['courseId', 'host'], (courseId, host) => { + const tabs = [ + Factory.build( + 'tab', + { + title: 'Course', + priority: 0, + slug: 'courseware', + type: 'courseware', + }, + { courseId, path: 'course/' }, + ), + Factory.build( + 'tab', + { + title: 'Discussion', + priority: 1, + slug: 'discussion', + type: 'discussion', + }, + { courseId, path: 'discussion/forum/' }, + ), + Factory.build( + 'tab', + { + title: 'Wiki', + priority: 2, + slug: 'wiki', + type: 'wiki', + }, + { courseId, path: 'course_wiki' }, + ), + Factory.build( + 'tab', + { + title: 'Progress', + priority: 3, + slug: 'progress', + type: 'progress', + }, + { courseId, path: 'progress' }, + ), + Factory.build( + 'tab', + { + title: 'Instructor', + priority: 4, + slug: 'instructor', + type: 'instructor', + }, + { courseId, path: 'instructor' }, + ), + ]; + + return tabs.map( + tab => ({ + tab_id: tab.slug, + title: tab.title, + url: `${host}${tab.url}`, + }), + ); + }, + ); diff --git a/src/course-home/data/__factories__/datesTabData.factory.js b/src/course-home/data/__factories__/datesTabData.factory.js index 6f173fef..4a70a7d7 100644 --- a/src/course-home/data/__factories__/datesTabData.factory.js +++ b/src/course-home/data/__factories__/datesTabData.factory.js @@ -1,5 +1,8 @@ import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies +// Sample data helpful when developing & testing, to see a variety of configurations. +// This set of data is not realistic (mix of having access and not), but it +// is intended to demonstrate many UI results. Factory.define('datesTabData') .attrs({ dates_banner_info: { @@ -9,19 +12,213 @@ Factory.define('datesTabData') }, course_date_blocks: [ { - assigment_type: 'Homework', - date: '2013-02-05T05:00:00Z', + date: '2020-05-01T17:59:41Z', date_type: 'course-start-date', description: '', learner_has_access: true, link: '', title: 'Course Starts', - extraInfo: '', + extra_info: null, + }, + { + assignment_type: 'Homework', + complete: true, + date: '2020-05-04T02:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + title: 'Multi Badges Completed', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2020-05-05T02:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + title: 'Multi Badges Past Due', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2020-05-27T02:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'Both Past Due 1', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2020-05-27T02:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'Both Past Due 2', + extra_info: null, + }, + { + assignment_type: 'Homework', + complete: true, + date: '2020-05-28T08:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'One Completed/Due 1', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2020-05-28T08:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'One Completed/Due 2', + extra_info: null, + }, + { + assignment_type: 'Homework', + complete: true, + date: '2020-05-29T08:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'Both Completed 1', + extra_info: null, + }, + { + assignment_type: 'Homework', + complete: true, + date: '2020-05-29T08:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'Both Completed 2', + extra_info: null, + }, + { + date: '2020-06-16T17:59:40.942669Z', + date_type: 'verified-upgrade-deadline', + description: "Don't miss the opportunity to highlight your new knowledge and skills by earning a verified certificate.", + learner_has_access: true, + link: 'https://example.com/', + title: 'Upgrade to Verified Certificate', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2030-08-17T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: false, + link: 'https://example.com/', + title: 'One Verified 1', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2030-08-17T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'One Verified 2', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2030-08-17T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'ORA Verified 2', + extra_info: "ORA Dates are set by the instructor, and can't be changed", + }, + { + assignment_type: 'Homework', + date: '2030-08-18T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: false, + link: 'https://example.com/', + title: 'Both Verified 1', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2030-08-18T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: false, + link: 'https://example.com/', + title: 'Both Verified 2', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2030-08-19T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + title: 'One Unreleased 1', + }, + { + assignment_type: 'Homework', + date: '2030-08-19T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + link: 'https://example.com/', + title: 'One Unreleased 2', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2030-08-20T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + title: 'Both Unreleased 1', + extra_info: null, + }, + { + assignment_type: 'Homework', + date: '2030-08-20T05:59:40.942669Z', + date_type: 'assignment-due-date', + description: '', + learner_has_access: true, + title: 'Both Unreleased 2', + extra_info: null, + }, + { + date: '2030-08-23T00:00:00Z', + date_type: 'course-end-date', + description: '', + learner_has_access: true, + link: '', + title: 'Course Ends', + extra_info: null, + }, + { + date: '2030-09-01T00:00:00Z', + date_type: 'verification-deadline-date', + description: 'You must successfully complete verification before this date to qualify for a Verified Certificate.', + learner_has_access: false, + link: 'https://example.com/', + title: 'Verification Deadline', + extra_info: null, }, ], missed_deadlines: false, missed_gated_content: false, learner_is_full_access: true, - user_timezone: null, + user_timezone: 'America/New_York', verified_upgrade_link: 'http://localhost:18130/basket/add/?sku=8CF08E5', }); diff --git a/src/course-home/data/__snapshots__/redux.test.js.snap b/src/course-home/data/__snapshots__/redux.test.js.snap index 4c956299..78bcb64c 100644 --- a/src/course-home/data/__snapshots__/redux.test.js.snap +++ b/src/course-home/data/__snapshots__/redux.test.js.snap @@ -1,24 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Data layer integration tests Should initialize store 1`] = ` -Object { - "courseHome": Object { - "courseId": null, - "courseStatus": "loading", - "toastBodyLink": null, - "toastBodyText": null, - "toastHeader": "", - }, - "courseware": Object { - "courseId": null, - "courseStatus": "loading", - "sequenceId": null, - "sequenceStatus": "loading", - }, - "models": Object {}, -} -`; - exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize, and save metadata 1`] = ` Object { "courseHome": Object { @@ -78,15 +59,209 @@ Object { "course-v1:edX+DemoX+Demo_Course_1": Object { "courseDateBlocks": Array [ Object { - "assigmentType": "Homework", - "date": "2013-02-05T05:00:00Z", + "date": "2020-05-01T17:59:41Z", "dateType": "course-start-date", "description": "", - "extraInfo": "", + "extraInfo": null, "learnerHasAccess": true, "link": "", "title": "Course Starts", }, + Object { + "assignmentType": "Homework", + "complete": true, + "date": "2020-05-04T02:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "title": "Multi Badges Completed", + }, + Object { + "assignmentType": "Homework", + "date": "2020-05-05T02:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "title": "Multi Badges Past Due", + }, + Object { + "assignmentType": "Homework", + "date": "2020-05-27T02:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "Both Past Due 1", + }, + Object { + "assignmentType": "Homework", + "date": "2020-05-27T02:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "Both Past Due 2", + }, + Object { + "assignmentType": "Homework", + "complete": true, + "date": "2020-05-28T08:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "One Completed/Due 1", + }, + Object { + "assignmentType": "Homework", + "date": "2020-05-28T08:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "One Completed/Due 2", + }, + Object { + "assignmentType": "Homework", + "complete": true, + "date": "2020-05-29T08:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "Both Completed 1", + }, + Object { + "assignmentType": "Homework", + "complete": true, + "date": "2020-05-29T08:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "Both Completed 2", + }, + Object { + "date": "2020-06-16T17:59:40.942669Z", + "dateType": "verified-upgrade-deadline", + "description": "Don't miss the opportunity to highlight your new knowledge and skills by earning a verified certificate.", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "Upgrade to Verified Certificate", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-17T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": false, + "link": "https://example.com/", + "title": "One Verified 1", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-17T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "One Verified 2", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-17T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": "ORA Dates are set by the instructor, and can't be changed", + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "ORA Verified 2", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-18T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": false, + "link": "https://example.com/", + "title": "Both Verified 1", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-18T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": false, + "link": "https://example.com/", + "title": "Both Verified 2", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-19T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "learnerHasAccess": true, + "title": "One Unreleased 1", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-19T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "https://example.com/", + "title": "One Unreleased 2", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-20T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "title": "Both Unreleased 1", + }, + Object { + "assignmentType": "Homework", + "date": "2030-08-20T05:59:40.942669Z", + "dateType": "assignment-due-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "title": "Both Unreleased 2", + }, + Object { + "date": "2030-08-23T00:00:00Z", + "dateType": "course-end-date", + "description": "", + "extraInfo": null, + "learnerHasAccess": true, + "link": "", + "title": "Course Ends", + }, + Object { + "date": "2030-09-01T00:00:00Z", + "dateType": "verification-deadline-date", + "description": "You must successfully complete verification before this date to qualify for a Verified Certificate.", + "extraInfo": null, + "learnerHasAccess": false, + "link": "https://example.com/", + "title": "Verification Deadline", + }, ], "datesBannerInfo": Object { "contentTypeGatingEnabled": false, @@ -97,7 +272,7 @@ Object { "learnerIsFullAccess": true, "missedDeadlines": false, "missedGatedContent": false, - "userTimezone": null, + "userTimezone": "America/New_York", "verifiedUpgradeLink": "http://localhost:18130/basket/add/?sku=8CF08E5", }, }, diff --git a/src/course-home/data/api.js b/src/course-home/data/api.js index 41ba798e..8bdedb90 100644 --- a/src/course-home/data/api.js +++ b/src/course-home/data/api.js @@ -89,7 +89,12 @@ export async function getCourseHomeCourseMetadata(courseId) { return normalizeCourseHomeCourseMetadata(data); } +// For debugging purposes, you might like to see a fully loaded dates tab. +// Just uncomment the next few lines and the immediate 'return' in the function below +// import { Factory } from 'rosie'; +// import './__factories__'; export async function getDatesTabData(courseId) { + // return camelCaseObject(Factory.build('datesTabData')); const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/dates/${courseId}`; try { const { data } = await getAuthenticatedHttpClient().get(url); diff --git a/src/course-home/data/redux.test.js b/src/course-home/data/redux.test.js index 2446eebf..460ff532 100644 --- a/src/course-home/data/redux.test.js +++ b/src/course-home/data/redux.test.js @@ -16,20 +16,9 @@ const { loggingService } = initializeMockApp(); const axiosMock = new MockAdapter(getAuthenticatedHttpClient()); describe('Data layer integration tests', () => { - const courseMetadata = Factory.build('courseMetadata'); - const courseHomeMetadata = Factory.build( - 'courseHomeMetadata', { - course_id: courseMetadata.id, - }, - { courseTabs: courseMetadata.tabs }, - ); - - const courseId = courseMetadata.id; - const courseBaseUrl = `${getConfig().LMS_BASE_URL}/api/courseware/course`; - const courseMetadataBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/course_metadata`; - - const courseUrl = `${courseBaseUrl}/${courseId}`; - const courseMetadataUrl = `${courseMetadataBaseUrl}/${courseId}`; + const courseHomeMetadata = Factory.build('courseHomeMetadata'); + const { courseId } = courseHomeMetadata; + const courseMetadataUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/course_metadata/${courseId}`; let store; @@ -40,15 +29,10 @@ describe('Data layer integration tests', () => { store = initializeStore(); }); - it('Should initialize store', () => { - expect(store.getState()).toMatchSnapshot(); - }); - describe('Test fetchDatesTab', () => { const datesBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/dates`; it('Should fail to fetch if error occurs', async () => { - axiosMock.onGet(courseUrl).networkError(); axiosMock.onGet(courseMetadataUrl).networkError(); axiosMock.onGet(`${datesBaseUrl}/${courseId}`).networkError(); @@ -63,7 +47,6 @@ describe('Data layer integration tests', () => { const datesUrl = `${datesBaseUrl}/${courseId}`; - axiosMock.onGet(courseUrl).reply(200, courseMetadata); axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata); axiosMock.onGet(datesUrl).reply(200, datesTabData); @@ -79,7 +62,6 @@ describe('Data layer integration tests', () => { const outlineBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/outline`; it('Should result in fetch failure if error occurs', async () => { - axiosMock.onGet(courseUrl).networkError(); axiosMock.onGet(courseMetadataUrl).networkError(); axiosMock.onGet(`${outlineBaseUrl}/${courseId}`).networkError(); @@ -94,7 +76,6 @@ describe('Data layer integration tests', () => { const outlineUrl = `${outlineBaseUrl}/${courseId}`; - axiosMock.onGet(courseUrl).reply(200, courseMetadata); axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata); axiosMock.onGet(outlineUrl).reply(200, outlineTabData); diff --git a/src/course-home/dates-tab/Badge.jsx b/src/course-home/dates-tab/Badge.jsx index a9453664..fdf84c07 100644 --- a/src/course-home/dates-tab/Badge.jsx +++ b/src/course-home/dates-tab/Badge.jsx @@ -4,7 +4,10 @@ import classNames from 'classnames'; export default function Badge({ children, className }) { return ( - + {children} ); diff --git a/src/course-home/dates-tab/DatesTab.test.jsx b/src/course-home/dates-tab/DatesTab.test.jsx new file mode 100644 index 00000000..6f8e124a --- /dev/null +++ b/src/course-home/dates-tab/DatesTab.test.jsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { Route } from 'react-router'; +import MockAdapter from 'axios-mock-adapter'; +import { Factory } from 'rosie'; +import { getConfig, history } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { waitForElementToBeRemoved } from '@testing-library/dom'; +import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import DatesTab from './DatesTab'; +import { fetchDatesTab } from '../data'; +import initializeMockApp from '../../setupTest'; +import initializeStore from '../../store'; +import { TabContainer } from '../../tab-page'; +import { UserMessagesProvider } from '../../generic/user-messages'; + +initializeMockApp(); + +describe('DatesTab', () => { + let store; + let component; + let axiosMock; + let courseId; + + beforeEach(() => { + store = initializeStore(); + component = ( + + + + + + + + + + ); + + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + + const courseMetadata = Factory.build('courseHomeMetadata'); + courseId = courseMetadata.courseId; + axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/course_home/v1/course_metadata/${courseId}`).reply(200, courseMetadata); + + history.push(`/course/${courseId}/dates`); // so tab can pull course id from url + }); + + // The dates tab is largely repetitive non-interactive static data. Thus it's a little tough to follow + // testing-library's advice around testing the way your user uses the site (i.e. can't find form elements by label or + // anything). Instead, we find elements by printed date (which is what the user sees) and data-testid. Which is + // better than assuming anything about how the surrounding elements are organized by div and span or whatever. And + // better than adding non-style class names. + // Hence the following getDay query helper. + async function getDay(date) { + const dateNode = await screen.findByText(date); + let parent = dateNode.parentElement; + while (parent) { + if (parent.dataset && parent.dataset.testid === 'dates-day') { + return { + day: parent, + header: within(parent).getByTestId('dates-header'), + items: within(parent).queryAllByTestId('dates-item'), + }; + } + parent = parent.parentElement; + } + throw new Error('Did not find day container'); + } + + describe('when receiving a full set of dates data', () => { + beforeEach(() => { + const datesTabData = Factory.build('datesTabData'); + axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/course_home/v1/dates/${courseId}`).reply(200, datesTabData); + render(component); + }); + + it('handles unreleased & complete', async () => { + const { header } = await getDay('Sun, May 3, 2020'); + const badges = within(header).getAllByTestId('dates-badge'); + expect(badges).toHaveLength(2); + expect(badges[0]).toHaveTextContent('Completed'); + expect(badges[1]).toHaveTextContent('Not Yet Released'); + }); + + it('handles unreleased & past due', async () => { + const { header } = await getDay('Mon, May 4, 2020'); + const badges = within(header).getAllByTestId('dates-badge'); + expect(badges).toHaveLength(2); + expect(badges[0]).toHaveTextContent('Past Due'); + expect(badges[1]).toHaveTextContent('Not Yet Released'); + }); + + it('handles verified only', async () => { + const { day } = await getDay('Sun, Aug 18, 2030'); + const badge = within(day).getByTestId('dates-badge'); + expect(badge).toHaveTextContent('Verified Only'); + }); + + it('verified only has no link', async () => { + const { day } = await getDay('Sun, Aug 18, 2030'); + expect(within(day).queryByRole('link')).toBeNull(); + }); + + it('same status items have header badge', async () => { + const { day, header } = await getDay('Tue, May 26, 2020'); + const badge = within(header).getByTestId('dates-badge'); + expect(badge).toHaveTextContent('Past Due'); // one header badge + expect(within(day).getAllByTestId('dates-badge')).toHaveLength(1); // no other badges + }); + + it('different status items have individual badges', async () => { + const { header, items } = await getDay('Thu, May 28, 2020'); + const headerBadges = within(header).queryAllByTestId('dates-badge'); + expect(headerBadges).toHaveLength(0); // no header badges + expect(items).toHaveLength(2); + expect(within(items[0]).getByTestId('dates-badge')).toHaveTextContent('Completed'); + expect(within(items[1]).getByTestId('dates-badge')).toHaveTextContent('Past Due'); + }); + + it('shows extra info', async () => { + const { items } = await getDay('Sat, Aug 17, 2030'); + expect(items).toHaveLength(3); + + const tipIcon = within(items[2]).getByTestId('dates-extra-info'); + const tipText = "ORA Dates are set by the instructor, and can't be changed"; + + expect(screen.queryByText(tipText)).toBeNull(); // tooltip does not start in DOM + userEvent.hover(tipIcon); + const tooltip = screen.getByText(tipText); // now it's there + userEvent.unhover(tipIcon); + waitForElementToBeRemoved(tooltip); // and it's gone again + }); + }); +}); diff --git a/src/course-home/dates-tab/Day.jsx b/src/course-home/dates-tab/Day.jsx index 6fc85aa0..c01c4a19 100644 --- a/src/course-home/dates-tab/Day.jsx +++ b/src/course-home/dates-tab/Day.jsx @@ -27,7 +27,7 @@ function Day({ const { color, badges } = getBadgeListAndColor(date, intl, null, items); return ( -
  • +
  • {/* Top Line */} {!first &&
    } @@ -39,7 +39,7 @@ function Day({ {/* Content */}
    -
    +

    +

    {item.assignmentType && `${item.assignmentType}: `}{title} @@ -72,7 +72,7 @@ function Day({ {item.extraInfo} } > - + )}
    diff --git a/src/course-home/dates-tab/fakeData.js b/src/course-home/dates-tab/fakeData.js deleted file mode 100644 index f135da3b..00000000 --- a/src/course-home/dates-tab/fakeData.js +++ /dev/null @@ -1,228 +0,0 @@ -// Sample data helpful when developing, to see a variety of configurations. -// This set of data is not realistic (mix of having access and not), but it -// is intended to demonstrate many UI results. -// To use, have getDatesTabData in api.js return the result of this call instead: -/* -import fakeDatesData from '../dates-tab/fakeData'; -export async function getDatesTabData(courseId, version) { - if (tab === 'dates') { return camelCaseObject(fakeDatesData()); } - ... -} -*/ - -export default function fakeDatesData() { - return JSON.parse(` -{ - "course_date_blocks": [ - { - "date": "2020-05-01T17:59:41Z", - "date_type": "course-start-date", - "description": "", - "learner_has_access": true, - "link": "", - "title": "Course Starts", - "extra_info": null - }, - { - "assignment_type": "Homework", - "complete": true, - "date": "2020-05-04T02:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "title": "Multi Badges Completed", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2020-05-05T02:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "title": "Multi Badges Past Due", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2020-05-27T02:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "Both Past Due 1", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2020-05-27T02:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "Both Past Due 2", - "extra_info": null - }, - { - "assignment_type": "Homework", - "complete": true, - "date": "2020-05-28T08:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "One Completed/Due 1", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2020-05-28T08:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "One Completed/Due 2", - "extra_info": null - }, - { - "assignment_type": "Homework", - "complete": true, - "date": "2020-05-29T08:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "Both Completed 1", - "extra_info": null - }, - { - "assignment_type": "Homework", - "complete": true, - "date": "2020-05-29T08:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "Both Completed 2", - "extra_info": null - }, - { - "date": "2020-06-16T17:59:40.942669Z", - "date_type": "verified-upgrade-deadline", - "description": "Don't miss the opportunity to highlight your new knowledge and skills by earning a verified certificate.", - "learner_has_access": true, - "link": "https://example.com/", - "title": "Upgrade to Verified Certificate", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2030-08-17T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": false, - "link": "https://example.com/", - "title": "One Verified 1", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2030-08-17T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "One Verified 2", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2030-08-17T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "ORA Verified 2", - "extra_info": "ORA Dates are set by the instructor, and can't be changed" - }, - { - "assignment_type": "Homework", - "date": "2030-08-18T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": false, - "link": "https://example.com/", - "title": "Both Verified 1", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2030-08-18T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": false, - "link": "https://example.com/", - "title": "Both Verified 2", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2030-08-19T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "title": "One Unreleased 1" - }, - { - "assignment_type": "Homework", - "date": "2030-08-19T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "link": "https://example.com/", - "title": "One Unreleased 2", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2030-08-20T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "title": "Both Unreleased 1", - "extra_info": null - }, - { - "assignment_type": "Homework", - "date": "2030-08-20T05:59:40.942669Z", - "date_type": "assignment-due-date", - "description": "", - "learner_has_access": true, - "title": "Both Unreleased 2", - "extra_info": null - }, - { - "date": "2030-08-23T00:00:00Z", - "date_type": "course-end-date", - "description": "", - "learner_has_access": true, - "link": "", - "title": "Course Ends", - "extra_info": null - }, - { - "date": "2030-09-01T00:00:00Z", - "date_type": "verification-deadline-date", - "description": "You must successfully complete verification before this date to qualify for a Verified Certificate.", - "learner_has_access": false, - "link": "https://example.com/", - "title": "Verification Deadline", - "extra_info": null - } - ], - "display_reset_dates_text": false, - "learner_is_verified": false, - "user_timezone": "America/New_York", - "verified_upgrade_link": "https://example.com/" -} - `); -}