mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 10:22:16 +00:00
fix(client): preview button with screenreader text (#63061)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com>
This commit is contained in:
@@ -584,9 +584,9 @@
|
||||
"instructions": "Instructions",
|
||||
"notes": "Notes",
|
||||
"preview": "Preview",
|
||||
"terminal": "Terminal",
|
||||
"editor": "Editor",
|
||||
"interactive-editor": "Interactive Editor"
|
||||
"interactive-editor": "Interactive Editor",
|
||||
"terminal": "Terminal"
|
||||
},
|
||||
"editor-alerts": {
|
||||
"tab-trapped": "Pressing tab will now insert the tab character",
|
||||
@@ -1034,6 +1034,10 @@
|
||||
"terminal-output": "Terminal output",
|
||||
"not-available": "Not available",
|
||||
"interactive-editor-desc": "Turn static code examples into interactive editors. This allows you to edit and run the code directly on the page.",
|
||||
"hide-terminal": "Hide the terminal",
|
||||
"move-terminal-to-new-window": "Move the terminal to a new window and focus it",
|
||||
"move-terminal-to-main-window": "Move the terminal to this window and close the external terminal window",
|
||||
"close-external-terminal-window": "Close the external terminal window",
|
||||
"pinyin-to-hanzi-input-desc": "This task uses Pinyin-to-Hanzi inputs. Type pinyin with tone numbers (1 to 5). When you enter a correct syllable, it will turn into a Chinese character. If you press backspace after a Chinese character, it will change back to pinyin and remove the last thing you typed: if it's a tone number, the tone is removed; if it's a letter, the letter is removed.",
|
||||
"pinyin-tone-input-desc": "This task uses Pinyin Tone inputs. Type pinyin with tone numbers (1 to 5). When you enter a tone number, it will be converted to a tone mark. If you press backspace, the last thing you typed is removed: if it's a tone number, the tone is removed; if it's a letter, the letter is removed."
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import store from 'store';
|
||||
import { DailyCodingChallengeLanguages } from '../../../redux/prop-types';
|
||||
import { challengeTypes } from '@freecodecamp/shared/config/challenge-types';
|
||||
import EditorTabs from './editor-tabs';
|
||||
|
||||
interface ClassicLayoutProps {
|
||||
@@ -21,9 +20,9 @@ interface ClassicLayoutProps {
|
||||
showInstructions: boolean;
|
||||
showPreviewPane: boolean;
|
||||
showPreviewPortal: boolean;
|
||||
challengeType: number;
|
||||
togglePane: (pane: string) => void;
|
||||
hasInteractiveEditor?: never;
|
||||
usesTerminal: boolean;
|
||||
hasContentOutline?: never;
|
||||
}
|
||||
|
||||
@@ -108,7 +107,7 @@ const ActionRow = (props: ActionRowProps): JSX.Element => {
|
||||
isDailyCodingChallenge,
|
||||
dailyCodingChallengeLanguage,
|
||||
setDailyCodingChallengeLanguage,
|
||||
challengeType
|
||||
usesTerminal
|
||||
} = props;
|
||||
|
||||
// sets screen reader text for the two preview buttons
|
||||
@@ -119,35 +118,39 @@ const ActionRow = (props: ActionRowProps): JSX.Element => {
|
||||
portal: t('aria.open-preview-in-new-window')
|
||||
};
|
||||
|
||||
// preview open in main window
|
||||
// open in main window
|
||||
if (showPreviewPane && !showPreviewPortal) {
|
||||
previewBtnsSrText.pane = t('aria.hide-preview');
|
||||
previewBtnsSrText.portal = t('aria.move-preview-to-new-window');
|
||||
|
||||
// preview open in external window
|
||||
if (usesTerminal) {
|
||||
previewBtnsSrText.pane = t('aria.hide-terminal');
|
||||
previewBtnsSrText.portal = t('aria.move-terminal-to-new-window');
|
||||
} else {
|
||||
previewBtnsSrText.pane = t('aria.hide-preview');
|
||||
previewBtnsSrText.portal = t('aria.move-preview-to-new-window');
|
||||
}
|
||||
// open in external window
|
||||
} else if (showPreviewPortal && !showPreviewPane) {
|
||||
previewBtnsSrText.pane = t('aria.move-preview-to-main-window');
|
||||
previewBtnsSrText.portal = t('aria.close-external-preview-window');
|
||||
if (usesTerminal) {
|
||||
previewBtnsSrText.pane = t('aria.move-terminal-to-main-window');
|
||||
previewBtnsSrText.portal = t('aria.close-external-terminal-window');
|
||||
} else {
|
||||
previewBtnsSrText.pane = t('aria.move-preview-to-main-window');
|
||||
previewBtnsSrText.portal = t('aria.close-external-preview-window');
|
||||
}
|
||||
}
|
||||
|
||||
return previewBtnsSrText;
|
||||
}
|
||||
|
||||
const isPythonChallenge =
|
||||
challengeType === challengeTypes.python ||
|
||||
challengeType === challengeTypes.multifilePythonCertProject ||
|
||||
challengeType === challengeTypes.pyLab ||
|
||||
challengeType === challengeTypes.dailyChallengePy;
|
||||
|
||||
const previewButtonText = isPythonChallenge
|
||||
? t('learn.editor-tabs.terminal')
|
||||
: t('learn.editor-tabs.preview');
|
||||
|
||||
const handleLanguageChange = (language: DailyCodingChallengeLanguages) => {
|
||||
store.set('dailyCodingChallengeLanguage', language);
|
||||
setDailyCodingChallengeLanguage(language);
|
||||
};
|
||||
|
||||
const previewPaneButtonText =
|
||||
usesTerminal == false
|
||||
? 'learn.editor-tabs.preview'
|
||||
: 'learn.editor-tabs.terminal';
|
||||
|
||||
return (
|
||||
<div className='action-row' data-playwright-test-label='action-row'>
|
||||
<div className='tabs-row' data-playwright-test-label='tabs-row'>
|
||||
@@ -207,7 +210,7 @@ const ActionRow = (props: ActionRowProps): JSX.Element => {
|
||||
onClick={() => togglePane('showPreviewPane')}
|
||||
>
|
||||
<span className='sr-only'>{getPreviewBtnsSrText().pane}</span>
|
||||
<span aria-hidden='true'>{previewButtonText}</span>
|
||||
<span aria-hidden='true'>{t(previewPaneButtonText)}</span>
|
||||
</button>
|
||||
<button
|
||||
aria-expanded={!!showPreviewPortal}
|
||||
|
||||
@@ -271,6 +271,12 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
||||
const editorPaneFlex =
|
||||
!displayPreviewConsole && !displayPreviewPane ? 1 : editorPane.flex;
|
||||
|
||||
const usesTerminal =
|
||||
challengeType === challengeTypes.python ||
|
||||
challengeType === challengeTypes.multifilePythonCertProject ||
|
||||
challengeType === challengeTypes.pyLab ||
|
||||
challengeType === challengeTypes.dailyChallengePy;
|
||||
|
||||
return (
|
||||
<div className='desktop-layout' data-playwright-test-label='desktop-layout'>
|
||||
{isProjectStyle && (
|
||||
@@ -287,7 +293,7 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
||||
showPreviewPane={showPreviewPane}
|
||||
showPreviewPortal={showPreviewPortal}
|
||||
togglePane={togglePane}
|
||||
challengeType={challengeType}
|
||||
usesTerminal={usesTerminal}
|
||||
data-playwright-test-label='action-row'
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -40,6 +40,7 @@ interface MobileLayoutProps {
|
||||
updateUsingKeyboardInTablist: (arg0: boolean) => void;
|
||||
testOutput: JSX.Element;
|
||||
usesMultifileEditor: boolean;
|
||||
usesTerminal: boolean;
|
||||
}
|
||||
|
||||
const tabs = {
|
||||
@@ -164,7 +165,8 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
|
||||
setShowPreviewPortal,
|
||||
portalWindow,
|
||||
windowTitle,
|
||||
usesMultifileEditor
|
||||
usesMultifileEditor,
|
||||
usesTerminal
|
||||
} = this.props;
|
||||
|
||||
const displayPreviewPane = hasPreview && showPreviewPane;
|
||||
@@ -206,9 +208,13 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
|
||||
return portalBtnSrText;
|
||||
}
|
||||
|
||||
const previewTriggerText =
|
||||
usesTerminal == false
|
||||
? 'learn.editor-tabs.preview'
|
||||
: 'learn.editor-tabs.terminal';
|
||||
|
||||
// Unlike the desktop layout the mobile version does not have an ActionRow,
|
||||
// but still needs a way to switch between the different tabs.
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
@@ -240,7 +246,7 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
|
||||
</TabsTrigger>
|
||||
{hasPreview && (
|
||||
<TabsTrigger value={tabs.preview}>
|
||||
{i18next.t('learn.editor-tabs.preview')}
|
||||
{i18next.t(previewTriggerText)}
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
|
||||
@@ -465,6 +465,12 @@ function ShowClassic({
|
||||
);
|
||||
};
|
||||
|
||||
const usesTerminal =
|
||||
challengeType === challengeTypes.python ||
|
||||
challengeType === challengeTypes.multifilePythonCertProject ||
|
||||
challengeType === challengeTypes.pyLab ||
|
||||
challengeType === challengeTypes.dailyChallengePy;
|
||||
|
||||
return (
|
||||
<Hotkeys
|
||||
challengeType={challengeType}
|
||||
@@ -507,6 +513,7 @@ function ShowClassic({
|
||||
}
|
||||
updateUsingKeyboardInTablist={updateUsingKeyboardInTablist}
|
||||
usesMultifileEditor={usesMultifileEditor}
|
||||
usesTerminal={usesTerminal}
|
||||
/>
|
||||
) : (
|
||||
<DesktopLayout
|
||||
|
||||
@@ -28,6 +28,40 @@ test.describe('Desktop view', () => {
|
||||
await expect(previewPortalButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('Preview button is visible during HTML/CSS/JS challenges', async ({
|
||||
page
|
||||
}) => {
|
||||
const previewPaneButton = page.getByTestId('preview-pane-button');
|
||||
const previewPortalButton = page.getByRole('button', {
|
||||
name: translations.aria['move-preview-to-new-window']
|
||||
});
|
||||
|
||||
expect(previewPortalButton).toBeDefined();
|
||||
|
||||
const hidePreviewText = translations.aria['hide-preview'];
|
||||
const previewText = translations.learn['editor-tabs']['preview'];
|
||||
|
||||
await expect(previewPaneButton).toHaveText(hidePreviewText + previewText);
|
||||
});
|
||||
|
||||
test('Terminal button is visible during Python challenges', async ({
|
||||
page
|
||||
}) => {
|
||||
await page.goto('/learn/python-v9/workshop-caesar-cipher/step-1');
|
||||
const terminalPaneButton = page.getByTestId('preview-pane-button');
|
||||
const terminalPortalButton = page.getByRole('button', {
|
||||
name: translations.aria['move-terminal-to-new-window']
|
||||
});
|
||||
|
||||
const hideTerminalText = translations.aria['hide-terminal'];
|
||||
const terminalText = translations.learn['editor-tabs']['terminal'];
|
||||
|
||||
await expect(terminalPaneButton).toHaveText(
|
||||
hideTerminalText + terminalText
|
||||
);
|
||||
expect(terminalPortalButton).toBeDefined();
|
||||
});
|
||||
|
||||
test('Clicking instructions button hides instructions panel, but not any buttons', async ({
|
||||
page
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user