feat: wizard interceptor

This commit is contained in:
Torsten Dittmann
2022-10-13 18:53:01 +02:00
parent 989ba36bf8
commit 9f6f6e3a6c
6 changed files with 111 additions and 15 deletions
+17 -1
View File
@@ -12,6 +12,7 @@
<script lang="ts">
import { Steps } from '$lib/components';
import { Button, Form } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { wizard } from '$lib/stores/wizard';
import { createEventDispatcher, type SvelteComponent } from 'svelte';
import WizardExitModal from './wizardExitModal.svelte';
@@ -29,6 +30,7 @@
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && !showExitModal) {
event.preventDefault();
dispatch('exit');
wizard.hide();
}
}
@@ -37,6 +39,7 @@
if (confirmExit) {
showExitModal = true;
} else {
dispatch('exit');
wizard.hide();
}
}
@@ -48,7 +51,20 @@
}
}
function submit() {
async function submit() {
if ($wizard.interceptor) {
try {
await $wizard.interceptor();
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
return;
}
}
wizard.setInterceptor(null);
if (isLastStep) {
dispatch('finish');
} else {
+18 -3
View File
@@ -1,10 +1,25 @@
<script lang="ts">
import { wizard } from '$lib/stores/wizard';
/**
* Pass callback that is run before the Form submit, can be canceled with throwing an exception.
*/
export let beforeSubmit: () => Promise<void> = null;
if (beforeSubmit) {
wizard.setInterceptor(beforeSubmit);
}
</script>
<header class="form-header">
<h1 class="heading-level-6">
<slot name="title" />
</h1>
<p>
<slot name="subtitle" />
</p>
{#if $$slots.subtitle}
<p>
<slot name="subtitle" />
</p>
{/if}
</header>
<slot />
+12 -3
View File
@@ -1,10 +1,19 @@
<script lang="ts">
import InputText from '$lib/elements/forms/inputText.svelte';
import WizardStep from '$lib/layout/wizardStep.svelte';
let value: string = null;
async function beforeSubmit() {
if (value === 'fail') {
throw new Error('failed');
}
}
</script>
<WizardStep>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">step-1</svelte:fragment>
<svelte:fragment slot="subtitle">sub-title-1</svelte:fragment>
<input name="step-1-required" required data-testid="step-1-required" type="text" />
<input name="step-1-optional" data-testid="step-1-optional" type="text" />
<InputText label="step-1-required" id="step-1-required" required maxlength={12} bind:value />
<InputText label="step-1-optional" id="step-1-optional" />
</WizardStep>
+3 -2
View File
@@ -1,10 +1,11 @@
<script lang="ts">
import { InputText } from '$lib/elements/forms';
import WizardStep from '$lib/layout/wizardStep.svelte';
</script>
<WizardStep>
<svelte:fragment slot="title">step-2</svelte:fragment>
<svelte:fragment slot="subtitle">sub-title-2</svelte:fragment>
<input data-testid="step-2-first" type="text" />
<input data-testid="step-2-second" type="text" />
<InputText label="step-2-first" id="step-2-first" />
<InputText label="step-2-second" id="step-2-second" />
</WizardStep>
+10 -1
View File
@@ -4,12 +4,14 @@ import { writable } from 'svelte/store';
export type WizardStore = {
show: boolean;
component?: typeof SvelteComponent;
interceptor?: () => Promise<void>;
};
function createWizardStore() {
const { subscribe, update } = writable<WizardStore>({
show: false,
component: null
component: null,
interceptor: null
});
return {
@@ -21,6 +23,13 @@ function createWizardStore() {
return n;
}),
setInterceptor: (callback: WizardStore['interceptor']) => {
update((n) => {
n.interceptor = callback;
return n;
});
},
hide: () =>
update((n) => {
n.show = false;
+51 -5
View File
@@ -2,6 +2,7 @@ import '@testing-library/jest-dom';
import { render } from '@testing-library/svelte';
import { wizard } from '../../../src/lib/stores/wizard';
import { tick } from 'svelte';
import userEvent from '@testing-library/user-event';
import WizardComponent from '../../../src/lib/mock/wizard.test.svelte';
import WizardContainer from '../../../src/lib/mock/wizard.container.test.svelte';
@@ -44,15 +45,15 @@ test('shows wizard and hides it', async () => {
});
test('shows next step with submit', async () => {
const { getByTestId, getByRole, queryByText } = render(WizardContainer);
const { container, getByRole, queryByText } = render(WizardContainer);
wizard.start(WizardComponent);
await tick();
const form = getByRole('form') as HTMLFormElement;
const step1 = queryByText('step-1');
const step1Required = getByTestId('step-1-required');
const step1Optional = getByTestId('step-1-optional');
const step1Required = container.querySelector('#step-1-required');
const step1Optional = container.querySelector('#step-1-optional');
expect(step1).toBeInTheDocument();
expect(step1Required).toBeInTheDocument();
@@ -64,8 +65,53 @@ test('shows next step with submit', async () => {
expect(step1).not.toBeInTheDocument();
const step2 = queryByText('step-2');
const step2First = getByTestId('step-2-first');
const step2Second = getByTestId('step-2-second');
const step2First = container.querySelector('#step-2-first');
const step2Second = container.querySelector('#step-2-second');
expect(step2).toBeInTheDocument();
expect(step2First).toBeInTheDocument();
expect(step2Second).toBeInTheDocument();
form.submit();
await tick();
expect(form).not.toBeInTheDocument();
expect(step1).not.toBeInTheDocument();
expect(step2).not.toBeInTheDocument();
});
test('intercepts submit', async () => {
const { container, getByRole, queryByText } = render(WizardContainer);
wizard.start(WizardComponent);
await tick();
const form = getByRole('form') as HTMLFormElement;
const step1 = queryByText('step-1');
const step1Required = container.querySelector('#step-1-required');
const step1Optional = container.querySelector('#step-1-optional');
expect(step1).toBeInTheDocument();
expect(step1Required).toBeInTheDocument();
expect(step1Optional).toBeInTheDocument();
await userEvent.type(step1Required, 'fail');
form.submit();
await tick();
expect(step1).toBeInTheDocument();
expect(step1Required).toBeInTheDocument();
expect(step1Optional).toBeInTheDocument();
await userEvent.type(step1Required, 'works');
form.submit();
await tick();
expect(step1).not.toBeInTheDocument();
const step2 = queryByText('step-2');
const step2First = container.querySelector('#step-2-first');
const step2Second = container.querySelector('#step-2-second');
expect(step2).toBeInTheDocument();
expect(step2First).toBeInTheDocument();