feat: bringing it all together

This is now showing the MainMenu on the profile page which in turn
pulls in the Settings modal.

I wasn't able to register the modal on Storybook yet, which means
that there won't be any on the Profile template or page story.

The implementation in Skeleton is causing some accessibility
warnings. However, since there next version is taking a radical
different approach I'm inclined to let it be for now. We should
document it somewhere, I guess.

Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
This commit is contained in:
André Jaenisch 2024-07-27 14:12:08 +02:00
parent 581ab76ca7
commit f20587cf1f
No known key found for this signature in database
GPG key ID: 5A668E771F1ED854
12 changed files with 639 additions and 259 deletions

View file

@ -12,33 +12,19 @@ You should have received a copy of the GNU Affero General Public License along w
-->
<script>
import {
AppRail,
AppRailAnchor,
AppRailTile,
Drawer,
getDrawerStore
} from '@skeletonlabs/skeleton';
import { AppRail, AppRailAnchor, AppRailTile } from '@skeletonlabs/skeleton';
import { createEventDispatcher } from 'svelte';
import { _, locale, locales } from 'svelte-i18n';
import { Bell24, Gear24, Key24, Paintbrush24, Person24 } from 'svelte-octicons';
let currentTile = 0;
const drawerStore = getDrawerStore();
const dispatcher = createEventDispatcher();
function onTileChanged(ev) {
const settings = { id: ev.target.value };
drawerStore.open(settings);
dispatcher('switch-settings', ev.target.value);
}
</script>
<Drawer>
{#if $drawerStore.id === 'profile'}
<b>Profile settings go here</b>
{:else}
{$drawerStore.id}
{/if}
</Drawer>
<AppRail aspectRatio="auto" gap="gap-8" height="h-auto" width="w-[230px]" spacing="my-2">
<svelte:fragment slot="lead">
<AppRailTile

View file

@ -42,7 +42,8 @@ You should have received a copy of the GNU Affero General Public License along w
<div class="flex flex-col bg-white border-surface-300 rounded-border px-4">
<div class="flex gap-4 bg-white">
<Code24 fill="currentColor" />
<span class="text-surface-500 -mb-2 flex-1">{$_('page.profile.menu.details.repository')}</span>
<span class="text-surface-500 -mb-2 flex-1">{$_('page.profile.menu.details.repository')}</span
>
<ChevronDown24 fill="currentColor" />
</div>
<div class="flex flex-col ps-4">
@ -52,7 +53,8 @@ You should have received a copy of the GNU Affero General Public License along w
</div>
<div class="flex gap-4 bg-white">
<GitBranch24 fill="currentColor" />
<span class="text-surface-500 -mb-2 flex-1">{$_('page.profile.menu.details.branches')}</span>
<span class="text-surface-500 -mb-2 flex-1">{$_('page.profile.menu.details.branches')}</span
>
</div>
<div class="flex gap-4 bg-white">
<GitCommit24 fill="currentColor" />
@ -70,9 +72,10 @@ You should have received a copy of the GNU Affero General Public License along w
</div>
<div class="flex gap-4 bg-white">
<GitPullRequest24 fill="currentColor" />
<span class="text-surface-500 -mb-2 flex-1">{$_('page.profile.menu.details.merge_requests')}</span>
<span
class="text-surface-500 text-center bg-secondary-200 rounded-full min-w-6 self-baseline"
<span class="text-surface-500 -mb-2 flex-1"
>{$_('page.profile.menu.details.merge_requests')}</span
>
<span class="text-surface-500 text-center bg-secondary-200 rounded-full min-w-6 self-baseline"
>2</span
>
</div>
@ -88,7 +91,9 @@ You should have received a copy of the GNU Affero General Public License along w
</div>
<div class="flex gap-4 bg-white">
<CommentDiscussion24 fill="currentColor" />
<span class="text-surface-500 -mb-2 flex-1">{$_('page.profile.menu.details.moderation')}</span>
<span class="text-surface-500 -mb-2 flex-1"
>{$_('page.profile.menu.details.moderation')}</span
>
</div>
</div>
<div class="flex gap-4 bg-white">
@ -98,4 +103,3 @@ You should have received a copy of the GNU Affero General Public License along w
</div>
</div>
</div>

View file

@ -14,9 +14,18 @@ You should have received a copy of the GNU Affero General Public License along w
<script>
import SettingsSidebar from '../atoms/SettingsSidebar.svelte';
import SettingsProfile from './SettingsProfile.svelte';
let activeSetting = 'profile';
function onTileChanged(ev) {
activeSetting = ev.detail;
}
</script>
<div class="flex bg-surface-50">
<SettingsSidebar />
<SettingsProfile />
<SettingsSidebar on:switch-settings={onTileChanged} />
{#if activeSetting === 'profile'}
<SettingsProfile />
{/if}
</div>

View file

@ -20,6 +20,7 @@ You should have received a copy of the GNU Affero General Public License along w
import Created from '../atoms/Created.svelte';
import DisplayName from '../atoms/DisplayName.svelte';
import History from '../molecules/History.svelte';
import MainMenu from '../molecules/MainMenu.svelte';
import Project from '../molecules/Project.svelte';
/**
@ -71,129 +72,140 @@ You should have received a copy of the GNU Affero General Public License along w
};
</script>
<section class="w-full mx-auto flex px-8 pt-8">
<!-- Profile header -->
<div>
<Avatar avatar={data.user.avatar} displayName={data.user.display_name} />
</div>
<!-- Board -->
<div class="flex flex-1 flex-col ps-8">
<div class="self-end">
<Created created_at={data.user.created_at} locale={data.locale} />
<BlockOrReport />
</div>
<!-- Top Row -->
<div class="flex mt-2">
<!-- Board -->
<div class="flex flex-col flex-1 gap-2">
<div class="flex items-end">
<DisplayName displayName={data.user.display_name} {i18n} pronoun={data.user.pronoun} />
</div>
<span class="leading-tight">
{data.user.username}@{data.user.instance}
</span>
<div class="flex h-full w-full">
<aside class="w-[400px]">
<MainMenu />
</aside>
<main>
<section class="w-full mx-auto flex px-8 pt-8">
<!-- Profile header -->
<div>
<Avatar avatar={data.user.avatar} displayName={data.user.display_name} />
</div>
<!-- Interaction Links -->
<button type="button" class="btn-icon border rounded-none self-start">
<Star16 fill="currentColor" />
</button>
</div>
</div>
</section>
<section class="w-full h-full mx-auto flex flex-wrap mt-8 px-8">
<!-- Board -->
<div class="flex flex-1 flex-col px-4">
<div class="space-y-6">
<h2 class="h2 font-semibold leading-tight text-base px-4">
{$_('page.profile.projects.heading')}
</h2>
{#if data?.user?.followingsMap}
<ul>
{#each data.user.followingsMap as following}
{#if following.type === 'Project'}
<li>
<Project
collaborators={mapCollaboratorsToMember(
[data.user.person],
data.user.collaboratorsMap,
following.id
)}
{i18n}
project={following}
/>
</li>
{/if}
{/each}
</ul>
<!-- Board -->
<div class="flex flex-1 flex-col ps-8">
<div class="self-end">
<Created created_at={data.user.created_at} locale={data.locale} />
<BlockOrReport />
</div>
<!-- Top Row -->
<div class="flex mt-2">
<!-- Board -->
<div class="flex flex-col flex-1 gap-2">
<div class="flex items-end">
<DisplayName
displayName={data.user.display_name}
{i18n}
pronoun={data.user.pronoun}
/>
</div>
<span class="leading-tight">
{data.user.username}@{data.user.instance}
</span>
</div>
<!-- Interaction Links -->
<button type="button" class="btn-icon border rounded-none self-start">
<Star16 fill="currentColor" />
</button>
</div>
</div>
</section>
<section class="w-full h-full mx-auto flex flex-wrap mt-8 px-8">
<!-- Board -->
<div class="flex flex-1 flex-col px-4">
<div class="space-y-6">
<h2 class="h2 font-semibold leading-tight text-base px-4">
{$_('page.profile.projects.heading')}
</h2>
{#if data?.user?.followingsMap}
<ul>
{#each data.user.followingsMap as following}
{#if following.type === 'Project'}
<li>
<Project
collaborators={mapCollaboratorsToMember(
[data.user.person],
data.user.collaboratorsMap,
following.id
)}
{i18n}
project={following}
/>
</li>
{/if}
{/each}
</ul>
<h2 class="h2 font-semibold leading-tight text-base px-4">
{$_('page.profile.repositories.heading')}
</h2>
<section class="flex flex-col gap-4">
{#each data.user.followingsMap as following}
{#if following.type === 'Repository'}
<div class="border border-surface-100">
<Project collaborators={[]} {i18n} project={following} />
</div>
{/if}
{/each}
</section>
<div>
<p>The following is DEBUG information and will be removed in the near future.</p>
{#if data?.user}
<details>
<summary>Person</summary>
<pre>{JSON.stringify(data.user.person, null, 2)}</pre>
</details>
<details>
<summary>Followings</summary>
<pre>{JSON.stringify(data.user.followings, null, 2)}</pre>
</details>
<details>
<summary>Followings mapped</summary>
<p>These are the contents of the items in the previous dump.</p>
<pre>{JSON.stringify(data.user.followingsMap, null, 2)}</pre>
</details>
<details>
<summary>Collaborators mapped</summary>
<p>These are the contents of the items in the previous dump.</p>
<pre>{JSON.stringify(data.user.collaboratorsMap, null, 2)}</pre>
</details>
<details>
<summary>Commits</summary>
<p>These are the contents of the items in the previous dump.</p>
<pre>{JSON.stringify(data.user.commits, null, 2)}</pre>
</details>
<details>
<summary>Commits mapped</summary>
<p>These are the contents of the items in the previous dump.</p>
<pre>{JSON.stringify(data.user.commitsMap, null, 2)}</pre>
</details>
<h2 class="h2 font-semibold leading-tight text-base px-4">
{$_('page.profile.repositories.heading')}
</h2>
<section class="flex flex-col gap-4">
{#each data.user.followingsMap as following}
{#if following.type === 'Repository'}
<div class="border border-surface-100">
<Project collaborators={[]} {i18n} project={following} />
</div>
{/if}
{/each}
</section>
<div>
<p>The following is DEBUG information and will be removed in the near future.</p>
{#if data?.user}
<details>
<summary>Person</summary>
<pre>{JSON.stringify(data.user.person, null, 2)}</pre>
</details>
<details>
<summary>Followings</summary>
<pre>{JSON.stringify(data.user.followings, null, 2)}</pre>
</details>
<details>
<summary>Followings mapped</summary>
<p>These are the contents of the items in the previous dump.</p>
<pre>{JSON.stringify(data.user.followingsMap, null, 2)}</pre>
</details>
<details>
<summary>Collaborators mapped</summary>
<p>These are the contents of the items in the previous dump.</p>
<pre>{JSON.stringify(data.user.collaboratorsMap, null, 2)}</pre>
</details>
<details>
<summary>Commits</summary>
<p>These are the contents of the items in the previous dump.</p>
<pre>{JSON.stringify(data.user.commits, null, 2)}</pre>
</details>
<details>
<summary>Commits mapped</summary>
<p>These are the contents of the items in the previous dump.</p>
<pre>{JSON.stringify(data.user.commitsMap, null, 2)}</pre>
</details>
{/if}
</div>
{:else}
<p class="px-4">
{$_('page.profile.projects.empty')}
{@html $_('page.profile.projects.add_or_import', {
values: {
addElementOpen: '<a class="anchor" href="#">',
addElementClose: '</a>',
importElementOpen: `<a class="anchor" href="/projects/import/" data-sb-kind=${sb}>`,
importElementClose: '</a>'
}
})}
</p>
{/if}
</div>
{:else}
<p class="px-4">
{$_('page.profile.projects.empty')}
{@html $_('page.profile.projects.add_or_import', {
values: {
addElementOpen: '<a class="anchor" href="#">',
addElementClose: '</a>',
importElementOpen: `<a class="anchor" href="/projects/import/" data-sb-kind=${sb}>`,
importElementClose: '</a>'
}
})}
</p>
{/if}
</div>
</div>
<!-- History -->
<div class="flex flex-1 flex-col px-4">
<div class="space-y-6">
<History
commitsMap={data?.user?.commitsMap || []}
{data}
followingsMap={data?.user?.followingsMap || []}
/>
</div>
</div>
</section>
</div>
<!-- History -->
<div class="flex flex-1 flex-col px-4">
<div class="space-y-6">
<History
commitsMap={data?.user?.commitsMap || []}
{data}
followingsMap={data?.user?.followingsMap || []}
/>
</div>
</div>
</section>
</main>
</div>

View file

@ -7,8 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<script>
import { locale } from 'svelte-i18n';
import { initializeStores } from '@skeletonlabs/skeleton';
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
import { initializeStores, storePopup, Modal } from '@skeletonlabs/skeleton';
import Settings from '$lib/components/molecules/Settings.svelte';
import { mapLocaleToDir } from '$lib/i18n';
// The ordering of these imports is critical to your app working properly
@ -25,8 +27,14 @@ SPDX-License-Identifier: AGPL-3.0-or-later
}
});
const modalRegistry = {
settings: { ref: Settings }
};
initializeStores();
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
</script>
<Modal components={modalRegistry} />
<!-- Page Route Content -->
<slot />

View file

@ -0,0 +1,26 @@
/* Stories for Settings molecule.
* Copyright (C) 2024 André Jaenisch
* SPDX-FileCopyrightText: 2024 André Jaenisch
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type { Meta, StoryObj } from '@storybook/svelte';
import Settings from '$lib/components/molecules/Settings.svelte';
const meta = {
title: 'Molecules/Settings',
component: Settings,
tags: ['autodocs']
} satisfies Meta<Settings>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Plain: Story = {};

View file

@ -14,7 +14,7 @@ import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floati
import { storePopup } from '@skeletonlabs/skeleton';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/svelte';
import { get, readable } from 'svelte/store';
import { readable } from 'svelte/store';
import { init, locale, register } from 'svelte-i18n';
import MainMenuSummary from '../../../src/lib/components/atoms/MainMenuSummary.svelte';

View file

@ -27,13 +27,10 @@ describe('SettingsSidebar.svelte', () => {
it('should mount', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const drawerStore = readable([]);
// Nothing to prepare
// Act
const { container } = render(SettingsSidebar, {
context: new Map([['drawerStore', drawerStore]])
});
const { container } = render(SettingsSidebar);
// Assert
expect(container).toBeTruthy();
@ -41,13 +38,10 @@ describe('SettingsSidebar.svelte', () => {
it('should have a headline', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const drawerStore = readable([]);
// Nothing to prepare
// Act
render(SettingsSidebar, {
context: new Map([['drawerStore', drawerStore]])
});
render(SettingsSidebar);
// Assert
expect(screen.getByText(enMessages.settings.headline)).toBeInTheDocument();
@ -55,13 +49,10 @@ describe('SettingsSidebar.svelte', () => {
it('should have a profile tile', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const drawerStore = readable([]);
// Nothing to prepare
// Act
render(SettingsSidebar, {
context: new Map([['drawerStore', drawerStore]])
});
render(SettingsSidebar);
// Assert
expect(screen.getByText(enMessages.settings.profile.label)).toBeInTheDocument();
@ -69,13 +60,10 @@ describe('SettingsSidebar.svelte', () => {
it('should have an account tile', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const drawerStore = readable([]);
// Nothing to prepare
// Act
render(SettingsSidebar, {
context: new Map([['drawerStore', drawerStore]])
});
render(SettingsSidebar);
// Assert
expect(screen.getByText(enMessages.settings.account.label)).toBeInTheDocument();
@ -83,13 +71,10 @@ describe('SettingsSidebar.svelte', () => {
it('should have a SSH/GPG keys tile', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const drawerStore = readable([]);
// Nothing to prepare
// Act
render(SettingsSidebar, {
context: new Map([['drawerStore', drawerStore]])
});
render(SettingsSidebar);
// Assert
expect(screen.getByText(enMessages.settings.ssh_gpg_keys.label)).toBeInTheDocument();
@ -97,13 +82,10 @@ describe('SettingsSidebar.svelte', () => {
it('should have an appearance tile', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const drawerStore = readable([]);
// Nothing to prepare
// Act
render(SettingsSidebar, {
context: new Map([['drawerStore', drawerStore]])
});
render(SettingsSidebar);
// Assert
expect(screen.getByText(enMessages.settings.appearance.label)).toBeInTheDocument();
@ -111,15 +93,48 @@ describe('SettingsSidebar.svelte', () => {
it('should have a notifications tile', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const drawerStore = readable([]);
// Nothing to prepare
// Act
render(SettingsSidebar, {
context: new Map([['drawerStore', drawerStore]])
});
render(SettingsSidebar);
// Assert
expect(screen.getByText(enMessages.settings.notifications.label)).toBeInTheDocument();
});
describe('when clicking a tile', () => {
it('should dispatch a switch-settings event when clicking a tile', () => {
// Arrange
const listener = vi.fn();
// Act
const { component } = render(SettingsSidebar);
component.$on('switch-settings', listener);
const profileTile = screen.getByText(enMessages.settings.profile.label);
expect(profileTile).toBeInTheDocument();
profileTile.click();
// Assert
expect(listener).toHaveBeenCalledOnce();
});
it('should pass the clicked tile in the event', () => {
// Arrange
const listener = vi.fn();
// Act
const { component } = render(SettingsSidebar);
component.$on('switch-settings', listener);
const profileTile = screen.getByText(enMessages.settings.profile.label);
expect(profileTile).toBeInTheDocument();
profileTile.click();
// Assert
expect(listener).toHaveBeenCalledWith(
new CustomEvent('switch-settings', {
detail: 'profile'
})
);
});
});
});

View file

@ -109,7 +109,9 @@ describe('MainMenuDetails.svelte', () => {
render(MainMenuDetails);
// Assert
expect(screen.getByText(enMessages.page.profile.menu.details.merge_requests)).toBeInTheDocument();
expect(
screen.getByText(enMessages.page.profile.menu.details.merge_requests)
).toBeInTheDocument();
});
it('should have a people item', () => {

View file

@ -0,0 +1,72 @@
/* Component test for Settings molecule.
* Copyright (C) 2024 André Jaenisch
* SPDX-FileCopyrightText: 2024 André Jaenisch
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/svelte';
import { tick } from 'svelte';
import { init, locale, register } from 'svelte-i18n';
import Settings from '../../../src/lib/components/molecules/Settings.svelte';
import enMessages from '../../../src/lib/i18n/locales/en.json';
describe('Settings.svelte', () => {
beforeEach(() => {
register('en', () => import('../../../src/lib/i18n/locales/en.json'));
init({ fallbackLocale: 'en', initialLocale: 'en' });
locale.set('en');
});
it('should mount', () => {
// Arrange
// Nothing to prepare
// Act
const { container } = render(Settings);
// Assert
expect(container).toBeTruthy();
});
it('should have an profile section', () => {
// Arrange
// Nothing to prepare
// Act
render(Settings);
// Assert
// There are multiple nodes with that name and I haven't determined the appropriate heading level
expect(screen.getAllByText(enMessages.settings.profile.headline)).not.toHaveLength(0);
});
describe('when switching the active setting', () => {
it('should not have an profile section', async () => {
// Arrange
// Nothing to prepare
// Act
render(Settings);
const profileTileAndSection = screen.getAllByText(
enMessages.settings.profile.headline
).length;
const notificationTile = screen.getByText(enMessages.settings.notifications.label);
notificationTile.click();
// Wait for reactivity
await tick();
// Assert
expect(screen.queryAllByText(enMessages.settings.profile.headline).length).toBeLessThan(
profileTileAndSection
);
});
});
});

View file

@ -10,9 +10,12 @@
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
import { storePopup } from '@skeletonlabs/skeleton';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/svelte';
import { init, locale, register } from 'svelte-i18n';
import { readable } from 'svelte/store';
import Profile from '../../../src/lib/components/pages/Profile.svelte';
import enMessages from '../../../src/lib/i18n/locales/en.json';
@ -26,12 +29,24 @@ describe('Profile.svelte', () => {
it('should mount', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
const { container } = render(Profile, { data });
const { container } = render(Profile, { context, props });
// Assert
expect(container).toBeTruthy();
@ -39,12 +54,24 @@ describe('Profile.svelte', () => {
it('should have a h1', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const h1 = screen.getByRole('heading', { level: 1 });
// Assert
@ -54,12 +81,24 @@ describe('Profile.svelte', () => {
it('should have a block button', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const button = screen.getByRole('button', { name: 'block' });
// Assert
@ -68,12 +107,24 @@ describe('Profile.svelte', () => {
it('should have a report button', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const button = screen.getByRole('button', { name: 'report' });
// Assert
@ -83,12 +134,24 @@ describe('Profile.svelte', () => {
// FIXME: Reenable once emoji was replaced with svelte-octicon
it.skip('should have a like button', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const button = screen.getByRole('button', { name: enMessages.page.profile.activities.like });
// Assert
@ -98,12 +161,24 @@ describe('Profile.svelte', () => {
describe('projects', () => {
it('should have a h2', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const h2 = screen.getByRole('heading', {
level: 2,
name: enMessages.page.profile.projects.heading
@ -115,12 +190,24 @@ describe('Profile.svelte', () => {
it('should add a project', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const a = screen.getByRole('link', { name: 'Add a project' });
// Assert
@ -129,12 +216,24 @@ describe('Profile.svelte', () => {
it('should import a project', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const a = screen.getByRole('link', { name: 'import a project' });
// Assert
@ -146,12 +245,24 @@ describe('Profile.svelte', () => {
describe('history', () => {
it('should have a h2', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const h2 = screen.getByRole('heading', {
level: 2,
name: enMessages.page.profile.history.heading
@ -163,16 +274,28 @@ describe('Profile.svelte', () => {
it('should have an entry for created account', () => {
// Arrange
const data = {
user: {
created_with: 'Anvil',
instance: 'domain.example',
username: 'jane_doe'
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {
created_with: 'Anvil',
instance: 'domain.example',
username: 'jane_doe'
}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const li = screen.getByRole('listitem');
// Assert
@ -183,9 +306,9 @@ describe('Profile.svelte', () => {
expect(li).toHaveTextContent(
new RegExp(
enMessages.page.profile.history.activities.setup.description
.replace('{username}', data.user.username)
.replace('{instance}', data.user.instance)
.replace('{created_with}', data.user.created_with)
.replace('{username}', props.data.user.username)
.replace('{instance}', props.data.user.instance)
.replace('{created_with}', props.data.user.created_with)
)
);
});

View file

@ -10,9 +10,12 @@
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
import { storePopup } from '@skeletonlabs/skeleton';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/svelte';
import { init, locale, register } from 'svelte-i18n';
import { readable } from 'svelte/store';
import Profile from '../../../src/lib/components/templates/Profile.svelte';
import enMessages from '../../../src/lib/i18n/locales/en.json';
@ -26,12 +29,24 @@ describe('Profile.svelte', () => {
it('should mount', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
const { container } = render(Profile, { data });
const { container } = render(Profile, { context, props });
// Assert
expect(container).toBeTruthy();
@ -39,12 +54,24 @@ describe('Profile.svelte', () => {
it('should have a h1', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const h1 = screen.getByRole('heading', { level: 1 });
// Assert
@ -54,12 +81,24 @@ describe('Profile.svelte', () => {
it('should have a block button', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const button = screen.getByRole('button', { name: 'block' });
// Assert
@ -68,12 +107,24 @@ describe('Profile.svelte', () => {
it('should have a report button', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const button = screen.getByRole('button', { name: 'report' });
// Assert
@ -83,12 +134,24 @@ describe('Profile.svelte', () => {
// FIXME: Reenable once emoji was replaced with svelte-octicon
it.skip('should have a like button', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const button = screen.getByRole('button', { name: enMessages.page.profile.activities.like });
// Assert
@ -98,12 +161,24 @@ describe('Profile.svelte', () => {
describe('projects', () => {
it('should have a h2', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const h2 = screen.getByRole('heading', {
level: 2,
name: enMessages.page.profile.projects.heading
@ -115,12 +190,24 @@ describe('Profile.svelte', () => {
it('should add a project', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const a = screen.getByRole('link', { name: 'Add a project' });
// Assert
@ -129,12 +216,24 @@ describe('Profile.svelte', () => {
it('should import a project', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const a = screen.getByRole('link', { name: 'import a project' });
// Assert
@ -146,12 +245,24 @@ describe('Profile.svelte', () => {
describe('history', () => {
it('should have a h2', () => {
// Arrange
const data = {
user: {}
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const h2 = screen.getByRole('heading', {
level: 2,
name: enMessages.page.profile.history.heading
@ -163,16 +274,28 @@ describe('Profile.svelte', () => {
it('should have an entry for created account', () => {
// Arrange
const data = {
user: {
created_with: 'Anvil',
instance: 'domain.example',
username: 'jane_doe'
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
const props = {
data: {
user: {
created_with: 'Anvil',
instance: 'domain.example',
username: 'jane_doe'
}
}
};
const context = new Map([
['modalStore', modalStore],
['storePopup', storePopup]
]);
// Act
render(Profile, { data });
render(Profile, { context, props });
const li = screen.getByRole('listitem');
// Assert
@ -183,9 +306,9 @@ describe('Profile.svelte', () => {
expect(li).toHaveTextContent(
new RegExp(
enMessages.page.profile.history.activities.setup.description
.replace('{username}', data.user.username)
.replace('{instance}', data.user.instance)
.replace('{created_with}', data.user.created_with)
.replace('{username}', props.data.user.username)
.replace('{instance}', props.data.user.instance)
.replace('{created_with}', props.data.user.created_with)
)
);
});