Add tests to demonstrate a race condition in EntityListProvider
Signed-off-by: Jacob Raihle kdm951 <jacob.raihle@teliacompany.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import type { GetEntitiesResponse } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
alertApiRef,
|
||||
@@ -23,7 +23,10 @@ import {
|
||||
identityApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import { mockApis, TestApiProvider } from '@backstage/test-utils';
|
||||
import { useMountEffect } from '@react-hookz/web';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import qs from 'qs';
|
||||
import { PropsWithChildren } from 'react';
|
||||
@@ -37,10 +40,9 @@ import {
|
||||
EntityTypeFilter,
|
||||
EntityUserFilter,
|
||||
} from '../filters';
|
||||
import { EntityListProvider, useEntityList } from './useEntityListProvider';
|
||||
import { useMountEffect } from '@react-hookz/web';
|
||||
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { MockPromise } from '../testUtils/MockPromise';
|
||||
import { EntityListPagination } from '../types';
|
||||
import { EntityListProvider, useEntityList } from './useEntityListProvider';
|
||||
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
@@ -341,6 +343,62 @@ describe('<EntityListProvider />', () => {
|
||||
filter: { kind: 'group' },
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the last applied filter even if an earlier request finishes later', async () => {
|
||||
const { result } = renderHook(() => useEntityList(), {
|
||||
wrapper: createWrapper({ pagination }),
|
||||
});
|
||||
|
||||
const firstResult = new MockPromise<GetEntitiesResponse>();
|
||||
const secondResult = new MockPromise<GetEntitiesResponse>();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.backendEntities.length).toBeGreaterThan(0);
|
||||
});
|
||||
expect(result.current.totalItems).toBe(2);
|
||||
expect(result.current.backendEntities.length).toBe(2);
|
||||
expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
|
||||
|
||||
mockCatalogApi.getEntities!.mockReturnValueOnce(firstResult);
|
||||
|
||||
await act(async () => {
|
||||
result.current.updateFilters({
|
||||
kind: new EntityKindFilter('api', 'API'),
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCatalogApi.getEntities).toHaveBeenNthCalledWith(2, {
|
||||
filter: { kind: 'api' },
|
||||
});
|
||||
});
|
||||
|
||||
mockCatalogApi.getEntities!.mockReturnValueOnce(secondResult);
|
||||
|
||||
await act(async () => {
|
||||
result.current.updateFilters({
|
||||
kind: new EntityKindFilter('system', 'System'),
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCatalogApi.getEntities).toHaveBeenNthCalledWith(3, {
|
||||
filter: { kind: 'system' },
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
secondResult.resolve({
|
||||
items: [],
|
||||
});
|
||||
firstResult.resolve({
|
||||
items: entities,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.filters.kind!.value).toBe('system');
|
||||
expect(result.current.backendEntities.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('<EntityListProvider pagination />', () => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { MockPromise } from './MockPromise.ts';
|
||||
|
||||
describe('MockPromise', () => {
|
||||
it('is resolved when its resolve function is called', async () => {
|
||||
const p = new MockPromise<number>();
|
||||
p.resolve(12);
|
||||
return expect(p).resolves.toBe(12);
|
||||
});
|
||||
|
||||
it('is rejected when its reject function is called', async () => {
|
||||
const p = new MockPromise();
|
||||
p.reject('Test rejection');
|
||||
return expect(p).rejects.toMatch('Test rejection');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
type ResolveFn<T> = (value: T | PromiseLike<T>) => void;
|
||||
type RejectFn = (reason?: any) => void;
|
||||
type Executor<T> = (resolve: ResolveFn<T>, reject: RejectFn) => void;
|
||||
|
||||
export class MockPromise<T> extends Promise<T> {
|
||||
private _resolve!: ResolveFn<T>;
|
||||
private _reject!: RejectFn;
|
||||
|
||||
constructor(executor?: Executor<T>) {
|
||||
if (executor !== undefined) {
|
||||
super(executor);
|
||||
return;
|
||||
}
|
||||
|
||||
let res: ResolveFn<T>;
|
||||
let rej: RejectFn;
|
||||
super((resolve, reject) => {
|
||||
res = resolve;
|
||||
rej = reject;
|
||||
});
|
||||
this._resolve = res!;
|
||||
this._reject = rej!;
|
||||
}
|
||||
|
||||
get resolve() {
|
||||
return this._resolve;
|
||||
}
|
||||
|
||||
get reject() {
|
||||
return this._reject;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user