feat: quick-capture bookmarklet
One-click job capture from any posting, reusing the existing jobimport/preview parser. - AddJobModal accepts initialUrl and auto-imports once on open - App reads a /?add=<encoded url> param, opens Add Job pre-filled, and strips the param from the address bar - QuickCaptureCard in Settings offers a draggable bookmarklet (href set via ref since React blocks javascript: URLs) plus copyable code - EN/NB translations; README feature note - 2 frontend tests; full suite green (22 suites / 50 tests) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { ToastProvider } from './toast';
|
||||
import { I18nProvider } from './i18n/I18nProvider';
|
||||
import { api } from './api';
|
||||
|
||||
// Avoid pulling the date-fns v4 ESM adapter into Jest; the picker isn't under test here.
|
||||
jest.mock('@mui/x-date-pickers/DatePicker', () => ({
|
||||
DatePicker: ({ label }: any) => <div>{label}</div>,
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line import/first
|
||||
import AddJobModal from './components/AddJobModal';
|
||||
|
||||
jest.setTimeout(15000);
|
||||
|
||||
jest.mock('./api', () => ({
|
||||
api: {
|
||||
get: jest.fn(() => Promise.resolve({ data: [] })),
|
||||
post: jest.fn(() => Promise.resolve({ data: {} })),
|
||||
put: jest.fn(() => Promise.resolve({ data: {} })),
|
||||
patch: jest.fn(() => Promise.resolve({ data: {} })),
|
||||
delete: jest.fn(() => Promise.resolve({ data: {} })),
|
||||
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
|
||||
},
|
||||
getApiErrorMessage: jest.fn(() => 'error'),
|
||||
}));
|
||||
|
||||
const mockedApi = api as jest.Mocked<typeof api>;
|
||||
|
||||
function renderModal(initialUrl?: string) {
|
||||
return render(
|
||||
<ToastProvider>
|
||||
<I18nProvider>
|
||||
<AddJobModal open initialUrl={initialUrl} onClose={() => {}} onCreated={() => {}} />
|
||||
</I18nProvider>
|
||||
</ToastProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockedApi.get.mockResolvedValue({ data: [] } as any);
|
||||
mockedApi.post.mockImplementation((url: string) => {
|
||||
if (url === '/jobimport/preview') {
|
||||
return Promise.resolve({ data: { success: true, title: 'Imported Backend Role', company: 'Acme', location: 'Oslo', description: 'desc', tags: ['C#'] } } as any);
|
||||
}
|
||||
return Promise.resolve({ data: {} } as any);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
test('auto-imports from initialUrl and prefills the form', async () => {
|
||||
renderModal('https://example.com/jobs/123');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedApi.post).toHaveBeenCalledWith('/jobimport/preview', { url: 'https://example.com/jobs/123' });
|
||||
});
|
||||
|
||||
expect(await screen.findByDisplayValue('Imported Backend Role')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not auto-import when no initialUrl is given', async () => {
|
||||
renderModal(undefined);
|
||||
|
||||
// Wait for the modal to render, then confirm no import was triggered.
|
||||
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
||||
expect(mockedApi.post).not.toHaveBeenCalledWith('/jobimport/preview', expect.anything());
|
||||
});
|
||||
Reference in New Issue
Block a user