Skip to content

Commit abfe085

Browse files
fix(test): fix infinite loops w/ react and @testing-library/dom
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 defined 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.
1 parent 9173759 commit abfe085

File tree

3 files changed

+21
-7
lines changed

3 files changed

+21
-7
lines changed

src/compiler/transformers/add-component-meta-proxy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export const createComponentMetadataProxy = (compilerMeta: d.ComponentCompilerMe
6464
*/
6565
export const createAnonymousClassMetadataProxy = (
6666
compilerMeta: d.ComponentCompilerMeta,
67-
clazz: ts.Expression
67+
clazz: ts.ClassExpression
6868
): ts.CallExpression => {
6969
const compactMeta: d.ComponentRuntimeMetaCompact = formatComponentRuntimeMeta(compilerMeta, true);
7070
const literalMeta = convertValueToLiteral(compactMeta);

src/compiler/transformers/component-native/proxy-custom-element-function.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,22 @@ export const proxyCustomElement = (
4747
continue;
4848
}
4949

50+
// to narrow the type of `declaration.initializer` to `ts.ClassExpression`
51+
if (!ts.isClassExpression(declaration.initializer)) {
52+
continue;
53+
}
54+
55+
const renamedClassExpression = ts.factory.updateClassExpression(
56+
declaration.initializer,
57+
ts.getModifiers(declaration.initializer),
58+
ts.factory.createIdentifier(principalComponent.componentClassName),
59+
declaration.initializer.typeParameters,
60+
declaration.initializer.heritageClauses,
61+
declaration.initializer.members
62+
);
63+
5064
// wrap the Stencil component's class declaration in a component proxy
51-
const proxyCreationCall = createAnonymousClassMetadataProxy(principalComponent, declaration.initializer);
65+
const proxyCreationCall = createAnonymousClassMetadataProxy(principalComponent, renamedClassExpression);
5266
ts.addSyntheticLeadingComment(proxyCreationCall, ts.SyntaxKind.MultiLineCommentTrivia, '@__PURE__', false);
5367

5468
// update the component's variable declaration to use the new initializer

src/compiler/transformers/test/proxy-custom-element-function.spec.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe('proxy-custom-element-function', () => {
8282
const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]);
8383

8484
expect(transpiledModule.outputText).toContain(
85-
`export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true);`
85+
`export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true);`
8686
);
8787
});
8888

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

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

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

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

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

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

118118
expect(transpiledModule.outputText).toContain(
119-
`export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true), bar = 'goodbye?';`
119+
`export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true), bar = 'goodbye?';`
120120
);
121121
});
122122
});

0 commit comments

Comments
 (0)