Files
civitai__civitai/src/components/Moderation/HuggingFaceImport/AddFromImportsButton.browser.test.tsx
T
briant 8fddfbe6e0 feat(models): add Hugging Face imports to a version from Manage files
Moderators can now put an already-transferred Hugging Face file onto a
model version without leaving its Manage files page. The "Add from
Hugging Face imports" button opens a picker that lists unattached
imports, grouped by batch and filterable by group name; each file is
attached once a type is chosen for it.

The type is never defaulted. `suggestFileType` deliberately makes no
suggestion for primary weights, and that label decides whether the
version loads, so a list's first entry is not a safe fallback. A
suggestion, where one exists, is shown only as the placeholder. The
import page's own Attach control dropped the same fallback.

The picker opens through the dialog store. Manage files is itself a
store dialog, and an inline Modal rendered at Mantine's lower default
z-index, behind it, so the button appeared to do nothing.

`FilesProvider` seeds its file list once, so a file created outside its
upload path never appeared until a reload. `adoptFiles` adds the named
files, read from `getByIdForEdit` (the primary): append-only, so unsaved
metadata edits and in-flight uploads are untouched. The server-row
mapping is now one function shared with the initial seed.

The attach loop runs one file at a time, attempts every file after a
failure, and reports the ids it created. A lost import claim leaves a
created file whose id appears only in the error, so every failure is
shown and the notification stays open. `getModelFileTypeOptions` is
now the one definition of the file-type list and its labels, used by
the picker, the Attach control and the creator's own type select.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015cyrXpr87t9Tj3bnzRrUhp
2026-09-16 11:01:01 -06:00

50 lines
1.9 KiB
TypeScript

import { describe, expect, test, vi } from 'vitest';
import { page } from 'vitest/browser';
import type * as FilesProviderModule from '~/components/Resource/FilesProvider';
import { renderWithProviders } from '../../../../test/component-setup';
const { mockOpen, mockAdoptFiles, user } = vi.hoisted(() => ({
mockOpen: vi.fn(),
mockAdoptFiles: vi.fn(),
user: { current: { isModerator: true } as { isModerator: boolean } | null },
}));
// Pins the dialog-store route; the reason is on AddFromImportsButton.
vi.mock('~/components/Dialog/triggers/add-from-hugging-face-imports', () => ({
openAddFromImportsModal: mockOpen,
}));
vi.mock('~/components/Resource/FilesProvider', async (importOriginal) => ({
...(await importOriginal<typeof FilesProviderModule>()),
useFilesContext: () => ({ adoptFiles: mockAdoptFiles, modelType: 'Checkpoint' }),
}));
vi.mock('~/hooks/useCurrentUser', () => ({ useCurrentUser: () => user.current }));
import { AddFromImportsButton } from '~/components/Moderation/HuggingFaceImport/AddFromImportsButton';
describe('AddFromImportsButton', () => {
test('opens the picker through the dialog store, with the provider handles', async () => {
user.current = { isModerator: true };
renderWithProviders(<AddFromImportsButton modelVersionId={42} />);
await page.getByRole('button', { name: 'Add from Hugging Face imports' }).click();
expect(mockOpen).toHaveBeenCalledWith({
modelVersionId: 42,
modelType: 'Checkpoint',
adoptFiles: mockAdoptFiles,
});
});
test('renders nothing for a non-moderator', async () => {
user.current = { isModerator: false };
renderWithProviders(
<div data-testid="host">
<AddFromImportsButton modelVersionId={42} />
</div>
);
await expect.element(page.getByTestId('host')).toBeInTheDocument();
expect(page.getByTestId('host').element().childElementCount).toBe(0);
});
});