Files
yoshiko-pg__difit/vitest.setup.ts
T
yoshiko 5f697599ba feat: Add revision selector with floating UI for dynamic base/target selection (#165)
* feat: Add revision selector to change base/target commits dynamically

Add dropdown selectors to allow users to change the base and target revisions
without restarting the server.

Changes:
- Add /api/revisions endpoint to fetch available branches and recent commits
- Modify /api/diff to accept base/target query parameters
- Add RevisionSelector component with optgroups (Special, Recent Commits, Branches)
- Add revision state management in App.tsx with confirmation dialog for clearing comments
- Handle working/staged constraints (working can only compare with staged)
- Update test mocks to handle multiple API endpoints

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Resolve HEAD/HEAD^ to commit hashes for revision selector

When starting without arguments (default HEAD), the revision selector was
showing "All Uncommitted Changes" for both base and target because HEAD and
HEAD^ values didn't exist in the dropdown options.

Changes:
- Add resolvedBase and resolvedTarget to getRevisionOptions() to resolve
  symbolic refs like HEAD/HEAD^ to actual commit hashes
- Update /api/revisions to return resolved values
- Use resolved values as initial selection in revision selector
- This ensures the correct commits are selected on initial load

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* fix: Resolve symbolic refs in /api/diff response for revision selector

The previous fix only resolved refs in /api/revisions, but /api/diff was
still returning HEAD/HEAD^ which would overwrite the resolved values.

Changes:
- Add resolveCommitish() method to GitDiffParser
- Resolve symbolic refs (HEAD, HEAD^, etc.) in /api/diff response
- Use the new method in getRevisionOptions() to avoid duplication

Now both /api/diff and /api/revisions return resolved commit hashes,
ensuring the revision selector displays the correct values.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* fix: Use shortHash for select values to match resolved hashes

The select options were using full commit hashes (commit.hash) as values,
but the server was returning 7-character short hashes. This caused the
selector to not match and default to the first option.

Changes:
- Change RevisionSelector to use commit.shortHash as option values
- Remove debug logging

Now the selector correctly matches the resolved short hashes returned
from the server.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* feat: Sort branches with default branch first in revision selector

Add getDefaultBranch() method to detect the repository's default branch
(usually main or master) by checking origin/HEAD, and sort the branch list
to show it first.

Changes:
- Add getDefaultBranch() method to GitDiffParser
  - Try to get default branch from origin/HEAD
  - Fall back to common defaults (main, master) if origin/HEAD not set
- Sort branches in getRevisionOptions():
  1. Default branch (main/master)
  2. Current branch
  3. Others (alphabetically)

This makes it easier to find the main branch in the selector.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* feat: Replace native select with custom dropdown for revision selector

Replace the native select element with a custom dropdown that provides
better visibility and UX:
- Click or hover to open dropdown
- Wider dropdown (320px min) with better spacing
- Full commit messages visible (no truncation in dropdown)
- Section headers for Special/Recent Commits/Branches
- Selected item highlighted
- Auto-closes on mouse leave or outside click
- Uses ChevronDown icon for better affordance

The custom dropdown provides much better readability compared to the
native select, especially for long commit messages and branch names.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* feat: Improve revision selector with floating-ui and UX enhancements

User request: Improve the revision selector in the header to prevent viewport overflow and enhance interaction.

Implemented improvements:
- Install @floating-ui/react dependency
- Update RevisionSelector to use floating-ui for viewport-aware positioning with flip() and shift() middleware
- Replace click interaction with hover/blur using useHover() and useFocus() hooks
- Add safePolygon() to prevent dropdown from closing when moving mouse to dropdown
- Reduce selector trigger width to half (max-w-[100px]) and prevent text wrapping
- Set dropdown width to 360px
- Allow commit messages to wrap in dropdown with break-words class
- Set initial focus to current value when dropdown opens using getInitialFocusIndex()
- Disable react-hooks/refs ESLint rule due to false positives with @floating-ui/react callback refs

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* fix: Remove commit message truncation in revision selector

Remove the 50-character truncation of commit messages in the revision
selector dropdown. Messages are now displayed in full and will wrap
using the break-words CSS class applied in the frontend.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* fix: Make commit message font size consistent with other options

Add text-xs class to commit hash and message to match the font size
used in Special options and Branches sections.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* feat: Disable commits older than base in target selector

Add disabledValues prop to RevisionSelector to support disabling multiple
values. In the target revision selector, disable not only the base revision
but also all commits older than the base revision to prevent selecting
invalid revision ranges.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* refactor: Simplify disabled values by removing disabledValue prop

Replace disabledValue prop with disabledValues array for consistency.
This simplifies the implementation by using a single approach for
disabling options in the revision selector.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* unclear existing comments

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 17:36:26 +09:00

51 lines
1.4 KiB
TypeScript

import '@testing-library/jest-dom';
import { vi } from 'vitest';
import { Agent, setGlobalDispatcher } from 'undici';
// Disable keep-alive for tests to prevent ECONNRESET errors
// Use pipelining=0 instead of keepAlive=false for this version of undici
setGlobalDispatcher(new Agent({ pipelining: 0 }));
// Mock fetch globally for component tests, but not for server integration tests
if (!process.env.VITEST_SERVER_TEST) {
global.fetch = vi.fn();
}
// Mock console.error to suppress error logs during tests
global.console.error = vi.fn();
// Mock window.getComputedStyle
Object.defineProperty(window, 'getComputedStyle', {
value: () => ({
getPropertyValue: () => '',
}),
});
// Global test utilities
export const mockFetch = (response: any, revisionsResponse?: any) => {
(global.fetch as any).mockImplementation((url: string) => {
// Handle /api/revisions endpoint
if (url.includes('/api/revisions')) {
return Promise.resolve({
ok: revisionsResponse !== null,
json: async () =>
revisionsResponse ?? {
specialOptions: [],
branches: [],
commits: [],
},
});
}
// Default: /api/diff and others
return Promise.resolve({
ok: true,
json: async () => response,
blob: async () => ({ size: 1024 }),
});
});
};
export const mockFetchError = (error: string) => {
(global.fetch as any).mockRejectedValue(new Error(error));
};