angular/components · error · Error

setContenteditableValue can only be called on a `contentedit

Error message

setContenteditableValue can only be called on a `contenteditable` element.

What it means

UnitTestElement.setContenteditableValue in src/cdk/testing/testbed/unit-test-element.ts is the TestBed (unit-test) implementation of setContenteditableValue. It performs the identical guard — the contenteditable attribute must be '', 'true', or 'plaintext-only' — and throws this Error otherwise. After the guard it stabilizes the fixture and sets element.textContent directly.

Source

Thrown at src/cdk/testing/testbed/unit-test-element.ts:204

    if (options?.exclude) {
      return _getTextWithExcludedElements(this.element, options.exclude);
    }
    return (this.element.textContent || '').trim();
  }

  /**
   * Sets the value of a `contenteditable` element.
   * @param value Value to be set on the element.
   */
  async setContenteditableValue(value: string): Promise<void> {
    const contenteditableAttr = await this.getAttribute('contenteditable');

    if (
      contenteditableAttr !== '' &&
      contenteditableAttr !== 'true' &&
      contenteditableAttr !== 'plaintext-only'
    ) {
      throw new Error('setContenteditableValue can only be called on a `contenteditable` element.');
    }

    await this._stabilize();
    this.element.textContent = value;
  }

  /** Gets the value for the given attribute from the element. */
  async getAttribute(name: string): Promise<string | null> {
    await this._stabilize();
    return this.element.getAttribute(name);
  }

  /** Checks whether the element has the given class. */
  async hasClass(name: string): Promise<boolean> {
    await this._stabilize();
    return this.element.classList.contains(name);
  }

View on GitHub (pinned to 0411926e7d)

Solutions

  1. Render the element with contenteditable="true" (or bind [attr.contenteditable] and set isEditing = true in the test) before the call.
  2. Use sendKeys for inputs and textareas; setContenteditableValue is only for contenteditable nodes.
  3. Trigger change detection (fixture.detectChanges()) after toggling the attribute so the DOM reflects editability.
  4. Guard the call with getAttribute('contenteditable') to fail with a clearer test assertion instead of the throw.

Example fix

// before
it('sets text', () => { el.setContenteditableValue('x'); });
// after
it('sets text', () => { fixture.componentInstance.isEditing = true; fixture.detectChanges(); el.setContenteditableValue('x'); });
Defensive patterns

Strategy: validation

Validate before calling

// In the unit test, before calling:
el.removeAttribute('contenteditable') === undefined; // ensure present
fixture.componentInstance.isEditing = true;
fixture.detectChanges();
expect(el.getAttribute('contenteditable')).toBe('true');

Type guard

function canSetContenteditable(el: HTMLElement): boolean {
  const v = el.getAttribute('contenteditable');
  return v === 'true' || v === 'plaintext-only' || v === '';
}

Try / catch

try {
  await el.setContenteditableValue('new text');
} catch (e) {
  if (String(e.message).includes('setContenteditableValue can only be called')) {
    fail('Element under test must render with contenteditable="true"');
  } else throw e;
}

Prevention

When it happens

Trigger: In unit tests using TestbedHarnessEnvironment/TestElement, calling setContenteditableValue on an element rendered without a contenteditable attribute (or with contenteditable="false"), such as a plain div, span, or form input.

Common situations: Unit tests for custom rich-text/editor components where the editable attribute is bound conditionally ([attr.contenteditable]="isEditing") and the test forgot to enable editing; tests copied from sendKeys examples and adapted to the wrong API.

Related errors


AI-assisted analysis of angular/components@0411926e7d (2026-08-31). Data as JSON: /api/errors/9cc634a2957689f2. Report an issue: GitHub.