Skip to content

refactor(text-editor): extract editor-config to enable real-stack testing#4139

Open
john-traas wants to merge 6 commits into
mainfrom
text-editor-real-stack-tests
Open

refactor(text-editor): extract editor-config to enable real-stack testing#4139
john-traas wants to merge 6 commits into
mainfrom
text-editor-real-stack-tests

Conversation

@john-traas

@john-traas john-traas commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What

Extracts the text editor's schema and plugin assembly out of the limel-prosemirror-adapter component into a pure, importable module, and adds an integration test that exercises the real editor stack.

  • refactor: initializeSchema() / createEditorState() (private methods on the adapter) → pure buildEditorSchema() / buildEditorPlugins() in editor-config.ts. The component delegates to them; plugin order and behaviour are preserved exactly.
  • test: editor-config.spec.ts builds the production schema + the real ordered plugin list and asserts, using the official prosemirror-test-builder:
    • commands produce the expected transaction;
    • the link/image handlePaste ordering contract (ProseMirror is first-truthy-wins) — guarding against a new/changed plugin silently shadowing another on a shared event;
    • a transaction sequence keeps the document structurally valid.
  • build: adds a clean script (shx rm -rf .stencil dist www) and the prosemirror-test-builder dev dependency.

Why

The editor is now used across the CRM, so we need regression coverage for cross-plugin interactions (paste / keymap / transaction chains). Those only surface when the real, full plugin set runs against the real schema — previously impossible because the schema and plugin list were private to the component. This unblocks that, tests against source (immune to stale-dist issues), and needs no bespoke test-harness suite.

Verification

  • npm run test:spec: 884/884 pass (incl. the new spec)
  • text-editor e2e: 14/14 pass
  • Behaviour-preserving: no plugin order or logic changes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a clean script to remove generated build output.
    • Introduced shared schema and plugin builders for the text editor configuration.
  • Bug Fixes
    • Improved editor consistency by using the centralized schema/plugin setup.
    • Ensured paste handling follows the correct link-before-image behavior and non-link clipboard outcomes.
  • Tests
    • Added real-stack integration tests validating the editor schema, command/transaction behavior, paste handling order, and document integrity.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@john-traas, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 59fa2768-2d75-45b5-8cd7-41289e0b8f81

📥 Commits

Reviewing files that changed from the base of the PR and between 3e31c98 and c499fb6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • package.json
  • src/components/text-editor/prosemirror-adapter/editor-config.spec.ts
  • src/components/text-editor/prosemirror-adapter/editor-config.ts
  • src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx
📝 Walkthrough

Walkthrough

A shared ProseMirror schema and plugin builder were extracted into editor-config.ts, and the text editor adapter now builds its editor state through those helpers. New integration tests cover schema contents, command execution, plugin ordering, and transaction validity, and package.json adds a clean script plus a test helper dependency.

Changes

ProseMirror editor configuration

Layer / File(s) Summary
Schema contract and types
src/components/text-editor/prosemirror-adapter/editor-config.ts
Defines the editor content type, schema options, and schema builder that extends the base schema with custom nodes and marks.
Plugin contract and ordering
src/components/text-editor/prosemirror-adapter/editor-config.ts
Defines the plugin options and the ordered plugin builder that assembles setup, keymaps, trigger, link, image, menu, action bar, and table plugins.
Adapter wiring to shared builders
src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx
Replaces inline schema and plugin construction in the adapter with calls to the shared schema and plugin builders.
Integration tests and toolchain support
package.json, src/components/text-editor/prosemirror-adapter/editor-config.spec.ts
Adds the test builder dependency, a clean script, and integration tests for schema contents, command execution, plugin ordering, and transaction validity.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • Lundalogik/lime-elements#3562: Refactors ProseMirror schema/plugin construction, including link mark and paste-handling wiring that overlaps with this editor configuration change.

Suggested reviewers: civing

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main refactor: extracting editor-config and adding real-stack testing for the text editor.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch text-editor-real-stack-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown

Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-4139/

@john-traas john-traas force-pushed the text-editor-real-stack-tests branch from b817bc0 to 74f39e1 Compare June 17, 2026 13:22
@john-traas john-traas self-assigned this Jun 18, 2026
@john-traas john-traas marked this pull request as ready for review June 26, 2026 08:57
@john-traas john-traas force-pushed the text-editor-real-stack-tests branch from 719d673 to bc1e2e9 Compare July 6, 2026 21:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx`:
- Around line 223-245: The clear() method in ProseMirrorAdapter is canceling a
pending debounced change too early, which can drop a legitimate change('')
emission when the editor is already empty. Update clear() so it first checks the
serialized content state, and only cancels changeEmitter / resets changeWaiting
if it will actually call updateView(''); use the clear(), changeEmitter,
changeWaiting, and updateView symbols to keep the behavior aligned with
handleTransaction and lastEmittedValue.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8535a0c-e026-4419-802b-d81fdab531a0

📥 Commits

Reviewing files that changed from the base of the PR and between 719d673 and bc1e2e9.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • package.json
  • src/components/text-editor/prosemirror-adapter/editor-config.spec.ts
  • src/components/text-editor/prosemirror-adapter/editor-config.ts
  • src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx`:
- Around line 223-245: The clear() method in ProseMirrorAdapter is canceling a
pending debounced change too early, which can drop a legitimate change('')
emission when the editor is already empty. Update clear() so it first checks the
serialized content state, and only cancels changeEmitter / resets changeWaiting
if it will actually call updateView(''); use the clear(), changeEmitter,
changeWaiting, and updateView symbols to keep the behavior aligned with
handleTransaction and lastEmittedValue.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8535a0c-e026-4419-802b-d81fdab531a0

📥 Commits

Reviewing files that changed from the base of the PR and between 719d673 and bc1e2e9.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • package.json
  • src/components/text-editor/prosemirror-adapter/editor-config.spec.ts
  • src/components/text-editor/prosemirror-adapter/editor-config.ts
  • src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx
🛑 Comments failed to post (1)
src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx (1)

223-245: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

clear() cancels a pending real change before checking if it's a no-op.

The pending debounced change is cancelled unconditionally whenever changeWaiting is true, before checking whether the current content is already empty. If a user just deleted all content (so currentContent === '' and a debounced change('') is pending) and a consumer calls clear() right at that moment, this method cancels that legitimate pending emission and then returns early (since content is already empty) — the consumer never receives the change('') event, even though lastEmittedValue was already updated internally in handleTransaction. This can leave a consumer's mirrored value permanently stale.

Reorder so the cancellation only happens once we know clear() will actually do work:

🐛 Proposed fix
     `@Method`()
     public async clear(): Promise<void> {
         if (!this.view) {
             return;
         }
 
-        // Drop any pending debounced change so it cannot fire after this
-        // clear and resurrect the old content.
-        if (this.changeWaiting) {
-            this.changeEmitter.cancel();
-            this.changeWaiting = false;
-        }
-
         const currentContent = this.contentConverter.serialize(
             this.view,
             this.schema
         );
         if (currentContent === '') {
             return;
         }
 
+        // Drop any pending debounced change so it cannot fire after this
+        // clear and resurrect the old content.
+        if (this.changeWaiting) {
+            this.changeEmitter.cancel();
+            this.changeWaiting = false;
+        }
+
         await this.updateView('');
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    `@Method`()
    public async clear(): Promise<void> {
        if (!this.view) {
            return;
        }

        const currentContent = this.contentConverter.serialize(
            this.view,
            this.schema
        );
        if (currentContent === '') {
            return;
        }

        // Drop any pending debounced change so it cannot fire after this
        // clear and resurrect the old content.
        if (this.changeWaiting) {
            this.changeEmitter.cancel();
            this.changeWaiting = false;
        }

        await this.updateView('');
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx`
around lines 223 - 245, The clear() method in ProseMirrorAdapter is canceling a
pending debounced change too early, which can drop a legitimate change('')
emission when the editor is already empty. Update clear() so it first checks the
serialized content state, and only cancels changeEmitter / resets changeWaiting
if it will actually call updateView(''); use the clear(), changeEmitter,
changeWaiting, and updateView symbols to keep the behavior aligned with
handleTransaction and lastEmittedValue.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Comment thread src/components/text-editor/prosemirror-adapter/editor-config.spec.ts Outdated
@john-traas john-traas force-pushed the text-editor-real-stack-tests branch from 6af6cc6 to c499fb6 Compare July 9, 2026 08:06
@john-traas john-traas removed their assignment Jul 9, 2026

@rasmusflomen rasmusflomen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one small question, nothing blocking. Other than that it's a clean, behaviour-preserving extraction — I diffed the moved code against the old method bodies and node/mark assembly plus plugin order match exactly. Nice to finally have the link-before-image paste ordering locked down by a real-stack test. Great work, John!

import { Languages } from '../../date-picker/date.types';
import { TriggerCharacter, InlineImages } from '../text-editor.types';

type ContentType = 'markdown' | 'html';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking, just a thought: now that this file names the type, could the adapter import ContentType from here instead of repeating the 'markdown' | 'html' literal at prosemirror-adapter.tsx:70? That way there's one definition and the two can't drift apart. Fine to leave since the literal predates this PR.

@rasmusflomen rasmusflomen self-assigned this Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants