bug(mock-doc): `Element.toggleAttribute()` is missing, so components that use it fail during SSR
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- web-dev
Research direction
The method belongs in MockElement in packages/mock-doc/src/node.ts, next to hasAttribute, setAttribute and removeAttribute. Read those first to match their conventions, then reproduce the missing method by calling toggleAttribute on a MockDocument element. Done means toggleAttribute covers the four cases in the issue's table (with and without force) and returns the right boolean, and the existing mock-doc tests still pass.
Written by the indexing model from the issue text.
Description
Prerequisites
- I have read the Contributing Guidelines.
- I agree to follow the Code of Conduct.
- I have searched for existing issues that already report this problem, without success.
Stencil Version
4.45.3 and 5.0.0-rc.1
Current Behavior
mock-doc's MockElement doesn't implement Element.toggleAttribute(). Server rendering runs components on mock-doc, so a component that calls it works in the browser but throws during renderToString(). The render still completes, but the attribute is missing from the output and the error appears only in diagnostics, which makes the bug easy to miss.
toggleAttribute() is the standard way to set a boolean attribute from state, and every browser Stencil 5 supports has it, so component authors reach for it naturally.
Expected Behavior
Workaround
Authors replace it with an explicit branch:
active ? this.el.setAttribute('data-active', '') : this.el.removeAttribute('data-active');
It works, but every component that may be server-rendered has to know to avoid a standard DOM method.
Suggested fix
Add toggleAttribute() to MockElement in packages/mock-doc/src/node.ts, next to hasAttribute, setAttribute and removeAttribute, with the DOM's semantics for the optional force argument:
toggleAttribute(attrName: string, force?: boolean): boolean {
const has = this.hasAttribute(attrName);
if (force === undefined ? !has : force) {
if (!has) this.setAttribute(attrName, '');
return true;
}
if (has) this.removeAttribute(attrName);
return false;
}
┌─────────────────────────────┬──────────────────┬───────────┬─────────┐
│ Call │ Attribute before │ After │ Returns │
├─────────────────────────────┼──────────────────┼───────────┼─────────┤
│ toggleAttribute('a') │ absent │ a="" │ true │
├─────────────────────────────┼──────────────────┼───────────┼─────────┤
│ toggleAttribute('a') │ present │ absent │ false │
├─────────────────────────────┼──────────────────┼───────────┼─────────┤
│ toggleAttribute('a', true) │ present │ unchanged │ true │
├─────────────────────────────┼──────────────────┼───────────┼─────────┤
│ toggleAttribute('a', false) │ absent │ unchanged │ false │
└─────────────────────────────┴──────────────────┴───────────┴─────────┘
Going through setAttribute and removeAttribute keeps mock-doc's existing attame lowercasing, and the attributeChangedCallback behavior.
System Info
Steps to Reproduce
Reproduction
import { Component, Element, Prop, h } from '@stencil/core';
@Component({ tag: 'my-flag', encapsulation: { type: 'shadow' } })
export class MyFlag {
@Element() el!: HTMLElement;
@Prop() active = false;
componentWillLoad() {
this.el.toggleAttribute('data-active', this.active);
}
render() {
return <slot />;
}
}
import { renderToString } from './hydrate/index.js';
const r = await renderToString('<my-flag active>Hi</my-flag>', { fullDocument: false });
console.log(r.html); // <my-flag active="" class="hydrated" s-id="1">…: no data-active
console.log(r.diagnostics); // TypeError: this.el.toggleAttribute is not a function
The same happens with new MockDocument() directly: typeof doc.createElement('div').toggleAttribute is 'undefined', on 4.45.3 and 5.0.0-rc.1.
### Code Reproduction URL
https://github.com
### Additional Information
_No response_
- Dominant language
- TypeScript
- Stars
- 13.2k
- Forks
- 865
- Avg merge
- 2d 16h
- Merged PRs (30d)
- 29
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from stenciljs/core
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Stencil FOUC styles should use @layer to avoid cascade conflicts with React 19Possibly taken @johnjenkins claimed this 230 days ago. OpenStencil v5
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 45/100
stenciljs/core#7016 · 1 reaction ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 22/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 35/100
Maintainers usually reply within 1 day
Similar issues
-
[BUG]: Duplicate preposition in heading and unformatted feedback text on Strengthen Argument pageOpenbug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
AOSSIE-Org/DebateAI#801 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
agents area: runtime automation chore cli consistency dependencies developer-experience difficulty: easy documentation enhancement good first issue help wanted hygiene json-output priority: P3 starters tests
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Add: BOM CineOpenapproved check:passed streams:add
Difficulty 1/5 Under an hour Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
area:claude-code-web bug has repro platform:linux platform:web
Difficulty 1/5 Under an hour Newbie friendliness 66/100
anthropics/claude-code#110168 ·