]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21692 Refactor ComponentContainer-test to make it clear what happened there
authorViktor Vorona <viktor.vorona@sonarsource.com>
Thu, 22 Feb 2024 14:06:50 +0000 (15:06 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 22 Feb 2024 20:02:53 +0000 (20:02 +0000)
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx

index 96d49c4056bf25f5bf2a874f824cfbf99bdf35a5..dc1f1781e7e19afe9773c44dcc7732c5a21977ae 100644 (file)
@@ -174,6 +174,14 @@ it('should show component not found if target branch is not found for fixing pul
 });
 
 describe('getTasksForComponent', () => {
+  beforeEach(() => {
+    jest.useFakeTimers();
+  });
+  afterEach(() => {
+    jest.runOnlyPendingTimers();
+    jest.useRealTimers();
+  });
+
   it('reload component after task progress finished', async () => {
     jest
       .mocked(getTasksForComponent)
@@ -186,43 +194,35 @@ describe('getTasksForComponent', () => {
 
     renderComponentContainer();
 
-    jest.useFakeTimers();
+    // getComponentNavigation is called imidiately after the component is mounted
+    expect(getComponentNavigation).toHaveBeenCalledTimes(1);
 
-    // First round, there's something in the queue, and component navigation was
-    // not called again (it's called once at mount, hence the 1 times assertion
-    // here).
-    await waitFor(() => {
-      expect(getComponentNavigation).toHaveBeenCalledTimes(1);
-    });
+    // we check that setTimeout is not yet set, because it requires getComponentNavigation to finish first (as a microtask)
+    expect(jest.getTimerCount()).toBe(0);
+
+    // First round, a pending task in the queue. This should trigger the setTimeout.
+    // We need waitFor because getComponentNavigation is not done yet and it is waiting to be run as a microtask
     await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(1));
 
+    // getTasksForComponent updated the tasks, which triggers the setTimeout
+    expect(jest.getTimerCount()).toBe(1);
+    // we run the timer to trigger the next getTasksForComponent call
     jest.runOnlyPendingTimers();
-    jest.useRealTimers();
 
-    // Second round, the queue is now empty, hence we assume the previous task
-    // was done. We immediately load the component again.
-    await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(2));
+    // Second round, the queue is now empty. This should trigger the component to reload.
+    expect(getTasksForComponent).toHaveBeenCalledTimes(2);
 
-    // Trigger the update.
-    // The component was correctly re-loaded.
+    // Since the set of tasks is changed the component fetching is triggered
+    // we need waitFor because getTasksForComponent is triggered, but not yet done, and it is waiting to be run as a microtask
     await waitFor(() => {
       expect(getComponentNavigation).toHaveBeenCalledTimes(2);
     });
-    // The status API call will be called 1 final time after the component is
-    // fully loaded, so the total will be 3.
-    expect(getTasksForComponent).toHaveBeenCalledTimes(3);
-
-    // Make sure the timeout was cleared. It should not be called again.
-    jest.useFakeTimers();
-    jest.runAllTimers();
 
-    // The number of calls haven't changed.
-    await waitFor(() => {
-      expect(getComponentNavigation).toHaveBeenCalledTimes(2);
-    });
+    // since waitFor waited for all microtasks and state changes getTasksForComponent was called as well because of component change.
     expect(getTasksForComponent).toHaveBeenCalledTimes(3);
 
-    jest.useRealTimers();
+    // Make sure the timeout was cleared. It should not be called again.
+    expect(jest.getTimerCount()).toBe(0);
   });
 
   it('reloads component after task progress finished, and moves straight to current', async () => {
@@ -242,31 +242,33 @@ describe('getTasksForComponent', () => {
 
     renderComponentContainer();
 
-    jest.useFakeTimers();
+    // getComponentNavigation is called imidiately after the component is mounted
+    expect(getComponentNavigation).toHaveBeenCalledTimes(1);
 
-    // First round, nothing in the queue, and component navigation was not called
-    // again (it's called once at mount, hence the 1 times assertion here).
-    await waitFor(() => {
-      expect(getComponentNavigation).toHaveBeenCalledTimes(1);
-    });
+    // First round, no tasks in queue. This should trigger the setTimeout.
+    // We need waitFor because getComponentNavigation is not done yet and it is waiting to be run as a microtask
     await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(1));
 
+    // Despite the fact taht we don't have any tasks in the queue, the component.analysisDate is undefined, so we trigger setTimeout
+    expect(jest.getTimerCount()).toBe(1);
     jest.runOnlyPendingTimers();
-    jest.useRealTimers();
 
     // Second round, nothing in the queue, BUT a success task is current. This
     // means the queue was processed too quick for us to see, and we didn't see
     // any pending tasks in the queue. So we immediately load the component again.
-    await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(2));
+    expect(getTasksForComponent).toHaveBeenCalledTimes(2);
 
-    // Trigger the update.
-    // The component was correctly re-loaded.
+    // Since the set of tasks is changed the component fetching is triggered
+    // we need waitFor because getTasksForComponent is triggered, but not yet done, and it is waiting to be run as a microtask
     await waitFor(() => {
       expect(getComponentNavigation).toHaveBeenCalledTimes(2);
     });
     // The status API call will be called 1 final time after the component is
     // fully loaded, so the total will be 3.
     expect(getTasksForComponent).toHaveBeenCalledTimes(3);
+
+    // Make sure the timeout was cleared. It should not be called again.
+    expect(jest.getTimerCount()).toBe(0);
   });
 
   it('only fully loads a non-empty component once', async () => {
@@ -280,11 +282,13 @@ describe('getTasksForComponent', () => {
     } as unknown as Awaited<ReturnType<typeof getTasksForComponent>>);
 
     renderComponentContainer();
-    await waitFor(() => {
-      expect(getComponentNavigation).toHaveBeenCalledTimes(1);
-    });
+
+    expect(getComponentNavigation).toHaveBeenCalledTimes(1);
 
     await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(1));
+
+    // Since the component has analysisDate, and the queue is empty, the setTimeout will not be triggered
+    expect(jest.getTimerCount()).toBe(0);
   });
 
   it('only fully reloads a non-empty component if there was previously some task in progress', async () => {
@@ -304,28 +308,30 @@ describe('getTasksForComponent', () => {
 
     renderComponentContainer();
 
-    jest.useFakeTimers();
-
     // First round, a pending task in the queue. This should trigger a reload of the
     // status endpoint.
     await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(1));
 
+    expect(jest.getTimerCount()).toBe(1);
     jest.runOnlyPendingTimers();
-    jest.useRealTimers();
 
     // Second round, nothing in the queue, and a success task is current. This
     // implies the current task was updated, and previously we displayed some information
     // about a pending task. This new information must prompt the component to reload
     // all data.
-    await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(2));
+    expect(getTasksForComponent).toHaveBeenCalledTimes(2);
 
     // The component was correctly re-loaded.
     await waitFor(() => {
       expect(getComponentNavigation).toHaveBeenCalledTimes(2);
     });
+
     // The status API call will be called 1 final time after the component is
     // fully loaded, so the total will be 3.
     expect(getTasksForComponent).toHaveBeenCalledTimes(3);
+
+    // Make sure the timeout was cleared. It should not be called again.
+    expect(jest.getTimerCount()).toBe(0);
   });
 });
 
@@ -473,11 +479,17 @@ describe('tutorials', () => {
     await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(1));
     expect(mockedReplace).not.toHaveBeenCalled();
 
+    // Since component.analysisDate is undefined we trigger setTimeout
+    expect(jest.getTimerCount()).toBe(1);
     jest.runOnlyPendingTimers();
+    expect(getTasksForComponent).toHaveBeenCalledTimes(2);
+
+    // Since we have condition (component.analysisDate is undefined) that will lead to endless loop,
+    // it is easier to enable realTimers to avoid act() warnings.
     jest.useRealTimers();
 
-    await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(2));
-    expect(mockedReplace).toHaveBeenCalledWith(getProjectUrl(componentKey));
+    // getTasksForComponent is called but not finished yet, so we need to wait for it to finish
+    await waitFor(() => expect(mockedReplace).toHaveBeenCalledWith(getProjectUrl(componentKey)));
   });
 
   it('should redirect to project branch dashboard from tutorials when receiving new related scan report', async () => {
@@ -505,22 +517,30 @@ describe('tutorials', () => {
       push: jest.fn(),
     });
 
+    jest.useFakeTimers();
+
     renderComponentContainer(
       { hasFeature: jest.fn().mockReturnValue(true) },
       `tutorials?id=${componentKey}`,
       '/',
     );
 
-    jest.useFakeTimers();
-
     await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(1));
     expect(mockedReplace).not.toHaveBeenCalled();
 
+    // Since component.analysisDate is undefined we trigger setTimeout
+    expect(jest.getTimerCount()).toBe(1);
     jest.runOnlyPendingTimers();
+    expect(getTasksForComponent).toHaveBeenCalledTimes(2);
+
+    // Since we have condition (component.analysisDate is undefined) that will lead to endless loop,
+    // it is easier to enable realTimers to avoid act() warnings.
     jest.useRealTimers();
 
-    await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(2));
-    expect(mockedReplace).toHaveBeenCalledWith(getProjectUrl(componentKey, branchName));
+    // getTasksForComponent is called but not finished yet, so we need to wait for it to finish
+    await waitFor(() =>
+      expect(mockedReplace).toHaveBeenCalledWith(getProjectUrl(componentKey, branchName)),
+    );
   });
 
   it('should redirect to project pull request dashboard from tutorials when receiving new related scan report', async () => {
@@ -550,22 +570,30 @@ describe('tutorials', () => {
       push: jest.fn(),
     });
 
+    jest.useFakeTimers();
+
     renderComponentContainer(
       { hasFeature: jest.fn().mockReturnValue(true) },
       `tutorials?id=${componentKey}`,
       '/',
     );
 
-    jest.useFakeTimers();
-
     await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(1));
     expect(mockedReplace).not.toHaveBeenCalled();
 
+    // Since component.analysisDate is undefined we trigger setTimeout
+    expect(jest.getTimerCount()).toBe(1);
     jest.runOnlyPendingTimers();
+    expect(getTasksForComponent).toHaveBeenCalledTimes(2);
+
+    // Since we have condition (component.analysisDate is undefined) that will lead to endless loop,
+    // it is easier to enable realTimers to avoid act() warnings.
     jest.useRealTimers();
 
-    await waitFor(() => expect(getTasksForComponent).toHaveBeenCalledTimes(2));
-    expect(mockedReplace).toHaveBeenCalledWith(getPullRequestUrl(componentKey, pullRequestKey));
+    // getTasksForComponent is called but not finished yet, so we need to wait for it to finish
+    await waitFor(() =>
+      expect(mockedReplace).toHaveBeenCalledWith(getPullRequestUrl(componentKey, pullRequestKey)),
+    );
   });
 });