go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/build/pages/builder_page/builder_page.test.tsx (about)

     1  // Copyright 2023 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import { GrpcError, RpcCode } from '@chopsui/prpc-client';
    16  import { act, render, screen } from '@testing-library/react';
    17  
    18  import { CacheOption } from '@/generic_libs/tools/cached_fn';
    19  import { BuilderItem } from '@/proto/go.chromium.org/luci/buildbucket/proto/builder_common.pb';
    20  import {
    21    BuildersClientImpl,
    22    GetBuilderRequest,
    23  } from '@/proto/go.chromium.org/luci/buildbucket/proto/builder_service.pb';
    24  import { FakeContextProvider } from '@/testing_tools/fakes/fake_context_provider';
    25  
    26  import { BuilderPage } from './builder_page';
    27  import { EndedBuildsSection } from './ended_builds_section';
    28  import { MachinePoolSection } from './machine_pool_section';
    29  
    30  jest.mock('./machine_pool_section', () =>
    31    createSelectiveMockFromModule<typeof import('./machine_pool_section')>(
    32      './machine_pool_section',
    33      ['MachinePoolSection'],
    34    ),
    35  );
    36  
    37  jest.mock('./ended_builds_section', () =>
    38    createSelectiveMockFromModule<typeof import('./ended_builds_section')>(
    39      './ended_builds_section',
    40      ['EndedBuildsSection'],
    41    ),
    42  );
    43  
    44  jest.mock('./started_builds_section', () =>
    45    createSelectiveMockFromModule<typeof import('./started_builds_section')>(
    46      './started_builds_section',
    47      ['StartedBuildsSection'],
    48    ),
    49  );
    50  
    51  jest.mock('./pending_builds_section', () =>
    52    createSelectiveMockFromModule<typeof import('./pending_builds_section')>(
    53      './pending_builds_section',
    54      ['PendingBuildsSection'],
    55    ),
    56  );
    57  
    58  jest.mock('./views_section', () =>
    59    createSelectiveMockFromModule<typeof import('./views_section')>(
    60      './views_section',
    61      ['ViewsSection'],
    62    ),
    63  );
    64  
    65  describe('BuilderPage', () => {
    66    let getBuilderMock: jest.SpyInstance<
    67      Promise<BuilderItem>,
    68      [req: GetBuilderRequest, cacheOpt?: CacheOption | undefined]
    69    >;
    70    let mockedEndedBuildsSection: jest.MockedFunctionDeep<
    71      typeof EndedBuildsSection
    72    >;
    73    let mockedMachinePoolSection: jest.MockedFunctionDeep<
    74      typeof MachinePoolSection
    75    >;
    76  
    77    beforeEach(() => {
    78      jest.useFakeTimers();
    79      getBuilderMock = jest.spyOn(BuildersClientImpl.prototype, 'GetBuilder');
    80      mockedEndedBuildsSection = jest
    81        .mocked(EndedBuildsSection)
    82        .mockReturnValue(<>mocked ended builds section</>);
    83      mockedMachinePoolSection = jest.mocked(MachinePoolSection);
    84    });
    85  
    86    afterEach(() => {
    87      jest.useRealTimers();
    88      getBuilderMock.mockReset();
    89      mockedEndedBuildsSection.mockReset();
    90      mockedMachinePoolSection.mockReset();
    91    });
    92  
    93    test('should render correctly', async () => {
    94      getBuilderMock.mockResolvedValue(
    95        BuilderItem.fromPartial({
    96          id: { project: 'project', bucket: 'bucket', builder: 'builder' },
    97          config: {
    98            swarmingHost: 'https://swarming.host',
    99            descriptionHtml: 'some builder description',
   100            dimensions: ['10:key1:val1', '12:key2:val2'] as readonly string[],
   101          },
   102        }),
   103      );
   104      render(
   105        <FakeContextProvider
   106          mountedPath="/p/:project/builder/:bucket/:builder"
   107          routerOptions={{
   108            initialEntries: ['/p/project/builder/bucket/builder'],
   109          }}
   110        >
   111          <BuilderPage />
   112        </FakeContextProvider>,
   113      );
   114  
   115      await act(() => jest.runAllTimersAsync());
   116  
   117      expect(
   118        screen.queryByText(
   119          'Failed to query the builder. If you can see recent builds, the builder might have been deleted recently.',
   120        ),
   121      ).not.toBeInTheDocument();
   122      expect(screen.getByText('some builder description')).toBeInTheDocument();
   123      expect(mockedEndedBuildsSection).toHaveBeenCalledWith(
   124        {
   125          builderId: { project: 'project', bucket: 'bucket', builder: 'builder' },
   126        },
   127        expect.anything(),
   128      );
   129      expect(mockedMachinePoolSection).toHaveBeenCalledWith(
   130        {
   131          swarmingHost: 'https://swarming.host',
   132          dimensions: [
   133            { key: 'key1', value: 'val1' },
   134            { key: 'key2', value: 'val2' },
   135          ],
   136        },
   137        expect.anything(),
   138      );
   139    });
   140  
   141    test('failing to query builder should not break the builder page', async () => {
   142      getBuilderMock.mockRejectedValue(
   143        new GrpcError(RpcCode.NOT_FOUND, 'builder was removed'),
   144      );
   145      render(
   146        <FakeContextProvider
   147          mountedPath="/p/:project/builder/:bucket/:builder"
   148          routerOptions={{
   149            initialEntries: ['/p/project/builder/bucket/builder'],
   150          }}
   151        >
   152          <BuilderPage />
   153        </FakeContextProvider>,
   154      );
   155  
   156      await act(() => jest.runAllTimersAsync());
   157  
   158      expect(
   159        screen.getByText(
   160          'Failed to query the builder. If you can see recent builds, the builder might have been deleted recently.',
   161        ),
   162      ).toBeInTheDocument();
   163      // The ended builds section should still be displayed.
   164      expect(screen.getByText('mocked ended builds section')).toBeInTheDocument();
   165      expect(mockedEndedBuildsSection).toHaveBeenCalledWith(
   166        {
   167          builderId: { project: 'project', bucket: 'bucket', builder: 'builder' },
   168        },
   169        expect.anything(),
   170      );
   171      expect(mockedMachinePoolSection).not.toHaveBeenCalled();
   172    });
   173  });