go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/build/components/build_table/summary_column/summary_column.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 { act, cleanup, render, screen } from '@testing-library/react'; 16 17 import { OutputBuild } from '@/build/types'; 18 import { Status } from '@/proto/go.chromium.org/luci/buildbucket/proto/common.pb'; 19 import { FakeContextProvider } from '@/testing_tools/fakes/fake_context_provider'; 20 21 import { BuildTable } from '../build_table'; 22 import { BuildTableBody } from '../build_table_body'; 23 import { BuildTableHead } from '../build_table_head'; 24 import { BuildTableRow } from '../build_table_row'; 25 import { useBuild } from '../context'; 26 27 import { SummaryContentCell, SummaryHeadCell } from './summary_column'; 28 29 jest.mock('../context', () => 30 self.createSelectiveSpiesFromModule<typeof import('../context')>( 31 '../context', 32 ['useBuild'], 33 ), 34 ); 35 36 const buildWithSummary = { 37 id: '1234', 38 status: Status.SUCCESS, 39 summaryMarkdown: '<strong>build is fine</strong>', 40 } as OutputBuild; 41 42 const buildWithNoSummary = { 43 id: '2345', 44 status: Status.SUCCESS, 45 } as OutputBuild; 46 47 describe('SummaryContentCell', () => { 48 beforeEach(() => { 49 jest.useFakeTimers(); 50 }); 51 52 afterEach(() => { 53 jest.useRealTimers(); 54 }); 55 56 describe('when there is summary', () => { 57 let useBuildSpy: jest.MockedFunctionDeep<typeof useBuild>; 58 59 beforeEach(() => { 60 useBuildSpy = jest.mocked(useBuild); 61 render( 62 <FakeContextProvider> 63 <BuildTable> 64 <BuildTableHead> 65 <SummaryHeadCell /> 66 </BuildTableHead> 67 <BuildTableBody> 68 <BuildTableRow build={buildWithSummary}> 69 <SummaryContentCell /> 70 </BuildTableRow> 71 </BuildTableBody> 72 </BuildTable> 73 </FakeContextProvider>, 74 ); 75 }); 76 77 afterEach(() => { 78 useBuildSpy.mockClear(); 79 cleanup(); 80 }); 81 82 it('should expand/collapse correctly', async () => { 83 const toggleRowButton = screen.getByLabelText('toggle-row'); 84 const toggleAllRowsButton = screen.getByLabelText('toggle-all-rows'); 85 86 expect(toggleRowButton).not.toBeDisabled(); 87 expect(toggleRowButton).not.toHaveStyleRule('visibility', 'hidden'); 88 89 expect( 90 toggleRowButton.querySelector("[data-testid='ChevronRightIcon']"), 91 ).not.toHaveStyle({ display: 'none' }); 92 expect( 93 toggleRowButton.querySelector("[data-testid='ExpandMoreIcon']"), 94 ).toHaveStyle({ display: 'none' }); 95 96 // Expand by clicking on toggle button. 97 act(() => toggleRowButton.click()); 98 expect( 99 toggleRowButton.querySelector("[data-testid='ChevronRightIcon']"), 100 ).toHaveStyle({ display: 'none' }); 101 expect( 102 toggleRowButton.querySelector("[data-testid='ExpandMoreIcon']"), 103 ).not.toHaveStyle({ display: 'none' }); 104 105 // Collapse by clicking on toggle button. 106 act(() => toggleRowButton.click()); 107 expect( 108 toggleRowButton.querySelector("[data-testid='ChevronRightIcon']"), 109 ).not.toHaveStyle({ display: 'none' }); 110 expect( 111 toggleRowButton.querySelector("[data-testid='ExpandMoreIcon']"), 112 ).toHaveStyle({ display: 'none' }); 113 114 // Expand again by changing the default state. 115 act(() => toggleAllRowsButton.click()); 116 await act(() => jest.runAllTimersAsync()); 117 expect( 118 toggleRowButton.querySelector("[data-testid='ChevronRightIcon']"), 119 ).toHaveStyle({ display: 'none' }); 120 expect( 121 toggleRowButton.querySelector("[data-testid='ExpandMoreIcon']"), 122 ).not.toHaveStyle({ display: 'none' }); 123 124 // Collapse again by changing the default state. 125 act(() => toggleAllRowsButton.click()); 126 await act(() => jest.runAllTimersAsync()); 127 expect( 128 toggleRowButton.querySelector("[data-testid='ChevronRightIcon']"), 129 ).not.toHaveStyle({ display: 'none' }); 130 expect( 131 toggleRowButton.querySelector("[data-testid='ExpandMoreIcon']"), 132 ).toHaveStyle({ display: 'none' }); 133 }); 134 135 it('should avoid unnecessary rerendering', async () => { 136 const toggleRowButton = screen.getByLabelText('toggle-row'); 137 const toggleAllRowsButton = screen.getByLabelText('toggle-all-rows'); 138 expect(useBuildSpy).toHaveBeenCalledTimes(1); 139 140 // Expand by clicking on toggle button. 141 act(() => toggleRowButton.click()); 142 expect(useBuildSpy).toHaveBeenCalledTimes(1); 143 144 // Expand by changing the default state. 145 act(() => toggleAllRowsButton.click()); 146 await act(() => jest.runAllTimersAsync()); 147 expect(useBuildSpy).toHaveBeenCalledTimes(1); 148 149 // Collapse by clicking on toggle button. 150 act(() => toggleRowButton.click()); 151 expect(useBuildSpy).toHaveBeenCalledTimes(1); 152 153 // Collapse by changing the default state. 154 act(() => toggleAllRowsButton.click()); 155 await act(() => jest.runAllTimersAsync()); 156 expect(useBuildSpy).toHaveBeenCalledTimes(1); 157 }); 158 }); 159 160 describe('when there is no summary', () => { 161 let useBuildSpy: jest.MockedFunctionDeep<typeof useBuild>; 162 163 beforeEach(() => { 164 useBuildSpy = jest.mocked(useBuild); 165 render( 166 <FakeContextProvider> 167 <BuildTable> 168 <BuildTableHead> 169 <SummaryHeadCell /> 170 </BuildTableHead> 171 <BuildTableBody> 172 <BuildTableRow build={buildWithNoSummary}> 173 <SummaryContentCell /> 174 </BuildTableRow> 175 </BuildTableBody> 176 </BuildTable> 177 </FakeContextProvider>, 178 ); 179 }); 180 181 afterEach(() => { 182 useBuildSpy.mockClear(); 183 cleanup(); 184 }); 185 186 it('should disable toggle button', async () => { 187 const toggleButton = screen.getByLabelText('toggle-row'); 188 189 expect(toggleButton).toBeDisabled(); 190 expect(toggleButton).toHaveStyleRule('visibility', 'hidden'); 191 }); 192 193 it('should avoid unnecessary rerendering', async () => { 194 const toggleAllRowsButton = screen.getByLabelText('toggle-all-rows'); 195 expect(useBuildSpy).toHaveBeenCalledTimes(1); 196 197 // Expand by changing the default state. 198 act(() => toggleAllRowsButton.click()); 199 await act(() => jest.runAllTimersAsync()); 200 // Not rerendered. There is no summary anyway. 201 expect(useBuildSpy).toHaveBeenCalledTimes(1); 202 203 // Collapse by changing the default state. 204 act(() => toggleAllRowsButton.click()); 205 await act(() => jest.runAllTimersAsync()); 206 // Not rerendered. There is no summary anyway. 207 expect(useBuildSpy).toHaveBeenCalledTimes(1); 208 }); 209 }); 210 });