January 2026
My Journey to Becoming a Jaspr Contributor: Fixing RawText Testing
By Bilal Aksoy
After building our website with Jaspr (detailed in our previous posts), I wanted to add proper component tests. We had three interactive components (Navigation, TechScroller, and ContactForm) and I wanted to verify they rendered correctly.
I wrote my first test for the Navigation component. Ran dart test. It crashed.
type 'TestRenderElement' is not a subtype of type 'MarkupRenderObject' in type cast
That's how I accidentally became a Jaspr contributor.
The Bug: When Tests Meet SVG Icons
Our Navigation component uses RawText to render SVG menu icons (the hamburger icon for mobile, the close icon for the overlay). When I tried to test the component with
jaspr_test's testComponents function, it failed immediately.
The error message pointed to raw_text_vm.dart, line 28, in the render object creation. Something about type casting.
At first, I thought I was using the testing framework wrong. I searched the documentation, looked at examples, tried different approaches. Nothing worked. Any component containing
RawText (which includes basically any component with inline SVG) couldn't be tested.
Understanding the Problem
I opened the Jaspr source code and found the issue in raw_text_vm.dart:
@override
MarkupRenderObject createRenderObject() {
return _RawTextElement(
parent: parent!.renderObject as MarkupRenderObject,
rawHtml: component.text,
);
}
The code assumes RawText always renders in a server context with MarkupRenderObject. But in test environments, Jaspr uses
TestRenderObject instead. The hard cast to MarkupRenderObject fails because tests use a different render object hierarchy.
This wasn't a theoretical edge case. It was blocking me from testing any component with SVG icons. I'd have to skip component-level tests entirely and rely only on unit tests for state logic.
The Fix: Making Tests Work Like Production
The solution required understanding Jaspr's rendering architecture. The framework has three different render contexts:
- Server-side rendering (SSR/SSG) uses
MarkupRenderObject - Client-side hydration uses
DomRenderObject - Test environment uses
TestRenderObject
RawText only supported the first two. Tests were left out.
The fix involved making the render object creation more flexible. Instead of hard-casting to MarkupRenderObject, the approach was to enable
rawHtml support in the test environment while keeping it separate from render contexts that don't need it.
Here's the conceptual approach (the actual implementation evolved through code review, detailed below):
// Added optional rawHtml parameter to base interface
RenderText createChildRenderText(String text, {String? rawHtml}) {
return RenderText(text: text, rawHtml: rawHtml);
}
Then updated TestRenderObject to handle raw HTML:
class TestRenderText implements RenderText {
TestRenderText({required this.text, this.rawHtml});
final String text;
final String? rawHtml;
@override
void update(String text, {String? rawHtml}) {
this.text = text;
this.rawHtml = rawHtml;
}
}
Finally, removed the problematic cast from raw_text_vm.dart:
@override
RenderObject createRenderObject() {
return parent!.renderObject.createChildRenderText(
component.text,
rawHtml: component.text,
);
}
The Pull Request
I created issue #726 documenting the bug and opened PR #727 with the fix.
The initial changes were surgical:
- 5 files modified
- +28, −20 lines
- No breaking changes
- All existing tests pass
The Iteration: Better Design Through Code Review
Shortly after submitting the PR, the maintainer (schultek) provided feedback. My initial approach added optional
rawHtml parameters to all render objects, including DomRenderObject which didn't need it.
The feedback: "I want to avoid DomRenderObject having the rawHtml parameter because it's not used."
This made perfect sense. Why add API surface to classes that don't need it? The suggestion was to create separate interface classes that only the relevant render objects would implement.
I refactored the solution to introduce two new interfaces:
abstract class RawableRenderObject {
RenderText createChildRenderText(String text, {String? rawHtml});
}
abstract class RawableRenderText extends RenderText {
void update(String text, {String? rawHtml});
}
Now only MarkupRenderObject and TestRenderObject implement RawableRenderObject, and only their corresponding text classes implement
RawableRenderText. DomRenderObject remains unchanged. Cleaner design, no unused parameters.
I pushed the updated implementation, and the PR is now awaiting final review.
Reflections
Before this, I'd never looked at Jaspr's internals. The render object architecture seemed complex and intimidating. But when I had a specific problem to solve, the code became much more approachable.
The error message ("type 'TestRenderElement' is not a subtype of type 'MarkupRenderObject' in type cast") was clear enough to point me to the exact line causing the problem. Without that clarity, I would have been lost.
My initial implementation worked, but it wasn't optimal. The maintainer's feedback pushed me to think more carefully about API design. Why add parameters to classes that don't need them? The refactored solution with interface classes felt cleaner.
I didn't need to be a Jaspr expert to contribute this fix. I just had a real problem I was trying to solve, and I was willing to read the source code and iterate based on feedback. The barrier to contributing felt lower than I expected.
The Bigger Picture
This fix came directly from building a real project with Jaspr. We weren't trying to contribute. We were trying to ship a website. But using a relatively new framework means occasionally hitting rough edges.
When I hit this edge, I could have worked around it or waited for someone else to fix it. Instead, I decided to try fixing it myself. That choice led to understanding the framework better and becoming part of the ecosystem rather than just consuming it.
The Merge
The PR was merged into Jaspr's main branch. The refactored solution with interface classes is now part of the framework's core testing infrastructure, awaiting the next release.
Future Jaspr developers won't face the testing blocker I encountered. And our Navigation component? It finally has proper component tests. No more workarounds, no more skipping tests because
RawText crashes them.
From bug reporter to framework contributor. All because we wanted to test our components properly.
Resources
By Bilal Aksoy • January 2026
← Back to Blog