@procore/text-editor 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1 -22
  3. package/codemod/__fixtures__/procore.config.ternary.js +18 -0
  4. package/codemod/__fixtures__/src/components/MultilineFormRichText.tsx +2 -1
  5. package/codemod/text-editor-migrate.js +313 -105
  6. package/codemod/text-editor-migrate.test.js +28 -0
  7. package/dist/TextEditor/TextEditor.d.ts +1 -1
  8. package/dist/TextEditor/TextEditor.js +81 -36
  9. package/dist/TextEditor/TextEditor.js.map +1 -1
  10. package/dist/TextEditor/TextEditor.styles.js +1 -1
  11. package/dist/TextEditor/TextEditor.types.d.ts +32 -31
  12. package/dist/TextEditor/TextEditor.types.js.map +1 -1
  13. package/dist/TextEditor/TextEditorProvider.js +1 -1
  14. package/dist/TextEditor/TextEditorProvider.js.map +1 -1
  15. package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js +2 -0
  16. package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js.map +1 -1
  17. package/dist/TextEditor/textEditorTheming/textEditorTheming.styles.js +2 -1
  18. package/dist/TextEditor/textEditorTheming/textEditorTheming.styles.js.map +1 -1
  19. package/dist/TextEditor/useTabAsNavigation.js +2 -1
  20. package/dist/TextEditor/useTabAsNavigation.js.map +1 -1
  21. package/dist/TextEditor/utils/config.js +5 -3
  22. package/dist/TextEditor/utils/config.js.map +1 -1
  23. package/dist/TextEditor/utils/index.d.ts +0 -1
  24. package/dist/TextEditor/utils/index.js +0 -1
  25. package/dist/TextEditor/utils/index.js.map +1 -1
  26. package/dist/TextEditorOutput/TextEditorOutput.styles.js +1 -1
  27. package/dist/_typedoc/TextEditor/TextEditor.types.json +51 -51
  28. package/dist/_typedoc/TextEditor/TextEditorProvider.types.json +2 -2
  29. package/dist/_typedoc/TextEditorOutput/TextEditorOutput.types.json +3 -3
  30. package/dist/_typedoc/deprecations.json +1 -1
  31. package/package.json +13 -16
  32. package/dist/TextEditor/utils/plugins.d.ts +0 -7
  33. package/dist/TextEditor/utils/plugins.js +0 -184
  34. package/dist/TextEditor/utils/plugins.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # @procore/text-editor
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - New toolbar defaults. Remove `plugins` and `config` props to rely on the TextEditor package's default internal configuration.
8
+ - Add Enter key `stopPropagation` to prevent form submission when pressing Enter in the editor.
9
+
10
+ ## 0.0.3
11
+
12
+ ### Patch Changes
13
+
14
+ - 4656bce: Default provider TAB key as navigation to remove keyboard trap
15
+
16
+ ## 0.0.2
17
+
18
+ ### Patch Changes
19
+
20
+ - 7d01b54: Fix some types and remove legacy tinymce no-ops and focus.
21
+
22
+ ## 0.0.1
23
+
24
+ ### Patch Changes
25
+
26
+ - f95beb2: Initial release of a separate TextEditor package, extracted from core-react.
package/README.md CHANGED
@@ -10,7 +10,7 @@ yarn add @procore/text-editor
10
10
 
11
11
  ## Usage
12
12
 
13
- ### Basic Usage
13
+ ### TextEditor Usage
14
14
 
15
15
  ```tsx
16
16
  import { TextEditor } from '@procore/text-editor'
@@ -27,27 +27,6 @@ function MyComponent() {
27
27
  }
28
28
  ```
29
29
 
30
- ### With Custom Configuration
31
-
32
- ```tsx
33
- import { TextEditor } from '@procore/text-editor'
34
-
35
- function MyComponent() {
36
- const [value, setValue] = React.useState('')
37
-
38
- return (
39
- <TextEditor
40
- value={value}
41
- onChange={(newValue) => setValue(newValue)}
42
- plugins={['link', 'table']}
43
- config={(defaultConfig) => ({
44
- ...defaultConfig,
45
- // Your custom CKEditor configuration
46
- })}
47
- />
48
- )
49
- }
50
- ```
51
30
 
52
31
  ### TextEditorProvider
53
32
 
@@ -0,0 +1,18 @@
1
+ const { coreReactJestConfig } = require('@procore/core-react/jestConfig')
2
+
3
+ module.exports = ({ env }) => ({
4
+ app: {
5
+ webpackOverride(config) {
6
+ return config
7
+ },
8
+ moduleFederation: {
9
+ shared:
10
+ process.env.NODE_ENV === 'production'
11
+ ? { react: deps.react, 'react-dom': deps['react-dom'] }
12
+ : {},
13
+ },
14
+ },
15
+ jestOverride: (defaultConfig) => {
16
+ return coreReactJestConfig(defaultConfig)
17
+ },
18
+ })
@@ -1,4 +1,4 @@
1
- import { Form } from '@procore/core-react'
1
+ import { Form, TextEditor } from '@procore/core-react'
2
2
  import React from 'react'
3
3
 
4
4
  export const MultilineFormRichText = () => {
@@ -9,6 +9,7 @@ export const MultilineFormRichText = () => {
9
9
  label="Description"
10
10
  plugins={['table']}
11
11
  />
12
+ <TextEditor value="notes" plugins={['links']} />
12
13
  </Form>
13
14
  )
14
15
  }
@@ -9,6 +9,7 @@
9
9
  * 2. Updates Form.RichText usage to include textEditorComponent prop
10
10
  * 3. Updates Jest configuration to use textEditorJestConfig
11
11
  * 4. Adds ckeditor5 singleton to module federation shared config
12
+ * 5. Remove deprecated plugins prop from TextEditor and Form.RichText components
12
13
  */
13
14
 
14
15
  const fs = require('fs')
@@ -168,6 +169,52 @@ function transformImports(fileContent) {
168
169
  return { content: newContent, modified, needsTextEditorImport }
169
170
  }
170
171
 
172
+ /**
173
+ * Remove deprecated plugins prop from TextEditor and Form.RichText components
174
+ */
175
+ function removeDeprecatedProps(fileContent) {
176
+ let modified = false
177
+ let newContent = fileContent
178
+
179
+ // Pattern to match TextEditor components with plugins prop
180
+ const textEditorRegex = /(<TextEditor)([\s\S]*?)(\/>|>)/g
181
+ newContent = newContent.replace(
182
+ textEditorRegex,
183
+ (match, opening, props, closing) => {
184
+ let updatedProps = props
185
+ const hasPlugins = props.includes('plugins=')
186
+
187
+ if (hasPlugins) {
188
+ // Remove plugins prop
189
+ updatedProps = updatedProps.replace(/\s*plugins\s*=\s*\{[^}]*\}/g, '')
190
+ modified = true
191
+ }
192
+
193
+ return opening + updatedProps + closing
194
+ }
195
+ )
196
+
197
+ // Pattern to match Form.RichText components with plugins prop
198
+ const formRichTextRegex = /(<Form\.RichText(?:Field)?)([\s\S]*?)(\/>|>)/g
199
+ newContent = newContent.replace(
200
+ formRichTextRegex,
201
+ (match, opening, props, closing) => {
202
+ let updatedProps = props
203
+ const hasPlugins = props.includes('plugins=')
204
+
205
+ if (hasPlugins) {
206
+ // Remove plugins prop
207
+ updatedProps = updatedProps.replace(/\s*plugins\s*=\s*\{[^}]*\}/g, '')
208
+ modified = true
209
+ }
210
+
211
+ return opening + updatedProps + closing
212
+ }
213
+ )
214
+
215
+ return { content: newContent, modified }
216
+ }
217
+
171
218
  /**
172
219
  * Transform Form.RichText usage
173
220
  */
@@ -289,9 +336,255 @@ function transformFormRichText(fileContent) {
289
336
  return { content: newContent, modified }
290
337
  }
291
338
 
339
+ /**
340
+ * Add ckeditor5 singleton configuration to a shared object
341
+ * Returns the modified content and whether it was modified
342
+ */
343
+ function addSingletonToSharedObject(content, sharedMatch) {
344
+ const insertPosition = sharedMatch.index + sharedMatch[0].length
345
+
346
+ // Detect indentation by looking at the content after `shared: {` or similar
347
+ // Find the first property inside shared to determine proper indentation
348
+ const afterShared = content.substring(insertPosition)
349
+ const firstPropertyMatch = afterShared.match(/\n(\s+)\w+\s*:/)
350
+
351
+ let propertyIndent = ''
352
+ let indentUnit = ' ' // default to 2 spaces
353
+
354
+ if (firstPropertyMatch) {
355
+ // Use the same indentation as the first property inside shared
356
+ propertyIndent = firstPropertyMatch[1]
357
+ // Detect indent unit from the property indent
358
+ const sharedLineMatch = content
359
+ .substring(0, sharedMatch.index)
360
+ .match(/\n(\s+)(?:shared|\?)/)
361
+ if (sharedLineMatch) {
362
+ const sharedIndent = sharedLineMatch[1]
363
+ // indentUnit is the difference between property indent and shared indent
364
+ if (propertyIndent.length > sharedIndent.length) {
365
+ indentUnit = propertyIndent.substring(sharedIndent.length)
366
+ }
367
+ }
368
+ } else {
369
+ // Fallback: detect from the 'shared' line indentation
370
+ const beforeMatch = content.substring(0, sharedMatch.index)
371
+ const lastNewlineIndex = beforeMatch.lastIndexOf('\n')
372
+ const lineStart =
373
+ lastNewlineIndex >= 0
374
+ ? beforeMatch.substring(lastNewlineIndex + 1)
375
+ : beforeMatch
376
+ const baseIndentMatch = lineStart.match(/^(\s*)/)
377
+ const baseIndent = baseIndentMatch ? baseIndentMatch[1] : ''
378
+
379
+ // Determine if we're using tabs or spaces
380
+ const useTabs = baseIndent.includes('\t')
381
+ indentUnit = useTabs ? '\t' : ' '
382
+ propertyIndent = baseIndent + indentUnit
383
+ }
384
+
385
+ // Build the ckeditor5 configuration
386
+ const ckeditor5Config = `
387
+ ${propertyIndent}ckeditor5: {
388
+ ${propertyIndent}${indentUnit}requiredVersion: deps['ckeditor5'],
389
+ ${propertyIndent}${indentUnit}singleton: true,
390
+ ${propertyIndent}},`
391
+
392
+ // Insert the ckeditor5 configuration after the opening brace
393
+ return (
394
+ content.substring(0, insertPosition) +
395
+ ckeditor5Config +
396
+ content.substring(insertPosition)
397
+ )
398
+ }
399
+
400
+ /**
401
+ * Add deps import/require statement to the file if not already present
402
+ */
403
+ function addDepsImport(content, filePath) {
404
+ // Check if deps variable is already declared
405
+ if (
406
+ content.includes("require('./package.json')") ||
407
+ content.includes("require('./package.json').dependencies") ||
408
+ content.includes("from './package.json'")
409
+ ) {
410
+ return content
411
+ }
412
+
413
+ // Determine if using CommonJS or ES modules
414
+ const isESModule =
415
+ content.includes('export default') ||
416
+ content.includes('export {') ||
417
+ filePath.endsWith('.mjs')
418
+
419
+ if (isESModule) {
420
+ // For ES modules, add import at the top
421
+ const importStatement =
422
+ "import deps from './package.json' with { type: 'json' }\n"
423
+
424
+ // Find the first import or export statement
425
+ const firstImportMatch = content.match(/^(import|export)/m)
426
+ if (firstImportMatch) {
427
+ const insertPos = content.indexOf(firstImportMatch[0])
428
+ return (
429
+ content.substring(0, insertPos) +
430
+ importStatement +
431
+ content.substring(insertPos)
432
+ )
433
+ } else {
434
+ return importStatement + content
435
+ }
436
+ } else {
437
+ // For CommonJS, add require at the top
438
+ const requireStatement =
439
+ "const deps = require('./package.json').dependencies\n"
440
+
441
+ // Find the first require or const statement
442
+ const firstRequireMatch = content.match(/^(const|let|var|require)/m)
443
+ if (firstRequireMatch) {
444
+ const insertPos = content.indexOf(firstRequireMatch[0])
445
+ return (
446
+ content.substring(0, insertPos) +
447
+ requireStatement +
448
+ content.substring(insertPos)
449
+ )
450
+ } else {
451
+ return requireStatement + content
452
+ }
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Find the matching closing brace for an opening brace at the given position
458
+ */
459
+ function findMatchingBrace(content, openBracePos) {
460
+ let depth = 1
461
+ let pos = openBracePos + 1
462
+
463
+ while (pos < content.length && depth > 0) {
464
+ const char = content[pos]
465
+ if (char === '{') {
466
+ depth++
467
+ } else if (char === '}') {
468
+ depth--
469
+ }
470
+ pos++
471
+ }
472
+
473
+ return depth === 0 ? pos - 1 : -1
474
+ }
475
+
476
+ /**
477
+ * Check if an object block contains a 'react' key
478
+ */
479
+ function objectContainsReact(content, openBracePos) {
480
+ const closeBracePos = findMatchingBrace(content, openBracePos)
481
+ if (closeBracePos === -1) return false
482
+
483
+ const objectContent = content.substring(openBracePos, closeBracePos + 1)
484
+ // Check for react as a key (handles react:, 'react':, "react":)
485
+ return /(?:^|[{,\s])react\s*:|['"]react['"]\s*:/.test(objectContent)
486
+ }
487
+
488
+ /**
489
+ * Transform module federation with ternary conditional pattern
490
+ * Handles patterns like:
491
+ * shared: process.env.NODE_ENV === 'production'
492
+ * ? { react: deps.react, 'react-dom': deps['react-dom'] }
493
+ * : {},
494
+ * Only adds ckeditor5 to branches that contain 'react'
495
+ */
496
+ function transformModuleFederationTernary(fileContent, filePath) {
497
+ let newContent = fileContent
498
+
499
+ // Pattern to match ternary conditional for shared - find the opening brace of the consequent
500
+ // Matches: shared: <condition> ? {
501
+ const ternaryRegex =
502
+ /shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?\s*(\{)/g
503
+
504
+ let match
505
+ let offset = 0
506
+
507
+ // Find all ternary patterns and process the consequent (true branch)
508
+ while ((match = ternaryRegex.exec(fileContent)) !== null) {
509
+ const adjustedIndex = match.index + offset
510
+ const bracePos = adjustedIndex + match[0].length - 1 // Position of the opening brace
511
+
512
+ // Only add ckeditor5 if this branch contains 'react'
513
+ if (!objectContainsReact(newContent, bracePos)) {
514
+ continue
515
+ }
516
+
517
+ const adjustedMatch = {
518
+ ...match,
519
+ index: bracePos,
520
+ 0: '{',
521
+ }
522
+
523
+ const beforeLength = newContent.length
524
+ newContent = addSingletonToSharedObject(newContent, adjustedMatch)
525
+ offset += newContent.length - beforeLength
526
+ }
527
+
528
+ // Now find and process the alternate (false branch) - the : { ... } part
529
+ // We need to find the consequent's closing brace first, then find the alternate
530
+ const ternaryRegex2 = /shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?\s*\{/g
531
+
532
+ let match2
533
+ while ((match2 = ternaryRegex2.exec(newContent)) !== null) {
534
+ // Find the opening brace of the consequent
535
+ const consequentOpenBrace = match2.index + match2[0].length - 1
536
+
537
+ // Find the matching closing brace
538
+ const consequentCloseBrace = findMatchingBrace(
539
+ newContent,
540
+ consequentOpenBrace
541
+ )
542
+
543
+ if (consequentCloseBrace === -1) continue
544
+
545
+ // Find the alternate branch - look for : { after the closing brace
546
+ const afterConsequent = newContent.substring(consequentCloseBrace + 1)
547
+ const alternateMatch = afterConsequent.match(/^\s*:\s*(\{)/)
548
+
549
+ if (alternateMatch) {
550
+ const alternateBracePos =
551
+ consequentCloseBrace +
552
+ 1 +
553
+ alternateMatch.index +
554
+ alternateMatch[0].length -
555
+ 1
556
+
557
+ // Only add ckeditor5 if this branch contains 'react'
558
+ if (!objectContainsReact(newContent, alternateBracePos)) {
559
+ continue
560
+ }
561
+
562
+ const adjustedMatch = {
563
+ index: alternateBracePos,
564
+ 0: '{',
565
+ }
566
+
567
+ newContent = addSingletonToSharedObject(newContent, adjustedMatch)
568
+ }
569
+ }
570
+
571
+ // Add deps import if needed only if we made changes
572
+ if (newContent !== fileContent) {
573
+ newContent = addDepsImport(newContent, filePath)
574
+ }
575
+
576
+ const modified = newContent !== fileContent
577
+ if (modified) {
578
+ stats.moduleFederationUpdated++
579
+ }
580
+
581
+ return { content: newContent, modified }
582
+ }
583
+
292
584
  /**
293
585
  * Transform module federation configuration to add ckeditor5 singleton
294
586
  * Handles both hammer.config.* and procore.config.* files
587
+ * Supports both direct object pattern and ternary conditional pattern
295
588
  */
296
589
  function transformModuleFederation(fileContent, filePath) {
297
590
  let modified = false
@@ -321,6 +614,12 @@ function transformModuleFederation(fileContent, filePath) {
321
614
  return { content: newContent, modified }
322
615
  }
323
616
 
617
+ // Check if this is a ternary conditional pattern
618
+ const ternaryPattern = /shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?/
619
+ if (ternaryPattern.test(newContent)) {
620
+ return transformModuleFederationTernary(newContent, filePath)
621
+ }
622
+
324
623
  // Pattern to match shared object in moduleFederation
325
624
  // Handles both `shared: {` and `shared:{` patterns
326
625
  const sharedObjectRegex = /(shared\s*:\s*\{)/g
@@ -332,109 +631,8 @@ function transformModuleFederation(fileContent, filePath) {
332
631
  // Find the match and its position
333
632
  const match = sharedObjectRegex.exec(newContent)
334
633
  if (match) {
335
- const insertPosition = match.index + match[0].length
336
-
337
- // Detect indentation by looking at the content after `shared: {`
338
- // Find the first property inside shared to determine proper indentation
339
- const afterShared = newContent.substring(insertPosition)
340
- const firstPropertyMatch = afterShared.match(/\n(\s+)\w+\s*:/)
341
-
342
- let propertyIndent = ''
343
- let indentUnit = ' ' // default to 2 spaces
344
-
345
- if (firstPropertyMatch) {
346
- // Use the same indentation as the first property inside shared
347
- propertyIndent = firstPropertyMatch[1]
348
- // Detect indent unit from the property indent
349
- const sharedLineMatch = newContent
350
- .substring(0, match.index)
351
- .match(/\n(\s+)shared/)
352
- if (sharedLineMatch) {
353
- const sharedIndent = sharedLineMatch[1]
354
- // indentUnit is the difference between property indent and shared indent
355
- if (propertyIndent.length > sharedIndent.length) {
356
- indentUnit = propertyIndent.substring(sharedIndent.length)
357
- }
358
- }
359
- } else {
360
- // Fallback: detect from the 'shared' line indentation
361
- const beforeMatch = newContent.substring(0, match.index)
362
- const lastNewlineIndex = beforeMatch.lastIndexOf('\n')
363
- const lineStart =
364
- lastNewlineIndex >= 0
365
- ? beforeMatch.substring(lastNewlineIndex + 1)
366
- : beforeMatch
367
- const baseIndentMatch = lineStart.match(/^(\s*)/)
368
- const baseIndent = baseIndentMatch ? baseIndentMatch[1] : ''
369
-
370
- // Determine if we're using tabs or spaces
371
- const useTabs = baseIndent.includes('\t')
372
- indentUnit = useTabs ? '\t' : ' '
373
- propertyIndent = baseIndent + indentUnit
374
- }
375
-
376
- // Build the ckeditor5 configuration
377
- const ckeditor5Config = `
378
- ${propertyIndent}ckeditor5: {
379
- ${propertyIndent}${indentUnit}requiredVersion: deps['ckeditor5'],
380
- ${propertyIndent}${indentUnit}singleton: true,
381
- ${propertyIndent}},`
382
-
383
- // Insert the ckeditor5 configuration after `shared: {`
384
- newContent =
385
- newContent.substring(0, insertPosition) +
386
- ckeditor5Config +
387
- newContent.substring(insertPosition)
388
-
389
- // Check if deps variable is already declared
390
- if (
391
- !newContent.includes("require('./package.json')") &&
392
- !newContent.includes("require('./package.json').dependencies") &&
393
- !newContent.includes("from './package.json'")
394
- ) {
395
- // Add deps declaration at the top of the file
396
- // Determine if using CommonJS or ES modules
397
- const isESModule =
398
- newContent.includes('export default') ||
399
- newContent.includes('export {') ||
400
- filePath.endsWith('.mjs')
401
-
402
- if (isESModule) {
403
- // For ES modules, add import at the top
404
- const importStatement =
405
- "import deps from './package.json' with { type: 'json' }\n"
406
-
407
- // Find the first import or export statement
408
- const firstImportMatch = newContent.match(/^(import|export)/m)
409
- if (firstImportMatch) {
410
- const insertPos = newContent.indexOf(firstImportMatch[0])
411
- newContent =
412
- newContent.substring(0, insertPos) +
413
- importStatement +
414
- newContent.substring(insertPos)
415
- } else {
416
- newContent = importStatement + newContent
417
- }
418
- } else {
419
- // For CommonJS, add require at the top
420
- const requireStatement =
421
- "const deps = require('./package.json').dependencies\n"
422
-
423
- // Find the first require or const statement
424
- const firstRequireMatch = newContent.match(
425
- /^(const|let|var|require)/m
426
- )
427
- if (firstRequireMatch) {
428
- const insertPos = newContent.indexOf(firstRequireMatch[0])
429
- newContent =
430
- newContent.substring(0, insertPos) +
431
- requireStatement +
432
- newContent.substring(insertPos)
433
- } else {
434
- newContent = requireStatement + newContent
435
- }
436
- }
437
- }
634
+ newContent = addSingletonToSharedObject(newContent, match)
635
+ newContent = addDepsImport(newContent, filePath)
438
636
 
439
637
  modified = true
440
638
  stats.moduleFederationUpdated++
@@ -505,7 +703,8 @@ function processFile(filePath) {
505
703
  // Config file detection
506
704
  const isConfigFile =
507
705
  /^jest\.(config|setup)\.(js|ts|cjs|mjs)$/.test(fileName) ||
508
- /\.config\.(js|ts|cjs|mjs)$/.test(fileName)
706
+ /\.config\.(js|ts|cjs|mjs)$/.test(fileName) ||
707
+ /\.config\..*\.(js|ts|cjs|mjs)$/.test(fileName) // Last one for tests like "procore.config.ternay.js"
509
708
 
510
709
  // Process Jest configuration
511
710
  if (isConfigFile && content.includes('coreReactJestConfig')) {
@@ -540,6 +739,13 @@ function processFile(filePath) {
540
739
  newContent = formResult.content
541
740
  wasModified = true
542
741
  }
742
+
743
+ // Remove deprecated plugins prop
744
+ const deprecatedPropsResult = removeDeprecatedProps(newContent)
745
+ if (deprecatedPropsResult.modified) {
746
+ newContent = deprecatedPropsResult.content
747
+ wasModified = true
748
+ }
543
749
  }
544
750
 
545
751
  // Write back if modified
@@ -721,7 +927,9 @@ ${colors.bold}Examples:${colors.reset}
721
927
 
722
928
  if (stats.filesProcessed > 0) {
723
929
  console.log(`${colors.bold}Next steps:${colors.reset}\n`)
724
- console.log('1. Review the changes made by the codemod')
930
+ console.log(
931
+ '1. Review the changes made by the codemod. May need to be linted.\n'
932
+ )
725
933
  console.log(
726
934
  '2. Review previous implementation for additional props and migrate to CKEditor, https://ckeditor.com/docs/ckeditor5/latest/features/index.html\n'
727
935
  )
@@ -129,6 +129,15 @@ describe('text-editor-migrate codemod', () => {
129
129
  expect(content).not.toContain('textEditorComponent={TextEditor}')
130
130
  expect(content).not.toContain('@procore/text-editor')
131
131
  })
132
+
133
+ it('should remove deprecated plugins prop from Form.RichText', () => {
134
+ execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
135
+
136
+ const content = readFile('src/components/MultilineFormRichText.tsx')
137
+
138
+ expect(content).not.toContain('plugins=')
139
+ expect(content).toContain('textEditorComponent={TextEditor}')
140
+ })
132
141
  })
133
142
 
134
143
  describe('Jest configuration transformations', () => {
@@ -209,6 +218,25 @@ describe('text-editor-migrate codemod', () => {
209
218
  expect(matches).toHaveLength(1)
210
219
  })
211
220
 
221
+ it('should add ckeditor5 only to ternary branches that contain react', () => {
222
+ execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
223
+
224
+ const content = readFile('procore.config.ternary.js')
225
+
226
+ // Should have ckeditor5 only in the branch with react (production branch)
227
+ const ckeditor5Matches = content.match(/ckeditor5:/g)
228
+ expect(ckeditor5Matches).toHaveLength(1)
229
+
230
+ // Should have deps import
231
+ expect(content).toContain(
232
+ "const deps = require('./package.json').dependencies"
233
+ )
234
+
235
+ // Should have singleton: true only once
236
+ const singletonMatches = content.match(/singleton:\s*true/g)
237
+ expect(singletonMatches).toHaveLength(1)
238
+ })
239
+
212
240
  it('should not modify config files without moduleFederation.shared', () => {
213
241
  // Create a config file without moduleFederation
214
242
  const noModFedConfig = `
@@ -41,4 +41,4 @@ import type { TextEditorProps } from './TextEditor.types';
41
41
  * }),
42
42
  * })
43
43
  */
44
- export declare function TextEditor({ disabled, error, value, initialValue, onChange, onInit, onBlur, onKeyDown, config: externalConfig, plugins: stringPlugins, locale: propLocale, onDirty, ...restProps }: TextEditorProps): React.JSX.Element | null;
44
+ export declare function TextEditor(props: Readonly<TextEditorProps>): React.JSX.Element | null;