mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: wizard interceptor
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user