mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' into tooltip
This commit is contained in:
Generated
+2145
-2952
File diff suppressed because it is too large
Load Diff
+16
-15
@@ -23,32 +23,33 @@
|
||||
"chart.js": "^3.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.21.1",
|
||||
"@sveltejs/adapter-auto": "1.0.0-next.36",
|
||||
"@sveltejs/kit": "1.0.0-next.318",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.41",
|
||||
"@playwright/test": "^1.22.0",
|
||||
"@sveltejs/adapter-auto": "1.0.0-next.40",
|
||||
"@sveltejs/kit": "1.0.0-next.328",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.44",
|
||||
"@testing-library/dom": "^8.13.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/svelte": "^3.1.1",
|
||||
"@testing-library/user-event": "^14.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
||||
"@typescript-eslint/parser": "^5.20.0",
|
||||
"eslint": "^8.13.0",
|
||||
"@testing-library/user-event": "^14.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.23.0",
|
||||
"@typescript-eslint/parser": "^5.23.0",
|
||||
"eslint": "^8.15.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte3": "^3.4.1",
|
||||
"jest": "^27.5.1",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"jest": "^28.1.0",
|
||||
"jest-environment-jsdom": "^28.1.0",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"sass": "^1.50.1",
|
||||
"svelte": "^3.47.0",
|
||||
"sass": "^1.51.0",
|
||||
"svelte": "^3.48.0",
|
||||
"svelte-check": "^2.7.0",
|
||||
"svelte-jester": "^2.3.2",
|
||||
"svelte-preprocess": "^4.10.6",
|
||||
"ts-jest": "^27.1.4",
|
||||
"ts-jest": "^28.0.2",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^2.9.5"
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^2.9.9"
|
||||
},
|
||||
"type": "module",
|
||||
"jest": {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
export let size: number;
|
||||
export let src: string;
|
||||
export let name: string;
|
||||
</script>
|
||||
|
||||
<img
|
||||
width={size}
|
||||
height={size}
|
||||
class="avatar"
|
||||
style="--size: {size}px"
|
||||
{src}
|
||||
title={name}
|
||||
alt={name} />
|
||||
@@ -5,10 +5,6 @@
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
if (navigator.clipboard === undefined) {
|
||||
//TODO: add fallback to old methods
|
||||
throw new Error('Clipboard API only available to SSL');
|
||||
}
|
||||
await navigator.clipboard.writeText(value);
|
||||
addNotification({
|
||||
message: 'Copied to clipboard.',
|
||||
|
||||
@@ -14,3 +14,7 @@ export { default as DropListItem } from './dropListItem.svelte';
|
||||
export { default as DropListLink } from './dropListLink.svelte';
|
||||
export { default as Collapsible } from './collapsible.svelte';
|
||||
export { default as Tooltip } from './tooltip.svelte';
|
||||
export { default as Avatar } from './avatar.svelte';
|
||||
export { default as SwitchBox } from './switchBox.svelte';
|
||||
export { default as SwitchBoxes } from './switchBoxes.svelte';
|
||||
export { default as InfoSection } from './infoSection.svelte';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<div>
|
||||
<i class="icon-info-circled" />
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
type SwitchBox = {
|
||||
label: string;
|
||||
id: string;
|
||||
src: string;
|
||||
alt: string;
|
||||
href: string;
|
||||
linkText: string;
|
||||
value: boolean;
|
||||
required: boolean;
|
||||
disabled: boolean;
|
||||
wip: boolean;
|
||||
};
|
||||
|
||||
export let box: SwitchBox;
|
||||
|
||||
let { label, id, src, alt, href, linkText, disabled, required, value, wip } = box;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
//TODO: move SwitchBox type outside component
|
||||
</script>
|
||||
|
||||
<li class="card">
|
||||
<label class="switch-box" for={id}>
|
||||
<div class="switch-box-image">
|
||||
<img height="50" width="50" src={src || 'https://via.placeholder.com/50'} {alt} />
|
||||
</div>
|
||||
<span class="switch-box-title">{label}</span>
|
||||
{#if !wip}
|
||||
<a {href} class="link" target="_blank">
|
||||
<span class="text">{linkText || 'Docs'} </span>
|
||||
<span class="icon-link-ext" aria-hidden="true" />
|
||||
</a>
|
||||
<input
|
||||
{id}
|
||||
{disabled}
|
||||
{required}
|
||||
type="checkbox"
|
||||
class="switch"
|
||||
role="switch"
|
||||
bind:checked={value}
|
||||
on:change={() => dispatch('updated', { value, id })} />
|
||||
{/if}
|
||||
</label>
|
||||
</li>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
import SwitchBox from './switchBox.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let boxes;
|
||||
</script>
|
||||
|
||||
<ul class="grid-box">
|
||||
{#each boxes as box}
|
||||
<SwitchBox {box} on:updated={(e) => dispatch('updated', e.detail)} />
|
||||
{/each}
|
||||
</ul>
|
||||
@@ -9,6 +9,7 @@
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
export let autofocus = false;
|
||||
export let meter = true;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
|
||||
@@ -33,14 +34,16 @@
|
||||
class="input-text"
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
<meter
|
||||
value={strength > 100 ? 100 : strength}
|
||||
min="0"
|
||||
max="100"
|
||||
class="password-meter"
|
||||
class:is-weak={strength !== 0 && strength <= 33}
|
||||
class:is-medium={strength > 33 && strength <= 66}
|
||||
class:is-strong={strength > 66 && strength <= 100}
|
||||
aria-label="Password strength week" />
|
||||
{#if meter}
|
||||
<meter
|
||||
value={strength > 100 ? 100 : strength}
|
||||
min="0"
|
||||
max="100"
|
||||
class="password-meter"
|
||||
class:is-weak={strength !== 0 && strength <= 33}
|
||||
class:is-medium={strength > 33 && strength <= 66}
|
||||
class:is-strong={strength > 66 && strength <= 100}
|
||||
aria-label="Password strength weak" />
|
||||
{/if}
|
||||
</div>
|
||||
</FormItem>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const onKeyUp = (event: KeyboardEvent) => {
|
||||
const valueChange = (event: Event) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
@@ -32,5 +32,5 @@
|
||||
{required}
|
||||
type="search"
|
||||
class="input-text"
|
||||
on:keyup={onKeyUp}
|
||||
on:input={valueChange}
|
||||
bind:this={element} />
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<FormItem>
|
||||
<label class="label" for={id}>{label}</label>
|
||||
<div class="select">
|
||||
<select bind:value {required} {disabled}>
|
||||
<select bind:value {id} {required} {disabled}>
|
||||
{#each options as option}
|
||||
<option value={option.value} selected={option.value === value}>
|
||||
{option.label}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
export let autofocus = false;
|
||||
export let autocomplete = true;
|
||||
export let maxlength: number = null;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
@@ -29,6 +30,7 @@
|
||||
{disabled}
|
||||
{required}
|
||||
{maxlength}
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<li class="inline-links-item">
|
||||
<a class="link" href="https://github.com/appwrite/appwrite" target="_blank">
|
||||
<span class="icon-github-circled" aria-hidden="true" />
|
||||
<span class="text">GitHun</span>
|
||||
<span class="text">GitHub</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-links-item">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { DropList, DropListItem, DropListLink } from '$lib/components';
|
||||
import { DropList, DropListItem, DropListLink, Avatar } from '$lib/components';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
@@ -46,12 +46,10 @@
|
||||
<DropList bind:show={showDropdown} position="bottom" horizontal="left">
|
||||
<button class="transparent-button" on:click={() => (showDropdown = !showDropdown)}>
|
||||
<span class="is-only-desktop">{$user.name}</span>
|
||||
<img
|
||||
width="40"
|
||||
height="40"
|
||||
class="user-image"
|
||||
src={sdkForConsole.avatars.getInitials($user.name, 40, 40).toString()}
|
||||
alt="" />
|
||||
<Avatar
|
||||
size={50}
|
||||
name={$user.name}
|
||||
src={sdkForConsole.avatars.getInitials($user.name, 50, 50).toString()} />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListLink href="/console/$me" icon="user">Your Account</DropListLink>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { base } from '$app/paths';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { Back } from '$lib/components';
|
||||
import { Cover } from '$lib/layout';
|
||||
@@ -22,4 +21,5 @@
|
||||
<Tabs />
|
||||
</Cover>
|
||||
{/if}
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<div aria-busy="true" />
|
||||
{:then response}
|
||||
{#if response.total}
|
||||
<p>{response.total} documents found</p>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{#each columns as column}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { base } from '$app/paths';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { Back } from '$lib/components';
|
||||
import { Cover } from '$lib/layout';
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<div aria-busy="true" />
|
||||
{:then response}
|
||||
{#if response.total}
|
||||
<p>{response.total} functions found</p>
|
||||
<Tiles>
|
||||
{#each response.functions as func}
|
||||
<Tile
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script>
|
||||
import { base } from '$app/paths';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import { Back } from '$lib/components';
|
||||
import { Cover } from '$lib/layout';
|
||||
const projectId = $page.params.project;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { base } from '$app/paths';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { Back } from '$lib/components';
|
||||
import { Cover } from '$lib/layout';
|
||||
@@ -20,5 +19,4 @@
|
||||
<svelte:fragment slot="title">Settings</svelte:fragment>
|
||||
<Tabs />
|
||||
</Cover>
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<div aria-busy="true" />
|
||||
{:then response}
|
||||
{#if response.total}
|
||||
<p>{response.total} files found</p>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead width={30} />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { Back } from '$lib/components';
|
||||
import { Cover } from '$lib/layout';
|
||||
@@ -21,5 +20,4 @@
|
||||
<Tabs />
|
||||
</Cover>
|
||||
{/if}
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { Modal, InfoSection } from '$lib/components';
|
||||
import { Button, InputNumber, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { project } from '../store';
|
||||
|
||||
export let showUserLimitModal = false;
|
||||
export let authLimit: number;
|
||||
|
||||
const projectId = $project.$id;
|
||||
let userLimit: number = $project.authLimit;
|
||||
|
||||
const update = async () => {
|
||||
try {
|
||||
const oauth = await sdkForConsole.projects.updateAuthLimit(projectId, userLimit);
|
||||
console.log(oauth);
|
||||
authLimit = userLimit;
|
||||
showUserLimitModal = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Updated project users limit successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={update}>
|
||||
<Modal bind:show={showUserLimitModal}>
|
||||
<svelte:fragment slot="header">Max Allowed Users</svelte:fragment>
|
||||
<InputNumber id="userLimit" label="User Limit" autofocus={true} bind:value={userLimit} />
|
||||
<InfoSection>
|
||||
<p>
|
||||
This limit will prevent new users from signing up for your project, no matter what
|
||||
auth method has been used. You will still be able to create users and team
|
||||
memberships from your Appwrite console. For an unlimited amount of users, set the
|
||||
limit to 0. Max limit is 10,000.
|
||||
</p>
|
||||
</InfoSection>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit>Update</Button>
|
||||
<Button secondary on:click={() => (showUserLimitModal = false)}>Cancel</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Modal, Copy, InfoSection } from '$lib/components';
|
||||
import { Button, InputPassword, InputText, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
|
||||
export let showModal = false;
|
||||
export let provider: string;
|
||||
let appId: string, secret: string;
|
||||
const projectId = $page.params.project;
|
||||
let redirectURI = `${
|
||||
sdkForConsole.config.endpoint
|
||||
}/account/session/oauth2/callback/${provider.toLocaleLowerCase()}/${projectId}`;
|
||||
|
||||
const update = async () => {
|
||||
try {
|
||||
const oauth = await sdkForConsole.projects.updateOAuth2(
|
||||
projectId,
|
||||
provider,
|
||||
appId,
|
||||
secret
|
||||
);
|
||||
console.log(oauth);
|
||||
showModal = false;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={update}>
|
||||
<Modal bind:show={showModal}>
|
||||
<svelte:fragment slot="header">{provider} OAuth2 Settings</svelte:fragment>
|
||||
<InputText
|
||||
id="appId"
|
||||
label="App Id"
|
||||
autofocus={true}
|
||||
autocomplete={false}
|
||||
bind:value={appId} />
|
||||
<InputPassword id="secret" label="App Secret" meter={false} bind:value={secret} />
|
||||
<InfoSection>
|
||||
<p>
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider} app configuration.
|
||||
</p>
|
||||
<Copy bind:value={redirectURI} />
|
||||
</InfoSection>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit>Update</Button>
|
||||
<Button secondary on:click={() => (showModal = false)}>Cancel</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -44,6 +44,7 @@
|
||||
<div aria-busy="true" />
|
||||
{:then response}
|
||||
{#if response.total}
|
||||
<p>{response.total} users found</p>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead width={30} />
|
||||
|
||||
@@ -1,10 +1,125 @@
|
||||
<script>
|
||||
import { Card } from '$lib/components';
|
||||
<script lang="ts">
|
||||
import { SwitchBoxes } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import Toggle from './_toggleOAuth.svelte';
|
||||
import SetUserLimit from './_setUserLimit.svelte';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { project } from '../store';
|
||||
|
||||
let showModal = false;
|
||||
let showUserLimitModal = false;
|
||||
const projectId = $project.$id;
|
||||
let provider: string;
|
||||
let authLimit = $project.authLimit;
|
||||
|
||||
const authUpdate = async (event) => {
|
||||
try {
|
||||
await sdkForConsole.projects.updateAuthStatus(
|
||||
projectId,
|
||||
event.detail.id,
|
||||
event.detail.value
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Updated project auth status successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const providerUpdate = async (event) => {
|
||||
provider = event.detail.id;
|
||||
showModal = true;
|
||||
};
|
||||
|
||||
let authBoxes = [
|
||||
{
|
||||
label: 'Email/Password',
|
||||
id: 'email-password',
|
||||
href: 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateSession',
|
||||
value: $project.authEmailPassword
|
||||
},
|
||||
{
|
||||
label: 'Magic URL',
|
||||
id: 'magic-url',
|
||||
href: 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateMagicURLSession',
|
||||
value: $project.authUsersAuthMagicURL
|
||||
},
|
||||
{
|
||||
label: 'Anonymous',
|
||||
id: 'anonymous',
|
||||
href: 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateAnonymousSession',
|
||||
value: $project.authAnonymous
|
||||
},
|
||||
{
|
||||
label: 'Invites',
|
||||
id: 'invites',
|
||||
href: 'https://appwrite.io/docs/client/teams?sdk=web-default#teamsCreateMembership',
|
||||
value: $project.authInvites
|
||||
},
|
||||
{
|
||||
label: 'JWT',
|
||||
id: 'jwt',
|
||||
href: 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateJWT',
|
||||
value: $project.authJWT
|
||||
},
|
||||
{
|
||||
label: 'Phone (soon)',
|
||||
id: 'phone',
|
||||
value: false,
|
||||
wip: true
|
||||
}
|
||||
];
|
||||
|
||||
let providersBoxes = [
|
||||
{
|
||||
label: 'Amazon',
|
||||
id: 'amazon',
|
||||
href: 'https://developer.amazon.com/apps-and-games/services-and-apis',
|
||||
linkText: 'OAuth2 Docs'
|
||||
},
|
||||
{
|
||||
label: 'Apple',
|
||||
id: 'apple',
|
||||
href: 'https://developer.amazon.com/apps-and-games/services-and-apis',
|
||||
linkText: 'OAuth2 Docs'
|
||||
},
|
||||
{
|
||||
label: 'BitBucket',
|
||||
id: 'bitbucket',
|
||||
href: 'https://developer.amazon.com/apps-and-games/services-and-apis',
|
||||
linkText: 'OAuth2 Docs'
|
||||
}
|
||||
];
|
||||
|
||||
//TODO: move authBoxes and providersBoxes to a store
|
||||
//TODO: if operation not successful revert switchbox value
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<h1>Settings</h1>
|
||||
|
||||
<Card />
|
||||
<p>
|
||||
{authLimit ? `${authLimit} Users allowed` : 'Unlimited Users '}
|
||||
<button
|
||||
on:click={() => {
|
||||
showUserLimitModal = true;
|
||||
}}
|
||||
class=" is-text link"
|
||||
><span class="text">{authLimit ? 'Change Limit' : 'Set Limit'}</span></button>
|
||||
</p>
|
||||
<h2>Settings</h2>
|
||||
<p>Choose auth methods you wish to use.</p>
|
||||
<SwitchBoxes boxes={authBoxes} on:updated={authUpdate} />
|
||||
<h3>OAuth2 Providers</h3>
|
||||
<SwitchBoxes boxes={providersBoxes} on:updated={providerUpdate} />
|
||||
</Container>
|
||||
|
||||
{#if provider && showModal}
|
||||
<Toggle {provider} bind:showModal />
|
||||
{/if}
|
||||
|
||||
<SetUserLimit bind:authLimit bind:showUserLimitModal />
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<div aria-busy="true" />
|
||||
{:then response}
|
||||
{#if response.total}
|
||||
<p>{response.total} teams found</p>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead width={30} />
|
||||
|
||||
+50
-1
@@ -1,9 +1,58 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('login page has inputs', async ({ page }) => {
|
||||
/*TODO: Things to test in login:
|
||||
- presence of forgot password link
|
||||
- validation message on wrong input
|
||||
- correct response and redirect after login
|
||||
- logout works
|
||||
- back button does not log out user
|
||||
- forward button does not log in user after logout
|
||||
- limit to total number of login attempts
|
||||
-
|
||||
*/
|
||||
|
||||
test('login page has inputs and button', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
const mail = page.locator('id=email');
|
||||
const pass = page.locator('id=password');
|
||||
const button = page.locator('button:has-text("Login")');
|
||||
expect(await mail.isVisible());
|
||||
expect(await pass.isVisible());
|
||||
expect(await button.isVisible());
|
||||
});
|
||||
|
||||
test('login page has a working sign up link', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
const signup = page.locator('a[href="/register"]');
|
||||
expect(await signup.isVisible());
|
||||
await signup.click();
|
||||
await page.waitForNavigation();
|
||||
expect(page.url()).toContain('/register');
|
||||
expect(await page.locator('Register').isVisible());
|
||||
});
|
||||
|
||||
test('login page inputs are navigable by keyboard', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
const mail = page.locator('id=email');
|
||||
await mail.focus();
|
||||
await page.keyboard.type('wrongemail@apppwrite.io');
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.type('password');
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Enter');
|
||||
expect(await page.locator('.toaster-item').isVisible());
|
||||
expect(await page.locator('text=Invalid credentials').isVisible());
|
||||
});
|
||||
|
||||
test('login page shows error & response is 401 with wrong inputs', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
await page.fill('id=email', 'wrongemail@apppwrite.io');
|
||||
await page.fill('id=password', 'wrongpassword');
|
||||
await page.click('button:has-text("Login")');
|
||||
page.on('response', (response) => {
|
||||
expect(response.status()).toBe(401);
|
||||
});
|
||||
expect(await page.locator('.toaster-item').isVisible());
|
||||
expect(await page.locator('text=Invalid credentials').isVisible());
|
||||
});
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import { Copy } from '../../../src/lib/components';
|
||||
|
||||
const value = 'This is a test';
|
||||
|
||||
test('shows copy component', () => {
|
||||
const { getByRole } = render(Copy, { value });
|
||||
const input = document.querySelector('input');
|
||||
const button = getByRole('button');
|
||||
|
||||
expect(input).toBeInTheDocument();
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(input).toHaveAttribute('type', 'text');
|
||||
expect(input).toBeDisabled();
|
||||
});
|
||||
|
||||
test('copy to clipboard function called on click', async () => {
|
||||
const { getByRole } = render(Copy, { value });
|
||||
|
||||
Object.assign(window.navigator, {
|
||||
clipboard: {
|
||||
writeText: jest.fn().mockImplementation(() => Promise.resolve())
|
||||
}
|
||||
});
|
||||
|
||||
const button = getByRole('button');
|
||||
await fireEvent.click(button);
|
||||
|
||||
expect(window.navigator.clipboard.writeText).toHaveBeenCalledWith('This is a test');
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import { DropList } from '../../../src/lib/components';
|
||||
|
||||
const data = {
|
||||
show: true,
|
||||
position: 'top',
|
||||
horizontal: 'right'
|
||||
};
|
||||
|
||||
test('shows drop list component', () => {
|
||||
render(DropList, { ...data });
|
||||
const wrapper = document.querySelector('div');
|
||||
const dropList = document.querySelector('ul');
|
||||
|
||||
expect(wrapper).toBeInTheDocument();
|
||||
expect(dropList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('hide drop list on body click', async () => {
|
||||
render(DropList, { ...data });
|
||||
const body = document.querySelector('body');
|
||||
const dropList = document.querySelector('ul');
|
||||
|
||||
await fireEvent.click(body);
|
||||
expect(dropList).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('hide drop list on wrapper click', async () => {
|
||||
render(DropList, { ...data });
|
||||
const wrapper = document.querySelector('div');
|
||||
const dropList = document.querySelector('ul');
|
||||
|
||||
await fireEvent.click(wrapper);
|
||||
expect(dropList).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('drop list visible on list click', async () => {
|
||||
render(DropList, { ...data });
|
||||
const dropList = document.querySelector('ul');
|
||||
|
||||
await fireEvent.click(dropList);
|
||||
expect(dropList).toBeInTheDocument();
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import { SwitchBox } from '../../../src/lib/components';
|
||||
|
||||
const box = {
|
||||
id: 'input',
|
||||
src: 'https://via.placeholder.com/50',
|
||||
label: 'Bool',
|
||||
alt: 'image',
|
||||
required: false,
|
||||
disabled: false,
|
||||
value: false
|
||||
};
|
||||
|
||||
test('shows boolean input', () => {
|
||||
const { getByText, getByRole, getByAltText } = render(SwitchBox, {
|
||||
box
|
||||
});
|
||||
const checkbox = getByRole('switch');
|
||||
const img = getByAltText('image');
|
||||
|
||||
expect(getByText('Bool')).toBeInTheDocument();
|
||||
expect(img).toBeInTheDocument();
|
||||
expect(checkbox).toBeInTheDocument();
|
||||
expect(checkbox).toHaveAttribute('type', 'checkbox');
|
||||
});
|
||||
|
||||
test('shows boolean input - required', () => {
|
||||
box.required = true;
|
||||
const { getByRole } = render(SwitchBox, { box });
|
||||
|
||||
expect(getByRole('switch')).toBeRequired();
|
||||
});
|
||||
|
||||
test('shows boolean input - disabled', () => {
|
||||
box.disabled = true;
|
||||
const { getByRole } = render(SwitchBox, { box });
|
||||
|
||||
expect(getByRole('switch')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('state', async () => {
|
||||
const { getByRole, component } = render(SwitchBox, { box });
|
||||
const checkbox = getByRole('switch');
|
||||
|
||||
setTimeout(() => {
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(component.box.value).toStrictEqual(false);
|
||||
}, 1);
|
||||
|
||||
await fireEvent.click(checkbox);
|
||||
setTimeout(() => {
|
||||
expect(checkbox).toBeChecked();
|
||||
expect(component.box.value).toStrictEqual(true);
|
||||
}, 1);
|
||||
|
||||
await fireEvent.click(checkbox);
|
||||
setTimeout(() => {
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(component.box.value).toStrictEqual(false);
|
||||
}, 1);
|
||||
|
||||
component.box.value = true;
|
||||
setTimeout(() => {
|
||||
expect(checkbox).toBeChecked();
|
||||
expect(component.box.value).toStrictEqual(true);
|
||||
}, 1);
|
||||
|
||||
component.box.value = false;
|
||||
setTimeout(() => {
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(component.box.value).toStrictEqual(false);
|
||||
}, 1);
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { InputCheckbox } from '../../../src/lib/elements/forms';
|
||||
|
||||
test('shows checkbox input', () => {
|
||||
const { getByText, getByLabelText } = render(InputCheckbox, { id: 'input', label: 'checkbox' });
|
||||
const checkbox = getByLabelText('checkbox');
|
||||
|
||||
expect(getByText('checkbox')).toBeInTheDocument();
|
||||
expect(checkbox).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows checkbox input - required', () => {
|
||||
const { getByLabelText } = render(InputCheckbox, {
|
||||
id: 'input',
|
||||
label: 'checkbox',
|
||||
required: true
|
||||
});
|
||||
|
||||
expect(getByLabelText('checkbox')).toBeRequired();
|
||||
});
|
||||
|
||||
test('shows checkbox input - disabled', () => {
|
||||
const { getByLabelText } = render(InputCheckbox, {
|
||||
id: 'input',
|
||||
label: 'checkbox',
|
||||
disabled: true
|
||||
});
|
||||
|
||||
expect(getByLabelText('checkbox')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('state', async () => {
|
||||
const { component, getByLabelText } = render(InputCheckbox, { id: 'input', label: 'checkbox' });
|
||||
const checkbox = getByLabelText('checkbox');
|
||||
|
||||
expect(component.value).toEqual(false);
|
||||
await userEvent.click(checkbox);
|
||||
expect(component.value).toEqual(true);
|
||||
});
|
||||
|
||||
test('state', async () => {
|
||||
const { getByLabelText, component } = render(InputCheckbox, { id: 'input', label: 'checkbox' });
|
||||
const checkbox = getByLabelText('checkbox');
|
||||
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(component.value).toStrictEqual(false);
|
||||
|
||||
await fireEvent.click(checkbox);
|
||||
expect(checkbox).toBeChecked();
|
||||
expect(component.value).toStrictEqual(true);
|
||||
|
||||
await fireEvent.click(checkbox);
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(component.value).toStrictEqual(false);
|
||||
|
||||
component.value = true;
|
||||
expect(checkbox).toBeChecked();
|
||||
expect(component.value).toStrictEqual(true);
|
||||
|
||||
component.value = false;
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(component.value).toStrictEqual(false);
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import { InputRadio } from '../../../src/lib/elements/forms';
|
||||
import radioGroup from './radioGroup.test.svelte';
|
||||
|
||||
const data = {
|
||||
id: 'radio',
|
||||
label: 'radio',
|
||||
group: 'radio',
|
||||
value: 'radio',
|
||||
name: 'radio'
|
||||
};
|
||||
|
||||
test('shows label', () => {
|
||||
const { getByText } = render(InputRadio, { ...data });
|
||||
|
||||
expect(getByText('radio')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows boolean input - required', () => {
|
||||
const { getByRole } = render(InputRadio, { ...data, required: true });
|
||||
|
||||
expect(getByRole('radio')).toBeRequired();
|
||||
});
|
||||
|
||||
test('shows boolean input - disabled', () => {
|
||||
const { getByRole } = render(InputRadio, { ...data, disabled: true });
|
||||
|
||||
expect(getByRole('radio')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('state', async () => {
|
||||
const { getByLabelText } = render(radioGroup);
|
||||
|
||||
const one = getByLabelText('one');
|
||||
const two = getByLabelText('two');
|
||||
const three = getByLabelText('three');
|
||||
|
||||
await fireEvent.click(one);
|
||||
expect(one).toBeChecked();
|
||||
expect(two).not.toBeChecked();
|
||||
expect(three).not.toBeChecked();
|
||||
|
||||
await fireEvent.click(two);
|
||||
expect(one).not.toBeChecked();
|
||||
expect(two).toBeChecked();
|
||||
expect(three).not.toBeChecked();
|
||||
|
||||
await fireEvent.click(three);
|
||||
expect(one).not.toBeChecked();
|
||||
expect(two).not.toBeChecked();
|
||||
expect(three).toBeChecked();
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/svelte';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { InputSearch } from '../../../src/lib/elements/forms';
|
||||
|
||||
test('shows search input', () => {
|
||||
const { getByPlaceholderText } = render(InputSearch, { placeholder: 'search' });
|
||||
|
||||
expect(getByPlaceholderText('search')).toBeInTheDocument();
|
||||
expect(getByPlaceholderText('search')).toHaveAttribute('type', 'search');
|
||||
});
|
||||
|
||||
test('shows input search - required', () => {
|
||||
const { getByPlaceholderText } = render(InputSearch, { placeholder: 'search', required: true });
|
||||
|
||||
expect(getByPlaceholderText('search')).toBeRequired();
|
||||
});
|
||||
|
||||
test('shows input search - disabled', () => {
|
||||
const { getByPlaceholderText } = render(InputSearch, { placeholder: 'search', disabled: true });
|
||||
|
||||
expect(getByPlaceholderText('search')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('shows input search - autofocus', () => {
|
||||
const { getByPlaceholderText } = render(InputSearch, {
|
||||
placeholder: 'search',
|
||||
autofocus: true
|
||||
});
|
||||
|
||||
expect(getByPlaceholderText('search')).toHaveFocus();
|
||||
});
|
||||
|
||||
test('shows input search - placeholder', () => {
|
||||
const { getByPlaceholderText } = render(InputSearch, {
|
||||
placeholder: 'find me'
|
||||
});
|
||||
|
||||
expect(getByPlaceholderText('find me')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('state change after debounce', async () => {
|
||||
const debounce = 250;
|
||||
const { component, getByPlaceholderText } = render(InputSearch, {
|
||||
placeholder: 'search',
|
||||
value: '',
|
||||
debounce
|
||||
});
|
||||
const input = getByPlaceholderText('search');
|
||||
|
||||
expect(component.value).toEqual('');
|
||||
await userEvent.type(input, 'lorem');
|
||||
expect(component.value).not.toEqual('lorem');
|
||||
setTimeout(() => {
|
||||
expect(component.value).toEqual('lorem');
|
||||
}, debounce);
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/svelte';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { InputSelect } from '../../../src/lib/elements/forms';
|
||||
|
||||
const options = [
|
||||
{ value: '', label: 'None' },
|
||||
{ value: '1', label: 'One' },
|
||||
{ value: '2', label: 'Two' },
|
||||
{ value: '3', label: 'Three' }
|
||||
];
|
||||
|
||||
test('shows select input', () => {
|
||||
const { getByText, getByLabelText } = render(InputSelect, {
|
||||
id: 'select',
|
||||
label: 'select',
|
||||
options
|
||||
});
|
||||
const select = getByLabelText('select');
|
||||
|
||||
expect(getByText('select')).toBeInTheDocument();
|
||||
expect(select).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows options', () => {
|
||||
const { getByText } = render(InputSelect, {
|
||||
id: 'select',
|
||||
label: 'select',
|
||||
options
|
||||
});
|
||||
|
||||
options.forEach((option) => {
|
||||
expect(getByText(option.label)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('shows select input - required', () => {
|
||||
const { getByLabelText } = render(InputSelect, {
|
||||
id: 'select',
|
||||
label: 'select',
|
||||
options,
|
||||
required: true
|
||||
});
|
||||
|
||||
expect(getByLabelText('select')).toBeRequired();
|
||||
});
|
||||
|
||||
test('shows select input - disabled', () => {
|
||||
const { getByLabelText } = render(InputSelect, {
|
||||
id: 'select',
|
||||
label: 'select',
|
||||
options,
|
||||
disabled: true
|
||||
});
|
||||
|
||||
expect(getByLabelText('select')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('state', async () => {
|
||||
const { component, getByLabelText } = render(InputSelect, {
|
||||
id: 'select',
|
||||
label: 'select',
|
||||
options,
|
||||
value: ''
|
||||
});
|
||||
const select = getByLabelText('select');
|
||||
|
||||
expect(component.value).toEqual('');
|
||||
|
||||
await userEvent.selectOptions(select, '1');
|
||||
expect(component.value).toEqual(options[1].value);
|
||||
|
||||
await userEvent.selectOptions(select, '2');
|
||||
expect(component.value).toEqual(options[2].value);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import { InputRadio } from '../../../src/lib/elements/forms';
|
||||
</script>
|
||||
|
||||
<InputRadio label="one" id="one" group="radio" value="1" name="radio" />
|
||||
<InputRadio label="two" id="two" group="radio" value="2" name="radio" />
|
||||
<InputRadio label="three" id="three" group="radio" value="3" name="radio" />
|
||||
Reference in New Issue
Block a user