소스 검색

[frontend] Add nonStick option for global alerts

This adds an option to have non-sticky warning and error alerts.
Johan Åhlén 1 년 전
부모
커밋
7d4f3886dd

+ 42 - 14
desktop/core/src/desktop/js/reactComponents/AlertComponent/AlertComponent.test.tsx

@@ -23,7 +23,7 @@ import huePubSub from '../../utils/huePubSub';
 import AlertComponent from './AlertComponent';
 
 describe('AlertComponent', () => {
-  test('it should show a global error message', async () => {
+  it('should show a global error message', async () => {
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
 
@@ -34,7 +34,7 @@ describe('AlertComponent', () => {
     expect(alerts[0]).toHaveTextContent('Some error');
   });
 
-  test('it should show multiple global error messages', async () => {
+  it('should show multiple global error messages', async () => {
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
 
@@ -47,7 +47,7 @@ describe('AlertComponent', () => {
     expect(alerts[1]).toHaveTextContent('Error 2');
   });
 
-  test("it shouldn't show empty error messages", async () => {
+  it("shouldn't show empty error messages", async () => {
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
 
@@ -56,7 +56,7 @@ describe('AlertComponent', () => {
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
   });
 
-  test('it should show unique error messages', async () => {
+  it('should show unique error messages', async () => {
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
 
@@ -70,7 +70,7 @@ describe('AlertComponent', () => {
     expect(alerts[1]).toHaveTextContent('Error 2');
   });
 
-  test('alerts should be closable', async () => {
+  it('should close alerts when clicked', async () => {
     const user = userEvent.setup();
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
@@ -90,24 +90,52 @@ describe('AlertComponent', () => {
     expect(alertsAfterClosing[1]).toHaveTextContent('Error 3');
   });
 
-  test('info alerts should close automatically after 3 seconds', async () => {
+  const expectAlertToBeGoneAfterThreeSeconds = () => {
+    // It should still be open after 2 seconds
+    act(() => {
+      jest.advanceTimersByTime(2000);
+    });
+    expect(screen.queryAllByRole('alert')).toHaveLength(1);
+
+    // After 3.1 seconds, it should really be closed
+    act(() => {
+      jest.advanceTimersByTime(1100);
+    });
+    expect(screen.queryAllByRole('alert')).toHaveLength(0);
+  };
+
+  it('should close info alerts automatically after 3 seconds', async () => {
     jest.useFakeTimers();
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
     act(() => huePubSub.publish('hue.global.info', { message: 'info' }));
     expect(screen.queryAllByRole('alert')).toHaveLength(1);
 
-    //It should still be open after 2 seconds
-    act(() => {
-      jest.advanceTimersByTime(2000);
-    });
+    expectAlertToBeGoneAfterThreeSeconds();
+
+    jest.useRealTimers();
+  });
+
+  it('should close warning alerts when when noStick is set to true', async () => {
+    jest.useFakeTimers();
+    render(<AlertComponent />);
+    expect(screen.queryAllByRole('alert')).toHaveLength(0);
+    act(() => huePubSub.publish('hue.global.warning', { message: 'Some warning', noStick: true }));
     expect(screen.queryAllByRole('alert')).toHaveLength(1);
 
-    //After 3.1 seconds, it should really be closed
-    act(() => {
-      jest.advanceTimersByTime(1000);
-    });
+    expectAlertToBeGoneAfterThreeSeconds();
+
+    jest.useRealTimers();
+  });
+
+  it('should close error alerts when when noStick is set to true', async () => {
+    jest.useFakeTimers();
+    render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
+    act(() => huePubSub.publish('hue.global.error', { message: 'Some error', noStick: true }));
+    expect(screen.queryAllByRole('alert')).toHaveLength(1);
+
+    expectAlertToBeGoneAfterThreeSeconds();
 
     jest.useRealTimers();
   });

+ 22 - 27
desktop/core/src/desktop/js/reactComponents/AlertComponent/AlertComponent.tsx

@@ -14,26 +14,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
+import { AlertProps } from 'antd/lib/alert';
 import Alert from 'cuix/dist/components/Alert/Alert';
 
-import huePubSub from 'utils/huePubSub';
 import './AlertComponent.scss';
 import { i18nReact } from '../../utils/i18nReact';
+import { useHuePubSub } from '../useHuePubSub';
 
 interface HueAlert {
   message: string;
+  noStick: boolean;
 }
 
-type alertType = 'error' | 'info' | 'warning';
+type alertType = AlertProps['type'];
+
 interface VisibleAlert {
   alert: HueAlert;
   type: alertType;
 }
-
 const AlertComponent: React.FC = () => {
   const [alert, setAlerts] = useState<VisibleAlert[]>([]);
-
   const updateAlerts = (alert: HueAlert, type: alertType) => {
     if (!alert.message) {
       return;
@@ -47,7 +48,7 @@ const AlertComponent: React.FC = () => {
 
       const newAlert: VisibleAlert = { alert, type };
 
-      if (type === 'info') {
+      if (type === 'info' || alert.noStick) {
         setTimeout(() => {
           handleClose(newAlert);
         }, 3000);
@@ -57,32 +58,26 @@ const AlertComponent: React.FC = () => {
     });
   };
 
-  useEffect(() => {
-    const hueSub = huePubSub.subscribe('hue.global.error', (newAlert: HueAlert) => {
+  useHuePubSub<HueAlert>({
+    topic: 'hue.global.error',
+    callback: newAlert => {
       updateAlerts(newAlert, 'error');
-    });
-    return () => {
-      hueSub.remove();
-    };
-  }, []);
+    }
+  });
 
-  useEffect(() => {
-    const hueSub = huePubSub.subscribe('hue.global.info', (newAlert: HueAlert) => {
+  useHuePubSub<HueAlert>({
+    topic: 'hue.global.info',
+    callback: newAlert => {
       updateAlerts(newAlert, 'info');
-    });
-    return () => {
-      hueSub.remove();
-    };
-  }, []);
+    }
+  });
 
-  useEffect(() => {
-    const hueSub = huePubSub.subscribe('hue.global.warning', (newAlert: HueAlert) => {
+  useHuePubSub<HueAlert>({
+    topic: 'hue.global.warning',
+    callback: newAlert => {
       updateAlerts(newAlert, 'warning');
-    });
-    return () => {
-      hueSub.remove();
-    };
-  }, []);
+    }
+  });
 
   const handleClose = (alertObjToClose: VisibleAlert) => {
     const filteredAlerts = alert.filter(alertObj => alertObj !== alertObjToClose);

+ 1 - 1
desktop/core/src/desktop/js/reactComponents/useHuePubSub.ts

@@ -28,7 +28,7 @@ export function useHuePubSub<T>({
   app
 }: {
   topic: string;
-  callback?: (info?: T) => void;
+  callback?: (info: T) => void;
   app?: string;
 }): T | undefined {
   const [huePubSubState, setHuePubSubState] = useState<{ info: T | undefined }>({