Skip to content

Commit

Permalink
fix(test): fix infinite loops w/ react and @testing-library/dom
Browse files Browse the repository at this point in the history
This fixes an issue (documented in #3434) where when using
`@testing-libary/dom` to test a Stencil component wrapped with the React
framework wrappers could produce an infinite loop that would cause the
tests to fail.

The issue relates to an assumption that `@testing-library/dom` makes
about the `.name` property on the constructor for a custom element. In
particular, `@testing-library/dom` expects the property to be truthy
here:

https://github.com/testing-library/dom-testing-library/blob/fb069c93983bc0300a6e1c91bdec5bf9443b5286/src/DOMElementFilter.ts#L198

When building with the `dist-custom-elements` output target we create an
anonymous class expression and inline it into a call in the emitted JS
to `proxyCustomElement`, like this:

```js
const MyComponent$1 = /*@__PURE__*/ proxyCustomElement(
  class extends HTMLElement {
    ...
  },
  [1, "my-component", {}]
);
```

We made a change (#3248) to fix an issue (#3191) with webpack
treeshaking where if we didn't inline an anonymous class expression like
this we would get improper tree shaking in webpack.

One consequence, however, of an _anonymous_ inline class expression is
that the `.name` property on its constructor is going to be `""`, which
fails the false-ey test in `@testing-library/dom` referenced above.

So in order to fix the issue we can simply insert a name so that the
inlined class expression is no longer anonymous, like so:

```js
const MyComponent$1 = /*@__PURE__*/ proxyCustomElement(
  class MyComponent extends HTMLElement {
    ...
  },
  [1, "my-component", {}]
);
```

This fixes the issue with infinite loops while testing with the React
wrapper. Additionally, using the reproduction case provided for #3191 we
can confirm that this does not cause a regression with respect the
previous fix for the webpack treeshaking issue.
  • Loading branch information
alicewriteswrongs committed Mar 22, 2023
1 parent 9173759 commit 8078da7
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/compiler/transformers/add-component-meta-proxy.ts
Expand Up @@ -64,7 +64,7 @@ export const createComponentMetadataProxy = (compilerMeta: d.ComponentCompilerMe
*/
export const createAnonymousClassMetadataProxy = (
compilerMeta: d.ComponentCompilerMeta,
clazz: ts.Expression
clazz: ts.ClassExpression
): ts.CallExpression => {
const compactMeta: d.ComponentRuntimeMetaCompact = formatComponentRuntimeMeta(compilerMeta, true);
const literalMeta = convertValueToLiteral(compactMeta);
Expand Down
Expand Up @@ -47,8 +47,22 @@ export const proxyCustomElement = (
continue;
}

// to narrow the type of `declaration.initializer` to `ts.ClassExpression`
if (!ts.isClassExpression(declaration.initializer)) {
continue;
}

const renamedClassExpression = ts.factory.updateClassExpression(
declaration.initializer,
ts.getModifiers(declaration.initializer),
ts.factory.createIdentifier(principalComponent.componentClassName),
declaration.initializer.typeParameters,
declaration.initializer.heritageClauses,
declaration.initializer.members
);

// wrap the Stencil component's class declaration in a component proxy
const proxyCreationCall = createAnonymousClassMetadataProxy(principalComponent, declaration.initializer);
const proxyCreationCall = createAnonymousClassMetadataProxy(principalComponent, renamedClassExpression);
ts.addSyntheticLeadingComment(proxyCreationCall, ts.SyntaxKind.MultiLineCommentTrivia, '@__PURE__', false);

// update the component's variable declaration to use the new initializer
Expand Down
Expand Up @@ -82,7 +82,7 @@ describe('proxy-custom-element-function', () => {
const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]);

expect(transpiledModule.outputText).toContain(
`export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true);`
`export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true);`
);
});

Expand All @@ -94,7 +94,7 @@ describe('proxy-custom-element-function', () => {
const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]);

expect(transpiledModule.outputText).toContain(
`export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true);`
`export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true);`
);
});

Expand All @@ -105,18 +105,18 @@ describe('proxy-custom-element-function', () => {
const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]);

expect(transpiledModule.outputText).toContain(
`export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true), foo = 'hello world!';`
`export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true), foo = 'hello world!';`
);
});

it('wraps a class initializer properly in the middle of multiple variable declarations', () => {
const code = `const foo = 'hello world!', ${componentClassName} = class extends HTMLElement {}, bar = 'goodbye?'`;
const code = `const foo = 'hello world!', ${componentClassName} = class ${componentClassName} extends HTMLElement {}, bar = 'goodbye?'`;

const transformer = proxyCustomElement(compilerCtx, transformOpts);
const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]);

expect(transpiledModule.outputText).toContain(
`export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true), bar = 'goodbye?';`
`export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true), bar = 'goodbye?';`
);
});
});
Expand Down

0 comments on commit 8078da7

Please sign in to comment.