Closed
Description
Hello,
First of all: great library!
I encountered an issue that I have problem solving and probably missing something in the setup. I searched for the solution in issues, tried different options but without success.
Looks like the component I'm testing is rendered twice. First test case works correctly, but the next one actually finds duplicated elements on the screen and looks like it's not teared down between tests.
Here is my test file:
spec.ts
const renderComponent = async () => {
const searchStoreMock = provideMockStore(SearchStore, {
selectors: [
{ selector: selectLoading, value: false },
{ selector: selectSearchHistory, value: [] },
{
selector: selectSearchResults,
value: {
results: []
},
},
{ selector: selectHasSearchResults, value: true }
],
});
const component = await render(SearchComponent, {
imports: [
ReactiveFormsModule,
provideTranslationsMock(),
MockComponent(ItemsListComponent),
],
providers: [
{ provide: SearchStore, useValue: searchStoreMock },
{
provide: ScrollMemoryService,
useValue: createMock(ScrollMemoryService),
},
{ provide: VehicleStatusService, useValue: { online$: of(true) } },
provideMock(Logger),
provideMock(UserStore),
provideMock(PlayerStore),
provideMock(DetailsStore),
],
});
const inputControl = screen.getByRole('textbox');
const searchModeSpy = subscribeSpyTo(
component.fixture.componentInstance.isSearchMode$
);
return {
component,
searchStoreMock,
inputControl,
searchModeSpy,
};
};
describe('SearchComponent', () => {
it('should call search when provided input', async () => {
// arrange
const { searchStoreMock, inputControl } = await renderComponent();
// act
await userEvent.type(inputControl, 'test');
// assert
expect(inputControl).toHaveValue('test');
await waitFor(() => expect(searchStoreMock.search).toBeCalledWith('test'));
});
it('should switch to dark mode when search query provided in input', async () => {
// arrange
const { searchStoreMock, inputControl, component, searchModeSpy } =
await renderComponent();
// assert
expect(searchModeSpy.getLastValue()).toBe(false);
// act
await userEvent.type(inputControl, 'test');
// assert
expect(inputControl).toHaveValue('test');
await waitFor(() =>
expect(i18n(T.spotify.tabbar.lib.search)).not.toBeInTheDocument()
);
});
});
and it fails at the second case with following error:
jest logs
FAIL ***/search/search.component.spec.ts
● SearchComponent › should switch to dark mode when search query provided in input
TestingLibraryElementError: Found multiple elements with the role "textbox"
Here are the matching elements:
Ignored nodes: comments, script, style
<input
class="input dark ng-untouched ng-valid ng-dirty"
name="search"
ng-reflect-form="[object Object]"
placeholder="Artists, Songs or Podcasts"
type="text"
/>
Ignored nodes: comments, script, style
<input
class="input dark ng-untouched ng-dirty ng-valid"
name="search"
ng-reflect-form="[object Object]"
placeholder="Artists, Songs or Podcasts"
type="text"
/>
(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).
Ignored nodes: comments, script, style
<body>
<div
class="screen-search page-container"
id="screen-search"
ng-version="14.0.0"
>
<section
class="page-content"
>
<page-title
class="hidden"
ng-reflect-hidden="true"
ng-reflect-title="Search"
>
<p
class="h2 header"
>
Search
</p>
<div
class="content"
>
<mond-profile
class="pressable"
>
<mond-image
class="image error-loading circle"
ng-reflect-circle="true"
ng-reflect-svg-placeholder="profile"
ng-reflect-svg-placeholder-size="full"
svgplaceholder="profile"
svgplaceholdersize="full"
>
<svg-icon
_nghost-a-c117=""
aria-hidden="true"
class="placeholder svg full"
ng-reflect-key="profile"
role="img"
/>
<img
alt="cover image"
class="image"
src=""
/>
<spinner
class="loader small"
ng-reflect-size="small"
ng-reflect-visible="false"
size="small"
/>
</mond-image>
</mond-profile>
</div>
</page-title>
<div
class="input-container"
>
<div
class="input-wrapper dark"
>
<input
class="input dark ng-untouched ng-valid ng-dirty"
name="search"
ng-reflect-form="[object Object]"
placeholder="Artists, Songs or Podcasts"
type="text"
/>
</div>
<mond-button
class="h4 cancel-button h5"
role="button"
type="button"
>
Cancel
</mond-button>
</div>
</section>
<section
class="search-content"
>
<section
class="page-content"
>
<div
class="filters"
>
<mond-button
class="h4 filter-button secondary disabled active"
ng-reflect-active="true"
ng-reflect-disabled="true"
ng-reflect-secondary="true"
role="button"
type="button"
>
Top results
</mond-button>
<mond-button
class="h4 filter-button secondary disabled"
ng-reflect-active="false"
ng-reflect-disabled="true"
ng-reflect-secondary="true"
role="button"
type="button"
>
Albums
</mond-button>
<mond-button
class="h4 filter-button secondary disabled"
ng-reflect-active="false"
ng-reflect-disabled="true"
ng-reflect-secondary="true"
role="button"
type="button"
>
Songs
</mond-button>
<mond-button
class="h4 filter-button secondary disabled"
ng-reflect-active="false"
ng-reflect-disabled="true"
ng-reflect-secondary="true"
role="button"
type="button"
>
Playlists
</mond-button>
<mond-button
class="h4 filter-button secondary disabled"
ng-reflect-active="false"
ng-reflect-disabled="true"
ng-reflect-secondary="true"
role="button"
type="button"
>
Artists
</mond-button>
<mond-button
class="h4 filter-button secondary disabled"
ng-reflect-active="false"
ng-reflect-disabled="true"
ng-reflect-secondary="true"
role="button"
type="button"
>
Podcasts & Shows
</mond-button>
</div>
</section>
<div
class="list-container"
>
<items-list
class="list active"
ng-reflect-items=""
ng-reflect-loading="false"
ng-reflect-scroll-memory-tag="search.top"
ng-reflect-transformer="extended"
ng-reflect-with-top-transparency="false"
/>
<items-list
class="list"
ng-reflect-items=""
ng-reflect-loading="false"
ng-reflect-scroll-memory-tag="search.albums"
...
70 | });
71 |
> 72 | const inputControl = screen.getByRole('textbox');
| ^
73 | const searchModeSpy = subscribeSpyTo(
74 | component.fixture.componentInstance.isSearchMode$
75 | );
My jest setup:
jest.config.js
module.exports = {
preset: 'jest-preset-angular',
testMatch: ['**/(*.)+(spec).+(ts)'],
setupFilesAfterEnv: [
'jest-preset-angular/setup-jest',
'@testing-library/jest-dom',
'<rootDir>/node_modules/@hirez_io/observer-spy/dist/setup-auto-unsubscribe.js',
],
setupFiles: ['jest-canvas-mock'],
moduleNameMapper: {
'^@/fg/(.*)$': '<rootDir>/projects/mond-fg/src/$1',
'^@/bg/(.*)$': '<rootDir>/projects/mond-bg/src/$1',
'^@/mond-types$': '<rootDir>/projects/mond-types',
'^@/tools$': '<rootDir>/projects/tools',
'^@/app-config$': '<rootDir>/projects/app-config',
'^i18n/(.*)$': '<rootDir>/projects/i18n/$1',
},
collectCoverage: true,
globalSetup: 'jest-preset-angular/global-setup',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
isolatedModules: true,
},
},
transform: {
'^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular',
},
coverageDirectory: 'coverage',
coverageReporters: ['json', 'lcov', 'text', 'clover', 'html'],
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/dist/'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
clearMocks: true,
restoreMocks: true,
resetMocks: true,
};
I'm running angular@14.0.0 on node@16.15.1. Dependencies versions:
package.json
{
"@testing-library/angular": "^12.3.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/user-event": "^14.4.3",
"jest": "28.1.3",
"jest-preset-angular": "^12.2.6",
"ng-mocks": "^14.11.0",
"ts-jest": "28.0.5",
}
I honestly have no idea why the mocks are not reset between the tests. I would love to have at least suggestion where the issue may lie. Thanks in advance!