Skip to content

Commit 4292840

Browse files
authored
Merge pull request #478 from ets-cfuhrman-pfe/feature/replace-gift-text-editor
GIFT Editor - Replace textarea with monaco editor
2 parents 03e3b18 + 5a47f00 commit 4292840

24 files changed

Lines changed: 872 additions & 246 deletions

client/package-lock.json

Lines changed: 58 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@fortawesome/fontawesome-svg-core": "^6.7.2",
2222
"@fortawesome/free-solid-svg-icons": "^6.7.2",
2323
"@fortawesome/react-fontawesome": "^0.2.0",
24+
"@monaco-editor/react": "^4.7.0",
2425
"@mui/icons-material": "^7.0.2",
2526
"@mui/lab": "^5.0.0-alpha.153",
2627
"@mui/material": "^7.0.2",
@@ -36,6 +37,7 @@
3637
"jwt-decode": "^4.0.0",
3738
"katex": "^0.16.22",
3839
"marked": "^15.0.8",
40+
"monaco-editor": "^0.55.1",
3941
"nanoid": "^5.1.5",
4042
"qrcode.react": "^4.2.0",
4143
"react": "^18.3.1",
@@ -55,6 +57,7 @@
5557
"@babel/preset-react": "^7.26.3",
5658
"@babel/preset-typescript": "^7.27.0",
5759
"@eslint/js": "^9.24.0",
60+
"@playwright/test": "1.56.1",
5861
"@testing-library/dom": "^10.4.0",
5962
"@testing-library/jest-dom": "^6.6.3",
6063
"@testing-library/react": "^16.3.0",
@@ -67,7 +70,6 @@
6770
"@typescript-eslint/eslint-plugin": "^8.29.1",
6871
"@typescript-eslint/parser": "^8.29.1",
6972
"@vitejs/plugin-react-swc": "^3.8.1",
70-
"@playwright/test": "1.56.1",
7173
"cross-env": "^7.0.3",
7274
"eslint": "^9.29.0",
7375
"eslint-plugin-eslint-comments": "^3.2.0",

client/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Share from './pages/Teacher/Share/Share';
1111
import Register from './pages/AuthManager/providers/SimpleLogin/Register';
1212
import ResetPassword from './pages/AuthManager/providers/SimpleLogin/ResetPassword';
1313
import ManageRoomV2 from './pages/Teacher/ManageRoom/ManageRoomV2';
14-
import EditorQuizV2 from './pages/Teacher/EditorQuiz/EditorQuizV2';
14+
import EditorQuiz from './pages/Teacher/EditorQuiz/EditorQuiz';
1515

1616
// Pages espace étudiant
1717
import JoinRoomV2 from './pages/Student/JoinRoom/JoinRoomV2';
@@ -97,7 +97,7 @@ const App: React.FC = () => {
9797
/>
9898
<Route
9999
path="/teacher/editor-quiz/:id"
100-
element={isTeacherAuthenticated ? <EditorQuizV2 /> : <Navigate to="/login" />}
100+
element={isTeacherAuthenticated ? <EditorQuiz /> : <Navigate to="/login" />}
101101
/>
102102
<Route
103103
path="/teacher/manage-room/:quizId"

client/src/__tests__/components/Editor/Editor.test.tsx

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,10 @@
22
import React from 'react';
33
import { render, fireEvent, screen } from '@testing-library/react';
44
import '@testing-library/jest-dom';
5-
import EditorV2 from 'src/components/Editor/EditorV2';
65

7-
// Mock ResizeObserver which is not present in JSDOM
8-
class ResizeObserverMock {
9-
observe() {}
10-
unobserve() {}
11-
disconnect() {}
12-
}
13-
global.ResizeObserver = ResizeObserverMock as any;
6+
import Editor from 'src/components/Editor/Editor';
147

15-
describe('EditorV2 Component', () => {
8+
describe('Editor Component', () => {
169
const mockOnEditorChange = jest.fn();
1710

1811
const sampleProps = {
@@ -22,66 +15,66 @@ describe('EditorV2 Component', () => {
2215
};
2316

2417
beforeEach(() => {
25-
render(<EditorV2 {...sampleProps} />);
18+
jest.clearAllMocks();
2619
});
2720

2821
it('renders correctly with initial value', () => {
22+
render(<Editor {...sampleProps} />);
2923
const editorTextarea = screen.getByRole('textbox') as HTMLTextAreaElement;
3024
expect(editorTextarea).toBeInTheDocument();
3125
expect(editorTextarea.value).toBe('Sample Initial Value');
3226
});
3327

3428
it('calls onEditorChange callback when editor value changes', () => {
29+
render(<Editor {...sampleProps} />);
3530
const editorTextarea = screen.getByRole('textbox') as HTMLTextAreaElement;
3631
fireEvent.change(editorTextarea, { target: { value: 'Updated Value' } });
3732
expect(mockOnEditorChange).toHaveBeenCalledWith('Updated Value');
3833
});
3934

4035
it('updates editor value when initialValue prop changes', () => {
41-
const updatedProps = {
42-
label: 'Updated Label',
43-
initialValue: 'Updated Initial Value',
44-
onEditorChange: mockOnEditorChange
45-
};
46-
47-
render(<EditorV2 {...updatedProps} />);
48-
49-
const editorTextareas = screen.getAllByRole('textbox') as HTMLTextAreaElement[];
50-
const editorTextarea = editorTextareas[1];
36+
const { rerender } = render(<Editor {...sampleProps} />);
37+
rerender(
38+
<Editor
39+
label='Updated Label'
40+
initialValue='Updated Initial Value'
41+
onEditorChange={mockOnEditorChange}
42+
/>
43+
);
5144

45+
const editorTextarea = screen.getByRole('textbox') as HTMLTextAreaElement;
5246
expect(editorTextarea.value).toBe('Updated Initial Value');
5347
});
5448

5549
test('should call change text with the correct value on textarea change', () => {
56-
const updatedProps = {
57-
label: 'Updated Label',
58-
initialValue: 'Updated Initial Value',
59-
onEditorChange: mockOnEditorChange
60-
};
61-
62-
render(<EditorV2 {...updatedProps} />);
50+
render(
51+
<Editor
52+
label='Updated Label'
53+
initialValue='Updated Initial Value'
54+
onEditorChange={mockOnEditorChange}
55+
/>
56+
);
6357

64-
const editorTextareas = screen.getAllByRole('textbox') as HTMLTextAreaElement[];
65-
const editorTextarea = editorTextareas[1];
58+
const editorTextarea = screen.getByRole('textbox') as HTMLTextAreaElement;
6659
fireEvent.change(editorTextarea, { target: { value: 'New value' } });
6760

6861
expect(editorTextarea.value).toBe('New value');
6962
});
7063

7164
test('should call onEditorChange with an empty string if textarea value is falsy', () => {
72-
const updatedProps = {
73-
label: 'Updated Label',
74-
initialValue: 'Updated Initial Value',
75-
onEditorChange: mockOnEditorChange
76-
};
77-
78-
render(<EditorV2 {...updatedProps} />);
65+
render(
66+
<Editor
67+
label='Updated Label'
68+
initialValue='Updated Initial Value'
69+
onEditorChange={mockOnEditorChange}
70+
/>
71+
);
7972

80-
const editorTextareas = screen.getAllByRole('textbox') as HTMLTextAreaElement[];
81-
const editorTextarea = editorTextareas[1];
73+
const editorTextarea = screen.getByRole('textbox') as HTMLTextAreaElement;
8274
fireEvent.change(editorTextarea, { target: { value: '' } });
8375

8476
expect(editorTextarea.value).toBe('');
77+
expect(mockOnEditorChange).toHaveBeenCalledWith('');
8578
});
8679

8780

client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ const unsupportedQuestions = [
1616
'$CATEGORY a/b/c'
1717
];
1818

19+
beforeAll(() => {
20+
Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
21+
configurable: true,
22+
value: jest.fn(),
23+
});
24+
});
25+
1926
describe('GIFTTemplatePreviewV2 Component', () => {
2027
it('renders error message when questions contain invalid syntax', () => {
2128
render(<GIFTTemplatePreviewV2 questions={['T{']} hideAnswers={false} />);
@@ -76,4 +83,19 @@ describe('GIFTTemplatePreviewV2 Component', () => {
7683
expect(unsupportedMessages).toHaveLength(4);
7784
});
7885

86+
it('highlights the active question anchor when activeQuestionIndex is provided', () => {
87+
render(
88+
<GIFTTemplatePreviewV2
89+
questions={validQuestions}
90+
hideAnswers={false}
91+
activeQuestionIndex={1}
92+
/>
93+
);
94+
95+
const previewContainer = screen.getByTestId('preview-container');
96+
const activeAnchor = previewContainer.querySelector('.gift-preview-question-anchor--active');
97+
expect(activeAnchor).toBeInTheDocument();
98+
expect(activeAnchor).toHaveAttribute('data-question-index', '1');
99+
});
100+
79101
});

0 commit comments

Comments
 (0)