From 32a5cf8b64c4ba2abda9d495c7adf44143e9f404 Mon Sep 17 00:00:00 2001 From: jdabbech-ledger Date: Fri, 4 Oct 2024 13:42:45 +0200 Subject: [PATCH 1/3] :lipstick: (smpl): Select and list device in a drawer --- apps/sample/src/app/client-layout.tsx | 13 +-- apps/sample/src/components/Device/index.tsx | 51 ++++++++++-- .../MainView/ConnectDeviceActions.tsx | 6 +- .../MainView/DeviceSelectionDrawer.tsx | 47 +++++++++++ apps/sample/src/components/MainView/index.tsx | 40 ++++----- apps/sample/src/components/Sidebar/index.tsx | 52 ++++++------ apps/sample/src/components/StyledDrawer.tsx | 7 +- .../DeviceSelectionProvider/index.tsx | 81 +++++++++++++++++++ 8 files changed, 227 insertions(+), 70 deletions(-) create mode 100644 apps/sample/src/components/MainView/DeviceSelectionDrawer.tsx create mode 100644 apps/sample/src/providers/DeviceSelectionProvider/index.tsx diff --git a/apps/sample/src/app/client-layout.tsx b/apps/sample/src/app/client-layout.tsx index 46d5b4805..126912aad 100644 --- a/apps/sample/src/app/client-layout.tsx +++ b/apps/sample/src/app/client-layout.tsx @@ -19,6 +19,7 @@ import { SdkProvider } from "@/providers/DeviceSdkProvider"; import { DeviceSessionsProvider } from "@/providers/DeviceSessionsProvider"; import { SdkConfigProvider } from "@/providers/SdkConfig"; import { GlobalStyle } from "@/styles/globalstyles"; +import { DeviceSelectionProvider } from "@/providers/DeviceSelectionProvider"; const Root = styled(Flex)` flex-direction: row; @@ -44,11 +45,13 @@ const ClientRootLayout: React.FC = ({ children }) => { - - -
- {children} - + + + +
+ {children} + + diff --git a/apps/sample/src/components/Device/index.tsx b/apps/sample/src/components/Device/index.tsx index 53862ae12..50a39746c 100644 --- a/apps/sample/src/components/Device/index.tsx +++ b/apps/sample/src/components/Device/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback } from "react"; import { ConnectionType, DeviceModelId, @@ -9,6 +9,8 @@ import styled, { DefaultTheme } from "styled-components"; import { useDeviceSessionState } from "@/hooks/useDeviceSessionState"; import { useDeviceSessionsContext } from "@/providers/DeviceSessionsProvider"; +import { useSdk } from "@/providers/DeviceSdkProvider"; +import { useDeviceSelectionContext } from "@/providers/DeviceSelectionProvider"; import { StatusText } from "./StatusText"; @@ -17,7 +19,7 @@ const Root = styled(Flex).attrs({ p: 5, mb: 8, borderRadius: 2 })` theme.colors.neutral.c30}; align-items: center; border: ${({ active, theme }: { theme: DefaultTheme; active: boolean }) => - `1px solid ${active ? theme.colors.success.c40 : "transparent"}`}; + `1px solid ${active ? theme.colors.opacityDefault.c40 : "transparent"}`}; cursor: ${({ active }: { active: boolean }) => active ? "normal" : "pointer"}; `; @@ -45,8 +47,8 @@ type DeviceProps = { type: ConnectionType; sessionId: DeviceSessionId; model: DeviceModelId; - onDisconnect: () => Promise; - onSelect: () => void; + showActiveIndicator?: boolean; + showSelectDeviceAction?: boolean; }; function getIconComponent(model: DeviceModelId) { @@ -66,20 +68,42 @@ export const Device: React.FC = ({ name, type, model, - onDisconnect, - onSelect, sessionId, + showActiveIndicator, + showSelectDeviceAction, }) => { const sessionState = useDeviceSessionState(sessionId); const { state: { selectedId }, + dispatch, } = useDeviceSessionsContext(); + const { setVisibility: setDeviceSelectionVisibility } = + useDeviceSelectionContext(); + const sdk = useSdk(); + const onDisconnect = useCallback(async () => { + try { + await sdk.disconnect({ sessionId }); + dispatch({ type: "remove_session", payload: { sessionId } }); + } catch (e) { + console.error(e); + } + }, [dispatch, sdk, sessionId]); + const onSelect = useCallback(() => { + dispatch({ + type: "select_session", + payload: { sessionId }, + }); + }, [sessionId]); const IconComponent = getIconComponent(model); const isActive = selectedId === sessionId; + return ( - + - + @@ -104,6 +128,17 @@ export const Device: React.FC = ({
+ {showSelectDeviceAction && ( + setDeviceSelectionVisibility(true)} + > + + Change device + + + + )} Disconnect diff --git a/apps/sample/src/components/MainView/ConnectDeviceActions.tsx b/apps/sample/src/components/MainView/ConnectDeviceActions.tsx index 948e1f60e..9ef72cf17 100644 --- a/apps/sample/src/components/MainView/ConnectDeviceActions.tsx +++ b/apps/sample/src/components/MainView/ConnectDeviceActions.tsx @@ -67,7 +67,7 @@ export const ConnectDeviceActions = ({ size="large" data-testid="CTA_select-device" > - Select a device + With Mockserver ) : ( @@ -77,7 +77,7 @@ export const ConnectDeviceActions = ({ backgroundColor="main" size="large" > - Select a USB device + With USB onSelectDeviceClicked(BuiltinTransports.BLE)} @@ -85,7 +85,7 @@ export const ConnectDeviceActions = ({ backgroundColor="main" size="large" > - Select a BLE device + With Bluetooth ); diff --git a/apps/sample/src/components/MainView/DeviceSelectionDrawer.tsx b/apps/sample/src/components/MainView/DeviceSelectionDrawer.tsx new file mode 100644 index 000000000..2983f5e3a --- /dev/null +++ b/apps/sample/src/components/MainView/DeviceSelectionDrawer.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { SdkError } from "@ledgerhq/device-management-kit"; +import { Text } from "@ledgerhq/react-ui"; +import styled from "styled-components"; + +import { Device } from "@/components/Device"; +import { ConnectDeviceActions } from "@/components/MainView/ConnectDeviceActions"; +import { StyledDrawer } from "@/components/StyledDrawer"; +import { useDeviceSessionsContext } from "@/providers/DeviceSessionsProvider"; + +const AvailableDevicesText = styled(Text).attrs({ + mt: 5, + variant: "body", + fontWeight: "regular", + color: "opacityDefault.c60", +})``; + +export const DeviceSelectionDrawer: React.FC<{ + isOpen: boolean; + onClose: () => void; + onError: (error: SdkError | null) => void; +}> = ({ isOpen, onClose, onError }) => { + const { + state: { deviceById }, + } = useDeviceSessionsContext(); + return ( + + + Connect another device + + + Available devices +
+ {Object.entries(deviceById).map(([sessionId, device]) => ( + + ))} +
+
+ ); +}; diff --git a/apps/sample/src/components/MainView/index.tsx b/apps/sample/src/components/MainView/index.tsx index bce6cd792..e29853b6a 100644 --- a/apps/sample/src/components/MainView/index.tsx +++ b/apps/sample/src/components/MainView/index.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState } from "react"; import { SdkError } from "@ledgerhq/device-management-kit"; -import { Badge, Flex, Icon, Notification, Text } from "@ledgerhq/react-ui"; +import { Flex, Text, Button } from "@ledgerhq/react-ui"; import Image from "next/image"; import styled, { DefaultTheme } from "styled-components"; -import { ConnectDeviceActions } from "./ConnectDeviceActions"; +import { useDeviceSelectionContext } from "@/providers/DeviceSelectionProvider"; const Root = styled(Flex)` flex: 1; @@ -12,11 +12,6 @@ const Root = styled(Flex)` align-items: center; flex-direction: column; `; -const ErrorNotification = styled(Notification)` - position: absolute; - bottom: 10px; - width: 70%; -`; const Description = styled(Text).attrs({ my: 6 })` color: ${({ theme }: { theme: DefaultTheme }) => theme.colors.neutral.c70}; @@ -28,6 +23,8 @@ const NanoLogo = styled(Image).attrs({ mb: 8 })` export const MainView: React.FC = () => { const [connectionError, setConnectionError] = useState(null); + const { setVisibility: setDeviceSelectionVisibility } = + useDeviceSelectionContext(); useEffect(() => { let timeoutId: NodeJS.Timeout; @@ -57,25 +54,16 @@ export const MainView: React.FC = () => { Use this application to test Ledger hardware device features. - - - {connectionError && ( - } - /> - } - hasBackground - title="Error" - description={ - connectionError.message || - (connectionError.originalError as Error | undefined)?.message - } - /> - )} + ); }; diff --git a/apps/sample/src/components/Sidebar/index.tsx b/apps/sample/src/components/Sidebar/index.tsx index 041359d68..095df9545 100644 --- a/apps/sample/src/components/Sidebar/index.tsx +++ b/apps/sample/src/components/Sidebar/index.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { BuiltinTransports } from "@ledgerhq/device-management-kit"; import { Box, Flex, IconsLegacy, Link, Text } from "@ledgerhq/react-ui"; import { useRouter } from "next/navigation"; @@ -26,6 +26,14 @@ const Root = styled(Flex).attrs({ py: 8, px: 6 })` : theme.colors.background.drawer}; `; +const NoDeviceContainer = styled(Flex).attrs({ + backgroundColor: "opacityDefault.c10", + mb: 8, + borderRadius: 2, +})` + height: 66px; +`; + const Subtitle = styled(Text).attrs({ mb: 5 })``; const MenuContainer = styled(Box)` @@ -49,7 +57,6 @@ export const Sidebar: React.FC = () => { const exportLogs = useExportLogsCallback(); const { state: { deviceById, selectedId }, - dispatch, } = useDeviceSessionsContext(); const { state: { transport }, @@ -64,21 +71,13 @@ export const Sidebar: React.FC = () => { setVersion(""); }); }, [sdk]); - const onDeviceDisconnect = useCallback( - async (sessionId: string) => { - try { - await sdk.disconnect({ sessionId }); - dispatch({ type: "remove_session", payload: { sessionId } }); - } catch (e) { - console.error(e); - } - }, - [dispatch, sdk], - ); const router = useRouter(); return ( - + router.push("/")} mb={8} @@ -95,20 +94,21 @@ export const Sidebar: React.FC = () => { Device -
- {Object.entries(deviceById).map(([sessionId, device]) => ( +
+ {selectedId ? ( - dispatch({ type: "select_session", payload: { sessionId } }) - } - onDisconnect={() => onDeviceDisconnect(sessionId)} + key={selectedId} + sessionId={selectedId} + name={deviceById[selectedId].name} + model={deviceById[selectedId].modelId} + type={deviceById[selectedId].type} + showSelectDeviceAction /> - ))} + ) : ( + + No device connected + + )}
Menu diff --git a/apps/sample/src/components/StyledDrawer.tsx b/apps/sample/src/components/StyledDrawer.tsx index 6acab3bc0..cf5f0d18b 100644 --- a/apps/sample/src/components/StyledDrawer.tsx +++ b/apps/sample/src/components/StyledDrawer.tsx @@ -12,16 +12,19 @@ const DescriptionText = styled(Text).attrs({ export const StyledDrawer: React.FC<{ title: string; - description: string; big: boolean; isOpen: boolean; onClose(): void; children: React.ReactNode; + description?: string; }> = ({ title, description, big, isOpen, onClose, children }) => { return ( - {description} + {description && ( + {description} + + )} {children} diff --git a/apps/sample/src/providers/DeviceSelectionProvider/index.tsx b/apps/sample/src/providers/DeviceSelectionProvider/index.tsx new file mode 100644 index 000000000..cb203d6f0 --- /dev/null +++ b/apps/sample/src/providers/DeviceSelectionProvider/index.tsx @@ -0,0 +1,81 @@ +import React, { createContext, useContext, useState } from "react"; +import { SdkError } from "@ledgerhq/device-management-kit"; +import { DeviceSelectionDrawer } from "@/components/MainView/DeviceSelectionDrawer"; +import { Button, Flex, Icons, Text } from "@ledgerhq/react-ui"; +import { Sidebar } from "@/components/Sidebar"; + +type DeviceSelectionContextType = { + setError: (error: SdkError | null) => void; + setVisibility: (visible: boolean) => void; +}; + +const DeviceSelectionContext = createContext({ + setError: () => null, + setVisibility: () => null, +}); + +export const DeviceSelectionProvider: React.FC = ({ + children, +}) => { + const [visible, setVisibility] = useState(false); + const [error, setError] = useState(null); + + return ( + + {error ? ( + <> + + + + + Connection error + + + {error.message || + (error.originalError as Error | undefined)?.message || + "Unknown error"} + + + + + ) : ( + children + )} + setVisibility(false)} + onError={(error) => { + if (error) { + setVisibility(false); + } + setError(error); + }} + /> + + ); +}; + +export const useDeviceSelectionContext = () => + useContext(DeviceSelectionContext); From 0f00af19d8ae2665e029fb243e98b4a09d4f2482 Mon Sep 17 00:00:00 2001 From: jdabbech-ledger Date: Fri, 4 Oct 2024 13:44:52 +0200 Subject: [PATCH 2/3] :white_check_mark: (smpl): Update e2e tests with drawer device selection --- .../cases/device-action_list-apps.spec.ts | 2 +- .../cases/device-action_open-app.spec.ts | 2 +- .../device-command_close-bitcoin-app.spec.ts | 2 +- ...device-command_get-app-and-version.spec.ts | 2 +- .../device-command_open-bitcoin-app.spec.ts | 2 +- .../cases/device-connection.spec.ts | 16 +++++++++----- .../cases/device-disconnection.spec.ts | 22 ++++++++++++++----- .../cases/eth_get-address_happy-paths.spec.ts | 6 ++--- .../eth_get-address_unhappy-paths.spec.ts | 2 +- .../eth_sign-message_happy-paths.spec.ts | 6 ++--- .../eth_sign-message_unhappy-paths.spec.ts | 2 +- .../eth_sign-transaction_happy-paths.spec.ts | 4 ++-- ...eth_sign-transaction_unhappy-paths.spec.ts | 2 +- ...eth_sign-typed-message-happy-paths.spec.ts | 6 ++--- ...h_sign-typed-message-unhappy-paths.spec.ts | 2 +- apps/sample/playwright/utils/thenHandlers.ts | 18 ++++++++++----- apps/sample/playwright/utils/whenHandlers.ts | 22 +++++++++++++++++-- 17 files changed, 79 insertions(+), 39 deletions(-) diff --git a/apps/sample/playwright/cases/device-action_list-apps.spec.ts b/apps/sample/playwright/cases/device-action_list-apps.spec.ts index eef568ac8..2229075fe 100644 --- a/apps/sample/playwright/cases/device-action_list-apps.spec.ts +++ b/apps/sample/playwright/cases/device-action_list-apps.spec.ts @@ -25,7 +25,7 @@ test.describe("device action: list apps", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Then execute list apps via device action", async () => { diff --git a/apps/sample/playwright/cases/device-action_open-app.spec.ts b/apps/sample/playwright/cases/device-action_open-app.spec.ts index fec1e9557..3c8823ba7 100644 --- a/apps/sample/playwright/cases/device-action_open-app.spec.ts +++ b/apps/sample/playwright/cases/device-action_open-app.spec.ts @@ -24,7 +24,7 @@ test.describe("device action: open bitcoin app", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Then execute open app via device action", async () => { diff --git a/apps/sample/playwright/cases/device-command_close-bitcoin-app.spec.ts b/apps/sample/playwright/cases/device-command_close-bitcoin-app.spec.ts index f29950172..42c048067 100644 --- a/apps/sample/playwright/cases/device-command_close-bitcoin-app.spec.ts +++ b/apps/sample/playwright/cases/device-command_close-bitcoin-app.spec.ts @@ -27,7 +27,7 @@ test.describe("device command: close bitcoin app", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Then execute open app via device command", async () => { diff --git a/apps/sample/playwright/cases/device-command_get-app-and-version.spec.ts b/apps/sample/playwright/cases/device-command_get-app-and-version.spec.ts index a7abe7dfd..461e7e788 100644 --- a/apps/sample/playwright/cases/device-command_get-app-and-version.spec.ts +++ b/apps/sample/playwright/cases/device-command_get-app-and-version.spec.ts @@ -38,7 +38,7 @@ test.describe("device command: get app and version", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Then execute open app via device command", async () => { diff --git a/apps/sample/playwright/cases/device-command_open-bitcoin-app.spec.ts b/apps/sample/playwright/cases/device-command_open-bitcoin-app.spec.ts index ba8bce09f..f1491696f 100644 --- a/apps/sample/playwright/cases/device-command_open-bitcoin-app.spec.ts +++ b/apps/sample/playwright/cases/device-command_open-bitcoin-app.spec.ts @@ -26,7 +26,7 @@ test.describe("device command: open bitcoin app", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Then execute open app via device command", async () => { diff --git a/apps/sample/playwright/cases/device-connection.spec.ts b/apps/sample/playwright/cases/device-connection.spec.ts index c9c2341ee..26d0d1cbe 100644 --- a/apps/sample/playwright/cases/device-connection.spec.ts +++ b/apps/sample/playwright/cases/device-connection.spec.ts @@ -1,8 +1,11 @@ /* eslint-disable no-restricted-imports */ import { test } from "@playwright/test"; -import { thenDeviceIsConnected } from "../utils/thenHandlers"; -import { whenConnectingDevice } from "../utils/whenHandlers"; +import { + thenDeviceIsConnected, + thenDeviceIsListedAndConnected, +} from "../utils/thenHandlers"; +import { whenCloseDrawer, whenConnectingDevice } from "../utils/whenHandlers"; test.describe("device connection", () => { test.beforeEach(async ({ page }) => { @@ -13,7 +16,7 @@ test.describe("device connection", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); }); @@ -21,13 +24,14 @@ test.describe("device connection", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Given second device is connected", async () => { - await whenConnectingDevice(page); + await whenConnectingDevice(page, false); - await thenDeviceIsConnected(page, 1); + await thenDeviceIsListedAndConnected(page, 1); + await whenCloseDrawer(page); }); }); }); diff --git a/apps/sample/playwright/cases/device-disconnection.spec.ts b/apps/sample/playwright/cases/device-disconnection.spec.ts index c4b9fbff7..bf322dec6 100644 --- a/apps/sample/playwright/cases/device-disconnection.spec.ts +++ b/apps/sample/playwright/cases/device-disconnection.spec.ts @@ -3,11 +3,15 @@ import { test } from "@playwright/test"; import { thenDeviceIsConnected, + thenDeviceIsListedAndConnected, thenNoDeviceIsConnected, } from "../utils/thenHandlers"; import { + whenCloseDrawer, whenConnectingDevice, whenDisconnectDevice, + whenDisconnectListedDevice, + whenOpenSelectDeviceDrawer, } from "../utils/whenHandlers"; test.describe("device disconnection", () => { @@ -19,7 +23,7 @@ test.describe("device disconnection", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Then disconnect device", async () => { @@ -33,19 +37,25 @@ test.describe("device disconnection", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Given second device is connected", async () => { - await whenConnectingDevice(page); + await whenConnectingDevice(page, false); + + await thenDeviceIsListedAndConnected(page, 1); - await thenDeviceIsConnected(page, 1); + await whenCloseDrawer(page); }); await test.step("Then disconnect device", async () => { - await whenDisconnectDevice(page); + await whenOpenSelectDeviceDrawer(page); - await whenDisconnectDevice(page); + await whenDisconnectListedDevice(page); + + await whenDisconnectListedDevice(page); + + await whenCloseDrawer(page); await thenNoDeviceIsConnected(page); }); diff --git a/apps/sample/playwright/cases/eth_get-address_happy-paths.spec.ts b/apps/sample/playwright/cases/eth_get-address_happy-paths.spec.ts index 837944c39..60243ef93 100644 --- a/apps/sample/playwright/cases/eth_get-address_happy-paths.spec.ts +++ b/apps/sample/playwright/cases/eth_get-address_happy-paths.spec.ts @@ -36,7 +36,7 @@ test.describe("ETH Signer: get address, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: get address", async () => { @@ -71,7 +71,7 @@ test.describe("ETH Signer: get address, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: get address with checkOnDevice on", async () => { @@ -121,7 +121,7 @@ test.describe("ETH Signer: get address, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Then execute ETH: get address", async () => { diff --git a/apps/sample/playwright/cases/eth_get-address_unhappy-paths.spec.ts b/apps/sample/playwright/cases/eth_get-address_unhappy-paths.spec.ts index 6176089fb..1126210fb 100644 --- a/apps/sample/playwright/cases/eth_get-address_unhappy-paths.spec.ts +++ b/apps/sample/playwright/cases/eth_get-address_unhappy-paths.spec.ts @@ -32,7 +32,7 @@ test.describe("ETH Signer: get address, unhappy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("Then execute ETH: get address with malformed derivation paths", async () => { diff --git a/apps/sample/playwright/cases/eth_sign-message_happy-paths.spec.ts b/apps/sample/playwright/cases/eth_sign-message_happy-paths.spec.ts index 7991f6e5a..9f2efd5f7 100644 --- a/apps/sample/playwright/cases/eth_sign-message_happy-paths.spec.ts +++ b/apps/sample/playwright/cases/eth_sign-message_happy-paths.spec.ts @@ -33,7 +33,7 @@ test.describe("ETH Signer: sign message, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign message", async () => { @@ -71,7 +71,7 @@ test.describe("ETH Signer: sign message, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign message", async () => { @@ -133,7 +133,7 @@ test.describe("ETH Signer: sign message, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign message", async () => { diff --git a/apps/sample/playwright/cases/eth_sign-message_unhappy-paths.spec.ts b/apps/sample/playwright/cases/eth_sign-message_unhappy-paths.spec.ts index d07afc052..5e8904cb1 100644 --- a/apps/sample/playwright/cases/eth_sign-message_unhappy-paths.spec.ts +++ b/apps/sample/playwright/cases/eth_sign-message_unhappy-paths.spec.ts @@ -33,7 +33,7 @@ test.describe("ETH Signer: sign message, unhappy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign message with malformed derivation paths", async () => { diff --git a/apps/sample/playwright/cases/eth_sign-transaction_happy-paths.spec.ts b/apps/sample/playwright/cases/eth_sign-transaction_happy-paths.spec.ts index 4769463f5..796e9d017 100644 --- a/apps/sample/playwright/cases/eth_sign-transaction_happy-paths.spec.ts +++ b/apps/sample/playwright/cases/eth_sign-transaction_happy-paths.spec.ts @@ -36,7 +36,7 @@ test.describe("ETH Signer: sign transaction, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign transaction", async () => { @@ -75,7 +75,7 @@ test.describe("ETH Signer: sign transaction, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign transaction", async () => { diff --git a/apps/sample/playwright/cases/eth_sign-transaction_unhappy-paths.spec.ts b/apps/sample/playwright/cases/eth_sign-transaction_unhappy-paths.spec.ts index 4ab5e6b47..126897a50 100644 --- a/apps/sample/playwright/cases/eth_sign-transaction_unhappy-paths.spec.ts +++ b/apps/sample/playwright/cases/eth_sign-transaction_unhappy-paths.spec.ts @@ -33,7 +33,7 @@ test.describe("ETH Signer: sign transaction, unhappy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign transaction with malformed derivation paths", async () => { diff --git a/apps/sample/playwright/cases/eth_sign-typed-message-happy-paths.spec.ts b/apps/sample/playwright/cases/eth_sign-typed-message-happy-paths.spec.ts index 624e210ae..09da5b9e6 100644 --- a/apps/sample/playwright/cases/eth_sign-typed-message-happy-paths.spec.ts +++ b/apps/sample/playwright/cases/eth_sign-typed-message-happy-paths.spec.ts @@ -33,7 +33,7 @@ test.describe("ETH Signer: sign EIP712 message, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign EIP712 message", async () => { @@ -72,7 +72,7 @@ test.describe("ETH Signer: sign EIP712 message, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign EIP712 message", async () => { @@ -135,7 +135,7 @@ test.describe("ETH Signer: sign EIP712 message, happy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign EIP712 message", async () => { diff --git a/apps/sample/playwright/cases/eth_sign-typed-message-unhappy-paths.spec.ts b/apps/sample/playwright/cases/eth_sign-typed-message-unhappy-paths.spec.ts index 4cf56a50d..74678e8fe 100644 --- a/apps/sample/playwright/cases/eth_sign-typed-message-unhappy-paths.spec.ts +++ b/apps/sample/playwright/cases/eth_sign-typed-message-unhappy-paths.spec.ts @@ -33,7 +33,7 @@ test.describe("ETH Signer: sign EIP712 message, unhappy paths", () => { await test.step("Given first device is connected", async () => { await whenConnectingDevice(page); - await thenDeviceIsConnected(page, 0); + await thenDeviceIsConnected(page); }); await test.step("When execute ETH: sign typed message with malformed derivation paths", async () => { diff --git a/apps/sample/playwright/utils/thenHandlers.ts b/apps/sample/playwright/utils/thenHandlers.ts index 5b253c2e6..5c678a570 100644 --- a/apps/sample/playwright/utils/thenHandlers.ts +++ b/apps/sample/playwright/utils/thenHandlers.ts @@ -2,8 +2,13 @@ import { expect, Locator, Page } from "@playwright/test"; import { asyncPipe } from "@/utils/pipes"; -const getDeviceLocator = - (deviceIndex: number = 0) => +const getDeviceLocator = (page: Page): Page => { + const targetChild = page.getByTestId("container_main-device").locator("> *"); + return targetChild as unknown as Page; +}; + +const getListedDeviceLocator = + (deviceIndex: number) => (page: Page): Page => { const targetChild = page .getByTestId("container_devices") @@ -24,11 +29,14 @@ const verifyDeviceConnectedStatus = async ( return locator; }; -export const thenDeviceIsConnected = ( +export const thenDeviceIsConnected = (page: Page): Promise => + asyncPipe(getDeviceLocator, verifyDeviceConnectedStatus)(page); + +export const thenDeviceIsListedAndConnected = ( page: Page, - deviceIndex: number = 0, + index: number, ): Promise => - asyncPipe(getDeviceLocator(deviceIndex), verifyDeviceConnectedStatus)(page); + asyncPipe(getListedDeviceLocator(index), verifyDeviceConnectedStatus)(page); const getAllDeviceNames = async (page: Page): Promise => { return page diff --git a/apps/sample/playwright/utils/whenHandlers.ts b/apps/sample/playwright/utils/whenHandlers.ts index 12bb20319..eaba69ba6 100644 --- a/apps/sample/playwright/utils/whenHandlers.ts +++ b/apps/sample/playwright/utils/whenHandlers.ts @@ -44,8 +44,20 @@ const fillInputFields = return page; }; -export const whenConnectingDevice = (page: Page): Promise => - clickByTestId("CTA_select-device")(page); +export const whenOpenSelectDeviceDrawer = async (page: Page): Promise => + await clickByTestId("CTA_open-select-device-drawer")(page); + +export const whenConnectingDevice = async ( + page: Page, + closeDrawer: boolean = true, +): Promise => { + await whenOpenSelectDeviceDrawer(page); + const newPage = await clickByTestId("CTA_select-device")(page); + if (closeDrawer) { + return whenCloseDrawer(page); + } + return newPage; +}; export const whenClicking = (page: Page, ctaSelector: string): Promise => clickByTestId(ctaSelector)(page); @@ -88,6 +100,9 @@ export const whenExecute = export const whenExecuteDeviceAction = whenExecute("device-action", true); export const whenExecuteDeviceCommand = whenExecute("device-command", true); +const getMainDevice = (page: Page): Locator => + page.getByTestId("container_main-device").locator("> *").first(); + const getFirstDevice = (page: Page): Locator => page.getByTestId("container_devices").locator("> *").first(); @@ -98,6 +113,9 @@ const clickDeviceOptionAndDisconnect = async (page: Page): Promise => { }; export const whenDisconnectDevice = (page: Page): Promise => + asyncPipe(getMainDevice, clickDeviceOptionAndDisconnect)(page); + +export const whenDisconnectListedDevice = (page: Page): Promise => asyncPipe(getFirstDevice, clickDeviceOptionAndDisconnect)(page); const drawerCloseButtonSelector = From ef58ea5b70101e39e7c9e5dc661912de464bce21 Mon Sep 17 00:00:00 2001 From: jdabbech-ledger Date: Fri, 4 Oct 2024 13:46:16 +0200 Subject: [PATCH 3/3] :bookmark: (smpl): Changeset --- .changeset/good-humans-serve.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/good-humans-serve.md diff --git a/.changeset/good-humans-serve.md b/.changeset/good-humans-serve.md new file mode 100644 index 000000000..b304caa5f --- /dev/null +++ b/.changeset/good-humans-serve.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/device-sdk-sample": patch +--- + +Select device in a drawer