fix(client): a11y and styling issues in challenge transcript (#65580)

This commit is contained in:
Huyen Nguyen
2026-01-30 03:28:26 -08:00
committed by GitHub
parent f3d1df4ce5
commit 680a8c2a47
5 changed files with 71 additions and 51 deletions
@@ -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>';
}
});