diff --git a/src/schedule-and-details/introducing-section/contentTypeUtils.test.ts b/src/schedule-and-details/introducing-section/contentTypeUtils.test.ts
new file mode 100644
index 0000000000..c818a1827f
--- /dev/null
+++ b/src/schedule-and-details/introducing-section/contentTypeUtils.test.ts
@@ -0,0 +1,234 @@
+/**
+ * Tests for content type detection utilities
+ */
+import { containsHtml, determineEditorType, type EditorType } from './contentTypeUtils';
+
+describe('contentTypeUtils', () => {
+ describe('containsHtml', () => {
+ describe('should return false for non-HTML content', () => {
+ test.each([
+ ['empty string', ''],
+ ['null', null],
+ ['plain text', 'This is just plain text'],
+ ['text with special characters', 'Text with @#$%^&*()'],
+ ['text with quotes', 'Text with "quotes" and \'apostrophes\''],
+ ['text with newlines', 'Line 1\nLine 2\nLine 3'],
+ ['text with angle brackets but not HTML', '5 < 10 and 10 > 5'],
+ ['mathematical expressions', 'x = y < z > w'],
+ ] as const)('for %s', (_description: string, input: string | null) => {
+ expect(containsHtml(input)).toBe(false);
+ });
+ });
+
+ describe('should return true for HTML content', () => {
+ test.each([
+ // Basic HTML tags
+ ['simple paragraph', '
Hello world
'],
+ ['heading tag', 'Title
'],
+ ['div element', 'Content
'],
+ ['span element', 'Text'],
+ ['self-closing tag', '
'],
+ ['self-closing without space', '
'],
+
+ // HTML tags with attributes
+ ['tag with class', 'Text
'],
+ ['tag with id', 'Text
'],
+ ['tag with multiple attributes', 'Link'],
+
+ // Mixed case HTML
+ ['uppercase tag', 'Paragraph
'],
+ ['mixed case tag', 'Content
'],
+
+ // HTML with content
+ ['nested tags', ''],
+ ['multiple tags', 'Title
Paragraph
'],
+ ['formatting tags', 'Text with bold and italic'],
+
+ // Complex HTML structures
+ ['list structure', ''],
+ ['table structure', ''],
+ ['form elements', ''],
+ ['image tag', '
'],
+
+ // HTML entities
+ ['named entities', 'Price: $100 & free shipping'],
+ ['more entities', 'Copyright © 2024 – All rights reserved'],
+ ['quotes entity', 'He said "Hello" to me'],
+ ['numeric entities', 'Special char: € ©'],
+
+ // HTML with text content
+ ['HTML in mixed content', 'Introduction: This is the main content.
End.'],
+ ['multiple entities', 'A & B < C > D'],
+
+ // Edge cases
+ ['unclosed tag', 'Unclosed paragraph'],
+ ['tag with newlines', '
\nMultiline\ncontent\n
'],
+ ] as const)('for %s', (_description: string, input: string) => {
+ expect(containsHtml(input)).toBe(true);
+ });
+ });
+
+ describe('edge cases', () => {
+ test('should handle very long content', () => {
+ const longText = 'a'.repeat(10000);
+ expect(containsHtml(longText)).toBe(false);
+
+ const longHtml = `${longText}
`;
+ expect(containsHtml(longHtml)).toBe(true);
+ });
+
+ test('should handle content with only whitespace', () => {
+ expect(containsHtml(' \n\t ')).toBe(false);
+ });
+ });
+ });
+
+ describe('determineEditorType', () => {
+ describe('should return "text" for non-HTML content', () => {
+ test.each([
+ ['empty string', ''],
+ ['null', null],
+ ['plain text', 'This is just plain text content'],
+ ['long plain text', 'Lorem ipsum '.repeat(100)],
+ ['text with special chars', 'Email: test@example.com, Phone: (555) 123-4567'],
+ ['mathematical content', '2 + 2 = 4, x < y, a > b'],
+ ['code-like content', 'function() { return value < threshold; }'],
+ ] as const)('for %s', (_description: string, input: string | null) => {
+ expect(determineEditorType(input)).toBe('text');
+ });
+ });
+
+ describe('should return "html" for HTML content', () => {
+ test.each([
+ // Simple HTML
+ ['basic paragraph', 'Simple paragraph
'],
+ ['heading', 'Section Title
'],
+ ['formatted text', 'Text with bold formatting'],
+
+ // Complex HTML structures
+ ['nested elements', ''],
+ ['lists', ''],
+ ['tables', ''],
+ ['links and images', '
'],
+
+ // Course content examples
+ ['course overview', 'Course Overview
This course covers...
'],
+ ['about sidebar', ''],
+
+ // HTML entities
+ ['content with entities', 'Price: $100 & includes shipping – 50% off!'],
+ ['mixed content', 'Introduction Main content with © symbol
conclusion'],
+ ] as const)('for %s', (_description: string, input: string) => {
+ expect(determineEditorType(input)).toBe('html');
+ });
+ });
+
+ describe('integration scenarios', () => {
+ test('should handle real course overview content', () => {
+ const courseOverview = `
+
+
Introduction to Computer Science
+
This course provides a comprehensive introduction to computer science concepts.
+
What You'll Learn:
+
+ - Programming fundamentals
+ - Data structures and algorithms
+ - Software engineering practices
+
+
Prerequisites: Basic mathematics knowledge
+
Duration: 12 weeks
+
+ `;
+
+ expect(containsHtml(courseOverview)).toBe(true);
+ expect(determineEditorType(courseOverview)).toBe('html');
+ });
+
+ test('should handle sidebar HTML content', () => {
+ const sidebarHtml = `
+
+ `;
+
+ expect(containsHtml(sidebarHtml)).toBe(true);
+ expect(determineEditorType(sidebarHtml)).toBe('html');
+ });
+
+ test('should handle plain text course descriptions', () => {
+ const plainDescription = 'A beginner-friendly course covering the basics of programming. No prior experience required.';
+
+ expect(containsHtml(plainDescription)).toBe(false);
+ expect(determineEditorType(plainDescription)).toBe('text');
+ });
+
+ test('should handle empty or minimal content', () => {
+ expect(determineEditorType('')).toBe('text');
+ expect(determineEditorType(' ')).toBe('text');
+ expect(determineEditorType(null as any)).toBe('text');
+ expect(determineEditorType(undefined as any)).toBe('text');
+ });
+ });
+
+ describe('type safety', () => {
+ test('should return correct EditorType', () => {
+ const result: EditorType = determineEditorType('Test
');
+ expect(result).toBe('html');
+
+ const result2: EditorType = determineEditorType('Plain text');
+ expect(result2).toBe('text');
+ });
+ });
+
+ describe('performance considerations', () => {
+ test('should handle very large content efficiently', () => {
+ const largeContent = 'This is a large text content. '.repeat(1000);
+ const start = Date.now();
+ const result = determineEditorType(largeContent);
+ const end = Date.now();
+
+ expect(result).toBe('text');
+ expect(end - start).toBeLessThan(100); // Should complete in under 100ms
+ });
+
+ test('should handle large HTML content efficiently', () => {
+ const largeHtml = `${'
Paragraph content.
'.repeat(1000)}
`;
+ const start = Date.now();
+ const result = determineEditorType(largeHtml);
+ const end = Date.now();
+
+ expect(result).toBe('html');
+ expect(end - start).toBeLessThan(100); // Should complete in under 100ms
+ });
+ });
+ });
+
+ describe('function integration', () => {
+ test('containsHtml and determineEditorType should be consistent', () => {
+ const testCases: Array = [
+ 'Plain text',
+ 'HTML content
',
+ 'Text with & entities',
+ '',
+ null,
+ undefined,
+ '',
+ ];
+
+ testCases.forEach((content) => {
+ const hasHtml = containsHtml(content as any);
+ const editorType = determineEditorType(content as any);
+
+ if (hasHtml) {
+ expect(editorType).toBe('html');
+ } else {
+ expect(editorType).toBe('text');
+ }
+ });
+ });
+ });
+});
diff --git a/src/schedule-and-details/introducing-section/contentTypeUtils.ts b/src/schedule-and-details/introducing-section/contentTypeUtils.ts
new file mode 100644
index 0000000000..31ada1498c
--- /dev/null
+++ b/src/schedule-and-details/introducing-section/contentTypeUtils.ts
@@ -0,0 +1,46 @@
+/**
+ * Utility functions for detecting content type and
+ * determining appropriate editor type for TinyMCE editor
+ */
+
+// Define the supported editor types
+export type EditorType = 'text' | 'html';
+
+/**
+ * Detects if content contains HTML tags
+ * @param content - The content to analyze
+ * @returns True if content contains HTML tags
+ */
+export const containsHtml = (content: string | null): boolean => {
+ if (!content) {
+ return false;
+ }
+
+ // Check for common HTML patterns
+ const htmlPatterns: RegExp[] = [
+ /<\/?[a-z][\s\S]*>/i, // HTML tags
+ /&[a-z]+;/i, // HTML entities
+ /\d+;/, // Numeric entities
+ ];
+
+ return htmlPatterns.some((pattern) => pattern.test(content));
+};
+
+/**
+ * Determines the appropriate editor type based on content analysis
+ * @param content - The content to analyze
+ * @returns The recommended editor type ('text' or 'html')
+ */
+export const determineEditorType = (content: string | null): EditorType => {
+ if (!content) {
+ return 'text';
+ }
+
+ // If content contains HTML, use html editor for better HTML editing
+ if (containsHtml(content)) {
+ return 'html';
+ }
+
+ // For plain text content, use text editor
+ return 'text';
+};
diff --git a/src/schedule-and-details/introducing-section/index.jsx b/src/schedule-and-details/introducing-section/index.jsx
index 32e8e21c55..71e7bdccd7 100644
--- a/src/schedule-and-details/introducing-section/index.jsx
+++ b/src/schedule-and-details/introducing-section/index.jsx
@@ -11,6 +11,7 @@ import { WysiwygEditor } from '../../generic/WysiwygEditor';
import SectionSubHeader from '../../generic/section-sub-header';
import IntroductionVideo from './introduction-video';
import ExtendedCourseDetails from './extended-course-details';
+import { determineEditorType } from './contentTypeUtils';
import messages from './messages';
const IntroducingSection = ({
@@ -112,6 +113,7 @@ const IntroducingSection = ({
{intl.formatMessage(messages.courseOverviewLabel)}
onChange(value, 'overview')}
/>
{overviewHelpText}
@@ -121,6 +123,7 @@ const IntroducingSection = ({
{intl.formatMessage(messages.courseAboutSidebarLabel)}
onChange(value, 'aboutSidebarHtml')}
/>
{aboutSidebarHelpText}