Skip to content

bug: newSpecPage does not invoke @AttrDeserialize decorator #4

@Jagget

Description

@Jagget

Prerequisites

Stencil Version

4.38.3

Current Behavior

newSpecPage from jest-stencil-runner does not call @AttrDeserialize methods. The prop retains the raw attribute string instead of the deserialized object.

  • instance.test equals '{"foo":"bar","num":42}' (string)
  • instance.test should equal { foo: 'bar', num: 42 } (object)

Expected Behavior

When a component uses @AttrDeserialize to parse complex attribute strings (e.g., JSON), the newSpecPage function should invoke the deserializer when hydrating the component from HTML. The prop should contain the deserialized value (parsed object), not the raw attribute string.

System Info

             System: node 24.11.0
           Platform: darwin (24.6.0)
          CPU Model: Apple M2 Pro (12 cpus)
              Build: 1762374454
            Stencil: 4.38.3 😋
         TypeScript: 5.9.3
             Rollup: 4.34.9
             Parse5: 7.2.1
             jQuery: 4.0.0-pre
             Terser: 5.37.0
jest-stencil-runner: 0.0.9
               Jest: 30.2.0

Steps to Reproduce

  1. Create a component with @PropSerialize and @AttrDeserialize decorators:
import { AttrDeserialize, Component, Prop, PropSerialize, h } from '@stencil/core';

@Component({
  tag: 'inner-one',
  shadow: true,
})
export class InnerOne {
  @Prop() test: Record<string, unknown> = {};

  @PropSerialize('test')
  userSerialize(newVal: Record<string, unknown>) {
    try {
      return JSON.stringify(newVal);
    } catch (e) {
      return null;
    }
  }

  @AttrDeserialize('test')
  userDeserialize(newVal: string) {
    try {
      return JSON.parse(newVal);
    } catch (e) {
      return null;
    }
  }

  render() {
    return <div class="test">{JSON.stringify(this.test)}</div>;
  }
}
  1. Write a spec test:
import { newSpecPage } from 'jest-stencil-runner';
import { InnerOne } from './inner-one';

it('should deserialize test prop from JSON attribute string', async () => {
  const page = await newSpecPage({
    components: [InnerOne],
    html: `<inner-one test='{"foo":"bar","num":42}'></inner-one>`,
  });

  const instance = page.rootInstance as InnerOne;
  expect(instance.test).toEqual({ foo: 'bar', num: 42 }); // FAILS
});
  1. Run the test — it fails because instance.test is the raw string, not the parsed object.

  2. Additionally, accessing object properties in render fails:

render() {
  return (
    <div>
      <div class="test">{JSON.stringify(this.test)}</div>
      <div class="test-obj">{this.test?.foo}</div>
    </div>
  );
}
it('should render test prop from JSON attribute string', async () => {
  const page = await newSpecPage({
    components: [InnerOne],
    html: `<inner-one test='{"foo":"bar","num":42}'></inner-one>`,
  });

  const testDiv = page.root?.shadowRoot?.querySelector('div.test-obj');
  expect(testDiv).toEqualText('bar'); // FAILS - renders empty because this.test is a string
});

Code Reproduction URL

...

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions