diff --git a/src/components/wizard/OAuthInstructions.test.tsx b/src/components/wizard/OAuthInstructions.test.tsx new file mode 100644 index 0000000..f15093b --- /dev/null +++ b/src/components/wizard/OAuthInstructions.test.tsx @@ -0,0 +1,58 @@ +// @vitest-environment jsdom +// Tests for OAuthInstructions — collapsible OAuth step-by-step guide +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { OAuthInstructions } from './OAuthInstructions'; + +describe('OAuthInstructions', () => { + it('renders collapsed by default (instructions not visible)', () => { + render( + + ); + // The numbered steps should not be visible in collapsed state + expect(screen.queryByText(/Install rclone/i)).toBeNull(); + }); + + it('expands when user clicks the toggle button, showing numbered steps', async () => { + const user = userEvent.setup(); + render( + + ); + const toggleBtn = screen.getByRole('button'); + await user.click(toggleBtn); + // After expanding, steps should be visible + expect(screen.getByText(/Install rclone/i)).toBeDefined(); + }); + + it('shows the backend-specific authorizeCommand in the expanded content', async () => { + const user = userEvent.setup(); + const cmd = 'rclone authorize "drive"'; + render( + + ); + const toggleBtn = screen.getByRole('button'); + await user.click(toggleBtn); + expect(screen.getByText(cmd)).toBeDefined(); + }); + + it('shows backend name in the toggle button text', () => { + render( + + ); + const toggleBtn = screen.getByRole('button'); + expect(toggleBtn.textContent).toContain('Dropbox'); + }); +}); diff --git a/src/components/wizard/OAuthInstructions.tsx b/src/components/wizard/OAuthInstructions.tsx new file mode 100644 index 0000000..19c0529 --- /dev/null +++ b/src/components/wizard/OAuthInstructions.tsx @@ -0,0 +1,69 @@ +import { useState } from 'react'; + +interface OAuthInstructionsProps { + backendName: string; // e.g. "Google Drive" + authorizeCommand: string; // e.g. 'rclone authorize "drive"' + steps?: string[]; // optional custom steps (defaults to generic OAuth flow) +} + +const DEFAULT_STEPS = (authorizeCommand: string): string[] => [ + 'Install rclone on a machine that has a web browser.', + `Run the authorize command: ${authorizeCommand}`, + 'A browser window will open — authenticate with your account.', + 'Copy the JSON token printed in the terminal.', + 'Paste the token into the field above.', +]; + +export function OAuthInstructions({ backendName, authorizeCommand, steps }: OAuthInstructionsProps) { + const [expanded, setExpanded] = useState(false); + + const resolvedSteps = steps ?? DEFAULT_STEPS(authorizeCommand); + + return ( +
+ + + {expanded && ( +
+
    + {resolvedSteps.map((step, index) => { + // The second step contains the command — render it with a code block + if (step.startsWith('Run the authorize command:')) { + return ( +
  1. + Run the authorize command:{' '} + + {authorizeCommand} + +
  2. + ); + } + return
  3. {step}
  4. ; + })} +
+
+ )} +
+ ); +}