| ... | ... | @@ -2484,7 +2484,7 @@ it.each(["client", "frame"] as const)( |
| 2484 | 2484 | }, |
| 2485 | 2485 | ); |
| 2486 | 2486 | |
| 2487 | | it.each(["darwin", "win32"] as const)( |
| 2487 | it.each(["darwin", "win32", "linux"] as const)( |
| 2488 | 2488 | "extracts the same structured accessibility tree on %s", |
| 2489 | 2489 | async (platform) => { |
| 2490 | 2490 | const bounds = { x: 100, y: 200, width: 800, height: 600 }; |
| ... | ... | @@ -2492,7 +2492,13 @@ it.each(["darwin", "win32"] as const)( |
| 2492 | 2492 | role: "window", |
| 2493 | 2493 | name: "Editor", |
| 2494 | 2494 | bounds, |
| 2495 | | tree: async () => ({ name: "Editor", children: [{ name: "Save", children: [] }] }), |
| 2495 | tree: async () => ({ |
| 2496 | name: "Editor", |
| 2497 | children: [ |
| 2498 | { name: "Save", children: [] }, |
| 2499 | { name: "Below scroll view", children: [] }, |
| 2500 | ], |
| 2501 | }), |
| 2496 | 2502 | children: async () => [ |
| 2497 | 2503 | { |
| 2498 | 2504 | role: "button", |
| ... | ... | @@ -2500,6 +2506,13 @@ it.each(["darwin", "win32"] as const)( |
| 2500 | 2506 | bounds: { x: 300, y: 350, width: 100, height: 50 }, |
| 2501 | 2507 | children: async () => [], |
| 2502 | 2508 | }, |
| 2509 | { |
| 2510 | role: "static_text", |
| 2511 | name: "Below scroll view", |
| 2512 | bounds: { x: 300, y: 1_000, width: 100, height: 50 }, |
| 2513 | visible: false, |
| 2514 | children: async () => [], |
| 2515 | }, |
| 2503 | 2516 | ], |
| 2504 | 2517 | }; |
| 2505 | 2518 | accessibilityByPidMock.mockReset().mockResolvedValue({ children: async () => [window] }); |
| ... | ... | @@ -2521,6 +2534,12 @@ it.each(["darwin", "win32"] as const)( |
| 2521 | 2534 | : undefined, |
| 2522 | 2535 | "Save", |
| 2523 | 2536 | ); |
| 2537 | assert.deepInclude( |
| 2538 | result?.accessibility?.format === "element-tree" |
| 2539 | ? result.accessibility.root.children[1] |
| 2540 | : undefined, |
| 2541 | { name: "Below scroll view", bounds: null, state: { visible: false } }, |
| 2542 | ); |
| 2524 | 2543 | assert.lengthOf(accessibilityByPidMock.mock.calls, platform === "win32" ? 0 : 1); |
| 2525 | 2544 | assert.lengthOf(accessibilityForegroundMock.mock.calls, platform === "win32" ? 1 : 0); |
| 2526 | 2545 | }, |
| ... | ... | @@ -2559,6 +2578,48 @@ it.each(["darwin", "win32"] as const)( |
| 2559 | 2578 | }, |
| 2560 | 2579 | ); |
| 2561 | 2580 | |
| 2581 | it.each([ |
| 2582 | ["darwin", "", "value", 650], |
| 2583 | ["win32", "", "value", 650], |
| 2584 | ["linux", "x11", "value", 650], |
| 2585 | ["linux", "wayland", "value", 650], |
| 2586 | ["darwin", "", "name", 100], |
| 2587 | ] as const)( |
| 2588 | "preserves long text outside the scroll view on %s %s (%s)", |
| 2589 | async (platform, session, field, lines) => { |
| 2590 | const text = `${"Document line\n".repeat(lines)}End of document`; |
| 2591 | vi.stubEnv("XDG_SESSION_TYPE", session); |
| 2592 | const bounds = { x: 0, y: 0, width: 800, height: 600 }; |
| 2593 | const window = { |
| 2594 | role: "window", |
| 2595 | name: "Editor", |
| 2596 | bounds, |
| 2597 | tree: async () => ({ name: "Editor", children: [{ [field]: text, children: [] }] }), |
| 2598 | children: async () => [ |
| 2599 | { role: "text_area", [field]: text, bounds, children: async () => [] }, |
| 2600 | ], |
| 2601 | }; |
| 2602 | accessibilityByPidMock.mockReset().mockResolvedValue({ children: async () => [window] }); |
| 2603 | accessibilityForegroundMock |
| 2604 | .mockReset() |
| 2605 | .mockResolvedValue({ pid: 123, asElement: () => window }); |
| 2606 | try { |
| 2607 | const result = await readAccessibleWindowContext( |
| 2608 | { title: "Editor", bounds, owner: { processId: 123 } }, |
| 2609 | platform, |
| 2610 | "Editor", |
| 2611 | ); |
| 2612 | assert.deepEqual(result?.accessibility, { |
| 2613 | format: "flat-text", |
| 2614 | text: `Editor\n${text}`, |
| 2615 | truncated: false, |
| 2616 | }); |
| 2617 | } finally { |
| 2618 | vi.unstubAllEnvs(); |
| 2619 | } |
| 2620 | }, |
| 2621 | ); |
| 2622 | |
| 2562 | 2623 | it.each([ |
| 2563 | 2624 | { names: ["⠙ t3code"], expected: "Verified text" }, |
| 2564 | 2625 | { names: ["⠋ t3code", "⠙ t3code"], expected: undefined }, |
| ... | ... | @@ -2686,6 +2747,81 @@ it("falls back to completed flat text when rich traversal reaches the deadline", |
| 2686 | 2747 | } |
| 2687 | 2748 | }); |
| 2688 | 2749 | |
| 2750 | it("keeps a truncated element tree when the flat text read fails", async () => { |
| 2751 | const bounds = { x: 0, y: 0, width: 800, height: 600 }; |
| 2752 | accessibilityByPidMock.mockReset().mockResolvedValue({ |
| 2753 | children: async () => [ |
| 2754 | { |
| 2755 | role: "window", |
| 2756 | name: "Editor", |
| 2757 | bounds, |
| 2758 | tree: async () => { |
| 2759 | throw new Error("Text read failed"); |
| 2760 | }, |
| 2761 | children: async () => [ |
| 2762 | { |
| 2763 | role: "text_area", |
| 2764 | value: "x".repeat(8_001), |
| 2765 | bounds: { x: 10, y: 50, width: 700, height: 500 }, |
| 2766 | children: async () => [], |
| 2767 | }, |
| 2768 | ], |
| 2769 | }, |
| 2770 | ], |
| 2771 | }); |
| 2772 | |
| 2773 | const result = await readAccessibleWindowContext( |
| 2774 | { title: "Editor", bounds, owner: { processId: 123 } }, |
| 2775 | "darwin", |
| 2776 | "Editor", |
| 2777 | ); |
| 2778 | assert.equal(result?.accessibility?.format, "element-tree"); |
| 2779 | assert.isTrue(result?.accessibility?.truncated); |
| 2780 | assert.deepEqual( |
| 2781 | result?.accessibility?.format === "element-tree" |
| 2782 | ? result.accessibility.root.children[0]?.bounds |
| 2783 | : undefined, |
| 2784 | { x: 10, y: 50, width: 700, height: 500 }, |
| 2785 | ); |
| 2786 | }); |
| 2787 | |
| 2788 | it("keeps a truncated tree when flat text would not recover any text", async () => { |
| 2789 | const bounds = { x: 0, y: 0, width: 800, height: 600 }; |
| 2790 | accessibilityByPidMock.mockReset().mockResolvedValue({ |
| 2791 | children: async () => [ |
| 2792 | { |
| 2793 | role: "window", |
| 2794 | name: "Editor", |
| 2795 | bounds, |
| 2796 | tree: async () => ({ name: "Editor", children: [{ name: "Help", children: [] }] }), |
| 2797 | children: async () => [ |
| 2798 | { |
| 2799 | role: "button", |
| 2800 | name: "Help", |
| 2801 | description: "Help text ".repeat(250), |
| 2802 | bounds, |
| 2803 | children: async () => [], |
| 2804 | }, |
| 2805 | ], |
| 2806 | }, |
| 2807 | ], |
| 2808 | }); |
| 2809 | |
| 2810 | const result = await readAccessibleWindowContext( |
| 2811 | { title: "Editor", bounds, owner: { processId: 123 } }, |
| 2812 | "darwin", |
| 2813 | "Editor", |
| 2814 | ); |
| 2815 | assert.equal(result?.accessibility?.format, "element-tree"); |
| 2816 | assert.isTrue(result?.accessibility?.truncated); |
| 2817 | assert.include( |
| 2818 | result?.accessibility?.format === "element-tree" |
| 2819 | ? result.accessibility.root.children[0]?.description |
| 2820 | : undefined, |
| 2821 | "Help text", |
| 2822 | ); |
| 2823 | }); |
| 2824 | |
| 2689 | 2825 | it("times out after three seconds without overlapping the outstanding accessibility read", async () => { |
| 2690 | 2826 | vi.useFakeTimers(); |
| 2691 | 2827 | accessibilityByPidMock.mockReset(); |