@ledgerhq/device-mockserver-react 0.0.0-mockserver-react-20260929114136 → 0.0.0-mockserver-react-20260929124741
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/cjs/package.json +1 -1
- package/lib/cjs/src/DeviceOsInfo.test.js +2 -0
- package/lib/cjs/src/DeviceOsInfo.test.js.map +7 -0
- package/lib/cjs/src/DeviceScreen.test.js +2 -0
- package/lib/cjs/src/DeviceScreen.test.js.map +7 -0
- package/lib/cjs/src/DeviceScreenButtons.test.js +2 -0
- package/lib/cjs/src/DeviceScreenButtons.test.js.map +7 -0
- package/lib/cjs/src/DeviceScreenImage.test.js +2 -0
- package/lib/cjs/src/DeviceScreenImage.test.js.map +7 -0
- package/lib/cjs/src/MockServerDevice.js.map +1 -1
- package/lib/cjs/src/MockServerDevice.test.js +2 -0
- package/lib/cjs/src/MockServerDevice.test.js.map +7 -0
- package/lib/cjs/src/deviceModels.test.js +2 -0
- package/lib/cjs/src/deviceModels.test.js.map +7 -0
- package/lib/cjs/src/useMockServerDevice.js +1 -1
- package/lib/cjs/src/useMockServerDevice.js.map +3 -3
- package/lib/cjs/src/useMockServerDevice.test.js +2 -0
- package/lib/cjs/src/useMockServerDevice.test.js.map +7 -0
- package/lib/esm/package.json +1 -1
- package/lib/esm/src/DeviceOsInfo.test.js +2 -0
- package/lib/esm/src/DeviceOsInfo.test.js.map +7 -0
- package/lib/esm/src/DeviceScreen.test.js +2 -0
- package/lib/esm/src/DeviceScreen.test.js.map +7 -0
- package/lib/esm/src/DeviceScreenButtons.test.js +2 -0
- package/lib/esm/src/DeviceScreenButtons.test.js.map +7 -0
- package/lib/esm/src/DeviceScreenImage.test.js +2 -0
- package/lib/esm/src/DeviceScreenImage.test.js.map +7 -0
- package/lib/esm/src/MockServerDevice.js.map +1 -1
- package/lib/esm/src/MockServerDevice.test.js +2 -0
- package/lib/esm/src/MockServerDevice.test.js.map +7 -0
- package/lib/esm/src/deviceModels.test.js +2 -0
- package/lib/esm/src/deviceModels.test.js.map +7 -0
- package/lib/esm/src/useMockServerDevice.js +1 -1
- package/lib/esm/src/useMockServerDevice.js.map +3 -3
- package/lib/esm/src/useMockServerDevice.test.js +2 -0
- package/lib/esm/src/useMockServerDevice.test.js.map +7 -0
- package/lib/types/src/DeviceOsInfo.test.d.ts +2 -0
- package/lib/types/src/DeviceOsInfo.test.d.ts.map +1 -0
- package/lib/types/src/DeviceScreen.test.d.ts +2 -0
- package/lib/types/src/DeviceScreen.test.d.ts.map +1 -0
- package/lib/types/src/DeviceScreenButtons.test.d.ts +2 -0
- package/lib/types/src/DeviceScreenButtons.test.d.ts.map +1 -0
- package/lib/types/src/DeviceScreenImage.test.d.ts +2 -0
- package/lib/types/src/DeviceScreenImage.test.d.ts.map +1 -0
- package/lib/types/src/MockServerDevice.d.ts +2 -2
- package/lib/types/src/MockServerDevice.d.ts.map +1 -1
- package/lib/types/src/MockServerDevice.test.d.ts +2 -0
- package/lib/types/src/MockServerDevice.test.d.ts.map +1 -0
- package/lib/types/src/deviceModels.test.d.ts +2 -0
- package/lib/types/src/deviceModels.test.d.ts.map +1 -0
- package/lib/types/src/useMockServerDevice.d.ts +2 -1
- package/lib/types/src/useMockServerDevice.d.ts.map +1 -1
- package/lib/types/src/useMockServerDevice.test.d.ts +2 -0
- package/lib/types/src/useMockServerDevice.test.d.ts.map +1 -0
- package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ import { MockServerDevice } from "@ledgerhq/device-mockserver-react";
|
|
|
24
24
|
| Prop | Description |
|
|
25
25
|
| ------------------ | ------------------------------------------------------------------------------------ |
|
|
26
26
|
| `url` | Mock server URL. |
|
|
27
|
-
| `token` |
|
|
27
|
+
| `token` | Token of the session that owns the device. |
|
|
28
28
|
| `deviceId` | Mock server device id. |
|
|
29
29
|
| `floating` | A window over the page, dragged by its header and collapsed to it. Inline otherwise. |
|
|
30
30
|
| `defaultCollapsed` | Whether the floating window starts collapsed. |
|
package/lib/cjs/package.json
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var u=Object.create;var d=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var h=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var b=(e,o,t,i)=>{if(o&&typeof o=="object"||typeof o=="function")for(let p of B(o))!x.call(e,p)&&p!==t&&d(e,p,{get:()=>o[p],enumerable:!(i=v(o,p))||i.enumerable});return e};var w=(e,o,t)=>(t=e!=null?u(h(e)):{},b(o||!e||!e.__esModule?d(t,"default",{value:e,enumerable:!0}):t,e));var n=w(require("react")),m=require("react-dom/client"),a=require("./deviceModels"),c=require("./DeviceOsInfo");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const r=e=>({id:"device-1",device_type:"stax",apps:e}),l=[{name:"BOLOS",version:"1.10.1"},{name:"Bitcoin",version:"2.5.0"},{name:"Ethereum",version:"1.22.3"}];describe("DeviceOsInfo",()=>{let e,o;const t=s=>e.querySelector(`[data-testid="${s}"]`),i=s=>(0,n.act)(()=>o.render(s)),p=async s=>(0,n.act)(async()=>{t(s).click(),await Promise.resolve()});beforeEach(()=>{vi.useFakeTimers(),e=document.createElement("div"),document.body.append(e),o=(0,m.createRoot)(e)}),afterEach(()=>{(0,n.act)(()=>o.unmount()),e.remove(),vi.useRealTimers()}),it("lists the installed apps, without the OS",()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax")})),expect(t("button_open-app-Bitcoin")).not.toBeNull(),expect(t("button_open-app-Ethereum")).not.toBeNull(),expect(t("button_open-app-BOLOS")).toBeNull()}),it("says when no app is installed",()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r([l[0]]),model:(0,a.findDeviceScreenModel)("nanoX")})),expect(t("list_device-apps")?.textContent).toBe("No apps installed")}),it("cannot open apps without an emulator",()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("nanoS")})),expect(t("button_open-app-Bitcoin").disabled).toBe(!0)}),it("shows the app being opened",async()=>{const s=vi.fn(()=>new Promise(()=>{}));i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax"),onOpenApp:s})),await p("button_open-app-Bitcoin"),expect(s).toHaveBeenCalledWith("Bitcoin"),expect(t("spinner_opening-app-screen")?.textContent).toContain("Opening Bitcoin")}),it("shows why an app could not be opened",async()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax"),onOpenApp:()=>Promise.reject(new Error("Could not open (6d00)"))})),await p("button_open-app-Bitcoin"),expect(t("spinner_opening-app-screen")).toBeNull(),expect(e.textContent).toContain("Could not open (6d00)")}),it("gives up when an opened app never shows its screen",async()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax"),onOpenApp:()=>Promise.resolve()})),await p("button_open-app-Ethereum"),(0,n.act)(()=>{vi.advanceTimersByTime(1e4)}),expect(e.textContent).toContain("Ethereum opened but its screen never showed up")}),it("falls back to the app's initial when its icon is missing",()=>{i(n.default.createElement(c.DeviceOsInfo,{device:r(l),model:(0,a.findDeviceScreenModel)("stax")})),(0,n.act)(()=>{t("button_open-app-Bitcoin").querySelector("img").dispatchEvent(new Event("error"))}),expect(t("button_open-app-Bitcoin")?.querySelector("img")).toBeNull(),expect(t("button_open-app-Bitcoin")?.textContent).toBe("BBitcoin")})});
|
|
2
|
+
//# sourceMappingURL=DeviceOsInfo.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/DeviceOsInfo.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\n\nimport { findDeviceScreenModel } from \"./deviceModels\";\nimport { DeviceOsInfo } from \"./DeviceOsInfo\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst aDevice = (apps: { name: string; version: string }[]) =>\n ({ id: \"device-1\", device_type: \"stax\", apps }) as unknown as Device;\n\nconst APPS = [\n { name: \"BOLOS\", version: \"1.10.1\" },\n { name: \"Bitcoin\", version: \"2.5.0\" },\n { name: \"Ethereum\", version: \"1.22.3\" },\n];\n\ndescribe(\"DeviceOsInfo\", () => {\n let container: HTMLDivElement;\n let root: Root;\n\n const query = (testId: string) =>\n container.querySelector<HTMLElement>(`[data-testid=\"${testId}\"]`);\n const render = (\n element: React.ReactElement<React.ComponentProps<typeof DeviceOsInfo>>,\n ) => act(() => root.render(element));\n const click = async (testId: string) =>\n act(async () => {\n query(testId)!.click();\n await Promise.resolve();\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n it(\"lists the installed apps, without the OS\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n />,\n );\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n expect(query(\"button_open-app-Ethereum\")).not.toBeNull();\n expect(query(\"button_open-app-BOLOS\")).toBeNull();\n });\n\n it(\"says when no app is installed\", () => {\n render(\n <DeviceOsInfo\n device={aDevice([APPS[0]!])}\n model={findDeviceScreenModel(\"nanoX\")}\n />,\n );\n\n expect(query(\"list_device-apps\")?.textContent).toBe(\"No apps installed\");\n });\n\n it(\"cannot open apps without an emulator\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"nanoS\")}\n />,\n );\n\n expect(\n (query(\"button_open-app-Bitcoin\") as HTMLButtonElement).disabled,\n ).toBe(true);\n });\n\n it(\"shows the app being opened\", async () => {\n const onOpenApp = vi.fn(() => new Promise<void>(() => {}));\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={onOpenApp}\n />,\n );\n\n await click(\"button_open-app-Bitcoin\");\n\n expect(onOpenApp).toHaveBeenCalledWith(\"Bitcoin\");\n expect(query(\"spinner_opening-app-screen\")?.textContent).toContain(\n \"Opening Bitcoin\",\n );\n });\n\n it(\"shows why an app could not be opened\", async () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={() => Promise.reject(new Error(\"Could not open (6d00)\"))}\n />,\n );\n\n await click(\"button_open-app-Bitcoin\");\n\n expect(query(\"spinner_opening-app-screen\")).toBeNull();\n expect(container.textContent).toContain(\"Could not open (6d00)\");\n });\n\n it(\"gives up when an opened app never shows its screen\", async () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={() => Promise.resolve()}\n />,\n );\n\n await click(\"button_open-app-Ethereum\");\n act(() => {\n vi.advanceTimersByTime(10_000);\n });\n\n expect(container.textContent).toContain(\n \"Ethereum opened but its screen never showed up\",\n );\n });\n\n it(\"falls back to the app's initial when its icon is missing\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n />,\n );\n\n act(() => {\n query(\"button_open-app-Bitcoin\")!\n .querySelector(\"img\")!\n .dispatchEvent(new Event(\"error\"));\n });\n\n expect(query(\"button_open-app-Bitcoin\")?.querySelector(\"img\")).toBeNull();\n expect(query(\"button_open-app-Bitcoin\")?.textContent).toBe(\"BBitcoin\");\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_react", "import_client", "import_deviceModels", "import_DeviceOsInfo", "aDevice", "apps", "APPS", "container", "root", "query", "testId", "render", "element", "click", "React", "onOpenApp"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var m=Object.create;var d=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var f=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var g=(t,r,i,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of w(r))!b.call(t,n)&&n!==i&&d(t,n,{get:()=>r[n],enumerable:!(o=h(r,n))||o.enumerable});return t};var x=(t,r,i)=>(i=t!=null?m(f(t)):{},g(r||!t||!t.__esModule?d(i,"default",{value:t,enumerable:!0}):i,t));var p=x(require("react")),u=require("react-dom/client"),v=require("./DeviceScreen");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const y={id:"device-1",device_type:"stax",apps:[{name:"Bitcoin",version:"2.5.0"}]},a=(t={})=>({screenshot:vi.fn(()=>Promise.resolve(null)),idle:vi.fn(()=>Promise.resolve({kind:"os-info",device:y})),pressButton:vi.fn(()=>Promise.resolve()),touch:vi.fn(()=>Promise.resolve()),openApp:vi.fn(()=>Promise.resolve()),...t});describe("DeviceScreen",()=>{let t,r;const i=e=>t.querySelector(`[data-testid="${e}"]`),o=e=>(0,p.act)(()=>r.render(p.default.createElement(v.DeviceScreen,{deviceType:"stax",...e}))),n=(e=0)=>(0,p.act)(()=>vi.advanceTimersByTimeAsync(e)),s=(e,c,l={})=>(0,p.act)(()=>{e.dispatchEvent(new MouseEvent(c,{bubbles:!0,button:0,...l}))});beforeEach(()=>{vi.useFakeTimers(),URL.createObjectURL=vi.fn(()=>"blob:frame"),URL.revokeObjectURL=vi.fn(),HTMLElement.prototype.setPointerCapture=vi.fn(),HTMLElement.prototype.releasePointerCapture=vi.fn(),t=document.createElement("div"),document.body.append(t),r=(0,u.createRoot)(t)}),afterEach(()=>{(0,p.act)(()=>r.unmount()),t.remove(),vi.useRealTimers()}),describe("inline",()=>{it("shows a placeholder until the screen answers",()=>{o({api:a({screenshot:()=>new Promise(()=>{})})}),expect(i("placeholder_device-screen-loading")).not.toBeNull()}),it("shows the live screen of a running app",async()=>{o({api:a({screenshot:()=>Promise.resolve(new Blob(["png"]))})}),await n(),expect(i("image_device-screen")?.getAttribute("src")).toBe("blob:frame")}),it("shows the installed apps while no app runs",async()=>{o({api:a()}),await n(),expect(i("button_open-app-Bitcoin")).not.toBeNull()}),it("says there is no screen when the api has nothing to show instead",async()=>{o({api:a({idle:void 0})}),await n(),expect(t.textContent).toContain("No screen to capture")}),it("shows why the screen is unavailable",async()=>{o({api:a({screenshot:()=>Promise.reject(new Error("offline"))})}),await n(),expect(t.textContent).toContain("Screen unavailable"),expect(t.textContent).toContain("offline")}),it("keeps the last frame through a few failures while live",async()=>{const e=vi.fn().mockResolvedValueOnce(new Blob(["png"])).mockRejectedValue(new Error("app quitting")),c=()=>e.mock.calls.length-1;for(o({api:a({screenshot:e})}),await n();c()<3;)await n(500);for(expect(i("image_device-screen")).not.toBeNull();c()<4;)await n(500);expect(i("image_device-screen")).toBeNull(),expect(t.textContent).toContain("app quitting")}),it("drives a Nano with its buttons and refreshes after",async()=>{const e=a({screenshot:vi.fn(()=>Promise.resolve(new Blob(["png"])))});o({api:e,deviceType:"nanoX"}),await n(),s(i("button_device-screen-left"),"pointerdown"),s(i("button_device-screen-left"),"pointerup"),await n(),expect(e.pressButton).toHaveBeenCalledWith("left","press-and-release"),expect(e.screenshot).toHaveBeenCalledTimes(2)}),it("drives a touch device by its screen, without buttons",async()=>{o({api:a({screenshot:()=>Promise.resolve(new Blob(["png"]))})}),await n(),expect(i("image_device-screen")).not.toBeNull(),expect(i("button_device-screen-left")).toBeNull()})}),describe("floating",()=>{const e=()=>i("container_device-screen"),c=()=>e().firstElementChild;it("titles the window with the model and firmware",async()=>{o({api:a(),floating:!0,firmwareVersion:"1.10.1"}),await n(),expect(e().textContent).toContain("Stax - 1.10.1"),expect(e().style.width).toBe("224px")}),it("stops polling while collapsed",async()=>{const l=a();o({api:l,floating:!0,defaultCollapsed:!0}),await n(5e3),expect(l.screenshot).not.toHaveBeenCalled(),expect(i("button_open-app-Bitcoin")).toBeNull(),(0,p.act)(()=>i("button_toggle-device-screen").click()),await n(),expect(l.screenshot).toHaveBeenCalled(),expect(i("button_open-app-Bitcoin")).not.toBeNull()}),it("is dragged by its header",async()=>{o({api:a(),floating:!0}),await n(),s(c(),"pointerdown",{clientX:10,clientY:10}),s(c(),"pointermove",{clientX:110,clientY:60}),s(c(),"pointerup"),s(c(),"pointermove",{clientX:500,clientY:500}),expect(e().style.left).toBe("100px"),expect(e().style.top).toBe("50px")}),it("is not dragged from its button or with another mouse button",async()=>{o({api:a(),floating:!0}),await n(),s(i("button_toggle-device-screen"),"pointerdown"),s(c(),"pointerdown",{button:2}),s(c(),"pointermove",{clientX:110,clientY:60}),expect(e().style.left).toBe("")}),it("is resized from its edge, down to a minimum width",async()=>{o({api:a(),floating:!0}),await n(),e().getBoundingClientRect=()=>({left:100,top:80,width:224,height:300});const l=i("handle_resize-device-screen");s(l,"pointerdown",{clientX:324}),s(l,"pointermove",{clientX:424}),expect(e().style.width).toBe("324px"),expect(e().style.left).toBe("100px"),s(l,"pointermove",{clientX:0}),s(l,"pointerup"),s(l,"pointermove",{clientX:600}),expect(e().style.width).toBe("200px")})})});
|
|
2
|
+
//# sourceMappingURL=DeviceScreen.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/DeviceScreen.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nimport { type ScreenApi } from \"./types\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst DEVICE = {\n id: \"device-1\",\n device_type: \"stax\",\n apps: [{ name: \"Bitcoin\", version: \"2.5.0\" }],\n} as unknown as Device;\n\nconst anApi = (overrides: Partial<ScreenApi> = {}): ScreenApi => ({\n screenshot: vi.fn(() => Promise.resolve(null)),\n idle: vi.fn(() =>\n Promise.resolve({ kind: \"os-info\" as const, device: DEVICE }),\n ),\n pressButton: vi.fn(() => Promise.resolve()),\n touch: vi.fn(() => Promise.resolve()),\n openApp: vi.fn(() => Promise.resolve()),\n ...overrides,\n});\n\ndescribe(\"DeviceScreen\", () => {\n let container: HTMLDivElement;\n let root: Root;\n\n const query = (testId: string) =>\n container.querySelector<HTMLElement>(`[data-testid=\"${testId}\"]`);\n const render = (props: Partial<DeviceScreenProps> & { api: ScreenApi }) =>\n act(() => root.render(<DeviceScreen deviceType=\"stax\" {...props} />));\n const wait = (ms = 0) => act(() => vi.advanceTimersByTimeAsync(ms));\n const pointer = (target: Element, type: string, init: MouseEventInit = {}) =>\n act(() => {\n target.dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n URL.createObjectURL = vi.fn(() => \"blob:frame\");\n URL.revokeObjectURL = vi.fn();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n HTMLElement.prototype.releasePointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n describe(\"inline\", () => {\n it(\"shows a placeholder until the screen answers\", () => {\n render({ api: anApi({ screenshot: () => new Promise(() => {}) }) });\n\n expect(query(\"placeholder_device-screen-loading\")).not.toBeNull();\n });\n\n it(\"shows the live screen of a running app\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.resolve(new Blob([\"png\"])) }),\n });\n await wait();\n\n expect(query(\"image_device-screen\")?.getAttribute(\"src\")).toBe(\n \"blob:frame\",\n );\n });\n\n it(\"shows the installed apps while no app runs\", async () => {\n render({ api: anApi() });\n await wait();\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n });\n\n it(\"says there is no screen when the api has nothing to show instead\", async () => {\n render({ api: anApi({ idle: undefined }) });\n await wait();\n\n expect(container.textContent).toContain(\"No screen to capture\");\n });\n\n it(\"shows why the screen is unavailable\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.reject(new Error(\"offline\")) }),\n });\n await wait();\n\n expect(container.textContent).toContain(\"Screen unavailable\");\n expect(container.textContent).toContain(\"offline\");\n });\n\n it(\"keeps the last frame through a few failures while live\", async () => {\n const screenshot = vi\n .fn<() => Promise<Blob | null>>()\n .mockResolvedValueOnce(new Blob([\"png\"]))\n .mockRejectedValue(new Error(\"app quitting\"));\n const failures = () => screenshot.mock.calls.length - 1;\n render({ api: anApi({ screenshot }) });\n await wait();\n\n while (failures() < 3) await wait(500);\n expect(query(\"image_device-screen\")).not.toBeNull();\n\n while (failures() < 4) await wait(500);\n expect(query(\"image_device-screen\")).toBeNull();\n expect(container.textContent).toContain(\"app quitting\");\n });\n\n it(\"drives a Nano with its buttons and refreshes after\", async () => {\n const api = anApi({\n screenshot: vi.fn(() => Promise.resolve(new Blob([\"png\"]))),\n });\n render({ api, deviceType: \"nanoX\" });\n await wait();\n\n pointer(query(\"button_device-screen-left\")!, \"pointerdown\");\n pointer(query(\"button_device-screen-left\")!, \"pointerup\");\n await wait();\n\n expect(api.pressButton).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n expect(api.screenshot).toHaveBeenCalledTimes(2);\n });\n\n it(\"drives a touch device by its screen, without buttons\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.resolve(new Blob([\"png\"])) }),\n });\n await wait();\n\n expect(query(\"image_device-screen\")).not.toBeNull();\n expect(query(\"button_device-screen-left\")).toBeNull();\n });\n });\n\n describe(\"floating\", () => {\n const windowElement = () => query(\"container_device-screen\")!;\n const header = () => windowElement().firstElementChild!;\n\n it(\"titles the window with the model and firmware\", async () => {\n render({ api: anApi(), floating: true, firmwareVersion: \"1.10.1\" });\n await wait();\n\n expect(windowElement().textContent).toContain(\"Stax - 1.10.1\");\n expect(windowElement().style.width).toBe(\"224px\");\n });\n\n it(\"stops polling while collapsed\", async () => {\n const api = anApi();\n render({ api, floating: true, defaultCollapsed: true });\n await wait(5000);\n\n expect(api.screenshot).not.toHaveBeenCalled();\n expect(query(\"button_open-app-Bitcoin\")).toBeNull();\n\n act(() => query(\"button_toggle-device-screen\")!.click());\n await wait();\n\n expect(api.screenshot).toHaveBeenCalled();\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n });\n\n it(\"is dragged by its header\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n pointer(header(), \"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(header(), \"pointermove\", { clientX: 110, clientY: 60 });\n pointer(header(), \"pointerup\");\n pointer(header(), \"pointermove\", { clientX: 500, clientY: 500 });\n\n expect(windowElement().style.left).toBe(\"100px\");\n expect(windowElement().style.top).toBe(\"50px\");\n });\n\n it(\"is not dragged from its button or with another mouse button\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n pointer(query(\"button_toggle-device-screen\")!, \"pointerdown\");\n pointer(header(), \"pointerdown\", { button: 2 });\n pointer(header(), \"pointermove\", { clientX: 110, clientY: 60 });\n\n expect(windowElement().style.left).toBe(\"\");\n });\n\n it(\"is resized from its edge, down to a minimum width\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n windowElement().getBoundingClientRect = () =>\n ({ left: 100, top: 80, width: 224, height: 300 }) as DOMRect;\n const handle = query(\"handle_resize-device-screen\")!;\n\n pointer(handle, \"pointerdown\", { clientX: 324 });\n pointer(handle, \"pointermove\", { clientX: 424 });\n expect(windowElement().style.width).toBe(\"324px\");\n expect(windowElement().style.left).toBe(\"100px\");\n\n pointer(handle, \"pointermove\", { clientX: 0 });\n pointer(handle, \"pointerup\");\n pointer(handle, \"pointermove\", { clientX: 600 });\n expect(windowElement().style.width).toBe(\"200px\");\n });\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_react", "import_client", "import_DeviceScreen", "DEVICE", "anApi", "overrides", "container", "root", "query", "testId", "render", "props", "React", "wait", "ms", "pointer", "target", "type", "init", "screenshot", "failures", "api", "windowElement", "header", "handle"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var h=Object.create;var p=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var b=Object.getPrototypeOf,E=Object.prototype.hasOwnProperty;var f=(t,r,e,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of v(r))!E.call(t,o)&&o!==e&&p(t,o,{get:()=>r[o],enumerable:!(n=m(r,o))||n.enumerable});return t};var g=(t,r,e)=>(e=t!=null?h(b(t)):{},f(r||!t||!t.__esModule?p(e,"default",{value:t,enumerable:!0}):e,t));var s=g(require("react")),c=require("react-dom/client"),u=require("./DeviceScreenButtons");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});describe("DeviceScreenButtons",()=>{let t,r;const e=vi.fn(),n=i=>t.querySelector(`[data-testid="button_device-screen-${i}"]`),o=(i,l,d={})=>(0,s.act)(()=>{i.dispatchEvent(new MouseEvent(l,{bubbles:!0,button:0,...d}))}),a=(i,l,d)=>(0,s.act)(()=>{i.dispatchEvent(new KeyboardEvent(l,{bubbles:!0,key:d}))});beforeEach(()=>{vi.useFakeTimers(),e.mockReset(),HTMLElement.prototype.setPointerCapture=vi.fn(),t=document.createElement("div"),document.body.append(t),r=(0,c.createRoot)(t),(0,s.act)(()=>r.render(s.default.createElement(u.DeviceScreenButtons,{onPress:e})))}),afterEach(()=>{(0,s.act)(()=>r.unmount()),t.remove(),vi.useRealTimers()}),it("sends a quick click as one press-and-release",()=>{o(n("left"),"pointerdown"),o(n("left"),"pointerup"),expect(e).toHaveBeenCalledOnce(),expect(e).toHaveBeenCalledWith("left","press-and-release")}),it("sends a hold as a press then a release",()=>{o(n("both"),"pointerdown"),(0,s.act)(()=>{vi.advanceTimersByTime(200)}),o(n("both"),"pointerup"),expect(e.mock.calls).toEqual([["both","press"],["both","release"]])}),it("ignores buttons other than the main one",()=>{o(n("right"),"pointerdown",{button:2}),o(n("right"),"pointerup",{button:2}),expect(e).not.toHaveBeenCalled()}),it("handles one button at a time",()=>{o(n("left"),"pointerdown"),o(n("right"),"pointerdown"),o(n("left"),"pointerup"),expect(e).toHaveBeenCalledWith("left","press-and-release"),expect(e).toHaveBeenCalledOnce()}),it("drives the buttons from the keyboard",()=>{a(n("right"),"keydown","Enter"),a(n("right"),"keyup","Enter"),a(n("right"),"keydown","a"),expect(e).toHaveBeenCalledOnce(),expect(e).toHaveBeenCalledWith("right","press-and-release")}),it("releases a held button when unmounted",()=>{o(n("left"),"pointerdown"),(0,s.act)(()=>{vi.advanceTimersByTime(200)}),(0,s.act)(()=>r.render(s.default.createElement(s.default.Fragment,null))),expect(e).toHaveBeenLastCalledWith("left","release")})});
|
|
2
|
+
//# sourceMappingURL=DeviceScreenButtons.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/DeviceScreenButtons.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\nimport { DeviceScreenButtons } from \"./DeviceScreenButtons\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\ndescribe(\"DeviceScreenButtons\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const onPress = vi.fn();\n\n const button = (name: string) =>\n container.querySelector<HTMLButtonElement>(\n `[data-testid=\"button_device-screen-${name}\"]`,\n )!;\n const pointer = (target: Element, type: string, init: MouseEventInit = {}) =>\n act(() => {\n target.dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n const key = (target: Element, type: string, value: string) =>\n act(() => {\n target.dispatchEvent(\n new KeyboardEvent(type, { bubbles: true, key: value }),\n );\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n onPress.mockReset();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n act(() => root.render(<DeviceScreenButtons onPress={onPress} />));\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n it(\"sends a quick click as one press-and-release\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n pointer(button(\"left\"), \"pointerup\");\n\n expect(onPress).toHaveBeenCalledOnce();\n expect(onPress).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n });\n\n it(\"sends a hold as a press then a release\", () => {\n pointer(button(\"both\"), \"pointerdown\");\n act(() => {\n vi.advanceTimersByTime(200);\n });\n pointer(button(\"both\"), \"pointerup\");\n\n expect(onPress.mock.calls).toEqual([\n [\"both\", \"press\"],\n [\"both\", \"release\"],\n ]);\n });\n\n it(\"ignores buttons other than the main one\", () => {\n pointer(button(\"right\"), \"pointerdown\", { button: 2 });\n pointer(button(\"right\"), \"pointerup\", { button: 2 });\n\n expect(onPress).not.toHaveBeenCalled();\n });\n\n it(\"handles one button at a time\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n pointer(button(\"right\"), \"pointerdown\");\n pointer(button(\"left\"), \"pointerup\");\n\n expect(onPress).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n expect(onPress).toHaveBeenCalledOnce();\n });\n\n it(\"drives the buttons from the keyboard\", () => {\n key(button(\"right\"), \"keydown\", \"Enter\");\n key(button(\"right\"), \"keyup\", \"Enter\");\n key(button(\"right\"), \"keydown\", \"a\");\n\n expect(onPress).toHaveBeenCalledOnce();\n expect(onPress).toHaveBeenCalledWith(\"right\", \"press-and-release\");\n });\n\n it(\"releases a held button when unmounted\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n act(() => {\n vi.advanceTimersByTime(200);\n });\n act(() => root.render(<></>));\n\n expect(onPress).toHaveBeenLastCalledWith(\"left\", \"release\");\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_react", "import_client", "import_DeviceScreenButtons", "container", "root", "onPress", "button", "name", "pointer", "target", "type", "init", "key", "value", "React"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var h=Object.create;var s=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var b=Object.getPrototypeOf,g=Object.prototype.hasOwnProperty;var E=(e,n,t,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of v(n))!g.call(e,o)&&o!==t&&s(e,o,{get:()=>n[o],enumerable:!(r=m(n,o))||r.enumerable});return e};var f=(e,n,t)=>(t=e!=null?h(b(e)):{},E(n||!e||!e.__esModule?s(t,"default",{value:e,enumerable:!0}):t,e));var i=f(require("react")),d=require("react-dom/client"),p=require("./DeviceScreenImage");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});describe("DeviceScreenImage",()=>{let e,n;const t=vi.fn(),r=()=>e.querySelector('[data-testid="image_device-screen"]'),o=l=>(0,i.act)(()=>n.render(i.default.createElement(p.DeviceScreenImage,{src:"blob:1",onTouch:l}))),c=(l,u={})=>(0,i.act)(()=>{r().dispatchEvent(new MouseEvent(l,{bubbles:!0,button:0,...u}))}),a=()=>{Object.defineProperty(r(),"naturalWidth",{value:400}),Object.defineProperty(r(),"naturalHeight",{value:600}),r().getBoundingClientRect=()=>({left:0,top:0,width:200,height:300})};beforeEach(()=>{t.mockReset(),HTMLElement.prototype.setPointerCapture=vi.fn(),HTMLElement.prototype.releasePointerCapture=vi.fn(),e=document.createElement("div"),document.body.append(e),n=(0,d.createRoot)(e)}),afterEach(()=>{(0,i.act)(()=>n.unmount()),e.remove()}),it("maps a touch to device pixels and releases it where it went down",()=>{o(t),a(),c("pointerdown",{clientX:50,clientY:150}),c("pointerup",{clientX:190,clientY:10}),expect(t.mock.calls).toEqual([[100,300,"press"],[100,300,"release"]])}),it("clamps a touch on the far edge to the last pixel",()=>{o(t),a(),c("pointerdown",{clientX:200,clientY:300}),expect(t).toHaveBeenCalledWith(399,599,"press")}),it("ignores touches before the image has a size",()=>{o(t),c("pointerdown",{clientX:10,clientY:10}),c("pointerup"),expect(t).not.toHaveBeenCalled()}),it("ignores touches on a button-driven device",()=>{o(),a(),c("pointerdown",{clientX:10,clientY:10}),expect(t).not.toHaveBeenCalled()}),it("releases a held touch when unmounted",()=>{o(t),a(),c("pointerdown",{clientX:50,clientY:150}),(0,i.act)(()=>n.render(i.default.createElement(i.default.Fragment,null))),expect(t).toHaveBeenLastCalledWith(100,300,"release")}),it("says so when the screenshot cannot be decoded",()=>{o(t),(0,i.act)(()=>{r().dispatchEvent(new Event("error"))}),expect(e.querySelector('[data-testid="text_device-screen-undecodable"]')).not.toBeNull()})});
|
|
2
|
+
//# sourceMappingURL=DeviceScreenImage.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/DeviceScreenImage.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\nimport { DeviceScreenImage } from \"./DeviceScreenImage\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\ndescribe(\"DeviceScreenImage\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const onTouch = vi.fn();\n\n const image = () =>\n container.querySelector<HTMLImageElement>(\n '[data-testid=\"image_device-screen\"]',\n )!;\n const render = (touch?: typeof onTouch) =>\n act(() => root.render(<DeviceScreenImage src=\"blob:1\" onTouch={touch} />));\n const pointer = (type: string, init: MouseEventInit = {}) =>\n act(() => {\n image().dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n /** A 400x600 screen drawn at 200x300 from the page's top left corner. */\n const sizeImage = () => {\n Object.defineProperty(image(), \"naturalWidth\", { value: 400 });\n Object.defineProperty(image(), \"naturalHeight\", { value: 600 });\n image().getBoundingClientRect = () =>\n ({ left: 0, top: 0, width: 200, height: 300 }) as DOMRect;\n };\n\n beforeEach(() => {\n onTouch.mockReset();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n HTMLElement.prototype.releasePointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n });\n\n it(\"maps a touch to device pixels and releases it where it went down\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n pointer(\"pointerup\", { clientX: 190, clientY: 10 });\n\n expect(onTouch.mock.calls).toEqual([\n [100, 300, \"press\"],\n [100, 300, \"release\"],\n ]);\n });\n\n it(\"clamps a touch on the far edge to the last pixel\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 200, clientY: 300 });\n\n expect(onTouch).toHaveBeenCalledWith(399, 599, \"press\");\n });\n\n it(\"ignores touches before the image has a size\", () => {\n render(onTouch);\n\n pointer(\"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(\"pointerup\");\n\n expect(onTouch).not.toHaveBeenCalled();\n });\n\n it(\"ignores touches on a button-driven device\", () => {\n render();\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 10, clientY: 10 });\n\n expect(onTouch).not.toHaveBeenCalled();\n });\n\n it(\"releases a held touch when unmounted\", () => {\n render(onTouch);\n sizeImage();\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n\n act(() => root.render(<></>));\n\n expect(onTouch).toHaveBeenLastCalledWith(100, 300, \"release\");\n });\n\n it(\"says so when the screenshot cannot be decoded\", () => {\n render(onTouch);\n\n act(() => {\n image().dispatchEvent(new Event(\"error\"));\n });\n\n expect(\n container.querySelector('[data-testid=\"text_device-screen-undecodable\"]'),\n ).not.toBeNull();\n });\n});\n"],
|
|
5
|
+
"mappings": "wdAAA,IAAAA,EAA2B,oBAC3BC,EAAsC,4BAEtCC,EAAkC,+BAElC,OAAO,OAAO,WAAY,CAAE,yBAA0B,EAAK,CAAC,EAE5D,SAAS,oBAAqB,IAAM,CAClC,IAAIC,EACAC,EACJ,MAAMC,EAAU,GAAG,GAAG,EAEhBC,EAAQ,IACZH,EAAU,cACR,qCACF,EACII,EAAUC,MACd,OAAI,IAAMJ,EAAK,OAAO,EAAAK,QAAA,cAAC,qBAAkB,IAAI,SAAS,QAASD,EAAO,CAAE,CAAC,EACrEE,EAAU,CAACC,EAAcC,EAAuB,CAAC,OACrD,OAAI,IAAM,CACRN,EAAM,EAAE,cACN,IAAI,WAAWK,EAAM,CAAE,QAAS,GAAM,OAAQ,EAAG,GAAGC,CAAK,CAAC,CAC5D,CACF,CAAC,EAEGC,EAAY,IAAM,CACtB,OAAO,eAAeP,EAAM,EAAG,eAAgB,CAAE,MAAO,GAAI,CAAC,EAC7D,OAAO,eAAeA,EAAM,EAAG,gBAAiB,CAAE,MAAO,GAAI,CAAC,EAC9DA,EAAM,EAAE,sBAAwB,KAC7B,CAAE,KAAM,EAAG,IAAK,EAAG,MAAO,IAAK,OAAQ,GAAI,EAChD,EAEA,WAAW,IAAM,CACfD,EAAQ,UAAU,EAClB,YAAY,UAAU,kBAAoB,GAAG,GAAG,EAChD,YAAY,UAAU,sBAAwB,GAAG,GAAG,EACpDF,EAAY,SAAS,cAAc,KAAK,EACxC,SAAS,KAAK,OAAOA,CAAS,EAC9BC,KAAO,cAAWD,CAAS,CAC7B,CAAC,EAED,UAAU,IAAM,IACd,OAAI,IAAMC,EAAK,QAAQ,CAAC,EACxBD,EAAU,OAAO,CACnB,CAAC,EAED,GAAG,mEAAoE,IAAM,CAC3EI,EAAOF,CAAO,EACdQ,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,GAAI,CAAC,EACpDA,EAAQ,YAAa,CAAE,QAAS,IAAK,QAAS,EAAG,CAAC,EAElD,OAAOL,EAAQ,KAAK,KAAK,EAAE,QAAQ,CACjC,CAAC,IAAK,IAAK,OAAO,EAClB,CAAC,IAAK,IAAK,SAAS,CACtB,CAAC,CACH,CAAC,EAED,GAAG,mDAAoD,IAAM,CAC3DE,EAAOF,CAAO,EACdQ,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,IAAK,QAAS,GAAI,CAAC,EAErD,OAAOL,CAAO,EAAE,qBAAqB,IAAK,IAAK,OAAO,CACxD,CAAC,EAED,GAAG,8CAA+C,IAAM,CACtDE,EAAOF,CAAO,EAEdK,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,EAAG,CAAC,EACnDA,EAAQ,WAAW,EAEnB,OAAOL,CAAO,EAAE,IAAI,iBAAiB,CACvC,CAAC,EAED,GAAG,4CAA6C,IAAM,CACpDE,EAAO,EACPM,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,EAAG,CAAC,EAEnD,OAAOL,CAAO,EAAE,IAAI,iBAAiB,CACvC,CAAC,EAED,GAAG,uCAAwC,IAAM,CAC/CE,EAAOF,CAAO,EACdQ,EAAU,EACVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,GAAI,CAAC,KAEpD,OAAI,IAAMN,EAAK,OAAO,EAAAK,QAAA,gBAAAA,QAAA,aAAE,CAAG,CAAC,EAE5B,OAAOJ,CAAO,EAAE,yBAAyB,IAAK,IAAK,SAAS,CAC9D,CAAC,EAED,GAAG,gDAAiD,IAAM,CACxDE,EAAOF,CAAO,KAEd,OAAI,IAAM,CACRC,EAAM,EAAE,cAAc,IAAI,MAAM,OAAO,CAAC,CAC1C,CAAC,EAED,OACEH,EAAU,cAAc,gDAAgD,CAC1E,EAAE,IAAI,SAAS,CACjB,CAAC,CACH,CAAC",
|
|
6
|
+
"names": ["import_react", "import_client", "import_DeviceScreenImage", "container", "root", "onTouch", "image", "render", "touch", "React", "pointer", "type", "init", "sizeImage"]
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/MockServerDevice.tsx"],
|
|
4
|
-
"sourcesContent": ["import React, { useMemo } from \"react\";\nimport { MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nimport { mockServerScreenApi } from \"./mockServerScreenApi\";\nimport { useMockServerDevice } from \"./useMockServerDevice\";\n\nexport type MockServerDeviceProps = Pick<\n DeviceScreenProps,\n \"floating\" | \"defaultCollapsed\"\n> & {\n url: string;\n /**
|
|
4
|
+
"sourcesContent": ["import React, { useMemo } from \"react\";\nimport { MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nimport { mockServerScreenApi } from \"./mockServerScreenApi\";\nimport { useMockServerDevice } from \"./useMockServerDevice\";\n\nexport type MockServerDeviceProps = Pick<\n DeviceScreenProps,\n \"floating\" | \"defaultCollapsed\"\n> & {\n url: string;\n /** Of the session owning the device: devices are scoped to their session. */\n token: string;\n deviceId: string;\n};\n\nexport const MockServerDevice: React.FC<MockServerDeviceProps> = ({\n url,\n token,\n deviceId,\n ...props\n}) => {\n const client = useMemo(() => new MockClient(url, { token }), [url, token]);\n const api = useMemo(\n () => mockServerScreenApi(client, deviceId),\n [client, deviceId],\n );\n const device = useMockServerDevice(client, deviceId);\n\n // Decides touch or buttons, so nothing renders until it is known. An\n // unreadable record still shows the screen, and its errors.\n if (device === undefined) return null;\n\n return (\n <DeviceScreen\n api={api}\n deviceType={device?.device_type ?? \"\"}\n firmwareVersion={device?.firmware_version}\n {...props}\n />\n );\n};\n"],
|
|
5
5
|
"mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAA+B,oBAC/BC,EAA2B,8CAE3BC,EAAqD,0BACrDC,EAAoC,iCACpCC,EAAoC,iCAY7B,MAAMN,EAAoD,CAAC,CAChE,IAAAO,EACA,MAAAC,EACA,SAAAC,EACA,GAAGC,CACL,IAAM,CACJ,MAAMC,KAAS,WAAQ,IAAM,IAAI,aAAWJ,EAAK,CAAE,MAAAC,CAAM,CAAC,EAAG,CAACD,EAAKC,CAAK,CAAC,EACnEI,KAAM,WACV,OAAM,uBAAoBD,EAAQF,CAAQ,EAC1C,CAACE,EAAQF,CAAQ,CACnB,EACMI,KAAS,uBAAoBF,EAAQF,CAAQ,EAInD,OAAII,IAAW,OAAkB,KAG/B,EAAAC,QAAA,cAAC,gBACC,IAAKF,EACL,WAAYC,GAAQ,aAAe,GACnC,gBAAiBA,GAAQ,iBACxB,GAAGH,EACN,CAEJ",
|
|
6
6
|
"names": ["MockServerDevice_exports", "__export", "MockServerDevice", "__toCommonJS", "import_react", "import_device_mockserver_client", "import_DeviceScreen", "import_mockServerScreenApi", "import_useMockServerDevice", "url", "token", "deviceId", "props", "client", "api", "device", "React"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var l=Object.create;var s=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,h=Object.prototype.hasOwnProperty;var u=(e,t,o,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let c of v(t))!h.call(e,c)&&c!==o&&s(e,c,{get:()=>t[c],enumerable:!(i=d(t,c))||i.enumerable});return e};var p=(e,t,o)=>(o=e!=null?l(k(e)):{},u(t||!e||!e.__esModule?s(o,"default",{value:e,enumerable:!0}):o,e));var n=p(require("react")),a=require("react-dom/client"),r=require("@ledgerhq/device-mockserver-client"),m=require("./MockServerDevice");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});vi.mock("@ledgerhq/device-mockserver-client",()=>({MockClient:vi.fn()}));describe("MockServerDevice",()=>{let e,t;const o=vi.fn();beforeEach(()=>{vi.useFakeTimers(),o.mockReset(),vi.mocked(r.MockClient).mockImplementation(()=>({getDevice:o,getScreenshot:()=>new Promise(()=>{})})),e=document.createElement("div"),document.body.append(e),t=(0,a.createRoot)(e)}),afterEach(()=>{(0,n.act)(()=>t.unmount()),e.remove(),vi.useRealTimers()});const i=()=>(0,n.act)(()=>t.render(n.default.createElement(m.MockServerDevice,{url:"http://mock",token:"session-token",deviceId:"device-1",floating:!0})));it("renders nothing until the device's model is known",()=>{o.mockReturnValue(new Promise(()=>{})),i(),expect(e.childElementCount).toBe(0)}),it("shows the device's screen in its session",async()=>{o.mockResolvedValue({id:"device-1",device_type:"flex",firmware_version:"1.6.1"}),i(),await(0,n.act)(()=>vi.advanceTimersByTimeAsync(0)),expect(r.MockClient).toHaveBeenCalledWith("http://mock",{token:"session-token"}),expect(o).toHaveBeenCalledWith("device-1"),expect(e.textContent).toContain("Flex - 1.6.1")}),it("still shows the screen when the device cannot be read",async()=>{o.mockRejectedValue(new Error("offline")),i(),await(0,n.act)(()=>vi.advanceTimersByTimeAsync(0)),expect(e.querySelector('[data-testid="container_device-screen"]')).not.toBeNull()})});
|
|
2
|
+
//# sourceMappingURL=MockServerDevice.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/MockServerDevice.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { MockServerDevice } from \"./MockServerDevice\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nvi.mock(\"@ledgerhq/device-mockserver-client\", () => ({\n MockClient: vi.fn(),\n}));\n\ndescribe(\"MockServerDevice\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const getDevice = vi.fn();\n\n beforeEach(() => {\n vi.useFakeTimers();\n getDevice.mockReset();\n vi.mocked(MockClient).mockImplementation(\n () =>\n ({\n getDevice,\n getScreenshot: () => new Promise(() => {}),\n }) as unknown as MockClient,\n );\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n const render = () =>\n act(() =>\n root.render(\n <MockServerDevice\n url=\"http://mock\"\n token=\"session-token\"\n deviceId=\"device-1\"\n floating\n />,\n ),\n );\n\n it(\"renders nothing until the device's model is known\", () => {\n getDevice.mockReturnValue(new Promise(() => {}));\n\n render();\n\n expect(container.childElementCount).toBe(0);\n });\n\n it(\"shows the device's screen in its session\", async () => {\n getDevice.mockResolvedValue({\n id: \"device-1\",\n device_type: \"flex\",\n firmware_version: \"1.6.1\",\n });\n\n render();\n await act(() => vi.advanceTimersByTimeAsync(0));\n\n expect(MockClient).toHaveBeenCalledWith(\"http://mock\", {\n token: \"session-token\",\n });\n expect(getDevice).toHaveBeenCalledWith(\"device-1\");\n expect(container.textContent).toContain(\"Flex - 1.6.1\");\n });\n\n it(\"still shows the screen when the device cannot be read\", async () => {\n getDevice.mockRejectedValue(new Error(\"offline\"));\n\n render();\n await act(() => vi.advanceTimersByTimeAsync(0));\n\n expect(\n container.querySelector('[data-testid=\"container_device-screen\"]'),\n ).not.toBeNull();\n });\n});\n"],
|
|
5
|
+
"mappings": "wdAAA,IAAAA,EAA2B,oBAC3BC,EAAsC,4BACtCC,EAA2B,8CAE3BC,EAAiC,8BAEjC,OAAO,OAAO,WAAY,CAAE,yBAA0B,EAAK,CAAC,EAE5D,GAAG,KAAK,qCAAsC,KAAO,CACnD,WAAY,GAAG,GAAG,CACpB,EAAE,EAEF,SAAS,mBAAoB,IAAM,CACjC,IAAIC,EACAC,EACJ,MAAMC,EAAY,GAAG,GAAG,EAExB,WAAW,IAAM,CACf,GAAG,cAAc,EACjBA,EAAU,UAAU,EACpB,GAAG,OAAO,YAAU,EAAE,mBACpB,KACG,CACC,UAAAA,EACA,cAAe,IAAM,IAAI,QAAQ,IAAM,CAAC,CAAC,CAC3C,EACJ,EACAF,EAAY,SAAS,cAAc,KAAK,EACxC,SAAS,KAAK,OAAOA,CAAS,EAC9BC,KAAO,cAAWD,CAAS,CAC7B,CAAC,EAED,UAAU,IAAM,IACd,OAAI,IAAMC,EAAK,QAAQ,CAAC,EACxBD,EAAU,OAAO,EACjB,GAAG,cAAc,CACnB,CAAC,EAED,MAAMG,EAAS,OACb,OAAI,IACFF,EAAK,OACH,EAAAG,QAAA,cAAC,oBACC,IAAI,cACJ,MAAM,gBACN,SAAS,WACT,SAAQ,GACV,CACF,CACF,EAEF,GAAG,oDAAqD,IAAM,CAC5DF,EAAU,gBAAgB,IAAI,QAAQ,IAAM,CAAC,CAAC,CAAC,EAE/CC,EAAO,EAEP,OAAOH,EAAU,iBAAiB,EAAE,KAAK,CAAC,CAC5C,CAAC,EAED,GAAG,2CAA4C,SAAY,CACzDE,EAAU,kBAAkB,CAC1B,GAAI,WACJ,YAAa,OACb,iBAAkB,OACpB,CAAC,EAEDC,EAAO,EACP,QAAM,OAAI,IAAM,GAAG,yBAAyB,CAAC,CAAC,EAE9C,OAAO,YAAU,EAAE,qBAAqB,cAAe,CACrD,MAAO,eACT,CAAC,EACD,OAAOD,CAAS,EAAE,qBAAqB,UAAU,EACjD,OAAOF,EAAU,WAAW,EAAE,UAAU,cAAc,CACxD,CAAC,EAED,GAAG,wDAAyD,SAAY,CACtEE,EAAU,kBAAkB,IAAI,MAAM,SAAS,CAAC,EAEhDC,EAAO,EACP,QAAM,OAAI,IAAM,GAAG,yBAAyB,CAAC,CAAC,EAE9C,OACEH,EAAU,cAAc,yCAAyC,CACnE,EAAE,IAAI,SAAS,CACjB,CAAC,CACH,CAAC",
|
|
6
|
+
"names": ["import_react", "import_client", "import_device_mockserver_client", "import_MockServerDevice", "container", "root", "getDevice", "render", "React"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var e=require("./deviceModels");describe("findDeviceScreenModel",()=>{it("finds a model by the mock server's device type",()=>{expect((0,e.findDeviceScreenModel)("stax")).toMatchObject({label:"Stax",touch:!0,speculos:!0})}),it("marks Nano S as having no emulator",()=>{expect((0,e.findDeviceScreenModel)("nanoS").speculos).toBe(!1)}),it("falls back to a button-driven model for an unknown type",()=>{expect((0,e.findDeviceScreenModel)("unknown")).toMatchObject({label:"Device",touch:!1})})});
|
|
2
|
+
//# sourceMappingURL=deviceModels.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/deviceModels.test.ts"],
|
|
4
|
+
"sourcesContent": ["import { findDeviceScreenModel } from \"./deviceModels\";\n\ndescribe(\"findDeviceScreenModel\", () => {\n it(\"finds a model by the mock server's device type\", () => {\n expect(findDeviceScreenModel(\"stax\")).toMatchObject({\n label: \"Stax\",\n touch: true,\n speculos: true,\n });\n });\n\n it(\"marks Nano S as having no emulator\", () => {\n expect(findDeviceScreenModel(\"nanoS\").speculos).toBe(false);\n });\n\n it(\"falls back to a button-driven model for an unknown type\", () => {\n expect(findDeviceScreenModel(\"unknown\")).toMatchObject({\n label: \"Device\",\n touch: false,\n });\n });\n});\n"],
|
|
5
|
+
"mappings": "aAAA,IAAAA,EAAsC,0BAEtC,SAAS,wBAAyB,IAAM,CACtC,GAAG,iDAAkD,IAAM,CACzD,UAAO,yBAAsB,MAAM,CAAC,EAAE,cAAc,CAClD,MAAO,OACP,MAAO,GACP,SAAU,EACZ,CAAC,CACH,CAAC,EAED,GAAG,qCAAsC,IAAM,CAC7C,UAAO,yBAAsB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAK,CAC5D,CAAC,EAED,GAAG,0DAA2D,IAAM,CAClE,UAAO,yBAAsB,SAAS,CAAC,EAAE,cAAc,CACrD,MAAO,SACP,MAAO,EACT,CAAC,CACH,CAAC,CACH,CAAC",
|
|
6
|
+
"names": ["import_deviceModels"]
|
|
7
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var o=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var v=(t,e)=>{for(var r in e)o(t,r,{get:e[r],enumerable:!0})},D=(t,e,r,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of m(e))!p.call(t,n)&&n!==r&&o(t,n,{get:()=>e[n],enumerable:!(i=s(e,n))||i.enumerable});return t};var a=t=>D(o({},"__esModule",{value:!0}),t);var T={};v(T,{useMockServerDevice:()=>y});module.exports=a(T);var u=require("react");const d=2e3;function y(t,e){const[r,i]=(0,u.useState)();return(0,u.useEffect)(()=>{let n=!1,c;if(i(void 0),!e)return;const l=()=>{t.getDevice(e).then(f=>{n||i(f)},()=>{n||(i(null),c=setTimeout(l,d))})};return l(),()=>{n=!0,clearTimeout(c)}},[t,e]),r}0&&(module.exports={useMockServerDevice});
|
|
2
2
|
//# sourceMappingURL=useMockServerDevice.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/useMockServerDevice.ts"],
|
|
4
|
-
"sourcesContent": ["import { useEffect, useState } from \"react\";\nimport {\n type Device,\n type MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\n\n/**\n * The mock server's record of a device: undefined while it loads, or while no\n * device id is given, and null
|
|
5
|
-
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,yBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAoC,
|
|
6
|
-
"names": ["useMockServerDevice_exports", "__export", "useMockServerDevice", "__toCommonJS", "import_react", "client", "deviceId", "device", "setDevice", "cancelled", "record"]
|
|
4
|
+
"sourcesContent": ["import { useEffect, useState } from \"react\";\nimport {\n type Device,\n type MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\n\nconst RETRY_MS = 2000;\n\n/**\n * The mock server's record of a device: undefined while it loads, or while no\n * device id is given, and null while it cannot be read. A failed read is\n * retried, so a transient error does not leave the device without its model.\n */\nexport function useMockServerDevice(\n client: MockClient,\n deviceId: string,\n): Device | null | undefined {\n const [device, setDevice] = useState<Device | null>();\n\n useEffect(() => {\n let cancelled = false;\n let retry: ReturnType<typeof setTimeout> | undefined;\n setDevice(undefined);\n if (!deviceId) return;\n\n const load = () => {\n client.getDevice(deviceId).then(\n (record) => {\n if (!cancelled) setDevice(record);\n },\n () => {\n if (cancelled) return;\n setDevice(null);\n retry = setTimeout(load, RETRY_MS);\n },\n );\n };\n load();\n\n return () => {\n cancelled = true;\n clearTimeout(retry);\n };\n }, [client, deviceId]);\n\n return device;\n}\n"],
|
|
5
|
+
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,yBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAoC,iBAMpC,MAAMC,EAAW,IAOV,SAASH,EACdI,EACAC,EAC2B,CAC3B,KAAM,CAACC,EAAQC,CAAS,KAAI,YAAwB,EAEpD,sBAAU,IAAM,CACd,IAAIC,EAAY,GACZC,EAEJ,GADAF,EAAU,MAAS,EACf,CAACF,EAAU,OAEf,MAAMK,EAAO,IAAM,CACjBN,EAAO,UAAUC,CAAQ,EAAE,KACxBM,GAAW,CACLH,GAAWD,EAAUI,CAAM,CAClC,EACA,IAAM,CACAH,IACJD,EAAU,IAAI,EACdE,EAAQ,WAAWC,EAAMP,CAAQ,EACnC,CACF,CACF,EACA,OAAAO,EAAK,EAEE,IAAM,CACXF,EAAY,GACZ,aAAaC,CAAK,CACpB,CACF,EAAG,CAACL,EAAQC,CAAQ,CAAC,EAEdC,CACT",
|
|
6
|
+
"names": ["useMockServerDevice_exports", "__export", "useMockServerDevice", "__toCommonJS", "import_react", "RETRY_MS", "client", "deviceId", "device", "setDevice", "cancelled", "retry", "load", "record"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var f=Object.create;var v=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var D=Object.getPrototypeOf,T=Object.prototype.hasOwnProperty;var C=(t,i,c,s)=>{if(i&&typeof i=="object"||typeof i=="function")for(let o of y(i))!T.call(t,o)&&o!==c&&v(t,o,{get:()=>i[o],enumerable:!(s=p(i,o))||s.enumerable});return t};var B=(t,i,c)=>(c=t!=null?f(D(t)):{},C(i||!t||!t.__esModule?v(c,"default",{value:t,enumerable:!0}):c,t));var n=B(require("react")),m=require("react-dom/client"),u=require("./useMockServerDevice");Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const a={id:"device-1",device_type:"stax"};describe("useMockServerDevice",()=>{let t,i,c;const s=({client:e,deviceId:r})=>(c=(0,u.useMockServerDevice)(e,r),null),o=e=>({getDevice:vi.fn(e)}),l=(e,r="device-1")=>(0,n.act)(()=>i.render(n.default.createElement(s,{client:e,deviceId:r}))),d=()=>(0,n.act)(()=>vi.advanceTimersByTimeAsync(0));beforeEach(()=>{vi.useFakeTimers(),c=void 0,t=document.createElement("div"),i=(0,m.createRoot)(t)}),afterEach(()=>{(0,n.act)(()=>i.unmount()),vi.useRealTimers()}),it("reads the device record",async()=>{const e=o(()=>Promise.resolve(a));l(e),expect(c).toBeUndefined(),await d(),expect(c).toBe(a),expect(e.getDevice).toHaveBeenCalledWith("device-1")}),it("reads nothing without a device id",async()=>{const e=o(()=>Promise.resolve(a));l(e,""),await d(),expect(c).toBeUndefined(),expect(e.getDevice).not.toHaveBeenCalled()}),it("retries a failed read until it succeeds",async()=>{const e=vi.fn().mockRejectedValueOnce(new Error("offline")).mockResolvedValue(a),r=o(e);l(r),await d(),expect(c).toBeNull(),await(0,n.act)(()=>vi.advanceTimersByTimeAsync(2e3)),expect(c).toBe(a),expect(e).toHaveBeenCalledTimes(2)}),it("stops retrying once unmounted",async()=>{const e=o(()=>Promise.reject(new Error("offline")));l(e),await d(),(0,n.act)(()=>i.render(n.default.createElement(n.default.Fragment,null))),await(0,n.act)(()=>vi.advanceTimersByTimeAsync(1e4)),expect(e.getDevice).toHaveBeenCalledOnce()})});
|
|
2
|
+
//# sourceMappingURL=useMockServerDevice.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/useMockServerDevice.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport {\n type Device,\n type MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\n\nimport { useMockServerDevice } from \"./useMockServerDevice\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst DEVICE = { id: \"device-1\", device_type: \"stax\" } as Device;\n\ndescribe(\"useMockServerDevice\", () => {\n let container: HTMLDivElement;\n let root: Root;\n let result: Device | null | undefined;\n\n const Probe: React.FC<{ client: MockClient; deviceId: string }> = ({\n client,\n deviceId,\n }) => {\n result = useMockServerDevice(client, deviceId);\n return null;\n };\n const aClient = (getDevice: () => Promise<Device>) =>\n ({ getDevice: vi.fn(getDevice) }) as unknown as MockClient;\n const render = (client: MockClient, deviceId = \"device-1\") =>\n act(() => root.render(<Probe client={client} deviceId={deviceId} />));\n const flush = () => act(() => vi.advanceTimersByTimeAsync(0));\n\n beforeEach(() => {\n vi.useFakeTimers();\n result = undefined;\n container = document.createElement(\"div\");\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n vi.useRealTimers();\n });\n\n it(\"reads the device record\", async () => {\n const client = aClient(() => Promise.resolve(DEVICE));\n\n render(client);\n expect(result).toBeUndefined();\n await flush();\n\n expect(result).toBe(DEVICE);\n expect(client.getDevice).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"reads nothing without a device id\", async () => {\n const client = aClient(() => Promise.resolve(DEVICE));\n\n render(client, \"\");\n await flush();\n\n expect(result).toBeUndefined();\n expect(client.getDevice).not.toHaveBeenCalled();\n });\n\n it(\"retries a failed read until it succeeds\", async () => {\n const getDevice = vi\n .fn<() => Promise<Device>>()\n .mockRejectedValueOnce(new Error(\"offline\"))\n .mockResolvedValue(DEVICE);\n const client = aClient(getDevice);\n\n render(client);\n await flush();\n expect(result).toBeNull();\n\n await act(() => vi.advanceTimersByTimeAsync(2000));\n\n expect(result).toBe(DEVICE);\n expect(getDevice).toHaveBeenCalledTimes(2);\n });\n\n it(\"stops retrying once unmounted\", async () => {\n const client = aClient(() => Promise.reject(new Error(\"offline\")));\n\n render(client);\n await flush();\n act(() => root.render(<></>));\n await act(() => vi.advanceTimersByTimeAsync(10_000));\n\n expect(client.getDevice).toHaveBeenCalledOnce();\n });\n});\n"],
|
|
5
|
+
"mappings": "wdAAA,IAAAA,EAA2B,oBAC3BC,EAAsC,4BAMtCC,EAAoC,iCAEpC,OAAO,OAAO,WAAY,CAAE,yBAA0B,EAAK,CAAC,EAE5D,MAAMC,EAAS,CAAE,GAAI,WAAY,YAAa,MAAO,EAErD,SAAS,sBAAuB,IAAM,CACpC,IAAIC,EACAC,EACAC,EAEJ,MAAMC,EAA4D,CAAC,CACjE,OAAAC,EACA,SAAAC,CACF,KACEH,KAAS,uBAAoBE,EAAQC,CAAQ,EACtC,MAEHC,EAAWC,IACd,CAAE,UAAW,GAAG,GAAGA,CAAS,CAAE,GAC3BC,EAAS,CAACJ,EAAoBC,EAAW,gBAC7C,OAAI,IAAMJ,EAAK,OAAO,EAAAQ,QAAA,cAACN,EAAA,CAAM,OAAQC,EAAQ,SAAUC,EAAU,CAAE,CAAC,EAChEK,EAAQ,OAAM,OAAI,IAAM,GAAG,yBAAyB,CAAC,CAAC,EAE5D,WAAW,IAAM,CACf,GAAG,cAAc,EACjBR,EAAS,OACTF,EAAY,SAAS,cAAc,KAAK,EACxCC,KAAO,cAAWD,CAAS,CAC7B,CAAC,EAED,UAAU,IAAM,IACd,OAAI,IAAMC,EAAK,QAAQ,CAAC,EACxB,GAAG,cAAc,CACnB,CAAC,EAED,GAAG,0BAA2B,SAAY,CACxC,MAAMG,EAASE,EAAQ,IAAM,QAAQ,QAAQP,CAAM,CAAC,EAEpDS,EAAOJ,CAAM,EACb,OAAOF,CAAM,EAAE,cAAc,EAC7B,MAAMQ,EAAM,EAEZ,OAAOR,CAAM,EAAE,KAAKH,CAAM,EAC1B,OAAOK,EAAO,SAAS,EAAE,qBAAqB,UAAU,CAC1D,CAAC,EAED,GAAG,oCAAqC,SAAY,CAClD,MAAMA,EAASE,EAAQ,IAAM,QAAQ,QAAQP,CAAM,CAAC,EAEpDS,EAAOJ,EAAQ,EAAE,EACjB,MAAMM,EAAM,EAEZ,OAAOR,CAAM,EAAE,cAAc,EAC7B,OAAOE,EAAO,SAAS,EAAE,IAAI,iBAAiB,CAChD,CAAC,EAED,GAAG,0CAA2C,SAAY,CACxD,MAAMG,EAAY,GACf,GAA0B,EAC1B,sBAAsB,IAAI,MAAM,SAAS,CAAC,EAC1C,kBAAkBR,CAAM,EACrBK,EAASE,EAAQC,CAAS,EAEhCC,EAAOJ,CAAM,EACb,MAAMM,EAAM,EACZ,OAAOR,CAAM,EAAE,SAAS,EAExB,QAAM,OAAI,IAAM,GAAG,yBAAyB,GAAI,CAAC,EAEjD,OAAOA,CAAM,EAAE,KAAKH,CAAM,EAC1B,OAAOQ,CAAS,EAAE,sBAAsB,CAAC,CAC3C,CAAC,EAED,GAAG,gCAAiC,SAAY,CAC9C,MAAMH,EAASE,EAAQ,IAAM,QAAQ,OAAO,IAAI,MAAM,SAAS,CAAC,CAAC,EAEjEE,EAAOJ,CAAM,EACb,MAAMM,EAAM,KACZ,OAAI,IAAMT,EAAK,OAAO,EAAAQ,QAAA,gBAAAA,QAAA,aAAE,CAAG,CAAC,EAC5B,QAAM,OAAI,IAAM,GAAG,yBAAyB,GAAM,CAAC,EAEnD,OAAOL,EAAO,SAAS,EAAE,qBAAqB,CAChD,CAAC,CACH,CAAC",
|
|
6
|
+
"names": ["import_react", "import_client", "import_useMockServerDevice", "DEVICE", "container", "root", "result", "Probe", "client", "deviceId", "aClient", "getDevice", "render", "React", "flush"]
|
|
7
|
+
}
|
package/lib/esm/package.json
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import i,{act as r}from"react";import{createRoot as m}from"react-dom/client";import{findDeviceScreenModel as p}from"./deviceModels";import{DeviceOsInfo as s}from"./DeviceOsInfo";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const a=t=>({id:"device-1",device_type:"stax",apps:t}),c=[{name:"BOLOS",version:"1.10.1"},{name:"Bitcoin",version:"2.5.0"},{name:"Ethereum",version:"1.22.3"}];describe("DeviceOsInfo",()=>{let t,l;const e=n=>t.querySelector(`[data-testid="${n}"]`),o=n=>r(()=>l.render(n)),d=async n=>r(async()=>{e(n).click(),await Promise.resolve()});beforeEach(()=>{vi.useFakeTimers(),t=document.createElement("div"),document.body.append(t),l=m(t)}),afterEach(()=>{r(()=>l.unmount()),t.remove(),vi.useRealTimers()}),it("lists the installed apps, without the OS",()=>{o(i.createElement(s,{device:a(c),model:p("stax")})),expect(e("button_open-app-Bitcoin")).not.toBeNull(),expect(e("button_open-app-Ethereum")).not.toBeNull(),expect(e("button_open-app-BOLOS")).toBeNull()}),it("says when no app is installed",()=>{o(i.createElement(s,{device:a([c[0]]),model:p("nanoX")})),expect(e("list_device-apps")?.textContent).toBe("No apps installed")}),it("cannot open apps without an emulator",()=>{o(i.createElement(s,{device:a(c),model:p("nanoS")})),expect(e("button_open-app-Bitcoin").disabled).toBe(!0)}),it("shows the app being opened",async()=>{const n=vi.fn(()=>new Promise(()=>{}));o(i.createElement(s,{device:a(c),model:p("stax"),onOpenApp:n})),await d("button_open-app-Bitcoin"),expect(n).toHaveBeenCalledWith("Bitcoin"),expect(e("spinner_opening-app-screen")?.textContent).toContain("Opening Bitcoin")}),it("shows why an app could not be opened",async()=>{o(i.createElement(s,{device:a(c),model:p("stax"),onOpenApp:()=>Promise.reject(new Error("Could not open (6d00)"))})),await d("button_open-app-Bitcoin"),expect(e("spinner_opening-app-screen")).toBeNull(),expect(t.textContent).toContain("Could not open (6d00)")}),it("gives up when an opened app never shows its screen",async()=>{o(i.createElement(s,{device:a(c),model:p("stax"),onOpenApp:()=>Promise.resolve()})),await d("button_open-app-Ethereum"),r(()=>{vi.advanceTimersByTime(1e4)}),expect(t.textContent).toContain("Ethereum opened but its screen never showed up")}),it("falls back to the app's initial when its icon is missing",()=>{o(i.createElement(s,{device:a(c),model:p("stax")})),r(()=>{e("button_open-app-Bitcoin").querySelector("img").dispatchEvent(new Event("error"))}),expect(e("button_open-app-Bitcoin")?.querySelector("img")).toBeNull(),expect(e("button_open-app-Bitcoin")?.textContent).toBe("BBitcoin")})});
|
|
2
|
+
//# sourceMappingURL=DeviceOsInfo.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/DeviceOsInfo.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\n\nimport { findDeviceScreenModel } from \"./deviceModels\";\nimport { DeviceOsInfo } from \"./DeviceOsInfo\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst aDevice = (apps: { name: string; version: string }[]) =>\n ({ id: \"device-1\", device_type: \"stax\", apps }) as unknown as Device;\n\nconst APPS = [\n { name: \"BOLOS\", version: \"1.10.1\" },\n { name: \"Bitcoin\", version: \"2.5.0\" },\n { name: \"Ethereum\", version: \"1.22.3\" },\n];\n\ndescribe(\"DeviceOsInfo\", () => {\n let container: HTMLDivElement;\n let root: Root;\n\n const query = (testId: string) =>\n container.querySelector<HTMLElement>(`[data-testid=\"${testId}\"]`);\n const render = (\n element: React.ReactElement<React.ComponentProps<typeof DeviceOsInfo>>,\n ) => act(() => root.render(element));\n const click = async (testId: string) =>\n act(async () => {\n query(testId)!.click();\n await Promise.resolve();\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n it(\"lists the installed apps, without the OS\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n />,\n );\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n expect(query(\"button_open-app-Ethereum\")).not.toBeNull();\n expect(query(\"button_open-app-BOLOS\")).toBeNull();\n });\n\n it(\"says when no app is installed\", () => {\n render(\n <DeviceOsInfo\n device={aDevice([APPS[0]!])}\n model={findDeviceScreenModel(\"nanoX\")}\n />,\n );\n\n expect(query(\"list_device-apps\")?.textContent).toBe(\"No apps installed\");\n });\n\n it(\"cannot open apps without an emulator\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"nanoS\")}\n />,\n );\n\n expect(\n (query(\"button_open-app-Bitcoin\") as HTMLButtonElement).disabled,\n ).toBe(true);\n });\n\n it(\"shows the app being opened\", async () => {\n const onOpenApp = vi.fn(() => new Promise<void>(() => {}));\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={onOpenApp}\n />,\n );\n\n await click(\"button_open-app-Bitcoin\");\n\n expect(onOpenApp).toHaveBeenCalledWith(\"Bitcoin\");\n expect(query(\"spinner_opening-app-screen\")?.textContent).toContain(\n \"Opening Bitcoin\",\n );\n });\n\n it(\"shows why an app could not be opened\", async () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={() => Promise.reject(new Error(\"Could not open (6d00)\"))}\n />,\n );\n\n await click(\"button_open-app-Bitcoin\");\n\n expect(query(\"spinner_opening-app-screen\")).toBeNull();\n expect(container.textContent).toContain(\"Could not open (6d00)\");\n });\n\n it(\"gives up when an opened app never shows its screen\", async () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n onOpenApp={() => Promise.resolve()}\n />,\n );\n\n await click(\"button_open-app-Ethereum\");\n act(() => {\n vi.advanceTimersByTime(10_000);\n });\n\n expect(container.textContent).toContain(\n \"Ethereum opened but its screen never showed up\",\n );\n });\n\n it(\"falls back to the app's initial when its icon is missing\", () => {\n render(\n <DeviceOsInfo\n device={aDevice(APPS)}\n model={findDeviceScreenModel(\"stax\")}\n />,\n );\n\n act(() => {\n query(\"button_open-app-Bitcoin\")!\n .querySelector(\"img\")!\n .dispatchEvent(new Event(\"error\"));\n });\n\n expect(query(\"button_open-app-Bitcoin\")?.querySelector(\"img\")).toBeNull();\n expect(query(\"button_open-app-Bitcoin\")?.textContent).toBe(\"BBitcoin\");\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["React", "act", "createRoot", "findDeviceScreenModel", "DeviceOsInfo", "aDevice", "apps", "APPS", "container", "root", "query", "testId", "render", "element", "click", "onOpenApp"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import d,{act as l}from"react";import{createRoot as u}from"react-dom/client";import{DeviceScreen as v}from"./DeviceScreen";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const m={id:"device-1",device_type:"stax",apps:[{name:"Bitcoin",version:"2.5.0"}]},s=(a={})=>({screenshot:vi.fn(()=>Promise.resolve(null)),idle:vi.fn(()=>Promise.resolve({kind:"os-info",device:m})),pressButton:vi.fn(()=>Promise.resolve()),touch:vi.fn(()=>Promise.resolve()),openApp:vi.fn(()=>Promise.resolve()),...a});describe("DeviceScreen",()=>{let a,p;const n=e=>a.querySelector(`[data-testid="${e}"]`),o=e=>l(()=>p.render(d.createElement(v,{deviceType:"stax",...e}))),t=(e=0)=>l(()=>vi.advanceTimersByTimeAsync(e)),i=(e,r,c={})=>l(()=>{e.dispatchEvent(new MouseEvent(r,{bubbles:!0,button:0,...c}))});beforeEach(()=>{vi.useFakeTimers(),URL.createObjectURL=vi.fn(()=>"blob:frame"),URL.revokeObjectURL=vi.fn(),HTMLElement.prototype.setPointerCapture=vi.fn(),HTMLElement.prototype.releasePointerCapture=vi.fn(),a=document.createElement("div"),document.body.append(a),p=u(a)}),afterEach(()=>{l(()=>p.unmount()),a.remove(),vi.useRealTimers()}),describe("inline",()=>{it("shows a placeholder until the screen answers",()=>{o({api:s({screenshot:()=>new Promise(()=>{})})}),expect(n("placeholder_device-screen-loading")).not.toBeNull()}),it("shows the live screen of a running app",async()=>{o({api:s({screenshot:()=>Promise.resolve(new Blob(["png"]))})}),await t(),expect(n("image_device-screen")?.getAttribute("src")).toBe("blob:frame")}),it("shows the installed apps while no app runs",async()=>{o({api:s()}),await t(),expect(n("button_open-app-Bitcoin")).not.toBeNull()}),it("says there is no screen when the api has nothing to show instead",async()=>{o({api:s({idle:void 0})}),await t(),expect(a.textContent).toContain("No screen to capture")}),it("shows why the screen is unavailable",async()=>{o({api:s({screenshot:()=>Promise.reject(new Error("offline"))})}),await t(),expect(a.textContent).toContain("Screen unavailable"),expect(a.textContent).toContain("offline")}),it("keeps the last frame through a few failures while live",async()=>{const e=vi.fn().mockResolvedValueOnce(new Blob(["png"])).mockRejectedValue(new Error("app quitting")),r=()=>e.mock.calls.length-1;for(o({api:s({screenshot:e})}),await t();r()<3;)await t(500);for(expect(n("image_device-screen")).not.toBeNull();r()<4;)await t(500);expect(n("image_device-screen")).toBeNull(),expect(a.textContent).toContain("app quitting")}),it("drives a Nano with its buttons and refreshes after",async()=>{const e=s({screenshot:vi.fn(()=>Promise.resolve(new Blob(["png"])))});o({api:e,deviceType:"nanoX"}),await t(),i(n("button_device-screen-left"),"pointerdown"),i(n("button_device-screen-left"),"pointerup"),await t(),expect(e.pressButton).toHaveBeenCalledWith("left","press-and-release"),expect(e.screenshot).toHaveBeenCalledTimes(2)}),it("drives a touch device by its screen, without buttons",async()=>{o({api:s({screenshot:()=>Promise.resolve(new Blob(["png"]))})}),await t(),expect(n("image_device-screen")).not.toBeNull(),expect(n("button_device-screen-left")).toBeNull()})}),describe("floating",()=>{const e=()=>n("container_device-screen"),r=()=>e().firstElementChild;it("titles the window with the model and firmware",async()=>{o({api:s(),floating:!0,firmwareVersion:"1.10.1"}),await t(),expect(e().textContent).toContain("Stax - 1.10.1"),expect(e().style.width).toBe("224px")}),it("stops polling while collapsed",async()=>{const c=s();o({api:c,floating:!0,defaultCollapsed:!0}),await t(5e3),expect(c.screenshot).not.toHaveBeenCalled(),expect(n("button_open-app-Bitcoin")).toBeNull(),l(()=>n("button_toggle-device-screen").click()),await t(),expect(c.screenshot).toHaveBeenCalled(),expect(n("button_open-app-Bitcoin")).not.toBeNull()}),it("is dragged by its header",async()=>{o({api:s(),floating:!0}),await t(),i(r(),"pointerdown",{clientX:10,clientY:10}),i(r(),"pointermove",{clientX:110,clientY:60}),i(r(),"pointerup"),i(r(),"pointermove",{clientX:500,clientY:500}),expect(e().style.left).toBe("100px"),expect(e().style.top).toBe("50px")}),it("is not dragged from its button or with another mouse button",async()=>{o({api:s(),floating:!0}),await t(),i(n("button_toggle-device-screen"),"pointerdown"),i(r(),"pointerdown",{button:2}),i(r(),"pointermove",{clientX:110,clientY:60}),expect(e().style.left).toBe("")}),it("is resized from its edge, down to a minimum width",async()=>{o({api:s(),floating:!0}),await t(),e().getBoundingClientRect=()=>({left:100,top:80,width:224,height:300});const c=n("handle_resize-device-screen");i(c,"pointerdown",{clientX:324}),i(c,"pointermove",{clientX:424}),expect(e().style.width).toBe("324px"),expect(e().style.left).toBe("100px"),i(c,"pointermove",{clientX:0}),i(c,"pointerup"),i(c,"pointermove",{clientX:600}),expect(e().style.width).toBe("200px")})})});
|
|
2
|
+
//# sourceMappingURL=DeviceScreen.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/DeviceScreen.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nimport { type ScreenApi } from \"./types\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst DEVICE = {\n id: \"device-1\",\n device_type: \"stax\",\n apps: [{ name: \"Bitcoin\", version: \"2.5.0\" }],\n} as unknown as Device;\n\nconst anApi = (overrides: Partial<ScreenApi> = {}): ScreenApi => ({\n screenshot: vi.fn(() => Promise.resolve(null)),\n idle: vi.fn(() =>\n Promise.resolve({ kind: \"os-info\" as const, device: DEVICE }),\n ),\n pressButton: vi.fn(() => Promise.resolve()),\n touch: vi.fn(() => Promise.resolve()),\n openApp: vi.fn(() => Promise.resolve()),\n ...overrides,\n});\n\ndescribe(\"DeviceScreen\", () => {\n let container: HTMLDivElement;\n let root: Root;\n\n const query = (testId: string) =>\n container.querySelector<HTMLElement>(`[data-testid=\"${testId}\"]`);\n const render = (props: Partial<DeviceScreenProps> & { api: ScreenApi }) =>\n act(() => root.render(<DeviceScreen deviceType=\"stax\" {...props} />));\n const wait = (ms = 0) => act(() => vi.advanceTimersByTimeAsync(ms));\n const pointer = (target: Element, type: string, init: MouseEventInit = {}) =>\n act(() => {\n target.dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n URL.createObjectURL = vi.fn(() => \"blob:frame\");\n URL.revokeObjectURL = vi.fn();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n HTMLElement.prototype.releasePointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n describe(\"inline\", () => {\n it(\"shows a placeholder until the screen answers\", () => {\n render({ api: anApi({ screenshot: () => new Promise(() => {}) }) });\n\n expect(query(\"placeholder_device-screen-loading\")).not.toBeNull();\n });\n\n it(\"shows the live screen of a running app\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.resolve(new Blob([\"png\"])) }),\n });\n await wait();\n\n expect(query(\"image_device-screen\")?.getAttribute(\"src\")).toBe(\n \"blob:frame\",\n );\n });\n\n it(\"shows the installed apps while no app runs\", async () => {\n render({ api: anApi() });\n await wait();\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n });\n\n it(\"says there is no screen when the api has nothing to show instead\", async () => {\n render({ api: anApi({ idle: undefined }) });\n await wait();\n\n expect(container.textContent).toContain(\"No screen to capture\");\n });\n\n it(\"shows why the screen is unavailable\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.reject(new Error(\"offline\")) }),\n });\n await wait();\n\n expect(container.textContent).toContain(\"Screen unavailable\");\n expect(container.textContent).toContain(\"offline\");\n });\n\n it(\"keeps the last frame through a few failures while live\", async () => {\n const screenshot = vi\n .fn<() => Promise<Blob | null>>()\n .mockResolvedValueOnce(new Blob([\"png\"]))\n .mockRejectedValue(new Error(\"app quitting\"));\n const failures = () => screenshot.mock.calls.length - 1;\n render({ api: anApi({ screenshot }) });\n await wait();\n\n while (failures() < 3) await wait(500);\n expect(query(\"image_device-screen\")).not.toBeNull();\n\n while (failures() < 4) await wait(500);\n expect(query(\"image_device-screen\")).toBeNull();\n expect(container.textContent).toContain(\"app quitting\");\n });\n\n it(\"drives a Nano with its buttons and refreshes after\", async () => {\n const api = anApi({\n screenshot: vi.fn(() => Promise.resolve(new Blob([\"png\"]))),\n });\n render({ api, deviceType: \"nanoX\" });\n await wait();\n\n pointer(query(\"button_device-screen-left\")!, \"pointerdown\");\n pointer(query(\"button_device-screen-left\")!, \"pointerup\");\n await wait();\n\n expect(api.pressButton).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n expect(api.screenshot).toHaveBeenCalledTimes(2);\n });\n\n it(\"drives a touch device by its screen, without buttons\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.resolve(new Blob([\"png\"])) }),\n });\n await wait();\n\n expect(query(\"image_device-screen\")).not.toBeNull();\n expect(query(\"button_device-screen-left\")).toBeNull();\n });\n });\n\n describe(\"floating\", () => {\n const windowElement = () => query(\"container_device-screen\")!;\n const header = () => windowElement().firstElementChild!;\n\n it(\"titles the window with the model and firmware\", async () => {\n render({ api: anApi(), floating: true, firmwareVersion: \"1.10.1\" });\n await wait();\n\n expect(windowElement().textContent).toContain(\"Stax - 1.10.1\");\n expect(windowElement().style.width).toBe(\"224px\");\n });\n\n it(\"stops polling while collapsed\", async () => {\n const api = anApi();\n render({ api, floating: true, defaultCollapsed: true });\n await wait(5000);\n\n expect(api.screenshot).not.toHaveBeenCalled();\n expect(query(\"button_open-app-Bitcoin\")).toBeNull();\n\n act(() => query(\"button_toggle-device-screen\")!.click());\n await wait();\n\n expect(api.screenshot).toHaveBeenCalled();\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n });\n\n it(\"is dragged by its header\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n pointer(header(), \"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(header(), \"pointermove\", { clientX: 110, clientY: 60 });\n pointer(header(), \"pointerup\");\n pointer(header(), \"pointermove\", { clientX: 500, clientY: 500 });\n\n expect(windowElement().style.left).toBe(\"100px\");\n expect(windowElement().style.top).toBe(\"50px\");\n });\n\n it(\"is not dragged from its button or with another mouse button\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n pointer(query(\"button_toggle-device-screen\")!, \"pointerdown\");\n pointer(header(), \"pointerdown\", { button: 2 });\n pointer(header(), \"pointermove\", { clientX: 110, clientY: 60 });\n\n expect(windowElement().style.left).toBe(\"\");\n });\n\n it(\"is resized from its edge, down to a minimum width\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n windowElement().getBoundingClientRect = () =>\n ({ left: 100, top: 80, width: 224, height: 300 }) as DOMRect;\n const handle = query(\"handle_resize-device-screen\")!;\n\n pointer(handle, \"pointerdown\", { clientX: 324 });\n pointer(handle, \"pointermove\", { clientX: 424 });\n expect(windowElement().style.width).toBe(\"324px\");\n expect(windowElement().style.left).toBe(\"100px\");\n\n pointer(handle, \"pointermove\", { clientX: 0 });\n pointer(handle, \"pointerup\");\n pointer(handle, \"pointermove\", { clientX: 600 });\n expect(windowElement().style.width).toBe(\"200px\");\n });\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["React", "act", "createRoot", "DeviceScreen", "DEVICE", "anApi", "overrides", "container", "root", "query", "testId", "render", "props", "wait", "ms", "pointer", "target", "type", "init", "screenshot", "failures", "api", "windowElement", "header", "handle"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import p,{act as o}from"react";import{createRoot as c}from"react-dom/client";import{DeviceScreenButtons as u}from"./DeviceScreenButtons";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});describe("DeviceScreenButtons",()=>{let r,i;const t=vi.fn(),e=s=>r.querySelector(`[data-testid="button_device-screen-${s}"]`),n=(s,l,d={})=>o(()=>{s.dispatchEvent(new MouseEvent(l,{bubbles:!0,button:0,...d}))}),a=(s,l,d)=>o(()=>{s.dispatchEvent(new KeyboardEvent(l,{bubbles:!0,key:d}))});beforeEach(()=>{vi.useFakeTimers(),t.mockReset(),HTMLElement.prototype.setPointerCapture=vi.fn(),r=document.createElement("div"),document.body.append(r),i=c(r),o(()=>i.render(p.createElement(u,{onPress:t})))}),afterEach(()=>{o(()=>i.unmount()),r.remove(),vi.useRealTimers()}),it("sends a quick click as one press-and-release",()=>{n(e("left"),"pointerdown"),n(e("left"),"pointerup"),expect(t).toHaveBeenCalledOnce(),expect(t).toHaveBeenCalledWith("left","press-and-release")}),it("sends a hold as a press then a release",()=>{n(e("both"),"pointerdown"),o(()=>{vi.advanceTimersByTime(200)}),n(e("both"),"pointerup"),expect(t.mock.calls).toEqual([["both","press"],["both","release"]])}),it("ignores buttons other than the main one",()=>{n(e("right"),"pointerdown",{button:2}),n(e("right"),"pointerup",{button:2}),expect(t).not.toHaveBeenCalled()}),it("handles one button at a time",()=>{n(e("left"),"pointerdown"),n(e("right"),"pointerdown"),n(e("left"),"pointerup"),expect(t).toHaveBeenCalledWith("left","press-and-release"),expect(t).toHaveBeenCalledOnce()}),it("drives the buttons from the keyboard",()=>{a(e("right"),"keydown","Enter"),a(e("right"),"keyup","Enter"),a(e("right"),"keydown","a"),expect(t).toHaveBeenCalledOnce(),expect(t).toHaveBeenCalledWith("right","press-and-release")}),it("releases a held button when unmounted",()=>{n(e("left"),"pointerdown"),o(()=>{vi.advanceTimersByTime(200)}),o(()=>i.render(p.createElement(p.Fragment,null))),expect(t).toHaveBeenLastCalledWith("left","release")})});
|
|
2
|
+
//# sourceMappingURL=DeviceScreenButtons.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/DeviceScreenButtons.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\nimport { DeviceScreenButtons } from \"./DeviceScreenButtons\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\ndescribe(\"DeviceScreenButtons\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const onPress = vi.fn();\n\n const button = (name: string) =>\n container.querySelector<HTMLButtonElement>(\n `[data-testid=\"button_device-screen-${name}\"]`,\n )!;\n const pointer = (target: Element, type: string, init: MouseEventInit = {}) =>\n act(() => {\n target.dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n const key = (target: Element, type: string, value: string) =>\n act(() => {\n target.dispatchEvent(\n new KeyboardEvent(type, { bubbles: true, key: value }),\n );\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n onPress.mockReset();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n act(() => root.render(<DeviceScreenButtons onPress={onPress} />));\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n it(\"sends a quick click as one press-and-release\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n pointer(button(\"left\"), \"pointerup\");\n\n expect(onPress).toHaveBeenCalledOnce();\n expect(onPress).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n });\n\n it(\"sends a hold as a press then a release\", () => {\n pointer(button(\"both\"), \"pointerdown\");\n act(() => {\n vi.advanceTimersByTime(200);\n });\n pointer(button(\"both\"), \"pointerup\");\n\n expect(onPress.mock.calls).toEqual([\n [\"both\", \"press\"],\n [\"both\", \"release\"],\n ]);\n });\n\n it(\"ignores buttons other than the main one\", () => {\n pointer(button(\"right\"), \"pointerdown\", { button: 2 });\n pointer(button(\"right\"), \"pointerup\", { button: 2 });\n\n expect(onPress).not.toHaveBeenCalled();\n });\n\n it(\"handles one button at a time\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n pointer(button(\"right\"), \"pointerdown\");\n pointer(button(\"left\"), \"pointerup\");\n\n expect(onPress).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n expect(onPress).toHaveBeenCalledOnce();\n });\n\n it(\"drives the buttons from the keyboard\", () => {\n key(button(\"right\"), \"keydown\", \"Enter\");\n key(button(\"right\"), \"keyup\", \"Enter\");\n key(button(\"right\"), \"keydown\", \"a\");\n\n expect(onPress).toHaveBeenCalledOnce();\n expect(onPress).toHaveBeenCalledWith(\"right\", \"press-and-release\");\n });\n\n it(\"releases a held button when unmounted\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n act(() => {\n vi.advanceTimersByTime(200);\n });\n act(() => root.render(<></>));\n\n expect(onPress).toHaveBeenLastCalledWith(\"left\", \"release\");\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["React", "act", "createRoot", "DeviceScreenButtons", "container", "root", "onPress", "button", "name", "pointer", "target", "type", "init", "key", "value"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import s,{act as r}from"react";import{createRoot as p}from"react-dom/client";import{DeviceScreenImage as u}from"./DeviceScreenImage";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});describe("DeviceScreenImage",()=>{let n,c;const e=vi.fn(),i=()=>n.querySelector('[data-testid="image_device-screen"]'),o=l=>r(()=>c.render(s.createElement(u,{src:"blob:1",onTouch:l}))),t=(l,d={})=>r(()=>{i().dispatchEvent(new MouseEvent(l,{bubbles:!0,button:0,...d}))}),a=()=>{Object.defineProperty(i(),"naturalWidth",{value:400}),Object.defineProperty(i(),"naturalHeight",{value:600}),i().getBoundingClientRect=()=>({left:0,top:0,width:200,height:300})};beforeEach(()=>{e.mockReset(),HTMLElement.prototype.setPointerCapture=vi.fn(),HTMLElement.prototype.releasePointerCapture=vi.fn(),n=document.createElement("div"),document.body.append(n),c=p(n)}),afterEach(()=>{r(()=>c.unmount()),n.remove()}),it("maps a touch to device pixels and releases it where it went down",()=>{o(e),a(),t("pointerdown",{clientX:50,clientY:150}),t("pointerup",{clientX:190,clientY:10}),expect(e.mock.calls).toEqual([[100,300,"press"],[100,300,"release"]])}),it("clamps a touch on the far edge to the last pixel",()=>{o(e),a(),t("pointerdown",{clientX:200,clientY:300}),expect(e).toHaveBeenCalledWith(399,599,"press")}),it("ignores touches before the image has a size",()=>{o(e),t("pointerdown",{clientX:10,clientY:10}),t("pointerup"),expect(e).not.toHaveBeenCalled()}),it("ignores touches on a button-driven device",()=>{o(),a(),t("pointerdown",{clientX:10,clientY:10}),expect(e).not.toHaveBeenCalled()}),it("releases a held touch when unmounted",()=>{o(e),a(),t("pointerdown",{clientX:50,clientY:150}),r(()=>c.render(s.createElement(s.Fragment,null))),expect(e).toHaveBeenLastCalledWith(100,300,"release")}),it("says so when the screenshot cannot be decoded",()=>{o(e),r(()=>{i().dispatchEvent(new Event("error"))}),expect(n.querySelector('[data-testid="text_device-screen-undecodable"]')).not.toBeNull()})});
|
|
2
|
+
//# sourceMappingURL=DeviceScreenImage.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/DeviceScreenImage.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\nimport { DeviceScreenImage } from \"./DeviceScreenImage\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\ndescribe(\"DeviceScreenImage\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const onTouch = vi.fn();\n\n const image = () =>\n container.querySelector<HTMLImageElement>(\n '[data-testid=\"image_device-screen\"]',\n )!;\n const render = (touch?: typeof onTouch) =>\n act(() => root.render(<DeviceScreenImage src=\"blob:1\" onTouch={touch} />));\n const pointer = (type: string, init: MouseEventInit = {}) =>\n act(() => {\n image().dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n /** A 400x600 screen drawn at 200x300 from the page's top left corner. */\n const sizeImage = () => {\n Object.defineProperty(image(), \"naturalWidth\", { value: 400 });\n Object.defineProperty(image(), \"naturalHeight\", { value: 600 });\n image().getBoundingClientRect = () =>\n ({ left: 0, top: 0, width: 200, height: 300 }) as DOMRect;\n };\n\n beforeEach(() => {\n onTouch.mockReset();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n HTMLElement.prototype.releasePointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n });\n\n it(\"maps a touch to device pixels and releases it where it went down\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n pointer(\"pointerup\", { clientX: 190, clientY: 10 });\n\n expect(onTouch.mock.calls).toEqual([\n [100, 300, \"press\"],\n [100, 300, \"release\"],\n ]);\n });\n\n it(\"clamps a touch on the far edge to the last pixel\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 200, clientY: 300 });\n\n expect(onTouch).toHaveBeenCalledWith(399, 599, \"press\");\n });\n\n it(\"ignores touches before the image has a size\", () => {\n render(onTouch);\n\n pointer(\"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(\"pointerup\");\n\n expect(onTouch).not.toHaveBeenCalled();\n });\n\n it(\"ignores touches on a button-driven device\", () => {\n render();\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 10, clientY: 10 });\n\n expect(onTouch).not.toHaveBeenCalled();\n });\n\n it(\"releases a held touch when unmounted\", () => {\n render(onTouch);\n sizeImage();\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n\n act(() => root.render(<></>));\n\n expect(onTouch).toHaveBeenLastCalledWith(100, 300, \"release\");\n });\n\n it(\"says so when the screenshot cannot be decoded\", () => {\n render(onTouch);\n\n act(() => {\n image().dispatchEvent(new Event(\"error\"));\n });\n\n expect(\n container.querySelector('[data-testid=\"text_device-screen-undecodable\"]'),\n ).not.toBeNull();\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["React", "act", "createRoot", "DeviceScreenImage", "container", "root", "onTouch", "image", "render", "touch", "pointer", "type", "init", "sizeImage"]
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/MockServerDevice.tsx"],
|
|
4
|
-
"sourcesContent": ["import React, { useMemo } from \"react\";\nimport { MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nimport { mockServerScreenApi } from \"./mockServerScreenApi\";\nimport { useMockServerDevice } from \"./useMockServerDevice\";\n\nexport type MockServerDeviceProps = Pick<\n DeviceScreenProps,\n \"floating\" | \"defaultCollapsed\"\n> & {\n url: string;\n /**
|
|
4
|
+
"sourcesContent": ["import React, { useMemo } from \"react\";\nimport { MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nimport { mockServerScreenApi } from \"./mockServerScreenApi\";\nimport { useMockServerDevice } from \"./useMockServerDevice\";\n\nexport type MockServerDeviceProps = Pick<\n DeviceScreenProps,\n \"floating\" | \"defaultCollapsed\"\n> & {\n url: string;\n /** Of the session owning the device: devices are scoped to their session. */\n token: string;\n deviceId: string;\n};\n\nexport const MockServerDevice: React.FC<MockServerDeviceProps> = ({\n url,\n token,\n deviceId,\n ...props\n}) => {\n const client = useMemo(() => new MockClient(url, { token }), [url, token]);\n const api = useMemo(\n () => mockServerScreenApi(client, deviceId),\n [client, deviceId],\n );\n const device = useMockServerDevice(client, deviceId);\n\n // Decides touch or buttons, so nothing renders until it is known. An\n // unreadable record still shows the screen, and its errors.\n if (device === undefined) return null;\n\n return (\n <DeviceScreen\n api={api}\n deviceType={device?.device_type ?? \"\"}\n firmwareVersion={device?.firmware_version}\n {...props}\n />\n );\n};\n"],
|
|
5
5
|
"mappings": "AAAA,OAAOA,GAAS,WAAAC,MAAe,QAC/B,OAAS,cAAAC,MAAkB,qCAE3B,OAAS,gBAAAC,MAA4C,iBACrD,OAAS,uBAAAC,MAA2B,wBACpC,OAAS,uBAAAC,MAA2B,wBAY7B,MAAMC,EAAoD,CAAC,CAChE,IAAAC,EACA,MAAAC,EACA,SAAAC,EACA,GAAGC,CACL,IAAM,CACJ,MAAMC,EAASV,EAAQ,IAAM,IAAIC,EAAWK,EAAK,CAAE,MAAAC,CAAM,CAAC,EAAG,CAACD,EAAKC,CAAK,CAAC,EACnEI,EAAMX,EACV,IAAMG,EAAoBO,EAAQF,CAAQ,EAC1C,CAACE,EAAQF,CAAQ,CACnB,EACMI,EAASR,EAAoBM,EAAQF,CAAQ,EAInD,OAAII,IAAW,OAAkB,KAG/Bb,EAAA,cAACG,EAAA,CACC,IAAKS,EACL,WAAYC,GAAQ,aAAe,GACnC,gBAAiBA,GAAQ,iBACxB,GAAGH,EACN,CAEJ",
|
|
6
6
|
"names": ["React", "useMemo", "MockClient", "DeviceScreen", "mockServerScreenApi", "useMockServerDevice", "MockServerDevice", "url", "token", "deviceId", "props", "client", "api", "device"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import r,{act as o}from"react";import{createRoot as s}from"react-dom/client";import{MockClient as c}from"@ledgerhq/device-mockserver-client";import{MockServerDevice as a}from"./MockServerDevice";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});vi.mock("@ledgerhq/device-mockserver-client",()=>({MockClient:vi.fn()}));describe("MockServerDevice",()=>{let e,n;const t=vi.fn();beforeEach(()=>{vi.useFakeTimers(),t.mockReset(),vi.mocked(c).mockImplementation(()=>({getDevice:t,getScreenshot:()=>new Promise(()=>{})})),e=document.createElement("div"),document.body.append(e),n=s(e)}),afterEach(()=>{o(()=>n.unmount()),e.remove(),vi.useRealTimers()});const i=()=>o(()=>n.render(r.createElement(a,{url:"http://mock",token:"session-token",deviceId:"device-1",floating:!0})));it("renders nothing until the device's model is known",()=>{t.mockReturnValue(new Promise(()=>{})),i(),expect(e.childElementCount).toBe(0)}),it("shows the device's screen in its session",async()=>{t.mockResolvedValue({id:"device-1",device_type:"flex",firmware_version:"1.6.1"}),i(),await o(()=>vi.advanceTimersByTimeAsync(0)),expect(c).toHaveBeenCalledWith("http://mock",{token:"session-token"}),expect(t).toHaveBeenCalledWith("device-1"),expect(e.textContent).toContain("Flex - 1.6.1")}),it("still shows the screen when the device cannot be read",async()=>{t.mockRejectedValue(new Error("offline")),i(),await o(()=>vi.advanceTimersByTimeAsync(0)),expect(e.querySelector('[data-testid="container_device-screen"]')).not.toBeNull()})});
|
|
2
|
+
//# sourceMappingURL=MockServerDevice.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/MockServerDevice.test.tsx"],
|
|
4
|
+
"sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { MockServerDevice } from \"./MockServerDevice\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nvi.mock(\"@ledgerhq/device-mockserver-client\", () => ({\n MockClient: vi.fn(),\n}));\n\ndescribe(\"MockServerDevice\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const getDevice = vi.fn();\n\n beforeEach(() => {\n vi.useFakeTimers();\n getDevice.mockReset();\n vi.mocked(MockClient).mockImplementation(\n () =>\n ({\n getDevice,\n getScreenshot: () => new Promise(() => {}),\n }) as unknown as MockClient,\n );\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n const render = () =>\n act(() =>\n root.render(\n <MockServerDevice\n url=\"http://mock\"\n token=\"session-token\"\n deviceId=\"device-1\"\n floating\n />,\n ),\n );\n\n it(\"renders nothing until the device's model is known\", () => {\n getDevice.mockReturnValue(new Promise(() => {}));\n\n render();\n\n expect(container.childElementCount).toBe(0);\n });\n\n it(\"shows the device's screen in its session\", async () => {\n getDevice.mockResolvedValue({\n id: \"device-1\",\n device_type: \"flex\",\n firmware_version: \"1.6.1\",\n });\n\n render();\n await act(() => vi.advanceTimersByTimeAsync(0));\n\n expect(MockClient).toHaveBeenCalledWith(\"http://mock\", {\n token: \"session-token\",\n });\n expect(getDevice).toHaveBeenCalledWith(\"device-1\");\n expect(container.textContent).toContain(\"Flex - 1.6.1\");\n });\n\n it(\"still shows the screen when the device cannot be read\", async () => {\n getDevice.mockRejectedValue(new Error(\"offline\"));\n\n render();\n await act(() => vi.advanceTimersByTimeAsync(0));\n\n expect(\n container.querySelector('[data-testid=\"container_device-screen\"]'),\n ).not.toBeNull();\n });\n});\n"],
|
|
5
|
+
"mappings": "AAAA,OAAOA,GAAS,OAAAC,MAAW,QAC3B,OAAS,cAAAC,MAA6B,mBACtC,OAAS,cAAAC,MAAkB,qCAE3B,OAAS,oBAAAC,MAAwB,qBAEjC,OAAO,OAAO,WAAY,CAAE,yBAA0B,EAAK,CAAC,EAE5D,GAAG,KAAK,qCAAsC,KAAO,CACnD,WAAY,GAAG,GAAG,CACpB,EAAE,EAEF,SAAS,mBAAoB,IAAM,CACjC,IAAIC,EACAC,EACJ,MAAMC,EAAY,GAAG,GAAG,EAExB,WAAW,IAAM,CACf,GAAG,cAAc,EACjBA,EAAU,UAAU,EACpB,GAAG,OAAOJ,CAAU,EAAE,mBACpB,KACG,CACC,UAAAI,EACA,cAAe,IAAM,IAAI,QAAQ,IAAM,CAAC,CAAC,CAC3C,EACJ,EACAF,EAAY,SAAS,cAAc,KAAK,EACxC,SAAS,KAAK,OAAOA,CAAS,EAC9BC,EAAOJ,EAAWG,CAAS,CAC7B,CAAC,EAED,UAAU,IAAM,CACdJ,EAAI,IAAMK,EAAK,QAAQ,CAAC,EACxBD,EAAU,OAAO,EACjB,GAAG,cAAc,CACnB,CAAC,EAED,MAAMG,EAAS,IACbP,EAAI,IACFK,EAAK,OACHN,EAAA,cAACI,EAAA,CACC,IAAI,cACJ,MAAM,gBACN,SAAS,WACT,SAAQ,GACV,CACF,CACF,EAEF,GAAG,oDAAqD,IAAM,CAC5DG,EAAU,gBAAgB,IAAI,QAAQ,IAAM,CAAC,CAAC,CAAC,EAE/CC,EAAO,EAEP,OAAOH,EAAU,iBAAiB,EAAE,KAAK,CAAC,CAC5C,CAAC,EAED,GAAG,2CAA4C,SAAY,CACzDE,EAAU,kBAAkB,CAC1B,GAAI,WACJ,YAAa,OACb,iBAAkB,OACpB,CAAC,EAEDC,EAAO,EACP,MAAMP,EAAI,IAAM,GAAG,yBAAyB,CAAC,CAAC,EAE9C,OAAOE,CAAU,EAAE,qBAAqB,cAAe,CACrD,MAAO,eACT,CAAC,EACD,OAAOI,CAAS,EAAE,qBAAqB,UAAU,EACjD,OAAOF,EAAU,WAAW,EAAE,UAAU,cAAc,CACxD,CAAC,EAED,GAAG,wDAAyD,SAAY,CACtEE,EAAU,kBAAkB,IAAI,MAAM,SAAS,CAAC,EAEhDC,EAAO,EACP,MAAMP,EAAI,IAAM,GAAG,yBAAyB,CAAC,CAAC,EAE9C,OACEI,EAAU,cAAc,yCAAyC,CACnE,EAAE,IAAI,SAAS,CACjB,CAAC,CACH,CAAC",
|
|
6
|
+
"names": ["React", "act", "createRoot", "MockClient", "MockServerDevice", "container", "root", "getDevice", "render"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{findDeviceScreenModel as e}from"./deviceModels";describe("findDeviceScreenModel",()=>{it("finds a model by the mock server's device type",()=>{expect(e("stax")).toMatchObject({label:"Stax",touch:!0,speculos:!0})}),it("marks Nano S as having no emulator",()=>{expect(e("nanoS").speculos).toBe(!1)}),it("falls back to a button-driven model for an unknown type",()=>{expect(e("unknown")).toMatchObject({label:"Device",touch:!1})})});
|
|
2
|
+
//# sourceMappingURL=deviceModels.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/deviceModels.test.ts"],
|
|
4
|
+
"sourcesContent": ["import { findDeviceScreenModel } from \"./deviceModels\";\n\ndescribe(\"findDeviceScreenModel\", () => {\n it(\"finds a model by the mock server's device type\", () => {\n expect(findDeviceScreenModel(\"stax\")).toMatchObject({\n label: \"Stax\",\n touch: true,\n speculos: true,\n });\n });\n\n it(\"marks Nano S as having no emulator\", () => {\n expect(findDeviceScreenModel(\"nanoS\").speculos).toBe(false);\n });\n\n it(\"falls back to a button-driven model for an unknown type\", () => {\n expect(findDeviceScreenModel(\"unknown\")).toMatchObject({\n label: \"Device\",\n touch: false,\n });\n });\n});\n"],
|
|
5
|
+
"mappings": "AAAA,OAAS,yBAAAA,MAA6B,iBAEtC,SAAS,wBAAyB,IAAM,CACtC,GAAG,iDAAkD,IAAM,CACzD,OAAOA,EAAsB,MAAM,CAAC,EAAE,cAAc,CAClD,MAAO,OACP,MAAO,GACP,SAAU,EACZ,CAAC,CACH,CAAC,EAED,GAAG,qCAAsC,IAAM,CAC7C,OAAOA,EAAsB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAK,CAC5D,CAAC,EAED,GAAG,0DAA2D,IAAM,CAClE,OAAOA,EAAsB,SAAS,CAAC,EAAE,cAAc,CACrD,MAAO,SACP,MAAO,EACT,CAAC,CACH,CAAC,CACH,CAAC",
|
|
6
|
+
"names": ["findDeviceScreenModel"]
|
|
7
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useEffect as
|
|
1
|
+
import{useEffect as l,useState as f}from"react";const s=2e3;function p(r,e){const[o,t]=f();return l(()=>{let n=!1,i;if(t(void 0),!e)return;const u=()=>{r.getDevice(e).then(c=>{n||t(c)},()=>{n||(t(null),i=setTimeout(u,s))})};return u(),()=>{n=!0,clearTimeout(i)}},[r,e]),o}export{p as useMockServerDevice};
|
|
2
2
|
//# sourceMappingURL=useMockServerDevice.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/useMockServerDevice.ts"],
|
|
4
|
-
"sourcesContent": ["import { useEffect, useState } from \"react\";\nimport {\n type Device,\n type MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\n\n/**\n * The mock server's record of a device: undefined while it loads, or while no\n * device id is given, and null
|
|
5
|
-
"mappings": "AAAA,OAAS,aAAAA,EAAW,YAAAC,MAAgB,
|
|
6
|
-
"names": ["useEffect", "useState", "useMockServerDevice", "client", "deviceId", "device", "setDevice", "cancelled", "record"]
|
|
4
|
+
"sourcesContent": ["import { useEffect, useState } from \"react\";\nimport {\n type Device,\n type MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\n\nconst RETRY_MS = 2000;\n\n/**\n * The mock server's record of a device: undefined while it loads, or while no\n * device id is given, and null while it cannot be read. A failed read is\n * retried, so a transient error does not leave the device without its model.\n */\nexport function useMockServerDevice(\n client: MockClient,\n deviceId: string,\n): Device | null | undefined {\n const [device, setDevice] = useState<Device | null>();\n\n useEffect(() => {\n let cancelled = false;\n let retry: ReturnType<typeof setTimeout> | undefined;\n setDevice(undefined);\n if (!deviceId) return;\n\n const load = () => {\n client.getDevice(deviceId).then(\n (record) => {\n if (!cancelled) setDevice(record);\n },\n () => {\n if (cancelled) return;\n setDevice(null);\n retry = setTimeout(load, RETRY_MS);\n },\n );\n };\n load();\n\n return () => {\n cancelled = true;\n clearTimeout(retry);\n };\n }, [client, deviceId]);\n\n return device;\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAS,aAAAA,EAAW,YAAAC,MAAgB,QAMpC,MAAMC,EAAW,IAOV,SAASC,EACdC,EACAC,EAC2B,CAC3B,KAAM,CAACC,EAAQC,CAAS,EAAIN,EAAwB,EAEpD,OAAAD,EAAU,IAAM,CACd,IAAIQ,EAAY,GACZC,EAEJ,GADAF,EAAU,MAAS,EACf,CAACF,EAAU,OAEf,MAAMK,EAAO,IAAM,CACjBN,EAAO,UAAUC,CAAQ,EAAE,KACxBM,GAAW,CACLH,GAAWD,EAAUI,CAAM,CAClC,EACA,IAAM,CACAH,IACJD,EAAU,IAAI,EACdE,EAAQ,WAAWC,EAAMR,CAAQ,EACnC,CACF,CACF,EACA,OAAAQ,EAAK,EAEE,IAAM,CACXF,EAAY,GACZ,aAAaC,CAAK,CACpB,CACF,EAAG,CAACL,EAAQC,CAAQ,CAAC,EAEdC,CACT",
|
|
6
|
+
"names": ["useEffect", "useState", "RETRY_MS", "useMockServerDevice", "client", "deviceId", "device", "setDevice", "cancelled", "retry", "load", "record"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import l,{act as i}from"react";import{createRoot as m}from"react-dom/client";import{useMockServerDevice as u}from"./useMockServerDevice";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const n={id:"device-1",device_type:"stax"};describe("useMockServerDevice",()=>{let d,o,t;const v=({client:e,deviceId:c})=>(t=u(e,c),null),r=e=>({getDevice:vi.fn(e)}),a=(e,c="device-1")=>i(()=>o.render(l.createElement(v,{client:e,deviceId:c}))),s=()=>i(()=>vi.advanceTimersByTimeAsync(0));beforeEach(()=>{vi.useFakeTimers(),t=void 0,d=document.createElement("div"),o=m(d)}),afterEach(()=>{i(()=>o.unmount()),vi.useRealTimers()}),it("reads the device record",async()=>{const e=r(()=>Promise.resolve(n));a(e),expect(t).toBeUndefined(),await s(),expect(t).toBe(n),expect(e.getDevice).toHaveBeenCalledWith("device-1")}),it("reads nothing without a device id",async()=>{const e=r(()=>Promise.resolve(n));a(e,""),await s(),expect(t).toBeUndefined(),expect(e.getDevice).not.toHaveBeenCalled()}),it("retries a failed read until it succeeds",async()=>{const e=vi.fn().mockRejectedValueOnce(new Error("offline")).mockResolvedValue(n),c=r(e);a(c),await s(),expect(t).toBeNull(),await i(()=>vi.advanceTimersByTimeAsync(2e3)),expect(t).toBe(n),expect(e).toHaveBeenCalledTimes(2)}),it("stops retrying once unmounted",async()=>{const e=r(()=>Promise.reject(new Error("offline")));a(e),await s(),i(()=>o.render(l.createElement(l.Fragment,null))),await i(()=>vi.advanceTimersByTimeAsync(1e4)),expect(e.getDevice).toHaveBeenCalledOnce()})});
|
|
2
|
+
//# sourceMappingURL=useMockServerDevice.test.js.map
|