mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
fix(client): a11y and styling issues in challenge transcript (#65580)
This commit is contained in:
@@ -4,14 +4,16 @@
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
#learn-app-wrapper .transcript-table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
border: 1px solid var(--gray-05);
|
||||
border-collapse: collapse;
|
||||
.challenge-transcript .transcript-dialogue {
|
||||
border: 1px solid var(--secondary-color);
|
||||
}
|
||||
|
||||
#learn-app-wrapper .transcript-table td {
|
||||
border: none;
|
||||
.challenge-transcript .transcript-dialogue p {
|
||||
margin: 0;
|
||||
padding: 6px 13px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.challenge-transcript .transcript-dialogue p:nth-child(odd) {
|
||||
background-color: var(--tertiary-background);
|
||||
}
|
||||
|
||||
@@ -59,21 +59,35 @@ describe('<ChallengeTranscript />', () => {
|
||||
setSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should render the transcript as a table when isDialogue is true', () => {
|
||||
it('should render the transcript as paragraphs when isDialogue is true', () => {
|
||||
store.set('fcc-transcript-expanded', true);
|
||||
|
||||
render(
|
||||
<ChallengeTranscript
|
||||
{...baseProps}
|
||||
transcript={'Hello\nWorld'}
|
||||
transcript={'<p><b>Alice</b>: Hello</p><p><b>Bob</b>: World</p>'}
|
||||
shouldPersistExpanded={true}
|
||||
isDialogue={true}
|
||||
/>
|
||||
);
|
||||
const table = screen.getByRole('table');
|
||||
expect(table).toBeVisible();
|
||||
expect(screen.getByRole('cell', { name: 'Hello' })).toBeVisible();
|
||||
expect(screen.getByRole('cell', { name: 'World' })).toBeVisible();
|
||||
|
||||
/* eslint-disable testing-library/no-node-access */
|
||||
const aliceB = screen.getByText('Alice');
|
||||
expect(aliceB).toBeVisible();
|
||||
expect(aliceB.tagName).toBe('B');
|
||||
|
||||
const aliceP = aliceB.parentElement;
|
||||
expect(aliceP?.tagName).toBe('P');
|
||||
expect(aliceP?.textContent).toBe('Alice: Hello');
|
||||
|
||||
const bobB = screen.getByText('Bob');
|
||||
expect(bobB).toBeVisible();
|
||||
expect(bobB.tagName).toBe('B');
|
||||
|
||||
const bobP = bobB.parentElement;
|
||||
expect(bobP?.tagName).toBe('P');
|
||||
expect(bobP?.textContent).toBe('Bob: World');
|
||||
/* eslint-enable testing-library/no-node-access */
|
||||
});
|
||||
|
||||
it('should render the transcript with PrismFormatted when isDialogue is false', () => {
|
||||
|
||||
@@ -36,7 +36,11 @@ function ChallengeTranscript({
|
||||
|
||||
return (
|
||||
<>
|
||||
<details data-testid='challenge-transcript' open={isOpen}>
|
||||
<details
|
||||
data-testid='challenge-transcript'
|
||||
className='challenge-transcript'
|
||||
open={isOpen}
|
||||
>
|
||||
<summary
|
||||
onClick={toggleExpandedState}
|
||||
aria-expanded={isOpen}
|
||||
@@ -46,20 +50,10 @@ function ChallengeTranscript({
|
||||
</summary>
|
||||
<Spacer size='m' />
|
||||
{isDialogue ? (
|
||||
<table className='transcript-table'>
|
||||
<tbody>
|
||||
{transcript
|
||||
.split('\n')
|
||||
.filter(line => line.trim() !== '')
|
||||
.map((line, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td dangerouslySetInnerHTML={{ __html: line }} />
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<div
|
||||
className='transcript-dialogue'
|
||||
dangerouslySetInnerHTML={{ __html: transcript }}
|
||||
/>
|
||||
) : (
|
||||
<PrismFormatted className='line-numbers' text={transcript} />
|
||||
)}
|
||||
|
||||
@@ -22,9 +22,7 @@ describe('scene-helpers', () => {
|
||||
}
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe(
|
||||
'\n<strong>Naomi</strong>: Hello world, I have 5 cats\n'
|
||||
);
|
||||
expect(result).toBe('<p><b>Naomi</b>: Hello world, I have 5 cats</p>');
|
||||
});
|
||||
|
||||
it('should build transcript from multiple commands with dialogue', () => {
|
||||
@@ -56,9 +54,9 @@ describe('scene-helpers', () => {
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe(
|
||||
'\n<strong>Naomi</strong>: Hello\n' +
|
||||
'\n<strong>Quincy</strong>: Hi there, I found 3 bugs\n' +
|
||||
'\n<strong>Naomi</strong>: How are you?\n'
|
||||
'<p><b>Naomi</b>: Hello</p>' +
|
||||
'<p><b>Quincy</b>: Hi there, I found 3 bugs</p>' +
|
||||
'<p><b>Naomi</b>: How are you?</p>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -88,8 +86,7 @@ describe('scene-helpers', () => {
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe(
|
||||
'\n<strong>Naomi</strong>: Hello\n' +
|
||||
'\n<strong>Naomi</strong>: How are you?\n'
|
||||
'<p><b>Naomi</b>: Hello</p>' + '<p><b>Naomi</b>: How are you?</p>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -106,7 +103,7 @@ describe('scene-helpers', () => {
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe(
|
||||
'\n<strong>Naomi</strong>: He said "I love TypeScript!" & she replied, \'I prefer Ruby! #ruby #rubyonrails\'\n'
|
||||
'<p><b>Naomi</b>: He said "I love TypeScript!" & she replied, \'I prefer Ruby! #ruby #rubyonrails\'</p>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -122,7 +119,7 @@ describe('scene-helpers', () => {
|
||||
}
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe('\n<strong>Naomi</strong>: \n');
|
||||
expect(result).toBe('<p><b>Naomi</b>: </p>');
|
||||
});
|
||||
|
||||
it('should handle dialogue with newlines', () => {
|
||||
@@ -137,7 +134,7 @@ describe('scene-helpers', () => {
|
||||
}
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe('\n<strong>Naomi</strong>: Hello\nworld\n');
|
||||
expect(result).toBe('<p><b>Naomi</b>: Hello\nworld</p>');
|
||||
});
|
||||
|
||||
it('should handle multiple consecutive commands from same character', () => {
|
||||
@@ -169,9 +166,9 @@ describe('scene-helpers', () => {
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe(
|
||||
'\n<strong>Naomi</strong>: First line\n' +
|
||||
'\n<strong>Naomi</strong>: Second line\n' +
|
||||
'\n<strong>Naomi</strong>: Third line\n'
|
||||
'<p><b>Naomi</b>: First line</p>' +
|
||||
'<p><b>Naomi</b>: Second line</p>' +
|
||||
'<p><b>Naomi</b>: Third line</p>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -187,9 +184,7 @@ describe('scene-helpers', () => {
|
||||
}
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe(
|
||||
'\n<strong>Naomi</strong>: Use <div> and <span> tags\n'
|
||||
);
|
||||
expect(result).toBe('<p><b>Naomi</b>: Use <div> and <span> tags</p>');
|
||||
});
|
||||
|
||||
it('should preserve Chinese dialogue with ruby annotations', () => {
|
||||
@@ -205,8 +200,25 @@ describe('scene-helpers', () => {
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toBe(
|
||||
'\n<strong>Naomi</strong>: <ruby>你好<rp>(</rp><rt>nǐ hǎo</rt><rp>)</rp></ruby>,<ruby>世界<rp>(</rp><rt>shì jiè</rt><rp>)</rp></ruby>。\n'
|
||||
'<p><b>Naomi</b>: <ruby>你好<rp>(</rp><rt>nǐ hǎo</rt><rp>)</rp></ruby>,<ruby>世界<rp>(</rp><rt>shì jiè</rt><rp>)</rp></ruby>。</p>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should wrap dialogue lines in p tags and speakers in b tags', () => {
|
||||
const commands: SceneCommand[] = [
|
||||
{
|
||||
character: 'Alice',
|
||||
startTime: 1,
|
||||
dialogue: {
|
||||
text: 'Hello',
|
||||
align: 'left'
|
||||
}
|
||||
}
|
||||
];
|
||||
const result = buildTranscript(commands);
|
||||
expect(result).toContain('<p>');
|
||||
expect(result).toContain('</p>');
|
||||
expect(result).toContain('<b>Alice</b>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,13 +6,11 @@ export const buildTranscript = (commands: SceneCommand[]): string => {
|
||||
if (command.character && command.dialogue && command.startTime) {
|
||||
transcriptText =
|
||||
transcriptText +
|
||||
'\n' +
|
||||
'<strong>' +
|
||||
'<p><b>' +
|
||||
command.character +
|
||||
'</strong>:' +
|
||||
' ' +
|
||||
'</b>: ' +
|
||||
command.dialogue.text +
|
||||
'\n';
|
||||
'</p>';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user