fix: phase 9+10 refactoring

This commit is contained in:
2026-02-16 07:04:15 +01:00
parent e7c395e1bd
commit 4051fa9333
8 changed files with 295 additions and 128 deletions

View File

@@ -0,0 +1,74 @@
import React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { AISuggestionsModal, type AISuggestions } from '../../../src/renderer/components/AISuggestionsModal/AISuggestionsModal';
const currentValues = {
title: 'Existing title',
alt: 'Existing alt',
caption: '',
};
const baseSuggestions: AISuggestions = {
title: 'Suggested title',
alt: 'Suggested alt',
caption: 'Suggested caption',
};
describe('AISuggestionsModal', () => {
it('shows suggested fields and applies only selected values', () => {
const onConfirm = vi.fn();
render(
<AISuggestionsModal
isOpen
isLoading={false}
suggestions={baseSuggestions}
currentValues={currentValues}
onConfirm={onConfirm}
onClose={vi.fn()}
/>
);
expect(screen.getByText('Suggested title')).toBeTruthy();
expect(screen.getByText('Suggested alt')).toBeTruthy();
expect(screen.getByText('Suggested caption')).toBeTruthy();
const applyButton = screen.getByRole('button', { name: 'Apply Selected' });
const [titleCheckbox, altCheckbox, captionCheckbox] = screen.getAllByRole('checkbox') as HTMLInputElement[];
expect(titleCheckbox.checked).toBe(false);
expect(altCheckbox.checked).toBe(false);
expect(captionCheckbox.checked).toBe(true);
expect(applyButton).not.toBeDisabled();
fireEvent.click(captionCheckbox);
expect(applyButton).toBeDisabled();
fireEvent.click(captionCheckbox);
expect(applyButton).not.toBeDisabled();
fireEvent.click(applyButton);
expect(onConfirm).toHaveBeenCalledTimes(1);
expect(onConfirm).toHaveBeenCalledWith({
caption: 'Suggested caption',
});
});
it('hides apply button when no suggestions are available', () => {
render(
<AISuggestionsModal
isOpen
isLoading={false}
suggestions={{}}
currentValues={{ title: '', alt: '', caption: '' }}
onConfirm={vi.fn()}
onClose={vi.fn()}
/>
);
expect(screen.queryByRole('button', { name: 'Apply Selected' })).toBeNull();
expect(screen.getByText('No suggestions were generated for this image.')).toBeTruthy();
});
});

View File

@@ -0,0 +1,51 @@
import React from 'react';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, act } from '@testing-library/react';
import { TagInput } from '../../../src/renderer/components/TagInput/TagInput';
describe('TagInput subscriptions', () => {
beforeEach(() => {
const onMock = vi.fn((_channel: string, _callback: (...args: unknown[]) => void) => vi.fn());
(window as any).electronAPI = {
...(window as any).electronAPI,
tags: {
getAll: vi.fn().mockResolvedValue([]),
create: vi.fn(),
},
on: onMock,
};
});
it('subscribes to tag refresh events on mount and unsubscribes on unmount', async () => {
const unsubscribeSpies = [vi.fn(), vi.fn(), vi.fn(), vi.fn()];
const onMock = vi
.fn()
.mockImplementationOnce(() => unsubscribeSpies[0])
.mockImplementationOnce(() => unsubscribeSpies[1])
.mockImplementationOnce(() => unsubscribeSpies[2])
.mockImplementationOnce(() => unsubscribeSpies[3]);
(window as any).electronAPI.on = onMock;
const { unmount } = render(<TagInput value={[]} onChange={vi.fn()} />);
await act(async () => {
await Promise.resolve();
});
expect(onMock).toHaveBeenCalledTimes(4);
expect(onMock.mock.calls.map((call) => call[0])).toEqual([
'tag:created',
'tag:deleted',
'tag:renamed',
'tags:merged',
]);
unmount();
unsubscribeSpies.forEach((spy) => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});

View File

@@ -0,0 +1,60 @@
import React from 'react';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, act } from '@testing-library/react';
import { TagsView } from '../../../src/renderer/components/TagsView/TagsView';
describe('TagsView subscriptions', () => {
beforeEach(() => {
const onMock = vi.fn((_channel: string, _callback: (...args: unknown[]) => void) => vi.fn());
(window as any).electronAPI = {
...(window as any).electronAPI,
tags: {
getWithCounts: vi.fn().mockResolvedValue([]),
getAll: vi.fn().mockResolvedValue([]),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
rename: vi.fn(),
merge: vi.fn(),
syncFromPosts: vi.fn(),
},
on: onMock,
};
});
it('subscribes to tag refresh events including updates and unsubscribes on unmount', async () => {
const unsubscribeSpies = [vi.fn(), vi.fn(), vi.fn(), vi.fn(), vi.fn()];
const onMock = vi
.fn()
.mockImplementationOnce(() => unsubscribeSpies[0])
.mockImplementationOnce(() => unsubscribeSpies[1])
.mockImplementationOnce(() => unsubscribeSpies[2])
.mockImplementationOnce(() => unsubscribeSpies[3])
.mockImplementationOnce(() => unsubscribeSpies[4]);
(window as any).electronAPI.on = onMock;
const { unmount } = render(<TagsView />);
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
expect(onMock).toHaveBeenCalledTimes(5);
expect(onMock.mock.calls.map((call) => call[0]).sort()).toEqual([
'tag:created',
'tag:deleted',
'tag:renamed',
'tag:updated',
'tags:merged',
]);
unmount();
unsubscribeSpies.forEach((spy) => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});