Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of [CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md) for the full list. Highlights: client proofing links (#683), custom content pages (#738), asset picker search (#740), concurrent-edit detection (#742), env-locked settings (#739), an accessibility baseline (#741), three security fixes with advisories (#737, #755, #759), and a pre-release quality pass (#780–#797). --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com> Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
95 lines
3.1 KiB
TypeScript
95 lines
3.1 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
|
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
|
|
import { JournalEditor } from '../journal/JournalEditor';
|
|
import { NotificationProvider } from '../Notifications';
|
|
import { ConfirmProvider } from '../ConfirmDialog';
|
|
|
|
/**
|
|
* The page editor's Publish button flips the Draft flag and saves at once
|
|
* (#722). When that save fails, the flipped flag is an unsaved edit: the editor
|
|
* has to say so and keep it saveable, not show "Saved" over a page that is
|
|
* still a draft on disk.
|
|
*/
|
|
|
|
const MARKDOWN = '---\ntitle: "FAQ"\ndraft: true\n---\n\nHello\n';
|
|
|
|
let putStatus = 500;
|
|
|
|
beforeEach(() => {
|
|
putStatus = 500;
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async (_url: string, init?: RequestInit) => {
|
|
if (init?.method === 'PUT') {
|
|
if (putStatus !== 200) {
|
|
return new Response(JSON.stringify({ error: 'Failed to save page' }), {
|
|
status: putStatus,
|
|
});
|
|
}
|
|
const sent = JSON.parse(init.body as string) as { rawMarkdown: string };
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: true,
|
|
page: { slug: 'faq', rawMarkdown: sent.rawMarkdown },
|
|
version: 'v2',
|
|
}),
|
|
{ status: 200 },
|
|
);
|
|
}
|
|
return new Response(
|
|
JSON.stringify({ page: { slug: 'faq', rawMarkdown: MARKDOWN }, version: 'v1' }),
|
|
{ status: 200 },
|
|
);
|
|
}),
|
|
);
|
|
// Node 26 defines its own global localStorage (undefined without
|
|
// --localstorage-file), which shadows jsdom's; give the editor a working one.
|
|
const store = new Map<string, string>();
|
|
vi.stubGlobal('localStorage', {
|
|
getItem: (k: string) => store.get(k) ?? null,
|
|
setItem: (k: string, v: string) => void store.set(k, String(v)),
|
|
removeItem: (k: string) => void store.delete(k),
|
|
clear: () => store.clear(),
|
|
});
|
|
// Drafts live in sessionStorage; each test starts without one.
|
|
window.sessionStorage.clear();
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
function renderEditor() {
|
|
return render(
|
|
<NotificationProvider>
|
|
<ConfirmProvider>
|
|
<JournalEditor slug="faq" kind="page" onBack={() => {}} />
|
|
</ConfirmProvider>
|
|
</NotificationProvider>,
|
|
);
|
|
}
|
|
|
|
describe('JournalEditor page mode: Publish', () => {
|
|
it('leaves a failed publish as an unsaved change', async () => {
|
|
renderEditor();
|
|
fireEvent.click(await screen.findByRole('button', { name: 'Publish' }));
|
|
|
|
await waitFor(() =>
|
|
expect(screen.getByRole('alert').textContent).toContain('Failed to save page'),
|
|
);
|
|
const save = screen.getByRole('button', { name: 'Save Changes' });
|
|
expect((save as HTMLButtonElement).disabled).toBe(false);
|
|
});
|
|
|
|
it('reads Saved after a publish that went through', async () => {
|
|
putStatus = 200;
|
|
renderEditor();
|
|
fireEvent.click(await screen.findByRole('button', { name: 'Publish' }));
|
|
|
|
await screen.findByRole('button', { name: 'Unpublish' });
|
|
await waitFor(() => expect(screen.getByRole('button', { name: /Saved/ })).toBeTruthy());
|
|
});
|
|
});
|