fix(journey-editor): selectedPersons change calls markDirty via $effect
Skip-first-run $effect tracks selectedPersons array length; any add/remove after mount marks the editor dirty so the unsaved-warning fires on nav. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ let { geschichte, onSubmit, submitting = false }: Props = $props();
|
|||||||
const unsaved = createUnsavedWarning();
|
const unsaved = createUnsavedWarning();
|
||||||
|
|
||||||
let title = $state(geschichte.title ?? '');
|
let title = $state(geschichte.title ?? '');
|
||||||
|
|
||||||
let body = $state(geschichte.body ?? '');
|
let body = $state(geschichte.body ?? '');
|
||||||
let status: 'DRAFT' | 'PUBLISHED' = $state(geschichte.status ?? 'DRAFT');
|
let status: 'DRAFT' | 'PUBLISHED' = $state(geschichte.status ?? 'DRAFT');
|
||||||
let selectedPersons: PersonOption[] = $state(
|
let selectedPersons: PersonOption[] = $state(
|
||||||
@@ -64,6 +65,17 @@ const alreadyAddedIds = $derived(
|
|||||||
const canPublish = $derived(items.length > 0 && !titleEmpty);
|
const canPublish = $derived(items.length > 0 && !titleEmpty);
|
||||||
const showPublishedEmptyWarning = $derived(status === 'PUBLISHED' && items.length === 0);
|
const showPublishedEmptyWarning = $derived(status === 'PUBLISHED' && items.length === 0);
|
||||||
|
|
||||||
|
// Skip the initial run so mounting with pre-existing persons doesn't mark dirty.
|
||||||
|
let _personEffectMounted = false;
|
||||||
|
$effect(() => {
|
||||||
|
void selectedPersons.length;
|
||||||
|
if (!_personEffectMounted) {
|
||||||
|
_personEffectMounted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsaved.markDirty();
|
||||||
|
});
|
||||||
|
|
||||||
let listEl: HTMLElement | null = $state(null);
|
let listEl: HTMLElement | null = $state(null);
|
||||||
let editorColEl: HTMLElement | null = $state(null);
|
let editorColEl: HTMLElement | null = $state(null);
|
||||||
|
|
||||||
|
|||||||
@@ -678,6 +678,44 @@ describe('JourneyEditor — unsaved warning banner', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('JourneyEditor — selectedPersons marks dirty', () => {
|
||||||
|
function getNavCallback() {
|
||||||
|
const calls = vi.mocked(beforeNavigate).mock.calls;
|
||||||
|
const [callback] = calls[calls.length - 1];
|
||||||
|
return (cancel = vi.fn()) => {
|
||||||
|
(callback as (nav: { cancel: () => void; to: { url: URL } | null }) => void)({
|
||||||
|
cancel,
|
||||||
|
to: { url: new URL('http://localhost/geschichten') }
|
||||||
|
});
|
||||||
|
return cancel;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('removing a person chip marks the editor dirty', async () => {
|
||||||
|
render(
|
||||||
|
JourneyEditor,
|
||||||
|
defaultProps({
|
||||||
|
geschichte: makeGeschichte({
|
||||||
|
persons: [{ id: 'p1', firstName: 'Anna', lastName: 'Schmidt' }]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Confirm navigation is NOT blocked initially (clean state)
|
||||||
|
const triggerNav = getNavCallback();
|
||||||
|
expect(triggerNav()).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Remove the person chip (aria-label = m.comp_multiselect_remove() = "Entfernen")
|
||||||
|
await userEvent.click(page.getByRole('button', { name: m.comp_multiselect_remove() }));
|
||||||
|
|
||||||
|
// After person removal, navigation should be blocked
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
const cancel = triggerNav();
|
||||||
|
expect(cancel).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('JourneyEditor — person chips from GeschichteView', () => {
|
describe('JourneyEditor — person chips from GeschichteView', () => {
|
||||||
it('renders person names in the sidebar chips (PersonView carries no displayName)', async () => {
|
it('renders person names in the sidebar chips (PersonView carries no displayName)', async () => {
|
||||||
render(
|
render(
|
||||||
|
|||||||
Reference in New Issue
Block a user