From 9b94cb12772e54f6545229550bb7cd1fcaa49c9f Mon Sep 17 00:00:00 2001 From: mkurczewski Date: Mon, 7 Oct 2024 18:07:50 +0200 Subject: [PATCH] Added time synchronization card for Harmony's overview screen --- .../renderer/locales/default/en-US.json | 3 + ...=> harmony-overview-content.component.tsx} | 8 +- .../harmony-overview.component.tsx | 3 +- .../components/overview/overview.styles.tsx | 13 + .../time-synchronization-ids.enum.ts | 8 + .../time-synchronization.component.tsx | 84 ++++++ .../time-synchronization.test.tsx | 267 ++++++++++++++++++ 7 files changed, 382 insertions(+), 4 deletions(-) rename libs/core/overview/components/overview-screens/harmony-overview/{overview-content.component.tsx => harmony-overview-content.component.tsx} (91%) create mode 100644 libs/core/overview/components/time-synchronization/time-synchronization-ids.enum.ts create mode 100644 libs/core/overview/components/time-synchronization/time-synchronization.component.tsx create mode 100644 libs/core/overview/components/time-synchronization/time-synchronization.test.tsx diff --git a/libs/core/__deprecated__/renderer/locales/default/en-US.json b/libs/core/__deprecated__/renderer/locales/default/en-US.json index a75cfdc989..4eeebac2bc 100644 --- a/libs/core/__deprecated__/renderer/locales/default/en-US.json +++ b/libs/core/__deprecated__/renderer/locales/default/en-US.json @@ -717,6 +717,9 @@ "module.overview.systemUpdateUpToDate": "You’re up to date.", "module.overview.systemVersion": "MuditaOS version", "module.overview.systemVersionTitle": "Current version:", + "module.overview.timeSynchronizationTitle": "Time and date", + "module.overview.timeSynchronizationDescription": "You can synchronize Harmony to match your computer’s date and time.", + "module.overview.timeSynchronizationButton": "Synchronize", "module.overview.updateAvailableAboutOsVersionSubDescription": "the device will be upgraded to this version", "module.overview.updateAvailableAboutUpdatesTitle": "{num, plural, =1 {ABOUT UPDATE} other {ABOUT UPDATES}}", "module.overview.updateAvailableButton": "{num, plural, =1 {Download} other {Download all}}", diff --git a/libs/core/overview/components/overview-screens/harmony-overview/overview-content.component.tsx b/libs/core/overview/components/overview-screens/harmony-overview/harmony-overview-content.component.tsx similarity index 91% rename from libs/core/overview/components/overview-screens/harmony-overview/overview-content.component.tsx rename to libs/core/overview/components/overview-screens/harmony-overview/harmony-overview-content.component.tsx index 968d2eac3c..aeae94d43b 100644 --- a/libs/core/overview/components/overview-screens/harmony-overview/overview-content.component.tsx +++ b/libs/core/overview/components/overview-screens/harmony-overview/harmony-overview-content.component.tsx @@ -9,9 +9,10 @@ import { DeviceType } from "device-protocol/models" import { FunctionComponent } from "Core/core/types/function-component.interface" import { DeviceInfo, + OverviewHarmonyWrapper, StatusInfo, SystemInfo, - OverviewWrapper, + TimeSynchronizationInfo, } from "Core/overview/components/overview/overview.styles" interface OverviewProps { @@ -36,7 +37,7 @@ const OverviewContent: FunctionComponent = ({ caseColour, }) => { return ( - + = ({ onDownload={onUpdateDownload} onUpdate={onUpdateInstall} /> - + + ) } export default OverviewContent diff --git a/libs/core/overview/components/overview-screens/harmony-overview/harmony-overview.component.tsx b/libs/core/overview/components/overview-screens/harmony-overview/harmony-overview.component.tsx index 7ddb5c240d..985c6f8f50 100644 --- a/libs/core/overview/components/overview-screens/harmony-overview/harmony-overview.component.tsx +++ b/libs/core/overview/components/overview-screens/harmony-overview/harmony-overview.component.tsx @@ -6,7 +6,8 @@ import { DeviceType } from "device-protocol/models" import { Feature, flags } from "Core/feature-flags" import { HarmonyOverviewProps } from "Core/overview/components/overview-screens/harmony-overview/harmony-overview.component.interface" -import OverviewContent from "Core/overview/components/overview-screens/harmony-overview/overview-content.component" +import OverviewContent + from "Core/overview/components/overview-screens/harmony-overview/harmony-overview-content.component" import { UpdateOsFlow } from "Core/overview/components/update-os-flow" import UpdatingForceModalFlow from "Core/overview/components/updating-force-modal-flow/updating-force-modal-flow.component" import { CheckForUpdateMode } from "Core/update/constants" diff --git a/libs/core/overview/components/overview/overview.styles.tsx b/libs/core/overview/components/overview/overview.styles.tsx index 7c2710da81..c18572fa85 100644 --- a/libs/core/overview/components/overview/overview.styles.tsx +++ b/libs/core/overview/components/overview/overview.styles.tsx @@ -9,6 +9,7 @@ import Status from "Core/overview/components/status/status.component" import System from "Core/overview/components/system/system.component" import FilesManager from "Core/overview/components/files-manager/files-manager.component" import Backup from "Core/overview/components/backup/backup.component" +import TimeSynchronization from "../time-synchronization/time-synchronization.component" export const DeviceInfo = styled(DevicePreview)` grid-area: Device; @@ -25,6 +26,10 @@ export const BackupInfo = styled(Backup)` grid-area: Backup; ` +export const TimeSynchronizationInfo = styled(TimeSynchronization)` + grid-area: TimeSynchronization; +` + const overviewWrapperWithBackup = css` grid-template-rows: repeat(3, minmax(20.4rem, 1fr)); grid-template-areas: @@ -49,6 +54,14 @@ export const OverviewPureWrapper = styled(OverviewWrapper)` ${overviewWrapperWithBackup}; ` +export const OverviewHarmonyWrapper = styled(OverviewWrapper)` + grid-template-areas: + "Device Network" + "Device System" + "Device TimeSynchronization"; + grid-template-rows: repeat(3, 1fr); +` + export const FileManagerInfo = styled(FilesManager)` grid-area: FilesManager; display: none; /* TODO: Remove when feature becomes available */ diff --git a/libs/core/overview/components/time-synchronization/time-synchronization-ids.enum.ts b/libs/core/overview/components/time-synchronization/time-synchronization-ids.enum.ts new file mode 100644 index 0000000000..1b4754d7e0 --- /dev/null +++ b/libs/core/overview/components/time-synchronization/time-synchronization-ids.enum.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +export enum TimeSynchronizationIds { + SynchronizeButton = "synchronize-button", +} diff --git a/libs/core/overview/components/time-synchronization/time-synchronization.component.tsx b/libs/core/overview/components/time-synchronization/time-synchronization.component.tsx new file mode 100644 index 0000000000..4ac5699e45 --- /dev/null +++ b/libs/core/overview/components/time-synchronization/time-synchronization.component.tsx @@ -0,0 +1,84 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import Card, { + CardAction, + CardBody, + CardContent, + CardHeader, +} from "Core/overview/components/card.elements" +import Text, { + TextDisplayStyle, +} from "Core/__deprecated__/renderer/components/core/text/text.component" +import { FunctionComponent } from "Core/core/types/function-component.interface" +import React from "react" +import { defineMessages, FormattedMessage } from "react-intl" +import { DisplayStyle } from "Core/__deprecated__/renderer/components/core/button/button.config" +import ButtonComponent from "Core/__deprecated__/renderer/components/core/button/button.component" +import { TimeSynchronizationIds } from "Core/overview/components/time-synchronization/time-synchronization-ids.enum" + +const messages = defineMessages({ + timeSynchronizationTitle: { + id: "module.overview.timeSynchronizationTitle", + }, + timeSynchronizationDescription: { + id: "module.overview.timeSynchronizationDescription", + }, + timeSynchronizationButton: { + id: "module.overview.timeSynchronizationButton", + }, +}) + +interface Props { + // deviceType: DeviceType + // osVersion?: string + // onUpdateCheck?: () => void + // onUpdate?: () => void + // onDownload?: () => void +} + +const TimeSynchronization: FunctionComponent = ({ + // deviceType, + // osVersion = "", + // onUpdateCheck = noop, + // onUpdate = noop, + // onDownload = noop, + ...props +}) => { + // const { + // checkForUpdateInProgress, + // checkForUpdatePerformed, + // checkForUpdateFailed, + // updateAvailable, + // updateDownloaded, + // } = useUpdateFlowState({ + // deviceType: deviceType, + // }) + return ( + + + + + + + + + + {/*TODO: Show current Harmony time and date */} + + + + + + + ) +} + +export default TimeSynchronization diff --git a/libs/core/overview/components/time-synchronization/time-synchronization.test.tsx b/libs/core/overview/components/time-synchronization/time-synchronization.test.tsx new file mode 100644 index 0000000000..528581fe23 --- /dev/null +++ b/libs/core/overview/components/time-synchronization/time-synchronization.test.tsx @@ -0,0 +1,267 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import thunk from "redux-thunk" +import React, { ComponentProps } from "react" +import { fireEvent } from "@testing-library/dom" +import { renderWithThemeAndIntl } from "Core/__deprecated__/renderer/utils/render-with-theme-and-intl" +import { intl } from "Core/__deprecated__/renderer/utils/intl" +import System from "Core/overview/components/system/system.component" +import { SystemTestIds } from "Core/overview/components/system/system-test-ids.enum" +import { PureDeviceData } from "Core/device" +import { CaseColour } from "core-device/models" +import { DeviceType } from "device-protocol/models" +import { Provider } from "react-redux" +import { ReduxRootState } from "Core/__deprecated__/renderer/store" +import createMockStore from "redux-mock-store" +import { OsRelease } from "Core/update/dto" +import { initialState as update } from "Core/update/reducers" +import { initialState as device } from "Core/device/reducers/device.reducer" +import { + OsReleaseType, + Product, + ReleaseProcessState, + SilentCheckForUpdateState, +} from "Core/update/constants" + +type Props = ComponentProps + +const defaultProps: Props = { + deviceType: DeviceType.MuditaPure, + osVersion: "1.0.0", +} + +const pureDeviceMock: PureDeviceData = { + networkName: "Network", + networkLevel: "5", + osVersion: "0.75.1", + batteryLevel: 0.99, + simCards: [ + { + slot: 1, + active: true, + number: 12345678, + network: "", + networkLevel: 0.75, + }, + ], + serialNumber: "303", + phoneLockTime: 1630703219, + memorySpace: { + reservedSpace: 124, + usedUserSpace: 1021, + total: 16000000000, + }, + caseColour: CaseColour.Gray, + backupFilePath: "path/to/directory/fileBase.tar", + token: "", +} + +const mockedRelease: OsRelease = { + date: "2021-02-02", + file: { + name: "test file", + size: 123, + url: "some-url", + }, + product: Product.PurePhone, + type: OsReleaseType.Daily, + version: "1.1.0", + mandatoryVersions: [], +} + +const defaultState = { + update, + device: { + ...device, + data: pureDeviceMock, + }, +} as ReduxRootState + +const render = ( + extraState?: Partial, + extraProps?: Partial +) => { + const storeMock = createMockStore([thunk])({ + ...defaultState, + ...extraState, + }) + + const props = { + ...defaultProps, + ...extraProps, + } + + return renderWithThemeAndIntl( + + + + ) +} + +test("renders os version properly", () => { + const { getByTestId } = render() + expect(getByTestId(SystemTestIds.OsVersion)).toHaveTextContent( + "[value] module.overview.muditaOsUpdateTitle 1.0.0" + ) +}) + +test("renders available update info properly", () => { + const { getByText } = render({ + update: { + ...update, + silentCheckForUpdate: SilentCheckForUpdateState.Loaded, + data: { + ...update.data, + availableReleasesForUpdate: [mockedRelease], + }, + }, + }) + expect( + getByText( + intl.formatMessage({ id: "module.overview.systemUpdateAvailable" }) + ) + ).toBeInTheDocument() +}) + +test("renders You're up to date info properly", () => { + const { getByText } = render({ + update: { + ...update, + silentCheckForUpdate: SilentCheckForUpdateState.Loaded, + }, + }) + expect( + getByText( + intl.formatMessage({ id: "module.overview.systemUpdateUpToDate" }) + ) + ).toBeInTheDocument() +}) + +test("renders 'check for updates' button properly", () => { + const { queryByRole } = render() + expect(queryByRole("button")).toHaveTextContent( + intl.formatMessage({ id: "module.overview.systemCheckForUpdates" }) + ) +}) + +test("renders 'update now' button properly", () => { + const { queryByRole } = render({ + update: { + ...update, + silentCheckForUpdate: SilentCheckForUpdateState.Loaded, + data: { + ...update.data, + availableReleasesForUpdate: [mockedRelease], + }, + }, + }) + expect(queryByRole("button")).toHaveTextContent( + intl.formatMessage({ id: "module.overview.systemDownloadAction" }) + ) +}) + +test("does not render any label when check for update was not performed", () => { + const { queryByText } = render({}) + expect( + queryByText( + intl.formatMessage({ id: "module.overview.systemUpdateAvailable" }) + ) + ).not.toBeInTheDocument() + expect( + queryByText( + intl.formatMessage({ id: "module.overview.systemUpdateUpToDate" }) + ) + ).not.toBeInTheDocument() + expect( + queryByText( + intl.formatMessage({ id: "module.overview.systemUpdateDownloaded" }) + ) + ).not.toBeInTheDocument() +}) +test("does not render any label when check for update is in progress", () => { + const { queryByText } = render({}) + expect( + queryByText( + intl.formatMessage({ id: "module.overview.systemUpdateAvailable" }) + ) + ).not.toBeInTheDocument() + expect( + queryByText( + intl.formatMessage({ id: "module.overview.systemUpdateUpToDate" }) + ) + ).not.toBeInTheDocument() + expect( + queryByText( + intl.formatMessage({ id: "module.overview.systemUpdateDownloaded" }) + ) + ).not.toBeInTheDocument() +}) + +test("checks for update after button click", () => { + const onUpdateCheck = jest.fn() + const props = { onUpdateCheck } + + const { getByRole } = render( + { + update: { + ...update, + silentCheckForUpdate: SilentCheckForUpdateState.Loaded, + }, + }, + props + ) + + fireEvent.click(getByRole("button")) + + expect(onUpdateCheck).toHaveBeenCalled() +}) + +test("triggers download after button click", () => { + const onDownload = jest.fn() + const props = { onDownload } + const { getByRole } = render( + { + update: { + ...update, + silentCheckForUpdate: SilentCheckForUpdateState.Loaded, + data: { + ...update.data, + availableReleasesForUpdate: [mockedRelease], + }, + }, + }, + props + ) + + fireEvent.click(getByRole("button")) + + expect(onDownload).toHaveBeenCalled() +}) + +test("triggers update after button click", () => { + const onUpdate = jest.fn() + const props = { onUpdate } + + const { getByRole } = render( + { + update: { + ...update, + silentCheckForUpdate: SilentCheckForUpdateState.Loaded, + data: { + ...update.data, + downloadedProcessedReleases: [ + { release: mockedRelease, state: ReleaseProcessState.Done }, + ], + }, + }, + }, + props + ) + + fireEvent.click(getByRole("button")) + + expect(onUpdate).toHaveBeenCalled() +})