addons/isl/src/__tests__/GeneratedFiles.test.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import {act, fireEvent, render, screen, waitFor} from '@testing-library/react';
b69ab319import App from '../App';
b69ab3110import {generatedFileCache} from '../GeneratedFile';
b69ab3111import {__TEST__} from '../UncommittedChanges';
b69ab3112import {readAtom, writeAtom} from '../jotaiUtils';
b69ab3113import platform from '../platform';
b69ab3114import {ignoreRTL} from '../testQueries';
b69ab3115import {
b69ab3116 closeCommitInfoSidebar,
b69ab3117 COMMIT,
b69ab3118 expectMessageSentToServer,
b69ab3119 openCommitInfoSidebar,
b69ab3120 resetTestMessages,
b69ab3121 simulateCommits,
b69ab3122 simulateMessageFromServer,
b69ab3123 simulateRepoConnected,
b69ab3124 simulateUncommittedChangedFiles,
b69ab3125} from '../testUtils';
b69ab3126import {GeneratedStatus} from '../types';
b69ab3127
b69ab3128/** Generated `num` files, in the repeating pattern: generated, partially generated, manual */
b69ab3129async function simulateGeneratedFiles(num: number) {
b69ab3130 const files = new Array(num).fill(null).map((_, i) => `file_${zeroPad(i)}.txt`);
b69ab3131 act(() => {
b69ab3132 simulateUncommittedChangedFiles({
b69ab3133 value: files.map(path => ({
b69ab3134 path,
b69ab3135 status: 'M',
b69ab3136 })),
b69ab3137 });
b69ab3138 });
b69ab3139 await waitFor(() => {
b69ab3140 expectMessageSentToServer({
b69ab3141 type: 'fetchGeneratedStatuses',
b69ab3142 paths: expect.anything(),
b69ab3143 });
b69ab3144 });
b69ab3145 act(() => {
b69ab3146 simulateMessageFromServer({
b69ab3147 type: 'fetchedGeneratedStatuses',
b69ab3148 results: Object.fromEntries(
b69ab3149 files.map((path, i) => [
b69ab3150 path,
b69ab3151 i % 3 === 0
b69ab3152 ? GeneratedStatus.Generated
b69ab3153 : i % 3 === 1
b69ab3154 ? GeneratedStatus.PartiallyGenerated
b69ab3155 : GeneratedStatus.Manual,
b69ab3156 ]),
b69ab3157 ),
b69ab3158 });
b69ab3159 });
b69ab3160}
b69ab3161
b69ab3162function zeroPad(n: number): string {
b69ab3163 return ('000' + n.toString()).slice(-3);
b69ab3164}
b69ab3165
b69ab3166describe('Generated Files', () => {
b69ab3167 beforeEach(() => {
b69ab3168 resetTestMessages();
b69ab3169 render(<App />);
b69ab3170 generatedFileCache.clear();
b69ab3171 act(() => {
b69ab3172 simulateRepoConnected();
b69ab3173 closeCommitInfoSidebar();
b69ab3174 expectMessageSentToServer({
b69ab3175 type: 'subscribe',
b69ab3176 kind: 'smartlogCommits',
b69ab3177 subscriptionID: expect.anything(),
b69ab3178 });
b69ab3179 simulateCommits({
b69ab3180 value: [
b69ab3181 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab3182 COMMIT('a', 'My Commit', '1'),
b69ab3183 COMMIT('b', 'Another Commit', 'a', {isDot: true}),
b69ab3184 ],
b69ab3185 });
b69ab3186 expectMessageSentToServer({
b69ab3187 type: 'subscribe',
b69ab3188 kind: 'uncommittedChanges',
b69ab3189 subscriptionID: expect.anything(),
b69ab3190 });
b69ab3191 });
b69ab3192 });
b69ab3193
b69ab3194 it('fetches generated files for uncommitted changes', async () => {
b69ab3195 await simulateGeneratedFiles(5);
b69ab3196 expectMessageSentToServer({
b69ab3197 type: 'fetchGeneratedStatuses',
b69ab3198 paths: ['file_000.txt', 'file_001.txt', 'file_002.txt', 'file_003.txt', 'file_004.txt'],
b69ab3199 });
b69ab31100 });
b69ab31101
b69ab31102 it('Shows generated files in their own sections', async () => {
b69ab31103 await simulateGeneratedFiles(10);
b69ab31104
b69ab31105 expect(screen.getByText(ignoreRTL('file_002.txt'))).toBeInTheDocument();
b69ab31106 expect(screen.getByText(ignoreRTL('file_005.txt'))).toBeInTheDocument();
b69ab31107 expect(screen.getByText(ignoreRTL('file_008.txt'))).toBeInTheDocument();
b69ab31108 expect(screen.getByText('Generated Files')).toBeInTheDocument();
b69ab31109 expect(screen.getByText('Partially Generated Files')).toBeInTheDocument();
b69ab31110 });
b69ab31111
b69ab31112 function goToNextPage() {
b69ab31113 fireEvent.click(screen.getByTestId('changed-files-next-page'));
b69ab31114 }
b69ab31115
b69ab31116 function expectHasPartiallyGeneratedFiles() {
b69ab31117 expect(screen.queryByText('Partially Generated Files')).toBeInTheDocument();
b69ab31118 }
b69ab31119 function expectHasGeneratedFiles() {
b69ab31120 expect(screen.queryByText('Generated Files')).toBeInTheDocument();
b69ab31121 }
b69ab31122 function expectNOTHasGeneratedFiles() {
b69ab31123 expect(screen.queryByText('Generated Files')).not.toBeInTheDocument();
b69ab31124 }
b69ab31125
b69ab31126 function getChangedFiles() {
b69ab31127 const found = [...document.querySelectorAll('.changed-file-path-text')].map(e =>
b69ab31128 (e as HTMLElement).innerHTML.replace(/\u200E/g, ''),
b69ab31129 );
b69ab31130 return found;
b69ab31131 }
b69ab31132
b69ab31133 it('Paginates generated files', async () => {
b69ab31134 await simulateGeneratedFiles(1200);
b69ab31135 // 1200 files, but 1000 files per fetched batch of generated statuses.
b69ab31136 // Sorted by status, that puts 1000/3 manual files, then 1000/3 partially generated, then 1000/3 generated,
b69ab31137 // then the remaining 200/3 manual, 200/3 partially generated, and 200/3 generated,
b69ab31138 // all in pages of 500 at a time.
b69ab31139
b69ab31140 // first page is manual and partial
b69ab31141 expectHasPartiallyGeneratedFiles();
b69ab31142 expectNOTHasGeneratedFiles();
b69ab31143 expect(getChangedFiles()).toMatchSnapshot();
b69ab31144
b69ab31145 // next page has partial and generated
b69ab31146 goToNextPage();
b69ab31147 expectHasPartiallyGeneratedFiles();
b69ab31148 expectHasGeneratedFiles();
b69ab31149 expect(getChangedFiles()).toMatchSnapshot();
b69ab31150
b69ab31151 // next page has remaining files from all 3 types
b69ab31152 goToNextPage();
b69ab31153 expectHasPartiallyGeneratedFiles();
b69ab31154 expectHasGeneratedFiles();
b69ab31155 expect(getChangedFiles()).toMatchSnapshot();
b69ab31156 });
b69ab31157
b69ab31158 it('Warns about too many files to fetch all generated statuses', async () => {
b69ab31159 await simulateGeneratedFiles(1001);
b69ab31160 expect(
b69ab31161 screen.getByText('There are more than 1000 files, some files may appear out of order'),
b69ab31162 ).toBeInTheDocument();
b69ab31163 });
b69ab31164
b69ab31165 it('remembers expanded state', async () => {
b69ab31166 writeAtom(__TEST__.generatedFilesInitiallyExpanded, true);
b69ab31167
b69ab31168 await simulateGeneratedFiles(1);
b69ab31169
b69ab31170 expect(screen.getByText(ignoreRTL('file_000.txt'))).toBeInTheDocument();
b69ab31171 expect(screen.getByText('Generated Files')).toBeInTheDocument();
b69ab31172 });
b69ab31173
b69ab31174 it('writes expanded state', async () => {
b69ab31175 expect(readAtom(__TEST__.generatedFilesInitiallyExpanded)).toEqual(false);
b69ab31176
b69ab31177 await simulateGeneratedFiles(1);
b69ab31178
b69ab31179 fireEvent.click(screen.getByText('Generated Files'));
b69ab31180
b69ab31181 expect(readAtom(__TEST__.generatedFilesInitiallyExpanded)).toEqual(true);
b69ab31182 });
b69ab31183
b69ab31184 it('clears generated files cache on refresh click', async () => {
b69ab31185 act(() => {
b69ab31186 simulateUncommittedChangedFiles({
b69ab31187 value: [
b69ab31188 {
b69ab31189 path: 'file.txt',
b69ab31190 status: 'M',
b69ab31191 },
b69ab31192 ],
b69ab31193 });
b69ab31194 });
b69ab31195 await waitFor(() => {
b69ab31196 expectMessageSentToServer({
b69ab31197 type: 'fetchGeneratedStatuses',
b69ab31198 paths: ['file.txt'],
b69ab31199 });
b69ab31200 });
b69ab31201 act(() => {
b69ab31202 simulateMessageFromServer({
b69ab31203 type: 'fetchedGeneratedStatuses',
b69ab31204 results: Object.fromEntries([['file.txt', GeneratedStatus.Manual]]),
b69ab31205 });
b69ab31206 });
b69ab31207
b69ab31208 expect(screen.queryByText('Generated Files')).not.toBeInTheDocument();
b69ab31209
b69ab31210 act(() => {
b69ab31211 fireEvent.click(screen.getByTestId('refresh-button'));
b69ab31212 });
b69ab31213 await waitFor(() => {
b69ab31214 expectMessageSentToServer({
b69ab31215 type: 'fetchGeneratedStatuses',
b69ab31216 paths: ['file.txt'],
b69ab31217 });
b69ab31218 });
b69ab31219 act(() => {
b69ab31220 simulateMessageFromServer({
b69ab31221 type: 'fetchedGeneratedStatuses',
b69ab31222 results: Object.fromEntries([['file.txt', GeneratedStatus.Generated]]),
b69ab31223 });
b69ab31224 });
b69ab31225
b69ab31226 expect(screen.getByText('Generated Files')).toBeInTheDocument();
b69ab31227 });
b69ab31228
b69ab31229 describe('Open All Files', () => {
b69ab31230 beforeEach(() => act(() => openCommitInfoSidebar()));
b69ab31231 async function simulateCommitWithFiles(files: Record<string, GeneratedStatus>) {
b69ab31232 act(() => {
b69ab31233 simulateCommits({
b69ab31234 value: [
b69ab31235 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab31236 COMMIT('a', 'Commit A', '1', {
b69ab31237 isDot: true,
b69ab31238 totalFileCount: 3,
b69ab31239 filePathsSample: Object.keys(files),
b69ab31240 }),
b69ab31241 ],
b69ab31242 });
b69ab31243 });
b69ab31244 await waitFor(() => {
b69ab31245 expectMessageSentToServer({
b69ab31246 type: 'fetchGeneratedStatuses',
b69ab31247 paths: expect.anything(),
b69ab31248 });
b69ab31249 });
b69ab31250 act(() => {
b69ab31251 simulateMessageFromServer({
b69ab31252 type: 'fetchedGeneratedStatuses',
b69ab31253 results: files,
b69ab31254 });
b69ab31255 });
b69ab31256 }
b69ab31257
b69ab31258 it('No generated files, opens all files', async () => {
b69ab31259 const openSpy = jest.spyOn(platform, 'openFiles').mockImplementation(() => {});
b69ab31260 await simulateCommitWithFiles({
b69ab31261 'file_partial.txt': GeneratedStatus.PartiallyGenerated,
b69ab31262 'file_manual.txt': GeneratedStatus.Manual,
b69ab31263 });
b69ab31264
b69ab31265 fireEvent.click(screen.getByText('Open All Files'));
b69ab31266 expect(openSpy).toHaveBeenCalledTimes(1);
b69ab31267 expect(openSpy).toHaveBeenCalledWith(['file_partial.txt', 'file_manual.txt']);
b69ab31268 });
b69ab31269
b69ab31270 it('Some generated files, opens all non-generated files', async () => {
b69ab31271 const openSpy = jest.spyOn(platform, 'openFiles').mockImplementation(() => {});
b69ab31272 await simulateCommitWithFiles({
b69ab31273 'file_gen.txt': GeneratedStatus.Generated,
b69ab31274 'file_partial.txt': GeneratedStatus.PartiallyGenerated,
b69ab31275 'file_manual.txt': GeneratedStatus.Manual,
b69ab31276 });
b69ab31277
b69ab31278 fireEvent.click(screen.getByText('Open Non-Generated Files'));
b69ab31279 expect(openSpy).toHaveBeenCalledTimes(1);
b69ab31280 expect(openSpy).toHaveBeenCalledWith(['file_partial.txt', 'file_manual.txt']);
b69ab31281 });
b69ab31282
b69ab31283 it('All generated files, opens all files', async () => {
b69ab31284 const openSpy = jest.spyOn(platform, 'openFiles').mockImplementation(() => {});
b69ab31285 await simulateCommitWithFiles({
b69ab31286 'file_gen1.txt': GeneratedStatus.Generated,
b69ab31287 'file_gen2.txt': GeneratedStatus.Generated,
b69ab31288 });
b69ab31289
b69ab31290 fireEvent.click(screen.getByText('Open All Files'));
b69ab31291 expect(openSpy).toHaveBeenCalledTimes(1);
b69ab31292 expect(openSpy).toHaveBeenCalledWith(['file_gen1.txt', 'file_gen2.txt']);
b69ab31293 });
b69ab31294 });
b69ab31295});