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>
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
|
|
import { ConfirmProvider, useChoice, useConfirm } from '../components/ConfirmDialog';
|
|
|
|
afterEach(cleanup);
|
|
|
|
/** UX stage 3 (#694 §3): what replaced window.confirm in the admin. */
|
|
function Trigger({ onResult }: { onResult: (ok: boolean) => void }) {
|
|
const confirm = useConfirm();
|
|
return (
|
|
<button
|
|
onClick={async () =>
|
|
onResult(
|
|
await confirm({
|
|
title: 'Delete it?',
|
|
message: 'Gone for good.',
|
|
confirmLabel: 'Delete',
|
|
danger: true,
|
|
}),
|
|
)
|
|
}
|
|
>
|
|
go
|
|
</button>
|
|
);
|
|
}
|
|
|
|
function setup() {
|
|
const results: boolean[] = [];
|
|
render(
|
|
<ConfirmProvider>
|
|
<Trigger onResult={(ok) => results.push(ok)} />
|
|
</ConfirmProvider>,
|
|
);
|
|
fireEvent.click(screen.getByText('go'));
|
|
return results;
|
|
}
|
|
|
|
describe('ConfirmDialog', () => {
|
|
it('asks in an alertdialog named by its title', () => {
|
|
setup();
|
|
const dialog = screen.getByRole('alertdialog', { name: 'Delete it?' });
|
|
expect(dialog.textContent).toContain('Gone for good.');
|
|
});
|
|
|
|
it('resolves true on confirm and closes', async () => {
|
|
const results = setup();
|
|
fireEvent.click(screen.getByRole('button', { name: 'Delete' }));
|
|
await waitFor(() => expect(results).toEqual([true]));
|
|
expect(screen.queryByRole('alertdialog')).toBeNull();
|
|
});
|
|
|
|
it('resolves false on cancel, and on Escape', async () => {
|
|
const results = setup();
|
|
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
await waitFor(() => expect(results).toEqual([false]));
|
|
fireEvent.click(screen.getByText('go'));
|
|
fireEvent.keyDown(document, { key: 'Escape' });
|
|
await waitFor(() => expect(results).toEqual([false, false]));
|
|
});
|
|
});
|
|
|
|
/** The concurrent-edit prompt (#601): more than two ways out. */
|
|
function ChoiceTrigger({ onResult }: { onResult: (id: string | null) => void }) {
|
|
const choose = useChoice();
|
|
return (
|
|
<button
|
|
onClick={async () =>
|
|
onResult(
|
|
await choose({
|
|
title: 'Changed elsewhere',
|
|
actions: [
|
|
{ id: 'overwrite', label: 'Overwrite anyway' },
|
|
{ id: 'reload', label: 'Reload (discard mine)' },
|
|
],
|
|
cancelLabel: 'Keep editing',
|
|
}),
|
|
)
|
|
}
|
|
>
|
|
choose
|
|
</button>
|
|
);
|
|
}
|
|
|
|
describe('useChoice', () => {
|
|
it('resolves to the picked action, or null on cancel', async () => {
|
|
const results: (string | null)[] = [];
|
|
render(
|
|
<ConfirmProvider>
|
|
<ChoiceTrigger onResult={(id) => results.push(id)} />
|
|
</ConfirmProvider>,
|
|
);
|
|
fireEvent.click(screen.getByText('choose'));
|
|
fireEvent.click(screen.getByRole('button', { name: 'Reload (discard mine)' }));
|
|
await waitFor(() => expect(results).toEqual(['reload']));
|
|
fireEvent.click(screen.getByText('choose'));
|
|
fireEvent.click(screen.getByRole('button', { name: 'Overwrite anyway' }));
|
|
await waitFor(() => expect(results).toEqual(['reload', 'overwrite']));
|
|
fireEvent.click(screen.getByText('choose'));
|
|
fireEvent.click(screen.getByRole('button', { name: 'Keep editing' }));
|
|
await waitFor(() => expect(results).toEqual(['reload', 'overwrite', null]));
|
|
});
|
|
});
|