mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
ci / Detect CI change scopes (push) Successful in 0s
landing-page-ci / Validate landing page (push) Failing after 3s
landing-page-staging / Deploy landing page to staging (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 1s
ci / Workspace unit tests (push) Failing after 2s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 2s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
* fix(web): make hand-off no-editors fallback perform a real reveal The Finder/Explorer/File Manager fallback button was only calling an optional onRequestRevealInFinder prop that the actual caller never passes, so the surface advertised an action it never performed. finder, explorer, and file-manager are real entries in the daemon's open-in catalogue (open / explorer / xdg-open), so route the fallback through openProjectInEditor(projectId, fallbackId) for a genuine reveal. Keep the renderer reveal bridge as a secondary fallback if the daemon spawn fails, and disable the button while busy so a double click can't queue two reveals. Adjacent: PreviewDrawOverlay's Send-while-streaming behavior is intentional (sending is queued downstream, not blocked), and the button already carries sendDisabledReason as its tooltip. Cover that contract with a regression test so a future change can't silently re-disable the control or drop the localized reason. Scope note: the i18n hand-off key migration that previously rode on this branch landed on main via a different key set, so this PR is narrowed to just the fallback wire-up and the two regression tests. * fix(web): surface daemon spawn failure inline in zero-editors fallback The zero-editors HandoffButton fallback called setError() on a rejected openProjectInEditor but returned only the <button>, so the error never rendered. Production callers (ProjectView) mount the component without onRequestRevealInFinder, so a daemon spawn failure became a silent no-op — exactly the failure mode the PR was meant to cover. Wrap the solo button in a handoff-wrap container and render the error inline next to it. Adds a regression test for the rejected-spawn path. * fix(web): align preview draw send-disabled test * fix(web): show handoff fallback for zero editors --------- Co-authored-by: nicejames <nicejames@gmail.com> Co-authored-by: mrcfps <mrc@powerformer.com>
72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
// Regression for the zero-editors fallback: when no editor is detected, the
|
|
// fallback button must perform a real reveal (open the project folder via the
|
|
// daemon's open-in catalogue: finder / explorer / file-manager) rather than a
|
|
// no-op that advertises an action it never runs.
|
|
|
|
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { HandoffButton } from '../../src/components/HandoffButton';
|
|
import { I18nProvider } from '../../src/i18n';
|
|
import type { HostEditorsResponse } from '@open-design/contracts';
|
|
|
|
const fetchHostEditors = vi.fn<() => Promise<HostEditorsResponse>>();
|
|
const openProjectInEditor = vi.fn();
|
|
|
|
vi.mock('../../src/providers/registry', () => ({
|
|
fetchHostEditors: () => fetchHostEditors(),
|
|
openProjectInEditor: (...args: unknown[]) => openProjectInEditor(...args),
|
|
}));
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
fetchHostEditors.mockReset();
|
|
openProjectInEditor.mockReset();
|
|
});
|
|
|
|
describe('HandoffButton zero-editors fallback', () => {
|
|
it('opens the project folder in the OS file manager via the daemon', async () => {
|
|
fetchHostEditors.mockResolvedValue({
|
|
platform: 'darwin',
|
|
editors: [],
|
|
});
|
|
openProjectInEditor.mockResolvedValue(undefined);
|
|
|
|
render(
|
|
<I18nProvider initial="en">
|
|
<HandoffButton projectId="p1" />
|
|
</I18nProvider>,
|
|
);
|
|
|
|
const fallback = (await screen.findByText('Finder')).closest('button') as HTMLButtonElement;
|
|
fireEvent.click(fallback);
|
|
|
|
await waitFor(() => expect(openProjectInEditor).toHaveBeenCalledWith('p1', 'finder'));
|
|
});
|
|
|
|
it('surfaces a daemon spawn failure inline so the fallback is not a silent no-op', async () => {
|
|
// The production caller (`ProjectView`) mounts `<HandoffButton projectId={…} />`
|
|
// with no `onRequestRevealInFinder` callback, so a rejected
|
|
// `openProjectInEditor` would otherwise leave users with a CTA that
|
|
// advertises Finder/Explorer/File Manager but does nothing visible.
|
|
fetchHostEditors.mockResolvedValue({
|
|
platform: 'darwin',
|
|
editors: [],
|
|
});
|
|
openProjectInEditor.mockRejectedValue(new Error('daemon refused: ENOENT'));
|
|
|
|
render(
|
|
<I18nProvider initial="en">
|
|
<HandoffButton projectId="p1" />
|
|
</I18nProvider>,
|
|
);
|
|
|
|
const fallback = (await screen.findByText('Finder')).closest('button') as HTMLButtonElement;
|
|
fireEvent.click(fallback);
|
|
|
|
const errorEl = await screen.findByTestId('handoff-fallback-error');
|
|
expect(errorEl.textContent).toContain('daemon refused: ENOENT');
|
|
});
|
|
});
|