From e581bd9081172a2ddacb9788c730ba6cdd5f814d Mon Sep 17 00:00:00 2001 From: Radi Totev Date: Fri, 3 Jun 2022 17:57:52 +0100 Subject: [PATCH] feat(client): shortcuts legend modal available on pressing the `?` key (#45530) * Add modal texts in translations.json file * Add shortcuts modal state in redux * Create shortcuts modal * Integrate shortcuts modal * Resolve codefactor.io issues * Extend list of shortcuts Based on this comment: https://github.com/freeCodeCamp/freeCodeCamp/issues/36841#issuecomment-933310078 * Remove temporary placeholder for modal title * Open modal fom Hotkeys instead of learn.tsx As suggested in this comment: https://github.com/freeCodeCamp/freeCodeCamp/pull/45530#issuecomment-1101224993 * Complete list in transaltions * Change shortcut presentation fo better a11y Use table instead of list items as suggestedin this comment: https://github.com/freeCodeCamp/freeCodeCamp/pull/45530#issuecomment-1101796368 * Add aria-labelledby * Remove GAnalytics * Remove leftover style * Remove table caption * autofocus on modal close button * Improve modal a11y - Add requested changes from https://github.com/freeCodeCamp/freeCodeCamp/pull/45530#issuecomment-1104764766 - Leave autofocus and parent div role=dialog changes for later. (https://github.com/freeCodeCamp/freeCodeCamp/pull/45530#issuecomment-1107754148) * [WIP] Alllow users to turn off keyboard shortcuts * Add keyboard shortcuts switch in settings * Disable shortcuts * Remove toggle switch description * Refactor and cleanup * Remove close button from modal header Suggested by bbsmooth: https://github.com/freeCodeCamp/freeCodeCamp/pull/45530#issuecomment-1107861091 * Fix lint issues * Disable shortcuts * Disable shortcuts by default * Update challenge output test * Update challenge-hot-keys test * Disable shortcuts from inside handlers Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> --- api-server/src/common/models/user.json | 4 + api-server/src/server/boot/settings.js | 13 ++ .../src/server/utils/publicUserProps.js | 1 + client/i18n/locales/english/translations.json | 14 +- .../src/client-only-routes/show-settings.tsx | 7 + .../src/components/profile/profile.test.tsx | 1 + client/src/components/settings/about.tsx | 11 +- .../settings/keyboard-shortcuts.tsx | 38 ++++++ client/src/redux/index.js | 2 + .../src/redux/keyboard-shortcuts-mode-saga.js | 27 ++++ client/src/redux/prop-types.ts | 2 + .../src/templates/Challenges/classic/show.tsx | 2 + .../Challenges/components/Hotkeys.tsx | 77 +++++++---- .../Challenges/components/shortcuts-modal.css | 29 +++++ .../Challenges/components/shortcuts-modal.tsx | 121 ++++++++++++++++++ .../src/templates/Challenges/redux/index.js | 4 +- config/constants.js | 1 + .../integration/learn/challenge-hot-keys.js | 8 ++ .../integration/learn/challenges/output.js | 5 +- tools/scripts/seed/certifiedUserData.js | 1 + tools/scripts/seed/seedAuthUser.js | 3 +- 21 files changed, 339 insertions(+), 32 deletions(-) create mode 100644 client/src/components/settings/keyboard-shortcuts.tsx create mode 100644 client/src/redux/keyboard-shortcuts-mode-saga.js create mode 100644 client/src/templates/Challenges/components/shortcuts-modal.css create mode 100644 client/src/templates/Challenges/components/shortcuts-modal.tsx diff --git a/api-server/src/common/models/user.json b/api-server/src/common/models/user.json index 9469518ada7..9d279d3014c 100644 --- a/api-server/src/common/models/user.json +++ b/api-server/src/common/models/user.json @@ -317,6 +317,10 @@ "type": "boolean", "default": false }, + "keyboardShortcuts": { + "type": "boolean", + "default": false + }, "profileUI": { "type": "object", "default": { diff --git a/api-server/src/server/boot/settings.js b/api-server/src/server/boot/settings.js index 8d0bdf44c70..d1a4b4006cf 100644 --- a/api-server/src/server/boot/settings.js +++ b/api-server/src/server/boot/settings.js @@ -45,6 +45,11 @@ export default function settingsController(app) { api.put('/update-user-flag', ifNoUser401, updateUserFlag); api.put('/update-my-socials', ifNoUser401, updateMySocials); api.put('/update-my-sound', ifNoUser401, updateMySound); + api.put( + '/update-my-keyboard-shortcuts', + ifNoUser401, + updateMyKeyboardShortcuts + ); api.put('/update-my-honesty', ifNoUser401, updateMyHonesty); api.put('/update-my-quincy-email', ifNoUser401, updateMyQuincyEmail); @@ -234,6 +239,13 @@ function updateMySound(...args) { createUpdateUserProperties(buildUpdate, validate)(...args); } +function updateMyKeyboardShortcuts(...args) { + const buildUpdate = body => _.pick(body, 'keyboardShortcuts'); + const validate = ({ keyboardShortcuts }) => + typeof keyboardShortcuts === 'boolean'; + createUpdateUserProperties(buildUpdate, validate)(...args); +} + function updateMyHonesty(...args) { const buildUpdate = body => _.pick(body, 'isHonest'); const validate = ({ isHonest }) => isHonest === true; @@ -271,6 +283,7 @@ function updateUserFlag(req, res, next) { const allowedKeys = [ 'theme', 'sound', + 'keyboardShortcuts', 'isHonest', 'portfolio', 'sendQuincyEmail', diff --git a/api-server/src/server/utils/publicUserProps.js b/api-server/src/server/utils/publicUserProps.js index e6b8871e699..e4ff3dc9196 100644 --- a/api-server/src/server/utils/publicUserProps.js +++ b/api-server/src/server/utils/publicUserProps.js @@ -55,6 +55,7 @@ export const userPropsForSession = [ 'sendQuincyEmail', 'theme', 'sound', + 'keyboardShortcuts', 'completedChallengeCount', 'completedProjectCount', 'completedCertCount', diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 2c2a333fae1..9d2554f365d 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -145,7 +145,8 @@ "my-timeline": "My timeline", "my-donations": "My donations", "night-mode": "Night Mode", - "sound-mode": "Campfire Mode" + "sound-mode": "Campfire Mode", + "keyboard-shortcuts": "Enable Keyboard Shortcuts" }, "headings": { "certs": "Certifications", @@ -684,5 +685,16 @@ "delete-p3": "You will need to create a new token to save future progress on the curriculum sections that use a virtual machine.", "no-thanks": "No thanks, I would like to keep my token", "yes-please": "Yes please, I would like to delete my token" + }, + "shortcuts": { + "title": "Keyboard shortcuts", + "table-header-action": "Action", + "table-header-key": "Key(s)", + "navigation-mode": "Navigation Mode", + "execute-challenge": "Execute Challenge", + "focus-editor": "Focus Editor", + "focus-instructions-panel": "Focus Instructions Panel", + "navigate-previous": "Navigate Previous Exercise", + "navigate-next": "Navigate Next Exercise" } } diff --git a/client/src/client-only-routes/show-settings.tsx b/client/src/client-only-routes/show-settings.tsx index 415d7e0dae2..e8cf2e1cf7d 100644 --- a/client/src/client-only-routes/show-settings.tsx +++ b/client/src/client-only-routes/show-settings.tsx @@ -48,6 +48,7 @@ interface ShowSettingsProps { submitNewAbout: () => void; toggleNightMode: (theme: Themes) => void; toggleSoundMode: (sound: boolean) => void; + toggleKeyboardShortcuts: (keyboardShortcuts: boolean) => void; updateInternetSettings: () => void; updateIsHonest: () => void; updatePortfolio: () => void; @@ -77,6 +78,8 @@ const mapDispatchToProps = { submitNewAbout, toggleNightMode: (theme: Themes) => updateMyTheme({ theme }), toggleSoundMode: (sound: boolean) => updateMySound({ sound }), + toggleKeyboardShortcuts: (keyboardShortcuts: boolean) => + updateUserFlag({ keyboardShortcuts }), updateInternetSettings: updateUserFlag, updateIsHonest: updateMyHonesty, updatePortfolio: updateMyPortfolio, @@ -93,6 +96,7 @@ export function ShowSettings(props: ShowSettingsProps): JSX.Element { submitNewAbout, toggleNightMode, toggleSoundMode, + toggleKeyboardShortcuts, user: { completedChallenges, email, @@ -121,6 +125,7 @@ export function ShowSettings(props: ShowSettingsProps): JSX.Element { points, theme, sound, + keyboardShortcuts, location, name, githubProfile, @@ -165,9 +170,11 @@ export function ShowSettings(props: ShowSettingsProps): JSX.Element { picture={picture} points={points} sound={sound} + keyboardShortcuts={keyboardShortcuts} submitNewAbout={submitNewAbout} toggleNightMode={toggleNightMode} toggleSoundMode={toggleSoundMode} + toggleKeyboardShortcuts={toggleKeyboardShortcuts} username={username} /> diff --git a/client/src/components/profile/profile.test.tsx b/client/src/components/profile/profile.test.tsx index 0e4172f84d0..8255e1e691d 100644 --- a/client/src/components/profile/profile.test.tsx +++ b/client/src/components/profile/profile.test.tsx @@ -50,6 +50,7 @@ const userProps = { savedChallenges: [], sendQuincyEmail: true, sound: true, + keyboardShortcuts: false, theme: Themes.Default, twitter: 'string', username: 'string', diff --git a/client/src/components/settings/about.tsx b/client/src/components/settings/about.tsx index a5ceb119e7d..61f5ff7d96c 100644 --- a/client/src/components/settings/about.tsx +++ b/client/src/components/settings/about.tsx @@ -14,6 +14,7 @@ import BlockSaveButton from '../helpers/form/block-save-button'; import SoundSettings from './sound'; import ThemeSettings, { Themes } from './theme'; import UsernameSettings from './username'; +import KeyboardShortcutsSettings from './keyboard-shortcuts'; type FormValues = { name: string; @@ -30,10 +31,12 @@ type AboutProps = { picture: string; points: number; sound: boolean; + keyboardShortcuts: boolean; submitNewAbout: (formValues: FormValues) => void; t: TFunction; toggleNightMode: (theme: Themes) => void; toggleSoundMode: (sound: boolean) => void; + toggleKeyboardShortcuts: (keyboardShortcuts: boolean) => void; username: string; }; @@ -198,10 +201,12 @@ class AboutSettings extends Component { const { currentTheme, sound, + keyboardShortcuts, username, t, toggleNightMode, - toggleSoundMode + toggleSoundMode, + toggleKeyboardShortcuts } = this.props; return (
@@ -260,6 +265,10 @@ class AboutSettings extends Component { toggleNightMode={toggleNightMode} /> +
); diff --git a/client/src/components/settings/keyboard-shortcuts.tsx b/client/src/components/settings/keyboard-shortcuts.tsx new file mode 100644 index 00000000000..60d17dbef82 --- /dev/null +++ b/client/src/components/settings/keyboard-shortcuts.tsx @@ -0,0 +1,38 @@ +import { Form } from '@freecodecamp/react-bootstrap'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import ToggleSetting from './toggle-setting'; + +type KeyboardShortcutsProps = { + keyboardShortcuts: boolean; + toggleKeyboardShortcuts: (sound: boolean) => void; +}; + +export default function KeyboardShortcutsSettings({ + keyboardShortcuts, + toggleKeyboardShortcuts +}: KeyboardShortcutsProps): JSX.Element { + const { t } = useTranslation(); + + return ( +
e.preventDefault()} + data-testid='fcc-enable-shortcuts-setting' + > + { + toggleKeyboardShortcuts(keyboardShortcuts ? false : true); + }} + /> + + ); +} + +KeyboardShortcutsSettings.displayName = 'KeyboardShortcutsSettings'; diff --git a/client/src/redux/index.js b/client/src/redux/index.js index d9e0f582ab8..4fd573a0060 100644 --- a/client/src/redux/index.js +++ b/client/src/redux/index.js @@ -19,6 +19,7 @@ import hardGoToEpic from './hard-go-to-epic'; import { createReportUserSaga } from './report-user-saga'; import { actionTypes as settingsTypes } from './settings/action-types'; import { createShowCertSaga } from './show-cert-saga'; +import { createKeyboardShortcuts } from './keyboard-shortcuts-mode-saga'; import updateCompleteEpic from './update-complete-epic'; import { createUserTokenSaga } from './user-token-saga'; import { createSaveChallengeSaga } from './save-challenge-saga'; @@ -81,6 +82,7 @@ export const sagas = [ ...createFetchUserSaga(actionTypes), ...createShowCertSaga(actionTypes), ...createReportUserSaga(actionTypes), + ...createKeyboardShortcuts({ ...actionTypes, ...settingsTypes }), ...createUserTokenSaga(actionTypes), ...createSaveChallengeSaga(actionTypes) ]; diff --git a/client/src/redux/keyboard-shortcuts-mode-saga.js b/client/src/redux/keyboard-shortcuts-mode-saga.js new file mode 100644 index 00000000000..65fb6c4a0e8 --- /dev/null +++ b/client/src/redux/keyboard-shortcuts-mode-saga.js @@ -0,0 +1,27 @@ +/* eslint-disable require-yield */ + +import { takeEvery } from 'redux-saga/effects'; +import store from 'store'; + +const shortcutsKey = 'fcc-keyboard-shortcuts'; + +export function setKeyboardShortcuts(setting) { + store.set(shortcutsKey, setting); +} + +function* updateLocalKeyboardShortcutsSaga({ payload }) { + const { user, keyboardShortcuts } = payload ?? {}; + if (user) { + const { keyboardShortcuts = false } = user; + setKeyboardShortcuts(keyboardShortcuts); + } else if (typeof keyboardShortcuts !== 'undefined') { + setKeyboardShortcuts(keyboardShortcuts); + } +} + +export function createKeyboardShortcuts(types) { + return [ + takeEvery(types.fetchUserComplete, updateLocalKeyboardShortcutsSaga), + takeEvery(types.updateUserFlagComplete, updateLocalKeyboardShortcutsSaga) + ]; +} diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index f45f5ff31c1..f99304cdbe1 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -59,6 +59,7 @@ export const UserPropType = PropTypes.shape({ sendQuincyEmail: PropTypes.bool, sound: PropTypes.bool, theme: PropTypes.string, + keyboardShortcuts: PropTypes.bool, twitter: PropTypes.string, username: PropTypes.string, website: PropTypes.string @@ -277,6 +278,7 @@ export type User = { sendQuincyEmail: boolean; sound: boolean; theme: Themes; + keyboardShortcuts: boolean; twitter: string; username: string; website: string; diff --git a/client/src/templates/Challenges/classic/show.tsx b/client/src/templates/Challenges/classic/show.tsx index 12eb938b019..50533cb61bc 100644 --- a/client/src/templates/Challenges/classic/show.tsx +++ b/client/src/templates/Challenges/classic/show.tsx @@ -28,6 +28,7 @@ import ResetModal from '../components/ResetModal'; import ChallengeTitle from '../components/challenge-title'; import CompletionModal from '../components/completion-modal'; import HelpModal from '../components/help-modal'; +import ShortcutsModal from '../components/shortcuts-modal'; import Notes from '../components/notes'; import Output from '../components/output'; import Preview from '../components/preview'; @@ -514,6 +515,7 @@ class ShowClassic extends Component { previewTitle={t('learn.project-preview-title')} showProjectPreview={showProjectPreview} /> + ); diff --git a/client/src/templates/Challenges/components/Hotkeys.tsx b/client/src/templates/Challenges/components/Hotkeys.tsx index 3354ff7ed2a..bccee0b47ee 100644 --- a/client/src/templates/Challenges/components/Hotkeys.tsx +++ b/client/src/templates/Challenges/components/Hotkeys.tsx @@ -3,14 +3,16 @@ import React from 'react'; import { HotKeys, GlobalHotKeys } from 'react-hotkeys'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { ChallengeFiles, Test } from '../../../redux/prop-types'; +import { ChallengeFiles, Test, User } from '../../../redux/prop-types'; +import { userSelector } from '../../../redux'; import { canFocusEditorSelector, setEditorFocusability, challengeFilesSelector, submitChallenge, - challengeTestsSelector + challengeTestsSelector, + openModal } from '../redux'; import './hotkeys.css'; @@ -18,14 +20,25 @@ const mapStateToProps = createSelector( canFocusEditorSelector, challengeFilesSelector, challengeTestsSelector, - (canFocusEditor: boolean, challengeFiles: ChallengeFiles, tests: Test[]) => ({ + userSelector, + ( + canFocusEditor: boolean, + challengeFiles: ChallengeFiles, + tests: Test[], + user: User + ) => ({ canFocusEditor, challengeFiles, - tests + tests, + user }) ); -const mapDispatchToProps = { setEditorFocusability, submitChallenge }; +const mapDispatchToProps = { + setEditorFocusability, + submitChallenge, + openShortcutsModal: () => openModal('shortcuts') +}; const keyMap = { navigationMode: 'escape', @@ -33,7 +46,8 @@ const keyMap = { focusEditor: 'e', focusInstructionsPanel: 'r', navigatePrev: ['p'], - navigateNext: ['n'] + navigateNext: ['n'], + showShortcuts: 'shift+/' }; interface HotkeysProps { @@ -50,6 +64,8 @@ interface HotkeysProps { setEditorFocusability: (arg0: boolean) => void; tests: Test[]; usesMultifileEditor?: boolean; + openShortcutsModal: () => void; + user: User; } function Hotkeys({ @@ -64,7 +80,9 @@ function Hotkeys({ setEditorFocusability, submitChallenge, tests, - usesMultifileEditor + usesMultifileEditor, + openShortcutsModal, + user: { keyboardShortcuts } }: HotkeysProps): JSX.Element { const handlers = { executeChallenge: (e: React.KeyboardEvent) => { @@ -88,24 +106,33 @@ function Hotkeys({ executeChallenge({ showCompletionModal: true }); } }, - focusEditor: (e: React.KeyboardEvent) => { - e.preventDefault(); - if (editorRef && editorRef.current) { - editorRef.current.focus(); - } - }, - focusInstructionsPanel: () => { - if (instructionsPanelRef && instructionsPanelRef.current) { - instructionsPanelRef.current.focus(); - } - }, - navigationMode: () => setEditorFocusability(false), - navigatePrev: () => { - if (!canFocusEditor) void navigate(prevChallengePath); - }, - navigateNext: () => { - if (!canFocusEditor) void navigate(nextChallengePath); - } + ...(keyboardShortcuts + ? { + focusEditor: (e: React.KeyboardEvent) => { + e.preventDefault(); + if (editorRef && editorRef.current) { + editorRef.current.focus(); + } + }, + focusInstructionsPanel: () => { + if (instructionsPanelRef && instructionsPanelRef.current) { + instructionsPanelRef.current.focus(); + } + }, + navigationMode: () => setEditorFocusability(false), + navigatePrev: () => { + if (!canFocusEditor) void navigate(prevChallengePath); + }, + navigateNext: () => { + if (!canFocusEditor) void navigate(nextChallengePath); + }, + showShortcuts: (e: React.KeyboardEvent) => { + if (!canFocusEditor && e.shiftKey && e.key === '?') { + openShortcutsModal(); + } + } + } + : {}) }; // GlobalHotKeys is always mounted and tracks all keypresses. Without it, // keyup events can be missed and react-hotkeys assumes that that key is still diff --git a/client/src/templates/Challenges/components/shortcuts-modal.css b/client/src/templates/Challenges/components/shortcuts-modal.css new file mode 100644 index 00000000000..4bd9e0ce48a --- /dev/null +++ b/client/src/templates/Challenges/components/shortcuts-modal.css @@ -0,0 +1,29 @@ +.shortcuts-modal-body table { + width: 100%; + margin-bottom: 2rem !important; +} + +.shortcuts-modal-body table caption { + text-align: center; +} + +.shortcuts-modal-body table th { + font-weight: bold; + text-align: center; +} + +.shortcuts-modal-body table tbody tr { + line-height: 2rem; + border-bottom: 1px solid #ccc; +} + +.shortcuts-modal-body table tbody tr td:nth-child(2) { + text-align: right; + font-weight: bold; +} + +@media screen and (max-width: 767px) { + .help-modal .btn-lg { + font-size: 16px; + } +} diff --git a/client/src/templates/Challenges/components/shortcuts-modal.tsx b/client/src/templates/Challenges/components/shortcuts-modal.tsx new file mode 100644 index 00000000000..5ca44206761 --- /dev/null +++ b/client/src/templates/Challenges/components/shortcuts-modal.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { Button, Modal } from '@freecodecamp/react-bootstrap'; +import { withTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; +import { createSelector } from 'reselect'; + +import { closeModal, isShortcutsModalOpenSelector } from '../redux'; +import { updateUserFlag } from '../../../redux/settings'; +import { userSelector } from '../../../redux'; +import { User } from '../../../redux/prop-types'; +import KeyboardShortcutsSettings from '../../../components/settings/keyboard-shortcuts'; + +import './shortcuts-modal.css'; + +interface ShortcutsModalProps { + closeShortcutsModal: () => void; + toggleKeyboardShortcuts: (keyboardShortcuts: boolean) => void; + isOpen: boolean; + t: (text: string) => string; + user: User; +} + +const mapStateToProps = createSelector( + isShortcutsModalOpenSelector, + userSelector, + (isOpen: boolean, user: User) => ({ isOpen, user }) +); +const mapDispatchToProps = (dispatch: Dispatch) => + bindActionCreators( + { + closeShortcutsModal: () => closeModal('shortcuts'), + toggleKeyboardShortcuts: (keyboardShortcuts: boolean) => + updateUserFlag({ keyboardShortcuts }) + }, + dispatch + ); + +export function ShortcutsModal({ + closeShortcutsModal, + toggleKeyboardShortcuts, + isOpen, + t, + user: { keyboardShortcuts } +}: ShortcutsModalProps): JSX.Element { + return ( + + + + {t('shortcuts.title')} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{t('shortcuts.table-header-action')}{t('shortcuts.table-header-key')}
{t('shortcuts.navigation-mode')}ESC
{t('shortcuts.execute-challenge')}CTRL/Command + Enter
{t('shortcuts.focus-editor')}e
{t('shortcuts.focus-instructions-panel')}r
{t('shortcuts.navigate-previous')}p
{t('shortcuts.navigate-next')}n
+
+ + + + +
+ ); +} + +ShortcutsModal.displayName = 'ShortcutsModal'; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(withTranslation()(ShortcutsModal)); diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index 872b6dc67a5..76bc059588c 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -39,7 +39,8 @@ const initialState = { help: false, video: false, reset: false, - projectPreview: false + projectPreview: false, + shortcuts: false }, projectFormValues: {}, successMessage: 'Happy Coding!' @@ -145,6 +146,7 @@ export const isVideoModalOpenSelector = state => state[ns].modal.video; export const isResetModalOpenSelector = state => state[ns].modal.reset; export const isProjectPreviewModalOpenSelector = state => state[ns].modal.projectPreview; +export const isShortcutsModalOpenSelector = state => state[ns].modal.shortcuts; export const isResettingSelector = state => state[ns].isResetting; export const isBuildEnabledSelector = state => state[ns].isBuildEnabled; diff --git a/config/constants.js b/config/constants.js index b5846ce5dcf..6c5a8206db0 100644 --- a/config/constants.js +++ b/config/constants.js @@ -121,6 +121,7 @@ let blocklist = [ 'update-my-socials', 'update-my-sound', 'update-my-theme', + 'update-my-keyboard-shortcuts', 'update-my-username', 'user', 'username', diff --git a/cypress/integration/learn/challenge-hot-keys.js b/cypress/integration/learn/challenge-hot-keys.js index e20ce54ba9a..4fb10f1bfca 100644 --- a/cypress/integration/learn/challenge-hot-keys.js +++ b/cypress/integration/learn/challenge-hot-keys.js @@ -34,6 +34,14 @@ const titles = { }; describe('The hotkeys should work correctly', () => { + beforeEach(() => { + cy.login(); + cy.visit('/settings'); + // enable shortcuts + cy.get('form[data-testid="fcc-enable-shortcuts-setting"]').within(() => { + cy.contains('On').click(); + }); + }); it('should be possible to navigate to the next challenge/projects and previous', () => { cy.visit(links.classic1); cy.focused().type('{esc}'); diff --git a/cypress/integration/learn/challenges/output.js b/cypress/integration/learn/challenges/output.js index 5f2032e3e38..5fb6f185ad6 100644 --- a/cypress/integration/learn/challenges/output.js +++ b/cypress/integration/learn/challenges/output.js @@ -1,6 +1,6 @@ const selectors = { defaultOutput: '.output-text', - editor: '.monaco-editor', + editor: 'div.monaco-editor textarea', hotkeys: '.default-layout > div', runTestsButton: 'button:contains("Run the Tests")' }; @@ -54,8 +54,7 @@ describe('Classic challenge', function () { // first wait for the editor to load cy.get(selectors.editor, { timeout: 15000 - }); - cy.get(selectors.hotkeys) + }) .focus() .type('{ctrl}{enter}') .then(() => { diff --git a/tools/scripts/seed/certifiedUserData.js b/tools/scripts/seed/certifiedUserData.js index 55c4c2b1fa3..6392caf06aa 100644 --- a/tools/scripts/seed/certifiedUserData.js +++ b/tools/scripts/seed/certifiedUserData.js @@ -4711,6 +4711,7 @@ module.exports = { yearsTopContributor: ['2019'], rand: 0.6126749173148205, theme: 'default', + keyboardShortcuts: false, profileUI: { isLocked: false, showAbout: true, diff --git a/tools/scripts/seed/seedAuthUser.js b/tools/scripts/seed/seedAuthUser.js index f8d062c6ddb..42353b8d154 100644 --- a/tools/scripts/seed/seedAuthUser.js +++ b/tools/scripts/seed/seedAuthUser.js @@ -84,7 +84,8 @@ const authUser = { }, isDonating: envVariables.includes('--donor'), emailAuthLinkTTL: null, - emailVerifyTTL: null + emailVerifyTTL: null, + keyboardShortcuts: false }; const blankUser = {