From cabddb74cb95566ae22db015e167400e2e401847 Mon Sep 17 00:00:00 2001 From: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com> Date: Mon, 8 Dec 2025 01:00:05 -0800 Subject: [PATCH] feat(challenge-parser,client): display Chinese dialogue with ruby annotations (#64235) --- .../components/scene/scene-helpers.test.ts | 17 +++++ .../Challenges/components/scene/scene.tsx | 5 +- .../with-chinese-scene-no-pinyin.md | 35 ++++++++++ .../parser/__fixtures__/with-chinese-scene.md | 37 ++++++++++ .../parser/plugins/add-scene.js | 45 +++++++++++++ .../parser/plugins/add-scene.test.js | 67 +++++++++++++++++++ 6 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 tools/challenge-parser/parser/__fixtures__/with-chinese-scene-no-pinyin.md create mode 100644 tools/challenge-parser/parser/__fixtures__/with-chinese-scene.md create mode 100644 tools/challenge-parser/parser/plugins/add-scene.test.js diff --git a/client/src/templates/Challenges/components/scene/scene-helpers.test.ts b/client/src/templates/Challenges/components/scene/scene-helpers.test.ts index bcac6738528..8db2fe7dce5 100644 --- a/client/src/templates/Challenges/components/scene/scene-helpers.test.ts +++ b/client/src/templates/Challenges/components/scene/scene-helpers.test.ts @@ -191,5 +191,22 @@ describe('scene-helpers', () => { '\nNaomi: Use
wrapper tags, keeping only the inner ruby elements + const innerHtml = html.replace(/^
|<\/p>$/g, ''); + + return { + ...command, + dialogue: { + ...command.dialogue, + text: innerHtml + } + }; + } + return command; + }); + } + file.data.scene = sceneJson; } } diff --git a/tools/challenge-parser/parser/plugins/add-scene.test.js b/tools/challenge-parser/parser/plugins/add-scene.test.js new file mode 100644 index 00000000000..c7307bd52b8 --- /dev/null +++ b/tools/challenge-parser/parser/plugins/add-scene.test.js @@ -0,0 +1,67 @@ +import { describe, beforeAll, beforeEach, it, expect } from 'vitest'; +import parseFixture from '../__fixtures__/parse-fixture'; +import addScene from './add-scene'; + +describe('add-scene', () => { + let sceneAST, chineseSceneAST, chineseSceneNoPinyinAST; + let file; + + beforeAll(async () => { + sceneAST = await parseFixture('scene.md'); + chineseSceneAST = await parseFixture('with-chinese-scene.md'); + chineseSceneNoPinyinAST = await parseFixture( + 'with-chinese-scene-no-pinyin.md' + ); + }); + + beforeEach(() => { + file = { data: { lang: 'en' } }; + }); + + it('should add scene data to file when scene section exists', () => { + const plugin = addScene(); + plugin(sceneAST, file); + + expect(file.data.scene).toBeDefined(); + expect(file.data.scene.setup.background).toBe('company2-center.png'); + expect(file.data.scene.commands).toHaveLength(3); + }); + + it('should preserve dialogue text for non-Chinese scenes', () => { + const plugin = addScene(); + plugin(sceneAST, file); + + expect(file.data.scene.commands[1].dialogue.text).toBe( + "I'm Maria, the team lead." + ); + expect(file.data.scene.commands[1].dialogue.text).not.toContain(''); + }); + + it('should convert Chinese hanzi-pinyin pairs to ruby HTML', () => { + file.data.lang = 'zh-CN'; + const plugin = addScene(); + plugin(chineseSceneAST, file); + + const dialogueText = file.data.scene.commands[0].dialogue.text; + expect(dialogueText).toBe( + '你好,世界。' + ); + }); + + it('should not convert Hanzi-only to ruby HTML', () => { + file.data.lang = 'zh-CN'; + const plugin = addScene(); + plugin(chineseSceneNoPinyinAST, file); + + expect(file.data.scene.commands[0].dialogue.text).toBe('你好,世界。'); + expect(file.data.scene.commands[0].dialogue.text).not.toContain(''); + }); + + it('should handle commands without dialogue', () => { + const plugin = addScene(); + plugin(sceneAST, file); + + expect(file.data.scene.commands[0].dialogue).toBeUndefined(); + expect(file.data.scene.commands[2].dialogue).toBeUndefined(); + }); +});