Skip to content

Commit

Permalink
chore(llm): introduce device actions hooks, PO model, loadBleState an…
Browse files Browse the repository at this point in the history
…d loadAccounts helpers in tests (#4169)

* chore(llm): improve typing of e2e bridge

* chore: introduce device action hooks + tests basic cases
  • Loading branch information
gre authored Aug 10, 2023
1 parent b1329e2 commit 75ef6eb
Show file tree
Hide file tree
Showing 64 changed files with 950 additions and 129 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-baboons-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/live-common": patch
---

Export Event type from 'app' device actions
5 changes: 5 additions & 0 deletions .changeset/late-tips-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": patch
---

Introduces use\*DeviceAction() hooks in order to mock test all device actions. It impacts all screens that have device actions.
5 changes: 5 additions & 0 deletions .changeset/tidy-rabbits-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/native-ui": patch
---

Introduce testID prop on Form/SelectableList
101 changes: 90 additions & 11 deletions apps/ledger-live-mobile/e2e/bridge/client.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,96 @@
import { Platform } from "react-native";
import { DescriptorEventType } from "@ledgerhq/hw-transport";
import invariant from "invariant";
import { Subject } from "rxjs";
import { Subject, Observable } from "rxjs";
import { AccountRaw } from "@ledgerhq/types-live";
import { ConnectAppEvent } from "@ledgerhq/live-common/hw/connectApp";
import { Event as AppEvent } from "@ledgerhq/live-common/hw/actions/app";
import { ConnectManagerEvent } from "@ledgerhq/live-common/hw/connectManager";
import { store } from "../../src/context/LedgerStore";
import { importSettings } from "../../src/actions/settings";
import { setAccounts } from "../../src/actions/accounts";
import { acceptGeneralTermsLastVersion } from "../../src/logic/terms";
import accountModel from "../../src/logic/accountModel";
import { navigate } from "../../src/rootnavigation";
import { BleState, SettingsState } from "../../src/reducers/types";
import { importBle } from "../../src/actions/ble";
import { InstallLanguageEvent } from "@ledgerhq/live-common/hw/installLanguage";
import { LoadImageEvent } from "@ledgerhq/live-common/hw/staxLoadImage";
import { SwapRequestEvent } from "@ledgerhq/live-common/exchange/swap/types";
import { FetchImageEvent } from "@ledgerhq/live-common/hw/staxFetchImage";
import { ExchangeRequestEvent } from "@ledgerhq/live-common/hw/actions/startExchange";
import { CompleteExchangeRequestEvent } from "@ledgerhq/live-common/exchange/platform/types";
import { RemoveImageEvent } from "@ledgerhq/live-common/hw/staxRemoveImage";
import { RenameDeviceEvent } from "@ledgerhq/live-common/hw/renameDevice";

type ClientData =
export type MockDeviceEvent =
| ConnectAppEvent
| AppEvent
| ConnectManagerEvent
| InstallLanguageEvent
| LoadImageEvent
| FetchImageEvent
| ExchangeRequestEvent
| SwapRequestEvent
| RemoveImageEvent
| RenameDeviceEvent
| CompleteExchangeRequestEvent
| { type: "complete" };

const mockDeviceEventSubject = new Subject<MockDeviceEvent>();

// these adaptor will filter the event type to satisfy typescript (workaround), it works because underlying exec usage will ignore unknown event type
export const connectAppExecMock = (): Observable<ConnectAppEvent> =>
mockDeviceEventSubject as Observable<ConnectAppEvent>;
export const initSwapExecMock = (): Observable<SwapRequestEvent> =>
mockDeviceEventSubject as Observable<SwapRequestEvent>;
export const startExchangeExecMock = (): Observable<ExchangeRequestEvent> =>
mockDeviceEventSubject as Observable<ExchangeRequestEvent>;
export const connectManagerExecMock = (): Observable<ConnectManagerEvent> =>
mockDeviceEventSubject as Observable<ConnectManagerEvent>;
export const staxFetchImageExecMock = (): Observable<FetchImageEvent> =>
mockDeviceEventSubject as Observable<FetchImageEvent>;
export const staxLoadImageExecMock = (): Observable<LoadImageEvent> =>
mockDeviceEventSubject as Observable<LoadImageEvent>;
export const staxRemoveImageExecMock = (): Observable<RemoveImageEvent> =>
mockDeviceEventSubject as Observable<RemoveImageEvent>;
export const installLanguageExecMock = (): Observable<InstallLanguageEvent> =>
mockDeviceEventSubject as Observable<InstallLanguageEvent>;
export const completeExchangeExecMock = (): Observable<CompleteExchangeRequestEvent> =>
mockDeviceEventSubject as Observable<CompleteExchangeRequestEvent>;
export const renameDeviceExecMock = (): Observable<RenameDeviceEvent> =>
mockDeviceEventSubject as Observable<RenameDeviceEvent>;

export type MessageData =
| {
type: DescriptorEventType;
payload: { id: string; name: string; serviceUUID: string };
}
| { type: "open" };
| { type: "open" }
| {
type: "mockDeviceEvent";
payload: MockDeviceEvent[];
}
| { type: "acceptTerms" }
| { type: "navigate"; payload: string }
| { type: "importSettings"; payload: Partial<SettingsState> }
| {
type: "importAccounts";
payload: {
data: AccountRaw;
version: number;
}[];
}
| {
type: "importBle";
payload: BleState;
}
| {
type: "setGlobals";
payload: { [key: string]: unknown };
};

export const e2eBridgeClient = new Subject<ClientData>();
export const e2eBridgeClient = new Subject<MessageData>();

let ws: WebSocket;

Expand All @@ -35,19 +109,16 @@ export function init(port = 8099) {
ws.onmessage = onMessage;
}

function onMessage(event: { data: unknown }) {
function onMessage(event: WebSocketMessageEvent) {
invariant(typeof event.data === "string", "[E2E Bridge Client]: Message data must be string");
const msg = JSON.parse(event.data);
const msg: MessageData = JSON.parse(event.data);
invariant(msg.type, "[E2E Bridge Client]: type is missing");

log(`Message\n${JSON.stringify(msg, null, 2)}`);
log(`Message type: ${msg.type}`);

e2eBridgeClient.next(msg);

switch (msg.type) {
case "add":
case "open":
e2eBridgeClient.next(msg);
break;
case "setGlobals":
Object.entries(msg.payload).forEach(([k, v]) => {
// @ts-expect-error global bullshit
Expand All @@ -61,10 +132,18 @@ function onMessage(event: { data: unknown }) {
store.dispatch(setAccounts(msg.payload.map(accountModel.decode)));
break;
}
case "mockDeviceEvent": {
msg.payload.forEach(e => mockDeviceEventSubject.next(e));
break;
}
case "importSettings": {
store.dispatch(importSettings(msg.payload));
break;
}
case "importBle": {
store.dispatch(importBle(msg.payload));
break;
}
case "navigate":
navigate(msg.payload, {});
break;
Expand Down
31 changes: 28 additions & 3 deletions apps/ledger-live-mobile/e2e/bridge/server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Server } from "ws";
import path from "path";
import fs from "fs";
import { toAccountRaw } from "@ledgerhq/live-common/account/index";
import { NavigatorName } from "../../src/const";
import { Subject } from "rxjs";
import { MessageData, MockDeviceEvent } from "./client";
import { BleState } from "../../src/reducers/types";
import { Account } from "@ledgerhq/types-live";

type ServerData = {
type: "walletAPIResponse";
Expand Down Expand Up @@ -47,13 +51,34 @@ export function loadConfig(fileName: string, agreed: true = true): void {
}
}

export function loadBleState(bleState: BleState) {
postMessage({ type: "importBle", payload: bleState });
}

export function loadAccounts(accounts: Account[]) {
postMessage({
type: "importAccounts",
payload: accounts.map(account => ({
version: 1,
data: toAccountRaw(account),
})),
});
}

function navigate(name: string) {
postMessage({
type: "navigate",
payload: name,
});
}

export function mockDeviceEvent(...args: MockDeviceEvent[]) {
postMessage({
type: "mockDeviceEvent",
payload: args,
});
}

export function addDevices(
deviceNames: string[] = ["Nano X de David", "Nano X de Arnaud", "Nano X de Didier Duchmol"],
): string[] {
Expand All @@ -74,7 +99,7 @@ export function setInstalledApps(apps: string[] = []) {
}

export function open() {
postMessage({ type: "open", payload: null });
postMessage({ type: "open" });
}

function onMessage(messageStr: string) {
Expand All @@ -96,10 +121,10 @@ function log(message: string) {
}

function acceptTerms() {
postMessage({ type: "acceptTerms", payload: null });
postMessage({ type: "acceptTerms" });
}

function postMessage(message: { type: string; payload: unknown }) {
function postMessage(message: MessageData) {
for (const ws of wss.clients.values()) {
ws.send(JSON.stringify(message));
}
Expand Down
2 changes: 1 addition & 1 deletion apps/ledger-live-mobile/e2e/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { by, device, element, waitFor } from "detox";
import { by, element, waitFor, device } from "detox";
import { Direction } from "react-native-modal";

const DEFAULT_TIMEOUT = 60000;
Expand Down
Loading

1 comment on commit 75ef6eb

@vercel
Copy link

@vercel vercel bot commented on 75ef6eb Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.