Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/lib/components/customId.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import { trackEvent } from '$lib/actions/analytics';
import { InnerModal } from '$lib/components';
import { InputId } from '$lib/elements/forms';

import { InputProjectId } from '$lib/elements/forms';
export let show = false;
export let name: string;
export let id: string;
export let autofocus = true;
export let fullWidth = false;

export let isProject = false;
$: if (!show) {
id = null;
}
Expand All @@ -29,7 +29,11 @@
</svelte:fragment>
<svelte:fragment slot="content">
<div class="form">
<InputId bind:value={id} {autofocus} />
{#if isProject}
<InputProjectId bind:value={id} {autofocus} />
{:else}
<InputId bind:value={id} {autofocus} />
{/if}
</div>
</svelte:fragment>
</InnerModal>
1 change: 1 addition & 0 deletions src/lib/elements/forms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export { default as InputId } from './inputId.svelte';
export { default as InputSecret } from './inputSecret.svelte';
export { default as Helper } from './helper.svelte';
export { default as Label } from './label.svelte';
export { default as InputProjectId } from './inputProjectId.svelte';
59 changes: 59 additions & 0 deletions src/lib/elements/forms/inputProjectId.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import { onMount } from 'svelte';
import { FormItem } from '.';
import TextCounter from './textCounter.svelte';

export let value = '';
export let autofocus = true;

let element: HTMLInputElement;
let icon = 'info';
const pattern = String.raw`^[a-z0-9][a-z0-9\-]*$`;
onMount(() => {
if (element && autofocus) {
element.focus();
}
});

const handleInvalid = (event: Event) => {
event.preventDefault();

if (element.validity.patternMismatch) {
icon = 'exclamation';
return;
}
};

$: if (value) {
icon = 'info';
}
</script>

<FormItem>
<div class="input-text-wrapper">
<input
id="id"
placeholder="Enter ID"
maxlength={36}
{pattern}
autocomplete="off"
type="text"
class="input-text"
bind:value
bind:this={element}
on:invalid={handleInvalid} />
<TextCounter count={value?.length ?? 0} max={36} />
</div>
</FormItem>
<div
class="u-flex u-gap-4 u-margin-block-start-8 u-small"
class:u-color-text-warning={icon === 'exclamation'}>
<span
class:icon-info={icon === 'info'}
class:icon-exclamation={icon === 'exclamation'}
class="u-cross-center u-line-height-1 u-color-text-gray"
aria-hidden="true" />
<span class="text u-line-height-1-5">
Allowed characters: lowercase alphanumeric and non-leading hyphen
</span>
</div>
2 changes: 1 addition & 1 deletion src/routes/console/onboarding/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
</Pill>
</div>
{:else}
<CustomId bind:show={showCustomId} name="Project" bind:id />
<CustomId bind:show={showCustomId} name="Project" isProject bind:id />
{/if}
<Button fullWidth submit disabled={name === ''} event="create_project">
Create project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
</Pill>
</div>
{:else}
<CustomId bind:show={showCustomId} name="Project" bind:id />
<CustomId bind:show={showCustomId} name="Project" isProject bind:id />
{/if}
</FormList>
<svelte:fragment slot="footer">
Expand Down
40 changes: 40 additions & 0 deletions tests/unit/elements/inputProjectId.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import '@testing-library/jest-dom';
import { render } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import { InputProjectId } from '../../../src/lib/elements/forms';

const validStrings = ['validstring', 'valid-string', 'validstring123', 'valid-'];

const invalidStrings = ['-invalid', 'Valid', '_invalid', 'Valid123', 'valid.string'];

test('shows id input', () => {
const { getByPlaceholderText } = render(InputProjectId);
const input = getByPlaceholderText('Enter ID');

expect(input).toBeInTheDocument();
expect(input).toHaveAttribute('type', 'text');
});

test('state', async () => {
const { component, getByPlaceholderText } = render(InputProjectId, { value: '' });
const input = getByPlaceholderText('Enter ID');

expect(component.value).toEqual('');
await userEvent.type(input, 'lorem');
expect(component.value).toEqual('lorem');
});

validStrings.forEach((validString) => {
test(`validates ${validString} as valid`, () => {
const { getByPlaceholderText } = render(InputProjectId, { value: validString });
const input = getByPlaceholderText('Enter ID') as HTMLInputElement;
expect(input.checkValidity()).toBe(true);
});
});
invalidStrings.forEach((invalidString) => {
test(`validates ${invalidString} as invalid`, () => {
const { getByPlaceholderText } = render(InputProjectId, { value: invalidString });
const input = getByPlaceholderText('Enter ID') as HTMLInputElement;
expect(input.checkValidity()).toBe(false);
});
});