chore: apply amnesty on existing not fixable issues (#32215)

* fix: eslint operator-linebreak issue

* fix: eslint quotes issue

* fix: react jsx indent and props issues

* fix: eslint trailing spaces issues

* fix: eslint line around directives issue

* fix: eslint semi rule

* fix: eslint newline per chain rule

* fix: eslint space infix ops rule

* fix: eslint space-in-parens issue

* fix: eslint space before function paren issue

* fix: eslint space before blocks issue

* fix: eslint arrow body style issue

* fix: eslint dot-location issue

* fix: eslint quotes issue

* fix: eslint quote props issue

* fix: eslint operator assignment issue

* fix: eslint new line after import issue

* fix: indent issues

* fix: operator assignment issue

* fix: all autofixable eslint issues

* fix: all react related fixable issues

* fix: autofixable eslint issues

* chore: remove all template literals

* fix: remaining autofixable issues

* chore: apply amnesty on all existing issues

* fix: failing xss-lint issues

* refactor: apply amnesty on remaining issues

* refactor: apply amnesty on new issues

* fix: remove file level suppressions

* refactor: apply amnesty on new issues
This commit is contained in:
Syed Ali Abbas Zaidi
2023-08-07 19:13:19 +05:00
committed by GitHub
parent e94af3c2d3
commit 8480dbc228
305 changed files with 1043 additions and 202 deletions

View File

@@ -2,7 +2,9 @@ import { ViewedEventTracker } from './ViewedEvent';
const completedBlocksKeys = new Set();
// eslint-disable-next-line import/prefer-default-export
export function markBlocksCompletedOnViewIfNeeded(runtime, containerElement) {
// eslint-disable-next-line no-undef
const blockElements = $(containerElement).find(
'.xblock-student_view[data-mark-completed-on-view-after-delay]',
).get();
@@ -21,6 +23,7 @@ export function markBlocksCompletedOnViewIfNeeded(runtime, containerElement) {
const blockKey = blockElement.dataset.usageId;
if (blockKey && !completedBlocksKeys.has(blockKey)) {
if (event.elementHasBeenViewed) {
// eslint-disable-next-line no-undef
$.ajax({
type: 'POST',
url: runtime.handlerUrl(blockElement, 'publish_completion'),
@@ -30,6 +33,7 @@ export function markBlocksCompletedOnViewIfNeeded(runtime, containerElement) {
}).then(
() => {
completedBlocksKeys.add(blockKey);
// eslint-disable-next-line no-param-reassign
blockElement.dataset.markCompletedOnViewAfterDelay = 0;
},
);

View File

@@ -1,4 +1,5 @@
/** Ensure that a function is only run once every `wait` milliseconds */
// eslint-disable-next-line max-classes-per-file
function throttle(fn, wait) {
let time = 0;
function delay() {

View File

@@ -13,43 +13,53 @@ describe('ViewedTracker', () => {
it('calls the handlers when an element is viewed', () => {
document.body.innerHTML = '<div id="d1"></div><div id="d2"></div><div id="d3"></div>';
const tracker = new ViewedEventTracker();
// eslint-disable-next-line no-restricted-syntax
for (const element of document.getElementsByTagName('div')) {
tracker.addElement(element, 1000);
}
// eslint-disable-next-line no-undef
const handlerSpy = jasmine.createSpy('handlerSpy');
tracker.addHandler(handlerSpy);
const elvIter = tracker.elementViewings.values();
// Pick two elements, and mock them so that one has met the criteria to be viewed,
// and the other hasn't.
const viewed = elvIter.next().value;
// eslint-disable-next-line no-undef
spyOn(viewed, 'areViewedCriteriaMet').and.returnValue(true);
viewed.checkIfViewed();
expect(handlerSpy).toHaveBeenCalledWith(viewed.el, {
elementHasBeenViewed: true,
});
const unviewed = elvIter.next().value;
// eslint-disable-next-line no-undef
spyOn(unviewed, 'areViewedCriteriaMet').and.returnValue(false);
unviewed.checkIfViewed();
// eslint-disable-next-line no-undef
expect(handlerSpy).not.toHaveBeenCalledWith(unviewed.el, jasmine.anything());
});
});
describe('ElementViewing', () => {
beforeEach(() => {
// eslint-disable-next-line no-undef
jasmine.clock().install();
});
afterEach(() => {
// eslint-disable-next-line no-undef
jasmine.clock().uninstall();
});
it('calls checkIfViewed when enough time has elapsed', () => {
const viewing = new ElementViewing({}, 500, () => {});
// eslint-disable-next-line no-undef
spyOn(viewing, 'checkIfViewed').and.callThrough();
viewing.seenForMs = 250;
viewing.handleVisible();
// eslint-disable-next-line no-undef
jasmine.clock().tick(249);
expect(viewing.checkIfViewed).not.toHaveBeenCalled();
// eslint-disable-next-line no-undef
jasmine.clock().tick(1);
expect(viewing.checkIfViewed).toHaveBeenCalled();
});
@@ -57,6 +67,7 @@ describe('ElementViewing', () => {
it('has been viewed after the specified number of milliseconds', () => {
const viewing = new ElementViewing({}, 500, () => {});
viewing.seenForMs = 250;
// eslint-disable-next-line no-undef
spyOn(Date, 'now').and.returnValue(750);
viewing.handleVisible();
viewing.markTopSeen();
@@ -65,6 +76,7 @@ describe('ElementViewing', () => {
viewing.checkIfViewed();
expect(viewing.hasBeenViewed).toBeFalsy();
Date.now.and.returnValue(1000);
// eslint-disable-next-line no-undef
jasmine.clock().tick(250);
expect(viewing.hasBeenViewed).toBeTruthy();
});

View File

@@ -64,6 +64,7 @@
Markdown.Editor = function(markdownConverter, idPostfix, help, imageUploadHandler) {
idPostfix = idPostfix || '';
// eslint-disable-next-line no-multi-assign
var hooks = this.hooks = new Markdown.HookCollection();
hooks.addNoop('onPreviewPush'); // called with no arguments after the preview has been refreshed
hooks.addNoop('postBlockquoteCreation'); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text
@@ -102,6 +103,7 @@
uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help, imageUploadHandler);
uiManager.setUndoRedoButtonStates();
// eslint-disable-next-line no-multi-assign
var forceRefresh = that.refreshPreview = function() { previewManager.refresh(true); };
forceRefresh();
@@ -163,6 +165,7 @@
var beforeReplacer, afterReplacer,
that = this;
if (remove) {
// eslint-disable-next-line no-multi-assign
beforeReplacer = afterReplacer = '';
} else {
beforeReplacer = function(s) { that.before += s; return ''; };
@@ -204,6 +207,7 @@
this.after += re.$1;
if (this.before) {
// eslint-disable-next-line no-multi-assign
regexText = replacementText = '';
while (nLinesBefore--) {
@@ -218,6 +222,7 @@
}
if (this.after) {
// eslint-disable-next-line no-multi-assign
regexText = replacementText = '';
while (nLinesAfter--) {
@@ -353,6 +358,7 @@
position.getTop = function(elem, isInner) {
var result = elem.offsetTop;
if (!isInner) {
// eslint-disable-next-line no-cond-assign
while (elem = elem.offsetParent) {
result += elem.offsetTop;
}
@@ -523,6 +529,7 @@
var keyCode = event.charCode || event.keyCode;
var keyCodeChar = String.fromCharCode(keyCode);
// eslint-disable-next-line default-case
switch (keyCodeChar) {
case 'y':
undoObj.redo();
@@ -636,6 +643,7 @@
this.setInputAreaSelectionStartEnd();
this.scrollTop = inputArea.scrollTop;
// eslint-disable-next-line no-mixed-operators
if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) {
this.text = inputArea.value;
}
@@ -862,7 +870,7 @@
var sibling = preview.nextSibling;
parent.removeChild(preview);
preview.innerHTML = text;
if (!sibling) { parent.appendChild(preview); } else { parent.insertBefore(preview, sibling); } // eslint-disable-line max-len, xss-lint: disable=javascript-jquery-insert-into-target
if (!sibling) { parent.appendChild(preview); } else { parent.insertBefore(preview, sibling); } // xss-lint: disable=javascript-jquery-insert-into-target
};
var nonSuckyBrowserPreviewSet = function(text) {
@@ -965,8 +973,10 @@
// It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel
// was chosen).
ui.prompt = function(title,
// eslint-disable-next-line no-shadow
urlLabel,
urlHelp,
// eslint-disable-next-line no-shadow
urlError,
urlDescLabel,
urlDescHelp,
@@ -974,6 +984,7 @@
urlDescError,
defaultInputText,
callback,
// eslint-disable-next-line no-shadow
imageIsDecorativeLabel,
imageUploadHandler) {
// These variables need to be declared at this level since they are used
@@ -1045,7 +1056,7 @@
}
document.getElementById('wmd-editor-dialog-form-errors').textContent = [
interpolate( // eslint-disable-line no-undef, xss-lint: disable=javascript-interpolate
interpolate( // xss-lint: disable=javascript-interpolate
ngettext(
// Translators: 'errorCount' is the number of errors found in the form.
'%(errorCount)s error found in form.', '%(errorCount)s errors found in form.',
@@ -1385,6 +1396,7 @@
button.removeAttribute('aria-disabled');
} else {
image.style.backgroundPosition = button.XShift + ' ' + disabledYShift;
// eslint-disable-next-line no-multi-assign
button.onmouseover = button.onmouseout = button.onclick = function() { };
// This line does not appear in vanilla WMD. It was added by edX to improve accessibility.
// It should become a separate commit applied to WMD's official HEAD if we remove this edited version
@@ -1681,6 +1693,7 @@
// *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the
// link text. linkEnteredCallback takes care of escaping any brackets.
chunk.selection = chunk.startTag + chunk.selection + chunk.endTag;
// eslint-disable-next-line no-multi-assign
chunk.startTag = chunk.endTag = '';
if (/\n\n/.test(chunk.selection)) {
@@ -1998,6 +2011,7 @@
chunk.findTags(/`/, /`/);
if (!chunk.startTag && !chunk.endTag) {
// eslint-disable-next-line no-multi-assign
chunk.startTag = chunk.endTag = '`';
if (!chunk.selection) {
chunk.selection = gettext('enter code here');
@@ -2006,6 +2020,7 @@
chunk.before += chunk.endTag;
chunk.endTag = '';
} else {
// eslint-disable-next-line no-multi-assign
chunk.startTag = chunk.endTag = '';
}
}
@@ -2130,6 +2145,7 @@
if (/#+/.test(chunk.startTag)) {
headerLevel = re.lastMatch.length;
}
// eslint-disable-next-line no-multi-assign
chunk.startTag = chunk.endTag = '';
// Try to get the current header level by looking for - and = in the line
@@ -2143,6 +2159,7 @@
}
// Skip to the next line so we can create the header markdown.
// eslint-disable-next-line no-multi-assign
chunk.startTag = chunk.endTag = '';
chunk.skipLines(1, 1);

View File

@@ -2,6 +2,7 @@
var output, Converter;
if (typeof exports === 'object' && typeof require === 'function') { // we're in a CommonJS (e.g. Node.js) module
output = exports;
// eslint-disable-next-line global-require
Converter = require('./Markdown.Converter').Converter;
} else {
output = window.Markdown;
@@ -63,7 +64,8 @@
tagname = tags[ctag].replace(/<\/?(\w+).*/, '$1');
// skip any already paired tags
// and skip tags in our ignore list; assume they're self-closed
if (tagpaired[ctag] || ignoredtags.search('<' + tagname + '>') > -1) { continue; } // eslint-disable-line max-len, xss-lint: disable=javascript-concat-html
// eslint-disable-next-line no-continue
if (tagpaired[ctag] || ignoredtags.search('<' + tagname + '>') > -1) { continue; } // xss-lint: disable=javascript-concat-html
tag = tags[ctag];
match = -1;
@@ -72,14 +74,14 @@
// this is an opening tag
// search forwards (next tags), look for closing tags
for (var ntag = ctag + 1; ntag < tagcount; ntag++) {
if (!tagpaired[ntag] && tags[ntag] === '</' + tagname + '>') { // eslint-disable-line max-len, xss-lint: disable=javascript-concat-html
if (!tagpaired[ntag] && tags[ntag] === '</' + tagname + '>') { // xss-lint: disable=javascript-concat-html
match = ntag;
break;
}
}
}
// eslint-disable-next-line brace-style
/* eslint-disable-next-line brace-style, no-multi-assign */
if (match == -1) { needsRemoval = tagremove[ctag] = true; } // mark for removal
else { tagpaired[match] = true; } // mark paired
}
@@ -89,6 +91,7 @@
// delete all orphaned tags from the string
var ctag = 0;
// eslint-disable-next-line no-shadow
html = html.replace(re, function(match) {
var res = tagremove[ctag] ? '' : match;
ctag++;

View File

@@ -6,6 +6,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import {StatusAlert} from '@edx/paragon/static';
// eslint-disable-next-line import/prefer-default-export
export class StatusAlertRenderer {
constructor(message, selector, afterselector) {
this.shiftFocus = this.shiftFocus.bind(this);

View File

@@ -7,6 +7,7 @@ $(document).ajaxError(function(event, jXHR) {
+ '(you must log in again to save your work).'
);
// eslint-disable-next-line no-alert
if (window.confirm(message)) {
var currentLocation = window.location.pathname;
window.location.href = '/login?next=' + encodeURIComponent(currentLocation);

View File

@@ -336,6 +336,7 @@ var edx = edx || {};
},
schedule_apply: function(nodes, f) {
// eslint-disable-next-line array-callback-return
nodes.map(function(node) {
f(node);
if (node !== undefined && node.children !== undefined) {
@@ -460,15 +461,19 @@ var edx = edx || {};
modal.find('input[name=time]').val(time);
modal.find('form').off('submit').on('submit', function(event) {
event.preventDefault();
// eslint-disable-next-line no-shadow
var date = $(this).find('input[name=date]').val(),
// eslint-disable-next-line no-shadow
time = $(this).find('input[name=time]').val();
var valid_date = new Date(date);
if (isNaN(valid_date.valueOf())) {
// eslint-disable-next-line no-alert
alert('Please enter a valid date');
return;
}
var valid_time = /^\d{1,2}:\d{2}?$/;
if (!time.match(valid_time)) {
// eslint-disable-next-line no-alert
alert('Please enter a valid time');
return;
}
@@ -521,8 +526,10 @@ var edx = edx || {};
unit = find_in(tree, chapter);
units[units.length] = unit;
if (sequential) {
// eslint-disable-next-line no-multi-assign
units[units.length] = unit = find_in(unit.children, sequential);
if (vertical) {
// eslint-disable-next-line no-multi-assign
units[units.length] = unit = find_in(unit.children, vertical);
}
}

View File

@@ -82,8 +82,8 @@
// inner function generate div and display response messages.
$('<div/>', {
class: 'message ' + group
}).appendTo('.bulk-exception-results').prepend( // eslint-disable-line max-len, xss-lint: disable=javascript-jquery-insert-into-target,javascript-jquery-prepend
"<button type='button' id= '" + group + "' class='arrow'> + </button>" + heading) // eslint-disable-line max-len, xss-lint: disable=javascript-concat-html
}).appendTo('.bulk-exception-results').prepend( // xss-lint: disable=javascript-jquery-insert-into-target,javascript-jquery-prepend
"<button type='button' id= '" + group + "' class='arrow'> + </button>" + heading) // xss-lint: disable=javascript-concat-html
.append($('<ul/>', {
class: group
}));
@@ -91,7 +91,7 @@
for (var i = 0; i < displayData.length; i++) { // eslint-disable-line vars-on-top
$('<li/>', {
text: displayData[i]
}).appendTo('div.message > .' + group); // eslint-disable-line max-len, xss-lint: disable=javascript-jquery-insert-into-target
}).appendTo('div.message > .' + group); // xss-lint: disable=javascript-jquery-insert-into-target
}
$('div.message > .' + group).hide();
}

View File

@@ -88,6 +88,7 @@
gettext('The certificate for this learner has been re-validated and the system is re-running the grade for this learner.') // eslint-disable-line max-len
);
},
// eslint-disable-next-line no-shadow
error: function(model, response) {
try {
var response_data = JSON.parse(response.responseText);

View File

@@ -22,6 +22,7 @@ var edx = edx || {};
* @param {string} url - The URL where the form data will be submitted.
* @param {Object} params - Form data, included as hidden inputs.
*/
// eslint-disable-next-line no-shadow
var configureForm = function(form, method, url, params) {
$('input', form).remove();
form.attr('action', url);
@@ -80,6 +81,7 @@ var edx = edx || {};
* @param {string} params.course - The ID of the course for the donation.
* @returns {DonationView}
*/
// eslint-disable-next-line no-shadow
initialize: function(params) {
this.$el = params.el;
this.course = params.course;

View File

@@ -15,6 +15,7 @@ var edx = edx || {};
ariaExpandedState = ($dropdownButton.attr('aria-expanded') === 'true'),
menuItems = $dropdown.find('a');
// eslint-disable-next-line no-shadow
var catchKeyPress = function(object, event) {
// get currently focused item
var $focusedItem = $(':focus');

View File

@@ -3,6 +3,7 @@ import React from 'react';
import Cookies from 'js-cookie';
import {DemographicsCollectionModal} from './DemographicsCollectionModal';
// eslint-disable-next-line import/prefer-default-export
export class DemographicsCollectionBanner extends React.Component {
constructor(props) {
super(props);
@@ -51,6 +52,7 @@ export class DemographicsCollectionBanner extends React.Component {
if (!(this.state.hideBanner)) {
return (
<div>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid, jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a id="demographics-banner-link" className="btn" onClick={() => this.setState({modalOpen: true})}>
<div
className="demographics-banner d-flex justify-content-lg-between flex-row py-1 px-2 mb-2 mb-lg-4"
@@ -63,6 +65,7 @@ export class DemographicsCollectionBanner extends React.Component {
<div className="demographics-banner-prompt d-inline-block font-weight-bold text-white mr-4 py-3 px-2 px-lg-3">
{gettext('Want to make edX better for everyone?')}
</div>
{/* eslint-disable-next-line react/button-has-type */}
<button className="demographics-banner-btn d-flex align-items-center bg-white font-weight-bold border-0 py-2 px-3 mx-2 mb-3 m-lg-0 shadow justify-content-center">
<span className="fa fa-thumbs-up px-2" aria-hidden="true" />
{gettext('Get started')}

View File

@@ -1,5 +1,6 @@
/* global gettext */
import React from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import get from 'lodash/get';
import Cookies from 'js-cookie';
import StringUtils from 'edx-ui-toolkit/js/utils/string-utils';
@@ -35,8 +36,10 @@ class DemographicsCollectionModal extends React.Component {
error: false,
// an error for when a specific demographics question fails to save
fieldError: false,
// eslint-disable-next-line react/no-unused-state
errorMessage: '',
loading: true,
// eslint-disable-next-line react/no-unused-state
open: this.props.open,
selected: {
[FIELD_NAMES.CURRENT_WORK]: '',
@@ -83,9 +86,11 @@ class DemographicsCollectionModal extends React.Component {
document.body.classList.remove('modal-open');
}
// eslint-disable-next-line react/sort-comp
loadOptions(field) {
const {choices} = get(this.state.options, field, {choices: []});
if (choices.length) {
// eslint-disable-next-line react/no-array-index-key
return choices.map((choice, i) => <option value={choice.value} key={choice.value + i}>{choice.display_name}</option>);
}
}
@@ -110,6 +115,7 @@ class DemographicsCollectionModal extends React.Component {
await this.jwtTokenService.getJwtToken();
await fetch(url, options);
} catch (error) {
// eslint-disable-next-line react/no-unused-state
this.setState({loading: false, fieldError: true, errorMessage: error});
}
@@ -178,6 +184,7 @@ class DemographicsCollectionModal extends React.Component {
const demographicsOptions = await optionsResponse.json();
return demographicsOptions;
} catch (error) {
// eslint-disable-next-line react/no-unused-state
this.setState({loading: false, error: true, errorMessage: error});
}
}
@@ -198,6 +205,7 @@ class DemographicsCollectionModal extends React.Component {
response = await fetch(`${this.props.demographicsBaseUrl}/demographics/api/v1/demographics/${this.props.user}/`, requestOptions);
} catch (e) {
// an error other than "no entry found" occured
// eslint-disable-next-line react/no-unused-state
this.setState({loading: false, error: true, errorMessage: e});
}
// an entry was not found in demographics, so we need to create one
@@ -234,6 +242,7 @@ class DemographicsCollectionModal extends React.Component {
const data = await postResponse.json();
return data;
} catch (e) {
// eslint-disable-next-line react/no-unused-state
this.setState({loading: false, error: true, errorMessage: e});
}
}
@@ -272,6 +281,7 @@ class DemographicsCollectionModal extends React.Component {
<br />
<span aria-hidden="true" className="fa fa-info-circle" />
{/* Need to strip out extra '"' characters in the marketingSiteBaseUrl prop or it tries to setup the href as a relative URL */}
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a className="pl-3" target="_blank" rel="noopener" href={`${this.props.marketingSiteBaseUrl}/demographics`.replace(/"/g, '')}>
{gettext('Why does edX collect this information?')}
</a>
@@ -354,6 +364,7 @@ class DemographicsCollectionModal extends React.Component {
{gettext('Have you ever served on active duty in the U.S. Armed Forces, Reserves, or National Guard?')}
</label>
<select
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
className="form-control"
onChange={this.handleSelectChange}
@@ -381,6 +392,7 @@ class DemographicsCollectionModal extends React.Component {
</label>
<select
className="form-control"
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
onChange={this.handleSelectChange}
key="self-education"
@@ -501,4 +513,5 @@ class DemographicsCollectionModal extends React.Component {
}
}
// eslint-disable-next-line import/prefer-default-export
export {DemographicsCollectionModal};

View File

@@ -30,6 +30,7 @@ class MultiselectDropdown extends React.Component {
document.removeEventListener('keydown', this.handleKeydown, false);
}
// eslint-disable-next-line react/sort-comp
findOption(data) {
return this.props.options.find((o) => o.value == data || o.display_name == data);
}
@@ -45,6 +46,7 @@ class MultiselectDropdown extends React.Component {
}
handleButtonClick(e) {
// eslint-disable-next-line react/no-access-state-in-setstate
this.setState({open: !this.state.open});
}
@@ -93,6 +95,7 @@ class MultiselectDropdown extends React.Component {
renderUnselect() {
return this.props.selected.length > 0 && (
// eslint-disable-next-line react/button-has-type
<button id="unselect-button" disabled={this.props.disabled} aria-label="Clear all selected" onClick={this.handleRemoveAllClick}>{gettext('Clear all')}</button>
);
}
@@ -105,7 +108,9 @@ class MultiselectDropdown extends React.Component {
const options = this.props.options.map((option, index) => {
const checked = this.props.selected.includes(option.value);
return (
// eslint-disable-next-line react/no-array-index-key
<div key={index} id={`${option.value}-option-container`} className="option-container">
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label className="option-label">
<input id={`${option.value}-option-checkbox`} className="option-checkbox" type="checkbox" value={option.value} checked={checked} onChange={this.handleOptionClick} />
<span className="pl-2">{option.display_name}</span>
@@ -141,6 +146,7 @@ class MultiselectDropdown extends React.Component {
>
<label id="multiselect-dropdown-label" htmlFor="multiselect-dropdown">{this.props.label}</label>
<div className="form-control d-flex">
{/* eslint-disable-next-line react/button-has-type */}
<button className="multiselect-dropdown-button" disabled={this.props.disabled} id="multiselect-dropdown-button" ref={this.setButtonRef} aria-haspopup="true" aria-expanded={this.state.open} aria-labelledby="multiselect-dropdown-label multiselect-dropdown-button" onClick={this.handleButtonClick}>
{this.renderSelected()}
</button>
@@ -154,12 +160,17 @@ class MultiselectDropdown extends React.Component {
}
}
// eslint-disable-next-line import/prefer-default-export
export {MultiselectDropdown};
MultiselectDropdown.propTypes = {
// eslint-disable-next-line react/require-default-props
label: PropTypes.string,
// eslint-disable-next-line react/require-default-props
emptyLabel: PropTypes.string,
// eslint-disable-next-line react/forbid-prop-types
options: PropTypes.array.isRequired,
// eslint-disable-next-line react/forbid-prop-types
selected: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
};

View File

@@ -1,5 +1,6 @@
import React from 'react';
// eslint-disable-next-line import/prefer-default-export, react/function-component-definition
export const SelectWithInput = (props) => {
const {
selectName,
@@ -21,6 +22,7 @@ export const SelectWithInput = (props) => {
<div className="d-flex flex-column pb-3">
<label htmlFor={selectName}>{labelText}</label>
<select
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
className="form-control"
name={selectName}

View File

@@ -1,10 +1,14 @@
/* global gettext */
import React from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import isFunction from 'lodash/isFunction';
const Page = ({children}) => children;
// eslint-disable-next-line react/function-component-definition
const Header = () => null;
// eslint-disable-next-line react/function-component-definition
const Closer = () => null;
// eslint-disable-next-line react/function-component-definition
const ErrorPage = () => null;
export default class Wizard extends React.Component {
constructor(props) {
@@ -15,6 +19,7 @@ export default class Wizard extends React.Component {
currentPage: 1,
totalPages: 0,
pages: [],
// eslint-disable-next-line react/no-unused-state
wizardContext: {},
};
@@ -27,6 +32,7 @@ export default class Wizard extends React.Component {
const wizardContext = this.props.wizardContext;
const closer = this.findSubComponentByType(Wizard.Closer.name)[0];
pages.push(closer);
// eslint-disable-next-line react/no-unused-state
this.setState({pages, totalPages, wizardContext});
}
@@ -41,6 +47,7 @@ export default class Wizard extends React.Component {
}
// this needs to handle the case of no provided header
// eslint-disable-next-line react/sort-comp
renderHeader() {
const header = this.findSubComponentByType(Wizard.Header.name)[0];
return header.props.children({currentPage: this.state.currentPage, totalPages: this.state.totalPages});
@@ -71,6 +78,7 @@ export default class Wizard extends React.Component {
{errorPage.props.children}
</div>
<div className="wizard-footer justify-content-end h-100 d-flex flex-column">
{/* eslint-disable-next-line react/button-has-type, react/no-unknown-property */}
<button className="wizard-button colored" arial-label={gettext('close questionnaire')} onClick={this.props.onWizardComplete}>{gettext('Close')}</button>
</div>
</div>
@@ -110,7 +118,9 @@ export default class Wizard extends React.Component {
</div>
{this.renderPage()}
<div className="wizard-footer justify-content-end h-100 d-flex flex-column">
{/* eslint-disable-next-line react/button-has-type */}
<button className={`wizard-button ${finalPage && 'colored'}`} onClick={this.wizardComplete} aria-label={gettext('finish later')}>{finalPage ? gettext('Return to my dashboard') : gettext('Finish later')}</button>
{/* eslint-disable-next-line react/button-has-type */}
<button className="wizard-button colored" hidden={finalPage} onClick={this.handleNext} aria-label={gettext('next page')}>{gettext('Next')}</button>
</div>
</div>

View File

@@ -19,6 +19,7 @@
fetchXhr: null,
performSearch: function(searchTerm, facets) {
// eslint-disable-next-line no-unused-expressions
this.fetchXhr && this.fetchXhr.abort();
this.searchTerm = searchTerm || '';
this.selectedFacets = facets || {};
@@ -37,6 +38,7 @@
},
loadNextPage: function() {
// eslint-disable-next-line no-unused-expressions
this.fetchXhr && this.fetchXhr.abort();
var data = this.preparePostData(this.page + 1);
this.fetchXhr = this.fetch({

View File

@@ -50,6 +50,7 @@
},
sendQuery: function(data) {
// eslint-disable-next-line no-unused-expressions
this.jqhxr && this.jqhxr.abort();
this.jqhxr = this.discovery.fetch({
type: 'POST',

View File

@@ -24,12 +24,14 @@
},
facetName: function(key) {
// eslint-disable-next-line no-mixed-operators
return this.meanings[key] && this.meanings[key].name || key;
},
termName: function(facetKey, termKey) {
return this.meanings[facetKey]
&& this.meanings[facetKey].terms
// eslint-disable-next-line no-mixed-operators
&& this.meanings[facetKey].terms[termKey] || termKey;
},

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -58,11 +58,13 @@
if (!this.logLevel) {
return false;
}
// eslint-disable-next-line prefer-spread
this.updateHistory.apply(this, arguments);
// Adds ID at the first place
Array.prototype.unshift.call(args, this.id);
if (console && console[logType]) {
if (console[logType].apply) {
// eslint-disable-next-line prefer-spread
console[logType].apply(console, args);
} else { // Do this for IE
console[logType](args.join(' '));
@@ -136,6 +138,7 @@
if (timeout) {
args.push(null, {timeout: timeout});
}
// eslint-disable-next-line prefer-spread
return Logger.log.apply(Logger, args);
};

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';
@@ -61,6 +62,7 @@
* @param {Array} plugins A list of plugins for the annotator.
* @param {Object} options An options for the annotator.
* */
// eslint-disable-next-line no-shadow
setupPlugins = function(annotator, plugins, options) {
_.each(plugins, function(plugin) {
var settings = options[plugin.toLowerCase()];

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';
@@ -124,6 +125,7 @@
if (jXHR.responseText) {
try {
message = $.parseJSON(jXHR.responseText).error;
// eslint-disable-next-line no-empty
} catch (error) { }
}

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';
@@ -22,6 +23,7 @@
sectionView;
if (sectionInfo) {
sectionView = chapterView.addChild(sectionInfo);
// eslint-disable-next-line no-shadow
_.each(sectionInfo.children, function(location) {
var notes = courseStructure.units[location];
if (notes) {

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';
@@ -94,6 +95,7 @@
_.each(sortedTagNames, function(tagName) {
noteGroup = notesByTag[tagName];
// eslint-disable-next-line no-shadow
var tagTitle = interpolate_text(
'{tagName} ({numberOfNotesWithTag})',
{tagName: tagName, numberOfNotesWithTag: noteGroup.length}

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -1,5 +1,7 @@
// eslint-disable-next-line no-shadow-restricted-names
(function($, undefined) {
var form_ext;
// eslint-disable-next-line no-multi-assign
$.form_ext = form_ext = {
ajax: function(options) {
return $.ajax(options);
@@ -12,6 +14,7 @@
type: method || 'GET',
data: data,
dataType: 'text json',
// eslint-disable-next-line no-shadow
success: function(data, status, xhr) {
element.trigger('ajax:success', [data, status, xhr]);
},

View File

@@ -272,6 +272,7 @@
submitButtonText: gettext('Upload File and Assign Students'),
extensions: '.csv',
url: this.context.uploadCohortsCsvUrl,
// eslint-disable-next-line no-shadow
successNotification: function(file, event, data) {
var message = interpolate_text(gettext(
"Your file '{file}' has been uploaded. Allow a few minutes for processing."

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-shadow-restricted-names
(function(define, undefined) {
'use strict';

View File

@@ -118,6 +118,7 @@ $('.mobile-menu, .global-header').on('keydown', function(e) {
// Enable arrow functionality within the menu.
if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && (isDropdownOption || isMobileOption
// eslint-disable-next-line no-mixed-operators
|| (isHamburgerMenu && $hamburgerMenu.hasClass('open')) || isToggle && $toggleUserDropdown.hasClass('open'))) {
isNext = e.key === 'ArrowDown';
if (isNext && !isHamburgerMenu && !isToggle && isLastItem) {

View File

@@ -16,6 +16,7 @@ if (!Array.prototype.find) {
o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
// eslint-disable-next-line no-bitwise
len = o.length >>> 0;
// 3. If IsCallable(predicate) is false, throw a TypeError exception.

View File

@@ -22,6 +22,7 @@ var onCertificatesReady = null;
confirmMessage = gettext('Prevent students from generating certificates in this course?');
}
// eslint-disable-next-line no-alert
if (!confirm(confirmMessage)) {
event.preventDefault();
}
@@ -40,6 +41,7 @@ var onCertificatesReady = null;
*/
var $section = $('section#certificates');
$section.on('click', '#btn-start-generating-certificates', function(event) {
// eslint-disable-next-line no-alert
if (!confirm(gettext('Start generating certificates for all students in this course?'))) {
event.preventDefault();
return;
@@ -65,6 +67,7 @@ var onCertificatesReady = null;
* Start regenerating certificates for students.
*/
$section.on('click', '#btn-start-regenerating-certificates', function(event) {
// eslint-disable-next-line no-alert
if (!confirm(gettext('Start regenerating certificates for students in this course?'))) {
event.preventDefault();
return;
@@ -104,6 +107,7 @@ var onCertificatesReady = null;
$(onCertificatesReady);
var Certificates = (function() {
// eslint-disable-next-line no-shadow
function Certificates($section) {
$section.data('wrapper', this);
this.instructor_tasks = new window.InstructorDashboard.util.PendingInstructorTasks($section);

View File

@@ -2,6 +2,7 @@
var CohortManagement;
CohortManagement = (function() {
// eslint-disable-next-line no-shadow
function CohortManagement($section) {
this.$section = $section;
this.$section.data('wrapper', this);

View File

@@ -89,6 +89,7 @@ such that the value can be defined later than this assignment (file load order).
return safeWaiter;
}());
// eslint-disable-next-line new-parens
sectionsHaveLoaded = new SafeWaiter;
$(function() {
@@ -198,6 +199,7 @@ such that the value can be defined later than this assignment (file load order).
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#open_response_assessment')
}
];
// eslint-disable-next-line no-void
if (edx.instructor_dashboard.proctoring !== void 0) {
sectionsToInitialize = sectionsToInitialize.concat([
{

View File

@@ -537,6 +537,7 @@ such that the value can be defined later than this assignment (file load order).
if (successes.length && dataFromServer.action === 'remove') {
// Translators: A list of users appears after this sentence;
renderList(gettext('These users were successfully removed as beta testers:'), (function() {
// eslint-disable-next-line no-shadow
var j, len1, results;
results = [];
for (j = 0, len1 = successes.length; j < len1; j++) {
@@ -549,6 +550,7 @@ such that the value can be defined later than this assignment (file load order).
if (errors.length && dataFromServer.action === 'add') {
// Translators: A list of users appears after this sentence;
renderList(gettext('These users were not added as beta testers:'), (function() {
// eslint-disable-next-line no-shadow
var j, len1, results;
results = [];
for (j = 0, len1 = errors.length; j < len1; j++) {
@@ -561,6 +563,7 @@ such that the value can be defined later than this assignment (file load order).
if (errors.length && dataFromServer.action === 'remove') {
// Translators: A list of users appears after this sentence;
renderList(gettext('These users were not removed as beta testers:'), (function() {
// eslint-disable-next-line no-shadow
var j, len1, results;
results = [];
for (j = 0, len1 = errors.length; j < len1; j++) {
@@ -575,6 +578,7 @@ such that the value can be defined later than this assignment (file load order).
gettext('Users must create and activate their account before they can be promoted to beta tester.'))
);
return renderList(gettext('Could not find users associated with the following identifiers:'), (function() { // eslint-disable-line max-len
// eslint-disable-next-line no-shadow
var j, len1, results;
results = [];
for (j = 0, len1 = noUsers.length; j < len1; j++) {

View File

@@ -674,6 +674,7 @@
this.$request_err_ee.empty();
this.$request_response_error_all.empty();
return function() {
// eslint-disable-next-line no-void
return cb != null ? cb.apply(this, arguments) : void 0;
};
};

View File

@@ -105,6 +105,7 @@ export default class AxiosJwtTokenService {
}
async getJwtToken() {
// eslint-disable-next-line no-useless-catch
try {
const decodedJwtToken = this.decodeJwtCookie(this.tokenCookieName);
if (!AxiosJwtTokenService.isTokenExpired(decodedJwtToken)) {
@@ -115,6 +116,7 @@ export default class AxiosJwtTokenService {
throw e;
}
// eslint-disable-next-line no-useless-catch
try {
return await this.refresh();
} catch (e) {

View File

@@ -57,6 +57,7 @@ const createRetryInterceptor = (options = {}) => {
try {
const backoffDelay = getBackoffMilliseconds(nthRetry);
// Delay (wrapped in a promise so we can await the setTimeout)
// eslint-disable-next-line no-promise-executor-return
await new Promise(resolve => setTimeout(resolve, backoffDelay));
// Make retry request
retryResponse = await httpClient.request(config);

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line padded-blocks
(function($) { // eslint-disable-line wrap-iife
'use strict';

View File

@@ -18,6 +18,7 @@ class CircleChart extends React.Component {
getCenter() {
const {centerHole, sliceBorder} = this.props;
if (centerHole) {
// eslint-disable-next-line no-shadow
const size = center / 2;
return (
<circle cx={center} cy={center} r={size} fill={sliceBorder.strokeColor} />
@@ -43,6 +44,7 @@ class CircleChart extends React.Component {
cx={center}
cy={center}
className="slice-1"
// eslint-disable-next-line react/no-array-index-key
key={index}
/>
);
@@ -85,6 +87,7 @@ class CircleChart extends React.Component {
<path
d={d}
className={`slice-${sliceIndex}`}
// eslint-disable-next-line react/no-array-index-key
key={index}
stroke={strokeColor}
strokeWidth={strokeWidth}
@@ -115,8 +118,11 @@ CircleChart.defaultProps = {
};
CircleChart.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
slices: PropTypes.array.isRequired,
// eslint-disable-next-line react/require-default-props
centerHole: PropTypes.bool,
// eslint-disable-next-line react/forbid-prop-types
sliceBorder: PropTypes.object
};

View File

@@ -3,6 +3,7 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
class CircleChartLegend extends React.Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
}
@@ -13,6 +14,7 @@ class CircleChartLegend extends React.Component {
return data.map(({value, label, sliceIndex}, index) => {
const swatchClass = `swatch-${sliceIndex}`;
return (
// eslint-disable-next-line react/no-array-index-key
<li className="legend-item" key={index}>
<div
className={classNames('color-swatch', swatchClass)}
@@ -49,6 +51,7 @@ class CircleChartLegend extends React.Component {
}
CircleChartLegend.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
data: PropTypes.array.isRequired
};

View File

@@ -3,6 +3,7 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
class Discussions extends React.Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
}
@@ -44,6 +45,7 @@ class Discussions extends React.Component {
'chart-icon',
{'fa fa-graduation-cap': !img}
)}
// eslint-disable-next-line no-extra-boolean-cast
style={{backgroundImage: !!img ? `url(${img})` : 'none'}}
aria-hidden="true"
/>

View File

@@ -6,6 +6,7 @@ const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
class DueDates extends React.Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
}
@@ -36,6 +37,7 @@ class DueDates extends React.Component {
return dates.sort((a, b) => new Date(a.due) > new Date(b.due))
.map(({format, due}, index) => (
// eslint-disable-next-line react/no-array-index-key
<li className="date-item" key={index}>
<div className="label">{this.getLabel(format)}</div>
<div className="data">{this.getDate(due)}</div>
@@ -44,6 +46,7 @@ class DueDates extends React.Component {
}
initLabelTracker(list) {
// eslint-disable-next-line prefer-const
let labels = Object.keys(list);
return labels.reduce((accumulator, key) => {
@@ -70,6 +73,7 @@ class DueDates extends React.Component {
}
DueDates.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
dates: PropTypes.array.isRequired
};

View File

@@ -20,12 +20,14 @@ const exGrades = [
];
class GradeTable extends React.Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
}
getTableGroup(type, groupIndex) {
const {grades} = this.props;
// eslint-disable-next-line array-callback-return
const groupData = grades.filter(value => {
if (value.assignment_type === type) {
return value;
@@ -38,6 +40,7 @@ class GradeTable extends React.Component {
}, index) => {
const label = multipleAssignments ? `${assignment_type} ${index + 1}` : assignment_type;
return (
// eslint-disable-next-line react/no-array-index-key
<tr key={index}>
<td>{label}</td>
<td>{passing_grade}/{total_possible}</td>
@@ -74,7 +77,9 @@ class GradeTable extends React.Component {
}
GradeTable.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
assignmentTypes: PropTypes.array.isRequired,
// eslint-disable-next-line react/forbid-prop-types
grades: PropTypes.array.isRequired,
passingGrade: PropTypes.number.isRequired,
percentGrade: PropTypes.number.isRequired

View File

@@ -4,9 +4,11 @@ import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import CircleChart from './CircleChart';
import CircleChartLegend from './CircleChartLegend';
import GradeTable from './GradeTable';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import DueDates from './DueDates';
import Discussions from './Discussions';
@@ -20,6 +22,7 @@ function arrayToObject(array) {
function countByType(type, assignments) {
let count = 0;
// eslint-disable-next-line array-callback-return
assignments.map(({format}) => {
if (format === type) {
count += 1;
@@ -42,7 +45,9 @@ function getAssignmentCounts(types, assignments) {
}
function getStreakIcons(count) {
// eslint-disable-next-line prefer-spread
return Array.apply(null, {length: count}).map((e, i) => (
// eslint-disable-next-line react/no-array-index-key
<span className="fa fa-trophy" aria-hidden="true" key={i} />
));
}
@@ -58,6 +63,7 @@ function getStreakString(count) {
return (count > 0) ? `Active ${count} ${unit} in a row` : false;
}
// eslint-disable-next-line import/prefer-default-export
export function LearnerAnalyticsDashboard(props) {
const {
grading_policy, grades, schedule, schedule_raw, week_streak, weekly_active_users, discussion_info, profile_images, passing_grade, percent_grade

View File

@@ -18,6 +18,7 @@ class EnterpriseLearnerPortalModal extends React.Component {
}
componentDidMount() {
// eslint-disable-next-line react/prop-types
const storageKey = `enterprise_learner_portal_modal__${this.props.enterpriseCustomerUUID}`;
const hasViewedModal = window.sessionStorage.getItem(storageKey);
if (!hasViewedModal) {
@@ -60,6 +61,7 @@ class EnterpriseLearnerPortalModal extends React.Component {
const { key } = e;
if (key === 'Escape') {
window.analytics.track('edx.ui.enterprise.lms.dashboard.learner_portal_modal.closed', {
// eslint-disable-next-line react/prop-types
enterpriseUUID: this.props.enterpriseCustomerUUID,
source: 'Escape',
});
@@ -67,6 +69,7 @@ class EnterpriseLearnerPortalModal extends React.Component {
}
}
// eslint-disable-next-line react/sort-comp
closeModal() {
this.setState({
isModalOpen: false,
@@ -75,6 +78,7 @@ class EnterpriseLearnerPortalModal extends React.Component {
openModal() {
window.analytics.track('edx.ui.enterprise.lms.dashboard.learner_portal_modal.opened', {
// eslint-disable-next-line react/prop-types
enterpriseUUID: this.props.enterpriseCustomerUUID,
});
this.setState({
@@ -83,6 +87,7 @@ class EnterpriseLearnerPortalModal extends React.Component {
}
getLearnerPortalUrl() {
// eslint-disable-next-line react/prop-types
const baseUrlWithSlug = `${this.props.enterpriseLearnerPortalBaseUrl}/${this.props.enterpriseCustomerSlug}`;
return `${baseUrlWithSlug}?utm_source=lms_dashboard_modal`;
}
@@ -90,6 +95,7 @@ class EnterpriseLearnerPortalModal extends React.Component {
handleLearnerPortalDashboardClick(e) {
e.preventDefault();
window.analytics.track('edx.ui.enterprise.lms.dashboard.learner_portal_modal.dashboard_cta.clicked', {
// eslint-disable-next-line react/prop-types
enterpriseUUID: this.props.enterpriseCustomerUUID,
});
setTimeout(() => {
@@ -116,6 +122,7 @@ class EnterpriseLearnerPortalModal extends React.Component {
{StringUtils.interpolate(
gettext('You have access to the {enterpriseName} dashboard'),
{
// eslint-disable-next-line react/prop-types
enterpriseName: this.props.enterpriseCustomerName,
},
)}
@@ -124,15 +131,18 @@ class EnterpriseLearnerPortalModal extends React.Component {
{StringUtils.interpolate(
gettext('To access the courses available to you through {enterpriseName}, visit the {enterpriseName} dashboard.'),
{
// eslint-disable-next-line react/prop-types
enterpriseName: this.props.enterpriseCustomerName,
},
)}
</p>
<div className="mt-4 d-flex align-content-center justify-content-end">
{/* eslint-disable-next-line react/button-has-type */}
<button
className="btn-link mr-3"
onClick={() => {
window.analytics.track('edx.ui.enterprise.lms.dashboard.learner_portal_modal.closed', {
// eslint-disable-next-line react/prop-types
enterpriseUUID: this.props.enterpriseCustomerUUID,
source: 'Cancel button',
});
@@ -158,4 +168,5 @@ class EnterpriseLearnerPortalModal extends React.Component {
}
}
// eslint-disable-next-line import/prefer-default-export
export { EnterpriseLearnerPortalModal };

View File

@@ -1,20 +1,26 @@
// eslint-disable-next-line no-undef
$(document).ready(() => {
'use strict';
const requestButtons = document.getElementsByClassName('request-cert');
for (let i = 0; i < requestButtons.length; i++) {
// eslint-disable-next-line no-loop-func
requestButtons[i].addEventListener('click', (event) => {
event.preventDefault();
const endpoint = !!event.target.dataset.endpoint && event.target.dataset.endpoint;
// eslint-disable-next-line no-undef
$.ajax({
type: 'POST',
url: endpoint,
dataType: 'text',
success: () => {
// eslint-disable-next-line no-restricted-globals
location.reload();
},
// eslint-disable-next-line no-unused-vars
error: (jqXHR, textStatus, errorThrown) => {
// eslint-disable-next-line no-restricted-globals
location.reload();
},
});

View File

@@ -6,6 +6,7 @@ class CourseCardCollection extends Backbone.Collection {
const defaults = {
model: CourseCard,
};
// eslint-disable-next-line prefer-object-spread
super(models, Object.assign({}, defaults, options));
}
}

View File

@@ -6,6 +6,7 @@ class ProgramCollection extends Backbone.Collection {
const defaults = {
model: Program,
};
// eslint-disable-next-line prefer-object-spread
super(models, Object.assign({}, defaults, options));
}
}

View File

@@ -34,6 +34,7 @@ class CourseCardModel extends Backbone.Model {
desiredCourseRun = enrolledCourseRun;
} else if (openEnrollmentCourseRuns.length > 0) {
if (openEnrollmentCourseRuns.length === 1) {
// eslint-disable-next-line prefer-destructuring
desiredCourseRun = openEnrollmentCourseRuns[0];
} else {
desiredCourseRun = CourseCardModel.getUnselectedCourseRun(openEnrollmentCourseRuns);
@@ -56,6 +57,7 @@ class CourseCardModel extends Backbone.Model {
if (courseRuns && courseRuns.length > 0) {
const courseRun = courseRuns[0];
// eslint-disable-next-line no-undef
$.extend(unselectedRun, {
marketing_url: courseRun.marketing_url,
is_enrollment_open: courseRun.is_enrollment_open,
@@ -76,6 +78,7 @@ class CourseCardModel extends Backbone.Model {
));
// Deep copy to avoid mutating this.context.
// eslint-disable-next-line no-undef
const enrollableCourseRuns = $.extend(true, [], rawCourseRuns);
// These are raw course runs from the server. The start
@@ -128,6 +131,7 @@ class CourseCardModel extends Backbone.Model {
if (upgradeableSeats.length > 0) {
const upgradeableSeat = upgradeableSeats[0];
if (upgradeableSeat) {
// eslint-disable-next-line prefer-destructuring
const currency = upgradeableSeat.currency;
if (currency === 'USD') {
return `$${upgradeableSeat.price}`;

View File

@@ -9,6 +9,7 @@ class CourseEnrollModel extends Backbone.Model {
course_id: '',
optIn: false,
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, attrs), ...args);
}
}

View File

@@ -13,6 +13,7 @@ class CourseEntitlementModel extends Backbone.Model {
expiredAt: null,
daysUntilExpiration: Number.MAX_VALUE,
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, attrs), ...args);
}
}

View File

@@ -1,10 +1,12 @@
import Backbone from 'backbone';
import CollectionListView from './views/collection_list_view';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import ProgramCardView from './views/program_card_view';
import ProgramCollection from './collections/program_collection';
import ProgressCollection from './collections/program_progress_collection';
import SidebarView from './views/sidebar_view';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import HeaderView from './views/program_list_header_view';
function ProgramListFactory(options) {
@@ -29,32 +31,35 @@ function ProgramListFactory(options) {
}
const activeSubscriptions = options.programsSubscriptionData
// eslint-disable-next-line camelcase
.filter(({ subscription_state }) => subscription_state === 'active')
.sort((a, b) => new Date(b.created) - new Date(a.created));
// Sort programs so programs with active subscriptions are at the top
if (activeSubscriptions.length) {
// eslint-disable-next-line no-param-reassign
options.programsData = options.programsData
.map((programsData) => ({
...programsData,
subscriptionIndex: activeSubscriptions.findIndex(
// eslint-disable-next-line camelcase
({ resource_id }) => resource_id === programsData.uuid,
),
}))
.sort(({ subscriptionIndex: indexA }, { subscriptionIndex: indexB }) => {
switch (true) {
case indexA === -1 && indexB === -1:
// Maintain the original order for non-subscription programs
return 0;
case indexA === -1:
// Move non-subscription program to the end
return 1;
case indexB === -1:
// Keep non-subscription program to the end
return -1;
default:
// Sort by subscriptionIndex in ascending order
return indexA - indexB;
case indexA === -1 && indexB === -1:
// Maintain the original order for non-subscription programs
return 0;
case indexA === -1:
// Move non-subscription program to the end
return 1;
case indexB === -1:
// Keep non-subscription program to the end
return -1;
default:
// Sort by subscriptionIndex in ascending order
return indexA - indexB;
}
});
}

View File

@@ -138,7 +138,7 @@ describe('Collection List View', () => {
const $cards = view.$el.find('.program-card');
expect($cards.length).toBe(2);
$cards.each((index, el) => {
// eslint-disable-next-line newline-per-chained-call
/* eslint-disable-next-line newline-per-chained-call, no-undef */
expect($(el).find('.title').html().trim()).toEqual(context.programsData[index].title);
});
});

View File

@@ -11,6 +11,7 @@ describe('Course Card View', () => {
const endDate = 'May 30, 2017';
const setupView = (data, isEnrolled, collectionCourseStatus) => {
// eslint-disable-next-line no-undef
const programData = $.extend({}, data);
const context = {
programData,
@@ -155,6 +156,7 @@ describe('Course Card View', () => {
});
it('should allow enrollment in future runs when the user has an expired enrollment', () => {
// eslint-disable-next-line no-undef
const newRun = $.extend({}, course.course_runs[0]);
const newRunKey = 'course-v1:foo+bar+baz';
const advertisedStart = 'Summer';

View File

@@ -32,6 +32,7 @@ describe('Course Enroll View', () => {
beforeEach(() => {
// Stub analytics tracking
// eslint-disable-next-line no-undef
window.analytics = jasmine.createSpyObj('analytics', ['track']);
// NOTE: This data is redefined prior to each test case so that tests
@@ -115,6 +116,7 @@ describe('Course Enroll View', () => {
urlModel = new Backbone.Model(urlMap);
}
view = new CourseEnrollView({
// eslint-disable-next-line no-undef
$parentEl: $('.course-actions'),
model: courseCardModel,
enrollModel: courseEnrollModel,
@@ -184,6 +186,7 @@ describe('Course Enroll View', () => {
expect(view.$('.enroll-button').length).toBe(1);
// eslint-disable-next-line no-undef
spyOn(courseEnrollModel, 'save');
view.$('.enroll-button').click();
@@ -194,6 +197,7 @@ describe('Course Enroll View', () => {
it('should enroll learner when enroll button is clicked with multiple course runs available', () => {
setupView(multiCourseRunList);
// eslint-disable-next-line no-undef
spyOn(courseEnrollModel, 'save');
view.$('.run-select').val(multiCourseRunList[1].key);
@@ -212,6 +216,7 @@ describe('Course Enroll View', () => {
expect(view.$('.enroll-button').length).toBe(1);
expect(view.trackSelectionUrl).toBeDefined();
// eslint-disable-next-line no-undef
spyOn(CourseEnrollView, 'redirect');
view.enrollSuccess();
@@ -230,6 +235,7 @@ describe('Course Enroll View', () => {
expect(view.$('.enroll-button').length).toBe(1);
expect(view.trackSelectionUrl).toBeDefined();
// eslint-disable-next-line no-undef
spyOn(CourseEnrollView, 'redirect');
view.enrollSuccess();
@@ -250,6 +256,7 @@ describe('Course Enroll View', () => {
expect(view.dashboardUrl).not.toBeDefined();
expect(view.trackSelectionUrl).not.toBeDefined();
// eslint-disable-next-line no-undef
spyOn(CourseEnrollView, 'redirect');
view.enrollSuccess();
@@ -263,6 +270,7 @@ describe('Course Enroll View', () => {
expect(view.$('.enroll-button').length).toBe(1);
expect(view.trackSelectionUrl).toBeDefined();
// eslint-disable-next-line no-undef
spyOn(CourseEnrollView, 'redirect');
view.enrollError(courseEnrollModel, { status: 500 });
@@ -284,6 +292,7 @@ describe('Course Enroll View', () => {
expect(view.$('.enroll-button').length).toBe(1);
expect(view.trackSelectionUrl).toBeDefined();
// eslint-disable-next-line no-undef
spyOn(CourseEnrollView, 'redirect');
view.enrollError(courseEnrollModel, response);
@@ -295,6 +304,7 @@ describe('Course Enroll View', () => {
it('sends analytics event when enrollment succeeds', () => {
setupView(singleCourseRunList, urls);
// eslint-disable-next-line no-undef
spyOn(CourseEnrollView, 'redirect');
view.enrollSuccess();
expect(window.analytics.track).toHaveBeenCalledWith(

View File

@@ -36,10 +36,15 @@ describe('EntitlementUnenrollmentView', () => {
describe('when an unenroll link is clicked', () => {
it('should reset the modal and set the correct values for header/submit', () => {
// eslint-disable-next-line no-undef
const $link1 = $('#link1');
// eslint-disable-next-line no-undef
const $link2 = $('#link2');
// eslint-disable-next-line no-undef
const $headerTxt = $('.js-entitlement-unenrollment-modal-header-text');
// eslint-disable-next-line no-undef
const $errorTxt = $('.js-entitlement-unenrollment-modal-error-text');
// eslint-disable-next-line no-undef
const $submitBtn = $('.js-entitlement-unenrollment-modal-submit');
$link1.trigger('click');
@@ -66,11 +71,13 @@ describe('EntitlementUnenrollmentView', () => {
describe('when the unenroll submit button is clicked', () => {
it('should send a DELETE request to the configured apiEndpoint', () => {
// eslint-disable-next-line no-undef
const $submitBtn = $('.js-entitlement-unenrollment-modal-submit');
const apiEndpoint = '/test/api/endpoint/1';
view.setSubmitData(apiEndpoint);
// eslint-disable-next-line no-undef
spyOn($, 'ajax').and.callFake((opts) => {
expect(opts.url).toEqual(apiEndpoint);
expect(opts.method).toEqual('DELETE');
@@ -78,11 +85,14 @@ describe('EntitlementUnenrollmentView', () => {
});
$submitBtn.trigger('click');
// eslint-disable-next-line no-undef
expect($.ajax).toHaveBeenCalled();
});
it('should set an error and disable submit if the apiEndpoint has not been properly set', () => {
// eslint-disable-next-line no-undef
const $errorTxt = $('.js-entitlement-unenrollment-modal-error-text');
// eslint-disable-next-line no-undef
const $submitBtn = $('.js-entitlement-unenrollment-modal-submit');
expect($submitBtn.data()).toEqual({});
@@ -90,8 +100,10 @@ describe('EntitlementUnenrollmentView', () => {
expect($errorTxt.html()).toEqual('');
expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(false);
// eslint-disable-next-line no-undef
spyOn($, 'ajax');
$submitBtn.trigger('click');
// eslint-disable-next-line no-undef
expect($.ajax).not.toHaveBeenCalled();
expect($submitBtn.data()).toEqual({});
@@ -102,11 +114,13 @@ describe('EntitlementUnenrollmentView', () => {
describe('when the unenroll request is complete', () => {
it('should redirect to the dashboard if the request was successful', () => {
// eslint-disable-next-line no-undef
const $submitBtn = $('.js-entitlement-unenrollment-modal-submit');
const apiEndpoint = '/test/api/endpoint/1';
view.setSubmitData(apiEndpoint);
// eslint-disable-next-line no-undef
spyOn($, 'ajax').and.callFake((opts) => {
expect(opts.url).toEqual(apiEndpoint);
expect(opts.method).toEqual('DELETE');
@@ -117,19 +131,23 @@ describe('EntitlementUnenrollmentView', () => {
responseJSON: { detail: 'success' },
});
});
// eslint-disable-next-line no-undef
spyOn(EntitlementUnenrollmentView, 'redirectTo');
$submitBtn.trigger('click');
// eslint-disable-next-line no-undef
expect($.ajax).toHaveBeenCalled();
expect(EntitlementUnenrollmentView.redirectTo).toHaveBeenCalledWith(view.dashboardPath);
});
it('should redirect to the login page if the request failed with an auth error', () => {
// eslint-disable-next-line no-undef
const $submitBtn = $('.js-entitlement-unenrollment-modal-submit');
const apiEndpoint = '/test/api/endpoint/1';
view.setSubmitData(apiEndpoint);
// eslint-disable-next-line no-undef
spyOn($, 'ajax').and.callFake((opts) => {
expect(opts.url).toEqual(apiEndpoint);
expect(opts.method).toEqual('DELETE');
@@ -140,9 +158,11 @@ describe('EntitlementUnenrollmentView', () => {
responseJSON: { detail: 'Authentication credentials were not provided.' },
});
});
// eslint-disable-next-line no-undef
spyOn(EntitlementUnenrollmentView, 'redirectTo');
$submitBtn.trigger('click');
// eslint-disable-next-line no-undef
expect($.ajax).toHaveBeenCalled();
expect(EntitlementUnenrollmentView.redirectTo).toHaveBeenCalledWith(
`${view.signInPath}?next=${encodeURIComponent(view.dashboardPath)}`,
@@ -150,12 +170,15 @@ describe('EntitlementUnenrollmentView', () => {
});
it('should set an error and disable submit if a non-auth error occurs', () => {
// eslint-disable-next-line no-undef
const $errorTxt = $('.js-entitlement-unenrollment-modal-error-text');
// eslint-disable-next-line no-undef
const $submitBtn = $('.js-entitlement-unenrollment-modal-submit');
const apiEndpoint = '/test/api/endpoint/1';
view.setSubmitData(apiEndpoint);
// eslint-disable-next-line no-undef
spyOn($, 'ajax').and.callFake((opts) => {
expect(opts.url).toEqual(apiEndpoint);
expect(opts.method).toEqual('DELETE');
@@ -166,6 +189,7 @@ describe('EntitlementUnenrollmentView', () => {
responseJSON: { detail: 'Bad request.' },
});
});
// eslint-disable-next-line no-undef
spyOn(EntitlementUnenrollmentView, 'redirectTo');
expect($submitBtn.prop('disabled')).toBe(false);
@@ -178,6 +202,7 @@ describe('EntitlementUnenrollmentView', () => {
expect($errorTxt.html()).toEqual(view.genericErrorMsg);
expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(true);
// eslint-disable-next-line no-undef
expect($.ajax).toHaveBeenCalled();
expect(EntitlementUnenrollmentView.redirectTo).not.toHaveBeenCalled();
});

View File

@@ -40,19 +40,19 @@ describe('Program Alert List View', () => {
it('should render no enrollement alert', () => {
expect(view.$('.alert:first .alert-heading').text().trim()).toEqual(
'Enroll in a Test Program\'s course'
'Enroll in a Test Program\'s course',
);
expect(view.$('.alert:first .alert-message').text().trim()).toEqual(
'You have an active subscription to the Test Program program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.'
'You have an active subscription to the Test Program program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.',
);
});
it('should render subscription trial is expiring alert', () => {
expect(view.$('.alert:last .alert-heading').text().trim()).toEqual(
'Subscription trial expires in 2 days'
'Subscription trial expires in 2 days',
);
expect(view.$('.alert:last .alert-message').text().trim()).toEqual(
'Your Test Program trial will expire in 2 days at 5:59 am on Apr 20, 2023 and the card on file will be charged $100/month USD.'
'Your Test Program trial will expire in 2 days at 5:59 am on Apr 20, 2023 and the card on file will be charged $100/month USD.',
);
});
});

View File

@@ -58,6 +58,7 @@ describe('Program card View', () => {
not_started: 3,
},
];
// eslint-disable-next-line no-undef
const subscriptionCollection = new Backbone.Collection([{
resource_id: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8',
subscription_state: 'active',
@@ -99,6 +100,7 @@ describe('Program card View', () => {
});
it('should call reEvaluatePicture if reLoadBannerImage is called', () => {
// eslint-disable-next-line no-undef
spyOn(ProgramCardView, 'reEvaluatePicture');
view.reLoadBannerImage();
expect(ProgramCardView.reEvaluatePicture).toHaveBeenCalled();
@@ -107,6 +109,7 @@ describe('Program card View', () => {
it('should handle exceptions from reEvaluatePicture', () => {
const message = 'Picturefill had exceptions';
// eslint-disable-next-line no-undef
spyOn(ProgramCardView, 'reEvaluatePicture').and.callFake(() => {
const error = { name: message };

View File

@@ -56,7 +56,9 @@ describe('Program Progress View', () => {
const testSubscriptionState = (state, heading, body) => {
isSubscriptionEligible = true;
subscriptionData.subscription_state = state;
// eslint-disable-next-line no-use-before-define
view = initView();
// eslint-disable-next-line no-param-reassign
body += ' on the <a class="subscription-link" href="/orders">Orders and subscriptions</a> page';
expect(view.$('.js-subscription-info')[0]).toBeInDOM();
@@ -164,6 +166,7 @@ describe('Program Progress View', () => {
expect(view.$('.course-list-heading').html()).toEqual('Earned Certificates');
expect($certificates).toHaveLength(certificateCollection.length);
$certificates.each((i, el) => {
// eslint-disable-next-line no-undef
const $link = $(el).find('.certificate-link');
const model = certificateCollection.at(i);

View File

@@ -527,7 +527,9 @@ describe('Program Details View', () => {
'YYYY-MM-DDTHH:mm:ss[Z]',
);
}
// eslint-disable-next-line no-use-before-define
view = initView({
// eslint-disable-next-line no-undef
programData: $.extend({}, options.programData, {
subscription_eligible: true,
}),
@@ -543,6 +545,7 @@ describe('Program Details View', () => {
};
const initView = (updates) => {
// eslint-disable-next-line no-undef
const viewOptions = $.extend({}, options, updates);
return new ProgramDetailsView(viewOptions);
@@ -588,6 +591,7 @@ describe('Program Details View', () => {
it('should render the program heading congratulations message if all courses completed', () => {
view = initView({
// Remove remaining courses so all courses are complete
// eslint-disable-next-line no-undef
courseData: $.extend({}, options.courseData, {
in_progress: [],
not_started: [],
@@ -614,26 +618,34 @@ describe('Program Details View', () => {
it('should render the basic course card information', () => {
view = initView();
view.render();
// eslint-disable-next-line no-undef
expect($(view.$('.course-title')[0]).text().trim()).toEqual('Star Trek: The Next Generation');
// eslint-disable-next-line no-undef
expect($(view.$('.enrolled')[0]).text().trim()).toEqual('Enrolled:');
// eslint-disable-next-line no-undef
expect($(view.$('.run-period')[0]).text().trim()).toEqual('Mar 20, 2017 - Mar 31, 2017');
});
it('should render certificate information', () => {
view = initView();
view.render();
// eslint-disable-next-line no-undef
expect($(view.$('.upgrade-message .card-msg')).text().trim()).toEqual('Certificate Status:');
// eslint-disable-next-line no-undef
expect($(view.$('.upgrade-message .price')).text().trim()).toEqual('$10.00');
// eslint-disable-next-line no-undef
expect($(view.$('.upgrade-button.single-course-run')[0]).text().trim()).toEqual('Upgrade to Verified');
});
it('should render full program purchase link', () => {
view = initView({
// eslint-disable-next-line no-undef
programData: $.extend({}, options.programData, {
is_learner_eligible_for_one_click_purchase: true,
}),
});
view.render();
// eslint-disable-next-line no-undef
expect($(view.$('.upgrade-button.complete-program')).text().trim()
.replace(/\s+/g, ' '))
.toEqual(
@@ -643,6 +655,7 @@ describe('Program Details View', () => {
it('should render partial program purchase link', () => {
view = initView({
// eslint-disable-next-line no-undef
programData: $.extend({}, options.programData, {
is_learner_eligible_for_one_click_purchase: true,
discount_data: {
@@ -655,6 +668,7 @@ describe('Program Details View', () => {
}),
});
view.render();
// eslint-disable-next-line no-undef
expect($(view.$('.upgrade-button.complete-program')).text().trim()
.replace(/\s+/g, ' '))
.toEqual(
@@ -666,7 +680,9 @@ describe('Program Details View', () => {
view = initView();
view.render();
expect(view.$('.run-select')[0].options.length).toEqual(2);
// eslint-disable-next-line no-undef
expect($(view.$('.select-choice')[0]).attr('for')).toEqual($(view.$('.run-select')[0]).attr('id'));
// eslint-disable-next-line no-undef
expect($(view.$('.enroll-button button')[0]).text().trim()).toEqual('Enroll Now');
});
@@ -677,12 +693,14 @@ describe('Program Details View', () => {
uuid: '0ffff5d6-0177-4690-9a48-aa2fecf94610',
};
view = initView({
// eslint-disable-next-line no-undef
programData: $.extend({}, options.programData, {
is_learner_eligible_for_one_click_purchase: true,
variant: 'partial',
}),
});
view.render();
// eslint-disable-next-line no-undef
$('.complete-program').click();
// Verify that analytics event fires when the purchase button is clicked.
expect(window.analytics.track).toHaveBeenCalledWith(

View File

@@ -3,6 +3,7 @@
import Backbone from 'backbone';
import ProgressCollection from '../collections/program_progress_collection';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import ProgramListHeaderView from '../views/program_list_header_view';
describe('Program List Header View', () => {
@@ -54,10 +55,10 @@ describe('Program List Header View', () => {
beforeEach(() => {
context.subscriptionCollection = new Backbone.Collection(
context.programsSubscriptionData
context.programsSubscriptionData,
);
context.progressCollection = new ProgressCollection(
context.userProgress
context.userProgress,
);
setFixtures('<div class="js-program-list-header"></div>');
view = new ProgramListHeaderView({
@@ -80,15 +81,15 @@ describe('Program List Header View', () => {
it('should render a program alert', () => {
expect(
view.$('.js-program-list-alerts .alert .alert-heading').html().trim()
view.$('.js-program-list-alerts .alert .alert-heading').html().trim(),
).toEqual('Enroll in a Test Program\'s course');
expect(
view.$('.js-program-list-alerts .alert .alert-message')
view.$('.js-program-list-alerts .alert .alert-message'),
).toContainHtml(
'According to our records, you are not enrolled in any courses included in your Test Program program subscription. Enroll in a course from the <i>Program Details</i> page.'
'According to our records, you are not enrolled in any courses included in your Test Program program subscription. Enroll in a course from the <i>Program Details</i> page.',
);
expect(
view.$('.js-program-list-alerts .alert .view-button').attr('href')
view.$('.js-program-list-alerts .alert .view-button').attr('href'),
).toEqual('/dashboard/programs/b90d70d5-f981-4508-bdeb-5b792d930c03/');
});
});

View File

@@ -41,6 +41,7 @@ describe('Progress Circle View', () => {
});
const initView = (progress) => {
// eslint-disable-next-line no-undef
const data = $.extend({}, context, {
progress,
});

View File

@@ -39,7 +39,7 @@ describe('Sidebar View', () => {
context: {
...context,
isUserB2CSubscriptionsEnabled: false,
}
},
});
view.render();
expect(view.$('.js-subscription-upsell')[0]).not.toBeInDOM();
@@ -53,7 +53,7 @@ describe('Sidebar View', () => {
.toMatch(/^Monthly program subscriptions . more flexible, more affordable$/);
expect(view.$('.js-subscription-upsell .advertise-message').html().trim())
.toEqual(
'Now available for many popular programs, affordable monthly subscription pricing can help you manage your budget more effectively. Subscriptions start at $39/month USD per program, after a 7-day full access free trial. Cancel at any time.'
'Now available for many popular programs, affordable monthly subscription pricing can help you manage your budget more effectively. Subscriptions start at $39/month USD per program, after a 7-day full access free trial. Cancel at any time.',
);
expect(view.$('.js-subscription-upsell a span:last').html().trim())
.toEqual('Explore subscription options');
@@ -64,7 +64,7 @@ describe('Sidebar View', () => {
it('should load the exploration panel given a marketing URL', () => {
expect(view.$('.program-advertise .advertise-message').html().trim())
.toEqual(
'Browse recently launched courses and see what\'s new in your favorite subjects'
'Browse recently launched courses and see what\'s new in your favorite subjects',
);
expect(view.$('.program-advertise a').attr('href'))
.toEqual(context.marketingUrl);

View File

@@ -29,11 +29,15 @@ describe('Unenroll View', () => {
it('switch between slides', () => {
view = initView();
// eslint-disable-next-line no-undef
expect($('.slide1').hasClass('hidden')).toEqual(true);
view.switchToSlideOne();
// eslint-disable-next-line no-undef
expect($('.slide1').hasClass('hidden')).toEqual(false);
// eslint-disable-next-line no-undef
expect($('.slide2').hasClass('hidden')).toEqual(true);
view.switchToSlideTwo();
// eslint-disable-next-line no-undef
expect($('.slide2').hasClass('hidden')).toEqual(false);
});
});

View File

@@ -16,6 +16,7 @@ class CertificateStatusView extends Backbone.View {
render() {
let data = this.model.toJSON();
// eslint-disable-next-line no-undef
data = $.extend(data, { certificateSvg: this.iconTpl() });
HtmlUtils.setHtml(this.$el, this.statusTpl(data));
}

View File

@@ -18,6 +18,7 @@ class CourseCardView extends Backbone.View {
const defaults = {
className: 'program-course-card',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -40,6 +41,7 @@ class CourseCardView extends Backbone.View {
}
render() {
// eslint-disable-next-line no-undef
const data = $.extend(this.model.toJSON(), {
enrolled: this.context.enrolled || '',
});

View File

@@ -13,6 +13,7 @@ class CourseEnrollView extends Backbone.View {
'change .run-select': 'updateEnrollUrl',
},
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -94,8 +95,10 @@ class CourseEnrollView extends Backbone.View {
updateEnrollUrl() {
if (this.model.get('is_mobile_only') === true) {
// eslint-disable-next-line no-undef
const courseRunKey = $('.run-select').val();
const href = `edxapp://enroll?course_id=${courseRunKey}&email_opt_in=true`;
// eslint-disable-next-line no-undef
$('.enroll-course-button').attr('href', href);
}
}

View File

@@ -23,6 +23,7 @@ class CourseEntitlementView extends Backbone.View {
'click .popover-dismiss': 'hideDialog',
},
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -51,16 +52,23 @@ class CourseEntitlementView extends Backbone.View {
// Grab elements from the parent card that work with this view
this.$parentEl = options.$parentEl; // Containing course card (must be a backbone view root el)
// eslint-disable-next-line no-undef
this.$enterCourseBtn = $(options.enterCourseBtn); // Button link to course home page
// eslint-disable-next-line no-undef
this.$courseCardMessages = $(options.courseCardMessages); // Additional session messages
// eslint-disable-next-line no-undef
this.$courseTitleLink = $(options.courseTitleLink); // Title link to course home page
// eslint-disable-next-line no-undef
this.$courseImageLink = $(options.courseImageLink); // Image link to course home page
// eslint-disable-next-line no-undef
this.$policyMsg = $(options.policyMsg); // Message for policy information
// Bind action elements with associated events to objects outside this view
this.$dateDisplayField = this.$parentEl ? this.$parentEl.find(options.dateDisplayField)
// eslint-disable-next-line no-undef
: $(options.dateDisplayField); // Displays current session dates
this.$triggerOpenBtn = this.$parentEl ? this.$parentEl.find(options.triggerOpenBtn)
// eslint-disable-next-line no-undef
: $(options.triggerOpenBtn); // Opens/closes session selection view
this.$triggerOpenBtn.on('click', this.toggleSessionSelectionPanel.bind(this));
@@ -77,14 +85,17 @@ class CourseEntitlementView extends Backbone.View {
postRender() {
// Close any visible popovers on click-away
// eslint-disable-next-line no-undef
$(document).on('click', (e) => {
if (this.$('.popover:visible').length
// eslint-disable-next-line no-undef
&& !($(e.target).closest('.enroll-btn-initial, .popover').length)) {
this.hideDialog(this.$('.enroll-btn-initial'));
}
});
// Initialize focus to cancel button on popover load
// eslint-disable-next-line no-undef
$(document).on('shown.bs.popover', () => {
this.$('.final-confirmation-btn:first').focus();
});
@@ -110,6 +121,7 @@ class CourseEntitlementView extends Backbone.View {
HtmlUtils.HTML('<span class="fa fa-spinner fa-spin" aria-hidden="true"></span>'),
);
// eslint-disable-next-line no-undef
$.ajax({
type: isLeavingSession ? 'DELETE' : 'POST',
url: this.enrollUrl,
@@ -330,6 +342,7 @@ class CourseEntitlementView extends Backbone.View {
removeDialog(el) {
/* Removes the Bootstrap v4 dialog modal from the update session enrollment button. */
// eslint-disable-next-line no-undef
const $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
if (this.$('popover').length) {
$el.popover('dispose');
@@ -338,6 +351,7 @@ class CourseEntitlementView extends Backbone.View {
hideDialog(el, returnFocus) {
/* Hides the modal if it is visible without removing it from the DOM. */
// eslint-disable-next-line no-undef
const $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
if (this.$('.popover:visible').length) {
$el.popover('hide');
@@ -350,7 +364,9 @@ class CourseEntitlementView extends Backbone.View {
handleVerificationPopoverA11y(e) {
/* Ensure that the second step verification popover is treated as an a11y compliant dialog */
let $nextButton;
// eslint-disable-next-line no-undef
const $verificationOption = $(e.target);
// eslint-disable-next-line no-undef
const openButton = $(e.target).closest('.course-entitlement-selection-container')
.find('.enroll-btn-initial');
if (e.key === 'Tab') {

View File

@@ -9,6 +9,7 @@ class EntitlementUnenrollmentView extends Backbone.View {
const defaults = {
el: '.js-entitlement-unenrollment-modal',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -29,14 +30,20 @@ class EntitlementUnenrollmentView extends Backbone.View {
this.browseCourses = options.browseCourses;
this.isEdx = options.isEdx;
// eslint-disable-next-line no-undef
this.$submitButton = $(this.submitButtonSelector);
// eslint-disable-next-line no-undef
this.$closeButton = $(this.closeButtonSelector);
// eslint-disable-next-line no-undef
this.$headerText = $(this.headerTextSelector);
// eslint-disable-next-line no-undef
this.$errorText = $(this.errorTextSelector);
this.$submitButton.on('click', this.handleSubmit.bind(this));
// eslint-disable-next-line no-undef
$(this.triggerSelector).each(function setUpTrigger() {
// eslint-disable-next-line no-undef
const $trigger = $(this);
$trigger.on('click', view.handleTrigger.bind(view));
@@ -54,6 +61,7 @@ class EntitlementUnenrollmentView extends Backbone.View {
}
handleTrigger(event) {
// eslint-disable-next-line no-undef
const $trigger = $(event.target);
const courseName = $trigger.data('courseName');
const courseNumber = $trigger.data('courseNumber');
@@ -76,6 +84,7 @@ class EntitlementUnenrollmentView extends Backbone.View {
}
this.$submitButton.prop('disabled', true);
// eslint-disable-next-line no-undef
$.ajax({
url: apiEndpoint,
method: 'DELETE',
@@ -170,6 +179,7 @@ class EntitlementUnenrollmentView extends Backbone.View {
}
onComplete(xhr) {
// eslint-disable-next-line prefer-destructuring
const status = xhr.status;
const message = xhr.responseJSON && xhr.responseJSON.detail;

View File

@@ -9,12 +9,14 @@ class ExploreNewProgramsView extends Backbone.View {
const defaults = {
el: '.program-advertise',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
initialize(data) {
this.tpl = HtmlUtils.template(exploreTpl);
this.context = data.context;
// eslint-disable-next-line no-undef
this.$parentEl = $(this.parentEl);
if (this.context.marketingUrl) {

View File

@@ -11,6 +11,7 @@ class ProgramAlertListView extends Backbone.View {
const defaults = {
el: '.js-program-details-alerts',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -34,45 +35,53 @@ class ProgramAlertListView extends Backbone.View {
const alertList = this.enrollmentAlerts.map(
({ title: programName, url }) => ({
url,
// eslint-disable-next-line no-undef
urlText: gettext('View program'),
title: StringUtils.interpolate(
// eslint-disable-next-line no-undef
gettext('Enroll in a {programName}\'s course'),
{ programName }
{ programName },
),
message: this.pageType === 'programDetails'
? StringUtils.interpolate(
// eslint-disable-next-line no-undef
gettext('You have an active subscription to the {programName} program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.'),
{ programName }
{ programName },
)
: HtmlUtils.interpolateHtml(
// eslint-disable-next-line no-undef
gettext('According to our records, you are not enrolled in any courses included in your {programName} program subscription. Enroll in a course from the {i_start}Program Details{i_end} page.'),
{
programName,
i_start: HtmlUtils.HTML('<i>'),
i_end: HtmlUtils.HTML('</i>'),
}
},
),
})
}),
);
return alertList.concat(this.trialEndingAlerts.map(
({ title: programName, remainingDays, ...data }) => ({
title: StringUtils.interpolate(
remainingDays < 1
// eslint-disable-next-line no-undef
? gettext('Subscription trial expires in less than 24 hours')
// eslint-disable-next-line no-undef
: ngettext('Subscription trial expires in {remainingDays} day', 'Subscription trial expires in {remainingDays} days', remainingDays),
{ remainingDays }
{ remainingDays },
),
message: StringUtils.interpolate(
remainingDays < 1
// eslint-disable-next-line no-undef
? gettext('Your {programName} trial will expire at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.')
// eslint-disable-next-line no-undef
: ngettext('Your {programName} trial will expire in {remainingDays} day at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.', 'Your {programName} trial will expire in {remainingDays} days at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.', remainingDays),
{
programName,
remainingDays,
...data,
}
},
),
})
}),
));
}
}

View File

@@ -18,6 +18,7 @@ class ProgramCardView extends Backbone.View {
};
},
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -38,6 +39,7 @@ class ProgramCardView extends Backbone.View {
render() {
const orgList = this.model.get('authoring_organizations').map(org => gettext(org.key));
// eslint-disable-next-line no-undef
const data = $.extend(
this.model.toJSON(),
this.getProgramProgress(),

View File

@@ -18,6 +18,7 @@ class ProgramDetailsSidebarView extends Backbone.View {
'click .pathway-button': 'trackPathwayClicked',
},
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -37,6 +38,7 @@ class ProgramDetailsSidebarView extends Backbone.View {
}
render() {
// eslint-disable-next-line no-undef
const data = $.extend(
{},
this.model.toJSON(),
@@ -114,7 +116,9 @@ class ProgramDetailsSidebarView extends Backbone.View {
// Credentials uses the uuid without dashes so we are converting here for consistency
program_uuid: this.programModel.attributes.uuid.replace(/-/g, ''),
program_name: this.programModel.attributes.title,
// eslint-disable-next-line no-undef
pathway_link_uuid: $(button).data('pathwayUuid').replace(/-/g, ''),
// eslint-disable-next-line no-undef
pathway_name: $(button).data('pathwayName'),
});
}

View File

@@ -7,10 +7,12 @@ import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
import CollectionListView from './collection_list_view';
import CourseCardCollection from '../collections/course_card_collection';
import CourseCardView from './course_card_view';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import HeaderView from './program_header_view';
import SidebarView from './program_details_sidebar_view';
import AlertListView from './program_alert_list_view';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import SubscriptionModel from '../models/program_subscription_model';
import launchIcon from '../../../images/launch-icon.svg';
@@ -28,6 +30,7 @@ class ProgramDetailsView extends Backbone.View {
'click .js-subscription-cta': 'trackSubscriptionCTA',
},
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -66,6 +69,7 @@ class ProgramDetailsView extends Backbone.View {
this.render();
// eslint-disable-next-line no-undef
const $courseUpsellButton = $('#program_dashboard_course_upsell_all_button');
trackECommerceEvents.trackUpsellClick($courseUpsellButton, 'program_dashboard_program', {
linkType: 'button',
@@ -107,6 +111,7 @@ class ProgramDetailsView extends Backbone.View {
launchIcon,
restartIcon,
};
// eslint-disable-next-line no-undef
data = $.extend(
data,
this.programModel.toJSON(),
@@ -140,6 +145,7 @@ class ProgramDetailsView extends Backbone.View {
el: '.js-course-list-remaining',
childView: CourseCardView,
collection: this.remainingCourseCollection,
// eslint-disable-next-line no-undef
context: $.extend(this.options, { collectionCourseStatus: 'remaining' }),
}).render();
}
@@ -149,6 +155,7 @@ class ProgramDetailsView extends Backbone.View {
el: '.js-course-list-completed',
childView: CourseCardView,
collection: this.completedCourseCollection,
// eslint-disable-next-line no-undef
context: $.extend(this.options, { collectionCourseStatus: 'completed' }),
}).render();
}
@@ -159,6 +166,7 @@ class ProgramDetailsView extends Backbone.View {
el: '.js-course-list-in-progress',
childView: CourseCardView,
collection: this.inProgressCourseCollection,
// eslint-disable-next-line no-undef
context: $.extend(
this.options,
{ enrolled: gettext('Enrolled'), collectionCourseStatus: 'in_progress' },
@@ -179,8 +187,10 @@ class ProgramDetailsView extends Backbone.View {
urls: this.options.urls,
});
let hasIframe = false;
// eslint-disable-next-line no-undef
$('#live-tab').click(() => {
if (!hasIframe) {
// eslint-disable-next-line no-undef
$('#live').html(HtmlUtils.HTML(this.options.live_fragment.iframe).toString());
hasIframe = true;
}
@@ -194,8 +204,8 @@ class ProgramDetailsView extends Backbone.View {
];
const isSomeCoursePurchasable = courseCollections.some((collection) => (
collection.some((course) => (
course.get('upgrade_url') &&
!(course.get('expired') === true)
course.get('upgrade_url')
&& !(course.get('expired') === true)
))
));
const programPurchasedWithoutSubscription = (
@@ -224,8 +234,8 @@ class ProgramDetailsView extends Backbone.View {
});
}
if (
this.subscriptionModel.get('remainingDays') <= 7 &&
this.subscriptionModel.get('hasActiveTrial')
this.subscriptionModel.get('remainingDays') <= 7
&& this.subscriptionModel.get('hasActiveTrial')
) {
alerts.trialEndingAlerts.push({
title: this.programModel.get('title'),
@@ -251,7 +261,7 @@ class ProgramDetailsView extends Backbone.View {
if (state === 'active') {
window.analytics.track(
'edx.bi.user.subscription.program-detail-page.manage.clicked',
this.subscriptionEventParams
this.subscriptionEventParams,
);
} else {
const isNewSubscription = state !== 'inactive';
@@ -262,7 +272,7 @@ class ProgramDetailsView extends Backbone.View {
is_new_subscription: isNewSubscription,
is_trial_eligible: isNewSubscription,
...this.subscriptionEventParams,
}
},
);
}
}
@@ -271,7 +281,7 @@ class ProgramDetailsView extends Backbone.View {
if (this.options.isSubscriptionEligible) {
window.analytics.track(
'edx.bi.user.subscription.program-detail-page.viewed',
this.subscriptionEventParams
this.subscriptionEventParams,
);
}
}

View File

@@ -12,6 +12,7 @@ class ProgramHeaderView extends Backbone.View {
const defaults = {
el: '.js-program-header',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -27,6 +28,7 @@ class ProgramHeaderView extends Backbone.View {
}
getLogo() {
// eslint-disable-next-line prefer-destructuring
const type = this.model.get('programData').type;
let logo = false;
@@ -51,6 +53,7 @@ class ProgramHeaderView extends Backbone.View {
}
render() {
// eslint-disable-next-line no-undef
const data = $.extend(this.model.toJSON(), {
breakpoints: this.breakpoints,
logo: this.getLogo(),

View File

@@ -26,6 +26,7 @@ class ProgressCircleView extends Backbone.View {
}
render() {
// eslint-disable-next-line no-undef
const data = $.extend({}, this.model.toJSON(), {
circleSegments: this.getProgressSegments(),
x: this.x,
@@ -62,6 +63,7 @@ class ProgressCircleView extends Backbone.View {
};
for (let i = 0; i < total; i += 1) {
// eslint-disable-next-line no-undef
const segmentData = $.extend({}, data, {
classList: (i >= this.model.get('progress').completed) ? 'incomplete' : 'complete',
degrees: data.degrees + (i * degreeInc),

View File

@@ -15,6 +15,7 @@ class SidebarView extends Backbone.View {
'click .js-subscription-upsell-cta ': 'trackSubscriptionUpsellCTA',
},
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -42,7 +43,7 @@ class SidebarView extends Backbone.View {
trackSubscriptionUpsellCTA() {
window.analytics.track(
'edx.bi.user.subscription.program-dashboard.upsell.clicked'
'edx.bi.user.subscription.program-dashboard.upsell.clicked',
);
}
}

View File

@@ -9,6 +9,7 @@ class SubscriptionUpsellView extends Backbone.View {
const defaults = {
el: '.js-subscription-upsell',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}

View File

@@ -7,6 +7,7 @@ class UnenrollView extends Backbone.View {
const defaults = {
el: '.unenroll-modal',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
@@ -37,6 +38,7 @@ class UnenrollView extends Backbone.View {
switchToSlideTwo() {
let reason = this.$(".reasons_survey input[name='reason']:checked").attr('val');
// eslint-disable-next-line no-undef
const courserunKey = $('#unenroll_course_id').val() + $('#unenroll_course_number').val();
if (reason === 'Other') {
reason = this.$('.other_text').val();
@@ -69,20 +71,25 @@ class UnenrollView extends Backbone.View {
unenrollComplete(event, xhr) {
if (xhr.status === 200) {
if (!this.isEdx) {
// eslint-disable-next-line no-restricted-globals
location.href = this.urls.dashboard;
} else {
this.switchToSlideOne();
this.$('.reasons_survey:first .submit_reasons').click(this.switchToSlideTwo.bind(this));
}
} else if (xhr.status === 400) {
// eslint-disable-next-line no-undef
$('#unenroll_error').text(
xhr.responseText,
).stop()
.css('display', 'block');
} else if (xhr.status === 403) {
// eslint-disable-next-line no-restricted-globals
location.href = `${this.urls.signInUser}?course_id=${
// eslint-disable-next-line no-undef
encodeURIComponent($('#unenroll_course_id').val())}&enrollment_action=unenroll`;
} else {
// eslint-disable-next-line no-undef
$('#unenroll_error').text(
gettext('Unable to determine whether we should give you a refund because'
+ ' of System Error. Please try again later.'),
@@ -101,16 +108,20 @@ class UnenrollView extends Backbone.View {
this.isEdx = options.isEdx;
this.closeButtonSelector = '.unenroll-modal .close-modal';
// eslint-disable-next-line no-undef
this.$closeButton = $(this.closeButtonSelector);
this.modalId = `#${this.$el.attr('id')}`;
this.mainPageSelector = '#dashboard-main';
this.triggerSelector = '.action-unenroll';
// eslint-disable-next-line no-undef
$(this.triggerSelector).each((index, element) => {
// eslint-disable-next-line no-undef
$(element).on('click', view.handleTrigger.bind($(element)));
});
this.$('.submit .submit-button').on('click', this.startSubmit.bind(this));
// eslint-disable-next-line no-undef
$('#unenroll_form').on('ajax:complete', this.unenrollComplete.bind(this));
}
}

Some files were not shown because too many files have changed in this diff Show More